「ジェイル・ハウス・ロック」の攻撃を受けているメンバーでも読めるコードを書きたい
※この記事はジョジョ第6部のネタバレが含まれているため、まだジョジョ第6部を読んでいない方は先に読むことを強くお勧めします。
もしあなたのチームのメンバーが不幸なことに「ジェイル・ハウス・ロック」の攻撃を受けているとしたら、そのメンバーはあなたのコードを理解できるでしょうか?
「ジェイル・ハウス・ロック」はジョジョ第6部に登場するG.D.st刑務所の主任看守、ミュッチャー・ミューラー(ミューミュー)のスタンドです。
脱獄しようとしてそれが潜む鉄格子に触れたものはどんなヤツだろうと3つだけは覚えられる。
この記事では「ジェイル・ハウス・ロック」の攻撃を受けていても読めるコードの書き方を紹介します。将来、「ジェイル・ハウス・ロック」の攻撃を受けてしまうのはあなたかもしれません。身につけておいて損はありません。
「ジェイル・ハウス・ロック」の攻略法とは?
3つの考え方を実践して「ジェイル・ハウス・ロック」に立ち向かいましょう。
- 「水面に映った銃弾」のように認識できるようコードを設計する
- 覚えるべきことを少なくしておく
- 自分を信じる
「水面に映った銃弾」のように認識できるようコードを設計する
3つしか覚えられない状況で、自分に向かって発射された4つ以上の銃弾をひとつひとつ認識してしまうと4つ目の銃弾を認識したときに1つ目の銃弾を忘れて被弾してしまいます。
徐倫は水面に映った複数の銃弾を一枚の写真のように認識することで、この難局を乗り切ることに成功しました。
「水面に映った銃弾」のように覚えておかなければならない情報をまとめることができれば、「ジェイル・ロック・ハウス」の攻撃を受けているメンバーでもコードを理解することができます。
型を活用する
JavaScript or TypeScript を選べる状況ならTypeScriptでコードを書きましょう。
JavaScriptでコードを書いた場合、関数に「どんな型の値を渡せばいいか、どんな値が返されるのか」と疑問に思ったとき、その型を覚えておくための記憶の枠を消費してしまうことになります。これでは「ジェイル・ハウス・ロック」の攻撃を受けているメンバーは永遠にあなたの書いたコードの理解に辿り着けません。
TypeScriptでコードを書けば、エディター上で型情報を知りたい関数にマウスポインターをホバーすることで「水面に映った銃弾」のように関数の呼び出し元と呼び出し先をまとまった情報として理解することができます。
JSDocコメントを活用する
JSDocはJavaScript or TypeScriptのコメントの書き方です。
変数、関数、またはオブジェクトのプロパティーといった宣言されたものの前に/** ... */
を使用したコメントを、JSDocコメント、あるいは単にJSDocと呼びます。
JSDocコメントを使用すると、そのコメントは宣言されたものの説明であるとエディターが解釈します。そして変数、関数、またはオブジェクトのプロパティーが使用されている場所でマウスポインターをホバーするとJSDocコメントを読むことができます。つまり現在読んでいるコードから離れることなく参照先のコメントが読めるため「水面に映った銃弾」のように理解できます。
私はJSDocコメントとして名前と型では伝えきれない情報を書くようにしています。例えば変数やオブジェクトのプロパティーには格納される値の具体例を書いておきます。次の例では型宣言にJSDocコメントを書いています。
type User = {
name: string;
/** 電話番号(e.g. `090-1234-5678`, `09012345678`) */
tel: string;
/** 郵便番号(e.g. `100-0001`, `1000001`) */
postalCode: string;
};
定義した型を変数に適用している様子です。
こちらのリンクでご確認いただけます。
このようなケースで例えばpostalCode
プロパティーに「ハイフンは含まれているのか、あるいは混ざっているのか」を知るために現在読んでいるコードを離れたら、「ジェイル・ロック・ハウス」の攻撃を受けているメンバーが元のコードの場所に戻ってきて続きのコードを読める保証はありません。
名前と型では伝えきれない情報は積極的にJSDocコメントに含めましょう。
関数を切り出す
if
やfor
などのコードブロックによりインデントが深くなっている場合、インデントされた場所によって異なるコンテキストが存在します。1つインデントされていれば1つコンテキストを覚えておかなければなりません。4つインデントされている場合、「ジェイル・ハウス・ロック」の攻撃を受けているメンバーがそのコードの理解に辿り着くことはないでしょう。
インデントされている部分を関数に切り出すことで「水面に映った銃弾」のように覚えておくべコンテキストをひとまとまりにすることができます。
パターンマッチを使う
パターンマッチとは、場合分けと同時に構成要素の取り出しのできる機能のことです。
Rubyを使う人は、よく知っている言語機能だと思います。JavaScript/TypeScriptでは言語機能としては実装されていませんが、ts-pattern
というライブラリーを使うことでパターンマッチを使うことができます。
以下はts-pattern
で紹介されているサンプルコードです。
import { match, P } from 'ts-pattern';
type Data =
| { type: 'text'; content: string }
| { type: 'img'; src: string };
type Result =
| { type: 'ok'; data: Data }
| { type: 'error'; error: Error };
const result: Result = ...;
const html = match(result)
.with({ type: 'error' }, () => <p>Oups! An error occured</p>)
.with({ type: 'ok', data: { type: 'text' } }, (res) => <p>{res.data.content}</p>)
.with({ type: 'ok', data: { type: 'img', src: P.select() } }, (src) => <img src={src} />)
.exhaustive();
私は三項演算子をネストさせたいケースではmatch
を使うようにしています。私のプロジェクトでは必ずESLintの"no-nested-ternary": "error"
にしているので、三項演算子をネストさせたいケースがあれば関数に切り出すかmatch
を使うか、どちらかで対応します。
パターンマッチを使うことで、複数の条件と返すべき値を「水面に映った銃弾」のようにひとまとまりで認識できるように書くことができます。
else
を書くときは注意を払う
if文のブロックが長い行数のときは注意しましょう。if
とelse
が離れてしまうため「ジェイル・ハウス・ロック」の攻撃を受けていれば「このelse
は何ではないケースなのか」を覚えていられないでしょう。
一般的な対策方法の1つはコメントを書くことです。else
でどのようなケースとして処理を書くべきなのかコメントしてから処理内容を記述します。
また、既に紹介している関数の切り出しやパターンマッチを使用することでもelse
のコンテキストを忘れてしまうことを回避することができます。
覚えるべきことを少なくしておく
たくさんのことを覚えなければならない状況では、ミューミューの手のひらで踊らされてしまいます。
技術選定やコードの設計により、日頃から覚えておかなければならないことを覚えておかなくてもいいことに変えておくことで、「ジェイル・ハウス・ロック」の攻撃に備えましょう。
型を活用する
JavaScript or TypeScript を選べる状況ならTypeScriptでコードを書きましょう。
JavaScriptでコードを書いた場合、関数に「どんな型の値を渡せばいいか、どんな値が返されるのか」と疑問に思ったとき、その型を覚えておくための記憶の枠を消費してしまうことになります。これでは「ジェイル・ハウス・ロック」の攻撃を受けているメンバーは永遠にあなたの書いたコードの理解に辿り着けません。
(以前にも同じようなことを書いたような気が...)
TypeScriptでコードを書けばいつでも型情報を把握することができるので、覚えておくべきことを少なくすることができます。
Early Returnで書く
Early Returnは、あるケースで返り値が確定する場合、すぐにreturnする書き方です。この記述の続きのロジックでは「あるケース」について考える必要がありません。文脈次第ではこのテクニックをGuardと呼ぶこともあります。詳しい説明は他の記事に譲りたいと思いますが、Early ReturnにせよGuardにせよ、忘れていいことを明示する書き方だと言えます。
キーワード引数、あるいはオブジェクトを引数に使う
関数が4つ以上の引数を受け取るように設計してしまうと、その関数が使われているコードを読んでいて4つ目の引数の意味を理解したとき、最初の引数の意味を忘れてしまうかもしれません。
RubyやPython、Swift、Kotlinといった言語ではキーワード引数という言語機能があります。キーワード引数を使えば引数の順番を覚える必要はありません。オブジェクトのkey:valueのように引数を書くことができるため、引数がどのように解釈されるのか一目瞭然で理解できます。
JavaScript / TypeScriptでは引数の括弧の内側でオブジェクトを作って渡すことでキーワード引数と同じメリットを享受することができます。もしTypeScriptを使うなら、必須の引数を渡していない場合はエラーがそのことを教えてくれます。
再代入しない
ある変数に再代入するということは、その変数を使用するときにどのような値が入っている複数の可能性を考慮しなくてはならないということです。そしてもちろん、4回以上再代入される可能性のあるコードを読めば、「ジェイル・ハウス・ロック」の攻撃を受けているメンバーは変数宣言時に格納されていた値を覚えていられないということになります。
変数宣言と同時に格納する値を計算するようにコードを構成することで、どのような値が格納されているのか知るためには広い範囲のコードを読まなければいけないという状況が解消されます。
自分を信じる
徐倫はエンポリオの協力により、自分のスタンドにミューミューの顔を映し出すことに成功します。そして記憶を失っている徐倫は次のセリフを口にします。
敵と考えていいのか?おまえは!
過去の自分を信じることができるからこそ、
すべては理解できていない状況においてもミューミューに容赦無いオラオラを浴びせます。
あなたは未来の自分が信じられるコードを書いているでしょうか?
関数型プログラミングのスタイルを身につける
関数型プログラミングで重要なルールは3つあります。(個人の見解です。)
1つは再代入しないことです。他に2つのルールがあります。
- 関数の戻り値は引数のみに依存する
- 関数は戻り値を返す以外の処理をしない
これらのルールを守ることで、「この関数は与えている引数以外に何を参照しているのだろうか?」と考えなくなり、「この関数は引数として渡したオブジェクトの内容を変更したかもしれない」といった可能性を捨てることができます。
関数型プログラミングのスタイルを身につけることで、自分を信じて最短距離でコードを理解することができます。
テストを書く
あなたが「ジェイル・ロック・ハウス」の攻撃を受けていると認識したとき、すべてのコードを読んで理解できる可能性はないと覚悟するでしょう。
攻撃を受けている状況において、テストがないコードとテストがあるコード、どちらを優先してデバックすると効率がよいでしょうか?
テストがあるということは、考えるべきケースが整理されているということです。つまり、テストを書くということは未来の自分に向けて『このコードは十分に考えて実装した』というメッセージとなり、テストが書かれたコードはあなたの敵ではない(バグがない)可能性が非常に高いということです。
さらに、もしテストが書いてあるならばテストケース全体を「水面に映った銃弾」のように認識してコードの振る舞いを理解できます。「ジェイル・ロック・ハウス」の攻撃を受けたメンバーがコードを読むとき、3つしか覚えられないので4つ以上のケースについて考えることはできませんが、テストケースを書いておくことでそのような状況を回避できます。
まとめ
これらのテクニックは、もちろん「ジェイル・ハウス・ロック」の攻撃に対して十分な効果がありますが、他にも新メンバーが戦力となるまでの期間が短縮されたり、機能の変更や追加について高いアジリティーが維持できるといったメリットがあります。
何よりこれらのテクニックが適用されているコードは、最小のストレスで読むことができます。それが私にとって最大のメリットです。ぜひ快適な開発ライフのためにこれらのテクニックを実践してみてください。
Discussion