Skip to content

简化原则

如果说 SOLID 关注的是结构,那么简化原则关注的是开发的"经济学"与认知的"减负"。简化原则包括 KISS(保持简单)、DRY(不要重复)和 YAGNI(你不会需要它),它们共同指导我们避免过度设计,保持代码的可维护性。

KISS 原则

KISS(Keep It Simple, Stupid)主张简洁优于复杂。简单的代码更易于阅读、测试和维护,而复杂的代码不仅增加了调试难度,还提高了新人的上手门槛。

在满足需求的前提下,选择最直接的算法和数据结构。例如,对于小规模数据集的处理,一个清晰的线性搜索可能比高度优化的平衡树实现更符合工程实际,因为后者的维护成本远超其性能收益。

实践 KISS 并不意味着编写劣质代码,而是指避免为了"巧妙"而牺牲可读性。代码的阅读次数远多于编写次数,优先考虑阅读者而非编写者。

简单 vs 复杂的对比

方面简单(遵循 KISS)过度复杂(违反 KISS)
可读性新人/半年后自己都能快速看懂阅读成本极高,认知负担大
维护性修改快、风险低小改动容易引入 bug,改动扩散
调试/测试问题容易定位,测试用例少而清晰隐藏 bug 多,测试覆盖困难
性能/扩展后期优化空间大(先做对再做快)过早优化导致架构僵硬,扩展反而更难
团队协作交接容易,Review 快代码审查变成痛苦,知识传递成本高
交付速度开发周期短,业务价值更快落地过度设计拖慢进度,YAGNI 场景常见

优先最直接的实现

java
// 简单:清晰直观
public boolean isValidEmail(String email) {
    return email != null && email.contains("@") && email.contains(".");
}

// 过度复杂:引入不必要的抽象
public boolean isValidEmail(String email) {
    EmailValidator validator = ValidatorFactory
        .createValidator(ValidatorType.EMAIL)
        .withConfiguration(EmailConfig.getDefault())
        .build();
    return validator.validate(email);
}

控制抽象层级

能用 if-else 清晰解决,就不要先上策略模式。能一个类搞定,就不要拆成 5 个接口 + 工厂。

拒绝"看起来很高级"的方案

除非有明确证据证明需要,否则不要用:

  • 复杂的泛型约束
  • 多层代理/装饰器
  • 事件总线代替直接调用
  • 自定义 DSL / 规则引擎

DRY 原则

DRY(Don't Repeat Yourself)认为,每一个知识点在系统中都必须有一个单一、明确、权威的表现形式。

重复的代码是系统逻辑不一致的温床。当某项业务规则发生变化时,如果该逻辑散落在多处,极易出现漏改,从而引发隐蔽的 Bug。

DRY 的应用范围超出代码本身,还包括数据库模式、测试计划、构建系统乃至文档。通过将重复逻辑封装到函数、类或模块中,开发者可以实现"一处修改,全局生效"的高效维护模式。

违反示例

java
// 违反 DRY:用户验证逻辑散落多处
public class OrderController {
    public void placeOrder(Order order) {
        if (order.getUser() == null || order.getUser().getId() == null) {
            throw new IllegalArgumentException("Invalid user");
        }
        // 订单处理逻辑...
    }
}

public class PaymentController {
    public void processPayment(Payment payment) {
        if (payment.getUser() == null || payment.getUser().getId() == null) {
            throw new IllegalArgumentException("Invalid user");
        }
        // 支付处理逻辑...
    }
}

遵循示例

java
// 遵循 DRY:抽取公共验证逻辑
public class UserValidator {
    public static void requireValidUser(User user) {
        if (user == null || user.getId() == null) {
            throw new IllegalArgumentException("Invalid user");
        }
    }
}

public class OrderController {
    public void placeOrder(Order order) {
        UserValidator.requireValidUser(order.getUser());
        // 订单处理逻辑...
    }
}

三次法则

过度追求 DRY 可能导致过早抽象,使代码路径变得迂回,反而降低可读性。许多架构师遵循"三次法则":只有当代码重复三次以上时,才进行抽象化。

出现次数建议操作理由
1 次保持独立尚未形成模式,抽象为时过早
2 次观察是否真的相同可能只是巧合,未来可能分化
3 次考虑抽取抽象模式已稳定,抽象收益大于成本

YAGNI 原则

YAGNI(You Aren't Gonna Need It)源自极限编程(XP),提醒开发者只在确实需要时才实现功能,而不要基于对未来的预测进行过度开发。

过度设计的代价是巨大的:不仅消耗了宝贵的开发时间,还向系统中注入了不必要的复杂性和潜在的 Bug 隐患。

遵循 YAGNI 并不意味着不进行规划,而是意味着不提前构建尚未定义的扩展点。由于未来的需求往往会发生偏离,提前编写的代码最后很可能变成被废弃的负担。

YAGNI 原则与敏捷开发的"响应变化高于遵循计划"理念高度契合。它鼓励我们关注当下的问题,而非猜测未来的需求。

违反示例

java
// 违反 YAGNI:为"可能需要的"功能提前设计
public interface PaymentProcessor {
    void processCreditCard(CreditCard card, Money amount);
    void processPayPal(PayPalAccount account, Money amount);
    void processBitcoin(BitcoinAddress address, Money amount);
    void processAlipay(AlipayAccount account, Money amount);
    // 当前只需要信用卡支付,其他都是"可能以后需要"
}

class OrderService {
    public void payOrder(Order order, PaymentType type) {
        switch (type) {
            case CREDIT_CARD: /* ... */
            case PAYPAL: /* 未实现 */
            case BITCOIN: /* 未实现 */
            case ALIPAY: /* 未实现 */
        }
    }
}

遵循示例

java
// 遵循 YAGNI:只实现当前需要的功能
public class CreditCardProcessor {
    public void process(CreditCard card, Money amount) {
        // 信用卡支付逻辑
    }
}

class OrderService {
    private final CreditCardProcessor paymentProcessor;

    public void payOrder(Order order, CreditCard card) {
        paymentProcessor.process(card, order.getTotal());
    }
}

即时设计 vs 超前设计

设计方式描述优点缺点
即时设计只设计当前需要的功能快速交付,灵活性高后期可能需要重构
超前设计提前设计未来可能需要的功能后期扩展方便过度设计,浪费开发时间

坚持"即时设计"能够让系统保持灵活性,从而快速响应当前最真实的业务价值。

抽象的辩证法

在追求卓越设计的过程中,开发者经常陷入"何时抽象"的困境。为了修正对 DRY 原则的教条式滥用,业界提出了 AHA 和 WET 等互补原则。

AHA 原则

AHA(Avoid Hasty Abstraction)主张,在完全理解领域知识并确认模式稳定之前,应抵制抽象的冲动。

匆忙建立的抽象往往是脆弱的,当需求发生微小的分化时,这种抽象会变得异常臃肿,被迫引入大量的标志位和条件分支来适配不同的场景,最终导致维护成本高于重复代码。

AHA 的核心哲学是:重复代码的成本通常低于错误的抽象。与其忍受一个扭曲的、难以修改的旧抽象,不如让两段逻辑暂时重复,直到它们的共性真正显现出来。

java
// 匆忙抽象:为"相似"的逻辑强行合并
public class MessageSender {
    public void send(Message message, Destination dest) {
        if (dest.getType() == DestType.EMAIL) {
            // 邮件发送逻辑
        } else if (dest.getType() == DestType.SMS) {
            // 短信发送逻辑
        } else if (dest.getType() == DestType.PUSH) {
            // 推送发送逻辑
        }
        // 类型判断越来越多,抽象开始泄漏
    }
}

// 等待模式稳定后再抽象
public class EmailSender {
    public void send(EmailMessage message) { /* 邮件逻辑 */ }
}

public class SmsSender {
    public void send(SmsMessage message) { /* 短信逻辑 */ }
}

WET 原则

WET(Write Everything Twice)建议在代码第二次出现时保持其独立性,而在第三次出现时才考虑抽象化。

这种策略在前端开发和快速原型设计中尤为有效。两段看起来相似的代码可能代表了两个完全不同的业务概念,如果强行合并,未来一旦业务逻辑分化,强行拆解抽象的代价将非常高昂。

原则对比

原则对重复的态度核心风险适用场景
DRY零容忍,追求单一来源导致过度耦合和复杂的层次结构核心业务规则、数据持久化
WET允许重复,遵循三次法则增加初始维护工作量UI 组件、工具类、快速原型
AHA优先清晰度逻辑散乱,难以进行全局变更需求不明确、多变的市场策略

实践建议

代码审查检查清单

  • [ ] 代码是否过于"巧妙",牺牲可读性换取简洁性?
  • [ ] 是否存在明显的重复逻辑?
  • [ ] 是否实现了当前不需要的功能?
  • [ ] 抽象是否过早,模式是否稳定?
  • [ ] 是否为了复用而引入了不必要的参数和分支?

重构示例

java
// 重构前:违反多项简化原则
public class OrderService {
    public void processOrder(Order order) {
        // 1. 过度复杂的验证
        if (order != null &&
            order.getItems() != null &&
            !order.getItems().isEmpty() &&
            order.getItems().stream().allMatch(item ->
                item != null && item.getQuantity() > 0)) {

            // 2. 重复的折扣计算
            Money discount = order.getTotal().multiply(0.1);
            if (order.getUser().getLevel() == UserLevel.VIP) {
                discount = order.getTotal().multiply(0.2);
            }

            // 3. 未使用的扩展点
            for (OrderProcessor processor : processors) {
                if (processor.supports(order)) {
                    processor.process(order);
                }
            }
        }
    }
}

// 重构后:遵循简化原则
public class OrderService {
    private final OrderValidator validator;
    private final DiscountCalculator discountCalculator;
    private final OrderProcessor orderProcessor;

    public void processOrder(Order order) {
        validator.validate(order);  // 职责分离
        Money discount = discountCalculator.calculate(order);  // 单一来源
        orderProcessor.process(order);  // 只使用当前需要的处理器
    }
}

简化原则的本质是经济学:用最少的成本(复杂性)实现最大的价值(功能)。KISS、DRY、YAGNI 不是孤立的教条,而是在不同层面上指导我们做出经济决策的思考工具。