关于 law-of-demeter
一项介绍迪米特法则、帮助降低组件间耦合的编码技能,涵盖对象访问链、API 边界、数据结构以及代码审查实践。
主要功能
- 01仅访问直接协作者
- 02用对象自身方法替代深层链式访问
- 03在边界封装外部 API
- 04为使用方展平数据
- 05代码审查清单
适用场景
- 01减少对嵌套对象的脆弱依赖
- 02简化需要深层模拟的测试
- 03避免第三方响应结构渗入业务逻辑
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ jordancoin/ codingskills --skill law-of-demeter安装后在 Claude Code 中输入/law-of-demeter确认已加载。
详细介绍
LoD — 迪米特法则(最少知识原则)
应用前
如果 .agents/stack-context.md 存在,请先阅读。应用此原则时,应采用检测到的技术栈中的惯用模式。有关框架特定的细节,请使用 context7 MCP 或网页搜索,切勿凭猜测。
原则
组件只应与其直接协作者交互。不要穿透对象访问其内部、子对象的内部,或更深层的内容。
更准确地说,一个方法只应调用以下对象的方法:(1)自身;(2)自身的字段;(3)自身的参数;(4)自身创建的对象;(5)全局或单例服务。
为什么这在生产环境中很重要
每一串访问链(order.customer.address.city)都是一串耦合关系。只要其中任何一环发生变化——客户模型重组、地址变为可选,或城市移至子对象中——所有调用方都会出错。
违反 LoD 会导致系统变得脆弱,看似很小的重构也会在无关代码中引发连锁故障。这也会让测试变得棘手:要测试一个穿透 4 层对象的函数,你必须模拟这 4 层对象。
在生产环境中,违反 LoD 是导致 NullPointerException 和 undefined is not an object 错误的主要原因之一。访问链中的每个 . 都可能导致空值或未定义值解引用。
规则
- 不要穿透对象逐层访问。 如果需要获取结构深处的数据,请让直接拥有该数据的对象提供。使用
order.shipping_city(),而不是order.customer.address.city。 - 告诉对象要做什么,而不是询问数据。 不要深入对象获取数据并自行做出决策,而要告诉对象你需要它完成什么。让它使用自己的内部数据。
- 在边界处封装外部 API。 第三方 SDK 和 API 响应通常会暴露深层结构。创建一个轻量封装层,只暴露代码所需的内容,并采用代码所需的结构。
- 为使用方扁平化数据。 在边界之间传递数据时(例如传给模板、前端或其他服务),应将嵌套结构扁平化为专门设计的结构,而不是暴露内部模型。
- 限制参数所需的知识。 函数接收一个对象时,只应使用自己直接需要的字段。如果只需要用户的电子邮件,就只接收电子邮件,而不是整个用户对象。
反模式
- 火车残骸式调用:
app.config.database.connection.pool.max_size——配置结构的任何变化都会导致调用出错 - 特性嫉妒: 某个函数使用另一个对象的数据多于自身的数据——它可能应该属于那个对象
- 透明封装: 通过 getter 暴露内部结构的对象,会让每个使用方都依赖其内部结构
- 测试中的深层模拟: 如果仅为测试订单处理就必须模拟
order.customer.address,说明代码访问得太深了
示例
-- LoD violation: reaching through the chain
def get_shipping_label(order):
name = order.customer.name
street = order.customer.address.street
city = order.customer.address.city
zip = order.customer.address.zip_code
return format_label(name, street, city, zip)
-- LoD-compliant: ask the order for what you need
def get_shipping_label(order):
info = order.shipping_info() # order knows how to assemble this
return format_label(info)
-- LoD violation: reaching into config internals
timeout = app.config.services.payment.timeout_ms
-- LoD-compliant: config provides what you need
timeout = config.payment_timeout()
边界
- LoD 不适用于纯数据结构。 访问 DTO、字典或值对象的字段是合理的——这些结构存在的目的就是承载数据。LoD 适用于具有行为的对象。
- LoD 不意味着要封装所有内容。 不要为每种访问模式都创建一个封装方法。应在有意义且内部结构可能发生变化的边界处应用此原则。
- 流式 API 和构建器 API 是例外。 像
query.select("name").where(active=true).limit(10)这样的链式调用不违反 LoD,因为每个方法都返回self——你始终在与同一个对象交互。 - 与 KISS 存在张力: 有时直接调用链比中间方法更清晰。应在耦合风险确实存在的地方应用 LoD(例如跨模块边界、穿透不稳定的结构),而不是在紧密耦合的内部代码中。
代码审查清单
- 是否存在 3 个或更多属性/方法访问,层层深入对象边界?
- 是否有函数访问另一个对象的内部细节多于访问自身的内部细节?(特性迷恋)
- 是否在边界处封装外部 API 的响应,而不是将其泄漏到业务逻辑中?
- 传入对象的内部结构改变时,是否不会导致这段代码出错?
- 测试是否必须构建深层对象图,才能测试简单逻辑?
相关技能
- solid:用于依赖反转和接口隔离(结构解耦)
- separation-of-concerns:用于保持各层相互独立
- kiss:当封装比访问链本身更复杂时
评论
支持 Markdown