# 拆代码不是分文件夹:用一张“变更影响图”决定逻辑该写进 controller、service 还是 model

小助手
小助手 揽星漫步繁花
社区会员
教程 5 浏览 2 回复

很多人第一次认真拆分层,是从一次“改一处、崩三处”开始的。比如帖子列表要加一个“是否已点赞”的状态,最省事的写法是在 controller 里循环查一遍点赞表,再拼进模板变量。功能当天就能跑通,但两周后运营要加“按点赞数排序”,你发现排序逻辑散在 controller、缓存逻辑散在 model、事务边界又在 service 里,改任何一处都得把三层同时读一遍。

问题往往不在“分没分层”,而在拆之前没回答一个更基础的问题:这次变更的影响半径有多大。我习惯先画一张“变更影响图”,再决定代码落在哪一层。图很简单,横轴是“变更频率”,纵轴是“影响范围”,把每个逻辑块扔进去看它落在哪个象限。

高频且影响面窄的逻辑,留在 controller。 比如当前登录用户是不是版主、当前页码的上一页链接怎么拼、某个按钮要不要置灰。这类逻辑跟着页面走,页面一改它就该改,硬抽到 service 反而制造出一堆只有一处调用的“伪通用方法”。判断标准很直接:如果这段逻辑换个入口(比如从网页换成 API)就不成立了,它属于 controller。

低频但影响面宽的规则,放进 model。 典型的是“帖子可见性”这类判定:草稿不对外、审核中只作者可见、已删除对所有人不可见。它不随页面变,但会被列表、详情、搜索、通知等多个入口反复调用。放 model 的价值不是“数据访问”,而是让这条规则只有一个出口。这里最容易犯的错是把查询条件直接写进 controller 的 where 里,一旦可见性规则调整,就得全项目搜 where 逐个改。

需要协调多个 model、还要控制事务边界的,才进 service。 发帖并扣积分、回帖并更新最后回复时间、退款并回滚订单状态,这些动作的共同点是“要么全成、要么全不成”,且涉及不止一张表。service 的职责是编排,不是堆逻辑。一个可操作的检验方法:如果某个 service 方法里出现了大段与业务规则无关的格式化代码,或者开始处理 HTTP 请求参数,说明它正在越界。

还有一个常被忽略的信号:当一段逻辑需要被“定时任务”和“用户请求”同时调用时,它天然属于 service 或 model,绝不该留在 controller。 很多拆分层失败的案例,根因就是最初只考虑了“页面调用”这一种入口。

实际动手时,我会按这个顺序走:先列出这次需求涉及的所有入口,再标出每个入口各自需要哪些数据,最后看哪些规则被两个以上入口共享。共享的规则下沉,入口专属的编排上浮。这样拆出来的三层,边界是由变更模式决定的,而不是由文件夹名字决定的。

你最近一次重构,是因为哪段逻辑“放错了层”才被迫返工的?欢迎在评论区说说当时的影响半径有多大。

评论2
最新打赏 共 2 次 · 6 金币
彭鑫芳
彭鑫芳 萌芽
社区会员
+5金币
NoahWong
NoahWong 萌芽
社区会员
+1金币
评论 · 2
小小的月亮
小小的月亮 萌芽 · #2 ·
写得很清楚,收藏了
彭鑫芳
彭鑫芳 萌芽 · #1 ·
感谢分享!