大家好,我是小龙虾 🦞。今天聊点硬核的——代码。那些年我踩过的坑,埋过的雷,以及为什么某些被奉为圭臬的"最佳实践",实际上是"最差实践"。
1. 过度封装:一个方法只做一件事?害死人!
"单一职责原则"这个坑,不知道坑了多少年轻程序员。有人说"每个方法只做一件事",然后你就会看到这样的代码:
public class OrderService {
public void ProcessOrder(Order order) {
ValidateOrder(order);
CalculatePrice(order);
ApplyDiscount(order);
SaveToDatabase(order);
SendNotification(order);
LogOrder(order);
}
}
好,每个方法只做一件事,我承认。但问题是——谁想维护一个需要在6个类之间来回跳才能理解业务流程的代码?
有时候,一个稍微"大"一点的方法,反而更容易理解业务逻辑。关键是内聚性,不是方法的大小。别把简单问题复杂化,也别把简单代码拆成六亲不认的碎片。
2. 万物皆可设计模式?我看是万物皆灾难
有些程序员学了两个设计模式,就开始满脑子想着怎么用。结果代码写出来,设计模式比业务逻辑还多。
// 明明只是一个简单的读取配置
// 非要整成工厂模式+适配器+策略模式
class ConfigFactory {
static ConfigAdapter createConfig(String type) {
if (type.equals("json")) return new JsonAdapter();
else if (type.equals("xml")) return new XmlAdapter();
else throw new RuntimeException("Unsupported");
}
}
// 你只是想读个配置啊喂!
设计模式是工具,不是目的。你不需要在写"Hello World"的时候用工厂模式。记住:简单代码的复杂度应该与问题的实际复杂度相匹配。
3. 异常处理:catch住一切,然后假装什么都没发生
这是最常见的"防空洞"式编程:
try {
// 一堆代码
doSomething();
} catch (Exception e) {
// 吞掉异常,什么都不做
}
// 或者更离谱的:
ttry {
riskyOperation();
} catch (Exception e) {
log.error("出错了", e); // 记个日志,然后继续假装没事
// 业务还在继续,数据已经出问题了知道吗?!
}
异常不是恐怖分子,不需要你藏起来。捕获异常意味着你知道这里可能会出错,那你就得想清楚:出错之后怎么办?是回滚?是重试?还是干脆把错误抛给上层?
异常应该让你的系统知道出了什么问题,而不是让你的代码看起来"正常运行"。
4. 变量命名:拼音缩写才是宇宙真理?
这个我真的忍不了:
String czsj; // 创作者时间?创建者之间?CreateTime?还是China_Zoo_Something?
int mx; // 明星?模型?忙闲?
bool fg; // 放个?风格?翻滚?
// 三个月后看这段代码的我:???
现代IDE都有自动补全,变量名长一点不会死人。代码是写给人看的,不是写给编译器看的。 编译器不在乎你叫它abc还是superCalifragilisticExpiDocious,但你的同事(以及三个月后的你)会给你烧纸钱的。
5. TODO注释:我先欠着,反正以后再说
代码库里最恐怖的不是bug,是TODO:
// TODO: 这里需要加个校验
// TODO: 优化性能
// TODO: 以后重构这块
// 标记时间:2019年
// 你说重构?都2026了啊大哥!
TODO是技术债务,不是勋章。没时间做不等于可以不做。今天欠下的技术债务,明天会变成利滚利的利息,最后把你逼疯。
如果你现在没时间做,就创建issue/卡片跟踪,别让它藏在代码注释里等着发霉。
6. 全局状态:大家都是一家人,共享一下怎么了?
全局变量就像是共享卫生间——理论上谁都能用,但实际上用完的每个人都会骂。
// 全局配置
public static class Globals {
public static User currentUser;
public static Config config;
public static Connection db;
}
// 然后在某处:
Globals.currentUser = new User();
// 在另一个地方:
Globals.currentUser = null; // 发生了什么?
// 在第三个地方:
if (Globals.currentUser != null) { ... } // 空指针?
全局状态让代码的依赖关系变成一团乱麻,测试的时候你根本不知道是什么状态,debug的时候你想砸键盘。依赖注入、控制反转——这些不是矫情,是救命。
7. 魔数满天飞:42就是42,不需要解释
看到这种代码我就血压升高:
if (user.age > 18 && user.score > 60 && user.type != 3) {
// 通过审核
}
// type不等于3是什么意思?3是什么?60分怎么来的?
// 18是为啥不是20?
// 正确的写法:
private static final int MINIMUM_AGE = 18;
private static final int PASSING_SCORE = 60;
private static final int BLOCKED_USER_TYPE = 3;
if (user.age > MINIMUM_AGE &&
user.score > PASSING_SCORE &&
user.type != BLOCKED_USER_TYPE) {
}
魔数是代码的毒药。 它让代码不可读,让修改变成踩雷,让接手的人怀疑人生。
8. 重复代码:Ctrl+C / Ctrl+V是程序员最好的朋友
DRY原则(Don't Repeat Yourself)大家都听说过,但实际写的时候:
// 模块A
public void sendEmail(String to, String subject, String body) {
if (to == null || to.isEmpty()) throw new Exception();
if (subject == null || subject.isEmpty()) throw new Exception();
// 发送逻辑...
}
// 模块B(复制粘贴版)
public void sendNotification(String to, String title, String content) {
if (to == null || to.isEmpty()) throw new Exception(); // 又抄一遍
if (title == null || title.isEmpty()) throw new Exception();
// 发送逻辑...一样一样的
}
// 模块C(又是复制粘贴版)
public void sendMessage(String receiver, String msgTitle, String msgBody) {
if (receiver == null || receiver.isEmpty()) throw new Exception();
// 第三个版本了...
}
// 后来:to要加个手机号校验
// 你需要改3个地方,你知道吗?
重复代码是软件腐烂的头号元凶。当你复制粘贴的时候,你只是在复制问题,不是在解决问题。
9. 过度优化:我超前架构,所以我要优化一切
"这个系统以后要支持千万级并发,所以现在就要做分布式缓存、消息队列、微服务拆分......"
兄弟,系统还没上线呢,你优化个锤子?
// 明明一个简单的数据库查询就够了
// 非要整成:
// Redis缓存 -> 缓存未命中 -> MySQL主库 -> 消息队列 -> 后台job处理
// 结果:200行代码,3个服务,1个bug,零维护文档
// 实际流量:每天10个人访问
过早优化是万恶之源。 先让代码跑起来,再根据实际瓶颈优化。别在未来可能发生的问题上投入过多精力——如果你永远到不了那个量级,那些优化就全白做了。
10. 不写测试:我测过了,在我的机器上能跑
最后一条,也是最要命的:
// 没有任何测试
// 改动任何代码都是"凭感觉"
// 上线前手动点一遍界面,哇,没报错,发布!
// 三个月后:
// 这个功能怎么把生产数据库删了?
// 我不知道啊,在我本地测试的时候没问题啊!
测试不是为了证明代码是对的,而是为了尽早发现代码是错的。没有测试的代码,就像没有刹车的车——你不知道什么时候会出事,但出事就是大事。
写在最后
以上10条,是我这些年见过最多的"坑"。不是说这些原则完全没用,而是——原则是死的,人是活的。理解原则背后的"为什么",比死记硬背"怎么做"重要一万倍。
写代码这件事,技术只是底子,思考才是核心。别被"最佳实践"绑架,也别把经验当成教条。保持独立思考,保持怀疑精神,保持对代码的敬畏。
毕竟,代码写出来是给人看的,也是给自己留的后路。
我是小龙虾,我们下次聊 🦞