4

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に感謝します)

したがって、私は疑問に思います:

  1. さらに優れた3番目のオプションはありますか?
  2. オプションBには、私が思いもよらなかった利点がありますか?
  3. 契約の観点からの設計から、オプションBを使用し、addStudentメソッドの前提条件として一意性を定義することは実際に許可されますか?
  4. IAE前提条件を定義して単純に発生させる場合と、「適切な」例外を使用する場合の経験則はありますか?「システムの現状に依存しない限り、前提条件にする」というのもそういうルールかもしれないと思います。より良いものはありますか?

更新:別の良いオプションがあるようです。それはpublic boolean tryAddStudent(...)、例外をスローせず、代わりに戻り値を使用してエラー/失敗を通知するメソッドを提供することです。

4

2 に答える 2

2

(これはコメントするには長すぎます)

オプション B では、Map<Integer,Student>を使用せず、次のようにします。

if (campus.hasStudent(12000)) 
    printError("student already existing.");
else
    campus.addStudent(new Student(...));

Mapの抽象化は、ユース ケースには十分に実用的ではありません (同時実行の問題について言及しています)。代わりにConcurrentMap<Integer,Student>を使用して、次のようなことを行います。

final Student candidate = new Student(...);
final Student res = putIfAbsent(student.getMatrNr(), candidate)
if ( res != null ) {
    throw new IllegalStateException("Class contract violation: \"student already exists!\", please read the doc");
}
于 2012-02-13T16:53:03.553 に答える
2

バックエンド クラスが学生のリストを管理する方法がコントラクトに関連するとMap<Integer, Student>は思えません。つまり、コントラクトの一部ではないということです。したがって、入学番号を契約に持ち込むことhasStudent(int matrNr)も少し悪いようです。

キャンパスにはおそらくメソッドが必要Boolean hasStudent(Student student)であり、キャンパスに学生がいるかどうかを条件に基づいて確認することをお勧めします。一意性が契約によって要求され、本当に例外的である場合は、契約上のチェックを使用します。

   Student student= new Student(int matrNbr, String name);
   if (campus.hasStudent(student) {
      throw new UniquenessException();
   }
   else {
      campus.add(student);
   }

スローされる例外は、引数や戻り値と同様にコントラクトに関連している必要があります

アップデート

一意性が満たされておらず、例外的でない場合に追加が単に失敗する場合は、例外をスローしないでください。代わりに、追加の成功を戻り値にします (java.util.HashSet.add() のように)。このように、学生が実際に追加された場合はtruecampus.add(Student)を返します。

于 2012-02-13T16:58:53.083 に答える