何时添加前置条件以及何时(仅)抛出异常?

时间:2011-04-19 19:13:39

标签: java file exception design-by-contract preconditions

我正在学习前提条件以及何时使用它们。有人告诉我 前提条件

@pre fileName must be the name of a valid file

不适合以下代码:

/**
Creates a new FileReader, given the name of file to read from.
@param fileName- the name of file to read from
@throw FileNotFoundException - if the named file does not exist,
is a directory rather than a regular file, or for some other reason cannot
be opened for reading.
*/
public FileReader readFile(String fileName) throws FileNotFoundException {
. . .
}//readFile

为什么会这样?

编辑:另一个例子

我们假设以下,作为一个例子,以“正确”的方式完成。 请注意IllegalArgumentException和前提条件。注意行为如何 很明确,以及如何进行投掷声明 设定前提条件。最重要的是,请注意包含的内容 NullPointerException的前提条件。再一次,为什么不呢?

/**
* @param start the beginning of the period
* @param end the end of the period; must not precede start
* @pre start <= end
* @post The time span of the returned period is positive.
* @throws IllegalArgumentException if start is after end
* @throws NullPointerException if start or end is null
*/
public Period(Date start, Date end) f

这些例子是否避免使用额外的前提条件?有人可以 认为如果我们避免先决条件,那么为什么要这样做呢? 也就是说,为什么不用@throws声明替换所有前置条件(如果 避免它们就是这里要做的事情)?

3 个答案:

答案 0 :(得分:4)

维基百科将前置条件定义为:

  

在计算机编程中,前提条件是条件或谓词,在执行某些代码段之前或在正式规范中的操作之前必须始终为真。

     

如果违反前提条件,则代码部分的效果变得不明确,因此可能会或可能不会执行其预期的工作。

在您的示例中,如果文件名无效的方法的效果是定义的(它必须抛出FileNotFoundException)。

换句话说,如果file有效是一个先决条件,我们就知道它总是有效的,并且抛出强制执行异常的合同部分是不会永远不适用的。无法访问的规范案例就像代码无法访问代码一样。

修改

  

如果我有一些先决条件,并且可以为这些条件提供定义的行为,那么如果我这样做会不会更好?

当然,但它不再是霍尔定义的先决条件。从形式上讲,一个方法具有前置条件pre和后置条件post意味着每次执行从状态prestate开始并以状态poststate结束的方法

pre(prestate) ==> post(poststate)

如果暗示的左侧是假的,无论poststate是什么,这都是真实的,即该方法将满足其合约而不管它是什么,即方法的行为未定义。

现在,快进到现代,方法可以抛出异常。对异常建模的通常方法是将它们视为特殊的返回值,即异常是否是后置条件的一部分。

  

但异常并非真的无法到达,是吗?

如果throws子句是​​postcondtion的一部分,你有类似的东西:

pre(prestate) ==> (pre(prestate) and return_valid) or (not pre(prestate) and throws_ exception)

在逻辑上等同于

pre(prestate) ==> (pre(prestate) and return_valid)

也就是说,你是否写了throws子句并不重要,这就是为什么我称这个规范案例无法访问。

  

我想说,例外情况可以作为前提条件的补充,告知用户如果他/她违反合同将会发生什么。

没有;抛出条款是合同的一部分,如果合同被破坏,则不会产生任何影响。

当然可以定义需要满足@throws子句而不考虑前提条件,但这有用吗?考虑:

@pre foo != null
@throws IllegalStateException if foo.active

如果foonull,是否必须抛出异常?在经典定义中,它是未定义的,因为我们假设没有人会为null传递foo。在你的定义中,我们必须在每个throws子句中明确重复:

@pre foo != null
@throws NullPointerException if foo == null
@throws IllegalStateException if foo != null && foo.active

如果我知道没有合理的程序员将null传递给该方法,为什么我应该被强制在我的规范中指定该情况?它有什么好处来描述对调用者无用的行为? (如果调用者想知道foo是否为null,他可以自己检查它,而不是调用我们的方法并捕获NullPointerException!)。

答案 1 :(得分:3)

好的,这就是我发现的:

<强>背景

基于以下原则,如Bertrand Meyer的书中所述 面向对象的软件构建

  

“非冗余原则”   情节应该是一个人的身体   常规测试常规   前提。“ - Bertrand Meyer

     

“前提条件可用性规则”   出现在前提条件下的特征   必须提供例程   例程的每个客户   可用。“ - Bertrand Meyer

,这两点回答了这个问题:

  1. 为了使前提条件有用,客户端(方法的用户)必须能够测试它们。
  2. 服务器 从不 测试前置条件,因为这会增加复杂性 系统。虽然,在调试系统时,会打开断言来执行此测试。
  3. 更多关于何时,为何以及如何使用前提条件:

      

    “合同设计的核心是   想法,表示为非冗余   原则,任何一致性   可能危及a的条件   例程正常运作你   应该分配执行   条件只有两个中的一个   合同中的合作伙伴。哪一个?   在每种情况下,你有两个   可能性:•你指定了   对客户负责,其中   如果情况将作为一部分出现   例程的先决条件。 • 要么   您指定供应商,其中   情况将出现在一个   条件指令的形式如果   条件然后...,或等价物   控制结构,在例程中   体。

         

    我们可以称之为第一种态度   要求和第二个   宽容。“ - Bertrand Meyer

    因此,只有在决定客户承担责任时,才应存在先决条件。 由于服务器应 测试前置条件,因此行为将变为未定义(如维基百科上所述)。

    <强>答案

    • 第一点回答第一个例子。
    • 至于第二个例子,它可能以正确的方式完成。这是因为 第一个@throws声明暗示该方法具有(除了断言) 测试了前提条件。这违反了第二点。

    至于空指针;这表明分配了空指针的责任 到服务器。也就是说,使用“宽容的态度”,而不是“苛刻的态度”。 这完全没问题。如果一个人选择实施苛刻的态度,那么就会 删除throws声明(但更重要的是; 测试它),然后添加 一个先决条件声明(也许是一个断言)。

答案 2 :(得分:2)

我认为按合同构思设计(我自己还没有使用)和前/后条件旨在保证从方法传入和传出的特定条件。特别是编译器(在这种情况下,理论上称为Java没有内置)需要能够验证合同条件。在文件前置条件的情况下,由于文件是外部资源,类可能会移动并且可能不存在相同的文件等,因此无法完成此操作。编译器(或预处理器)如何保证此类合同?

另一方面,如果您只是将它用于评论,那么它至少会向其他开发人员展示您的期望,但您仍然期望在文件未退出时会出现异常。

我认为它“不适合”合同设计的正式意义上的方法,因为即使是一个案例也无法验证。也就是说,您可以在一个环境中提供有效的文件名,但这可能在您的程序外部的其他环境中无效。

另一方面,日期示例,前置和后置条件可以在调用者上下文中验证,因为它们不受该方法本身无法控制的外部环境设置的影响。