関数の長さ

2016年11月30日

私のキャリアの中で、関数の長さはどのくらいであるべきかについて、多くの議論を聞いてきました。これは、より重要な問題、つまり「いつコードを独自の関数にカプセル化すべきか」の代用となるものです。これらのガイドラインの中には、長さに基づいたもの、例えば関数は画面に収まるサイズを超えてはならないというものもありました[1]。再利用に基づいたものもあり、複数回使用されるコードは独自の関数に配置する必要があるが、一度だけ使用されるコードはインラインのままにしておくべきだというものもありました。しかし、私にとって最も理にかなっているのは、**意図と実装の分離**です。コードの断片を見て、それが*何*をしているのかを理解するために労力を費やさなければならない場合は、それを関数に抽出し、その「何」にちなんで関数を命名する必要があります。そうすれば、再びそれを読んだときに、関数の目的がすぐに分かり、ほとんどの場合、関数がどのようにその目的を果たしているか、つまり関数の本体について気にする必要がなくなります。

この原則を受け入れて以来、私は非常に短い関数を書く習慣を身につけました。通常は数行だけです[2]。6行を超えるコードを持つ関数は、私には臭く感じ始めます。1行だけのコードの関数を持つことは珍しくありません[3]。サイズが重要ではないという事実は、ケント・ベックがオリジナルのSmalltalkシステムから示してくれた例によって、私に痛感させられました。当時のSmalltalkは白黒システムで動作していました。テキストやグラフィックを強調表示したい場合は、ビデオを反転させます。Smalltalkのグラフィッククラスには、このための「highlight」というメソッドがあり、その実装は「reverse」メソッドの呼び出しだけでした[4]。メソッドの名前は実装よりも長かったのですが、コードの意図とその実装の間には大きな隔たりがあったため、それは問題ではありませんでした。

関数呼び出しのパフォーマンスコストを心配しているため、短い関数を懸念する人もいます。私が若かった頃は、それが要因となることもありましたが、今では非常にまれです。最適化コンパイラは、より簡単にキャッシュできる短い関数の方がうまく機能することがよくあります。いつものように、パフォーマンス最適化に関する一般的なガイドラインが重要です。後で関数をインライン化することが必要な場合もありますが、多くの場合、小さな関数は速度を上げるための他の方法を示唆しています。リストの`isEmpty`メソッドを持つことに反対する人がいたことを覚えています。一般的なイディオムは`aList.length == 0`を使用することです。しかし、ここでは、コレクションが空かどうかを判断する方が、その長さを決定するよりも高速な場合、意図を明らかにする名前を関数で使用すると、パフォーマンスが向上する可能性があります。

このような小さな関数は、名前が適切な場合にのみ機能するため、命名に十分注意を払う必要があります。これは練習が必要ですが、一度慣れてしまえば、このアプローチはコードを驚くほど自己文書化することができます。大規模な関数は物語のように読むことができ、読者は必要に応じて詳細を掘り下げる関数を選択できます。

謝辞

Brandon Byars、Karthik Krishnan、Kevin Yeung、Luciano Ramalho、Pat Kua、Rebecca Parsons、Serge Gebhardt、Srikanth Venugopalan、Steven Loweは、社内メーリングリストでこの投稿の草稿について議論しました。

Christian Pekelerは、入れ子になった関数が私のサイズに関する観察に適合しないことを思い出させてくれました。

注記

1: または私の最初のプログラミングの仕事では、ラインプリンター用紙2ページ - Fortran IVで約130行

2: 多くの言語では、関数を使用して他の関数を囲むことができます。これは、オブジェクトとしての関数パターンを使用してクラスを実装するなど、スコープを縮小するメカニズムとしてよく使用されます。このような関数は当然はるかに大きくなります。

3: 私の関数の長さ

最近、このウェブサイトを構築するツールチェーンの関数の長さが気になりました。それは主にRubyで、約15 KLOCになります。メソッド本体の長さの累積頻度プロットを次に示します。

ご覧のとおり、そこにはたくさんの小さなメソッドがあります。私のコードベースのメソッドの半分は2行以下です。(ここでの行は、コメント、空白行、`def`行と`end`行を除くものです。)

データを表形式で示します(適切なHTMLテーブルに変換するのが面倒なので)。

              lines.freq lines.cumfreq lines.cumrelfreq
[1,2)          875           875        0.4498715
[2,3)          264          1139        0.5856041
[3,4)          195          1334        0.6858612
[4,5)          120          1454        0.7475578
[5,6)          116          1570        0.8071979
[6,7)           69          1639        0.8426735
[7,8)           75          1714        0.8812339
[8,9)           46          1760        0.9048843
[9,10)          50          1810        0.9305913
[10,15)         98          1908        0.9809769
[15,20)         24          1932        0.9933162
[20,50)         12          1944        0.9994859
      

4: この例は、ケントの優れた著書Smalltalk Best Practice Patternsの「Intention Revealing Message」にあります。