【软工方法论23】293_代码坏味道识别与消除
代码坏味道:识别与消除
你有没有这种感觉?
看一段代码总觉得哪里不对劲,但说不上来是哪里。
这就是代码坏味道(Code Smell)。
代码坏味道不是bug,而是代码中的"不对劲",预示着可能需要重构。
今天聊聊常见的代码坏味道和如何消除它们。
一、什么是代码坏味道?
代码坏味道:代码中的一些模式,暗示着设计或实现可能存在问题。
关键词:
- 不是bug,不会导致程序出错
- 是"味道",暗示可能有问题
- 需要经验来判断
- 是重构的信号
生活比喻:
- 房间有异味,不一定是坏了,可能只是需要打扫
- 代码有坏味道,不一定有bug,可能只是需要重构
“闻到坏味道,就该考虑洗个澡了——哦不,是重构了。”
二、常见坏味道及消除方法
1. 重复代码(Duplicated Code)
味道:同样的代码出现多次
// 坏味道
class OrderService {
public void sendEmail(Order order) {
System.out.println("发送邮件到:" + order.getCustomer().getEmail());
System.out.println("订单号:" + order.getId());
System.out.println("金额:" + order.getAmount());
}
}
class RefundService {
public void sendEmail(Order order) {
System.out.println("发送邮件到:" + order.getCustomer().getEmail());
System.out.println("订单号:" + order.getId());
System.out.println("金额:" + order.getAmount());
}
}
// 消除:提取公共方法
class NotificationService {
public void notifyOrder(Order order) {
System.out.println("发送通知到:" + order.getCustomer().getEmail());
System.out.println("订单号:" + order.getId());
System.out.println("金额:" + order.getAmount());
}
}
2. 过长函数(Long Method)
味道:函数代码太长,超过一屏
// 坏味道:100多行的函数
public void processOrder(Order order) {
// 100行代码...
}
// 消除:拆分成多个小函数
public void processOrder(Order order) {
validateOrder(order);
calculateDiscount(order);
saveOrder(order);
notifyCustomer(order);
}
private void validateOrder(Order order) {
// 验证逻辑
}
private void calculateDiscount(Order order) {
// 折扣计算
}
3. 过大类(Large Class)
味道:一个类做了太多事
// 坏味道:God Class
class UserManager {
// 100个方法,5000行代码
// 用户管理、权限管理、统计分析、日志记录...
}
// 消除:拆分成多个类
class UserService {
// 用户核心业务
}
class PermissionService {
// 权限管理
}
class UserAnalytics {
// 用户统计
}
4. 过长参数列表(Long Parameter List)
味道:函数参数太多,超过3个
// 坏味道
public void createUser(String name, String email, String phone,
String address, String city, String country, int age,
String gender, String profession) {
// ...
}
// 消除:使用参数对象
public void createUser(UserDTO userDTO) {
// ...
}
class UserDTO {
private String name;
private String email;
private String phone;
// ...
}
5. 发散式变化(Divergent Change)
味道:一个类因为不同原因需要修改
// 坏味道:数据库变了要改它,界面变了也要改它,逻辑变了还要改它
class UserManager {
public void saveUser() { /* 数据库逻辑 */ }
public void displayUser() { /* 界面逻辑 */ }
public void validateUser() { /* 业务逻辑 */ }
}
// 消除:按职责分离
class UserRepository { /* 数据库 */ }
class UserUI { /* 界面 */ }
class UserValidator { /* 验证 */ }
6. 霰弹式修改(Shotgun Surgery)
味道:一个修改需要改多个类
// 坏味道:改用户名要改User、Order、Log、Notification...
class User {
private String name;
}
class Order {
private String userName; // 也要改
}
class Log {
private String userName; // 也要改
}
// 消除:使用ID而不是复制数据
class Order {
private Long userId; // 用ID关联
}
class Log {
private Long userId; // 用ID关联
}
7. 依恋情节(Feature Envy)
味道:一个类更关心另一个类的数据
// 坏味道:UserService比User类本身还了解User
class UserService {
public double getUserScore(User user) {
int orderCount = user.getOrders().size();
int refundCount = user.getRefunds().size();
double totalAmount = 0;
for (Order o : user.getOrders()) {
totalAmount += o.getAmount();
}
return totalAmount / orderCount - refundCount * 10;
}
}
// 消除:把方法移到User类
class User {
public double getScore() {
// 这里面的逻辑更适合放在User类
}
}
8. 数据泥团(Data Clumps)
味道:一些数据总是同时出现
// 坏味道
class Order {
private String street;
private String city;
private String country; // 地址数据总是出现
}
class User {
private String street;
private String city;
private String country; // 又出现了
}
// 消除:提取Address类
class Address {
private String street;
private String city;
private String country;
}
class Order {
private Address shippingAddress;
}
class User {
private Address address;
}
9. 基本类型偏执(Primitive Obsession)
味道:过度使用基本类型,不愿用小对象
// 坏味道
class User {
private String phone; // 用String存电话
private String email; // 用String存邮箱
private String address; // 用String存地址
}
// 消除:使用值对象
class Phone {
private String number;
public boolean isValid() { /* 验证逻辑 */ }
}
class Email {
private String address;
public boolean isValid() { /* 验证逻辑 */ }
}
class Address {
private String detail;
}
10. Switch语句(Switch Statements)
味道:大量switch-case或if-else
// 坏味道
public String getGrade(int score) {
switch (score / 10) {
case 10:
case 9: return "A";
case 8: return "B";
case 7: return "C";
case 6: return "D";
default: return "F";
}
}
// 消除:使用多态或Map
private static final Map<Integer, String> GRADES = Map.of(
10, "A", 9, "A", 8, "B", 7, "C", 6, "D"
);
public String getGrade(int score) {
return GRADES.getOrDefault(score / 10, "F");
}
11. 平行继承体系(Parallel Inheritance)
味道:每创建一个类,就要创建另一个相关的类
// 坏味道
class WindowsButton { }
class WindowsLabel { }
class MacButton { }
class MacLabel { }
// 每加一个平台,要加一整套组件
// 消除:使用组合
class Button {
private Platform platform;
}
class Label {
private Platform platform;
}
12. 冗余类(Lazy Class)
味道:存在的价值很小的类
// 坏味道
class OrderConstants {
public static final String STATUS_NEW = "NEW";
}
// 只有几个常量,为什么要单独一个类?
// 消除:合并到使用它的类中
class Order {
public static final String STATUS_NEW = "NEW";
}
13. 夸夸其谈的未来(Speculative Generality)
味道:为了"将来可能用到"而过度设计
// 坏味道
interface AbstractItem {}
interface AbstractOrder {} // "以后可能用到"
class ConcreteItem implements AbstractItem {}
// 消除:现在需要什么就做什么
class Item {}
14. 临时字段(Temporary Field)
味道:字段只在某些情况下使用
// 坏味道
class Order {
private String normalField;
private String tempField; // 只在某个方法里使用
public void normalMethod() {
// 不用tempField
}
public void specialMethod() {
// tempField = xxx;
}
}
// 消除:把临时字段变成方法参数
public void specialMethod(String tempValue) {
// 不用tempField了
}
15. 链式调用(Message Chains)
味道:长长的链式调用
// 坏味道
String city = order.getCustomer()
.getAddress()
.getCity()
.getName();
// 消除:使用委托方法
class Order {
public String getCustomerCity() {
return customer.getAddress().getCity().getName();
}
}
16. 中间人(Middle Man)
味道:类大部分时间都在转发调用
// 坏味道
class UserManager {
public Address getUserAddress(Long userId) {
return userRepository.findById(userId).getAddress();
}
public Phone getUserPhone(Long userId) {
return userRepository.findById(userId).getPhone();
}
}
// 消除:直接调用
User user = userRepository.findById(userId);
Address address = user.getAddress();
三、坏味道速查表
| 坏味道 | 典型特征 | 消除方法 |
|---|---|---|
| 重复代码 | 复制粘贴的代码 | 提取方法/类 |
| 过长函数 | 超过一屏 | 拆分函数 |
| 过大类 | 太多方法/属性 | 拆分类 |
| 过长参数 | 参数超过3个 | 参数对象 |
| Switch | 大量if-else | 多态/Map |
| 数据泥团 | 总是一起出现的数据 | 提取类 |
| 基本类型偏执 | 过度使用String/int | 值对象 |
总结
代码坏味道是重构的信号:
识别坏味道:
- 重复代码
- 过长函数
- 过大类
- 过多参数
- 深层嵌套
消除方法:
- 提取方法/类
- 拆分大类
- 使用设计模式
- 参数对象化
- 使用Map代替switch
“代码坏味道就像身体的不适——不一定会立即生病,但提醒你该注意健康了。”
思考题:你遇到过哪些代码坏味道?是怎么消除的?

964

被折叠的 条评论
为什么被折叠?



