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

现在让我们假设这种方法有一个约束,例如“数据库中必须不存在具有相同入学编号的学生”。

我看到了如何实现这一点的两种选择:

选项 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. 选项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

我不相信后端班级管理学生名单的方式与合同有关——也就是说,它持有的 aMap<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() 中)。这样,如果实际添加了学生,则campus.add(Student)返回true 。

于 2012-02-13T16:58:53.083 回答