代码整洁之道阅读笔记
代码规范很多人在学习初期并不会多在意,也许有些基本的认知,如命名规范、换行空格、括号等,随着经历的项目越来越多,参与项目的人数也不断增加的情况下,规范的重要性也凸显出来,目录结构、更规范统一的命名、注释、异常处理等有了更严格的要求,代码整洁之道、架构整洁之道、Alibaba规范手册这类书也是为此而生的。
这里对代码整洁之道的内容做了简单梳理,一份简单的总结
一、命名
1.1 命名的几个要点
- 名副其实
- 避免误导
- 使用能读的出来的名称
- 使用可搜索得到的名称
- 避免使用编码,如名称前缀
- 避免思维映射
- 类名和对象名应该是名词或名词短语
- 方法名应当是动词或动词短语
- 避免使用只有少部分人才理解的命名,如俗语俚语等
- 每个概念对应同一个词
- 避免使用双关语,如统一使用add,但部分地方应该是insert或append
- 使用计算机领域的术语,避免依据问题所涉领域来命名
- 添加有意义的语境
1.2个人理解
这些要点基本是书中目录名称或拓展说明,一下是我在实际开发中领悟到的几个命名要点
- 命名时尽量选择范围更广的名称
如对于Redis相关的常量类,一般首先想到的类名是RedisConstant,看上去也没啥问题,不过再有新的缓存如本地缓存,再添加一个LocalCacheConstant类吗,有些冗余了,都是属于缓存的常量,为什么不一开始命名成CacheConstant呢 - 项目团队需要统一的命名风格
对于多人参与开发的项目,团队成员自行其是,也许在较高层面使用相同的命名风格,如驼峰命名法,常量全字母大写以下划线分隔等(可能这点不少团队也做不到),但在对领域相关命名、设置语境等就是对项目管理者的考验了
二、函数
2.1 书中要点:
- 短小
- 只做一件事
- 每个函数一个抽象层次
- 注意switch语句
- 描述性的函数名称
- 尽量少的函数参数
a.无参函数最优
b.一元参数可以接受
c.有标识参数太丑陋了
d.二元参数比一元更难懂
e.三元参数有点险恶了
f.更多的参数就要包装成对象
g.可结合参数名对函数进行命名 - 无副作用
副作用常包含时序性耦合和顺序依赖,和函数只做一件事存在冲突,只做一件事是理想状态,可以在命名或注释上表述函数的更多的信息 - 函数要做什么与要回答什么尽量不要一起使用
如一个函数要修改属性的状态,修改成功返回true,修改失败返回false,这是典型的做和回答一起,当然更好的做法是先校验需要修改的属性,再修改,可以分成两个方法 - 使用异常代替返回的错误码
好处是:- 减少对于错误情况的深层嵌套
- 将错误处理的逻辑从主路径分离出来
- 减少对错误码的强依赖
- 消除重复逻辑
2.2 个人理解
尽管书中给了以上不少函数编写的相关规则,但在实际开发中只能符合几种规则,一般是基于个人的编程思路写出初版复杂冗余的代码,之后根据优化规则去修改完成,不过对于需要快速迭代的项目,投入到优化重构的时间和精力有限,这也要求开发者在最初编程时就要进行一定的优化,不需要尽善尽美,能方便其他成员理解,方便之后的开发即可
三、注释
作者认为注释大多数时候是多余的,好的代码可以代替注释,不过不可能每个开发者都能通过好代码完美表达逻辑与意图,因此注释会帮助其弥补不足,不过有些情况下注释也是必要的,作者对好坏注释做了区分,好注释一般在应该出现的场景使用,坏注释大多是因为废话和误导。
好注释:法律信息的声明、对代码更深层次的意图说明、对晦涩难明命名的阐释、警示提醒、TODO、公共API中的javaDoc
坏注释:多余、有误导性、过期没有维护、日志式、信息量过多
总的来说,注释的目的是辅助阅读者更清晰地理解逻辑,大多时候好的代码不需要多少注释,大多注释也属于多余,不过不少人秉持着有总比没有好,注释让本就难看的代码雪上加霜也是常态了
四、格式
格式是为了让代码能延续下去,代码能完成需求只是基本要求,随着不断迭代,原先的代码早已面目全非,但好的规范和风格可以一直存在
4.1 垂直格式
- 注意一个文件中代码的行数,短文件通常比长文件易于理解
- 注重内容排布,抽象在上,细节在下
- 空行用于分隔不同的逻辑与内容,也增加了可读性
- 逻辑上紧密相关的代码需要靠近,注释有时会将这类代码分隔,这不是个好主意
- 关系紧密的概念应该互相靠近,如变量和变量在一起,常量与常量在一起,相关函数在一起,按调用顺序自上而下
4.2 横向格式
- 短代码行当然更受欢迎
- 空格表现紧密程度,如运算符优先级,不过当前的IDE格式化工具都是一视同仁
- 注意对齐和缩进
当前成熟的IDE开发工具会帮你解决大部分基本的格式问题,不过如何排布内容,函数和变量的位置这些还是需要仔细思考的,当然遵循团队的规则是第一位
五、对象和数据结构
- 取值器和赋值器需要关注对实现细节的隐藏,以抽象形态表现数据,即对象不应该通过存取器暴露其内部结构
- 过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数,面向对象的代码便于在不改动既有函数的前提下添加新的类
- The Law Of Demeter:模块不应了解它所操作的对象内部情况
对象和数据结构的设计决定了其可拓展的上限,对象对外曝露的操作尽量隐藏其实现,方便对数据结构和函数的添加或修改
六、错误处理
- 使用异常而非返回码
- 先写try-catch-finally语句,类似于先先定个框架
- 使用不可控异常,防止底层抛出的异常有更新会影响到其高层链路
- 给出异常发生的环境说明,日志给出详细信息
- 根据调用者定义异常类,将同一类异常归类整合,简化代码,不依赖于第三方API
- 别返回null值,返回空对象或空集合,避免NullPointerException出现,也别传递null值
错误处理不可或缺,但再过在意错误也会使其在代码中占据大量篇幅,这又会成为新的优化项,不使用null值这类规范就能能减少很多不必要的错误处理
七、边界
- API提供者和调用者的关注点不同,在不同视角下开发时需要关注对应的需求
- 浏览和学习第三方API,书中以学习使用log4j为例,编写测试方法进行调用学习即学习性测试
- 与第三方代码需要有预期的分隔,避免自己的代码涉及第三方代码中更多的信息,即与第三方代码的边界要清晰可控
边界主要指的是与第三方代码的关联程度,能解耦就解耦,第三方代码最好能热插拔,不能让其对自己代码的影响过深
八、单元测试
- TDD(Test-Driven Development,测试驱动开发)强调在编写实际代码之前先编写测试用例,其可以概括为三个定律:
a. 不要编写任何没有经过测试的生产代码
b. 只编写刚好足够的测试代码来让测试失败
c. 只编写刚好足够的生产代码来让测试通过 - 保持测试整洁,测试的代码应和生产代码一样收到重视,测试代码的可读性是最重要的,明确、简洁还有足够的表达力
- 测试环境和生产环境的标准不同,可使用的资源也不同,在保证可读性的基础上对于性能可以放松点限制
- 每个测试一个断言,即每个测试函数只测试一个概念
- 整洁测试的5条规则:快速、独立、可重复、自足验证、及时
这里的单元测试是基于JUnit的,当被测试代码有较多分支情况时,其测试代码容易重复,断言也多,更需要做好规范管理,且Spring启动较慢,不过基于groovy语言的spock测试框架做单元测试是个更好的选择。
即使在项目管理很成熟的大型公司,对单测代码的重视度也不是很高,大家更关心覆盖率,测试用例,至于个人怎么写单测一般没有严格要求,开发氛围、团队规范、需求交付、重要业务这些让开发者没有多少心力去完成优质规范的单测,毕竟这些单测代码属于各方的视线之外
九、类
- 类的组织,静态常量在先,之后接常量列表,公共函数在后,公共函数调用的私有方法紧随其后
- 类应该短小
a.单一职责原则:数量多短小类由于数量少庞大类,每个小类封装一个职责,与其他类协同完成系统期望的行为
b.内聚:方法操作的类中的变量越多,其内聚性越高,保持类中参数列表短和函数少而短有助于提高内聚
c.保持内聚性就得学会拆分类 - 为了修改而对类进行组织,这种组织也是为了内聚性,更短小职责单一的类,其中的变量和函数关联紧密,将修改隔离,易于拓展,可读性也提高了
代码能运行和代码整洁是两件不同的事,前者关乎你的饭碗,后者关系你的工作舒适程度,因为代码很烂而导致系统无法维护,大家都是有责任的
十、系统
- 将构造与使用分离,其核心机制是依赖注入和控制反转(也是Spring管理对象的核心)
- 系统必定会需要迭代扩充,设计架构时需要充分隔离各模块关注的问题
- 迭代和增量敏捷才是系统开发的常态,系统架构持续递增式的增长,开发者也需要持续地将关注面切分
- 面向切面编程,主要方法是代理模式,JDK内置的Java代理、Spring等框架封装的AOP、关注切分面的AspectJ语言
- 测试驱动系统架构,不要一开始就想着做大而全的设计,最佳的系统架构由模块化的关注面领域组成,不同领域之间互不侵害、相互解耦
- 决策需要建立在尽量足够多的信息基础上
- 标准需要为客户价值服务,不要太执着于通用标准
- 软件领域的特定语言DSL,可以使开发者与领域专家建立沟通桥梁,填平领域概念和实现领域概念的代码之间的“壕沟”,软件领域常见的一些DSL:sql、HTML/CSS、JSON/YAML、LaTex等
十一、迭进
- 运行所有测试:能持续编写测试的系统也能持续迭代,糟糕的代码也很难编写测试,测试也消除了清理代码就会破坏代码的恐惧
- 不要重复:代码内容相似或实现逻辑相似的都可以重复,常用的有模版方法设计模式,小规模复用是大规模复用的前提
- 更好的代码表达:易于理解的代码使得后续维护者减少维护成本,好的命名、函数与类的长度、设计模式、好的测试等
- 尽可能少的类和方法:教条主义总是产生冗余的代码,开发时采用更实用的手段,保持系统短小精悍
以上的四条规则自上而下重要性和优先级逐渐递减,这些在多年开发实践中总结的原则为系统开发提供了很好的指导,当然你自己也可以提炼更多的经验规则
十二 并发编程
- 并发的目的:将目的与时机分解
- 并发防御原则:
a.单一职责原则
b.限制数据作用域
c.使用数据复本
d.线程应尽可能独立 - 掌握Java库想的相关并发使用
- 并发执行模型:
a.生产者-消费者模型
b.读者-作者模型
c.宴席哲学家 - 注意同步方法之间的依赖
- 尽可能减小同步区域
并发编程复杂而破碎,远比单线线程的代码难以掌控,由于线程的执行顺序多数时候难以把控,共享变量也是如此,代码在大多数时候可以正常运行,一旦压力增加便会出现各种问题,想要
十三、并发的测试
书中对于并发
- 将伪失败看做可能得系统问题
- 确保线程之外的代码可工作
- 编写可插拔的线程代码
- 编写可调整的线程代码
- 要运行多余处理器数量的线程
- 在不同平台上运行
- 装置试错代码,两种方式:硬编码和自动化
硬编码:手工向代码中插入wait()、sleep()、yield()、priority()的调用,这种方式弊端较多且效果不是很好
自动化:使用CGLIB、ConTest等工具通过编程来装置代码,目的在于让线程已不同次序执行
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报