関数がキッチリしていると言える条件は、以下のような仕様が細部までしっかりと明確になっていることです。
- 使用目的が明確
- 定義域(受け付けられる入力の範囲)が明確
- 値域(得られる出力やエラー値の範囲)が明確
- 制限事項が明確
細かいところまで漏れなく仕様を決めて、「キッチリした関数」を実装するためのガイドラインは、「コメントを書く」ことです。関数のコーディングを開始する前に、その仕様についてコメントしましょう。
コーディングしながら書くのではなく、コーディングしてから書くのでもなく、コーディングする前に書くのです!
使用目的
関数の使用目的を書くのは、何を作ったのかを忘れないようにするためだけではありません。
明確に言葉で表現することによって、これから作ろうとしているものを自分自身に言い聞かせるためでもあります。実際に言葉で書いてみると、何を作るか分かっていたつもりでも、実は考えがまとまっていないことに気付くかもしれません。
そういう意味でも、関数を一つ書く前に、その目的についてコメントするのはおすすめの習慣です。
ちなみに、関数の使用目的は「作る人」ではなく、「使う人」の視点で書けるとベターです。どの関数も作るのは1回きりなので、使うためにコメントを読む回数のほうが多くなるからです。
定義域
例えば次の関数(文字列の長さを求める標準関数ですね)は、引数に0(NULL
)を指定することができません。
size_t strlen(const char *s);
このように、関数には入力できる値とできない値があるのが普通です。想定外の値を入力すれば、プログラムはクラッシュしてしまうかもしれません。
では、このことがコメントされていなかったら何が起こるでしょうか。
例えば、この関数のある利用者が「0を入力するとクラッシュするぞ」という「発見」をしたとしましょう。このとき、コメントに何も書かれていなければ、「これは関数のバグだろう」と判断するかもしれません。
その結果、クラッシュしないように関数の実装を手直ししたとしたら、これは「デバッグ」でしょうか?いいえ、この行為は紛れもなく仕様変更です!
もしも関数の定義域がコメントされていれば、どちらが間違っているのかすぐに分かったでしょう。このようなコメントには、安易な仕様変更を抑止する効果があるのです。
ちなみに、実際に関数をコーディングするときは、範囲外の入力を受け取ったらassertすればいいですね。
assert()
については別のトピックで詳しく説明します。
値域
以下の2点について、しっかり書いておきましょう。
- 正常な値:関数が正常に終了した場合に返却する値の範囲
- エラー値:関数がエラーになった場合に返却する値の範囲
エラー値は、関数を呼び出したあとの判定に必要な情報です。ファイル操作やメモリー確保など、ある程度の確率で発生するエラーの確実なチェックを促すためにも、ぜひコメントしておきましょう。
制限事項
例えば、文字列の文字数をカウントする関数には、次のような制約があるかもしれません。
- 「この関数は、文字列がUTF-8でエンコードされているものとして扱います」
こうした記述があれば、関数が不可解な動作をしたときに、頭を悩ませる時間を節約できるでしょう。
関数を作る前に仕様をコメントするのは、正直に言えば面倒臭い作業です。でも、それ以上に効果が期待できます。コメントを書くのに時間がかかっても、トータルでは時間の節約になるのではないでしょうか。
次のトピックでは、あとで関数を使う人(または使うとき)のために何をやっておくべきか検討しましょう。