代码整洁之道

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

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

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

引子

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

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

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

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

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

为什么要写整洁的代码

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

  • 这么复杂的逻辑,变量/函数名字给个缩写,你让大家猜它是谁/干啥呢?
  • 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:关于如何写出整洁的代码涉及方面实在太多了,这一篇就先到这里,如果反响好的话,后续可能会继续更。

相关推荐
AskHarries7 小时前
把一个外部系统接成 MCP 工具
后端·程序员
threerocks8 小时前
AI编程的商业模式已经在互联网大厂跑通了
程序员·aigc·ai编程
用户526835677908 小时前
云原生落地:如何配置 Alertmanager 插件,将 Prometheus 告警直接打通至硬件声光语音终端?
程序员
用户852495071848 小时前
我跟 AI 说了名字它转头就忘,后来我手动给它加了个"记忆"
程序员
zzzzzz3108 小时前
当甲方说'logo放大的同时再缩小一点'时,我用 AI 把这个需求做出来了
javascript·css·程序员
Hilaku8 小时前
Node.js 还能再战十年?给你一个不换引擎的理由
前端·javascript·程序员
Hyyy20 小时前
token是什么?为什么大模型会有上下文长度的限制
程序员·llm·ai编程
程序员cxuan1 天前
幽默,一个 Github 名字叫“马尾辫”,但是他给你省了 80% 的 token
人工智能·后端·程序员
kartjim1 天前
我用 AI 一小时写了一个世界杯数据可视化平台|前端 VibeCoding 初体验
前端·程序员·ai编程
SimonKing1 天前
艹,维护AI写的代码,我心态崩了......
java·后端·程序员