Update 16-Validating-Your-Code.md

This commit is contained in:
独奏猫
2019-08-27 15:19:51 +08:00
committed by GitHub
parent 3eac1b7699
commit 27f748fd89

View File

@@ -1,5 +1,3 @@
[TOC]
<!-- Validating Your Code -->
@@ -18,7 +16,7 @@
Java是一个静态类型的语言程序员经常对一种编程语言明显的安全性过于感到舒适“能通过编译器那就是没问题的”。但静态类型检查是一种非常局限性的测试。这说明编译器接受你的语法和基本类型规则但不意味着你的代码满足程序安全的目标。随着你代码经验的丰富你逐渐了解到你的代码从来没有满足过安全性这个目标。迈向代码的第一步就是创建代码测试争对你的目标检查代码行为。
### 单元测试
#### 单元测试
这个过程是将集成测试构建到你创建的所有代码中,并在当你每次构建你的系统时运行这些测试。这样,构建过程检查不仅仅是检查语法的错误,同时你也教它检查语义错误。
@@ -28,7 +26,7 @@ C风格的语言特别是C++通常会认为性能比安全更重要。用J
当我意识到要保证书中代码的正确性时我自己的测试经历就开始了。这本书通过Gradle构建系统 你需要安装JDK你可以通过输入gradlew compileJava编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目在我看来任何编程书籍的必备条件——你怎么能相信你没有编译的代码呢? 并且我发现我可以利用搜索和替换在整本书大范围的修改。如果我引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统发现了一个严重的漏洞。 编译程序是毫无疑问的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书并且发现了上面代码的缺陷。然而我收到了来自读者反馈的语法问题。我在实现一个自动化执行测试系统的时候使用了在早期能看到效果的步骤但这是迫于出版压力与此同时我明白我的程序绝对有问题这些都会变成bug让我自食其果。我也经常收到读者的抱怨说我没有显示足够的代码输出。我需要验证程序的输出并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码许多读者就是这么做的并且从中受益。然而这种态度背后的原因是我无法保证书中的输出是正确的。从经验来看我知道随着时间的推移会发生一些事情使得输出不再正确或者我一开始就没有把它弄对。为了解决这个问题我利用Python创建了一个工具你将在下载的示例中找到此工具。本书中的大多数程序都产生控制台输出该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较所以读者可以看到预期的输出并且知道这个输出已经被构建程序验证的。
### JUnit
#### JUnit
最初的Junit发布于2000年大概是基于Java 1.0因此不能使用Java的反射工具。因此用旧的JUnit编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽并编写了自己的单元测试框架作为注解一章的示例。这个框架走向了另一个极端“尝试最简单可行的方法”极限编程中的一个关键短语。从那之后Junit通过反射和注解得到了极大的改进这大大简化了编写单元测试代码的过程。在Java8中他们甚至增加了对lambdas表达式的支持。本书使用当时最新的Junit5版本
@@ -174,7 +172,7 @@ Cleaning up 4
断言语句不是必须的;你可以在没有断言的情况下运行测试,如果没有异常,则认为测试是成功的。
**compare()**是“helper方法”的一个例子它不是由JUnit执行的而是被类中的其他测试使用。只要没有**@Test**注解Junit就不会运行它也不需要特定的签名。在这**compare()**是**private**,表示在测试类使用,但他同样可以是**public**。其余的测试方法通过将其重构为compare()方法来消除重复的代码。
**compare()**是“helper方法”的一个例子它不是由JUnit执行的而是被类中的其他测试使用。只要没有**@Test**注解Junit就不会运行它也不需要特定的签名。在这**compare()**是**private**,表示在测试类使用,但他同样可以是**public**。其余的测试方法通过将其重构为**compare()**方法来消除重复的代码。
本书使用**build.gradle**控制测试,运行本章节的测试,命令:
@@ -196,7 +194,7 @@ JUnit包含许多额外的测试业务您可以在其上了解这些结构
Junit是Java最流行的单元测试框架但也有其它可以替代的。你可以通过互联网发现更适合你的那一个。
### 测试覆盖率的幻觉
#### 测试覆盖率的幻觉
测试覆盖率,同样也称为代码覆盖率,度量代码的测试百分比。百分比越高,测试的覆盖率越大。这里有很多[方法](https://en.wikipedia.org/wiki/Code_coverage)
@@ -208,7 +206,8 @@ Junit是Java最流行的单元测试框架但也有其它可以替代的。
<!-- Preconditions -->
## 前条件
## 前条件
前置条件的概念来自于契约式设计(**Design By Contract, DbC**), 利用断言机制实现。我们从Java的断言机制开始来介绍DBC最后使用谷歌Guava库作为前置条件。
#### 断言Assertions
@@ -402,16 +401,57 @@ Shouldn't be null: arg s
不管你是否同意第一条总是对的在足够多的情况下DbC确实是一种有用的方法。我认为与任何解决方案一样它的有用性也有界限。但如果你知道这些界限你就知道什么时候去尝试。尤其是设计过程中一个有价值的部分是特定类DbC约束的表达式如果无法指定约束则可能对要构建的内容了解得不够。
#### check指令
#### 检查指令
详细研究DbC之前思考最简单使用断言的办法**Meyer**称它为check指令。check指令说明你确信代码中的某个特定属性此时已经得到满足。check指令的思想是在代码中表达非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。
详细研究DbC之前思考最简单使用断言的办法**Meyer**称它为检查指令。检查指令说明你确信代码中的某个特定属性此时已经得到满足。检查指令的思想是在代码中表达非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。
在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是作为其中复杂的一部分。滴定完成后一个有用的check指令是能够断定液体变蓝了。
在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是作为其中复杂的一部分。滴定完成后一个有用的检查指令是能够断定液体变蓝了。
check指令对你的代码进行补充,当您可以测试并阐明对象或程序的状态时,应该使用它。
检查指令对你的代码进行补充,当您可以测试并阐明对象或程序的状态时,应该使用它。
#### 前置条件
<!-- Test-Driven Development -->
前置条件确保客户端(调用此方法的代码)履行其部分契约。这意味着在方法调用开始时几乎总是会检查参数(在你用那个方法做任何操作之前)以此保证它们的调用在方法中是合适的。因为你永远无法知道客户端会传递给你什么,前置条件是确保检查的一个好做法。
#### 后置条件
后置条件测试你在方法中所做的操作的结果。这段代码放在方法调用的末尾,在**return**语句之前(如果有的话)。对于长时间、复杂的方法,在返回计算结果之前需要对计算结果进行验证(也就是说,在某些情况下,由于某种原因,你不能总是相信结果),后置条件很重要,但是任何时候你可以描述方法结果上的约束时,最好将这些约束在代码中表示为后置条件。
#### 不变性
不变性保证了必须在方法调用之间维护的对象的状态。但是,它并不会阻止方法在执行过程中暂时偏离这些保证,它只是在说对象的状态信息应该总是遵守状态规则:
**1**. 在进入该方法时。
**2**. 在离开方法之前。
此外,不变性是关于构造后对象状态的保证。
根据这个描述,一个有效的不变性被定义为一个方法,可能被命名为**invariant()**,它在构造之后以及每个方法的开始和结束时调用。方法可调用如下:
assert invariant();
这样,如果出于性能原因禁用断言,就不会产生开销。
#### 放松DBC检查 或 非严格的DBC
尽管他强调了前置条件、后置条件和不变性的价值所在以及在开发过程中使用它们的重要性Meyer承认在一个产品中包含所有DbC代码并不总是实用的。您可以放松DbC检查它基于在特定的地方你可以对代码的信任程度。以下是放松检查的顺序最安全到最不安全
**1**. 不变性检查在每个方法一开始的时候是不能进行的,因为在每个方法结束的时候进行不变性检查能保证一开始的时候对象处于有效状态。也就是说,通常情况下,你可以相信对象的状态不会在方法调用之间发生变化。这是一个非常安全的假设,你可以只在代码末尾使用不变性检查来编写代码。
**2**. 接下来禁用后置条件检查,当你进行合理的单元测试以验证方法是否返回了适当的值时。因为不变性检查是观察对象的状态,后置条件检查仅在方法期间验证计算结果,因此可能会被丢弃,以便进行单元测试。单元测试不会像运行时后置条件检查那样安全,但是它可能已经足够了,特别是如果你对代码有信心的话。
**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管,它可能没有调用**invariant()**那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。
**4**. 最后,万不得已,禁用前置条件检查。这是最不安全、最不明智的选择,因为尽管你知道并且可以控制你自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,**A**在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B) **并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。
不应该直接删除检查的代码,因为只需要禁用检查(添加注释)。这样如果发现错误,你可以轻松地恢复检查以快速发现问题。
#### DBC + 单元测试
####
<!-- Test-Driven Development -->。
## 测试驱动开发