首页 文章

Contract 设计中哪些前提条件合情合理?

提问于
浏览
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.
}

如果我错了,请纠正我,但我认为在这个例子中,我通过简单地指定可能的输入值的(相当静态的)约束并且如果不满足那些引发通用的,未经检查的异常,则按照 Contract 范式进行设计 .

现在,有一个后端类管理学生列表,按其入学编号索引 . 它保存 Map<Integer, Student> 以保存此映射并通过 addStudent 方法提供对它的访问:

public void addStudent(Student student) {
    students.put(student.getMatrNr(), student);
}

现在让我们假设这个方法有一个约束,如“ a student with the same matriculation number must not already exist in the database ” .

我看到了如何实现这两个选项:

选项A.

如果学生具有相同的母校,则定义由 addStudent 引发的自定义 UniquenessException 课程 . 号码已经存在 . 调用代码将看起来像这样:

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是否具有我没想到的优势?

  • 从 Contract 的角度来看,实际上是否允许使用选项B并将唯一性定义为 addStudent 方法的前提条件?

  • 在定义前置条件时是否有经验法则,只需提高 IAE 以及何时使用"proper"例外?我认为"make it a pre-condition unless it depends on the current state of the system"可能是这样的规则 . 还有更好的吗?

UPDATE: 似乎还有另一个不错的选择,即提供一个 public boolean tryAddStudent(...) 方法,它不会抛出异常,而是使用返回值来表示错误/失败 .

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

    我不相信后端类管理学生列表的方式与 Contract 相关 - 也就是说,它持有 Map<Integer, Student> 不会成为 Contract 的一部分 . 因此,在 hasStudent(int matrNr) 中将预科编号纳入 Contract 似乎也有点邪恶 .

    我建议校园可能应该有一个方法 Boolean hasStudent(Student student) ,它会根据条件检查校园是否有学生 . 如果 Contract 要求具有唯一性,并且确实非常特殊,那么您将使用 Contract 检查:

    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 .

相关问题