设计原则与代码评审:如何在Code Review中检查设计原则,常见违反案例

代码评审这件事,很多团队都在做。但说实话,大部分Code Review都停留在「变量命名对不对」、「有没有内存泄漏」、「逻辑有没有bug」这个层面。真正能上升到设计原则层面的评审,少之又少。

我个人习惯是,每次Review代码时,脑子里会过一遍「这代码违反了哪条设计原则?」。你想想看,如果每次都能这么想,团队的代码质量怎么可能上不去?

今天我们就聊聊,怎么在Code Review中检查设计原则,以及那些我见过的、最常见的违反案例。

核心观点:Code Review是落地设计原则的最佳战场。没有Review,原则就是纸上谈兵。

一、Code Review中检查设计原则的四个层次

我在项目中遇到过很多次,开发人员提交的代码功能完全正确,但设计上却是一团糟。怎么系统地检查?我总结了一个四层检查法:

层次 检查重点 对应原则
第一层:结构 类是否过大?职责是否单一? 单一职责原则(SRP)
第二层:依赖 高层是否依赖低层?是否面向接口? 依赖倒置原则(DIP)
第三层:扩展 新增功能是否需要修改现有代码? 开闭原则(OCP)
第四层:替换 子类能否替换父类? 里氏替换原则(LSP)

嗯,这里要注意:不是每次Review都要把四个层次全过一遍。根据变更范围,挑重点看就行。

二、常见违反案例:我踩过的坑

案例1:违反单一职责原则(SRP)

这是最常见的。我曾经Review过一个「报表生成器」的代码,一个类干了三件事:读取数据、格式化报表、发送邮件。你想想看,哪天邮件服务器换了,这个类要改;报表格式变了,也要改;数据源换了,还要改。

// ❌ 违反SRP:一个类承担了太多职责
class ReportGenerator {
public:
    void generate() {
        fetchData();      // 职责1:数据获取
        formatReport();   // 职责2:报表格式化
        sendEmail();      // 职责3:邮件发送
    }
};

// ✅ 符合SRP:每个类只做一件事
class DataFetcher { /* 只负责获取数据 */ };
class ReportFormatter { /* 只负责格式化 */ };
class EmailSender { /* 只负责发送邮件 */ };

检查技巧:如果看到一个类的描述里出现了「和」、「以及」、「同时」这些词,大概率是违反SRP了。比如「这个类负责读取配置和初始化日志」——拆!

案例2:违反开闭原则(OCP)

我记得有一次,团队要新增一种支付方式。结果发现,支付处理类里有一个巨大的switch-case,每新增一种支付方式,就要改这个类。这就是典型的违反OCP。

// ❌ 违反OCP:新增支付方式需要修改现有代码
class PaymentProcessor {
public:
    void process(const std::string& type) {
        if (type == "alipay") { /* ... */ }
        else if (type == "wechat") { /* ... */ }
        // 新增支付方式?继续加else if!
    }
};

// ✅ 符合OCP:通过扩展实现新增
class PaymentMethod {
public:
    virtual void pay() = 0;
};

class Alipay : public PaymentMethod { /* ... */ };
class WechatPay : public PaymentMethod { /* ... */ };

避坑指南:我曾经在Review时看到一个大神写的代码,用模板特化实现了OCP。看起来很酷,但团队成员看不懂。记住:设计原则是为了让代码更好维护,不是为了炫技。

案例3:违反依赖倒置原则(DIP)

说白了,就是高层模块直接依赖了低层模块。我见过最典型的例子:业务逻辑层直接依赖了MySQL的API。哪天要换成PostgreSQL,整个业务层都得改。

// ❌ 违反DIP:高层依赖低层
class UserService {
private:
    MySQLRepository repo;  // 直接依赖具体实现
};

// ✅ 符合DIP:高层依赖抽象
class UserService {
private:
    IUserRepository& repo;  // 依赖接口
};

三、Code Review中的检查清单

我建议团队在Review时,可以对照这个清单快速过一遍:

  1. 类的大小:一个类超过300行?考虑拆分。
  2. 函数参数:超过3个参数?考虑封装成结构体。
  3. 条件分支:看到switch或if-else超过3个分支?考虑多态。
  4. 继承深度:继承层次超过3层?考虑组合。
  5. 循环依赖:A依赖B,B又依赖A?这是设计坏味道。
  6. 硬编码:字符串、数字直接写在代码里?提取成常量或配置。

我的经验:每次Review,至少能发现1-2个违反设计原则的地方。如果一次Review什么都没发现,要么是代码真的完美,要么是你没认真看——后者更常见。

四、设计原则检查的SVG流程图

下面这张图,是我在团队内部培训时用的。它展示了Code Review中检查设计原则的完整流程:

Code Review 设计原则检查流程 收到代码变更请求 第一层:结构检查(SRP) 类是否过大?职责是否单一? 第二层:依赖检查(DIP) 高层是否依赖抽象?是否面向接口? 第三层:扩展检查(OCP) 新增功能是否需要修改现有代码? 通过 / 打回修改 检查清单 □ 类 < 300行 □ 函数参数 < 3个 □ 无巨大switch □ 继承 < 3层 □ 无循环依赖 □ 无硬编码 □ 面向接口 □ 可扩展性 □ 可替换性 □ 单一职责 □ 依赖倒置 □ 开闭原则

五、如何推动团队落地

光有方法还不够,还得让团队愿意用。我分享几个实战经验:

  • 建立Checklist模板:把上面那个清单做成PR模板的一部分,提交代码时自动带出来。
  • 先抓大放小:刚开始不要追求完美,先抓最明显的违反案例。比如一个类干了五件事,这种一眼就能看出来。
  • 用案例说话:Review时不要只说「这违反了XX原则」,要解释「如果不改,以后加功能会多痛苦」。我一般会举一个以前踩过的坑。
  • 渐进式改进:不要指望一次Review把所有问题都修完。每次改一点,代码质量是慢慢提升的。

小技巧:我习惯在Review评论里加一个标签,比如「[SRP] 这个类建议拆分」或者「[OCP] 这里用策略模式替代switch」。时间长了,团队看到标签就知道是设计原则问题。

设计原则的落地,从来不是靠一次培训或者一份文档就能搞定的。它需要你在每一次Code Review中,一点一点地坚持。嗯,这条路没有捷径,但走通了,团队的代码质量会有质的飞跃。

专注资料整理