Student
次のコンストラクターを持つクラスがあると仮定します。
/** Initializes a student instance.
* @param matrNr matriculation number (allowed range: 10000 to 99999)
* @param firstName first name (at least 3 characters, no whitespace)
*/
public Student(int matrNr, String firstName) {
if (matrNr < 10000 || matrNr > 99999 || !firstName.matches("[^\\s]{3,}"))
throw new IllegalArgumentException("Pre-conditions not fulfilled");
// we're safe at this point.
}
間違っている場合は訂正してください。ただし、この例では、可能な入力値に(かなり静的な)制約を指定し、それらが満たされない場合は一般的な未チェックの例外を発生させることで、契約パラダイムによる設計に従いました。
現在、入学番号で索引付けされた学生のリストを管理するバックエンドクラスがあります。このマッピングを保存するためのを保持し、次のメソッドMap<Integer, Student>
を介してアクセスを提供します。addStudent
public void addStudent(Student student) {
students.put(student.getMatrNr(), student);
}
ここで、この方法に「同じ入学番号の学生がデータベースにすでに存在していてはならない」などの制約があると仮定します。
これを実現する方法には2つの選択肢があります。
オプションA
同じ母校の生徒UniquenessException
が育てたカスタムクラスを定義します。addStudent
番号はすでに存在します。呼び出しコードは次のようになります。
try {
campus.addStudent(new Student(...));
catch (UniquenessError) {
printError("student already existing.");
}
オプションB
要件を前提条件として記述しIAE
、それが成り立たない場合は単にを上げます。さらに、失敗するcanAddStudent(Student stud)
かどうかを事前にチェックする方法を提供します。addStudent
コードを呼び出すと、次のようになります。
Student stud = new Student(...);
if (campus.canAddStudent(stud))
campus.addStudent(stud);
else
printError("student already existing.");
オプションAは、少なくとも次の理由から、ソフトウェアエンジニアリングの観点からははるかにクリーンだと思います。
- 呼び出し元のコードを変更せずに、簡単にスレッドセーフにすることができます(正確な問題を説明しているように見えるTOCTTOUを教えてくれたVooに感謝します)
したがって、私は疑問に思います:
- さらに優れた3番目のオプションはありますか?
- オプションBには、私が思いもよらなかった利点がありますか?
- 契約の観点からの設計から、オプションBを使用し、
addStudent
メソッドの前提条件として一意性を定義することは実際に許可されますか? IAE
前提条件を定義して単純に発生させる場合と、「適切な」例外を使用する場合の経験則はありますか?「システムの現状に依存しない限り、前提条件にする」というのもそういうルールかもしれないと思います。より良いものはありますか?
更新:別の良いオプションがあるようです。それはpublic boolean tryAddStudent(...)
、例外をスローせず、代わりに戻り値を使用してエラー/失敗を通知するメソッドを提供することです。