行动与要求 我们的Graft数据库有一个接受用户命令的telnet-like接口。参照下面测试addComment的两个简单地变化: 在Test1里面,当对addComment命令进行测试时,直接使用getComments这个命令来检查结果状态。在整个测试过程中,只使用了一个单独的API入口点——subject。Test2是直接访问底层数据库实例并且使用其API来获取数据。与此同时,subject这个API也访问底层数据库。 因此Test1并不是真正意义上的“单元”测试,因为它还依赖另一个测试类。而Test2则更加专注并且写法更加简单,它直接访问目标数据结构,对源代码进行正确检查。 我们将继续讨论像Test1那样的测试,在同等级上,它会完成所有相同的操作,即基于同一级别的公共API对象测试会更好。这里的“更好”是指容易理解、更加重要和稳定可维护,因为它们不是耦合到内部实现的功能测试。 像Test2的这种测试则更常见,无论是直接访问底层(对象、属性、数据库……)还是通过模拟来直接对产生的副作用进行验证。这些技术往往会导致耦合且对测试难于维护,这种技术应该限制在“私有单元测试”中,切勿混合公共和私有单元测试”。
还有一件值得我们的注意的事情,Kent任何时候都会关注自己在做什么。专注意味着你会一心一意的做一件事情,不会受其他事情所影响,无论那件事情有多么重要或者只是简单修复。(备注:永远不要说永远)如果你在修复一个Bug的过程中还发现其他的事情,比如给一个类起更好的名字、删除已死的代码、修复一个未提交的Bug等。这些你都可以用列表记下来,等到手头的Bug修复成功后再去落实。 并行(Parallel)设计 并行设计意味着当改变一个设计的时候,你要尽可能的保持在原来的需求上渐渐添加新元素,直至转向新设计。在这个过程要不断地确保正确性,这需要花费更大的精力,当然这样做也会让其更加安全和容易实现可恢复重构。 一个典型的并行设计案例是使用NoSQL数据库替换RDBMS。开始时,你会把实现好的代码写入新的数据库中,然后再把它同时写进新旧这两个系统里面,并且从新的里面读取数据(也许这样就可以与旧的相比较进行验证),同时还在使用旧的数据库数据。接下来你将开始实际使用NoSQL数据库的数据,同时从旧DB读/写数据(这样你可以方便地切换回来)。只有当新的DB被证明正确无误时,才可以逐步删除旧的DB。 一个小型的并行设计案例是用对象替换方法参数,例如notifuComment方法: 步骤:
这样做的好处是,代码的正确性会得到保证并且可以随时工作,你可以在任何时候提交或停止。 可恢复的重构 对大规模的代码进行重构时,需要使用一些最佳实践来保证代码随时可被恢复。上面介绍的并行设计是被实践证明的,一种非常安全的重构步骤,在执行过程中不会破坏任何东西,代码的正确性也会得到保证。 在重构过程中,不同的人会有不同的测试策略。但是我想说的,程序员在开发代码时,应该有这样的理念:我因编写代码而得到报酬,而不是为了测试,所以我希望编写的代码能够达到一定水平以至于更少的被测试。(我认为,这种信心等级与工业标准相比还是比较高的,但这仅仅有点狂妄自大)如果我平时很少犯这种错误(比如在构造函数里面设置了错误的变量),就不用对它进行测试。我比较倾向做一些有意义测试错误,所以我会额外关心那种复杂条件下的逻辑结构。当在一个团队中编码时,我会改变测试策略,非常小心的对代码进行测试,最后对错误进行总结。 最后,我通过引用Kent的话:“how much testing to do”来结束这一主题。 对称守则 对称是一个非常抽象的概念,与沟通、简单和灵活相比更加具体,但仍然是普通的。在Kent的《Implementation Patterns》一书中指出,对称应该作为编程原则。 与不对称相比而言,代码的对称性还是比较容易把握的。它更容易去读和理解,所以对称代码会更加具体,再次引用Kent的话:
想象一下代码所要表达的想法,比如“从数据库中获取最后的更新文件”代码需要执行几次。如果方法名称和执行顺序都不同且彼此有很大的差别,那么这样的代码就是不对称的。问问自己:“这个方法是用来干嘛的”。一个对称代码的例子是保持代码在抽象层次上的一致性,比如方法。细心的读者可能已经注意到,一致性是对称的抽象层次,例如一致性的方法命名。但是,对称是抽象的,它涉及更多的想法,而不是规则(如“驼峰式”里的类名和方法命名规则)。
你可以在GitHub下载我们此次在俱乐部所开发的源码。 总结 我非常希望你们(亲爱的读者)能在平时的工作实践中运用这些价值观和设计原则,希望它们对你有用! 英文原文:Java Code Geeks |