关于编写可维护的代码的一些实践与想法

最近在改一个代码风格迥异并且经历过很多手的历史代码,发现中间中了很多代码设计上的一些反模式的错误。导致我修改代码以及新增功能的时候变得非常的困难,这篇文章是在带着个人情绪的情况下写出的,可能会存在一些主观性。

我下面列举几点我认为十分影响维护性的编码行为。

重复编写相同逻辑

冗长的相同逻辑,编写两遍。很多时候部分开发遇到一段性能较差的历史代码,但是又缺乏时间去优化这段代码,于是我们会重新写一套具有相同行为的代码,同时开出 API 接口出来用。虽然这样做的确是完成了既有的任务,但是其实是非常有隐患的。

如果有一个新来的同事需要修改既有 API 的行为,会狠狠的踩坑,因为两套逻辑同时在线上跑,并且具有相同的行为,很有可能这个新同事可能只改了一套逻辑,而且忽略了另外一套逻辑,这种情况真的很容易存在,因为新人往往是缺乏信息的。在 QA 不够充分的情况下,会引入难以排查的 BUG。

不要上来就直接加 if else 的特殊逻辑

很多时候,我们遇到一些需求,简单一看其实没什么修改的,只需要在主流程或者抽象中加入一句 if else 这样的特殊逻辑。 这样的简单的判断逻辑有很大可能在不久的将来,例如两三个月后狠狠的给你一拳。 在考虑加 if else 前,思考下这样写对之后自己的修改方便嘛?能否写在配置文件中用配置中心修改? 实在要加就需要给这段代码加上注释。需要确定你自己的代码的行为,而不是堆一堆逻辑上去,别人问到行为的时候说可能会做这个?可能会做那个? 通过巧合的方式编程,只会带来越来越多问题。

延迟抽象

程序员真的是一个非常有意思的群体,对 抽象 这件事情特别的钟情,包括我也是。我在各种仓库也见过好的抽象和烂的抽象。比较烂的抽象往往是那种,在一个接口 interface 中暴露出 10+ 个函数出来,同时每个函数的真正实现就只有两三行,这个我们一般叫它 宽接口。它的缺点是当你横向拓展的时候需要实现 10+ 个函数,非常痛苦,灵活性非常的差。同时每个实现的函数很有可能都是空实现,里面都没有实际执行的代码。

我的建议是:

  1. 先去写一些实现类,先简单用 switch 语句等行为控制调用,然后慢慢的根据实现类的共性慢慢地抽象出上层的接口,如果你用 Go 可以看看我之前对 Golang 抽象的一些思考。Golang: 如何处理日渐膨胀的 interface
  2. 将接口的函数数目大大减少,每个接口最多 4 个函数,这样实现起来较为方便,将大接口根据 单一职责原则 将功能拆分成不同的接口, 然后组合成不同的大接口。可以参考 Golang 的 https://cs.opensource.google/go/go/+/refs/tags/go1.18:src/io/io.go;l=127 的实现。

写代码少炫技

一行代码写出来一遍,但是会被人读百遍,所以写的时候简单性很重要,秀的代码/滥用设计模式的代码难以维护,同时会给系统带来复杂度。

将代码 group 起来

我经常发现一些代码,整个函数有 500+ 行,同时没有任何注释,并且整个函数 70% 都是在一个 for loop 里面做的。 我的建议就是如果你在写代码之前将这个函数要做的事情拆成一个个较小的事情,然后每个事情前面都会有注释,代码的可读性会好很多很多。

结语

吐槽结束,希望自己能在写代码的时候多思考一些,写出方便修改的代码。