Какие предварительные условия имеют смысл при проектировании по контракту? - PullRequest
4 голосов
/ 13 февраля 2012

Давайте предположим, что у нас есть класс 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);
}

Теперь давайте предположим, что существует ограничение на этот метод, например " студент стот же номер зачисления не должен уже существовать в базе данных".

Я вижу два варианта, как это может быть реализовано:

Опция 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 намного чище с точки зрения разработки программного обеспечения, по крайней мере, по следующей причине:

  • Его можно легко сделать потокобезопасным без изменения вызывающего кода (спасибо Voo за указание на TOCTTOU , который, кажется, описывает именно эту проблему)

Поэтому я задаюсь вопросом:

  1. Есть ли третий вариант, который даже лучше?
  2. Есть ли у варианта Б преимущество, о котором я не думал?
  3. Будет лина самом деле с точки зрения контракта было разрешено использовать опцию B и определить уникальность как предварительное условие метода addStudent?
  4. Есть ли эмпирическое правило при определении предварительных условийи просто поднять IAE и когда использовать «правильные» исключения?Я думаю «сделать это предварительным условием, если оно не зависит от текущего состояния системы» может быть таким правилом.Есть ли лучший?

ОБНОВЛЕНИЕ: Похоже, есть еще один хороший вариант, который заключается в предоставлении public boolean tryAddStudent(...) метода, который не выдает исключение, а вместо этого сигнализируетошибка / ошибка при использовании возвращаемого значения.

Ответы [ 2 ]

2 голосов
/ 13 февраля 2012

Я не верю, что способ, которым бэкэнд-класс управляет списком учащихся, будет иметь отношение к договору, то есть, если он содержит 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);
   }

Выданные исключения должны быть такими же релевантными для контракта, как аргументы и возвращаемые значения

UPDATE

Если добавление должно просто завершиться неудачей, если уникальность не соблюдается и не является исключительной, то не выбрасывайте исключение. Вместо этого сделайте успешное добавление возвращаемого значения (как в java.util.HashSet.add ()). Таким образом, campus.add(Student) вернет true , если ученик действительно добавится.

2 голосов
/ 13 февраля 2012

(это слишком долго для комментария)

В вашем варианте B я бы не использовал карту и затем выполните:

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

Абстракция Map недостаточно практична для вашего случая использования (вы упоминаете проблемы параллелизма), я бы использовал вместо ConcurrentMap и сделайте что-то вроде этого:

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");
}
...