有时你必须检测程序运行时间花在哪儿,从而看是否可以优化那一块的性能。剖析器可以找到这些导致程序慢的地方,因而你可以找到最轻松,最明显的方式加快程序运行速度。
剖析器收集的信息能显示程序哪一部分消耗内存,哪个方法最耗时。一些剖析器甚至能关闭垃圾回收,从而帮助限定内存分配的模式。
剖析器还可以帮助检测程序中的线程死锁。注意剖析和基准测试的区别。剖析关注的是已经运行在真实数据上的整个程序,而基准测试关注的是程序中隔离的片段,通常是去优化算法。
安装 Java 开发工具包(JDK)时会顺带安装一个虚拟的剖析器,叫做 VisualVM。它会被自动安装在与 javac 相同的目录下,你的执行路径应该已经包含该目录。启动 VisualVM 的控制台命令是:
> jvisualvm
运行该命令后会弹出一个窗口,其中包括一些指向帮助信息的链接。
当你在一个团队中工作时(包括尤其是开源项目),让每个人遵循相同的代码风格是非常有帮助的。这样阅读项目的代码时,不会因为风格的不同产生思维上的中断。然而,如果你习惯了某种不同的代码风格,那么记住项目中所有的风格准则对你来说可能是困难的。幸运的是,存在可以指出你代码中不符合风格准则的工具。
一个流行的风格检测器是 Checkstyle。查看本书 示例代码 中的 gradle.build 和 checkstyle.xml 文件中配置代码风格的方式。checkstyle.xml 是一个常用检测的集合,其中一些检测被注释掉了以允许使用本书中的代码风格。
运行所有风格检测的命令是:
gradlew checkstyleMain
一些文件仍然产生了风格检测警告,通常是因为这些例子展示了你在生产代码中不会使用的样例。
你还可以针对一个具体的章节运行代码检测。例如,下面命令会运行 Annotations 章节的风格检测:
gradlew annotations:checkstyleMain
尽管 Java 的静态类型检测可以发现基本的语法错误,其他的分析工具可以发现躲避 javac 检测的更加复杂的bug。一个这样的工具叫做 Findbugs。本书 示例代码 中的 build.gradle 文件包含了 Findbugs 的配置,所以你可以输入如下命令:
gradlew findbugsMain
这会为每一章生成一个名为 main.html 的报告,报告中会说明代码中潜在的问题。Gradle 命令的输出会告诉你每个报告在何处。
当你查看报告时,你将会看到很多 false positive 的情况,即代码没问题却报告了问题。我在一些文件中展示了不要做一些事的代码确实是正确的。
当我最初看到本书的 Findbugs 报告时,我发现了一些不是技术错误的地方,但能使我改善代码。如果你正在寻找 bug,那么在调试之前运行 Findbugs 是值得的,因为这将可能节省你数小时的时间找到问题。
单元测试能找到明显重要的 bug 类型,风格检测和 Findbugs 能自动执行代码重审,从而发现额外的问题。最终你走到了必须人为参与进来的地步。代码重审是一个或一群人的一段代码被另一个或一群人阅读和评估的众多方式之一。这最初看起来会使人不安,而且需要情感信任,但它的目的肯定不是羞辱任何人。
它的目标是找到程序中的错误,代码重审是最成功的能做到这点的途径之一。可惜的是,它们也经常被认为是“过于昂贵的”(有时这会成为程序员避免代码被重审时感到尴尬的借口)。
代码重审可以作为结对编程的一部分,作为代码签入过程的一部分(另一个程序员自动安排上审查新代码的任务)或使用群组预排的方式,即每个人阅读代码并讨论之。后一种方式对于分享知识和营造代码文化是极其有益的。
结对编程是指两个程序员一起编程的实践活动。通常来说,一个人“驱动”(敲击键盘,输入代码),另一人(观察者或指引者)重审和分析代码,同时也要思考策略。这产生了一种实时的代码重审。通常程序员会定期地互换角色。
结对编程有很多好处,但最显著的是分享知识和防止阻塞。最佳传递信息的方式之一就是一起解决问题,我已经在很多次研讨会使用了结对编程,都取得了很好的效果(同时,研讨会上的众人可以通过这种方式互相了解对方)。而且两个人一起工作时,可以更容易地推进开发的进展,而只有一个程序员的话,可能被轻易地卡住。
结对编程的程序员通常可以从工作中感到更高的满足感。有时很难向管理人员们推行结对编程,因为他们可能觉得两个程序员解决同一个问题的效率比他们分开解决不同问题的效率低。
尽管短期内是这样,但是结对编程能带来更高的代码质量;除了结对编程的其他益处,如果你眼光长远的话,这会产生更高的生产力。
维基百科上这篇 结对编程的文章 可以作为你深入了解结对编程的开始。
技术负债是指迭代发展的软件中为了应急而生的丑陋解决方案从而导致设计难以理解,代码难以阅读的部分。特别是当你必须修改和增加新特性的时候,这会造成麻烦。
重构可以矫正技术负债。重构的关键是它能改善代码设计,结构和可读性(因而减少代码负债),但是它不能改变代码的行为。
很难向管理人员推行重构:“我们将投入很多工作不是增加新的特性,当我们完成时,外界无感知变化。但是相信我们,事情会变得更加美好”。不幸的是,管理人员意识到重构的价值时都为时已晚了:当他们提出增加新的特性时,你不得不告诉他们做不到,因为代码基底已经埋藏了太多的问题,试图增加新特性可能会使软件崩溃,即使你能想出怎么做。
在开始重构代码之前,你需要有以下三个系统的支撑:
本书的代码托管在 Github 上,使用的是 git 版本控制系统。
没有这三个系统的支持,重构几乎是不可能的。确实,没有这些系统,起初维护和增加代码是一个巨大的挑战。令人意外的是,有很多成功的公司竟然在没有这三个系统的情况下在相当长的时间里勉强过得去。然而,对于这样的公司来说,在他们遇到严重的问题之前,这只是个时间问题。
维基百科上的 重构文章 提供了更多的细节。
在软件开发的早期,人们只能一次处理一步,所以他们坚信他们总是在经历快乐之旅,每个开发阶段无缝进入下一个。这种错觉经常被称为软件开发中的“瀑布流模型”。很多人告诉我瀑布流是他们的选择方法,好像这是一个选择工具,而不仅是一厢情愿。
在这片童话的土地上,每一步都按照指定的预计时间准时完美结束,然后下一步开始。当最后一步结束时,所有的部件都可以无缝地滑在一起,瞧,一个装载产品诞生了!
当然,现实中没有事能按计划或预计时间运作。相信它应该,然后当它不能时更加相信,只会使整件事变得更糟。否认证据不会产生好的结果。
除此之外,产品本身经常也不是对客户有价值的事物。有时一大堆的特性完全是浪费时间,因为创造出这些特性需求的人不是客户而是其他人。
因为受流水工作线的思路影响,所以每个开发阶段都有自己的团队。上游团队的延期传递到下游团队,当到了需要进行测试和集成的时候,这些团队被指望赶上预期时间,当他们必然做不到时,就认为他们是“差劲的团队成员”。不可能的时间安排和负相关的结合产生了自实现的预期:只有最绝望的开发者才会乐意做这些工作。
另外,商学院培养出的管理人员仍然被训练成只在已有的流程上做一些改动——这些流程都是基于工业时代制造业的想法上。注重培养创造力而不是墨守成规的商学院仍然很稀有。
终于一些编程领域的人们再也忍受不了这种情况并开始进行实验。最初一些实验叫做“极限编程”,因为它们与工业时代的思想完全不同。随着实验展示的结果,这些思想开始看起来像是常识。这些实验逐渐形成了如今显而易见的观点——尽管非常小——即把生产可运作的产品交到客户手中,询问他们 (A) 是否想要它 (B) 是否喜欢它工作的方式 © 还希望有什么其他有用的功能特性。
然后这些信息反馈给开发,从而继续产出一个新版本。版本不断迭代,项目最终演变成为客户带来真正价值的事物。
这完全颠倒了瀑布流开发的方式。你停止假设你要处理产品测试和把部署"作为最后一步"这类的事情。相反,每件事从开始到结束必须都在进行——即使一开始产品几乎没有任何特性。
这么做对于在开发周期的早期发现更多问题有巨大的益处。此外,不是做大量宏大超前的计划和花费时间金钱在许多无用的特性上,而是一直都能从顾客那得到反馈。当客户不再需要其他特性时,你就完成了。这节省了大量的时间和金钱,并提高了顾客的满意度。
有许多不同的想法导向这种方式,但是目前首要的术语叫持续集成(CI)。CI 与导向 CI 的想法之间的不同之处在于 CI 是一种独特的机械式的过程,过程中涵盖了这些想法;它是一种定义好的做事方式。事实上,它定义得如此明确以至于整个过程是自动化的。
当前 CI 技术的高峰是持续集成服务器。这是一台独立的机器或虚拟机,通常是由第三方公司托管的完全独立的服务。这些公司通常免费提供基本服务,如果你需要额外的特性如更多的处理器或内存或者专门的工具或系统,你需要付费。CI 服务器起初是完全空白状态,即只是可用的操作系统的最小配置。
这很重要因为你可能之前在你的开发机器上安装过一些程序,却没有在你的构建和部署系统中包含它。
正如重构一样,持续集成需要分布式版本管理,自动构建和自动测试系统作为基础。通常来说,CI 服务器会绑定到你的版本控制仓库上。当 CI 服务器发现仓库中有改变时,就会拉取最新版本的代码,并按照 CI 脚本中的过程处理。
这包括安装所有必要的工具和类库(记住,CI 服务器起初只有一个干净、基本的操作系统),所以如果过程中出现任何问题,你都可以发现它们。
接着它会执行脚本中定义的构建和测试操作;通常脚本中使用的命令与人们在安装和测试中使用的命令完全相同。如果执行成功或失败,CI 服务器会有许多种方式汇报给你,包括在你的代码仓库上显示一个简单的标记。
使用持续集成,每次你合进仓库时,这些改变都会被从头到尾验证。通过这种方式,一旦出现问题你能立即发现。甚至当你准备交付一个产品的新版本时,都不会有延迟或其他必要的额外步骤(在任何时刻都可以交付叫做持续交付)。
本书的示例代码都是在 Travis-CI(基于 Linux 的系统) 和 AppVeyor(Windows) 上自动测试的。你可以在 Gihub仓库 上的 Readme 看到通过/失败的标记。
“它在我的机器上正常工作了。” “我们不会运载你的机器!”
代码校验不是单一的过程或技术。每种方法只能发现特定类型的 bug,作为程序员的你在开发过程中会明白每个额外的技术都能增加代码的可靠性和鲁棒性。校验不仅能在开发过程中,还能在为应用添加新功能的整个项目期间帮你发现更多的错误。
现代化开发意味着比仅仅编写代码更多的内容,每种你在开发过程中融入的测试技术—— 包括而且尤其是你创建的能适应特定应用的自定义工具——都会带来更好、更快和更加愉悦的开发过程,同时也能为客户提供更高的价值和满意度体验。