契約による設計
日本語 | 契約による設計 |
英語 | design by contract |
ふりがな | けいやくによるせっけい |
フリガナ | ケイヤクニヨルセッケイ |
メソッドの引数及び戻り値に対して明確な仕様を決め、それに基づいて実装すること。
メソッドには、様々な仕様が存在する。
「引数にnullは不可」「文字列の長さは30以下」「0は不可」「100が渡された時のみ0、それ以外は100を返す」「エラー時にはnullを返す」等。
これらの仕様は、得てして明確には決められない。メソッドの多くは「丁寧に使ってね、変な使い方するとどうなるかわからないよ」という曖昧な仕様の元に作られている。
しかし、それでは少し使い方を間違えただけでバグが生まれ、また、実装の変化等によってそれまで動いていたものが動かなくなる事もある。
メソッドの仕様をあらかじめ明確にし、それに基づいて実装することで、バグを減らす方法、それが「契約による設計」である。
メソッドの仕様を「契約事項」とし、その契約をメソッドは必ず守ることとする。
契約はまずドキュメンテーションコメントとして記述し、明確に文書化する。
さらに、プログラム上で実際にその「契約事項」を守らせる。
契約事項のチェックの方法のひとつは、assertを用いたアサーションである。
メソッドの実装の最初で、まず引数をチェックし、実装の最後で返す戻り値をチェックする。
これらのチェックで、引数や戻り値が「契約に違反していないか」を調べる。
メソッドの実装及び、メソッドを呼び出す側は、その契約を守ってプログラムを組む。守らなかった場合にはAssertionError例外が投げられるため、そのプログラムは動かないことになる。
もうひとつのチェック方法は、JUnitを用いたテストファーストである。
テストファーストではメソッドに対してあらゆる引数による呼び出しを行い、その戻り値をチェックすることで、メソッドの契約が守られているかをチェックする。
この、契約事項のチェックを行うテストプログラムを先に作成することで、このテストプログラムそのものが「メソッドの契約文書」となり、メソッドが必ず契約を守っていることを約束させることになる。
基本的には、「契約による設計」は「意識の問題」である。
メソッド全てに細かい仕様を設けるということは、その仕様を決める時点で工数が掛かるため、多くの現場では推奨されない。たとえ「契約による設計」を行うことでプログラムのミスが減り、安全なプログラムを作ることができるため、最終的な工数は減ることになったとしても、それは確約されるものではなく、プロジェクト内での説得理由になりにくい。
さらに、アサーションやテストファーストを行うことの実質的な工数の問題や、仕様変更への対応の難しさに加えて、プログラムで「実際にチェックする」ことへの抵抗もある。
「契約による設計」は「性悪説」に基づいた思想である。「契約による設計」を行わない場合、「ちゃんとルールを守って使ってくれる」「使う側が実装を見て配慮してくれる」という「性善説」によって成り立っているのに対し、「契約による設計」では「使う側は何も考えてない」「人はミスをする」という「性悪説」を前提としている。
日本型プロジェクトでは基本的に性善説によって全てが成り立ち、馴れ合いの中でプロジェクトが進行する。そういった中では「契約による設計」は馴染まない。
だが、現実に性善説のプロジェクトはその多くが破綻しており、また、海外によるオフショア開発ではこれまでのプロジェクトの進め方ではうまくいかなくなっている。そういった現場では「契約による設計」が有効に働くだろう。
メソッドには、様々な仕様が存在する。
「引数にnullは不可」「文字列の長さは30以下」「0は不可」「100が渡された時のみ0、それ以外は100を返す」「エラー時にはnullを返す」等。
これらの仕様は、得てして明確には決められない。メソッドの多くは「丁寧に使ってね、変な使い方するとどうなるかわからないよ」という曖昧な仕様の元に作られている。
しかし、それでは少し使い方を間違えただけでバグが生まれ、また、実装の変化等によってそれまで動いていたものが動かなくなる事もある。
メソッドの仕様をあらかじめ明確にし、それに基づいて実装することで、バグを減らす方法、それが「契約による設計」である。
メソッドの仕様を「契約事項」とし、その契約をメソッドは必ず守ることとする。
契約はまずドキュメンテーションコメントとして記述し、明確に文書化する。
さらに、プログラム上で実際にその「契約事項」を守らせる。
契約事項のチェックの方法のひとつは、assertを用いたアサーションである。
メソッドの実装の最初で、まず引数をチェックし、実装の最後で返す戻り値をチェックする。
これらのチェックで、引数や戻り値が「契約に違反していないか」を調べる。
メソッドの実装及び、メソッドを呼び出す側は、その契約を守ってプログラムを組む。守らなかった場合にはAssertionError例外が投げられるため、そのプログラムは動かないことになる。
もうひとつのチェック方法は、JUnitを用いたテストファーストである。
テストファーストではメソッドに対してあらゆる引数による呼び出しを行い、その戻り値をチェックすることで、メソッドの契約が守られているかをチェックする。
この、契約事項のチェックを行うテストプログラムを先に作成することで、このテストプログラムそのものが「メソッドの契約文書」となり、メソッドが必ず契約を守っていることを約束させることになる。
基本的には、「契約による設計」は「意識の問題」である。
メソッド全てに細かい仕様を設けるということは、その仕様を決める時点で工数が掛かるため、多くの現場では推奨されない。たとえ「契約による設計」を行うことでプログラムのミスが減り、安全なプログラムを作ることができるため、最終的な工数は減ることになったとしても、それは確約されるものではなく、プロジェクト内での説得理由になりにくい。
さらに、アサーションやテストファーストを行うことの実質的な工数の問題や、仕様変更への対応の難しさに加えて、プログラムで「実際にチェックする」ことへの抵抗もある。
「契約による設計」は「性悪説」に基づいた思想である。「契約による設計」を行わない場合、「ちゃんとルールを守って使ってくれる」「使う側が実装を見て配慮してくれる」という「性善説」によって成り立っているのに対し、「契約による設計」では「使う側は何も考えてない」「人はミスをする」という「性悪説」を前提としている。
日本型プロジェクトでは基本的に性善説によって全てが成り立ち、馴れ合いの中でプロジェクトが進行する。そういった中では「契約による設計」は馴染まない。
だが、現実に性善説のプロジェクトはその多くが破綻しており、また、海外によるオフショア開発ではこれまでのプロジェクトの進め方ではうまくいかなくなっている。そういった現場では「契約による設計」が有効に働くだろう。
参考サイト
- (参考サイトはありません)
// Sample.java
public class Sample
{
public static void main( String[] args )
{
String string = "100";
try
{
// toInt()メソッドの使用例。
// まず、toInt()メソッドの引数にnullは渡せないので、
// nullチェックします。
if( string != null )
{
// そして呼び出します。
int result = toInt( string );
System.out.println( result );
// 100
}
}
catch( NumberFormatException e )
{
// 変換できなければNumberFormatException例外が
// 投げられます
e.printStackTrace();
}
}
/**
* 文字列を数値に変換するメソッド。
* @param string 数値の文字列形式。nullは不可。変換できない場合NumberFormatExceptionを投げます。
* @return 変換後の数値。
*/
public static int toInt( String string ) throws NumberFormatException
{
// 「契約による設計」に基づき、ちゃんとチェックします。
assert string != null : "nullは不可です。";
return Integer.parseInt( string );
}
}
public class Sample
{
public static void main( String[] args )
{
String string = "100";
try
{
// toInt()メソッドの使用例。
// まず、toInt()メソッドの引数にnullは渡せないので、
// nullチェックします。
if( string != null )
{
// そして呼び出します。
int result = toInt( string );
System.out.println( result );
// 100
}
}
catch( NumberFormatException e )
{
// 変換できなければNumberFormatException例外が
// 投げられます
e.printStackTrace();
}
}
/**
* 文字列を数値に変換するメソッド。
* @param string 数値の文字列形式。nullは不可。変換できない場合NumberFormatExceptionを投げます。
* @return 変換後の数値。
*/
public static int toInt( String string ) throws NumberFormatException
{
// 「契約による設計」に基づき、ちゃんとチェックします。
assert string != null : "nullは不可です。";
return Integer.parseInt( string );
}
}
// Sample.java public class Sample { public static void main( String[] args ) { String string = "100"; try { // toInt()メソッドの使用例。 // まず、toInt()メソッドの引数にnullは渡せないので、 // nullチェックします。 if( string != null ) { // そして呼び出します。 int result = toInt( string ); System.out.println( result ); // 100 } } catch( NumberFormatException e ) { // 変換できなければNumberFormatException例外が // 投げられます e.printStackTrace(); } } /** * 文字列を数値に変換するメソッド。 * @param string 数値の文字列形式。nullは不可。変換できない場合NumberFormatExceptionを投げます。 * @return 変換後の数値。 */ public static int toInt( String string ) throws NumberFormatException { // 「契約による設計」に基づき、ちゃんとチェックします。 assert string != null : "nullは不可です。"; return Integer.parseInt( string ); } }