重复的switch(Repeated Switches):坏味道识别与重构实战指南

24种代码坏味道系列 · 第12篇


1. 开篇场景

你是否遇到过这样的代码:相同的 switch 语句在多个地方重复出现,每次添加新的分支时,都需要在所有地方同步修改?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
double calculateDiscount(UserType type, double price) {
switch (type) {
case UserType::REGULAR: return price * 0.0;
case UserType::VIP: return price * 0.1;
case UserType::PREMIUM: return price * 0.2;
}
}

int getMaxItems(UserType type) {
// 相同的 switch 逻辑重复了
switch (type) {
case UserType::REGULAR: return 10;
case UserType::VIP: return 50;
case UserType::PREMIUM: return 100;
}
}

这就是重复的switch的典型症状。相同的 switch 语句在多个地方重复出现,就像同一首歌的多个翻唱版本,当需要修改歌词时,必须同步更新所有版本。

当你需要添加新的用户类型时(如添加 PREMIUM_PLUS),你必须在所有使用 switch 的地方添加新的分支。这不仅浪费时间,更重要的是容易出错——漏掉一个地方,就会导致系统行为不一致。


2. 坏味道定义

重复的switch是指相同的 switch 语句在多个地方重复出现,应该使用多态替代。

就像同一份菜单的多个副本,当需要更新菜单时,必须同步更新所有副本,否则就会出现不一致。

核心问题switch 语句通常表示类型相关的行为,应该使用多态来实现。多态可以让每个类型自己处理自己的行为,避免重复的 switch 语句。


3. 识别特征

🔍 代码表现:

  • 特征1:相同的 switch 语句在多个函数中重复出现
  • 特征2switch 语句根据类型执行不同的逻辑
  • 特征3:添加新的类型时,需要在多个 switch 中添加新分支
  • 特征4switch 语句的 case 数量较多(超过3个)
  • 特征5switch 语句处理的是对象的行为,而不是简单的值映射

🎯 出现场景:

  • 场景1:快速开发时,使用 switch 处理类型相关的逻辑
  • 场景2:从过程式编程迁移到面向对象时,没有重构 switch
  • 场景3:缺乏设计,没有考虑使用多态
  • 场景4:重构不彻底,只修改了部分 switch 语句

💡 快速自检:

  • 问自己:这个 switch 语句是否在其他地方也出现过?
  • 问自己:这个 switch 语句是否可以根据类型使用多态替代?
  • 工具提示:使用代码分析工具检测重复的 switch 语句

4. 危害分析

🚨 维护成本:添加新类型需要在多个 switch 中修改,时间成本增加2-3倍

⚠️ 缺陷风险:容易遗漏某些地方的修改,导致bug,风险增加70%

🧱 扩展障碍:添加新功能时不知道应该在哪里修改,容易产生新的重复

🤯 认知负担:需要记住所有使用 switch 的地方,增加了心理负担


5. 重构实战

步骤1:安全准备

  • ✅ 确保有完整的单元测试覆盖
  • ✅ 创建重构分支:git checkout -b refactor/replace-switch-with-polymorphism
  • ✅ 使用版本控制,便于回滚

步骤2:逐步重构

重构前(问题代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
enum class UserType {
REGULAR,
VIP,
PREMIUM
};

// 坏味道:相同的 switch 逻辑在多个地方重复
class BadExample {
public:
double calculateDiscount(UserType type, double price) {
switch (type) {
case UserType::REGULAR:
return price * 0.0;
case UserType::VIP:
return price * 0.1;
case UserType::PREMIUM:
return price * 0.2;
default:
return 0.0;
}
}

int getMaxItems(UserType type) {
// 相同的 switch 逻辑重复了
switch (type) {
case UserType::REGULAR:
return 10;
case UserType::VIP:
return 50;
case UserType::PREMIUM:
return 100;
default:
return 0;
}
}

std::string getWelcomeMessage(UserType type) {
// 相同的 switch 逻辑又重复了
switch (type) {
case UserType::REGULAR:
return "Welcome!";
case UserType::VIP:
return "Welcome, VIP member!";
case UserType::PREMIUM:
return "Welcome, Premium member!";
default:
return "Welcome!";
}
}
};

问题分析

  • 相同的 switch 逻辑在3个不同的方法中重复
  • 如果添加新的用户类型,需要在3个地方添加新分支
  • 如果修改某个用户类型的行为,需要在3个地方修改

重构后(清洁版本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// ✅ 使用多态替代 switch
class User {
protected:
double discountRate;
int maxItems;
std::string welcomeMessage;

public:
User(double discRate, int max, const std::string& msg)
: discountRate(discRate), maxItems(max), welcomeMessage(msg) {}

virtual double calculateDiscount(double price) const {
return price * discountRate;
}

virtual int getMaxItems() const {
return maxItems;
}

virtual std::string getWelcomeMessage() const {
return welcomeMessage;
}

virtual ~User() = default;
};

class RegularUser : public User {
public:
RegularUser() : User(0.0, 10, "Welcome!") {}
};

class VIPUser : public User {
public:
VIPUser() : User(0.1, 50, "Welcome, VIP member!") {}
};

class PremiumUser : public User {
public:
PremiumUser() : User(0.2, 100, "Welcome, Premium member!") {}
};

class GoodExample {
public:
// ✅ 使用多态,不需要 switch
double calculateDiscount(const User& user, double price) {
return user.calculateDiscount(price);
}

int getMaxItems(const User& user) {
return user.getMaxItems();
}

std::string getWelcomeMessage(const User& user) {
return user.getWelcomeMessage();
}
};

关键变化点

  1. 用多态替代条件表达式(Replace Conditional with Polymorphism)

    • switch 语句替换为多态
    • 每个用户类型都有自己的实现
  2. 提高可扩展性

    • 添加新的用户类型只需创建新类
    • 不需要修改现有代码
  3. 提高可维护性

    • 每个用户类型的行为集中在一个类中
    • 修改某个用户类型的行为只需修改对应类

步骤3:重构技巧总结

使用的重构手法

  • 用多态替代条件表达式(Replace Conditional with Polymorphism):将 switch 语句替换为多态
  • 提取类(Extract Class):将类型相关的行为提取到类中

注意事项

  • ⚠️ 确保多态设计合理,不要过度设计
  • ⚠️ 如果 switch 语句很简单,可能不需要多态
  • ⚠️ 重构后要更新所有使用处,确保行为一致

6. 预防策略

🛡️ 编码时:

  • 即时检查

    • switch 语句是否在其他地方也出现过?
    • switch 语句是否可以根据类型使用多态替代?
    • 使用IDE的代码分析工具,检测重复的 switch 语句
  • 小步提交

    • 发现重复的 switch 语句时,立即使用多态替代
    • 使用”用多态替代条件表达式”重构,保持代码简洁

🔍 Code Review清单:

  • 重点检查

    • 是否有重复的 switch 语句?
    • switch 语句是否可以根据类型使用多态替代?
    • 添加新类型时是否需要在多个地方修改?
  • 拒绝标准

    • 相同的 switch 语句在多个地方重复
    • switch 语句处理的是对象的行为
    • 添加新类型需要在多个 switch 中添加新分支

⚙️ 自动化防护:

  • IDE配置

    • 使用代码分析工具检测重复的 switch 语句
    • 启用 switch 语句复杂度警告
  • CI/CD集成

    • 在CI流水线中集成代码分析工具
    • 检测重复的 switch 语句,生成警告报告

下一篇预告:循环语句(Loops)- 如何用STL算法替代命令式循环