自分が書いたコードは自分で守る

チームでソフトウェア開発を進めていると、他の開発者が書いたコードを自分が呼び出したり、逆に自分の書いたコードが他の開発者のコードから呼び出されることがある。その境界はクラスやライブラリだったりする訳だが、何らかのデータのやり取りが発生することに変わりはない。問題はそのデータが妥当なものか否かということだ。

例えば、仕様書に「引数にnullが指定された場合、例外を投げる」と記載しても、実際のコードでは意図せぬ形でnullが指定されて呼び出されることは全然珍しくない。もちろん、所詮は人間が書くコードだからそんなバグが出てくるのは当然のことであり、これ自体は大した問題ではない。問題なのは「そのような異常状態をいかに早く見つけ出すか?」ということだ。中途半端な状態で動作が継続してしまうと症状が拡大し問題発見がかえって遅れるので、見つけ出すのは早いほど望ましい。

だから、各処理の冒頭において、外部から受け取るパラメータ値の妥当性を事前確認することは欠かせない。望ましくない値ならその場で「エラーコードを返す」「例外を投げる」処理が不可欠だし、自分の目の届く開発範囲なら問答無用でassertで落とすことも多い。テストコードで回帰テストを行う場合と同様に、開発者が気がついていない、或いは忘れてしまった仕様を浮かび上がらせるための措置として、assertによる防御的プログラミングは非常に有効だと思う。

全くの経験則だけど「assertを組み込んで検証しているモジュール」の品質は、そうではないモジュールの品質よりずっと良いことが多い。あるプロジェクトでは「引数チェックをほとんど行っていないモジュールA」が不適切な内部処理によりデタラメな値で「引数チェックを常に行っているモジュールB」を呼び出す状態が頻繁に発生していた。モジュールBがすかさず引数チェックを行いエラーを返していたのでB側はダウンしない一方、Aの方は自分自身が出力するデータの妥当性すら怪しいので品質が悪く、簡単なことで異常終了していた。念のため開発のメトリクスを取って比較したら、両者のバグ密度は明らかな差が生じていた。

決して落ちないモジュールBのタフな堅牢性に感心しつつも、モジュールAの品質の悪さに呆れたので、担当者にassert等を使って防御的な入出力チェックを追加し、モジュールBへ処理を渡す前に自分で自分自身を検証したらどうかとアドバイスしたのだけど、その開発者は防御的プログラミングという概念そのものを知らなかった。言語仕様としてassertは聞いたことがあるものの、それをどのように活用すべきかという知識が無かったらしい。Code Completeを読んで勉強していないのなら、そのような考え方を知らないのは仕方ないかも知れない。

むろん、assertを幾つか入れたところで直ぐに品質は改善しないけれど、そのような小さな工夫の積み重ねが結果的に品質へ影響するものなのだ。「仕様通りの正しい値が使われるはず」という思い込みは危険だ。過度に悲観的になる必要は無いけれど、物事を疑ってかかる姿勢は必要だと思う。

・開発者の多くが言葉としての「防御的プログラミング」を聞いたこともない。
・APIの仕様書には正常な場合の処理内容しか書かれておらず、不正なパラメータが渡された場合の振る舞いが何も記述されていない。
・結果として、APIの実装コードでは不正パラメータの検査されていない。あるいは、検査はしているが例外をスローすることなく、単純にリターンしている。

防御的プログラミング(2):柴田 芳樹 (Yoshiki Shibata):So-netブログ



関連