假设我们有一个名为
如果我说错了,请纠正我,但我认为在这个例子中,我遵循了按合同设计的范例,只是简单地规定了可能输入值的(相当静态的)约束,并且如果这些约束没有被满足就引发了一个通用的未经检查的异常。
现在,有一个后端类来管理学生列表,通过他们的注册号进行索引。它持有一个Map<Integer, Student>来保存这个映射,并通过一个addStudent方法提供对它的访问:
现在假设这个方法有一个限制条件,比如说“数据库中不能已经存在相同学号的学生”。
我可以想到两种实现方式:
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(...)
方法,它不会抛出异常,而是使用返回值来指示错误/失败。