上帝之手:一个比喻在软件里的落点

软件里的"上帝"不只一个。它落过两次:一次落在一个类 上,叫上帝类;一次落在一次操作 上,叫上帝之手。前一个看体积,后一个看位置。下面看下不同领域对上帝的描述,再看它落在软件工程中的表现。


一、系统化:七个领域里的"上帝"

这个词不是软件造的。它在七个地方被用过,每次都在借同一个意思。

领域 上帝在那里指什么
足球 · 1986 一个不在场的手替他进了球
圣经 神的手直接干预人间
戏剧 · 机械降神 剧情解不开,神被吊下来收场
游戏 · God Hand 玩家拥有一切权限
法学 · Act of God 人力无可奈何的自然力
物理 · 上帝粒子 一切质量的源头
哲学 · 上帝视角 不站任何位置,却看得见一切

看着散,其实只回答三个问题。

组 领域 上帝在那里的角色 软件借成什么
做 足球、圣经、戏剧 替人动手 上帝之手
定 游戏、法学 定人定不了的事 上帝之手
知 物理、哲学 知人知不了的事 上帝类

软件只借了两个:"做"和"定"借成上帝之手,"知"借成上帝类。

七次借用里,没有一个是"受约束的"。上帝粒子是最小单位,上帝视角是无所不见,Act of God 是人力无可奈何------它们不是"管得多"那么简单,是"没有上限"。

复制代码
顺带一句:1986 年那次,赛后发布会没有官方翻译,英媒当天也没按原声报道------这个词从第一天起就是转述出来的。软件借的也不是足球,是它背后那个**不受约束**的意思。

二、全局化:软件里的两个上帝

1、上帝类:一个类装得太多

java 复制代码
class DeptService {
    void add();  
    void remove();  
    void move();
    int totalBudget();  
    boolean checkPermission();
    void syncToOtherSystem();  
    void sendNotify();
}

七个方法,四类不相干的事:增删、算钱、鉴权、对接外部系统。这就是上帝类。

复制代码
Riel 在 1996 年给过一条最好用的直觉:**看到名字里带 Driver、Manager、System、Subsystem 的抽象,要非常怀疑。**

2、上帝之手:一次操作被抢走

java 复制代码
class TreeService {
    void addChild(Node p, Node c) {
        p.children.add(c);
        c.parent = p;
    }
}

四行,小得不能再小。可它还是上帝------因为它在替节点做联系。

3、上帝之手怎么长出来的

三个原因叠在一起:需求一条条加进来,每次都"先放这儿能跑";没人问一句"该不该它管";这句没问,边界就没了。

什么时候冒头?第二次改同一个文件的时候。 第一次改是正常开发;第二次发现"怎么又是这儿",那就是它长出来了。

4、后果

谁受罪:改需求的人怕碰坏别处,写测试的人要造五个依赖才能测一个方法。长在哪层:Service 层最容易------它天生叫"服务",什么都能往里放。

代价还不一样。上帝类的代价是改不动;上帝之手的代价是可能已经错了,而且没人知道。

5、名字:为什么不叫那些

不叫耦合。 耦合管的是"谁依赖谁":DeptService 依赖 DeptRepository,DeptRepository 一改,DeptService 就得跟着改。可就算把它抽成接口、换个实现,c.parent = p 那一行还在 DeptService 里,一动没动。降耦合换掉的是依赖,不是这一行。

不叫中心、控制器。 这两个词说的是代码摆在哪儿、是不是所有人都在调它。看这个:

java 复制代码
class SchoolService { void addGrade(Grade g, School s) { g.school = s; } }
class GradeService  { void addClass(Cls c, Grade g)    { c.grade  = g; } }
class ClassService  { void addStudent(Stu t, Cls c)    { t.cls    = c; } }

三个类平级、互不依赖,谁也不在中心。可三行父子关系都是别人给的。没有中心,手还在。

不叫迪米特法则。 迪米特管的是"你该不该知道别人的内部",上帝之手管的是"这一行谁执行"。给调用方一个 addChild 句柄:他不知道 children 是 List 还是 Map、走不走缓存,但他能让两个节点变成父子。他不知道你的内部,他照样能定关系。

而且迪米特能被形式合规:包一层 Facade、加一个 getter,检查就过了,那一行赋值一动没动。规则可以绕,赋值绕不过。

这里也就顺手答了那个问题------什么时候用比喻,什么时候用规则:

要人承认"这说的是我" ,用比喻;要人判定"这是错的",用规则。

比喻不需要精确,只需要让人一眼看见自己。上帝类、上帝之手都是比喻,所以它们都缺一句话的判据。


三、判据与边界

1、一行判据

java 复制代码
c.parent = this;   // 写在 Dept 里,还是写在 DeptService 里

上帝之手只有一个判据:这一行赋值写在谁家。

为什么落在这一行,而不落在"方法在谁家"?因为方法能搬,判断它的那句话搬不走------addChild 里调一个外面的 validator,形式上方法在节点,判的还是外面。方法能搬,那一行赋值搬不掉。

上帝类的判据也只有一句:随便挑两个方法,你说不出它们为什么在同一个类里。

两个动作可以对着代码验:把树中间的一个节点摘下来,它应当是一棵完整可用的子树;把那个专门帮它挂孩子的类整个删掉,树应当照活。

2、边界:它还要跟别的类打交道

关系由节点自己做,可它不孤立------一次「把部门调走」,往往还得开事务、查权限、加锁。这些它自己做不了。

但这里有个容易混的地方:给它一个环境,不等于替它做动作。

java 复制代码
@Transactional                          // 环境:整段要么成功要么全废
public void transfer(Long id, Long pid) {
    Dept d = repo.find(id);
    Dept p = repo.find(pid);
    d.attachTo(p);                      // 动作:还是节点自己走的
}

换一种写法,事务照样在外面:

java 复制代码
class DeptService {
    @Transactional
    void transfer(Dept p, Dept c) {
        p.children.add(c);              // 动作被替了
        c.parent = p;
    }
}

两段代码里 @Transactional 一模一样,位置都在外面。差别只在 parent 那一行谁写。

所以事务、锁这类东西,套在外面不算上帝之手。环境可以包在外面,动作不能替。

权限要单独看。如果规则是「只有本级管理员能删」,那是节点自己的判断,把用户当参数递进去就行:

java 复制代码
void remove(User by) {
    if (!by.isAdminOf(this)) throw ...;
}

只有「这个用户有没有某个系统权限码」那种才留在外面------那得问权限系统,节点问不到。

这一节要回答的其实是:关系归节点,资源归外面------这两件事不冲突,而且必须同时成立。


四、每个类都应该是自己的上帝

上帝不允许有上帝------不许有一个凌驾在所有类之上的上帝。

把所有事揽进一个类,是上帝类;把别人的关系替他写好,是上帝之手。反过来只有一种做法:把神权切成很多份,每份只有一个类那么大。 上帝没有被取消,是被切开了。

于是两个落点:

  • 对上帝类:一个类管的地方,不该超过它自己那一块
  • 对上帝之手:一个类不该替别的类做关联。

判断一个类合不合格,最后只看一句:它管的是不是它自己那一块。

相关推荐
TOSUN同星2 小时前
TSMaster 9月功能亮点:旋钮控件、三大模块、报文过滤、SOMEIP静态注册全升级
汽车·软件工程
rolt7 小时前
[鉴赏]摇身一变鼓吹软件工程-01-Allen Holub
ai·软件工程·uml·敏捷开发
长不大的杨瑞杰1 天前
OpenCode v2 团队治理实战:多仓库 Policy、灰度发布与故障回滚
运维·人工智能·安全·软件工程·devops
长不大的杨瑞杰2 天前
OpenCode v2 CI 与自动化实战:无交互运行、权限收口与制品闭环
运维·人工智能·ci/cd·自动化·软件工程·1024程序员节
iCxhust2 天前
如何实时同步自己的工作日志,在不同的设备间
前端·软件工程·1024程序员节·同步日志
小朱爱编程1233 天前
AI点了两次发布,我多了两篇事故
数据库·人工智能·大模型·软件工程
CoderIsArt3 天前
软件工程与软件架构的经典书籍与工具软件
软件工程
Dola_Zou4 天前
工商业储能系统核心策略
算法·自动化·软件工程
llilian_165 天前
脉冲时间间隔及计数如何精准测试?
功能测试·嵌入式硬件·51单片机·软件工程