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. 持续重构
好代码不是一次写出来的,而是持续重构出来的。
重构的原则:
- 小步重构:每次重构一小部分,不要一次大改
- 重构前后跑测试:重构前跑一遍测试(确保测试通过),重构后再跑一遍(确保没改坏)
- 消除坏味道:看到代码的坏味道(重复、过长函数、过大类、过长参数列表等),及时重构
- 童子军规则:离开营地时,让营地比你来的时候更干净。每次改代码,顺便把周围的代码改善一点
五、好代码和"牛逼代码"的区别
最后,我想说说好代码和"牛逼代码"的区别。
刚入行的时候,我觉得"牛逼代码"就是好代码——用了很多技巧、很短、很高深、别人看不懂。
但是现在我明白了,好代码和"牛逼代码"是两回事。
牛逼代码:
- 追求"炫技"——用各种技巧、黑魔法,显得水平高
- 追求"短"——一行代码解决复杂问题,不管可读性
- 追求"高深"——用复杂的方案解决简单的问题,显得架构师水平
- 结果:别人看不懂,维护困难,自己过几个月也看不懂
好代码:
- 追求"清晰"——代码一看就懂,不需要琢磨
- 追求"简单"——用最简单的方案解决问题
- 追求"可维护"——改起来放心,加功能顺手
- 结果:别人能看懂,维护成本低,生命周期长
牛逼代码是写给自己看的,是为了炫耀自己的水平;好代码是写给别人看的,是为了让别人能看懂、能维护。
作为一个职业程序员,我们应该追求好代码,而不是牛逼代码。因为代码是团队协作的产物,不是个人炫技的舞台。
当然,如果你是在写开源项目、写技术博客、参加编程比赛,"牛逼代码"可能有它的价值。但是在工作项目中,好代码才是我们应该追求的。
六、写在最后
代码写了五年,我才明白什么叫"好代码"。
从"能跑的代码就是好代码",到"代码越短越牛逼",到"设计模式用得多就是好",再到"好代码的核心是让人能看懂、能维护"——我对好代码的理解,经历了四个阶段,花了五年时间。
现在我认为,好代码的标准是:
- 可读性:让人能看懂
- 可维护性:让人能改、能扩展
- 简洁:简单、直接,不过度设计
- 性能:在需要的地方优化
- 健壮性:能处理异常情况
写出好代码的方法:
- 命名是头等大事
- 函数要小、要单一
- 注释要适当
- 模块划分要合理
- 写测试
- 持续重构
好代码不是一次写出来的,而是在持续的学习、实践、反思、重构中,逐步提升的。写了五年代码,我还在学习如何写出更好的代码。这条路,没有终点。
最后,用一句话总结:好代码,不是写给机器看的,而是写给人看的。让人能看懂、能维护的代码,才是好代码。
愿每一个程序员,都能写出让人能看懂、能维护的好代码。愿我们的代码,在几年后回头看,依然清晰、整洁、可维护,而不是"这是谁写的烂代码"——哦,原来是我自己写的。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录