假设我们有一个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有我没有想到的优势吗?
- 从合同设计的角度来看,实际上是否允许使用选项 B 并将唯一性定义为
addStudent
方法的先决条件? IAE
何时定义前置条件并简单地提出以及何时使用“正确”异常是否有经验法则?我认为“除非它取决于系统的当前状态,否则将其作为先决条件”可能是这样的规则。有更好的吗?
更新:似乎还有另一个不错的选择,即提供一种public boolean tryAddStudent(...)
不引发异常而是使用返回值指示错误/失败的方法。