关注代码质量(三)
关于提高、保证项目代码质量,除了上次写的各类规范,我们还能做些什么呢?
前期工作。
系统设计
虽然有“不要过度设计”等忠告,但这不等于“不要设计”。好的前期设计能够避免很多问题。
系统设计有两个方面,数据结构设计和系统结构设计。数据结构设计再分,可以分成数据库结构设计和操作数据结构设计。
数据库结构设计,简单说就是数据库库表的设计。建议库表依照完整的业务数据来构造,清楚的表达出数据间的关系。我曾在这上头栽过跟头,主要库表都是相应业务数据的子集,结果后期不得不多次增加字段,并且在增加字段过程中把数据关系也搅乱了。后悔不迭啊。
操作数据结构简单说就是系统运行时使用的数据容器类,一般是pojo类。建议这类结构应该针对实际操作的需要,从业务数据的某个维度出发,作为业务数据的一个子集来设计。可以考虑采用工厂模式或构造器模式。很少有操作会涉及业务数据结构的全部字段,用子集来做操作结构可以节省系统在运行时消耗的内存,并且可以让操作专注于自己的功能,同时减少数据结构变化造成的影响。我曾尝试过把系统操作结构按操作步骤分为四个子集;另外按照两类数据状态设计了另两个子集,效果不错。
系统结构设计上著述颇多。简单的说就两条,遵循基本设计原则,适当使用设计模式。
设计原则有不少,单一职责,里氏替换,开闭,等等。系统结构设计应该尽量遵循。
“使用设计模式”之前有一个状语:适当。文首提到的“不要过度设计”其实主要是针对设计模式而言的。使用设计模式能带来较好的健壮性,扩展性和可维护性;但是也会额外增加开发成本和系统的复杂性。使用时应该慎重权衡。如果弊大于利,那么宁可不采用设计模式,也不要“为模式而模式”。
拟定规范
开发之前还有一件可做的准备工作:拟定开发规范。
开发规范不仅仅是统一团队工作的事情。好的开发规范能够让接手工作的人尽快了解系统,能够让新人尽快融入团队;能够为测试、文档等后续工作提供便利;也能为一些基于命名的开发,如aop等提供开发基础。好处多着呢。
开发规范所规定的一般是代码风格相关。命名啦,注释啦什么的。
开发中可做的
代码习惯
或者叫“程序员的内功”。同样的功能,有的人写的代码更加“优雅”,有的人写出来则一堆“bad smell”。这方面的代码质量要靠个人的积累,很难同归规章制度来“保底”。
优化和重构
相比代码习惯,这要求更高层次的“内功”。优化和重构都应尽量维持原有的对外接口,通过改变内部逻辑或结构来提高系统的性能,效率或扩展性等。优化和重构是对前期,或者上次开发时的设计缺陷做的“擦屁股”,一般来说应尽量简化,避免太复杂的改动造成的未知影响。
单元测试
单元测试可以归入“代码习惯”中,不过我更喜欢单独列出来。单元测试不管是不是用用junit来写,应该确保它是自动化测试;非自动化的单元测试意义不是太大。应该保证对代码中判断条件的各种边界值都做了测试,边界是最容易出问题的地方之一;应该尽量确保测试的代码覆盖率,虽然代码覆盖率不能保证代码完全正确,但至少可以保证错误代码可以尽快被发现。最后,单元测试应该能够无缝整合到集成测试中去,不管是通过命名还是通过junit test suite。
后期工作
尽量不要把代码质量的希望寄托在后期工作上,因为这只能是亡羊补牢。应该努力通过前期、中期工作来保证代码质量,后期的工作作为查缺补漏来补充。
代码评审
我挺喜欢代码评审这东西的。几个人一起审核同一段代码,在保证代码质量的同时交流知识和经验。啊,听起来多么美好。可惜我们组几乎从来没做到过。代码评审的重点不应该是代码有没有实现其功能——这是测试的工作——而应该放在代码是否符合规范、代码的性能和效率等等方面。鼓励新人参加并且发言,新人常常是规范的坚定拥趸;老人们也要拿出积极性来,老人们的“内功”是不可多得的财富。
集成测试
我也管它叫做“全量测试”,是对整个系统的测试,主要检查修改的代码会不会对系统其它部分造成影响,或者检查模块对外接口是否依然正常工作。同单元测试一样,它应该是自动化的,保证对边界进行测试,并尽量提高覆盖率的。这三点可以通过单元测试来做到。除此之外,集成测试应该及时输出测试报告。
这是目前我对“如何保证、提高项目质量”的理解。其中有些部分正在新的项目组中进行尝试,比如规范,评审和集成测试。以后再继续总结经验和教训吧。