2016年,我写代码已经五年了。

这五年里,我写了很多代码——有项目代码、有开源代码、有自己折腾的小工具。代码量加起来,可能有几十万行了。

但是,直到写了五年,我才真正明白,什么叫"好代码"。

刚入行的时候,我对"好代码"的理解很肤浅,走了很多弯路。今天就来聊聊,我对"好代码"的理解,是如何一步步演变的,以及我现在认为什么才是"好代码"。

一、我对"好代码"理解的几个阶段

第一阶段:能跑的代码就是好代码

刚入行的时候,我觉得"能跑的代码就是好代码"。

那时候,我最大的目标就是"把功能实现"。只要代码能跑、能实现需求,就是好代码。至于代码写得好不好看、规不规范、别人能不能看懂,我根本不在乎。

那时候我写的代码,现在回头看,简直惨不忍睹:

  • 变量名随便起:a、b、c、tmp、data、info,根本不知道是什么意思
  • 函数写得很长:一个函数几百行,什么都干
  • 复制粘贴:同样的代码,复制粘贴十几次,从来不封装
  • 没有注释:代码是怎么想的,只有上帝和我知道(过几天连我自己都不知道了)
  • 硬编码:各种魔法数字、魔法字符串,散落在代码各处

但是那时候我觉得,"能跑就行",代码写得再烂,只要能实现功能,就是好代码。

现在想想,那时候的我,真是too young too simple。

第二阶段:代码越短越牛逼

工作了一两年之后,我开始觉得"代码越短越牛逼"。

那时候,我迷上了"一行代码解决问题"、"用最少的代码实现最多的功能"。我觉得,代码写得短,说明水平高;代码写得长,说明水平差。

于是,我开始追求"短代码":

  • 用三元运算符嵌套,一行写五六个条件判断
  • 用位运算代替普通运算,显得"高深"
  • 用各种语言的"黑魔法",写一些"看不懂但是很牛逼"的代码
  • 把多行代码压缩成一行,美其名曰"简洁"

那时候我写的代码,短是短了,但是可读性极差。别人看我的代码,要琢磨半天才能看懂;我自己过几个月再看,也要琢磨半天。

有一次,我写了一行"很牛逼"的代码,用了各种技巧,一行解决了一个复杂问题。我很得意,给同事看,同事看了半天,说"这行代码是干什么的?"我解释了半天,同事说"哦,原来是这样,但是你为什么不写清楚一点呢?"

那一刻,我开始反思:代码写得短,但是别人看不懂,这样的代码,真的是"好代码"吗?

第三阶段:设计模式用得多就是好

工作了三四年之后,我开始学习设计模式。那时候我觉得,"设计模式用得多就是好代码"。

我学了23种GoF设计模式,然后迫不及待地在项目中使用——不管适不适合,都要套个设计模式。简单的问题,也要用工厂模式、策略模式、观察者模式包装一下,显得"架构师水平"。

那时候我写的代码,设计模式用了一大堆,类图画得很漂亮,但是代码变得很复杂——一个简单的功能,要跳七八个类才能看懂。新增一个功能,要改好几个类。

有一次,我用了策略模式+工厂模式+单例模式,实现了一个"根据类型计算价格"的功能。我觉得很牛逼,但是后来需求变了,要加一种新的价格类型,我发现要改好几个类,加好几个新类,非常麻烦。而如果当初用一个简单的switch语句,加一个case就行了。

那一刻,我又开始反思:为了用设计模式而用设计模式,这样的代码,真的是"好代码"吗?

第四阶段:好代码的核心是"让人能看懂、能维护"

直到写了五年代码,我才真正明白:好代码的核心,不是"能跑"、不是"短"、不是"设计模式用得多",而是"让人能看懂、能维护"。

代码是写给人看的,不是写给机器看的。机器只关心代码能不能跑,但是人关心代码能不能看懂、能不能维护、能不能扩展。

一个项目,不是写完就完事了。代码写完之后,还要维护——修bug、加功能、重构、优化。这些工作,可能比写代码本身花的时间还多。而且,维护代码的人,可能不是写代码的人。

如果代码写得让人看不懂,维护的人就会很痛苦——要花大量时间理解代码,改代码的时候怕改坏,加功能的时候不知道从哪里下手。最终的结果就是,代码越来越烂,维护成本越来越高,最后只能推倒重写。

而好代码,让人一看就懂,改起来放心,加功能顺手。这样的代码,维护成本低,生命周期长,能为项目创造长期价值。

所以,好代码的核心,是"让人能看懂、能维护"。

二、好代码的标准

明白了好代码的核心之后,我总结了几个好代码的标准。

1. 可读性

可读性是好代码的第一标准。代码首先要让人能看懂。

可读性包括:

  • 命名清晰:变量名、函数名、类名,要见名知意,让人一看就知道是什么意思、干什么用的
  • 结构清晰:代码的组织结构要清晰,模块划分合理,函数职责单一
  • 逻辑清晰:代码的逻辑要清晰,避免过度嵌套、避免复杂的条件判断、避免"黑魔法"
  • 注释适当:该加注释的地方要加注释,解释"为什么这么做",而不是"做了什么"
  • 格式规范:代码的缩进、空格、换行、括号,要统一规范,让人看着舒服

可读性好的代码,就像一篇好文章——结构清晰、语言流畅、逻辑严密,让人读起来很舒服,很快就能理解。

2. 可维护性

可维护性是好代码的第二标准。代码要容易修改、容易扩展、容易调试。

可维护性包括:

  • 低耦合:模块之间的依赖要少,改一个模块不会影响其他模块
  • 高内聚:一个模块内部的功能要紧密相关,不要把不相关的功能塞在一个模块里
  • 单一职责:一个函数、一个类,只做一件事,不要什么都干
  • 开闭原则:对扩展开放,对修改关闭——加新功能的时候,尽量加新代码,而不是改旧代码
  • 可测试性:代码要容易写单元测试,依赖要能注入,不要写死

可维护性好的代码,改起来放心——改一个地方,不用担心其他地方出问题;加功能的时候,知道从哪里下手;出bug的时候,能快速定位。

3. 简洁

简洁是好代码的第三标准。代码要简单、直接,不要过度设计、不要过度复杂。

简洁包括:

  • 不要过度设计:不要为了"可能的未来需求"而过度设计,YAGNI原则(You Aren't Gonna Need It)
  • 不要重复:DRY原则(Don't Repeat Yourself),同样的代码不要重复写,要封装复用
  • 不要过度抽象:抽象是好的,但是过度抽象会让代码变得复杂难懂。抽象的层次要合适
  • 用最简单的方案解决问题:不要用大炮打蚊子,简单的问题用简单的方案
  • 删除无用代码:没用的代码、注释掉的代码、永远不会执行的代码,要及时删除

简洁的代码,就像一把锋利的刀——直接、有效、不拖泥带水。

4. 性能

性能是好代码的第四标准。代码要运行得快、占用资源少。

但是,性能不是"越早优化越好",而是"在需要的地方优化"。过早优化是万恶之源——在还不知道瓶颈在哪里的时候就优化,可能会优化错地方,而且会让代码变得复杂。

性能优化的正确姿势是:

  • 先写正确、清晰的代码
  • 用性能分析工具(profiler)找到瓶颈
  • 针对瓶颈进行优化
  • 优化后验证效果

好的性能,是"在正确的地方优化",而不是"处处优化"。

5. 健壮性

健壮性是好代码的第五标准。代码要能处理各种异常情况,不容易出bug。

健壮性包括:

  • 输入校验:对外部输入(用户输入、API参数、数据库数据)要做校验,不要假设输入一定是正确的
  • 异常处理:对可能出错的地方,要做异常处理,不要让程序崩溃
  • 边界条件:要考虑边界条件——空值、最大值、最小值、零、负数等
  • 容错降级:在依赖的服务出问题时,要有降级方案,不要让整个系统崩溃
  • 幂等性:对于写操作,要保证幂等——重复执行不会产生副作用

健壮的代码,在正常情况下能正常运行,在异常情况下也能优雅处理,不会轻易崩溃。

三、坏代码的特征

说了好代码的标准,再说说坏代码的特征。如果你写的代码有以下特征,那就要注意了。

1. 命名混乱

  • 变量名用a、b、c、tmp、data,不知道是什么意思
  • 函数名用doSomething、handleData,不知道具体干什么
  • 拼写错误、大小写混乱、中英文混用

2. 函数过长

  • 一个函数几百行甚至上千行,什么都干
  • 函数内部有多层嵌套,缩进很深,看不清逻辑
  • 一个函数做了很多不相关的事情

3. 复制粘贴

  • 同样的代码,复制粘贴很多次
  • 改一个bug,要改很多地方,容易漏改
  • 代码冗余,维护成本高

4. 硬编码

  • 魔法数字、魔法字符串,散落在代码各处
  • 配置写死在代码里,改配置要改代码
  • 不支持多环境、多语言

5. 注释缺失或注释错误

  • 没有注释,复杂的逻辑看不懂
  • 注释和代码不一致,代码改了注释没改,误导人
  • 注释只说"做了什么",不说"为什么这么做"

6. 过度设计

  • 为了"可能的未来需求"而过度设计
  • 简单的问题用复杂的方案解决
  • 设计模式滥用,为了用模式而用模式

7. 缺乏测试

  • 没有单元测试,改代码怕改坏
  • 测试覆盖率低,很多代码没有测试
  • 测试代码和生产代码不同步

8. 性能差

  • 循环里查数据库,N+1查询
  • 大对象加载到内存,内存溢出
  • 没有缓存,重复计算
  • 同步调用,阻塞等待

如果你发现自己的代码有以上特征,不要灰心,坏代码是可以变好的——通过重构,逐步改善。

四、如何写出好代码

明白了好代码的标准,再说说如何写出好代码。

1. 命名是头等大事

命名是写代码的第一件事,也是最重要的事。好的命名,能让代码自解释,不需要注释就能看懂。

命名的原则:

  • 见名知意:名字要能表达含义,让人一看就知道是什么
  • 准确:名字要准确,不要误导人
  • 一致:同一个概念,用同一个名字,不要一会儿叫user、一会儿叫account、一会儿叫member
  • 可读:名字要容易读,不要用生僻词、缩写、拼音
  • 长度适中:名字不要太长(难写),也不要太短(看不懂),在表达清楚的前提下尽量短

例子:

// 坏命名
$d = 30; // 什么d?30是什么?
function process($data) { ... } // process什么?data是什么?

// 好命名
$daysUntilExpiration = 30; // 过期天数
function calculateOrderTotal($orderItems) { ... } // 计算订单总价

2. 函数要小、要单一

函数是代码的基本组织单元。好的函数,要小、要单一职责。

函数的原则:

  • 短小:一个函数最好不要超过20-30行。如果太长,就拆分
  • 单一职责:一个函数只做一件事。如果一个函数做了多件事,就拆分
  • 参数少:函数的参数最好不要超过3-4个。如果参数太多,考虑用对象或数组封装
  • 无副作用:函数最好不要有副作用(修改全局变量、修改输入参数等)。函数的输出只依赖输入
  • 返回值清晰:函数的返回值要清晰,不要返回null表示错误(用异常),不要返回多种类型

例子:

// 坏函数:太长、做了太多事
function processOrder($order) {
    // 验证订单
    // 计算价格
    // 扣库存
    // 创建支付记录
    // 发送通知
    // ... 几百行
}

// 好函数:拆分、单一职责
function processOrder($order) {
    validateOrder($order);
    $total = calculateOrderTotal($order);
    deductInventory($order);
    createPaymentRecord($order, $total);
    sendOrderNotification($order);
}

3. 注释要适当

注释是代码的补充。好的注释,能解释"为什么这么做",而不是"做了什么"。

注释的原则:

  • 解释为什么,而不是做什么:代码本身就能说明"做了什么",注释应该解释"为什么这么做"(业务背景、设计决策、踩过的坑)
  • 不要多余的注释:不要给一目了然的代码加注释(如$i = 0; // i初始化为0),这样的注释是噪音
  • 保持注释和代码同步:代码改了,注释也要改。注释和代码不一致,比没有注释更糟糕
  • 用TODO/FIXME标记待办:有未完成的工作,用TODO/FIXME标记,方便后续查找
  • 公共API要有文档注释:公共的函数、类、接口,要有文档注释(PHPDoc、JSDoc等),说明用途、参数、返回值、异常

例子:

// 坏注释:只说做了什么
$total = $price * $quantity; // 计算总价

// 好注释:解释为什么
// 用整数分计算,避免浮点数精度问题
$totalInCents = $priceInCents * $quantity;

4. 模块划分要合理

模块是代码的高级组织单元。好的模块划分,能让代码结构清晰、低耦合高内聚。

模块划分的原则:

  • 按职责划分:一个模块负责一个职责(如用户模块、订单模块、支付模块)
  • 高内聚:一个模块内部的功能要紧密相关
  • 低耦合:模块之间的依赖要少,通过接口通信
  • 层次清晰:要有清晰的层次(如控制器层、服务层、数据访问层),不要跨层调用
  • 依赖方向清晰:依赖方向要一致(如控制器依赖服务,服务依赖数据访问),不要循环依赖

5. 写测试

测试是好代码的保障。有测试的代码,改起来放心——改完跑一遍测试,就知道有没有改坏。

测试的原则:

  • 单元测试:核心逻辑要有单元测试,覆盖正常场景、异常场景、边界场景
  • 测试覆盖率:核心代码的测试覆盖率要高(建议80%以上)
  • 测试要独立:每个测试用例要独立,不依赖其他测试的结果
  • 测试要快:单元测试要跑得快,不要依赖外部服务(数据库、网络等),用mock替代
  • 测试代码也是代码:测试代码也要规范、整洁、可维护

6. 持续重构

好代码不是一次写出来的,而是持续重构出来的。

重构的原则:

  • 小步重构:每次重构一小部分,不要一次大改
  • 重构前后跑测试:重构前跑一遍测试(确保测试通过),重构后再跑一遍(确保没改坏)
  • 消除坏味道:看到代码的坏味道(重复、过长函数、过大类、过长参数列表等),及时重构
  • 童子军规则:离开营地时,让营地比你来的时候更干净。每次改代码,顺便把周围的代码改善一点

五、好代码和"牛逼代码"的区别

最后,我想说说好代码和"牛逼代码"的区别。

刚入行的时候,我觉得"牛逼代码"就是好代码——用了很多技巧、很短、很高深、别人看不懂。

但是现在我明白了,好代码和"牛逼代码"是两回事。

牛逼代码

  • 追求"炫技"——用各种技巧、黑魔法,显得水平高
  • 追求"短"——一行代码解决复杂问题,不管可读性
  • 追求"高深"——用复杂的方案解决简单的问题,显得架构师水平
  • 结果:别人看不懂,维护困难,自己过几个月也看不懂

好代码

  • 追求"清晰"——代码一看就懂,不需要琢磨
  • 追求"简单"——用最简单的方案解决问题
  • 追求"可维护"——改起来放心,加功能顺手
  • 结果:别人能看懂,维护成本低,生命周期长

牛逼代码是写给自己看的,是为了炫耀自己的水平;好代码是写给别人看的,是为了让别人能看懂、能维护。

作为一个职业程序员,我们应该追求好代码,而不是牛逼代码。因为代码是团队协作的产物,不是个人炫技的舞台。

当然,如果你是在写开源项目、写技术博客、参加编程比赛,"牛逼代码"可能有它的价值。但是在工作项目中,好代码才是我们应该追求的。

六、写在最后

代码写了五年,我才明白什么叫"好代码"。

从"能跑的代码就是好代码",到"代码越短越牛逼",到"设计模式用得多就是好",再到"好代码的核心是让人能看懂、能维护"——我对好代码的理解,经历了四个阶段,花了五年时间。

现在我认为,好代码的标准是:

  1. 可读性:让人能看懂
  2. 可维护性:让人能改、能扩展
  3. 简洁:简单、直接,不过度设计
  4. 性能:在需要的地方优化
  5. 健壮性:能处理异常情况

写出好代码的方法:

  1. 命名是头等大事
  2. 函数要小、要单一
  3. 注释要适当
  4. 模块划分要合理
  5. 写测试
  6. 持续重构

好代码不是一次写出来的,而是在持续的学习、实践、反思、重构中,逐步提升的。写了五年代码,我还在学习如何写出更好的代码。这条路,没有终点。

最后,用一句话总结:好代码,不是写给机器看的,而是写给人看的。让人能看懂、能维护的代码,才是好代码。

愿每一个程序员,都能写出让人能看懂、能维护的好代码。愿我们的代码,在几年后回头看,依然清晰、整洁、可维护,而不是"这是谁写的烂代码"——哦,原来是我自己写的。