「変数の値を推測しやすくする」と読みやすいプログラムになる
6/20/2024
良いコードとは「他の人が読みやすいコード」であると広く言われています。コードは何度も読まれるので、他人(未来の自分も含む)が読んで理解しやすく書くべきだ、という理由です。業務でコードを書いたりレビューしたりしていて、この原則は全くその通りだと思います。
しかし、コードを書いているその時に、「他の人」の目線に立って「最短時間で理解できる」かを判断するのはなかなか難しいです。文章でも何でもそうだが、理解しやすいかは数日経ってから見返さないと分からないですよね。私は4-5年コーディングを経験してようやく経験的にどういうコードが「他の人が最短時間で理解できる」か分かってきました。それまではこの原則だけでは書き方を判断できず、根本的で重要なところを直さないまま細かい変数名にこだわったりなどして、あまり良いコードが書けてなかったなと思います。
「もう少し具体的な原則が欲しいな」と考えてコードを書き続けて、最近になって一つ良い原則に辿り着きました。
それは「変数の値を推測しやすくする」という原則です。実務で趣味で2年間コードを書いて、それ以上にたくさん読んで、今感じる最重要原則がこれです。...正直あまり具体的になっていない気もするのですが、この原則をベースにすると、良いコードを書くための色々な方法論について、重要度や優先度、使い所がよく理解できました。
どういうことかイメージを掴んでもらうために、まずは具体的な方法を紹介します。
具体例
具体例を通して、方法論の重要度や優先度がどう整理できるのか、説明します。
「変数の値を推測しやすくする」一番わかりやすい方法は「変数に適切な名前をつける」です。その変数にどんな値が入るかを変数名で説明すれば、読み手がその変数の名前を推測しやすくなりますね。
ただし、何でもかんでもわかりやすい名前をつければ良いというものでもないです。これはどちらが読みやすいでしょうか?
この場合、map内のコールバックの引数に適切な名前が付いていない後者の方が、私は読みやすいです。コールバックの引数名はこんなに詳細でなくても良い気がします。長くなることで、逆に引数に対してどういう操作をしているかがわかりにくいです。
この名前を正確にすべきか短くすべきか問題は、「変数の値を推測しやすくする」という原則に基づくと判断できます。
つまり、「基本は正確な名前をつけるべきだが、その変数の値を代入してすぐ利用するなら、値を追えるから名前はなんでも良い」ということです。なぜ先の例では変数名が適当で良いかというと、変数のスコープがごく狭いからです。pが何かはmapされている配列の名前から分かりますし、そのxが何かの情報を忘れる前に3行でpは役目を終えます。つまりこの場合、変数の名前が何であっても読めるので、名前を短くしてロジックを読みやすくした方が良いということです。
この例のように、「変数の値を推測しやすくする」という判断基準に沿うと、良いコードを書くテクニックをいつ・どう使えば良いかが分かります。
では、なぜ「変数の値を推測しやすくする」と「読みやすいコード」になるのでしょうか?
なぜ「変数の値を推測しやすくする」ことが重要なのか
「変数の値を推測しやすくする」と「読みやすいコード」になるのは、「コードを読む」という行為が、「使われている変数の値を頭の中で追跡する」こととほぼイコールだからです。例えば関数のコードを読むというのは、どういう引数を渡すのかと、どういう値が返されるのかを把握することです。返り値が何かを把握するには、関数の中で定義される(引数を含む)全ての変数の値を頭の中で追跡し、返り値となる変数がどれなのか、その値が最終的に何になるのかを把握する必要があります。このように、関数を読むというのは関数内の変数値を追跡することとほぼイコールです。他のコードを読む時も同様でしょう。
したがって、「頭の中で変数の値を推測しやすい」イコール「読みやすい」になるのです。
それでは、「変数の値を推測しやすくする」ための具体的な方法を見ていきましょう。内容自体はよくあるコーディングの方法論ですが、「変数の値を推測しやすくする」という原則に基づいて整理することで、正しく使えるようになります。
実践
変数の値を推測しやすくするには、まず可能な限り「推測すべき変数を減らす」ようにし、残った変数に対して「変数の値を分かりやすくする」というアプローチを取ります。
その方法を順にご紹介します。
変数の数を減らす
変数がなくなれば、その変数の値を推測する必要がなくなります。原点にして頂点の方法です。まずはこれを考えます。どんなサイズ・用途のプログラムに対しても常に有効です。
ソースコードをなるべく減らして、余計な変数やロジックを削除しましょう。余計なものを実装しないように、シンプルに作りましょう。同じロジックは共通化して、1回だけコーディングするようにします。JavaScriptなら
map
など、Pythonならリスト内包表記など、変数を減らせる記法を覚えて使いましょう。どうしても必要な変数だけを残したら、次の方法を使います。
変数の利用範囲(スコープ)を狭める
プログラム全体での変数の数が減らせなくても、変数の利用範囲をなるべく狭めれば、ある部分のコードを読むときに予測すべき変数の値の数は少なくなります。
スコープを狭めるには、基本的にはコードを関数に切り出します。関数の中で定義した変数は関数の外からは参照できないので、読み手は他の関数の中の変数を全て無視できる。もちろん関数ではなくクラスのメソッドでも同じ効果を得られる。
スコープの広い変数は減らすべきで、ごく狭い変数はたくさんあっても良いです。10行程度の関数なら大体全て頭に入れられるので、その中でいくら変数を定義されても理解できるでしょう。
関数へ切り出すときに少し工夫をすれば、さらに変数の値を推測しやすくなります。
引数と返り値の型定義とコメントで、関数の挙動を説明する
関数に使い方をコメントすれば、関数の中にある変数の値を推測する必要がなくなります。関数の実装を読む必要自体がなくなるからです。
多くの言語で、使い方を書くための専用記法があるので、それを使いましょう。javascriptならJSDoc、pythonならdocstringなどです。
ただし、関数の名前から自明なことはコメントしないようにしましょう。コメントはなるべく少ない方が良いです。今後関数の中身を修正したときに、コメントを修正し忘れて、コメントと実装が食い違う最悪の事態になったりするからです。例えばこの関数に対して
ユーザが有効かどうかを判定する
というコメントは不要です。
今回の例では、「アプリの認証済みユーザーとして」有効かどうかの判定という付加情報をコメントに書いています。もう一つ、推測すべき変数を減らす関数の実装方法として重要な点があります。
関数の出力を引数以外の変数に依存させない
引数の値が同じなら、関数は必ず同じ値を返すように実装しましょう。このような関数をステートレス関数と言います。
引数以外の状態、例えば関数の外で定義した変数やDBの値などによって結果が変わると、その値を推測しながら関数を使う必要が生じます。このような関数外の変数の値は特に推測しにくいので、極力無視できるようにしたいです。なぜ推測しにくいかというよ、関数の周りで定義・変更されないことが多いからです。例えば、DBの値はコードではなくアプリの実行環境によって変わりますね。
推測すべき変数を減らす方法はこのくらいです。次は変数の値を推測しやすくする方法をご紹介します。
命名と型定義で変数の値を説明する
関数や変数に値を説明する名前と型定義があると、格段に変数の値を推測しやすくなります。
特に型定義はメリットが分かるまでサボりがちですが、非常に有効です。VSCode等のIDEでは、変数にカーソルを当てるだけで型を教えてくれるので、値の推測ミスを大幅に減らせます。加えて、型定義があれば、IDEが型に沿った入力補完をしてくれるので、コードを書くスピードも上がります。プログラミング始めたての頃は型定義書くのが面倒そう...と思ってましたが、今は型定義されてないコードは読みにくすぎて & 書きにくすぎて拒絶反応が起きますね。変数にホバーしてanyとか出てくると😠って感じになります。
命名については前節の具体例でも示した通りです。個別の命名テクニックはここでは紹介しません。プロジェクトでルールが決まってる場合もありますし、色々と分かりやすくするテクニックがありますので。本などを読んで調べてみてください。
変数の値を変更しない
「変数の値がコロコロ書き換わるのは気持ち悪い、不安だ」という感覚を持ち、変数の値を変更する操作をなるべく避けましょう。変数への再代入、リストへの要素追加、オブジェクトの状態変更など、変数の値を変える操作があればあるほど、その変数値は推測しにくくなります。値が変わらない変数は、定義箇所だけ読めば値が分かるので、とても推測しやすいです。エディタでは定義箇所に飛べる機能があるので、ワンクリックで値が分かります。
言語仕様として値を変更できない (イミュータブルな) データ型が存在することも多いですので、なるべく使いましょう。例えば、Pythonではタプルがイミュータブルなリストです。rustは変数の値変更がかなり制限されている言語です。rustのドキュメントを少し読んでみると、変数の値を変更することにどういう問題があり、どう避けられているか、勉強になります。
ただし、全ての変数値を変更しないようにするのは難しいですし、逆に読みにくくなることもあります。例えばJavaScriptでは、変数の値の変更を制限する機能が少ないため、変数値を変更できないような実装は難しいです。
可読性や言語使用上の制限によって、変数の値を変更する必要がある場合は、次のテクニックを使います。
変数の値の変更を、コードを読む人に見える場所で行う
再代入をする場合は、読み手が気付きやすい場所で行いましょう。変数の定義箇所と遠く離れた箇所で再代入したり、その変数を引数に渡した関数の中でその値を変えるようなことをすると、読み手はその変数の値を推測するのに苦労します。特に関数の引数を変更するのは避けましょう。
やりがちなのが、クラスの状態をそのメソッドの中でコロコロ変えてしまうことです。クラスの状態を変えるメソッドはなるべく少なくし、かつなるべく状態変更専用のメソッドにしましょう。また、メソッドに状態が変わりそうな名前(
updateXXXState
など)をつけましょう。
逆にget
のようなデータを読み取るだけに見えるメソッドの中では、状態を変えないようにしましょう。広く使えて効果が高そうな方法はこのくらいです。
ただし、大事なのはこの方法を覚えておくことよりも、「変数の値を推測しやすくする」という基準でコーディングする意識を持つことです。この基準でこれらの方法のどれを適用するか判断できるようになりますし、他の方法を自分で考え出すこともできるようになります。
コーディングに限らないことですが、技術や方法をただ覚えておくだけでは、既知の問題にしか適用できません。
未知の問題にどの技術や方法を使えば良いかは、「何のためにその技術や方法を使うのか?」を理解しておくことが不可欠です。