文档管理中心

【综合】Clean Code系列1: 不仅是Java等语言编程规范

作者:HW-SQH


谷歌规范中有的条目,尽量跟其一致,没有的,也与C/C++规范的靠近。像语句,表达式,空格和空行,注释,C/C++/Java家族有相当多共通的地方。

有时,妥协也是艺术

编程规范的设计跟软件系统的设计一样,也要充分考虑兼容性、一致性、可读性。

尽量避免“一会儿向左,一会儿向右”,给开发团队造成浪费。也不能太抽象而难以落地,也不能太具体太啰嗦而失去广泛适用性。

这需要我们广泛阅读经典文献著作,也懂得如何抽象与提炼,宁缺毋滥

[ps: 2种声音:C///报告很细节,编程规范太粗了;编程规范太细了,整改工作量太大了... oops]

预计后续的发展方向,可以继续向深水区前进,持续提升编程能力和基础质量

  1. 继续参照经典著作,例如《Effective Java》第3版,《重构》第2版,深入理解并提炼更多的编程实践,把更多bad practice的检查落实到工具的自动化检查(与修复)中去。 这时,为了更加地工具友好(准确性,完整性),社区应该提供必要的充分的bad写法,提供足够的good写法。否则,工具的不精确,可能会僵化地告警而导致团队教条式执行,反而把代码改得更烂,例如见过static final int NUM_128 = 128。

  2. 其它优秀的规范,例如Petroware公司的代码风格,也可以吸取精华。 Petroware的风格是我见过最细致的了,仅仅是风格就有86条。它也把must, should can 的条目作了一定的区分。还有《 Java编码指南 编写安全可靠程序的75条建议 》,这属于可信代码的范围。

  3. Learning to program is more than learning the syntactic and semantic rules of a programming language. It also requires learning how to design programs. Any good book on programming must therefore teach program design. -- Ralph Johnson


    关键思想是,一致的风格比“正确”的风格更重要 -- 《编写可读代码的艺术》。

    代码的可读性可理解性可维护性,对于干净可信是极其重要的。

    或许,对于非英语母语的程序员来说,命名依然是排名第一的困扰。命名是门艺术,应该符合人体工程学Ergonomics


    以下条目或可考虑逐步加入,对其它语言也有参考意义。也可以作为Code Review的扩展参考


    1. 变量有长的生命范围就取长的名字表达,有短的生命范围取短的名字表达,长度2~31字符。

    也有例外:i,j,k等循环变量长度可为1;Lambds除了用it这类保留字外,其它也常用o1和o2,obj,item,elem

    在安卓中有时会也见到较长的,例如GardenPlantingDetailViewModelFactory

    2. 变量应该在声明时初始化,尽量声明在最小生命范围,尽量保持最小的生命周期

    3. 类级的变量不应该声明为public的,应提供getter/setter

    4. 对象的名称是隐式的,应该在方法名称中避免使用;正交,不能有二义性,保持唯一性可减少副作用的错误


    line.getLength(); // NOT:line.getLineLength();

    5. is前缀用于布尔变量和方法(第三方对JavaBeans的实例属性的处理例外考虑)

    isSet,isVisible,isFinished,isFound,isOpen

    布尔变量的setter方法必须用set前缀 void setFound(boolean isFound);

    is前缀 有一些替代方案,在某些情况下更适合

    6. 用户界面组件名称应以元素类型为后缀

    nameTextField,leftScrollbar,mainView,minLabel,printerDialog

    7. n或者numberOf前缀应该用于表示多个对象的变量

    nPoints,nLines,numberOfBooks

    8. 默认接口实现可以default为前缀

    class DefaultTableCellRenderer implements TableCellRenderer { // ... }

    9. 专用命名术语:把信息放进名字

    set/get: 用于直接访问字段/属性,且不耗时的情况,专用于JavaBeans、POJO

    update,modify,更新,修改某个对象的字段/属性,常常返回void

    from,convert,transform,常用于把一个值从一个类型转换为另一个类型的值,有参数有返回

    fetch,query:一般用于远程获取数据的情况,有返回值

    calculate,compute:用于耗时的计算方法,有返回值

    valueSet.computeAverage();

    matrix.computeInverse()

    find:从集合或者数据库查找某些item的方法,且有可能耗时

    vertex.findNearestVertex();

    matrix.findSmallestElement();

    init/initialize:可用于建立对象时初始化

    printer.initializeFontSet();

    其它专业名词有:

    send,或者deliver,发送一个值;dispatch,route,forward,分派/路由/转发请求

    find,或者search,extract,locate,返回搜索的elements

    start,或者create,begin,open

    make,或者setUp;

    build,newInstance,常见于建造者模式、工厂模式

    10. 互补性的实体必须使用互补的名称

    put/take,get/set, add/remove, create/destroy, start/stop, insert/delete, success/failure,increment/decrement, begin/end, first/last, up/down, min/max, next/previous, offer/poll, open/close, show/hide, send/receive,load/store/save,etc.

    11. 函数(返回对象的方法)应该以它们返回的内容命名返回值,对称工整
    即get,fetch,compute,find等得到值的函数要处理,Bar bar = foo.getBar();
    void方法常常用于处理副作用。
    提高可读性。明确了该单元应该做什么,特别是它不应该做的所有事情。这再次使代码更容易保持副作用。

    方法内部也要保持对称,抽象层次一致,不要凹凸不平

    12. Singleton类应该通过方法getInstance返回它们的唯一单例

    13. 代表工厂创建实例的类可以通过方法new[ClassName]来实现,容器类的用of创建,基本类型用valueOf创建,对象实例转换用toXxx

    14. 函数式编程中的常见命名,尽量满足Monad风格,包括map,flatMap,fold等,参见Optional

    15. 契约式设计、防御式编程有利于编写安全可靠的代码。应该使用@NotNull,@Nullable等注解来提升可靠性

    16. Javadoce注释要有意义且合理,完整且适当,不要包含多余的信息,例如方法名。每个tag要有定义,例如

    Each element needs a definition. This includes @param
    @throws SpecialException // Bad!
    @throws SpecialException if val is null or fails validation // Good!

    17. 对于 简单的布尔方法,直接返回表达式而不是用if,例如


    18. 总是显示指定访问修饰符,如果是包访问,则写注释说明。

    19. 不要包含不必要的代码,例如多余的死代码,未用到的变量,冗余的赋值或比较,例如

    20. 不要导入默认的包,例如java.lang,不要显式地extends Object

    21. 精通语法,掌握新的标准库API,减少圈复杂度,让代码更加简洁可读可理解,例如

    22. 增加单元测试的条目,阿里的规范也有。单元测试没有受到应有的重视,但它对于重构、健壮性是至关重要的

    a. 每个测试是独立的,不应假设测试执行的顺序

    b. 不仅要测试happy path,还应包括边界条件

    c. JUnit测试不应该打印 任何东西

    d. 使用特定的断言。JUnit提供了 assertEquals, assertArrayEquals, assertNull, assertSame等广泛的断言

    下面 assertEquals() 就提供了更多有用的信息:

    23. 附录 阿里的Java开发手册 中单元测试:

    1. 【强制】好的单元测试必须遵守 AIR 原则。
    说明: 单元测试在线上运行时,感觉像空气(AIR) 一样并不存在,但在测试质量的保障上, 却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
    A: Automatic(自动化)
    I: Independent(独立性)
    R: Repeatable(可重复)
    2. 【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执 行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。
    3. 【强制】保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间 决不能互相调用,也不能依赖执行的先后次序。
    反例: method2 需要依赖 method1 的执行, 将执行结果作为 method2 的输入。
    4. 【强制】单元测试是可以重复执行的,不能受到外界环境的影响。
    说明: 单元测试通常会被放到持续集成中,每次有代码 check in 时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等) 有依赖,容易导致持续集成机制的不可用。
    正例: 为了不受外界环境影响,要求设计代码时就把 SUT 的依赖改成注入,在测试时用 Spring 这样的 DI 框架注入一个本地(内存)实现或者 Mock 实现。
    5. 【强制】对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。
    说明: 只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。
    6. 【强制】核心业务、核心应用、核心模块的增量代码确保单元测试通过。
    说明: 新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正。
    7. 【强制】单元测试代码必须写在如下工程目录: src/test/java,不允许写在业务代码目录下。
    说明: 源码构建时会跳过此目录,而单元测试框架默认是扫描此目录。
    8. 【推荐】单元测试的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都要达到 100%
    说明: 在工程规约的应用分层中提到的 DAO 层, Manager 层,可重用度高的 Service,都应该进行单元测试。
    9. 【推荐】编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
    B: Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
    C: Correct,正确的输入,并得到预期的结果。
    D: Design,与设计文档相结合,来编写单元测试。
    E: Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得到预期的结果。
    10. 【推荐】对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。
    反例: 删除某一行数据的单元测试,在数据库中, 先直接手动增加一行作为删除目标,但是这一行新增数据并不符合业务插入规则, 导致测试结果异常。
    11. 【推荐】和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。
    正例: 在 RDC 内部单元测试中,使用 RDC_UNIT_TEST_的前缀标识数据。
    12. 【推荐】对于不可测的代码建议做必要的重构,使代码变得可测,避免为了达到测试要求而书写不规范测试代码。
    13. 【推荐】在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例。
    14. 【推荐】单元测试作为一种质量保障手段,不建议项目发布后补充单元测试用例,建议在项目提测前完成单元测试。
    15. 【参考】为了更方便地进行单元测试,业务代码应避免以下情况
    构造方法中做的事情过多。
    存在过多的全局变量和静态方法。
    存在过多的外部依赖。
    存在过多的条件语句。
    说明: 多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。
    16. 【参考】不要对单元测试存在如下误解
    那是测试同学干的事情。本文是开发手册,凡是本文内容都是与开发同学强相关的。
    单元测试代码是多余的。 系统的整体功能与各单元部件的测试正常与否是强相关的。
    单元测试代码不需要维护。一年半载后,那么单元测试几乎处于废弃状态。

    单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。


    24. 附录 一些参考文献

    代码大全》提到过,名字的长度不超过20个字母,限制嵌套超过3层;“降低复杂性是编写高质量的代码的关键”;

    实现模式Implementation Patterns》提到过方法在5~15行;

    高质量程序设计艺术》提到过函数在10~20行;

    代码整洁之道》提到过函数20行封顶最佳;函数参数不超过3个;
    Android AOSP代码风格,提到过方法行数不超过40行;
    Effective Java》第3版,提到匿名类少于10行,Lambda表达式1~3行;
    这样编码才规范 128个编码好习惯》,提到过名称不超过32字符。
    推荐这本新书作者认为应该重视并加强编码风格的教育,它比数学或英语更加重要。因为可缩短开发时间、便于维护、减少漏洞。
    点赞
    收藏
    回复
    13
    分享
    举报
    浏览3945 发布于2019-11-21 03:06未知归属地
    全部评论
    最多点赞
    最新发布
    最早发布
    写回答
    新增插入模板功能
    一键使用模板,快速填写内容,轻松发帖~
    知道了
    • 为了保障您的信息安全,请勿上传您的敏感个人信息(如您的密码等信息)和您的敏感资产信息(如关键源代码、签名私钥、调试安装包、业务日志等信息),且您需自行承担由此产生的信息泄露等安全风险。
    • 如您发布的内容为转载内容,请注明内容来源。

    我要发帖子

    了解社区公约,与您携手共创和谐专业的开发者社区。