50 万行代码喂出来的一些编程经验
踏入职场后写代码已经有 14 个年头,保守估计应该垒了有 50 万行的代码。尤其最近 1 年多从 0 开始写起 Bytebase,日常也会 review 同事的代码。趁着端午也总结了一些经验,这些经验聚焦在写代码的具体细节上,在道,法,术,技中更多归之于技。首先声明这些技是用来帮助写出更可维护的代码,而不是更快地写出代码,Enjoy。
命名篇
字母使用小写。有些系统对大小写不敏感。
名词使用单数。英文的复数规则比较复杂,尤其对于英语非母语程序员来说,用复数容易造成代码命名不一致。如果是表示数组的话,可以加上 List 后缀。
动词使用一般时态。同样英文的动词被动时态规则也有好多种,使用被动时态容易造成命名不一致。
使用 Reversed domain name notation (reverse-DNS) 来命名。比如在 Bytebase 里定义 issue 创建这个活动,我们可以用 bb.issue.create。而成员的创建可以用 bb.member.create。名字定义本身包含了结构,具备更好的可读性,同时搜索的时候也可以使用 bb.member 这样的前缀搜索。
范式篇
使用 Command Pattern,把每一个变更的行为都包装成一个 Command,具体 Command 的名字定义使用前面提到的 reversed domain name notation。这样的好处是我们能提取出一个中央的组件来处理这些命令,像 undo/redo, logging 这些就变得相当容易。绝大多数应用框架其实都有 Command Pattern 的身影。
考虑使用 Soft Delete 而不是 Hard Delete,通常在数据库表结构里加上一列 status, 其中一个值是 PENDING_DELETE,可以有效防止误操作,让 undo 的实现也变得简单。
除非有必须的理由,尽量避免持久化状态。持久化状态会大大增加将来迁移和升级的复杂度。
架构设计篇
先设计 Schema / 领域建模,再设计 API,再其他。
Schema / 领域建模设计的 4 个要点,对象本身,对象的行为,对象的约束以及对象间的关系。
培养使用 namespace 的意识。namespace 能帮助更好地做模块设计,前面提到的 reversed domain name notation 就是 namespace 的一种运用。
大型项目需要尽早采用 Plugin 架构,区分 Core 和 Plugin 部分。Linux 的 Core 其实不大,绝大多数都是 Driver 代码 (Plugin 的一种类型)。Bytebase 的代码也做了类似的拆分。
技术选型篇
使用 Restful 而不是 GraphQL。
RESTful 是更成熟的技术,有成熟的生态。
RESTful 帮助团队更早关注领域建模,因为需要识别出对象以及对象上的行为。
RESTful 帮助架构做更好的分层。RESTful 定义了更加克制的接口,界定了前后端的边界。而 GraphQL 很容易穿透界定的边界和抽象。
使用关系型数据库而不是 NoSQL。
想走得快用 MySQL,要走得远用 PostgreSQL。
除非业务本身需要对接多种数据库,否则谨慎考虑使用 ORM。ORM 通常只支持所有数据库功能的最大公约数,一些特色功能要么不支持,要么要用晦涩的语法。而且你无法精确控制生成的 SQL,影响代码可读性。
新的后端项目优先考虑使用 Go。
最后
写代码,正确性是第一位的,可读性是第二位的,性能是最末位的。糟糕的程序员也能写出机器能执行的代码,合格的程序员要写别人能理解的代码。
代码写得好用处本身并不大。技术总是服务于业务的,要结合业务理解写代码。最好的解法是不加任何额外的代码就能解决业务问题,而能胜过这个解法的则是通过减少代码来解决问题。
Deleted code is debugged code.
上面提到的观点在 Bytebase 中也有不少实践,感兴趣可以翻阅我们的代码:
https://github.com/bytebase/bytebase