敏捷测试转型 聊聊代码的整洁(下)

鼎叔 · 2023年08月18日 · 1961 次阅读

这是鼎叔的第七十一篇原创文章。行业大牛和刚毕业的小白,都可以进来聊聊。

欢迎关注本公众号《敏捷测试转型》,星标收藏,大量原创思考文章陆续推出。

相关文章:聊聊代码的整洁(上)https://mp.weixin.qq.com/s?__biz=MzkzMzI3NDYzNw==&mid=2247484314&idx=1&sn=0f05296ca6ab3efe6b236c0a776230e6&scene=21#wechat_redirect

下篇继续聊《整洁代码之道》,我们分享注释、代码格式、类、系统如何做到整洁,以及代码坏味道的处理清单。

Part4 注释

如果编程足够有表达力,我们可能根本不需要注释。注释最多是一种必须的恶,弥补我们在用代码表达意图时遭遇的失败。

乱七八糟的注释会适得其反,搞乱一个模块,提供错误信息的注释更有破坏性。对于糟糕的代码,不应该添加注释,而是应该重写它,不准确的注释比没有注释糟糕得多。

注释存在的时间越久,越可能离其描述的代码越远,因为程序员不能坚持维护注释。所以,只有代码能忠实地告诉你发生了什么,尽量用代码解释你的意图吧!

好的注释(不得不写)

1 法律信息。比如版权和著作权信息,鼎叔在某家公司推广过一键配置版权/著作权申明的代码,方便员工使用,同时降低代码外泄风险。该信息也可以指向外部规范文档,而不是列出具体条文。

2 解释决定背后的意图。程序员对他提供的解决方案心里不一定有底,这个注释可以让读者知道他内心究竟想做什么。

3 标准库或无法修改的第三方代码,为了解释这些晦涩的外部代码,不得已的情况下。

4 警告风险。让阅读的人谨慎从事,小心后果。也可以用来放大某段代码的重要性。

5 未来 TODO 的注释,程序员当前迭代来不及做,但是将来要回来完善的代码。注意将来完成后要删除这个//TODO 的注释。

6 公共 API 中给他人查阅的说明代码(Javadoc),同样要注意可读性。

坏的注释,即上述原因以外的绝大部分注释

1 只写给自己看的注释。

2 多余/废话,包括 javadoc 里面没有意义的复制/粘贴,也包括不必要的函数头注释。

其实代码本身能表达很清楚,函数和变量的名字能自我解释。

3 变更日志类注释。现代源码版本控制系统早已有完善的变更管理记录,这种注释没有必要。

同理,代码作者署名,废弃代码等,都不需要用注释来说明,源码系统足矣。

4 特定位置打标记 如 //take action /////////////

滥用打标记很容易被忽略,应尽量清理。

5 多层嵌套括号后面的注释。对于嵌套深度多的长函数,你要做的是让它变短,而不是写注释。

鼎叔注:某一线集团的代码,95% 的方法中嵌套层数深度在 3 以内,超过 5 层的嵌套代码会被建议清理。

6 上文说过,注释和被说明代码没有挨在一起,或者两者没有明显的逻辑关联,也都是坏注释。

所谓有趣的历史话题,其实也是无关的细节描述,放在注释里没有必要。

Part5 良好的代码格式

代码格式关于沟通,而开发者之间的沟通是头等大事。功能代码可能在下个版本被修改,但是你的代码风格和可读性会长期保留下来,影响代码的可维护性和可扩展性。

推荐格式技巧

Java 源代码文件最好长度在 200 行以内,最长 500 行。

就跟写报道一样,源代码有一个清晰的名字,然后顶层就像故事大纲给出高层次的概念和算法,再往下渐次展开细节,直到最底层的函数细节。

自上而下,每组代码行展示了一条完整的思路,中间被空行区隔开,避免阅读的逻辑跳跃。紧密关联的代码应该聚合在一起,一览无余。

变量声明应该尽可能靠近使用位置。相关调用函数应该放在一起,调用者应该尽量放在被调用者的上面。众人皆知的常量应该放在易于找到的位置,再传递到真实使用的地方。

从代码宽度来看,也是尽力保持代码行短小,最好在 80 个字符以下,不要超过 120 字符。

代码缩进格式对可读性很重要,能够让程序员快速查看自己的工作范围,快速跳过与当前关注情形无关的地方。

团队有权决定代码风格规则,让软件风格统一和阅读顺畅,只需花很短时间就能集体决定,再把规则纳入 IDE 的代码格式功能,就可以一直沿用了。

整合第三方代码

第三方程序包和框架的作者,必然会追求普适性,但使用者只是希望得到集中满足特定需求的接口,这种矛盾很容易导致代码边界出问题。

我们虽然没有测试第三方代码的职责,但是我们可以编写测试来遍览和理解第三方代码,通过这种学习性测试看看调用第三方代码是否正常工作,而不要在生产代码中直接实验新东西。当第三方代码发布新版本,学习性测试也能迅速发现新问题。

Part 6 类

和函数一样,类也应该足够短小。我们可以通过计算类的权责来评估其大小。单一权责原则(SRP)认为,类或模块应该只有一个权责,也只有一条修改它的理由。

类的名词应该描述其权责,如果无法为某个类起一个精确的名称,恰恰说明这个类可能太长了。

让软件能工作,和让软件保持整洁,是两种不同要求的工作,很多开发者担心大量的短小单一类会导致难以抓住全局,一目了然。但实际上单一的类组成的系统更容易让开发者找到东西。

类应该只有少数实体变量,类中的每个方法应该操作一个或多个这种变量,如果类中的每个变量都被每个方法所使用,它就具有最大的内聚性。保持良好内聚性,就必然要拆分很多短小的类。

类应该对扩展开放,对修改封闭,尽可能少惹麻烦。对内部细节的依赖会对系统测试带来麻烦,而部件之间的解耦则代表着系统中的元素互相隔离得很好,理解这些元素也变得更加容易。

Part 7 在系统层级上保持整洁

系统层级也应该是整洁的,系统意图应清晰可辨。

软件系统应该把起始过程的构造和执行逻辑分开,勤于打造有良好格式的坚固系统,借助模块化和关注面切分完成分散化管理,把敏捷决策授权给最有经验的人,鼓励延迟决策到最后一刻。

Kent Beck 提出了简单设计的四大原则,包括:

运行所有测试,不会验证的系统就不该部署。为了提高可测试性,工程师会使用依赖注入、接口和抽象等工具尽可能减少耦合。测试消除了对清理代码可能会引发破坏的恐惧。
消除重复。
表达力。通过不断尝试提高技艺。
尽可能减少类和方法。这条优先级低于前面三个。有时候类和方法数量太多,是由教条主义导致。

系统一旦遭受压力,会导致不整洁的并发程序难以招架。在单线程应用中,目的和时机紧密耦合,但有些系统对响应时间和吞吐量有要求,需要人工编写并发解决方案,改善系统响应时间。

并发算法的设计可能和单线程完全不同,目的和时机的解耦可能对系统结构产生巨大影响,工程师需要对设计策略做根本性修改。并发产生过程是复杂的,产生的缺陷也不是总能复现。

我们建议把并发相关处理代码,和其他代码分离(单一权责原则)。考虑到两个线程修改共享对象的同一字段时会互相干扰,建议限制共享数据的访问,做好数据封装;或者使用对象副本避免代码同步执行;或者线程尽可能独立,不和其他线程共享数据。

如果有时必须使用一个共享对象的多个方法,那么代码实现可以有基于客户端的锁定、基于服务端的锁定、基于适配服务端的中间层锁定,这三种实现手段。

要真正理解并发系统的死锁原因,就需要知道死锁的发生需要同时满足四个条件:

1 资源互斥

2 线程获得资源后会上锁及等待

3 没有线程抢夺资源的机制

4 两个线程循环等待

只用打破这四个条件的任何一个,就可以消灭死锁。

一句话:满足于仅仅让代码能工作的程序员还不够专业,他们害怕没时间改进代码的结构和设计,其实,保持代码整洁是相对容易的,要尽可能快地清理 “混乱”,不让 “腐坏” 有机会开始。

Part 8 代码坏味道的治理清单

作为本文的参考总结,回顾下主要的坏味道类型,给出针对性修改。

1 注释。注释只应该描述代码和设计的技术性信息,能在源代码控制等系统保存的信息不应放在注释里。删除掉无关的、废弃的、冗余的注释!

2 环境。编译构建系统应该是一步操作,执行全部单元测试也应该是一步操作。

3 函数。避免参数数量太多,避免布尔值参数,避免输出参数,避免不被调用的死函数。

4 名称。采用描述性名称,且与抽象层级相符。尽可能使用标准命名法(约定俗成),选用不会混淆的名称,名称的长度和作用范围的广范度相关。名称还应该说明副作用。

5 其他问题:

理想的源文件应该只包括一种编程语言。

函数名称中有明显提示的行为却没有被代码实现,会让读者失去对名字的信任。

所有较低层级概念(细节实现)放在派生类中,所有较高层级概念放在基类中。通常而言,基类对派生类应该一无所知。

找到不被执行的 “死代码”,体面地埋葬它。

两个没有互相依赖的模块,不应该被人为耦合。

特性依赖:类的方法应该只对其所属类中的变量和代码感兴趣,不该垂青其他类中的变量和函数。

最小惊异原则:代码应该放在读者自然而然期待它出现的地方。

用命名常量代替魔术数。魔术数不止指一个具体数字,还可泛指不能自我描述的符号。

避免否定性条件,尽可能将条件表示为肯定形式。

不要掩盖时序耦合。通过排列函数参数,让它们被调用的次序显而易见。

Demeter 定律:尽量不让某个模块了解太多其他协作者的信息。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册