Давайте предположим, что у нас есть класс 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 , который, кажется, описывает именно эту проблему)
Поэтому я задаюсь вопросом:
- Есть ли третий вариант, который даже лучше?
- Есть ли у варианта Б преимущество, о котором я не думал?
- Будет лина самом деле с точки зрения контракта было разрешено использовать опцию B и определить уникальность как предварительное условие метода
addStudent
? - Есть ли эмпирическое правило при определении предварительных условийи просто поднять
IAE
и когда использовать «правильные» исключения?Я думаю «сделать это предварительным условием, если оно не зависит от текущего состояния системы» может быть таким правилом.Есть ли лучший?
ОБНОВЛЕНИЕ: Похоже, есть еще один хороший вариант, который заключается в предоставлении public boolean tryAddStudent(...)
метода, который не выдает исключение, а вместо этого сигнализируетошибка / ошибка при использовании возвращаемого значения.