関数の命名において重要なこと
意図を示す
関数にはそれが行う処理を説明する名前をつけるのが基本である。関数の処理内容を名前で示すことで、呼出し側は内容を気にせず結果だけに集中し思考を節約することができる。関数の処理を説明する選択肢は無数にあるが、もっとも重要なのは関数が成し遂げようとすることの意図を簡潔に示すことである。意図に集中し、実現手段への言及を減らすことで、関数の意味をポイントを押さえて伝えることができる。
そのためには関数のアウトプットや成果に注目するのがよい。もし関数が実行されなかったら何が実現できないか、関数のスコープを抜けたときに残るものはなにか、などに注目してそれを説明する名前をつける。戻り値や出力用パラメーターによってアウトプットが表現されるのが最もわかりやすいパターンである。副作用のない関数の場合はこれらの方法で返却されるものがすべてであるので、それらのアウトプットを起点に名前を考えるとよい。副作用のある場合は、メンバ関数(メソッド)がオブジェクトに対する変更をおこなう場合や、グローバル変数に対する変更を行う場合があるが、返却値よりも状態の変更の方が意図がコード上目立ちにくいので名前で明確にするのがよい。特に、複数のプロパティやグローバル変数へ変更を行っている場合は単にそれらの変更を述べるだけでは不十分で、それらを同時に変更するとはどういうことなのかを関数の意図に沿って要約することが重要である。
「処理の意図を記述するようにする」という注意はコードコメントにもおおむね当てはまるが、コメントは意図だけでなく手段の詳細を説明せざるを得ないこともある(複雑なアルゴリズムの理解を助ける場合や、不自然な処理ステップがある場合の注意喚起など)。関数名は呼出し側と関数本体の境界にあるので、これらを意識的に分離して意図のみを明確にすることの価値が高い。
意図を簡潔に示せるように関数を分割・再構成する
意図を適切な長さの名前で説明できない場合はその関数が余分な処理を含んだり、明確な範囲で分割されてない可能性があるので、目的に沿った単位に分割する。例えばprocess_input()
というあいまいな名前の関数をリファクタリングし、処理内容を表す名前がvalidate_input_and_format_and_print_output()
という関数名になった場合はさらにvalidate_input()
とformat()
とprint_output()
という3つの関数に分割できそうだと考えられる。
このように分割することでそれぞれが別々に再利用可能になり得るし、機能追加でフォーマット変更があった際にどの部分が修正点になるかも明確になる。
あいまいにしない、しかし具体的にしすぎない
単語は等しく具体的なわけではなく「あいまいで複数の意味に使える」というものがある。あまり深く考えずにコードを書くとそういう単語を使用しがちだが、関数名という目立つ場所を無駄な文字の羅列で占めてしまうことになるので避けた方がいい。本で言えば章のタイトルのような重要な部分をないがしろにするようなもので、読みやすさが下がってしまう。CODE COMPLETE上巻P.210にこのことに関する分かりやすい説明がある。
意味のない動詞、あいまいな動詞、どっちつかずの動詞を使わない
動詞によってはゴムのように伸び縮みして、他の意味にも解釈できるものがある。たとえば、HandleCalculation(), PerformService(), OutputUser(), ProcessInput(), DealWithOutput()といった名前では、そのルーチンが何をするのか伝わってこない。ルーチンが計算、サービス、ユーザー、入力、出力と関係している程度のことしかわからない。
ルーチンが実行する処理があいまいであるために、名前の動詞までがあいまいになることがある。ルーチンは目的のあいまいさに苦しみ、その症状が意味の薄い名前となって現れる。そのような場合は、そのルーチンと関連するすべてのルーチンの構造を見直して、すべてのルーチンが明確な目的を持ち、それらの目的をきちんと説明する明確な名前を持つようにするのが最善の策である。
具体的である方がいいが、具体的にしすぎて意図を越え、実現手段の詳細に踏み込んでしまうのもよくない。実装に関する記述を名前にするのが絶対ダメなわけではないが、現状の実装への依存性が高くなり、名前の寿命が短くなるのでやるとしても意図的に行うべきだし、可能であれば避けた方がいい。
名前に責任をもつ
コードへ新しい機能が追加されていくと、関数の命名当初の意図とは異なる内容が混ざってくることがある。例えばinitialize
という関数で初期化でないことを行ったり、create
という名前をつけている関数が作成せずに再利用を行ったりと、名前からは微妙に想像できない内容が含まれるように変更されることがある。その場合はロジックの追加に合わせて名前も修正するか、名前を維持してそれに反する処理を関数外へ追い出せないか検討する。
経験的には、新規開発時と機能追加時では関数名を考えるのに使う時間やエネルギーの量が異なり、後者の方がたいていはうまくように思う。機能追加のために既存の関数を眺めると、コード実行のタイミング的にそこへ書くことで変更量も変更後のコードも少なくなる、というケースがあり、ついつい関数名を越える処理を入れたくなる。しかし、読みやすく維持しやすいコードという観点からは「タイミングが同じだったから」「似たような処理をしていたから」ということによる変更には合理性がなく、長期的には意図が混ざることで混乱する害の方が多い。変更量が多くなるように見えても関数を分割し、役割や意図が分かりやすい形で示した方が長期的に簡潔さを保てる。
Discussion