【软工方法论23】代码坏味道识别与消除

【软工方法论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

“代码坏味道就像身体的不适——不一定会立即生病,但提醒你该注意健康了。”


思考题:你遇到过哪些代码坏味道?是怎么消除的?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

通俗易懂学IT

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值