代码整洁之道

人最大的痛苦,就是无法跨越知道和做到的鸿沟 ------罗翔

该篇不写那些费脑的硬知识点了,聊点认知方面,些许轻松点的话题: 代码整洁之道。

对本职工作认真负责,是一种基本素养,对于一线正在撸代码的程序员,写好代码是一种天职,所以给我个人给本篇再加一个副标题------ 程序员职业素养。

引子

编程文化拥有悠久的历史,这个话题也被前辈们广泛讨论过,本人也在不同场合中和朋友、同事零零碎碎的聊过一些,还在某乎上回答过几个问题,之所以再正式的聊这个话题,是因为在近期我司产品研发部总监做了一次分享,叫做代码整洁之道,听完还是有所触动。

声明:该篇不是照本宣科,是《代码整洁之道》+ 总监的技术分享 + 自身工作经历结合。

  • 在座有多少人读过此书?我认为这本书应该是所有程序员都需要去读的。
  • 有谁曾为自己写了一段好的代码沾沾自喜?
  • 又有谁在过了许久看自己之前写的代码有过感触:写的依旧不错/糟糕透顶?

这是来自分享开篇的一些发问,不知是否触动了各位读者,大家也可以在心中回答一下,沉思过后相信各位读者会有些话要说,对于已经手撸千万行的你,也一定有自己的一套"代码整洁之道",我们不妨一起走过下文,在这过程中对比一下你心中的"道"。

本文目的:重温一些代码整洁之道,希望可以给日常撸业务的你带来一些动力/兴致/小幸福。

为什么要写整洁的代码

说这个问题,我们反向的想一下,面对不整洁的代码会怎么样("屎山"),是不是有很多话想说,我来起个头,说一下本人曾吐槽过的(代码就不上图了):

  • 这么复杂的逻辑,变量/函数名字给个缩写,你让大家猜它是谁/干啥呢?
  • js 里一个函数有 7、8 个参数,又没类型,光看调用来回切换要花十几分钟,闹呢?
  • 一个函数放好几百行代码,让我来回切换,你是怕我得颈椎病啊?
  • 在一些底层计算出现莫名的数值,不明不白的就加上或减去一个数字,咱也不知道它表达的啥?
  • if 判断的条件,直接写计算表达式或函数,能有个 4、5 行,每次看这段代码都要花很长时间,还总是出 bug,这不妥妥的"雷区"?
  • 这注释明明写的是 a 逻辑,结果下面代码写的是 b 逻辑,坑啊...
  • 一模一样的代码,复制 n 遍,写这段代码的脑子有坑吧。
  • 一个函数明明写的是 getXXX,结果里面改原始数据?出 bug 找半天,nm。
  • ......

所以,写出整洁的代码就是避免少出现以上出现的情况,可以总结为以下几条:

  • 更具可读性
  • 更节约时间、更高效
  • 更易维护
  • 更具扩展性
  • 更易重用
  • 更容易测试

命名

命名是程序界一级难题

起一个好的名字,至关重要,但并不容易,我常常为了起一个好的名字抓耳挠腮的,有时候跟合作的人会 battle 半天,我们需要建立一个认知: 在长远来看,花费大量时间来选择一个好名字是值得的,因为它所带来的价值或节约的时间远远超过了起名所花费的时间

起名字也分几个等级:

  1. 最基本的也是见名知意
  2. 好一点的可以更恰当的表现业务
  3. 再高级一点的可以覆盖领域的概念

有兴趣大家可以看下,《架构师修炼之道》提出对的 7 个阶段,以及如何提炼一个好的名字,下图是之前截的片段

如果要写全关于如何命名我怕是再写上 1000 字也不一定够,所以下面只写一些比较容易做到的,也是最基本的底线:

  1. 名字尽可能具体些(不要怕长),少用缩写 ,除非是大家共识的(JDBC、XML)
typescript 复制代码
    // bad
    const act = 1; // 不清楚是 active 还是 action
    function recursion() {}; // 只知道递归,但是不知道为什么要递归

    // good
    const action = 10;
    function getAllChildrenByNodes() {}
  1. 尽可能起一些简单的单词,最好能读出来,能写出来,容易记住的常规单词 ,想象一下,同事之间讨论读不出来这个单词的尴尬场景,记得之前做的一个商城项目,同事在设计表的时候用了一个单词 integration ,这个单词的本意是数学中的积分,而业务中实际上表达用户积分。这里有一份软件开发常用词汇表: 这里
  2. 变量用名词或形容词 + 名词,函数名用动词 + 名词或动词 + 形容词 + 名词,类名用名词
typescript 复制代码
    function calculateTotalPrice(){} // 计算总价
    function validateUserPermission(){} // 验证用户权限
    const customerName = "zhangsan"; // 客户姓名
    const recentOrders = [] // 最近的订单
    class PermissionBuilder() {} // 权限构造器
  1. 布尔值或函数用 is 或 has 开头,能优先用肯定,不要用否定,更不要用双重否定表示肯定 ,这很不高效
typescript 复制代码
    // bad
    isNotVisible = true;
    // good
    isVisible = false;
    // bad
    isNotInvisible = true; // 不是不可见的,表示可见的
    // good
    isVisible = true;

函数

  1. 函数尽可能的短小 ,有兴趣看一些知名函数编程的库: Lodash
  2. 参数尽可能的少 ,最佳状态是没有参数,其次是 1 个、2 个、3 个,理论上最好不要超过 3 个,不过具体还是要根据实际场景,有些情况下也难以避免。
  3. 遵循单一原则,一个函数只做一件事 。一个判断的小技巧:尝试想象对一个函数做单元测试,每可以测的一点就可以罗列一个函数。
  4. 参数最好不用布尔值,在参数多的情况最好使用对象代替 ,因为每次调用时都需要看一下原函数的参数是什么,顺序也要对照好,扩展也不方便,会越来越长。示例:
typescript 复制代码
    function getUsersByIds(ids: string[], isDeleted?: boolean, isArchived?: boolean){}
    getUsersByIds([], true, true);
    // good
    function getUsersByIds(ids, options: {isDeleted?: boolean, isArchived?: boolean}) {}
    getUsersByIds([], { isDeleted: true, isArchived: true})
  1. 函数尽可能不要依赖上下文 ,这点可以按照纯函数原则。
  2. 函数名、签名和函数逻辑和返回值必须保持一致, 挂羊头卖狗肉的行为非常可气,误导比读不懂代码更可怕,比如:
typescript 复制代码
    // 这个函数本意只是验证用户名是否合法,结果内部却包含修改用户信息的逻辑
    function verfiyUserName(user) {
      const regexp = /^[a-zA-Z0-9_-]{3,16}$/;
      if(regexp.test(user.name)) {
    	user.name = 'legal_' + user.name;
        return true;
      } else {
      	return false;
      }
    }
    const user = {id: 1, name: 'zhangsan' };
    const result = verfiyUserName();
  1. 函数不要产生副作用,尽可能不要改变原数据或上下文数据,使用不可变数据(Immutable Data) ,js 中也有一些让人诟病的原生函数,比如 sort、reverse、splice 等可以 filter 、map 或解构代替
typescript 复制代码
    const users = [
      {id: 2, name: '张三'},
      {id: 1, name: '李四'},
      {id: 3, name: '王五'}
    ];
    // 从用户列表中删除名字是王五的元素
    const newUsers = users.filter(x=> x.name !=='王五');
    // 按照 id 排序
    const newUsers = [...users].sort((a, b) => a.id - b.id);
  1. 提取重复的逻辑:我们应该时刻警醒,不要重复自己,当遇到两块一样的代码时,就要反思是不是可以提取出来。

注释

注释是弥补我们在用代码表达意图的失败表现 ------《代码整洁之道》

代码是具有表现力,所以书中以及我个人也是强烈不建议不加注释的,就是说能用代码表达就不要用,用注释表述不如起一个更恰当的名字,更好的函数签名。

因为很大程度上,我们添加了注释就要维护这段注释,又由于注释是不参与真正运行的,所以在后续的需求变化以及重构很容易出现注释没有及时更新,注释所描述的情况和实际运行的代码是不相匹配的,等其他人再看到这段代码时就会被误导,这种情况下错误的注释比没有注释更可恶。

再思考一下,你平常是怎么使用注释的,又为什么注释?

有意义的注释

  • 法律信息或许可证相关信息
  • 对外的接口,如 Public API、RPC Interface 等,通过注释可以让调用者快速了解如何使用,返回结果完整的描述
  • API Doc 用来生成文档的注释,而且经常要通过 URL 快速定位 API
  • 描述复杂的程序或业务
  • TODO:定义待办,应该做,但是由于某些原因目前还没做的, 需要注意的是,定期回顾这些 TODO,补充并删除
  • 废弃的方法注释
  • 警示
  • 特殊说明的注释 ,由于某些业务优先级高,经常出 Bug 的代码,改动需要做出解释

无意义的注释

  • 已经不在使用的代码,你可能觉得有可能还需要使用,所以暂时先注释,对于这种注释我们大胆的删掉,别慌,版本控制工具会保留修改记录
  • 变量或函数名、签名已经表明清楚意图的代码,就不要额外再用注释

用代码的表现力代替注释

  • 使用新变量 ,因为变量名本身就有说明的意义
  • 起一个好名字 ,与其花心思写注释,不如把这个时间和精力用来起一个好点的命名,如以下代码,第一个直接从名字上很简洁的说明该函数是计算平均值的,而第二段代码描述不清楚,需要用两条注释来说明先累加再求平均值,显然第一个更好

结语

正文内容就暂时告一段落,最后送给大家三句话 🤓

  1. 觉得写的不错,可以点点赞 🌹
  2. 如果有共鸣,或者有需要补充的,可以到评论区留言交流 🌹
  3. 赶快把项目中的"屎山",做个小优化吧

PS:关于如何写出整洁的代码涉及方面实在太多了,这一篇就先到这里,如果反响好的话,后续可能会继续更。

相关推荐
嚣张农民11 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
梓羽玩Python12 小时前
推荐一款用了5年的全能下载神器:Motrix!全平台支持,不限速下载网盘文件就靠它!
程序员·开源·github
梓羽玩Python12 小时前
这款一站式AI体验平台值得收藏起来!GPT-4o、GPT-4o Mini、Claude 3.5 Sonnet免费使用!
人工智能·程序员·设计
前端宝哥1 天前
10 个超赞的开发者工具,助你轻松提升效率
前端·程序员
XinZong1 天前
【VSCode插件推荐】想准时下班,你需要codemoss的帮助,分享AI写代码的愉快体验,附详细安装教程
前端·程序员
Goboy2 天前
0帧起步:3分钟打造个人博客,让技术成长与职业发展齐头并进
程序员·开源·操作系统
JaxNext2 天前
不选总统选配色,这一票投给 CSS logo
前端·css·程序员
y0ungsheep2 天前
CTF中的phar反序列化 [SWPU 2018]SimplePHP
运维·web安全·网络安全·php·代码规范
程序员鱼皮3 天前
刚毕业,去做边缘业务,还有救吗?
计算机·程序员·互联网·求职·简历
WujieLi3 天前
独立开发沉思录周刊:vol18.AI 正在成为无处不在的基础设施
程序员·设计·创业