四⼤极简架构原则KISS/DRY/YAGNI/LOD

  本文围绕日常开发中最常用的四条工程原则KISS(保持简单)、DRY(勿重复自己)、YAGNI(你不会需要它)与LOD(最少知识原则)展开,说明它们如何共同约束"复杂度的无控制增长"。先从过度设计、提前优化、重复代码三类常见陷阱的成因讲起,再分别拆解四条原则的核心含义、适用边界与典型反例;随后用对照与关系图帮助建立"何时简化、何时抽象、何时先别做"的判断直觉;最后给出一份可直接用于代码评审的检查清单,帮助团队在评审现场快速识别架构与实现中的工程风险。目标不是背诵口号,而是把极简原则变成可执行的决策习惯。

  架构设计最容易踩的坑,往往不是"不会做复杂系统",而是"把简单问题做成了复杂系统"。业务一开始只需要一个清晰的主流程,却在迭代中被一层层策略框架、配置中心、抽象工厂和"将来可能用得上"的扩展点淹没;代码一开始只是几处相似逻辑,却在复制粘贴与错误抽象之间反复摇摆。KISS、DRY、YAGNI、LOD之所以常被并列讨论,是因为它们分别从可读性、一致性、时机感、依赖边界四个方向,把复杂度往下压。它们不是互斥规则,而是一套互补的约束:KISS管"别写难懂的",DRY管"别写两遍的",YAGNI管"别写还没用的",LOD管"别让模块知道太多"。

零. 为什么需要"极简原则"?三类陷阱从哪里来?

  很多团队并非缺少设计能力,而是设计欲过强、反馈周期过长。过度设计常常来自对不确定性的恐惧,需求还在变,就提前预留了大量扩展点,接口还只有一个实现,就先抽出三层抽象,业务规则尚未稳定,就开始建模"通用平台"。其根源往往是把"未来可能需要"误当成"现在必须建设",又缺少成本核算,多出来的抽象会同时增加理解成本、改动成本与调试成本。

  提前优化则更隐蔽。它常披着"性能专业度"的外衣:在没有profiling证据前就引入缓存、异步、分片、自定义连接池。为了"少做几次计算"把清晰逻辑拆成难维护的技巧写法。根因通常是把局部微观指标抬到高于交付与可维护性,或把"感觉慢"当成"已经慢"。真正的问题未必出在那几毫秒上,而出在调用链、数据结构或IO模式上,但代码已经先复杂了。

  重复代码的成因则更日常,赶工期时复制粘贴最快。相似却不等价的业务被强行抽公共方法后又怕牵一发动全身,于是继续复制。缺少共享边界与模块职责,导致校验、转换、错误码映射在多个服务里各写一份。重复的真正危险不只是多敲了几行字,而是同一业务规则有多个真相源:修一处漏一处,线上行为开始分叉。

  可以把三类陷阱与四条原则的对应关系粗略看成一张决策地图:过度设计主要用YAGNI与KISS约束。重复逻辑用DRY约束,但要用KISS防止"错误抽象"。模块间过度耦合与"链式调用深处字段"用LOD约束。提前优化则要求先测量,再让KISS/YAGNI拦住多余技巧。

  如上图所示,面对工期紧张与需求不稳定,开发者容易出现过度预埋扩展、复制粘贴产生多份逻辑源、主观盲目优化三类问题,业界分别以YAGNI+KISS、DRY、先测量再优化的编程思想加以约束。但倘若机械教条地套用上述原则,又极易催生依赖蔓延、深层耦合的架构弊病,此时需LOD规范对象间交互边界,收敛不必要依赖,以此平衡代码简洁度与模块解耦目标。

一. KISS,简单不是简陋,而是"下一步仍然好改"

  KISS(Keep It Simple, Stupid)强调:在满足当前需求的前提下,选择最直白、最少概念的实现 。这里的"简单"不是偷工减料,而是让读者用最少的上下文就能回答三个问题:这段代码在做什么、为什么这样做、改动时会影响哪里 。当一段逻辑需要阅读者先理解一套自定义状态机框架才能看懂"其实就是if/else审批流",KISS就已经被违反了。

  KISS在落地时最常见的误区,是把"少写代码"等同于"简单"。一行正则塞满业务规则、一个巨大函数塞满所有分支、或者用技巧压缩控制流,字符数少了,认知负担反而上升。真正的简单,通常表现为清晰命名、浅层控制流、显式边界与可本地理解的数据变换。另一个误区是把"没有抽象"当成KISS:当同一规则已经稳定出现第三次,并且变体确实一致时,适度抽取反而是降低复杂度;KISS反对的是为不确定未来制造概念,不是反对一切结构化。

  下面这段对比很典型。第一种写法用策略注册中心表达"按类型计费",概念完整,但对当前只有两种类型的业务来说偏重;第二种写法更直接,也更容易在评审里被读懂。

python 复制代码
# 过度设计:为两种计费方式搭策略框架
class PricingStrategy(Protocol):
    def price(self, order: Order) -> Decimal: ...

class StrategyRegistry:
    def __init__(self):
        self._map: dict[str, PricingStrategy] = {}

    def register(self, kind: str, strategy: PricingStrategy) -> None:
        self._map[kind] = strategy

    def price(self, order: Order) -> Decimal:
        return self._map[order.kind].price(order)

# KISS:当前需求只需要两种规则时,直写更清楚
def price_order(order: Order) -> Decimal:
    if order.kind == "retail":
        return order.amount * Decimal("1.00")
    if order.kind == "wholesale":
        return order.amount * Decimal("0.85")
    raise ValueError(f"unsupported order kind: {order.kind}")

  当类型增加到五六种、规则开始共享前置校验、并且变化频率升高时,再引入策略也不迟。KISS的价值就在于把抽象的时机推迟到证据足够之时。

二. DRY,消灭的是"知识重复",不是"字符相似"

  DRY(Don't Repeat Yourself)常被误解成"看到相似代码就抽方法"。更准确的说法是:系统中每一份知识(业务规则、不变量、映射关系)都应有单一、明确、权威的表达 。两段代码长得很像,但若语义不同,一个校验下单库存、一个校验售后库存。强行合并会制造更离谱的耦合。反之,散落在三处的同一折扣规则才是真正该DRY的对象。

  重复代码通常沿两条路径生长。一条是水平复制,多个模块各自写一遍手机号校验、金额分转元、错误码翻译。另一条是演化分叉,最初同一函数被复制后,一边修了边界条件,另一边没修,表面上"差不多",实际上已经是两套规则。DRY要解决的是后者的根,而不是追求字面零重复。

  实践中有一条很实用的经验,先问"变的时候会不会一起变"。若两处逻辑会因同一业务决策同步修改,就值得抽取。若只是碰巧结构相似,保留各自表达往往更安全。抽取时也应优先抽"规则与转换",而不是过早抽"巨型工具类杂物间"。

python 复制代码
// 知识重复:折扣规则出现了两份真相
function checkoutPrice(amount: number, vip: boolean): number {
  return vip ? amount * 0.9 : amount;
}

function quotePrice(amount: number, vip: boolean): number {
  return vip ? amount * 0.9 : amount; // 另一处又写一遍
}

// DRY:单一真相源
function applyVipDiscount(amount: number, vip: boolean): number {
  return vip ? amount * 0.9 : amount;
}

  需要警惕"DRY洁癖"。把仅出现两次、且很快会分叉的逻辑强行公共化,会让函数参数变成一堆flag,最终比重复更难读。这时应让KISS回来仲裁:宁可短暂重复,也不要错误抽象

三. YAGNI,把"可能需要"从当前迭代里删掉

  YAGNI(You Aren't Gonna Need It)直接对抗提前建设。它的尖锐之处在于大多数"将来一定要用的扩展点",最后要么用不上,要么用上时形态已经变了,预埋设计反而成了改造阻力。YAGNI不否定长期规划,而是否定把规划误当成当前代码里的实体。路线图可以写在文档和里程碑里。只有被当前验收标准证明需要的行为,才值得变成类、表字段、配置项与分支。

  YAGNI与提前优化是近亲。没有证据就做缓存、消息总线、多级降级、可插拔规则引擎,都属于"现在支付复杂度税,换取不一定发生的收益"。更稳妥的顺序是:先让正确路径简单跑通,用监控与压测找到真实瓶颈,再做针对性增强。这样每一次变复杂都有账单可对。

  一个常见场景是"通用导出服务"。当前只要导出CSV,却同时做了Excel/PDF、模板市场、插件沙箱与分布式任务编排。若产品下一季度仍只导出CSV,这些前置投资就是纯负债。YAGNI的落地话术很务实:在设计评审里问"这个扩展点删掉后,哪个当前用例会失败?"答不上来,就先别建

python 复制代码
// YAGNI违反,当前只需CSV,却预埋一堆格式
type Exporter interface {
    ExportCSV(rows []Row) ([]byte, error)
    ExportXLSX(rows []Row) ([]byte, error)
    ExportPDF(rows []Row) ([]byte, error)
}

// YAGNI,先做当前需要的
func ExportCSV(rows []Row) ([]byte, error) {
    // ...
    return buf.Bytes(), nil
}

  等第二个真实格式需求出现、且导出链路稳定后,再引入接口通常成本更低,因为那时你已经知道变化点在哪里。

四. LOD,少知道一点,系统才松得开

  LOD(Law of Demeter,最少知识原则)关注模块的"社交范围":一个对象应尽量只与直接朋友通信,而不是顺着调用链去摸很远的内部结构。经典味道是order.getCustomer().getAddress().getCity()这类列车式写法,调用方不只依赖Order,还间接依赖Customer与Address的内部形状。任何一环结构调整,调用方都可能被迫修改。

  LOD的工程意义,是把封装从"私有字段"推进到"稳定能力"。比起暴露结构让别人层层取值,不如提供意图明确的方法:order.shippingCity()或由应用服务组装好需要的视图模型。这样变化被关在门内,外部只认能力契约。LOD也适用于服务间:一个接口不应要求调用方理解你的三级内部表关联,再拼装出业务结果,应提供符合用例的API

python 复制代码
// 违反LOD,调用方知道了过深的对象图
String city = order.getCustomer().getAddress().getCity();

// 更符合LOD,让"知道如何取城市"留在合适边界内
String city = order.shippingCity();
// 或在应用层:
String city = shippingInfoService.cityOf(order.getId());

  LOD不是禁止一切间接访问,而是提醒,每次多知道一个陌生人,就多一条耦合边。架构上的"分层清晰、依赖单向、接口面向用例",背后常常就是LOD在起作用。

五. 四条原则如何一起用,冲突时听谁的

  原则在真实项目里会发生张力。DRY推动抽取,KISS担心抽取过度。YAGNI推迟建设,业务方催促"顺便把扩展做了"。LOD要求封装,调试时又想直接把内部结构掏出来。因此需要一张可对照的心智表,而不是把口号并列背诵。

原则 核心问题 典型收益 常见误用
KISS 有没有更直白的表达? 可读、可改、可评审 用技巧换短代码;拒绝一切结构化
DRY 是否存在多份业务真相? 改一处处处一致 把"长得像"当成"是同一个"
YAGNI 删掉后当前需求是否仍满足? 少负债、快交付 把合理的边界设计也删掉
LOD 是否依赖了不该知道的内部? 低耦合、改动局部化 为封装而封装,API 粒度过碎

  再看三类陷阱与原则的主攻方向,便于评审时快速定位。

陷阱 常见表象 优先用什么管 评审时一句探针
过度设计 空接口、空扩展点、过早平台化 YAGNI、KISS "当前哪个用例依赖它?"
提前优化 无指标缓存/异步/特殊算法 YAGNI、KISS "慢点证据在哪?"
重复代码 多处同规则、修复不同步 DRY(辅以KISS) "会不会因同一决策一起变?"

  它们的协作可以概括成一条很短的决策链:先用YAGNI删掉当前不需要的,再用KISS选择直白表达。当同一知识出现多份权威拷贝时用DRY收敛,跨模块取值或依赖开始"钻地道"时用LOD收回边界。若抽取后可读性明显下降,就退回重复或缩小抽象范围,这时KISS拥有一票否决权。整理后的思维链应该为:

总结

  四大工程原则并非独立的口号式规则,而是一套可落地、可互补的代码设计决策体系,在实际开发中存在明确的协作决策逻辑。

  开发迭代中可遵循"先删减冗余、再简化实现、后收敛重复、最后规范依赖"的思维链,以YAGNI剔除无效超前设计,以KISS保障代码简洁直观,以DRY统一业务规则源头,以LOD收敛模块耦合关系,且当原则适用出现冲突时,以代码可读性为核心的KISS原则拥有最终否决权。

相关推荐
Dreamland工坊1 小时前
从AI 视频到可用资产:聊浏览器端图像编辑器的架构设计
架构
传感器与混合集成电路1 小时前
基于FPGA与ADC协同架构的高密度数据采集模块设计原理与应用场景分析
fpga开发·架构
蜀道山老天师1 小时前
从零读懂 K8s:核心概念、集群架构与工作原理
容器·架构·kubernetes
霸道流氓气质2 小时前
SpringBoot中设计模式组合使用示例-策略、模板、观察者、门面、工厂、单例。
spring boot·后端·设计模式
zlinear数据采集卡2 小时前
2mV纹波的代价:硬核拆解ZLinear模拟前端放大器与正负可调电源设计
arm开发·单片机·嵌入式硬件·fpga开发·架构·开源
阳明山水2 小时前
TimesFM与Moirai MoE零样本预测解析
人工智能·深度学习·算法·机器学习·架构
霸道流氓气质3 小时前
微服务架构核心概念业务示例
微服务·云原生·架构
带娃的IT创业者3 小时前
当推理速度突破物理极限:深度解析 MiMo-v2.5-Pro-UltraSpeed 的 1000 TPS 架构革命
人工智能·架构·大模型·架构优化·mimo·tps·推理速度
ttod_qzstudio3 小时前
【软考设计模式】观察者模式:一对多依赖的自动通知与订阅解耦精讲
观察者模式·设计模式