基本类型偏执(Primitive Obsession):坏味道识别与重构实战指南

基本类型偏执(Primitive Obsession):坏味道识别与重构实战指南

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


1. 开篇场景

你是否遇到过这样的代码:使用 std::string 表示邮箱地址,但没有验证;使用 std::string 表示电话号码,但没有验证;使用 int 表示年龄,但没有范围检查?

cpp 复制代码
void sendEmail(std::string email, std::string subject, std::string body) {
    // 使用 string 表示邮箱,但没有验证
    std::cout << "Sending email to: " << email << std::endl;
}

void setAge(int age) {
    // age 可能是负数或很大的数,但没有范围检查
    std::cout << "Age set to: " << age << std::endl;
}

这就是基本类型偏执 的典型症状。过度使用基本类型(如 int、string、double)表示有意义的领域概念,就像用数字"1"和"0"表示"是"和"否",虽然可以工作,但语义不清晰,容易出错。

当你需要验证邮箱格式时,你必须在每个使用邮箱的地方添加验证逻辑。当你需要修改年龄范围时,你必须在所有使用年龄的地方修改。这种设计使得代码难以维护,容易产生bug。


2. 坏味道定义

基本类型偏执是指过度使用基本类型表示有意义的领域概念,应该使用更有意义的类型。

就像用数字编码表示所有信息,虽然可以工作,但语义不清晰,容易出错。

核心问题:基本类型没有语义,无法表达领域概念。使用有意义的类型可以提高代码的可读性和可维护性,同时可以在类型层面保证数据的有效性。


3. 识别特征

🔍 代码表现:

  • 特征1 :使用基本类型表示有意义的领域概念(如用 string 表示邮箱)
  • 特征2:基本类型需要验证,但验证逻辑分散在多个地方
  • 特征3:基本类型有特殊含义,但没有类型层面的保护
  • 特征4:多个地方使用相同的基本类型,但含义不同
  • 特征5:基本类型需要转换或格式化,但逻辑分散

🎯 出现场景:

  • 场景1:快速开发时,使用基本类型简化代码
  • 场景2:从其他语言迁移时,保留了基本类型的习惯
  • 场景3:缺乏领域建模,没有考虑类型设计
  • 场景4:重构不彻底,只修改了部分代码

💡 快速自检:

  • 问自己:这个基本类型是否有特殊含义?
  • 问自己:这个基本类型是否需要验证?
  • 工具提示:使用代码分析工具检测基本类型的使用模式

4. 危害分析

🚨 维护成本:验证逻辑分散在多个地方,时间成本增加50%

⚠️ 缺陷风险:基本类型没有验证,容易产生无效数据,bug风险增加60%

🧱 扩展障碍:添加新验证规则需要在多个地方修改

🤯 认知负担:需要理解基本类型的特殊含义,增加了心理负担


5. 重构实战

步骤1:安全准备

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

步骤2:逐步重构

重构前(问题代码)
cpp 复制代码
// 坏味道:使用基本类型表示有意义的领域概念
class BadExample {
public:
    // 使用 string 表示邮箱,但没有验证
    void sendEmail(std::string email, std::string subject, std::string body) {
        std::cout << "Sending email to: " << email << std::endl;
    }
    
    // 使用 string 表示电话号码,但没有验证
    void callPhone(std::string phoneNumber) {
        std::cout << "Calling: " << phoneNumber << std::endl;
    }
    
    // 使用 string 表示货币,容易出错
    void processPayment(std::string amount, std::string currency) {
        std::cout << "Processing payment: " << amount << " " << currency << std::endl;
    }
    
    // 使用 int 表示年龄,但没有范围检查
    void setAge(int age) {
        // age 可能是负数或很大的数
        std::cout << "Age set to: " << age << std::endl;
    }
};

问题分析:

  • std::string 用于表示邮箱、电话号码、货币,但没有验证
  • int 用于表示年龄,但没有范围检查
  • 验证逻辑分散在多个地方,难以维护
重构后(清洁版本)
cpp 复制代码
// ✅ 创建有意义的类型
class Email {
private:
    std::string value;
    
    bool isValid(const std::string& email) {
        return email.find('@') != std::string::npos;
    }
    
public:
    Email(const std::string& email) {
        if (!isValid(email)) {
            throw std::invalid_argument("Invalid email address");
        }
        value = email;
    }
    
    std::string getValue() const { return value; }
};

class PhoneNumber {
private:
    std::string value;
    
    bool isValid(const std::string& phone) {
        for (char c : phone) {
            if (!std::isdigit(c) && c != '-') {
                return false;
            }
        }
        return phone.length() >= 10;
    }
    
public:
    PhoneNumber(const std::string& phone) {
        if (!isValid(phone)) {
            throw std::invalid_argument("Invalid phone number");
        }
        value = phone;
    }
    
    std::string getValue() const { return value; }
};

class Money {
private:
    double amount;
    std::string currency;
    
public:
    Money(double amt, const std::string& curr) : amount(amt), currency(curr) {
        if (amount < 0) {
            throw std::invalid_argument("Amount cannot be negative");
        }
    }
    
    double getAmount() const { return amount; }
    std::string getCurrency() const { return currency; }
    
    std::string toString() const {
        return std::to_string(amount) + " " + currency;
    }
};

class Age {
private:
    int value;
    
public:
    Age(int age) {
        if (age < 0 || age > 150) {
            throw std::invalid_argument("Invalid age");
        }
        value = age;
    }
    
    int getValue() const { return value; }
};

class GoodExample {
public:
    // ✅ 使用有意义的类型
    void sendEmail(const Email& email, const std::string& subject, const std::string& body) {
        std::cout << "Sending email to: " << email.getValue() << std::endl;
    }
    
    void callPhone(const PhoneNumber& phone) {
        std::cout << "Calling: " << phone.getValue() << std::endl;
    }
    
    void processPayment(const Money& money) {
        std::cout << "Processing payment: " << money.toString() << std::endl;
    }
    
    void setAge(const Age& age) {
        std::cout << "Age set to: " << age.getValue() << std::endl;
    }
};

关键变化点:

  1. 用对象替换数据值(Replace Data Value with Object):

    • 将基本类型替换为有意义的类型
    • 每个类型都有自己的验证逻辑
  2. 类型安全:

    • 类型层面保证数据的有效性
    • 无效数据无法创建对象
  3. 提高可维护性:

    • 验证逻辑集中在类型定义中
    • 修改验证规则只需修改类型定义

步骤3:重构技巧总结

使用的重构手法:

  • 用对象替换数据值(Replace Data Value with Object):将基本类型替换为有意义的类型
  • 封装字段(Encapsulate Field):将字段封装为私有,通过方法访问

注意事项:

  • ⚠️ 确保类型有清晰的语义
  • ⚠️ 如果类型只在局部使用,考虑使用局部类
  • ⚠️ 重构后要更新所有使用处,确保行为一致

6. 预防策略

🛡️ 编码时:

  • 即时检查:

    • 基本类型是否有特殊含义?
    • 基本类型是否需要验证?
    • 是否可以创建有意义的类型?
  • 小步提交:

    • 发现基本类型偏执时,立即创建有意义的类型
    • 使用"用对象替换数据值"重构,保持类型有意义

🔍 Code Review清单:

  • 重点检查:

    • 基本类型是否表示有意义的领域概念?
    • 基本类型是否需要验证?
    • 是否可以创建有意义的类型?
  • 拒绝标准:

    • 使用基本类型表示有意义的领域概念
    • 基本类型需要验证但没有类型层面的保护
    • 验证逻辑分散在多个地方

⚙️ 自动化防护:

  • IDE配置:

    • 使用代码分析工具检测基本类型的使用模式
    • 启用类型安全警告
  • CI/CD集成:

    • 在CI流水线中集成代码分析工具
    • 检测基本类型使用模式,生成警告报告

下一篇预告:重复的switch(Repeated Switches)- 如何用多态替代重复的switch语句

相关推荐
笨笨饿1 天前
#141_ Codex 本地 AI 代理工具链工作流重构:Headroom、Scrapling、Archify 统一接入
开发语言·人工智能·stm32·单片机·嵌入式硬件·ui·重构
JieDavid2 天前
奇智创达知识产权管理系统期限监控模块实操,告别人工期限疏漏,实现管理闭环!
大数据·运维·人工智能·经验分享·重构
凤城老人2 天前
从PyQt6到Electron:一款英语字帖生成器的跨平台重构实战与踩坑全记录
javascript·重构·electron
兰亭妙微UI设计公司3 天前
兰亭妙微UI设计公司 蚂蚁阿福APP拆解:对话式交互如何重构就医全流程?
重构·交互
科技研学社3 天前
2026外贸新规+订单重构:内衣内裤工厂,自动化如何应对供应链洗牌
运维·重构·自动化
火山引擎和TA的超级拍档们4 天前
地上铁×火山引擎:从埋点采集到万物Agent,AI 重构新能源物流全生命周期
人工智能·重构·火山引擎
银河技术4 天前
TB级文本去重实战:从单机 OOM 到 Spark / Ray 分布式架构的工程演进
分布式·微服务·重构·架构·spark·llm·rag
Funny_AI_LAB5 天前
重构 Coding Agent:当 LLM 没有 KV Cache 时,上下文工程该怎么做?
人工智能·经验分享·语言模型·重构
55873 生态系统5 天前
55873 正和博弈经济模型:重构数字时代商业价值体系
大数据·人工智能·重构·全域文明操作系统·55873 操作系统·55873全域文明生态体系
空堂与归5 天前
320B MoE 开源:IQuest-Q1 重构智能体编码
人工智能·重构·开源