有关注释的杂七杂八 🌲
别给糟糕的代码加注释,重新写吧
- 什么也比不上放置良好的注释来的有用
- 什么也不会比乱七八糟的注释更有本事搞乱一个模块
- 什么也不会比陈旧,提供错误信息的注释更具破坏性
若编程语言有足够的表达力,或者我们长于用这些语言来表达意图,就不那么需要注释 —— 也许根本不需要。
上面的话引自《代码整洁之道》。但是从事这个行业越久我越无法否认其正确性,我们必须要知道的一件事是代码具有实时性,即你现在项目中的代码总是当前最新的,否则也无法正确运行。然而上面的注释我们根本无法知道是什么时候写的,不具备实时性。
- 代码是在变动,演化的。然而注释并不能随之变动
- 程序员不会长期维护注释
- 注释会撒谎,而代码不会
- 不准确的注释比没注释坏的多
- ...
所以我的想法很坚定,注释无法美化糟糕的代码,与其花时间为糟糕的代码编写解释不
如花时间把糟糕的代码变得整洁。
用代码来阐述思想一直是最好的办法。
当然总有些注释是必须的或是有利的,还有一些注释是有害的,下面和大家聊一聊什么
是好注释,什么是坏注释。
好注释 🌈
- 法律信息
比如版权或者著作权的声明
- 提供信息的注释
比如描述一个方法的返回值,但是其实可以利用函数名来传达信息
- 阐释
把某些晦涩难懂的参数或者返回值翻译成某种可读的形式,更好的方式是让参数和返回值自身就足够清楚
- TODO 注释
这个可能大家都会经常用,用来记录我们哪里还有任务没有完成
- 放大
比如一个看似不起眼却很重要的变量,我们可以用注释凸显它的重要性
- ...
坏注释 😈
- 喃喃自语
只有作者读的懂的注释,当你打算开始写注释,就要讲清楚原委,和读者有良好的沟通
- 多余的注释
有的注释写不写没啥作用,很简单的方法都要写注释,甚至读代码都比看注释快
- 误导性注释
程序员都已经够辛苦了,你还要用注释欺骗人家
- 循规式注释
要求每个方法每个变量都要有注释,很多废话只会扰乱读者
- 位置标记
比如打了一堆 ****** 来标注位置,这个我上学的时候经常干
- 废话注释
毫无作用的废话
- 注释掉的代码
很多读者会想,代码依然留在那一定有原因,最后不敢删除畏手畏脚
- 信息过多的注释
注释中包含很多无关的细节,其实对读者完全没有必要
- ...
优雅的不写注释 🌿
首先我再次阐述之前说过的话,编码实际上是一种社会行为,是需要沟通的。而如何让我们不借助注释来阐述我们的思想,其实是需要我们长期探索并在实践中积累经验的,从我的经验与视角出发,其实让我们的代码库更加整洁其实主要从以下两个方面考量:
- 整洁编码
- 前端架构
下面我分开来讲~
注意,编码不是一个人的事情,在我眼里如何做到团队成员编码风格的相近才是最具成效且需要长期努力的任务,也是相对理想且难以做到的。正所谓,就算我们写的代码很烂,但是烂的我们的成员可以相互理解,也是一种优秀「瞎说的,哈哈哈,代码可维护性还是要团队成员一起追求的」。
整洁编码 📚
首先我先引用几位前辈的话,带大家感受一下,什么样的代码是整洁的:
- Bjarne:我喜欢优雅和高效的代码,代码的逻辑应当直接了当,叫缺陷难以隐藏们。尽量减少依赖关系,使之便于维护,依据某种分层战略完善错误处理代码,性能调至最优,省得引诱别人做没有规矩的优化,搞出一堆混乱出来,整洁的代码只做好一件事。
- Grady: 整洁的代码简单直接,整洁的代码从不隐藏设计者的意图,充满干净利落的抽象和直截了当的控制语句。
对于整洁编码可以先简单总结:
- 尽量减少依赖关系,便于维护
- 简单直接,充满了干净利落逻辑处理和直截了当的控制语句。
- 能够全部运行通过,并配有单元测试和验收测试
- 没有重复的代码
- 写的代码能够完全提现我们的设计理念「这个可以通过类、方法、属性的命名,代码逻辑编码的清晰来体现」
在我们日常编码中,命名和函数可以说是我们最常接触的,也是最能影响我们代码整洁度的。于是本文中,我将围绕这两个方向为大家介绍几种易于上手的整洁编码方案。
下文参考我之前写过的一篇文章:关于整洁代码与重构的摸爬滚打
命名 🌟
- 只要命名需要通过注释来补充,就表示我们的命名还是存在问题
- 所有的命名都要有实际意义,命名会告诉你它为什么存在,它做什么事情,应该怎么用
比如列举一段曾经上学的时候可能写出的代码:
#include <stdio.h> int main(){ printf("Hello, C! \n"); int i = 10; int m = 1; for(int j = 0; j < i; j+=m){ for(int n = 0; n< i-j;n++){ printf("*"); } printf("\n"); } return 0; }
我们看这里命名都是一大堆 i,m,n,j
之类的根本不知道这些变量用来干嘛,其实这段代码最后仅仅打印出来的是 *
组成的直角三角形。但是当时写代码我确实就是这样,i, j,m,n
等等字母用了一遍,也不包含什么语义上的东西,变量命名就是字母表里面选。
当然现在的命名就高端多了,开始从词典里面找了,还要排列组合,比如 getUser
,isAdmin
。语义上提升了,通过命名我们也可以直观的判断这个方法是干嘛的,这个变量是干嘛的。
这样看其实命名是很有学问的事情,下面我开始列举几点命名中可以快速提升代码整洁度的方法:
- 避免引起误导
不要用不该作为变量的专有名词来为变量命名,一组账号
accountList
,却不是List类型,也是存在误导。命名过于相似:比如XYZHandlerForAORBORC
和XYZControllerForAORBORDORC
,谁能一眼就看出来呢~
- 做有意义的区分
let fn = (array1,array2) =>{ for(let i =0 ;i<array1.length;i++){ array2[i] = array1[i]; } }
比如上面
array1
和array2
就不是有意义的区分,这只是一个赋值操作,完全可以是sourceArray
和DesArray
。再比如 起的名字:
userInfo
,userData
都是这种的,我们很难读懂这两个有啥子区别,这种区分也没啥意义,说白了这只是单词拼写的区分,而不是在语义上区分开了。
- 使用读的出来的名称
编程是社会活动,免不了与人交流对话,使用难以轻松读出来的声音会导致你的思想难于传达。并且人类的大脑中有专门处理语言的区域,可以辅助你理解问题,不加以运用简直暴殄天物。简单举个例子:getYMDHMS,这个方法就是获取时间,然而就是难以阅读,是不好的命名。
- 使用可以搜索的名称
之前的代码,我用个
i
作为变量。如果代码很长,我这想要追踪一下这个i
的变化,简直折磨。同理我不喜欢直接以value
,data
,info
等单词直接做变量,因为他们经常以其他变量的组成部分出现,难以追踪。
- 程序中有意义的数字或者字符串应该用常量进行替换,方便查找
export const DEFAULT_ORDERBY = '-updateTime' export const DEFAULT_CHECKEDNUM = 0
比如采用上面的方式,既可以让代码更加语义化也方便集中修改
- 类名和对象名应为名词或名词词组,方法名应为动词或动词词组
比如我们常用的
updatexxx
,filteredXXX
都是这样的命名规则
- 属性命名添加有用必要的语境,但是短名称如果足够用清楚,就比长名称好,别添加不必要的语境
- 每个概念对应一个词
比如
tag
和label
,ticket
和workOrder
各种混着用岂不是乱糟糟的,这读者容易混淆,也会为以后造成负担,也可能会隐藏一些 bug。所以我们在项目开发前可以确定一个名词术语表来避免这种情况发生。
- ...
函数 🌟
大师写代码是在讲故事,而不是在写程序。
- 短小:20封顶最佳
- 函数的缩进层级尽可能的少
- 函数参数尽量少
- 使用具有描述性的函数名
当然函数越短小,功能越集中,就越便于取好名字
- 抽取异常处理,即
try-catch
就是一个函数 ,函数应该只做一件事,错误处理就是一件事 - 标识参数丑陋不堪
const updateList = (flag) { if(flag){ // ... } else { // ... } }
比如一个方法,定义成上面这个样子,我们很难通过方法定义直接了解其能力以及参数的含义。
- 函数名是动词,参数是名词,并保证顺序
比如
saveField(name)
,assertExpectedEqualsActual(expected,actual)
- 无副作用
比如一个方法名是
updateList
,后来者应该顺理成章的认为这个方法只会更新列表,然而开发者在这个方法中加入了其他的逻辑,可能导致后来者在使用这个方法后导致副作用,而代码报错无法正常运行。
- 重复是软件中一切邪恶的根源,拒绝重复的代码
- ...
写代码和写文章一样,先去想你要写什么,最后再去打磨,初稿也许粗糙无序,那就要斟酌推敲,直到达成心中的样子。编程艺术也是语言设计的艺术。
前端架构 🎋
本人现在工作一年有余,一年半不足,对于前端架构并不能很好的输出给大家,所以在此给大家先挖一个大坑,本章节中如有错误理解,请大家不吝赐教,与我探讨交流,感谢。
首先,我先解释一下我为什么要把前端架构放在这样的一篇文章中,其实是存在两条原因:
- 从个人开发角度来看,优秀的前端架构可以增强代码的维护性
试想一个组织结构恶臭的项目,一定会影响你的阅读的,杂乱不堪的组件划分原则,不清晰的边界通通都会成为巨大的阻力。
- 最近换了组,到了天擎终端平台组,新的
leader
也分享了很多关于组件化的经验与理解
浅薄无知的小寒草🌿,在线求鞭策。
那么,大家在提到前端架构的时候,会想到什么呢,我反正会想到以下几点:
- 组件化
- 架构模式
- 规范 / 约定
- 分层
- ...
下面我逐条来讲~
架构模式 ✨
组件化我先跳过,最后再说,先说说架构模式,大家脑子里一定会想到 MVVM
,MVC
等模式,比如我们常用的 Vue
框架中的 MVVM
,以及普遍在 Spring
那一套中被提及并在在 nest.js
中有所应用的 MVC
。但是关于架构模式前端说的可能还是相对较少,我的水平也有限,而且说起来可能就会跑题了,于是也不在本文过多赘述。
规范&约定 ✨
关于规范或者约定,常见的包括:
- 目录结构
- 命名规范
- 术语表
- ...
其实这几点我们很好理解,我们会通过约定或者脚手架等方式来规范化我们的目录结构,使我们同一个产线下项目的目录结构保证一致。以及我们在开发前的设计阶段可能也需要出具一份术语表,这个前文也听到过一个含义用一个确定的词来表示,否则可能会导致代码的混乱。
关于命名规范,首先我们需要去约定一个统一的命名规则,我们常见的是变量命名为小驼峰,文件命名为连字符。但是这个命名规范其实我们可以做的事情不止这些,比如我说几个例子:
- 前端命名规范是小驼峰,服务端命名是下划线,我们怎么处理让前端编码中屏蔽掉命名规则差异。
- 同一个含义我们可以用很多命名来表示,比如:
handleStaffUpdate
/updateStaff
。在项目初期我们完全可以对其进行约束使用哪种命名风格,以让我们项目一致性加强。 - ...
分层 ✨
关于分层,大家的差异可能会比较大,比如我们可能会把我们的前端项目分为以下几层:
- 业务层
- 服务层
- 模型层「可能有也可能没有」
业务层就是我们比较熟悉的,各种业务代码。
服务层「server
」不知道大家的项目中有没有,我们项目使用 grpc
接口,由于接口粒度较高,我们通常会在 server
层对接口再次处理,合并,或者在这个层去完成一些服务端不合理设计的屏蔽。
模型层「model
」不常有,但是一些复杂的又需要复用的逻辑可能有这个层,就相当于逻辑的抽象,脱离于视图,之后如果我们需要复用这里的逻辑,而视图不同,我们就可以使用这个 model
。
合理的分层可以让我们的项目更清晰,减少代码冗杂,提升可维护性。
组件化 ✨
其实组件化一直都是前端架构中的大课题,首先我们可以通过组件化能得到什么,其实最重要的可能就是:
- 复用
不知道大家的项目有没有统计代码复用率,我们是有的,而且这也是前端工程质量很重要的一个指标。然而在追求组件化的过程中其实我们很少会拥有一个衡量标准:
- 什么情况需要拆分组件?
- 什么情况不需要拆分组件?
团队对这个问题没有一个统一认知的情况下很容易造成:
- 五花八门的组件拆分原则导致代码结构混乱
- 无效的组件拆分导致文件过多,维护困难
- 过深的组件嵌套层级「经历过的人一定会对此深恶痛绝」
- ...
其实我最开始的时候也喜欢把组件按照很细的粒度进行拆分,想的是总会有用到的时候嘛,但是从架构整洁的角度出发,过细或者过于粗糙的组件拆分都会导致维护困难,复用困难等问题,现在的我可能更会从复用性角度出发:
- 这个东西会不会复用?
只从复用性考量很容易的就会把组件区分为两大类:
- 需要复用的组件
- 几乎不会被复用的组件
注意我没有说什么组件是肯定不会被复用的,而是几乎不会被复用。
所以我们就可以坐下来思考,把我们工作中常见的场景拎出来,过一遍,因为我们工作的业务场景不同,所以我肯定还是以我的业务场景出发,那么我可以把我的组件分成几种:
- page 组件
- layout 组件
- 业务组件
其中我认为,page
组件是几乎不会复用的组件,layout
组件和业务组件在我眼里是可以复用的组件。
这只是很粗糙的的区分,之后还有很多问题:
- 如何把业务组件写的好用
- 如何确定一个组件的边界
- ...
这些我们就要从消费者角度考量了。
当然其实组件化也可以和分层一起考虑,因为组件其实也会有层级,比如:
- 基础 ui 组件[参考element-ui]
- 基础业务组件
基础业务组件也可以按照是否跨模块等原则继续进行分层,这个可以按照大家的业务场景自行考量。
总结 ✨
从实际经验出发,合理的架构确实是项目易于维护「从而优雅的不写注释🌿」,而这是一个自顶向下分析决策的过程,本章节篇幅有限,加上我水平有限,无法在此过多赘述,还请大家持续期待我的分享。
结束语 ☀️
那么本篇文章就结束了,涵盖了我个人经历上的摸爬滚打,解析什么样的注释是好的,
什么样的注释是坏的,并从编码整洁度与前端架构的角度出发来考量如何提升代码的可维护性。以此来论述我的观点:
注释不是维护代码的银弹,而便于维护的代码需要从整洁编码与前端架构两个「或者更多」层面入手。
我工作的时间不长也不短了,已经一年出头了,我一直秉承着编码是社会性工作,需要协同合作,代码的可维护性也是一名职业软件工程师需要持续追求的观点。
思考,实践,回顾的过程没有停歇,我在此也希望大家多思考,作为一名工程师我们需要追求的不仅仅只有:
- 这个框架的原理
- 那个技术的底层实现
- 新出来了这个技术栈
还有作为一个工程师
最根本的能力:
- 如何把一个工程做好「健壮性,可维护...」
注意本文寒草🌿没有踩一捧一,只是诉说我的观点,在未来成长中我的观点也可能发生变化。
写在最后
生我何用?
不能欢笑
灭我何用?
不减狂骄