
那天凌晨三点,我正准备合上笔记本睡觉,突然钉钉响了——线上又出问题了。排查了半天,发现是一个看起来"没毛病"的方法,在高并发下把CPU跑满了。问题出在哪?代码有"坏味道"。
什么是代码的坏味道?就是那些看起来能跑,但让人闻到就想捂鼻子的代码。今天分享20个我踩过坑后总结的重构技巧,希望能让你少熬几个通宵。
方法太长,一眼望不到头
我见过最恐怖的方法有300多行,里面嵌套了7层if-else。每次修改都像拆炸弹,生怕动错一个地方整个功能就崩了。
// 坏味道
public void processOrder(Order order) {
// 100多行的验证逻辑
// 50多行的计算逻辑
// 80多行的保存逻辑
// ...你懂的
}
// 重构后
public void processOrder(Order order) {
validateOrder(order);
calculatePrice(order);
saveOrder(order);
}
参数列表像火车一样长
见过那种方法签名占了两行的吗?我敢打赌,调用的时候你肯定要数好几遍参数位置。
// 坏味道
public void createUser(String name, String email, String phone,
int age, String address, String city,
String country, boolean isVip) {
// ...
}
// 重构后
public void createUser(UserInfo userInfo) {
// 用对象封装参数,清爽多了
}
魔法数字满天飞
代码里突然冒出个42,你知道是什么意思吗?我不知道,写代码的人过了三个月也不知道。
// 坏味道
if (user.getAge() > ) {
// 为什么是65?退休年龄?还是别的什么?
}
// 重构后
private static final int RETIREMENT_AGE = ;
if (user.getAge() > RETIREMENT_AGE) {
// 一眼就懂
}
用卫语句减少嵌套
嵌套太深的代码读起来像走迷宫。卫语句能让逻辑更清晰:
// 坏味道
public String processUser(User user) {
if (user != null) {
if (user.isActive()) {
if (user.hasPermission()) {
return "处理成功";
} else {
return "权限不足";
}
} else {
return "用户未激活";
}
} else {
return "用户不存在";
}
}
// 重构后
public String processUser(User user) {
if (user == null) return "用户不存在";
if (!user.isActive()) return "用户未激活";
if (!user.hasPermission()) return "权限不足";
return "处理成功";
}
用Stream API替换复杂循环
以前写个过滤加转换的逻辑,要写十几行。现在一行搞定:
// 坏味道
List<String> activeUserNames = new ArrayList<>();
for (User user : users) {
if (user.isActive() && user.getAge() > ) {
activeUserNames.add(user.getName().toUpperCase());
}
}
// 重构后
List<String> activeUserNames = users.stream()
.filter(User::isActive)
.filter(user -> user.getAge() > )
.map(user -> user.getName().toUpperCase())
.collect(Collectors.toList());
用Optional避免空指针
空指针异常,Java程序员永远的痛。Optional虽然不是万能药,但用对了确实香:
// 坏味道
public String getUserCity(Long userId) {
User user = userService.findById(userId);
if (user != null) {
Address address = user.getAddress();
if (address != null) {
return address.getCity();
}
}
return "未知";
}
// 重构后
public String getUserCity(Long userId) {
return userService.findById(userId)
.map(User::getAddress)
.map(Address::getCity)
.orElse("未知");
}
枚举替代魔法字符串
状态码用字符串?维护起来是个噩梦。枚举它不香吗?
// 坏味道
public void updateOrderStatus(String status) {
if ("PENDING".equals(status)) {
// ...
} else if ("CONFIRMED".equals(status)) {
// ...
}
// 万一写错了呢?
}
// 重构后
public enum OrderStatus {
PENDING("待处理"),
CONFIRMED("已确认"),
SHIPPED("已发货");
private final String description;
// 构造器和getter省略
}
过度设计的陷阱
刚学设计模式那会儿,我恨不得每个类都套个模式。结果写个简单功能搞出了工厂的工厂,同事都快被绕晕了。
记住:简单问题简单解决,复杂问题才需要复杂方案。不要为了炫技而过度设计。
缓存滥用
"性能不够,缓存来凑",这话没错。但我见过把整个数据库都缓存起来的操作,内存直接爆了。
缓存是双刃剑,用之前先想想:这个数据真的需要缓存吗?缓存失效怎么处理?数据一致性怎么保证?
异常处理的误区
// 坏味道
try {
// 业务逻辑
} catch (Exception e) {
// 吞掉异常,什么都不做
}
// 稍微好点
try {
// 业务逻辑
} catch (Exception e) {
log.error("出错了", e);
throw new BusinessException("系统异常");
}
吞异常是大忌,但记录日志后重新抛出业务异常,这样既不会丢失错误信息,又能给用户友好的提示。
代码重构不是一蹴而就的事情,而是一个持续改进的过程。每次code review的时候多问问自己:这段代码半年后的我还能看懂吗?新来的同事能快速理解吗?
你有没有遇到过让你印象深刻的"坏味道"代码?在评论区分享一下,让大家都警醒警醒。