敏捷技能修炼笔记一
第一部分 最关键的小舵板### 第一章 意图导向编程#### 1.1 意图导向编程:一个实例在这个实例中,需要写些代码。我们需要创建一个服务程序,它接受一个业务交易,然后提交。我们决定(不管是正确或错误)先简单创建一个对象,这个对象只包含一个公共方法来完成这件事。
具体需求如下:
- 1:交易信息开始于一串标准ASCII字符串。
- 2:这个信息字符申必须转换成一个字符串的数组,数组存放的值是此次交易用到的领域语言(domain language)中所包含的词汇元素(token)。
- 3:每一个词汇元素必须标准化(第一个字母大写,其余字母小写,空格和非字母数字的符号都要删掉)。
- 4:包含超过150个词汇元素的交易,应该采用不同于小型交易的方式(不同的算法)来提交,以提高效率。
- 5:如果提交成功,API方法应该返回”true”;失败,则应该返回”false”。
这里并没有涉及某些细节(比如提交的算法具体是什么),这样就可以只关注我们感兴趣的实践。
有一点是应该充分利用的:在学习一种程序语言编码的时候,我们会把问题分解为一系到的功能性步骤,在这个过程中,我们的思维不断地得到锻炼。随着代码写得越来越多,我们解决这类问题的能力也变得越来越强。
继续考虑前面的那个问题,需求列出的每一点要求都代表一个功能性步骤。在写代码的过程中,我们会按照一定的顺序,有意识地去完成每一点要求。而意图导向编程是指,先假设当前这个对象中,已经有了一个理想方法,它可以准确无误地完成你想做的事情,而不是直接盯着每一点要求来编写代码。先问问自己:”假如这个理想的方法已经存在,它应该具有什么样的输入参数,返回什么值?还有,对我来说,什么样的名字最符合它的意义?”
现在,因为这个方法并没有实际存在,所以除了你自己的最终意图之外,没有任何其他东西的约束(因此,你是”意图在主导编程”)。试试写出代码,像下面这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//java code public class Transaction { public Boolean commit (String command ) { Boolean result = true ; String[] tokens = tokenize ( command ); normalizeTokens( tokens ); if (isALargeTransaction(tokens)){ result = processLargeTransaction (tokens); } else { result = processSmallTransaction (tokens); } return result ; } }
书中此例所用的代码被包装到类Transcation中,我觉得对于当前的情况,不需要引入类来干扰问题的说明,只需要关注于实现的方法即可,同时,由于java语言的声明中就包含了返回值类型,所以,可以改进如下: 1 2 3 4 5 6 7 8 9
//java code Boolean commit(String command) { String[] tokens = tokenize (command); normalizeTokens(tokens); if (isALargeTransaction(tokens)){ return processLargeTransaction(tokens); } return processSmallTransaction(tokens); }
可以看到, - 第2行:commit方法中的参数类型String,与我们需求1的要求交易信息开始于一串标准ASCII字符串相对应。
- 第3行:tokenize方法及其返回值类型。与我们需求2的要求这个信息字符申必须转换成一个字符串的数组,数组存放的值是此次交易用到的领域语言(domain language)中所包含的词汇元素(token)相对应。
- 第4行: normalizeTokens(tokens)方法,与我们需求3每一个词汇元素必须标准化相对应。
- 第5-8行: 则用:isALargeTransaction方法进行交易类型的判断。并分别用processLargeTransaction与processSmallTransaction两个处理函数来对不同交易类型进行处理。同时,返回值也满足需求5对返回值类型的要求。
同时,采用意图导向编程,对于不同的编程语言也是同构的,以下列出几种常用编程语言的实现。
1
2
3
4
5
6
7
8
9
//c++ code bool commit(std::string command) { std::vector[std::string](std::string) tokens = tokenize(command); normalizeTokens(tokens); if (isALargeTransaction(tokens)){ return processLargeTransaction(tokens); } return processSmallTransaction(tokens); }
对于python或javascript之类的语言,函数定义中有包含返回值类型的,则可以加入一个返回值变量以让代码意思更清晰易懂: 1 2 3 4 5 6 7 8 9 10 11
#python code def commit (command ): result = true tokens = tokenize(command) normalizeTokens(tokens) if isALargeTransaction(tokens): result = processLargeTransaction(tokens) else: result = processSmallTransaction(tokens)
return result
1
2
3
4
5
6
7
8
9
10
11
12
13
//javascript code
commit(command){
var result = true;
tokens = tokenize(command);
normalizeTokens(tokens);
if (isALargeTransaction(tokens)){
result = processLargeTransaction(tokens);
}else{
result = processSmallTransaction(tokens);
}
return result;
}
commit()方法是我们为这个对象定义的应用程序接口(API)。当然,它是公共方法,这样才能交给客户端对象,为它们提供服务。所有其他方法(tokenize()、isALarge-Transaction()、processLargeTransaction()和processSma11Transaction())都不属于这个对象API的一部分,而仅仅是实现过程中的功能性步骤。因此,它们常常称为”辅助方法”(helper methods)。现在,我们先把它们当做私有方法(稍后我们会发现,这样做并不总是完全正确的)。重点是,它们是这个服务内部实现的一部分,而不是外界需要调用的接口。
并且,它们现在还没有真正存在。如果现在尝试编译一下代码,编译器肯定会报告它们并不存在(不过……我们将试这样能,让编译器帮助我们列出后续工作的待办事项清单。 为了代码编译通过,接下来我们必须要创建它们。
用这样的编码方式,我们可以把精力集中在如何分解最终目标,以及那些全局性的问题上。例如,我们必须考虑在实现语言中,用到的这个string数组是引用传递还是复制传递(很明显,我们猜想该语言是引用传递的;否则,我们就得把tokens作为nozmalizeTokens()函数的返回值保留下来)。至于其他单个步骤的实现细节,我们这里先不考虑。
这样做的意义是什么呢?我们很快会了解到,它有很多好处。但在此之前,让我们先确认一件很重要的事:这样做其实一点都不难。你的时间表里没有因此而增加更多的工作,与直接把所有代码写到一个很长的方法里(就像我们以前写程序一样,一大堆逻辑结构代码)的工作量是基本差不多的。仅有的少许不同,是我们思考的方式以及编码的顺序。
这非常重要。理论上,好的实践应该是在任何时候都可以实行,并且能够推广到整个团队,成为那些可以一直贯彻执行的守则。事实上,只有当这些实践的执行成本很低(或者说,几乎零成本)的时候,才有可能做到这点。
1.2优点还是老问题,用这样的方式编程意义何在呢?
有时候,这样一个小小的问题所引出的答案会令你惊叹不已。我们先把这些优点汇总罗列出来,再——进行分析。 如果遵循意图导向编程的方式,那么代码将会:
- 更加内聚(职责单一)
- 更加可读和清晰。
- 更易于调试。
- 更易于重构和优化, 所以只做最少的设计,满足当前需要。
- 更易于单元翻试。 同时,从这些优点中还会延伸出其他优点:代码更易于修改和扩展。其他还有:
- 模式更容易应用到代码中。
- 创建的方法可以容易地从一个类移动到另一个类。
- 代码更易于维护。
1.2.1方法的内聚性代码的质量标准之一,就是内聚性。它促使代码更易于理解、更易于扩展和修改。通常 情况下,我们希望软件实体(software entity)具备单一的特性,换句话说,一个实体只为了 ——个目的或原因而存在。
以类为例。一个类应该根据它的职责来定义,并且每个类通常应该只有一个职责。类的内部包含方法、状态以及与其他对象之间的关系,正是它们使得类的职责能够被正确地履行。在一个类的内部,如果各个方面都紧密相关,并且都在这个类的唯一职责范围之内,那么我们就说这个类的内聚性很强。 你也许会对前面的例子提出异议,我们的代码中有些部分的职责实际上是不同的,它们应该放到不同的类中。或许,把它们完全梳理清楚是一件挺棘手的事情。但是,即使无法做到完美无缺,如果遵循意图导向编程的方式,在绝大多数情况下,仍然可以达到一定程度上的内聚性。 方法的内聚也是关于单一性的问题,不过关注点是在函数上。如果一个方法只实现整体职能中一个单独的功能点,我们就说这个方法的内聚性很强。人的思维方式是单线程的,当人们开始”多任务”的时候,他们实际上只是快速地在多个任务之间进行切换而已;我们仍旧习惯于一次只思考一件事情。意图导向编程正是利用这一事实,用我们思维链条单一性的特点去创建同样具备单一性的内聚方法。 意图导向编程能带来诸多好处的一个很重要的原因,正是方法的内聚性。
1.2.2 可读性和表达性让我们再回过头来看看最初的代码示例,观察一下它的可读性怎样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//java code
public class Transaction {
public Boolean commit (String command ) {
Boolean result = true ;
String[] tokens = tokenize ( command );
normalizeTokens( tokens );
if (isALargeTransaction(tokens)){
result = processLargeTransaction (tokens);
} else {
result = processSmallTransaction (tokens);
}
return result ;
}
}
代码基本上是这样”讲”的:”我们获得一个指令,准备提交。先对指令进行分词,把 词汇元素标准化,然后根据得到的词汇元素集合的大小,决定采用大型事务的机制还是小型事务的机制进行处理。最后,返回处理结果。”
因为没有涉及每一步是”如何做”,而仅仅是”做什么”,所以只要快速地读一遍方法, 就可以很容易地了解到整体流程是如何工作的。有时候,我们需要的正是快速地了解事物在 如何运作。
这种方式提供了很好的可读性,表达也很清晰。值得注意的一点是,我们并没有在代码 中包含任何注释,但读者很容易了解到其中的意义。这是因为我们想通过注释表达的东西, 都已包含在方法的名称中。
当然,注释也是一种清晰的表达方法,但问题是,编译器看不到。同时,很多不相信注释准确性的程序员也会忽略它们。如果注释在代码中存在了很久,它就会变得不可靠,原因我们都很清楚:代码可能已经改变了,但注释或许并没有更新。如果过度相信注释,它可 能会误导我们,也没有其他简单的方法去验证是不是这样。我们只能被迫去调查代码,注释 的表达功能就这样蒸发掉了。因此,注释往往会被程序员忽略,失去它本来的作用。
回到我们讨论的主题,在意图导向编程中,组织一个方法的时候应该包括所有的流程步骤,但很少或者完全不用涉及实际的步骤实现。在某种意义上,这是另外一种形式的内聚: 把处理某件事的流程和具体做事的实现方式区分开。
另一件促使代码更易读和清晰的事情是,当我们创建代码实体时,它们的名字应该能表达出创建的目的。如果方法是内聚的,用一两个词来完整描述它们是做什么的就变得很容易,而不用在名称中加入许多下划线、”and”和”or”。同时,在我们给方法命名的时候,它们还没有真正存在,这促使我们去选择能表达自己想法的名字。这种名字揭示了命名背后的意图,我们把它们叫做”表露意图的名称”(intention-revealing names)。有一些命名是我们应该尽量避免的,比如有些名字,当你明白功能是什么之后,它很容易理解;但在别人解释之前,又容易引起误解。
注释会成为代码的一种”臭味”虽然我们没有断言说,不应该写注释,但有些注释确实是代码的一种”臭味”。例如,假如我们写了这样的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Transaction (
public Boolean commit ( String command ) (
Boolean result = true :
come hore
bone coue liece
Some more code here
Even some more code here that sets tokens
Some code here that normalizes Tokens
Some more code here that normalizes
some more coue use cnat normalizes ' rokens
Even more code here that normalizes Tokens
Code that determines if you have a large transaction
Set 1t = true if you do
if ( 1t ){
Some code here to process large transactions
More code here to process large transactions
} else {
Some code here to process small transactions
有效期限
More code here to process small transactions
}
return result ;
}
}
当看到这段代码时,你可能会说:”天哪,我可不明白它在干什么;让我们加些注释吧。”然后在上面加了些东西:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Transaction {
public Boolean commit ( String command ) f
Boolean result = true ;
// tokenize the string
Some code here
Some more code here
Even some more code here that sets tokens
//normalise the tokens I am there that normalizes Tokens normalises Tokens
some cooe code here Some more c
The more code here that normalizes Tokens
see it you have a large transaction Code that determines
if you have a large transaction Cet Ite true if you do
if ( it ) {
process large transaction It ,
to process large transactions 公民身份号码
Some Here code here to process large transactions
) else (
process small transaction time code here to process small transactions es ,
code here to process small transactions
return result ;
}
}
注意到了吗?当写完代码之后,我们加入注释来描述做了什么。如果遵循意图导向编辑 的方式,这些注释就不再需要。前面提到的替代注释的方法其实更有用,因为编译的时候 它们(代码本身)一定是最新的。
1.2.3 调试在Net Objectives公司的培训课程中,我们往往会问学员一个问题,他们是否花费了大量 的时间来修改代码中的错误。除了那些已经上过我们课程的人外,大部分人的答案都是”是的”。这也是让软件开发变得棘手的一个主要原因。
我们向他们指出,在调试过程中,寻找系统中的错误所在才是最花时间的部分。一旦错误装定位,修复工作反而不是什么太大的问题。几乎所有的学员听到这点,都会立刻点点头。由此可见,在创建需要调试的代码时,真正的技巧是用一切手段让代码中的错误容易再现。当然,我们应该尽量小心,在开始写代码的时候就别犯错。但是,哪怕你做到了尽善尽美,往往还有别人要来改动这段代码。 当遵循意图导向编程的时候,我们更容易做到让一个方法只做一件事。这时候,如果系统中有些部分不能正常工作了,试试下面的方法:
- (1)通读一遍整个方法,看看所有事情是怎么运作的。
- (2)对无法正常工作的部分,检查一下辅助方法的细节有什么问题。 相对于费力地查阅一大段复杂的代码,尤其是有些代码还可能涉及许多不相关的系统的方方面面,这种调试方法发现代码借误的速度要快得多。 例如,调试遗留系统是个苦差事,因为问题往往是多种原因共同引起的。其中一个很大的因素是它的代码常常是一大段一大段写出来的。因此,你最后不得不把代码打印出来,准备好各种彩色荧光笔,根据代码是做什么的来进行逻辑分块。”我会把数据库部分标为黄色, 业务逻辑标为蓝色……”,这样做既艰苦又容易出错,还很枯燥,对程序员来说,这可不是打发时间的好方式。还是让计算机来完成这些让人厌烦的工作吧。
1.2.4 重构和增强在创建系统的最初阶段,我们很难准确地知道设计应该做到什么程度,复杂度又是多少。由于复杂度是引起系统难以修改的原因之一,所以,如果可以的话,我们希望做最少的设计,仅仅增加那些真正需要的东西,让系统正常运行。
尽管如此,当我们实际做的时候往往会犯下很多错误,真正需要的功能并没有正确运行。或者即使我们的系统终于正常工作了,来自客户、利益相关人员或者市场的需求又更改了规则。 基于这点,我们常常需要这样做: ·重构系统(保持系统行为不变的情况下,更改它的结构)。
增强系统(增加或修改系统的行为以符合新的需求)。
重构通常认为是”清理”刚开始时写得糟糕的代码。有时候项目十万火急,我们没有认真顾及代码质量,草草维护和修改,代码就在这个过程中变得腐朽不堪。或者,当我们对整个程序有了更深的了解,清楚地知道代码需要重新设计时,也可以用到重构。
1999年,Martin Fowler写了一本经典的书籍《Refactoring》9,这本书记载了各种不同 的、在保持系统行为不变的情况下,如何修改代码的方法,并给每种方法命了名(通常叫做一次”迁移”)。
在学习重构原则的时候,大多数人首先学到的一个迁移方法叫做提炼函数(Extract Method);把一部分代码从一个巨大的方法中抽取出来,放到一个属于它自己的新方法中。而在原来代码中的那个位置直接调用这个新方法。由于原来方法的一部分临时变量也需要迁称到新方法中,所以需要多个步骤才能完成一个函数的提炼。
该书在讲述许多其他的重构迁移方法时,开始都有一句基本说明”在开始这个方法之前,你需要不断地采用’提炼函数’方法,直到所有的方法都是内聚的。”如果你遵循意图导向编程,你会发现你已经做到了这点。Ken Pugh在《Prefactoring)一书中也广泛地研究了像意图导向编程这样简单而有效的方式是如何更好地帮助我们编写程序的。
如果你已经做到了书中所写的那样,在写代码时习惯性地先”预构”(prefactored),则进行代码重构的时候,困难就要少很多。代码的方法内聚性越强,重构就越容易。
同样,意图导向编程能够让系统在以后增强时变得更加容易。让我们回到前面交易处理程序的例子。
设想一下6个月后,这些代码已经成为产品,一个新的需求加入进来:由于与这个业务交易系统有交互的某些第三方程序的原因,我们必须对某些旧版本的词汇元素进行转换(需要转换的词汇元素可能有很多)以支持整个系统的运行。现在,命令字符串中可能包含领域语言不再兼容的词汇元素,为了处理这种情况,我们必须再”更新”一下所有词汇元素。
在这种情况下,我们只需做出一个小小的修改。你大可拍着胸脯说:”没问题。”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//java code
public class Transaction {
public Boolean commit (String command ) {
Boolean result = true ;
String[] tokens = tokenize ( command );
normalizeTokens( tokens );
updateTokens(tokens)
if (isALargeTransaction(tokens)){
result = processLargeTransaction (tokens);
} else {
result = processSmallTransaction (tokens);
}
return result ;
}
}
接下来,就是写出updateTokens()方法,在这个过程中我们会发现,新代码对原新的其他部分造成破坏的可能性是非常小的。事实上,修改这段代码里的任何辅助方法,我们都可以信心十足地认为,我们仅仅改动了想要改变的部分。就像上面这样,内聚性让我们更容易进行封装。
1.2.5 单元测试在意图导向编程中,如果一个对象尚未完成,我们不会去扩充它的外部接口;相反,要确保在代码实现之前就把接口定义清楚。事实上,我们希望遵照模式所推荐的关于设计的基本建议:使用服务的客户端,在设计时应当遵照的是它的接口定义,而不是服务中的具体实现。
所以,至少在刚开始的时候,最好能把这些”辅助方法”都隐藏起来,因为它们都不是服务对外提供的API。我们不希望将来某一天,外部其他对象与这些方法发生什么关联(无 论是运作流程上的关联,还是直接存在引用关系)。这样可以让我们做到,在问题的方案细节发生变化的时候,无须修改系统中其他用到该对象的地方。
尽管如此,如果我们把所有的辅助方法都变成私有方法,又似乎不利于对象测试(参见 图1-1)。
<img
lazyload
src=“/images/loading.svg”
data-src=“data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U2NyaXB0VHlwZT0iYXBwbGljYXRpb24vZWNtYXNjcmlwdCIgY29udGVudFN0eWxlVHlwZT0idGV4dC9jc3MiIGhlaWdodD0iMzA0cHgiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiIHN0eWxlPSJ3aWR0aDoyODBweDtoZWlnaHQ6MzA0cHg7YmFja2dyb3VuZDojRkZGRkZGOyIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMjgwIDMwNCIgd2lkdGg9IjI4MHB4IiB6b29tQW5kUGFuPSJtYWduaWZ5Ij48ZGVmcz48ZmlsdGVyIGhlaWdodD0iMzAwJSIgaWQ9ImYxeTIyc2V6bzQ3ajQ1IiB3aWR0aD0iMzAwJSIgeD0iLTEiIHk9Ii0xIj48ZmVHYXVzc2lhbkJsdXIgcmVzdWx0PSJibHVyT3V0IiBzdGREZXZpYXRpb249IjIuMCIvPjxmZUNvbG9yTWF0cml4IGluPSJibHVyT3V0IiByZXN1bHQ9ImJsdXJPdXQyIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgLjQgMCIvPjxmZU9mZnNldCBkeD0iNC4wIiBkeT0iNC4wIiBpbj0iYmx1ck91dDIiIHJlc3VsdD0iYmx1ck91dDMiLz48ZmVCbGVuZCBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJibHVyT3V0MyIgbW9kZT0ibm9ybWFsIi8+PC9maWx0ZXI+PC9kZWZzPjxnPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE4IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjI0MSIgeD0iMTMiIHk9IjE2LjcwOCI+5Zu+MS0xIOaEj+WbvuWvvOWQkee8lueoiyDnp4HmnInmlrnms5U8L3RleHQ+PCEtLU1ENT1bNDU1NWJiMWIwOTExYTVhZGYyOGNlNmRiZTYwZmVkMmVdCmNsYXNzIFRyYW5zYWN0aW9uLS0+PHJlY3QgY29kZUxpbmU9IjQiIGZpbGw9IiNGRUZFQ0UiIGZpbHRlcj0idXJsKCNmMXkyMnNlem80N2o0NSkiIGhlaWdodD0iMTM3LjYzMjgiIGlkPSJUcmFuc2FjdGlvbiIgc3R5bGU9InN0cm9rZTojQTgwMDM2O3N0cm9rZS13aWR0aDoxLjU7IiB3aWR0aD0iMjU5IiB4PSI3IiB5PSIyNy45NTMxIi8+PGVsbGlwc2UgY3g9Ijk3Ljc1IiBjeT0iNDMuOTUzMSIgZmlsbD0iI0FERDFCMiIgcng9IjExIiByeT0iMTEiIHN0eWxlPSJzdHJva2U6I0E4MDAzNjtzdHJva2Utd2lkdGg6MS4wOyIvPjxwYXRoIGQ9Ik0xMDAuMDkzOCwzOS42MjUgQzk5LjE1NjMsMzkuMTg3NSA5OC41NjI1LDM5LjA0NjkgOTcuNjg3NSwzOS4wNDY5IEM5NS4wNjI1LDM5LjA0NjkgOTMuMDYyNSw0MS4xMjUgOTMuMDYyNSw0My44NDM4IEw5My4wNjI1LDQ0Ljk2ODggQzkzLjA2MjUsNDcuNTQ2OSA5NS4xNzE5LDQ5LjQzNzUgOTguMDYyNSw0OS40Mzc1IEM5OS4yODEzLDQ5LjQzNzUgMTAwLjQzNzUsNDkuMTQwNiAxMDEuMTg3NSw0OC41OTM4IEMxMDEuNzY1Niw0OC4xODc1IDEwMi4wOTM4LDQ3LjczNDQgMTAyLjA5MzgsNDcuMzQzOCBDMTAyLjA5MzgsNDYuODkwNiAxMDEuNzAzMSw0Ni41IDEwMS4yMzQ0LDQ2LjUgQzEwMS4wMTU2LDQ2LjUgMTAwLjgxMjUsNDYuNTc4MSAxMDAuNjI1LDQ2Ljc2NTYgQzEwMC4xNzE5LDQ3LjI1IDEwMC4xNzE5LDQ3LjI1IDk5Ljk4NDQsNDcuMzQzOCBDOTkuNTYyNSw0Ny42MDk0IDk4Ljg3NSw0Ny43MzQ0IDk4LjEwOTQsNDcuNzM0NCBDOTYuMDYyNSw0Ny43MzQ0IDk0Ljc2NTYsNDYuNjQwNiA5NC43NjU2LDQ0LjkzNzUgTDk0Ljc2NTYsNDMuODQzOCBDOTQuNzY1Niw0Mi4wNjI1IDk2LjAxNTYsNDAuNzUgOTcuNzUsNDAuNzUgQzk4LjMyODEsNDAuNzUgOTguOTM3NSw0MC45MDYzIDk5LjQwNjMsNDEuMTU2MyBDOTkuODkwNiw0MS40Mzc1IDEwMC4wNjI1LDQxLjY1NjMgMTAwLjE1NjMsNDIuMDYyNSBDMTAwLjIxODgsNDIuNDY4OCAxMDAuMjUsNDIuNTkzOCAxMDAuMzkwNiw0Mi43MTg4IEMxMDAuNTMxMyw0Mi44NTk0IDEwMC43NjU2LDQyLjk2ODggMTAwLjk4NDQsNDIuOTY4OCBDMTAxLjI1LDQyLjk2ODggMTAxLjUxNTYsNDIuODI4MSAxMDEuNjg3NSw0Mi42MDk0IEMxMDEuNzk2OSw0Mi40NTMxIDEwMS44MjgxLDQyLjI2NTYgMTAxLjgyODEsNDEuODQzOCBMMTAxLjgyODEsNDAuNDIxOSBDMTAxLjgyODEsMzkuOTg0NCAxMDEuODEyNSwzOS44NTk0IDEwMS43MTg4LDM5LjcwMzEgQzEwMS41NjI1LDM5LjQzNzUgMTAxLjI4MTMsMzkuMjk2OSAxMDAuOTg0NCwzOS4yOTY5IEMxMDAuNjg3NSwzOS4yOTY5IDEwMC40ODQ0LDM5LjM5MDYgMTAwLjI2NTYsMzkuNzAzMSBMMTAwLjA5MzgsMzkuNjI1IFogIiBmaWxsPSIjMDAwMDAwIi8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTIiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iNjkiIHg9IjExOC4yNSIgeT0iNDguMTA3NCI+VHJhbnNhY3Rpb248L3RleHQ+PGxpbmUgc3R5bGU9InN0cm9rZTojQTgwMDM2O3N0cm9rZS13aWR0aDoxLjU7IiB4MT0iOCIgeDI9IjI2NSIgeTE9IjU5Ljk1MzEiIHkyPSI1OS45NTMxIi8+PGxpbmUgc3R5bGU9InN0cm9rZTojQTgwMDM2O3N0cm9rZS13aWR0aDoxLjU7IiB4MT0iOCIgeDI9IjI2NSIgeTE9IjY3Ljk1MzEiIHkyPSI2Ny45NTMxIi8+PGVsbGlwc2UgY3g9IjE4IiBjeT0iNzguOTUzMSIgZmlsbD0iIzg0QkU4NCIgcng9IjMiIHJ5PSIzIiBzdHlsZT0ic3Ryb2tlOiMwMzgwNDg7c3Ryb2tlLXdpZHRoOjEuMDsiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIxMzQiIHg9IjI3IiB5PSI4Mi4xNjM2Ij5Cb29sZWFuIGNvbW1pdChTdHJpbmcpPC90ZXh0PjxyZWN0IGZpbGw9IiNGMjRENUMiIGhlaWdodD0iNiIgc3R5bGU9InN0cm9rZTojQzgyOTMwO3N0cm9rZS13aWR0aDoxLjA7IiB3aWR0aD0iNiIgeD0iMTUiIHk9Ijg4Ljc1NzgiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMSIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIxMjgiIHg9IjI3IiB5PSI5NC45NjgzIj5TdHJpbmcgdG9rZW5pemUoU3RyaW5nKTwvdGV4dD48cmVjdCBmaWxsPSIjRjI0RDVDIiBoZWlnaHQ9IjYiIHN0eWxlPSJzdHJva2U6I0M4MjkzMDtzdHJva2Utd2lkdGg6MS4wOyIgd2lkdGg9IjYiIHg9IjE1IiB5PSIxMDEuNTYyNSIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjEzOSIgeD0iMjciIHk9IjEwNy43NzI5Ij5ub3JtYWxpemVUb2tlbnMoU3RyaW5nKTwvdGV4dD48cmVjdCBmaWxsPSIjRjI0RDVDIiBoZWlnaHQ9IjYiIHN0eWxlPSJzdHJva2U6I0M4MjkzMDtzdHJva2Utd2lkdGg6MS4wOyIgd2lkdGg9IjYiIHg9IjE1IiB5PSIxMTQuMzY3MiIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjEyMiIgeD0iMjciIHk9IjEyMC41Nzc2Ij51cGRhdGVUb2tlbnMoU3RyaW5nKTwvdGV4dD48cmVjdCBmaWxsPSIjRjI0RDVDIiBoZWlnaHQ9IjYiIHN0eWxlPSJzdHJva2U6I0M4MjkzMDtzdHJva2Utd2lkdGg6MS4wOyIgd2lkdGg9IjYiIHg9IjE1IiB5PSIxMjcuMTcxOSIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjIwMyIgeD0iMjciIHk9IjEzMy4zODIzIj5Cb29sZWFuIGlzQWxhcmdlVHJhbnNjYXRpb24oU3RyaW5nKTwvdGV4dD48cmVjdCBmaWxsPSIjRjI0RDVDIiBoZWlnaHQ9IjYiIHN0eWxlPSJzdHJva2U6I0M4MjkzMDtzdHJva2Utd2lkdGg6MS4wOyIgd2lkdGg9IjYiIHg9IjE1IiB5PSIxMzkuOTc2NiIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjIzMyIgeD0iMjciIHk9IjE0Ni4xODciPkJvb2xlYW4gcHJvY2Vzc0xhcmdlVHJhbnNhY3Rpb24oU3RyaW5nKTwvdGV4dD48cmVjdCBmaWxsPSIjRjI0RDVDIiBoZWlnaHQ9IjYiIHN0eWxlPSJzdHJva2U6I0M4MjkzMDtzdHJva2Utd2lkdGg6MS4wOyIgd2lkdGg9IjYiIHg9IjE1IiB5PSIxNTIuNzgxMyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjExIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjIzMyIgeD0iMjciIHk9IjE1OC45OTE3Ij5Cb29sZWFuIHByb2Nlc3NTbWFsbFRyYW5zYWN0aW9uKFN0cmluZyk8L3RleHQ+PCEtLU1ENT1bZjUxYjA4ZjFlNTY0Njg4OTkwNzFlNzQzZTZmNjdhYjhdCmNsYXNzIFRyYW5zY2F0aW9uVGVzdC0tPjxyZWN0IGZpbGw9IiNGRUZFQ0UiIGZpbHRlcj0idXJsKCNmMXkyMnNlem80N2o0NSkiIGhlaWdodD0iNDgiIGlkPSJUcmFuc2NhdGlvblRlc3QiIHN0eWxlPSJzdHJva2U6I0E4MDAzNjtzdHJva2Utd2lkdGg6MS41OyIgd2lkdGg9IjEyNyIgeD0iNzMiIHk9IjI0Mi45NTMxIi8+PGVsbGlwc2UgY3g9Ijg4IiBjeT0iMjU4Ljk1MzEiIGZpbGw9IiNBREQxQjIiIHJ4PSIxMSIgcnk9IjExIiBzdHlsZT0ic3Ryb2tlOiNBODAwMzY7c3Ryb2tlLXdpZHRoOjEuMDsiLz48cGF0aCBkPSJNOTAuMzQzOCwyNTQuNjI1IEM4OS40MDYzLDI1NC4xODc1IDg4LjgxMjUsMjU0LjA0NjkgODcuOTM3NSwyNTQuMDQ2OSBDODUuMzEyNSwyNTQuMDQ2OSA4My4zMTI1LDI1Ni4xMjUgODMuMzEyNSwyNTguODQzOCBMODMuMzEyNSwyNTkuOTY4OCBDODMuMzEyNSwyNjIuNTQ2OSA4NS40MjE5LDI2NC40Mzc1IDg4LjMxMjUsMjY0LjQzNzUgQzg5LjUzMTMsMjY0LjQzNzUgOTAuNjg3NSwyNjQuMTQwNiA5MS40Mzc1LDI2My41OTM4IEM5Mi4wMTU2LDI2My4xODc1IDkyLjM0MzgsMjYyLjczNDQgOTIuMzQzOCwyNjIuMzQzOCBDOTIuMzQzOCwyNjEuODkwNiA5MS45NTMxLDI2MS41IDkxLjQ4NDQsMjYxLjUgQzkxLjI2NTYsMjYxLjUgOTEuMDYyNSwyNjEuNTc4MSA5MC44NzUsMjYxLjc2NTYgQzkwLjQyMTksMjYyLjI1IDkwLjQyMTksMjYyLjI1IDkwLjIzNDQsMjYyLjM0MzggQzg5LjgxMjUsMjYyLjYwOTQgODkuMTI1LDI2Mi43MzQ0IDg4LjM1OTQsMjYyLjczNDQgQzg2LjMxMjUsMjYyLjczNDQgODUuMDE1NiwyNjEuNjQwNiA4NS4wMTU2LDI1OS45Mzc1IEw4NS4wMTU2LDI1OC44NDM4IEM4NS4wMTU2LDI1Ny4wNjI1IDg2LjI2NTYsMjU1Ljc1IDg4LDI1NS43NSBDODguNTc4MSwyNTUuNzUgODkuMTg3NSwyNTUuOTA2MyA4OS42NTYzLDI1Ni4xNTYzIEM5MC4xNDA2LDI1Ni40Mzc1IDkwLjMxMjUsMjU2LjY1NjMgOTAuNDA2MywyNTcuMDYyNSBDOTAuNDY4OCwyNTcuNDY4OCA5MC41LDI1Ny41OTM4IDkwLjY0MDYsMjU3LjcxODggQzkwLjc4MTMsMjU3Ljg1OTQgOTEuMDE1NiwyNTcuOTY4OCA5MS4yMzQ0LDI1Ny45Njg4IEM5MS41LDI1Ny45Njg4IDkxLjc2NTYsMjU3LjgyODEgOTEuOTM3NSwyNTcuNjA5NCBDOTIuMDQ2OSwyNTcuNDUzMSA5Mi4wNzgxLDI1Ny4yNjU2IDkyLjA3ODEsMjU2Ljg0MzggTDkyLjA3ODEsMjU1LjQyMTkgQzkyLjA3ODEsMjU0Ljk4NDQgOTIuMDYyNSwyNTQuODU5NCA5MS45Njg4LDI1NC43MDMxIEM5MS44MTI1LDI1NC40Mzc1IDkxLjUzMTMsMjU0LjI5NjkgOTEuMjM0NCwyNTQuMjk2OSBDOTAuOTM3NSwyNTQuMjk2OSA5MC43MzQ0LDI1NC4zOTA2IDkwLjUxNTYsMjU0LjcwMzEgTDkwLjM0MzgsMjU0LjYyNSBaICIgZmlsbD0iIzAwMDAwMCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEyIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9Ijk1IiB4PSIxMDIiIHk9IjI2My4xMDc0Ij5UcmFuc2NhdGlvblRlc3Q8L3RleHQ+PGxpbmUgc3R5bGU9InN0cm9rZTojQTgwMDM2O3N0cm9rZS13aWR0aDoxLjU7IiB4MT0iNzQiIHgyPSIxOTkiIHkxPSIyNzQuOTUzMSIgeTI9IjI3NC45NTMxIi8+PGxpbmUgc3R5bGU9InN0cm9rZTojQTgwMDM2O3N0cm9rZS13aWR0aDoxLjU7IiB4MT0iNzQiIHgyPSIxOTkiIHkxPSIyODIuOTUzMSIgeTI9IjI4Mi45NTMxIi8+PCEtLU1ENT1bZmRlYTdjZTdiNDc3OGNmNmEzNWUwMGVjODgxN2U5ZjhdCnJldmVyc2UgbGluayBUcmFuc2FjdGlvbiB0byBUcmFuc2NhdGlvblRlc3QtLT48cGF0aCBjb2RlTGluZT0iMTQiIGQ9Ik0xMzYuNSwxODYuMzE3MSBDMTM2LjUsMjA3LjI3MTkgMTM2LjUsMjI3Ljg2NDEgMTM2LjUsMjQyLjg1NzggIiBmaWxsPSJub25lIiBpZD0iVHJhbnNhY3Rpb24tYmFja3RvLVRyYW5zY2F0aW9uVGVzdCIgc3R5bGU9InN0cm9rZTojQTgwMDM2O3N0cm9rZS13aWR0aDoxLjA7Ii8+PHBvbHlnb24gZmlsbD0ibm9uZSIgcG9pbnRzPSIxMjkuNSwxODYuMjg1MSwxMzYuNSwxNjYuMjg1MSwxNDMuNSwxODYuMjg1MSwxMjkuNSwxODYuMjg1MSIgc3R5bGU9InN0cm9rZTojQTgwMDM2O3N0cm9rZS13aWR0aDoxLjA7Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iMjYiIHg9IjEzNy41IiB5PSIyMDkuMDIiPua1i+ivlTwvdGV4dD48IS0tTUQ1PVs4NmYxZWFmOTk3NzAzZmMxNzkxMWI5YjlkNzA1OWI2YV0KQHN0YXJ0dW1sDQp0aXRsZSDlm74xLTEg5oSP5Zu+5a+85ZCR57yW56iLIOengeacieaWueazlSANCg0KDQpjbGFzcyBUcmFuc2FjdGlvbnsNCgkgICtCb29sZWFuIGNvbW1pdChTdHJpbmcpDQoJICAtU3RyaW5nIHRva2VuaXplKFN0cmluZykNCgkgIC1ub3JtYWxpemVUb2tlbnMoU3RyaW5nKQ0KCSAgLXVwZGF0ZVRva2VucyhTdHJpbmcpDQoJICAtQm9vbGVhbiBpc0FsYXJnZVRyYW5zY2F0aW9uKFN0cmluZykNCgkgIC1Cb29sZWFuIHByb2Nlc3NMYXJnZVRyYW5zYWN0aW9uKFN0cmluZykNCgkgIC1Cb29sZWFuIHByb2Nlc3NTbWFsbFRyYW5zYWN0aW9uKFN0cmluZykgDQp9DQoNClRyYW5zYWN0aW9uIDx8LWRvd24tIFRyYW5zY2F0aW9uVGVzdDrmtYvor5UNCkBlbmR1bWwNCgpQbGFudFVNTCB2ZXJzaW9uIDEuMjAyMS45YmV0YTIoVW5rbm93biBjb21waWxlIHRpbWUpCihHUEwgc291cmNlIGRpc3RyaWJ1dGlvbikKSmF2YSBSdW50aW1lOiBKYXZhKFRNKSBTRSBSdW50aW1lIEVudmlyb25tZW50CkpWTTogSmF2YSBIb3RTcG90KFRNKSA2NC1CaXQgU2VydmVyIFZNCkRlZmF1bHQgRW5jb2Rpbmc6IFVURi04Ckxhbmd1YWdlOiBlbgpDb3VudHJ5OiBVUwotLT48L2c+PC9zdmc+”
私有方法无法被单元测试调用,因此我们只能针对commit()方法来写测试,这意味着必须在一个单独的测试用例中测试对象的整体行为。这样的测试可能比你想象的要复杂得多,同时,在编写测试的过程中,有多种因素都有可能导致测试失败。我们可不想发生这些的情况。 也许我们可以解决这个难题。但不管怎样,把系统整体行为的各个不同方面区分开来。放到独立的方法中,至少在理论上让每个方法具备了可测试性,因为它们彼此之间不再耦合。就像使用这个类的客户端只与这个类的接口发生关联一样,在类的内部,AP1方法的实现只与辅助方法的接口发生关联。 那么,我们怎样来处理私有方法不能测试的问题呢?一般来说,有三个可行的方法: