代码整洁之道阅读笔记

代码规范很多人在学习初期并不会多在意,也许有些基本的认知,如命名规范、换行空格、括号等,随着经历的项目越来越多,参与项目的人数也不断增加的情况下,规范的重要性也凸显出来,目录结构、更规范统一的命名、注释、异常处理等有了更严格的要求,代码整洁之道、架构整洁之道、Alibaba规范手册这类书也是为此而生的。

这里对代码整洁之道的内容做了简单梳理,一份简单的总结

一、命名

1.1 命名的几个要点

  1. 名副其实
  2. 避免误导
  3. 使用能读的出来的名称
  4. 使用可搜索得到的名称
  5. 避免使用编码,如名称前缀
  6. 避免思维映射
  7. 类名和对象名应该是名词或名词短语
  8. 方法名应当是动词或动词短语
  9. 避免使用只有少部分人才理解的命名,如俗语俚语等
  10. 每个概念对应同一个词
  11. 避免使用双关语,如统一使用add,但部分地方应该是insert或append
  12. 使用计算机领域的术语,避免依据问题所涉领域来命名
  13. 添加有意义的语境

1.2个人理解

这些要点基本是书中目录名称或拓展说明,一下是我在实际开发中领悟到的几个命名要点

  1. 命名时尽量选择范围更广的名称
    如对于Redis相关的常量类,一般首先想到的类名是RedisConstant,看上去也没啥问题,不过再有新的缓存如本地缓存,再添加一个LocalCacheConstant类吗,有些冗余了,都是属于缓存的常量,为什么不一开始命名成CacheConstant呢
  2. 项目团队需要统一的命名风格
    对于多人参与开发的项目,团队成员自行其是,也许在较高层面使用相同的命名风格,如驼峰命名法,常量全字母大写以下划线分隔等(可能这点不少团队也做不到),但在对领域相关命名、设置语境等就是对项目管理者的考验了

二、函数

2.1 书中要点:

  1. 短小
  2. 只做一件事
  3. 每个函数一个抽象层次
  4. 注意switch语句
  5. 描述性的函数名称
  6. 尽量少的函数参数
    a.无参函数最优
    b.一元参数可以接受
    c.有标识参数太丑陋了
    d.二元参数比一元更难懂
    e.三元参数有点险恶了
    f.更多的参数就要包装成对象
    g.可结合参数名对函数进行命名
  7. 无副作用
    副作用常包含时序性耦合和顺序依赖,和函数只做一件事存在冲突,只做一件事是理想状态,可以在命名或注释上表述函数的更多的信息
  8. 函数要做什么与要回答什么尽量不要一起使用
    如一个函数要修改属性的状态,修改成功返回true,修改失败返回false,这是典型的做和回答一起,当然更好的做法是先校验需要修改的属性,再修改,可以分成两个方法
  9. 使用异常代替返回的错误码
    好处是:
    1. 减少对于错误情况的深层嵌套
    2. 将错误处理的逻辑从主路径分离出来
    3. 减少对错误码的强依赖
  10. 消除重复逻辑

2.2 个人理解

尽管书中给了以上不少函数编写的相关规则,但在实际开发中只能符合几种规则,一般是基于个人的编程思路写出初版复杂冗余的代码,之后根据优化规则去修改完成,不过对于需要快速迭代的项目,投入到优化重构的时间和精力有限,这也要求开发者在最初编程时就要进行一定的优化,不需要尽善尽美,能方便其他成员理解,方便之后的开发即可

三、注释

作者认为注释大多数时候是多余的,好的代码可以代替注释,不过不可能每个开发者都能通过好代码完美表达逻辑与意图,因此注释会帮助其弥补不足,不过有些情况下注释也是必要的,作者对好坏注释做了区分,好注释一般在应该出现的场景使用,坏注释大多是因为废话和误导。

好注释:法律信息的声明、对代码更深层次的意图说明、对晦涩难明命名的阐释、警示提醒、TODO、公共API中的javaDoc

坏注释:多余、有误导性、过期没有维护、日志式、信息量过多

总的来说,注释的目的是辅助阅读者更清晰地理解逻辑,大多时候好的代码不需要多少注释,大多注释也属于多余,不过不少人秉持着有总比没有好,注释让本就难看的代码雪上加霜也是常态了

四、格式

格式是为了让代码能延续下去,代码能完成需求只是基本要求,随着不断迭代,原先的代码早已面目全非,但好的规范和风格可以一直存在

4.1 垂直格式

  1. 注意一个文件中代码的行数,短文件通常比长文件易于理解
  2. 注重内容排布,抽象在上,细节在下
  3. 空行用于分隔不同的逻辑与内容,也增加了可读性
  4. 逻辑上紧密相关的代码需要靠近,注释有时会将这类代码分隔,这不是个好主意
  5. 关系紧密的概念应该互相靠近,如变量和变量在一起,常量与常量在一起,相关函数在一起,按调用顺序自上而下

4.2 横向格式

  1. 短代码行当然更受欢迎
  2. 空格表现紧密程度,如运算符优先级,不过当前的IDE格式化工具都是一视同仁
  3. 注意对齐和缩进

当前成熟的IDE开发工具会帮你解决大部分基本的格式问题,不过如何排布内容,函数和变量的位置这些还是需要仔细思考的,当然遵循团队的规则是第一位

五、对象和数据结构

  1. 取值器和赋值器需要关注对实现细节的隐藏,以抽象形态表现数据,即对象不应该通过存取器暴露其内部结构
  2. 过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数,面向对象的代码便于在不改动既有函数的前提下添加新的类
  3. The Law Of Demeter:模块不应了解它所操作的对象内部情况

对象和数据结构的设计决定了其可拓展的上限,对象对外曝露的操作尽量隐藏其实现,方便对数据结构和函数的添加或修改

六、错误处理

  1. 使用异常而非返回码
  2. 先写try-catch-finally语句,类似于先先定个框架
  3. 使用不可控异常,防止底层抛出的异常有更新会影响到其高层链路
  4. 给出异常发生的环境说明,日志给出详细信息
  5. 根据调用者定义异常类,将同一类异常归类整合,简化代码,不依赖于第三方API
  6. 别返回null值,返回空对象或空集合,避免NullPointerException出现,也别传递null值

错误处理不可或缺,但再过在意错误也会使其在代码中占据大量篇幅,这又会成为新的优化项,不使用null值这类规范就能能减少很多不必要的错误处理

七、边界

  1. API提供者和调用者的关注点不同,在不同视角下开发时需要关注对应的需求
  2. 浏览和学习第三方API,书中以学习使用log4j为例,编写测试方法进行调用学习即学习性测试
  3. 与第三方代码需要有预期的分隔,避免自己的代码涉及第三方代码中更多的信息,即与第三方代码的边界要清晰可控

边界主要指的是与第三方代码的关联程度,能解耦就解耦,第三方代码最好能热插拔,不能让其对自己代码的影响过深

八、单元测试

  1. TDD(Test-Driven Development,测试驱动开发)强调在编写实际代码之前先编写测试用例,其可以概括为三个定律:
    a. 不要编写任何没有经过测试的生产代码
    b. 只编写刚好足够的测试代码来让测试失败
    c. 只编写刚好足够的生产代码来让测试通过
  2. 保持测试整洁,测试的代码应和生产代码一样收到重视,测试代码的可读性是最重要的,明确、简洁还有足够的表达力
  3. 测试环境和生产环境的标准不同,可使用的资源也不同,在保证可读性的基础上对于性能可以放松点限制
  4. 每个测试一个断言,即每个测试函数只测试一个概念
  5. 整洁测试的5条规则:快速、独立、可重复、自足验证、及时

这里的单元测试是基于JUnit的,当被测试代码有较多分支情况时,其测试代码容易重复,断言也多,更需要做好规范管理,且Spring启动较慢,不过基于groovy语言的spock测试框架做单元测试是个更好的选择。
即使在项目管理很成熟的大型公司,对单测代码的重视度也不是很高,大家更关心覆盖率,测试用例,至于个人怎么写单测一般没有严格要求,开发氛围、团队规范、需求交付、重要业务这些让开发者没有多少心力去完成优质规范的单测,毕竟这些单测代码属于各方的视线之外

九、类

  1. 类的组织,静态常量在先,之后接常量列表,公共函数在后,公共函数调用的私有方法紧随其后
  2. 类应该短小
    a.单一职责原则:数量多短小类由于数量少庞大类,每个小类封装一个职责,与其他类协同完成系统期望的行为
    b.内聚:方法操作的类中的变量越多,其内聚性越高,保持类中参数列表短和函数少而短有助于提高内聚
    c.保持内聚性就得学会拆分类
  3. 为了修改而对类进行组织,这种组织也是为了内聚性,更短小职责单一的类,其中的变量和函数关联紧密,将修改隔离,易于拓展,可读性也提高了

代码能运行和代码整洁是两件不同的事,前者关乎你的饭碗,后者关系你的工作舒适程度,因为代码很烂而导致系统无法维护,大家都是有责任的

十、系统

  1. 将构造与使用分离,其核心机制是依赖注入和控制反转(也是Spring管理对象的核心)
  2. 系统必定会需要迭代扩充,设计架构时需要充分隔离各模块关注的问题
  3. 迭代和增量敏捷才是系统开发的常态,系统架构持续递增式的增长,开发者也需要持续地将关注面切分
  4. 面向切面编程,主要方法是代理模式,JDK内置的Java代理、Spring等框架封装的AOP、关注切分面的AspectJ语言
  5. 测试驱动系统架构,不要一开始就想着做大而全的设计,最佳的系统架构由模块化的关注面领域组成,不同领域之间互不侵害、相互解耦
  6. 决策需要建立在尽量足够多的信息基础上
  7. 标准需要为客户价值服务,不要太执着于通用标准
  8. 软件领域的特定语言DSL,可以使开发者与领域专家建立沟通桥梁,填平领域概念和实现领域概念的代码之间的“壕沟”,软件领域常见的一些DSL:sql、HTML/CSS、JSON/YAML、LaTex等

十一、迭进

  1. 运行所有测试:能持续编写测试的系统也能持续迭代,糟糕的代码也很难编写测试,测试也消除了清理代码就会破坏代码的恐惧
  2. 不要重复:代码内容相似或实现逻辑相似的都可以重复,常用的有模版方法设计模式,小规模复用是大规模复用的前提
  3. 更好的代码表达:易于理解的代码使得后续维护者减少维护成本,好的命名、函数与类的长度、设计模式、好的测试等
  4. 尽可能少的类和方法:教条主义总是产生冗余的代码,开发时采用更实用的手段,保持系统短小精悍
    以上的四条规则自上而下重要性和优先级逐渐递减,这些在多年开发实践中总结的原则为系统开发提供了很好的指导,当然你自己也可以提炼更多的经验规则

十二 并发编程

  1. 并发的目的:将目的与时机分解
  2. 并发防御原则:
    a.单一职责原则
    b.限制数据作用域
    c.使用数据复本
    d.线程应尽可能独立
  3. 掌握Java库想的相关并发使用
  4. 并发执行模型:
    a.生产者-消费者模型
    b.读者-作者模型
    c.宴席哲学家
  5. 注意同步方法之间的依赖
  6. 尽可能减小同步区域

并发编程复杂而破碎,远比单线线程的代码难以掌控,由于线程的执行顺序多数时候难以把控,共享变量也是如此,代码在大多数时候可以正常运行,一旦压力增加便会出现各种问题,想要

十三、并发的测试

书中对于并发

  1. 将伪失败看做可能得系统问题
  2. 确保线程之外的代码可工作
  3. 编写可插拔的线程代码
  4. 编写可调整的线程代码
  5. 要运行多余处理器数量的线程
  6. 在不同平台上运行
  7. 装置试错代码,两种方式:硬编码和自动化
    硬编码:手工向代码中插入wait()、sleep()、yield()、priority()的调用,这种方式弊端较多且效果不是很好
    自动化:使用CGLIB、ConTest等工具通过编程来装置代码,目的在于让线程已不同次序执行
posted @   staticFinal  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
点击右上角即可分享
微信分享提示