什么定义了健壮的代码?


42

我的教授在谈到“健壮”的代码时一直引用这个Java示例:

if (var == true) {
    ...
} else if (var == false) {
    ...
} else {
    ...
}

他声称“健壮的代码”意味着您的程序考虑了所有可能性,并且没有错误之类的东西-所有情况都由代码处理并导致有效状态,因此为“ else”。

但是,我对此表示怀疑。如果变量是布尔值,那么在逻辑上不可能使用第三状态时检查第三状态的意义何在?

“没有这样的错误”似乎也很荒谬;即使是Google应用程序,也会直接向用户显示错误,而不是无声地吞下错误或以某种方式将其视为有效状态。很好-我想知道什么时候出了问题。似乎很宣称应用程序永远不会有任何错误。

那么“健壮代码” 的实际定义是什么?



4
这仅适用于非强类型的语言。在强类型语言中,布尔类型的变量(不是一些伪装成布尔值的整数)只能为true或false,没有第三种选择……
Marjan Venema

23
问他如何对第三种情况进行测试,因为健壮的代码肯定需要进行测试,如果您没有设法对第三种情况进行测试,您将找不到任何可能潜伏在其中的错误。
gbjbaanb

2
@Marjan-用一种不太强类型的语言,最有可能会这样写:if(var){} else {}
kevin cline

2
我不知道x和!x都可能为真的任何语言。注意,我没有建议“ if(x == true)...”;我讨厌这样的比较。
凯文·克莱恩

Answers:


33

当第三状态在逻辑上不可能时,检查第三状态有什么意义?

那么Boolean?允许NULL状态既不是真也不是假的a呢?现在该软件应该做什么?某些软件必须像起搏器一样具有很高的防撞性。是否曾见过有人向a的数据库中添加一列Boolean并将初始数据初始化为NULL初始值?我知道我已经看过。

以下是一些链接,讨论了就软件而言强大的含义:

如果您认为这里有一个公认的“健壮”定义,那就祝您好运。可能有一些同义词,例如防炸弹或防白痴。 Duct Tape Programmer就是一个例子,通常至少在我对术语的理解中,他们都会编写健壮的代码。


13
如果这是一个可为null的布尔值,则Java和c#都将抛出,因此应首先检查null。
Esben Skov Pedersen

关于猫或狗的定义似乎尚未达成普遍共识。
图兰斯·科尔多瓦

11

为了我的讨论,布尔可以有2个状态,即True或False。其他任何与编程语言规范不一致的地方。如果您的工具链不符合其规格,则无论您做什么都将被抽水。如果开发人员创建的Bool类型具有两个以上的状态,那将是他在我的代码库上所做的最后一件事。

选项A。

if (var == true) {
    ...
} else if (var == false) {
    ...
} else {
    ...
}

选项B

if (var == true) {
    ...
} else {
    ...
}

我断言选项B更可靠.....

任何花招都可以告诉您处理意外错误。一旦想到它们,它们通常很容易被发现。您的教授给出的示例不可能发生,因此这是一个非常糟糕的示例。

如果没有复杂的测试工具,就不可能进行A测试。如果您无法创建它,您将如何对其进行测试?如果您尚未测试代码,怎么知道它有效?如果您不知道它是否有效,则说明您不是在编写功能强大的软件。我认为他们仍然称其为Catch22(很棒的电影,有时看)。

选项B很难测试。

下一个问题,问您这个问题的教授:“如果布尔既不是True也不是False,您希望我对此做些什么?” 那应该导致一个非常有趣的讨论.....

在大多数情况下,核心转储是适当的,最坏的情况是使用户烦恼或花费大量金钱。如果说模块是航天飞机实时重入计算系统,该怎么办?任何答案,无论多么不准确,都不会比中止更糟,这将杀死用户。因此,如果您知道答案可能是错误的,该怎么办,请继续尝试50/50,否则放弃并尝试100%失败。如果我是机组人员,我会乘50/50。

选项A杀死我选项B给我带来生存的机会。

但是,等等-这是对航天飞机再入的模拟-那么呢?中止,以便您了解。听起来是个好主意?-否-因为您需要测试计划交付的代码。

选项A更适合模拟,但不能部署。由于选项B是已部署的代码,因此没有用,因此模拟执行与实时系统相同。

假设这是一个有效的担忧。更好的解决方案是将错误处理与应用程序逻辑隔离开。

if (var != true || var != false) {
    errorReport("Hell just froze over, var must be true or false")
}
......
if (var == true){
 .... 
} else {
 .... 
}

进一步阅读 -Therac-25 X射线机,阿丽亚娜5号火箭故障和其他故障(链接有很多断开的链接,但足够的信息足以帮助Google)


1
“ ..意外错误。一旦想到它们,它们通常很容易被发现”-但是,当您想到它们时,它们不再是意外的。
gbjbaanb

7
一些问题,这就是如果你的代码if (var != true || var != false) {应该是&&吧。

1
我可以很容易地想到一个布尔值,它既不是真的也不是假的,但这仍然是意外的。如果您说布尔值不能为其他任何值,如果我检查一个字母是否为数字字符然后将其转换为整数值,我可以轻松地认为该整数值小于0或大于9,但仍然意外。
gnasher729

1
Java和C#支持Null布尔值,并具有实际应用程序。考虑一个包含人员列表的数据库。一段时间后,您决定需要一个性别(isMale)字段。Null表示“从未问过,所以不知道”;真表示男性,假表示女性。(好的,为简单起见,省略了变性性别...)。
kiwiron

@kiwiron:更好的解决方案是不使用枚举类型“ Male”,“ Female”,“ Did Not Ask”。枚举更好-可以在需要时进行扩展(例如,在您的无性恋者,两性恋者中,“拒绝回答”就会浮现)。
mattnz

9

实际上,您的代码不是更健壮,而是更小。最后else就是无法测试的无效代码。

在诸如航天器之类的关键软件中,禁止使用死代码,更普遍地讲,未经测试的代码是禁止的:如果宇宙射线产生单个事件异常,进而使您的死代码被激活,则一切皆有可能。如果SEU激活了一部分健壮代码,则(意外)行为将受到控制。


我不知道这是在航天器中禁止使用死代码吗?即你不能写最后一个?既然不能测试,就不能放入?但是,这意味着什么:“如果SEU激活了一部分健壮的代码,则(意外的)行为将受到控制。”
2015年

5
是的,在空间关键型软件中,测试覆盖率必须为100%,因此,禁止使用不可访问的代码(即无效代码)。
mouviciel 2015年

7

我认为教授可能会混淆“错误”和“错误”。健壮的代码当然应该很少/没有错误。健壮的代码可能并且在恶劣的环境中必须具有良好的错误管理(无论是异常处理还是严格的返回状态测试)。

我同意教授的代码示例很愚蠢,但没有我的愚蠢。

// Assign 3 to x
var x = 3;
x = 3;   // again, just for sure
while (x < 3 or x > 3) { x = 3; }  // being robust
if (x != 3) { ... }  // this got to be an error!

1
如果可以肯定地触发最后一个,则不需要太多的努力。任何有经验的C程序员都看到值突然改变。当然,从逻辑上讲,在受控的单线程环境中,这永远都不会发生。在现实生活中,if内的代码最终会发生。如果没有什么用处,可以在其中进行编码,然后不要编码!(在特定的软件开发过程中,我经历了一次有趣的经历,在这种情况下,我以诅咒的话提出了例外,以防万一发生不可能的事情……猜猜发生了什么?
Alex

2
真实的故事:boolean x = something(); if (x) { x = True // make sure it's really true, ... }
Andres F.

6

鲁棒代码的定义尚未达成共识,因为编程中的许多事情或多或少是主观的。

教授给出的示例取决于语言:

  • 在Haskell中,a Boolean可以是TrueFalse,没有第三种选择
  • 在C ++中,a bool可以是truefalse或(不幸的)来自某种可疑的类型转换,将其置于未知的情况下……这不应该发生,但是可能是由于先前的错误造成的。

但是,您教授的建议是通过在核心程序中间为不应发生的事件引入无关的逻辑来使代码模糊不清,因此,我将向您介绍防御性编程

在大学情况下,您甚至可以采用“按合同设计”策略来增强它:

  • 建立类的不变式(例如,列表中size的项目数data
  • 为每个函数建立前提条件和后置条件(例如,该函数只能a在小于的情况下调用10
  • 在每个函数的入口和出口点测试每个

例:

class List:
  def __init__(self, items):
    self.__size = len(items)
    self.__data = items

  def __invariant(self):
    assert self.__size == len(self.__data)

  def size(self):
    self.__invariant()

    return self.__size

  def at(self, index):
    """index should be in [0,size)"""
    self.__invariant()
    assert index >= 0 and index < self.__size

    return self.__data[index]

  def pushback(self, item):
    """the subsequent list is one item longer
       the item can be retrieved by self.at(self.size()-1)"""
    self.__invariant()

    self.__data.append(item)
    self.__size += 1

    self.__invariant()
    assert self.at(self.size()-1) == item

但是这位教授专门说这是Java,没有具体说var是什么类型。如果为Boolean,则可以为true,false或null。如果还有其他的话,它既可以等于真,也可以等于假。是的,健壮,防御和偏执之间有重叠。
安迪·坎菲尔德

2
在C,C ++和Objective-C中,布尔值可以像其他任何类型一样具有不确定的值,但是任何赋值都会将其设置为true或false,并且别无其他。例如:bool b = 0; b ++; b ++; 将b设置为true。
gnasher729

2

您的教授的方法是完全错误的。

一个函数或仅是少量代码,应该具有说明其功能的规范,该规范应涵盖所有可能的输入。并且应该编写代码,以确保其行为符合规范。在示例中,我将这样写规范非常简单:

Spec: If var is false then the function does "this", otherwise it does "that". 

然后编写函数:

if (var == false) dothis; else dothat; 

并且代码符合规范。所以您的教授说:如果var == 42怎么办?看一下规范:它说函数应该做到这一点。看一下代码:函数执行“ that”。该功能符合规格。

教授的代码使事情变得完全不那么可靠的事实是,当var既不是true也不是false时,使用他的方法,它将执行以前从未调用过且完全未经测试的代码,从而产生完全不可预测的结果。


1

我同意@ gnasher729的说法:您的教授的方法完全是错误的。

健壮意味着它可以抵抗破坏/故障,因为它几乎不需要做任何假设并且相互分离:它是自包含的,自定义的和可移植的。它还包括适应不断变化的需求。总之,您的代码是持久的

通常,这会转换为简短的函数,这些函数将从调用方传递的参数中获取数据,并使用消费者的公共接口(抽象方法,包装,间接,COM样式接口等),而不是包含具体实现代码的函数。


0

健壮的代码就是可以很好地处理故障的代码。不多不少。

失败的类型有很多:错误的代码,不完整的代码,意外的值,意外的状态,异常,资源用尽……。健壮的代码可以很好地处理这些问题。


0

我会考虑将您提供的代码作为防御性编程的示例(至少在我使用该术语时)。防御性编程的一部分是做出选择,以使对系统其余部分的行为所做出的假设最小化。例如,以下哪个更好:

for (int i = 0; i != sequence.length(); ++i) {
    // do something with sequence[i]
}

要么:

for (int i = 0; i < sequence.length(); ++i) {
    // do something with sequence[i]
}

(以防万一,请检查循环测试:第一个使用!=,第二个使用<)。

现在,在大多数情况下,这两个循环的行为将完全相同。但是,第一个(与相比!=)做了一个假设,即i每次迭代仅增加一次。如果跳过该值,sequence.length()则循环可能会继续超出序列的范围并导致错误。

因此,您可以提出一个论点,即第二种实现更为健壮:它不依赖于有关循环体是否发生更改的假设i(注意:实际上,它仍然做出i从不为负的假设)。

为了给您一个为什么不想做这个假设的动力,可以想象一下循环正在扫描一个字符串,进行一些文本处理。您编写了循环,一切都很好。现在,您的需求发生了变化,并且您决定需要在文本字符串中支持转义字符,因此,您可以更改循环主体,以便在检测到转义字符(例如,反斜杠)时,循环体会递增,i以在转义后立即跳过该字符。现在,第一个循环有一个错误,因为如果文本的最后一个字符是反斜杠,则循环主体将增加i,并且循环将继续到序列末尾。


-1

我个人将代码描述为“健壮”的代码,它具有以下重要属性:

  1. 如果我妈妈坐在它前面并使用它,她将无法破坏系统

现在,我的意思是使系统进入不稳定状态,或者导致UNHANDLED异常。您知道,有时对于一个简单的概念,您可以进行复杂的定义和解释。但是我更喜欢简单的定义。用户非常擅长于找到可靠的应用程序。如果您的应用程序用户向您发送了许多有关错误,状态丢失,不直观的工作流等的请求,则您的编程有问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.