メソッドには「事前条件」「事後条件」「不変条件」がある。
メソッドは、メソッドを実行する前に「メソッドが期待している状態」になっていなければならず、事前条件を満たさないと「メソッドが期待している結果」を得られず、メソッドの呼び出し前と呼出し後で「メソッドが変化しないと期待しているものは変化しない」のである。
それらを組み込んでプログラミングする事を「契約によるプログラミング、または契約による設計(Design by Contract)」と言う。プログラミング言語によっては、言語レベルでこの3つ全てを表せるものもある。Eiffel 等は特に有名で、D 言語ではかなり強力に書ける(どちらも詳しく知らないが)。
C や C++ 等では「アサーション」で事前条件を定義し、契約プログラミングを実現する事が多い。
C# や VB.NET ならば、例外で事前条件を検査する。Java でも通常は例外だと思うが、アサーションを使うという話しも聞く。
例えば以下のメソッド。指定した文字列の指定したインデックスの文字のみ大文字にして返すメソッドだ。
public static string UpperByIndex(string target, int index)
{
string u = target.Substring(index, 1).ToUpper();
string result = target.Substring(0, index) + u;
if(index < target.Length - 1)
{
result += target.Substring(index + 1);
}
return result;
}
一見何の問題もないコードだ。はっきり言って、現実にはこのようなコードが多い。
しかしプロの仕事としては失格である。
引数の検証をまるでしていない。target が Null ならどうなるか?System.NullReferenceException がスローされて原因を突き止めるのが難しくなる。index が 負の値であったり、target の長さより大きかったらどうなるか?System.ArgumentOutOfRangeException がスローされて原因を突き止めるのが難しくなる。原因を突き止めるには、メソッド内部を詳細に知る必要が出てくる。
このメソッドの事前条件は、target が 1 文字以上存在し、index が 0 以上で、target の長さ - 1 以下である事だ。
public static string UpperByIndex(string target, int index)
{
if(target == null)
{
throw new ArgumentNullException("target");
}
if(target == String.Empty)
{
throw new ArgumentOutOfRangeException("target", target, "target が空文字です。");
}
if(index < 0)
{
throw new ArgumentOutOfRangeException("index", index, "index が負の値です。");
}
if(target.Length - 1 < index)
{
throw new ArgumentOutOfRangeException("index", index, "index が target の長さより大きいです。");
}
string u = target.Substring(index, 1).ToUpper();
string result = target.Substring(0, index) + u;
if(index < target.Length - 1)
{
result += target.Substring(index + 1);
}
return result;
}
事前条件の検証を行うと、間違った使い方をした時に、メソッドを使うクライアントに何が悪いのかが一目瞭然に伝わる。そして間違った使い方をしないようにクライアントに強制できる。
このように、コードは例外をスローしている箇所だらけになるのが普通である。もちろん、完全なコントロールが可能な private メソッドはその限りではない。
例外をスローしている箇所が極端に少ないコードは要注意だ。