子兮子兮 子兮子兮

子兮子兮风兮寒,三江七泽情洄沿。

目录
关于“长期维护”的几条现实教训
/    

关于“长期维护”的几条现实教训

很多代码不是写坏的,而是“慢慢被时间拖坏的”。

做软件开发这些年,一个越来越深的体会是:
真正考验一个系统的,从来不是它刚写完时有多优雅,而是 一年、三年之后,它是否还能被安心地修改和维护

这篇文章不讲具体框架,也不讲某个技巧,而是记录一些在实际项目中反复踩坑后,对「长期维护」这件事的理解,算是写给未来自己的备忘。


一、能跑 ≠ 可维护

在项目早期,我们很容易被下面几个目标牵着走:

  • 功能能用
  • 需求能交付
  • 进度能推进

于是代码常常呈现出一种状态:

  • 结构勉强合理
  • 命名“差不多能懂”
  • 注释几乎没有
  • //TODO 留了一堆

短期看没问题,但半年后再回头看:

func handle(a, b int) int {
	if a == 1 {
		// ...
	}
	return b
}

你会完全想不起来:

  • a 代表什么?
  • 为什么等于 1 要特殊处理?
  • 这个返回值是否还有副作用?

可维护性的第一敌人不是复杂,而是“语义丢失”。

二、命名,其实是在写文档

很多人低估了命名的重要性。

在长期维护的项目中,真正被阅读最多的不是 README,而是:

  • 变量名
  • 函数名
  • 结构体字段名
  • 接口名

好的命名本质上是在回答三个问题:

  1. 这个东西“是什么”
  2. 它“负责什么”
  3. 它“不负责什么”

例如在 Go 项目中:

GetUser()

GetUserByID()

差别非常大。

再比如:

Process()

几乎等于没说,而:

ProcessExpiredOrders()

即使不看实现,也能知道边界。

  • 一个经验法则是:
    如果你需要靠注释才能解释名字,那名字本身就不够好。

三、结构稳定,比“设计精妙”更重要

很多人在项目初期容易陷入“设计焦虑”:

  • 要不要上领域模型?
  • 要不要抽接口?
  • 这里是不是应该用策略模式?
  • 将来扩展怎么办?

但现实是:
大多数系统死于过度设计,而不是设计不足。

在长期维护中,我逐渐认可一种原则:

先让目录结构稳定,再谈模式和抽象。

例如在 Go 项目中:

/cmd
/internal
  /service
  /repo
  /handler
  /model

哪怕内部实现不断变化,只要这几个层次稳定:

  • 职责就不会混乱
  • 新人容易上手
  • 重构不会牵一发动全身

稳定的“物理结构”,比聪明的抽象更抗时间。

四、日志是你写给未来的自己的信

很多系统的日志,要么太少,要么太多。

常见两种极端:

  1. 几乎没有关键日志
    出问题只能靠打断点或复现
  2. 疯狂打印 debug
    真出问题时反而淹没在海量信息里

比较理想的日志应该满足:

  • 能看出一次请求“做了什么”
  • 能定位失败发生在哪一步
  • 能还原关键上下文

例如:

log.Infof("create order failed, userID=%d, err=%v", userID, err)

比单纯的:

log.Error(err)

要有价值得多。

日志不是给机器看的,是给未来凌晨两点排障的自己看的

五、为“半年后的自己”写代码

一个很现实的问题是:

你现在写的代码,未来大概率还是你自己维护。

所以一个很实用的判断标准是:

如果三个月后再打开这段代码,你是否会骂当时的自己?

为了避免这种情况,我现在会刻意做几件小事:

  • 函数控制在合理长度内
  • 关键逻辑写清楚“为什么这么做”
  • 避免过度魔法值
  • 宁可多写几行,也不写晦涩的一行

代码不是炫技作品,而是长期资产。

六、写在最后

维护,是软件生命周期中占比最长的一段时间,却往往最不被重视。

如果说写代码是在“解决问题”,那维护代码,其实是在“减少未来的问题”。

希望这篇文章,也能在未来某天被我自己翻出来时,仍然觉得:

嗯,这些话当时是真的想明白了。