菜鸟日记 | 一点代码整洁之道的思考

195 阅读7分钟

写在前面

我也当一次标题党,最近粗读了一下大名鼎鼎的《代码整洁之道》的思考,想写一篇读书笔记总结下。

起因是最近接手了同事的代码,一边吐槽一边修复海量BUG,改不动了就重构,总算是顺利把需求交差了。 事后静下心来思考,其实需求功能并不复杂,还是开发者写的不好。再Review一下自己一年前的代码,也会觉得恶臭难忍。为什么大家都会有屎山代码呢?都说屎山难以避免,那有没有什么手段可以尽量减缓屎山堆积的速度呢?

以前端开发的视角来看,我觉得有三方面可以做到:

  1. 复用性
  2. 易读性
  3. 可测性

复用性

提高可复用性可以极大的减少代码重复,提高工作效率,也能从根源上杜绝一些bug的产生,同时快速支持类似需求的实现。

对重复零容忍

我理解复用性最主要的就是解决代码重复问题。好的程序员应该禁止纯粹copy,尽量做到对重复代码的零容忍。

以前在项目中经常看到几个页面UI长得很像,逻辑也差不多,于是开发人员省懒,直接把旧页面的组件复制过来,改改变量就是一个新的页面。这种垃圾代码越堆越多,每次需求改动都要把同样的改动复制到很多个地方,如果有细节差异还要记得复制粘贴后再修改,极容易漏写错写出各自BUG。

不仅如此,同样的逻辑(比如基本的数据结构变换,通用的业务处理逻辑)在各个node服务中多次出现,出问题要修改多份代码的现象也十分恶心。

总结下来就是,每当你写了几行一样的代码时,就要思考是不是可以封装一下,UI组件也好,逻辑也好,都应该有这个意识。虽然在第一次写的时候可能很痛苦,但是在为后来的自己积福,功能改动和DEBUG都能省下不少时间。

差异配置化

在封装代码的过程中,免不了要处理一些差异。以UI代码为例,不同场景的表单选项,展示样式,页面结构都有可能不一样,这个时候就要思考哪些部分是公共的,可以抽出来;哪些部分是私有的,要调用方自定义,是否需要默认值等。

一般而言,简单的差异化可以用配置(Map,List等结构)来解决,比如一个表格,对于不同用户要根据他的角色渲染对应的列,不能全量展示。这个时候我们可以先定义一个全量配置fullColumns,用roles字段定义每一列可见的角色。

const fullColumns = [  {    roles: ['dev'],
    render: () => 'dev',
  },
  {
    roles: ['qa'],
    render: () => 'qa',
  },
  {
    roles: ['pm'],
    render: () => 'pm',
  },
]

然后在调用方页面渲染时,只需要拿到当前用户的角色,过滤fullColumns再渲染即可。

当然这个例子比较简单,其他复杂的场景都可以以此类推。如果遇到特别复杂的差异,配置项的维度太多可能不好表达,那还是建议写函数把差异逻辑封装在内部去处理。

组件模块化

组件模块化是指借鉴class代码的风格,要学习设计模式,比如学会用策略模式替代if else,我也还在学习理论的路上,实践经验不足。

首先要了解基本的SOLID原则:

1 单一职责原则(SRP)

2 开放封闭原则(OCP)

3 里氏替换原则(LSP)

4 接口隔离原则(ISP)

5 依赖倒置原则(DIP)

组件要做好封装,内部细节不暴露给外部,只感知必要的参数和回调;组件之间要定义好标准的协议,面向协议开发;组件只能划分清晰,尽量满足单一职责原则,单个组件的代码量尽可能少。

总之,组件设计要做到高内聚低耦合,改动一个组件不会对其他组件产生影响,即便有影响,范围也是可知可控的。

易读性

好代码的标准有很多,但其中一条一定是让菜鸟也能看懂,这一点对于多人协作开发十分重要。

命名语义清晰

变量、函数、组件、Class、Type等的命名规范是老生常谈了,这一点《代码整洁之道》也有提到,一个好的命名可以避免很多理解上的问题,名字起得好,代码清晰就像看一篇英文文章一样。

至于具体的命名规范因团队而异,这里只举一些糟糕的反例参考。

  • 语义不清晰,比如一个函数被命名为render,这个名字太空洞,看不出要渲染什么元素;或者一个UI组件命名为Text,使用者不明白具体渲染什么格式的文本
  • 动词名词形容词等使用不当,比如一个布尔类型的变量被命名为executeSubmit,这种动+名结构,一般默认会被当做一个函数处理,但其实它是个变量,产生了歧义
  • 大小写驼峰混乱,一般Class、Type和组件采用大驼峰,函数、变量采用小驼峰,但如果不遵循这个规矩,光看命名无法区分出是哪种类型或者产生误判
  • 用途和命名不一致,比如一个函数叫getUserInfo,看起来是一个读的操作,但其实内部有写的操作,光看函数命名是无法感知到的,使用起来会有误解
  • 过于简陋的名称,比如经典的变量a, b, c或者one, two , three,甚至是拼音,这些都是无效命名

善用TypeScript类型

对于前端来说,如果项目语言用的是TypeScript,那还是应该利用好类型系统。

对于函数的入参和返回都应该定义清晰,尽量避免AnyScript,复杂场景要用好泛型,在不了解函数逻辑的前提下,通过类型也能大概知道它在做什么,调用它要注意什么参数细节,会返回什么数据结构,这些都应该一目了然,而不应该像面对一个黑盒。一个项目如果数据类型定义的很完备,那么光看Type就能了解业务的结构。

在开发阶段,还可以借助IDE的提示可以提前发现编码错误,也不用专门用文档记录数据类型,减少心智负担。

只写有必要的注释

编程界有句名言,”好的代码是自注释的“,这句话不无道理。

如果你的代码在设计上解耦做的好,可读性高,逻辑组织清晰,那么不需要注释也能无障碍阅读。如果代码写得不好,加再多的注释也无法从根源上解决问题,甚至错误的注释可能把人引入歧途;像”获取数据“、”渲染表格“这种废话更是没必要写。

我的观点是只写有必要的注释,并且写了每次代码变更时都要维护注释。比如一些写跟业务有关的特殊逻辑,告诉别人为什么这么写;或者一些待优化或者有坑但暂时没解决的代码,加个TODO标记;或者给一些函数加JsDocument,告诉别人要怎么用等等。

可测性

代码要便于维护,不仅要在设计方面着手,也要在测试方面做出努力。要尽量保证代码是可测并且方便测试的,这样即便有问题也能在上线前暴露出来,而不是像黑盒一样,我不知道为什么这么改就能跑了,出了问题也无法复现,这样子很可怕。

我认为测试这一点需要一些工具(比如单元测试)来保证,而不能纯靠人工的点点点,尤其是开发时间紧张时,自测很难覆盖全面甚至不测,直接交付给QA,如果QA时间也紧张测试不充分,直接上线那就等着用户投诉吧。

目前我们的项目正恰好缺乏单测,加上设计的不合理,所以代码质量会显得很差。后续要专门研究下这方面。