見た目、設計ときたら、次は名前付けだね。変数や関数の名前付けで困ったこと、あるんじゃないかな?
えっ? いつも適当に付けてるから、悩んだことないですよ。名前って、そんなに大事なんですか?
おいおい……そういうのは良くないな。プログラミングの半分は設計で残り半分は名前付け、と言ってもいいくらいに重要なことなんだよ?
そ、そんなに!?
名前が付いていない条件に名前を付けたり、それぞれの機能によりわかりやすい名前を付けたりするのは、とても大事なことだよ。それに、良い名前付けができるようになれば、自然と良い設計もできるようになってくるね
そう言われると、聞きたくなってきました……!
複雑な条件には名前を付けよう
if文などで条件式を書くとき、いくつもの条件を組み合わせた複雑な条件式が必要になることがあります。しかし長すぎる条件式は、あとで読むと、何を判別するための条件であったのか、書いた本人ですらわからなくなることがよくあります。このような場面では、条件式に理解しやすい名前を付けるとよいでしょう。
条件式を別の関数に分ける
たとえばJavaScriptにおいて、ファイル名の拡張子からJPEGファイルあるいはPNGファイルであると判断できたものだけを処理したい場面を考えます。JPEGファイルと判断する拡張子は次の4種類とします。
またPNGファイルと判断する拡張子は次の2種類とします。
これを素直に書くとリスト1のようになります。しかし、これでは条件文が長すぎて読みづらいです。
そこでリスト2のように、JPEGファイルの判別をする個所はisJPEG
、PNGファイルの判別をする個所はisPNG
と名前を付けて、それぞれを関数として抽出します。このようにすると、リスト3のように条件文がすっきりします。また、isJPEG()
やisPNG()
の詳細は知らなくても、JPEGファイルやPNGファイルに処理をしようとしている関数だということもわかります。
変数を使用する
真偽値を返す関数であっても、関数名からは戻り値の意味がわかりにくかったり、関数の使用方法が複雑すぎたりといった理由で、そのままでは条件式としての使用に適さないこともあります。このような場合は、その関数の戻り値を一度変数に代入し、簡潔かつ明確な名前を付けることで、コードが読みやすくなります。
例として、GLibというC言語用のユーティリティライブラリに含まれるg_file_get_contents()
という関数を見てみます。この関数は指定したファイルの中身を取得するための関数で、取得に成功した場合には真を、失敗した場合には偽を返します。この関数を使用してファイルの取得を試み、失敗した場合はエラー処理を行うという実装を作る場面を考えてみましょう。g_file_get_contents()
は真偽値が返される仕様であるため、リスト4のように、そのままif文の条件式として使用したくなります。
しかしg_file_get_contents()
は4つも引数を取るため、条件式が少し込み入った記述になり、どこからどこまでが関数の引数なのか、どの範囲をif文の条件とみなせばよいのかが、ぱっと見ではわかりにくくなっています。
また、g_file_get_contents()
の仕様を知らない人がこのコードをデバッグしようとしたとき、このif文による分岐が本当に正しい処理であるのかどうかをすぐには把握できないかもしれません。世の中には、今回の例のように成否を真偽値で表現する関数もあれば、成功したときには0を、失敗した場合には0以外のエラーコードを返すといった仕様の関数もあるからです。
さらに言えば、そもそも関数の戻り値が成否を表しているとも限りません。関数名からは、戻り値としてファイルの内容を返しそうな印象も受けます。この関数がこれらのどのパターンであるかは、この名前からは正確に判断することはできません。
このような場合には、リスト5のようにg_file_get_contents()
の戻り値を一度succeeded
という名前の変数に代入するとよいでしょう。この関数の仕様を知らない人にとっても、戻り値が真の場合に成功であるという動作がわかりやすくなりますし、if文の条件式もシンプルになってコードが読みやすくなります。
条件には真偽型であることがわかる名前を付ける
先ほどの例でも見たように、条件式を関数や変数にして名前を付ける際は、それが真偽型であること、そして真と偽がそれぞれ何を表すのかが明確になるような名前にするとよいでしょう。たとえば、次のような名前はYes/Noを判断しやすいためよく使われます。
- available(利用できる)、downloadable(ダウンロードできる)のような具体的な状態を表す形容詞
- connected(接続された)、succeeded(成功した)、failed(失敗した)のような具体的な動詞の受動態
- is、has、can、shouldなどの接頭辞を伴ったhasAttributeやisPNGのようなフレーズ
Rubyのメソッド名においては、真偽値を返す場合はクエスチョンマークを接尾辞として加えてconnected?
のように名付けるという慣習もあります。
たとえば、筆者の足永が開発に関わるHatoholというソフトウェアにおいて、ほかのホストとの接続状態を判別するための関数がgetConnectedStatus()
という名前であったことがありました。この関数は真偽値を返す関数で、真の場合には接続中、偽の場合は切断中を意味するというものでした(リスト6)。
しかし、getConnectedStatus()
という名前からは、単に「接続中」「切断中」という状態以外にも、「認証中」など、真偽値だけでは表現しきれない別の接続状態も取得できそうな印象を受けてしまいます。
このときはリスト7のように、先頭に真偽型であることを示すis
を付け、また意味を解釈するうえでなくても支障のないStatus
を外して、isConnected()
という関数名に変更しました。これは状態を取得する関数での話ですが、状態そのものを表す変数に名前を付ける場面であれば、connected
という1単語にしてもよいでしょう。
適切な長さの名前を付けよう
プログラムを書いていると、条件式だけでなく、変数名、関数名、クラス名、モジュール名、ファイル名など、何かに名前を付けるという機会が非常に多くあります。適切な名前が付けられているコードは読みやすく、メンテナンス性や拡張性が高くなります。しかし、何をもって適切な名前かどうかを判断すればよいのでしょうか?
名前は、「それが何であるか」をきちんと表していることが最も重要です。しかし、いくら正確でも「大宇宙銀河系内太陽系第3惑星地球における……」のように長すぎる名前は読み書きしにくいし、似たような名前が並ぶと区別もしにくいでしょう。短すぎず長すぎもしない適切な長さは、適切な名前の必要条件です。
とはいえ、機械的に名前の長さを何文字以上/何文字以下の範囲に収めればよいというものでもありません。適切な名前を付けたら結果的に適切な長さになっていた、というのが理想です[1]。適切な長さの適切な名前を付けるための考えかたとはどういうものでしょうか。
短すぎる名前、長すぎる名前
良いコードの書きかたについて語られる場面では、1文字変数は忌避されることが多いです。また、1文字でないにしても極端な省略がなされることもあります。そのようなコードは、一見しても何のためのコードなのかわかりにくいです。リスト8を見て、前提知識なしにいったい何のコードなのか判別できるでしょうか?
気づいた人もいるかもしれませんが、これは前章で例として挙げたアドオンtbforce-auth-at-startupにおける、「認証が必要な受信サーバの一覧を得る」処理です。そう言われてからコードを読んでみると、「ss
って何だ? 代入文を調べると……ああ、サーバの配列を表してるのか」「getSs
って何だ? ……ああ、サーバの一覧を取得するメソッドということか」と、なんとなく意味を読み取れるのではないでしょうか。しかし、すべてのコードがこんな調子では、読み進めるたびにいちいちつまずいてしまって、ストレスを感じてしまいますね。
では、変数や関数の名前に十分な量の情報が含まれていさえすれば問題ないのかというと、そうとも限りません。情報量の多すぎる名前、つまり長すぎる名前にもデメリットはあります。リスト9は、それぞれの変数が何を表すものであるかがわかるようになるべく多く情報を盛り込んだ例ですが、これをすんなり読めるでしょうか?
余白が少なくて、端的に言うと非常に「黒い」コードです。文字数が多いと単純に読むのにも時間がかかりますし、このような画面では自分が今どこを読んでいるのかを見失ってしまいがちです。
文脈に基づいて名前を付けよう
名前が短すぎたり長すぎたりするのは、文脈(コンテキスト)を適切に設定できていないからです。文脈をきちんと設定することによって、必要な情報を盛り込みつつも無駄な情報が削ぎ落とされた、1~2単語程度の適切な長さの名前を付けられます。
文脈とは、そのコードにたどり着くまでの間に何があったのかを説明する一連の背景事情のことです。リスト8、9のコードであれば、「メールクライアントのMozilla Thunderbird用の、起動直後に認証を求めるようにするアドオンにおける、認証処理モジュールの、認証が必要な受信サーバの一覧を得るためのメソッド」という文脈で書かれたコードだ、と言うことができます。
文脈の中ですでに説明されていることまでもが名前に含まれていると、冗長で長すぎる名前ということになります。リスト9でいえば、「Thunderbird用である」「認証のためのコードである」といったことは文脈から自明なので、変数名や関数名には含めなくてもよいでしょう。メソッドの戻り値はサーバの一覧ですから、順当に考えてArray
クラスのインスタンスであることは自明ですし、個々のサーバを表すものは構造化されたデータなので、Object
クラスのインスタンスであることも自明です。また、localization
やauthentication
のような長い単語の中には、文脈によってはフルスペルでは書かずにl10n
やauth
のように省略することのほうが多い単語もあります。
その一方で、「候補になり得るすべてのサーバ」と「見つかった認証が必要なサーバ」とは区別したいので、両者を見分けるためのフレーズをallServers
とfoundServers
のように残してよいでしょう。このように、無理に1単語にすることにこだわらないで、場合に応じて2単語や3単語を選択するのもポイントです。1単語にすることにばかりこだわってしまうと、ネイティブスピーカーでも辞書でしか見たことがないようなマイナーな単語ばかりが並ぶ、初見で意味のわからないコードになってしまいます。
文脈に基づいて名前を付けなおした例がリスト10です。過不足なく情報が盛り込まれているため、最初の2つの例に比べてずっと読みやすくなっているはずです。
文脈上明らかなら、1文字変数も使ってよい
文脈の中に説明が含まれていないにもかかわらず、それが何であるのかという説明が省略されていると、短すぎる名前ということになります。1文字変数が批判されるのは、まさにそれが理由です。1文字だけでは、それが何であるのかを説明するのは困難です。しかし、1文字だけでもその対象のことを正確に説明できるならば、1文字変数を使ってよい場面です。Array
クラスのsort
メソッドの比較関数における仮引数名などは、その代表でしょう。
リスト11は、サーバの配列をホスト名でソートする例です。このような比較関数は対象となる2つの要素を比較するためだけにあり、各要素は一般的に同じ種類のものであることを考えると、aServer
やbServer
のように無理にそれ単体で意味のある名前を付けるのはかえって冗長です。
forループにおけるカウンタ変数も、「forループのカウンタ」という文脈では、iの1文字だけで十分にその意味を表現できます。むしろ、iという1文字変数はforループのカウンタ変数に使うことが極めて多いので、下手にきちんとした単語で名付けるよりも文脈がよりはっきりするという見かたもできます。
このように、文脈上適切な名前を付けるということが最も重要です。それによっておのずと、変数名が適切な長さになります。
良い名前付けと良い設計は表裏一体なんだ。良い設計なら自然と良い名前を付けられるし、問題点を整理しきれていない設計だと良い名前を付けられない。良い名前を付けようと思ったら、名付ける対象が良い設計になっていないといけないんだ。適切な名前を付けやすいかどうかは、設計の良し悪しを測る一つのバロメーターだと言えるね
うーん、でもどうしても3単語や4単語の長い名前になっちゃうんですよ……
そういうのは登場人物が足りないんだ
ね。「AがBを使ってCをする」みたいに1つのメソッドで一気にやるんじゃなく、「BがCをする」「AはBに指示を出すだけ」みたいに処理を細かい単位に分けると、きっと良い名前を付けやすくなるよ(図1)
なるほど! 名前付けに困ったときは、設計を見なおす良い機会なんですね