长运行与临时调整要分离

存量 VM 不切换,我们可以提供一个接口来调整虚拟机的 cpu_mode 来实现。这样长运行的逻辑简单,而调整和长运行逻辑分离,降低复杂性。 核心:长运行与临时调整分离 存量 VM 不切换。我们可以提供一个接口来调整虚拟机的 cpu_mode 来实现。 这样长运行的逻辑简单,而调整和长运行逻辑分离,降低复杂性。 一定要区分长运行,有些需求确实是真需求,但不属于长运行逻辑。 比如存储虚拟机的切换。 我们太多控制开关,就是为了做这些东西,本来是一个脚本能批量处理的事,结果把逻辑搞的异常复杂。 一个例子 对,有个接口层的。 我们一个查询接口很慢,最后排查发现这个查询接口里一行一行的在更新数据。 追溯发现,是增加了某个字段,这个字段在后面的流程里比较重要,且不能简单的设置某个值。 所以在查询的时候,通过运算实时赋值。简直了。 大哥,你弄个脚本全部跑一遍就行了啊,升级的时候执行这个脚本啊。 小结 长运行与临时调整要分离; 存量 VM 不切换,提供一个接口来调整,长运行的逻辑简单; 一定要区分长运行,有些需求是真需求,但不属于长运行逻辑; 批量、整体操作,用脚本跑一遍就行,不要把逻辑搞的异常复杂; 不要在查询接口里一行一行的更新数据。

May 24, 2026 · 1 min · 29 words · 文叨叨

架构决定稳定性:核心对象 + 流水线

架构决定下限,代码决定上限。虽然代码影响很大,但是整体的稳定性,依然是架构占大部分决定因素。linux 网络栈足够复杂,但又有足够的扩展性,且非常的高性能,为什么?因为它流水线 + sk_buff。 核心:架构决定稳定性 架构决定下限,代码决定上限。 虽然代码影响很大,但是整体的稳定性,依然是架构占大部分决定因素。 linux 网络栈(sk_buff),足够复杂,但又有足够的扩展性,且非常的高性能。 它确很稳定,为什么? 因为他流水线 + sk_buff: 它的核心对象,是高内聚的、面向对象的; 它子模块是松耦合的,通过核心对象来协作。 我们的 iaas 流程呢? 没有核心对象,全是临时去数据库查,临时到数据库更新,临时到 hyper 获取环境信息。 修改也是修改很细微的变量,而不是一个从一而终的对象。 这种构成了各种千丝万缕的关系,且环节之间直接调用,相互耦合。 缺乏一个解耦式的流水线的设计。 即便不是 k8s 那种声明式的架构,即便是流程式的、面向过程的架构,也不至于这么乱呀。 最简单的解法 最简单的,主机是一个核心对象,从创建开始就确定了的,后续就不要再改了。 volume 是核心对象,和主机绑在一起。再加个网络核心对象,和主机绑定。 全流程从头到尾,都操作这三个核心对象,且通过责任链的方式,A 环节操作完了,B 环节。 这样每个环节是解耦的,他们不关心上下环节,只关心核心对象。 这种简单、稳定多了呀。 web 框架 gin,也是这样呀,大家都操作一个 context 对象,这样各个处理模块互不耦合,且扩展性好。 小结 架构决定下限,代码决定上限; 整体的稳定性,依然是架构占大部分决定因素; linux 网络栈稳定,因为它流水线 + sk_buff,核心对象高内聚,子模块松耦合; 我们的 iaas 流程没有核心对象,全是临时去数据库查、临时更新,构成千丝万缕的关系; 主机、volume、网络是核心对象,全流程操作这三个核心对象,通过责任链的方式,每个环节解耦。

May 23, 2026 · 1 min · 52 words · 文叨叨

简单是唯一标准吗?

很多时候讨论方案,“这个改起来简单”,然后就没有人说话了。简单没有错,问题是简单就是指这一次改动的简单呢?那运维的简单性、扩展的简单性、稳定的简单性、排查的简单性呢? 现象 很多时候讨论方案,“这个改起来简单”,然后就没有人说话了。 因为这东西真有点无从反驳。 改起来简单,风险就低,你怎么反驳,又不是你改,又不是你背锅。 包括网络上有一些图,基本表达是"能跑就别动"。 但是这玩意真的对吗? 有些时候不是跑的问题,是业务发展、竞品拼杀、商业博弈,必须有做一些特性和功能,这个时候是必须要动。 简单不是唯一标准 动不是问题,问题是怎么动,衡量标准是什么。 简单没有错,问题是简单就是指这一次改动的简单呢? 那运维的简单性呢?扩展的简单性呢?稳定的简单性呢?排查的简单性呢?这些都不谈? 比如要对接第三方备份厂商,居然有其他厂商入侵到我们计算节点,并且到我们计算节点删除 img 的操作方案。 可能这次是简单了,那下次呢?下个备份厂商呢? 包括备份对接 s3 接口,多好的需求,符合生态,非常标准化。 结果硬是做成了非标能力,绑定存储,绑定各种特定流程,非得模仿老功能,依赖中转节点。 难道就不能从计算节点直接上传 s3? 数不胜数的例子,这类问题,基本都是当时评审的时候"这个方案改动小"导致的。 这类问题真无解 这类问题其实真无解,就看 leader 的品味。 因为其他队员发言是没有权威的,文无第一。 尤其是"改动小",比如你的方案实现了从计算节点直接上传备份到 s3,并且是标准 s3 接口,以后不做改动对接任何备份厂商。 很好吧,所有人都知道这是对的,但是不会有人主动去赞同,因为改动大,得背锅。 这类问题短期没有人在意,但是时间长了,赚钱的部门、商业线第一个跳出来: “你们写的代码怎么这么多限制,为什么别人支持,我们不支持,很奇怪。为什么这个支持 s3,那个不支持,很奇怪。” 不过到这依然没有结论,依然看 leader 的品味,看老板的品味。 小结 简单是唯一标准吗?不是; 简单没有错,问题是简单就是指这一次改动的简单呢; 还要考虑运维的简单性、扩展的简单性、稳定的简单性、排查的简单性; 这类问题真无解,就看 leader 的品味; 所有人都知道是对的,但不会有人主动去赞同,因为改动大,得背锅。

May 17, 2026 · 1 min · 47 words · 文叨叨

边界:计算、存储、网络要分离

拿快照来说,为啥我们支持快照的多链这么复杂?按道理虚拟机只需要一个卷,至于这个卷背后的链是完全不需要关心的。如果计算存储分离,快照局限于存储模块,计算只需要一个卷。 现象:快照的多链为什么这么复杂 拿快照来说,为啥我们支持快照的多链这么复杂? 按道理虚拟机只需要一个卷,至于这个卷背后的链是完全不需要关心的。 但是放到我们代码,复杂无比,即便是单链也异常复杂,多链简直就做不下去了。 如果分开,不过多复杂的快照链,只需要在快照模块处理。 但是我们现在的代码,各种功能和存储都在一起: 开关机要管理快照链(不是调接口,而是一行一行的命令呀); 克隆要管理快照链; 迁移对快照链的操作更多了; 回滚快照、打快照,那更复杂了(快照的管理,一行一行的命令,元数据的修改,都在各种流程中交错)。简直了! 为什么这么难改 你可能会说,改呀,重构呀。 几十万行的屎山,牵一发动全身。不是一个人就能改的了的。 而且你改的过程中,别人还在持续扩展新功能,别人要怎么配合你的修改,都是很难的事。 这种推动,一定是自上而下的。不是说一人之力不能改,而是没人敢让你上。 得需要资源做 QA 大规模测试,还需要新扩展能力的配合,还需要领导有魄力,胆大心细。 执行人能做到心细,但是执行人胆大没用,别人怕出了问题你承担不了呀,得别人背锅。 需要决策人有魄力,自顶向下的推动。 但是如果老板或决策者离一线(售后、研发)远了,很可能不看重这些。 决策者大概率觉得,能跑就行,技术是工具,能满足客户需求即可。 但实际是,很多客户因为稳定性而流失,稳定性是因为屎山代码、架构问题、代码问题。 但是这些东西不能给财务数据带来改善,销售才能改善财务数据,所以继续怼销售提的需求,继续挖坑,继续流失客户。 根因:快照和计算绑定 就是因为我们的快照的处理、链的管理,都在计算里面。 因为我们主要用的 qcow2,qcow2 的快照性能高呀。 但是它有个问题,qcow2 的快照是和计算绑定的。 它为啥性能高,因为它需要计算层联动切换读写头。 如果要计算存储分离,就需要存储提供一种快照模式,不切换读写头,就能做快照。 lvm 是一种方式,bitmap,但是它做快照后写性能很低。 不考虑性能的因素下,计算、存储分离,极大降低快照开发的复杂性。 快照局限于存储模块,甚至是存储模块的快照子模块。计算只需要一个卷,不管其背后的链。 快照模块如何实现多链 A->B->C-> active 回滚到 A 后,A->active。 这个时候,要实现 B、C 的保留,且不被影响,也不影响别人,保留且独立。 如果是 qcow2 快照,那么天然是独立的,保留卷、保留链的数据关系即可; 如果是 lvm 这种,每个快照本身是独立的,保留即可,保留链关系元数据即可。 归根到底,链关系好处理,复杂性还是在存储端,性能和空间的取舍。 数据表的设计也简单,就是一个以原卷为根的一个树结构。 小结 边界:计算、存储、网络要分离; 按道理虚拟机只需要一个卷,至于这个卷背后的链是完全不需要关心的; 计算存储分离,极大降低快照开发的复杂性; 快照局限于存储模块,计算只需要一个卷,不管其背后的链; 几十万行的屎山,牵一发动全身,这种推动一定是自上而下的。

May 16, 2026 · 1 min · 61 words · 文叨叨

可变与不可变:找到变化,封装变化

设计模式一个基本点两个原则:一个基本点是找到变化、封装变化;两个原则是基于接口而不是基于实现、组合优于继承。核心是可变与不可变的隔离、解耦。 核心:找到变化,封装变化 找到变化,封装变化。 易变与不易变的隔离,可变与不可变的隔离,稳定与不稳定的隔离。 设计模式一个基本点两个原则: 一个基本点:find what varies and then encapsulate it。找到变化、封装变化; 两个原则:基于接口,而不是基于实现;组合优于继承。 找到变化、封装变化:核心是可变与不可变的隔离、解耦。开闭原则也有这个思想。 所有的扩展性都是因为变化,分解可变与不可变作为一个基本点,没毛病。 基于接口,其实目的也是为了组合 基于接口,其实本质也是为了解耦可变与稳定,接口应该是稳定的,实现是易变的。 组合优于继承,因为继承是紧耦合的。 组合更利于可变,组合的前提是抽出来不可变的接口,即封装可变与不可变。 接口和组合,其实都是可变与不可变的实践。 通过不可变的组合实现可变。 一个例子:iaas 的调度方案 存储组,会选一个默认存储池,它会影响调度结果,所以这个也应该做到调度里。 这是一个很容易进入误区的陷阱。 实际上上层很多东西都会影响到底层呀,因为底层接收的是参数,上层参数变了,底层结果当然也会变。这不能作为划分边界的依据呀。 比如调解手机声音,不能说,腾讯音乐能调整,所以我要把腾讯音乐做到手机的扬声器模块。 我们调度,肯定是一个原子调度模块,我们提供参数,上层传参,控制调度结果。 我们提供各种单一的原子的能力,上层通过组合这些原子能力实现多样化可扩展的调度。 比如选择存储池,底层原子接口只接受存储池参数,不应该帮去改变或者帮他去选择。这是单一的原子的。如果加了选择的逻辑,就不单一了呀。 同时,底层应该是不易变的。 自动选择就是一个易变的逻辑,不同客户需求不同。但是根据存储池调度,这个是稳定的。 所以,我们只需要接受存储池参数即可。至于这个参数怎么玩出花,是上层的事。 所以存储组交给上层服务是 ok 的。 分层,哪些是产品层,哪些是应用层,哪些是基础层,边界。 基础层必然是稳定的、原子的、提供基础能力,这就是边界。 小结 找到变化,封装变化; 设计模式一个基本点两个原则:找到变化封装变化、基于接口而非实现、组合优于继承; 核心是可变与不可变的隔离、解耦; 接口应该是稳定的,实现是易变的; 通过不可变的组合实现可变; 基础层必然是稳定的、原子的、提供基础能力,这就是边界。

May 10, 2026 · 1 min · 45 words · 文叨叨

简单 + 组合,才是最强的

《UNIX编程艺术》里讲的:cmd + 管道 + 文本的设计理念,让单个命令足够简单清晰,而命令的编排又可以创造无限扩展的能力。单元简单,才容易组合。 核心:简单 + 组合才是最强的 《UNIX编程艺术》里面讲的: cmd + 管道 + 文本的设计理念,让单个命令足够简单清晰,而命令的编排又可以创造无限扩展的能力。 单元简单,才容易组合; 单元复杂,组合困难; 世界复杂,业务多元,提供简单的单元,让业务按照自己的需求自己组合,这才是终极形态。 真实世界也是这样,原子、质子,组合成万物。 小结 简单 + 组合才是最强的; cmd + 管道 + 文本,让单个命令足够简单清晰,而命令的编排又可以创造无限扩展的能力; 单元简单,才容易组合;单元复杂,组合困难; 提供简单的单元,让业务按照自己的需求自己组合,这才是终极形态。

May 9, 2026 · 1 min · 29 words · 文叨叨

高可用要么不做,要么做完善:fence 是必备

稳定性相关的,要么不做,要么就要完善,要不然做了高可用,反而造成雪崩。高可用必备 fence,fence 就是 ipmi 强制关机,这是最稳妥的方式。 核心:高可用要么不做,要么做完善 稳定性相关的,要么不做,要么就要完善。 要不然做了高可用,反而造成雪崩。 比如一个 hyper 负载很高,本来可能 hyper 上个别业务有影响,结果触发灾难迁移,然后又迁移失败,反而导致多业务受影响,且恢复 2 天。哪个客户能接受! 比如我们的灾难,触发了,迁移到一半又发现空间不足,迁移失败。就是不完整嘛,能不能迁移,要提前规划好,不能迁移就不要动 hyper。告警通知给用户,用户自己处理。 半死不活的状态,客户更希望给他们通知,他们自己处理,而不是我们下线 hyper。 灾难当然复杂,尤其是半死不活的状态,就是薛定谔的猫,这种确实不好处理。 所以更需要多方参照开源系统的设计,看他们背后的考量和策略,结合我们自己的业务来系统性的设计,而不是打补丁式的缝缝补补。 比如增加超时时间,比如增加个 ping 检查,比如增加个本地自检。 高可用必备 fence fence 就是 ipmi 强制关机,这就是最稳妥的方式。 其他方式都会有各种问题,且会带来其他复杂性。 比如自检,节点都有异常了,怎么保证自检能正常运行? 外部观察是通过网络观察的,所有网络如果有问题就会判断为有问题,这种情况自检是正常的,两者如何协作? 自检和外部检测是独立的,如果要协作就涉及到网络和协同,这两个本身就很复杂,再加上两个复杂的检测。 如果不协同,保持独立,那么两者的时间差会导致哪些状况?又是复杂场景。 如果采用集中式观察,采用 fence,这是业界主流的做法。 状态,状态,状态 这里面的要素是什么? node 和资源,node 和资源的状态,非常非常重要。 要通过状态在分布式环境下协同控制。 状态,状态,状态。一定要围绕最终状态,一定要维护好最终状态,一定要确保最终状态的一致性、正确性。 然后其他机制都围绕最终状态设计。 这样通过最终状态,来解耦各种前因后果,解耦异常事件和恢复处理。 为什么这两个解耦非常重要? 因为异常事件千千万,不同的异常的恢复处理也不同,形成千丝万缕,一个网状复杂性。 但是通过状态来解耦后,各种异常回归到状态,状态再对应到恢复处理。这是一个港股,两头各种线,线性复杂性。 这其实又对应到 k8s 的声明式哲学。 在当前系统已有架构下,核心状态就是两个维度: node,状态:是否正常; vm,状态:属于哪个 node,是否应该运行。 node 可能无法期望,因为硬件可能客观因素故障。vm 是有期望状态的,比如期望是运行,实际是关机的。 小结 高可用要么不做,要么做完善,要不然做了高可用反而造成雪崩; 能不能迁移要提前规划好,不能迁移就不要动 hyper; 高可用必备 fence,fence 就是 ipmi 强制关机,这是最稳妥的方式; 状态,状态,状态,一定要围绕最终状态,维护好最终状态的一致性、正确性; 通过最终状态来解耦异常事件和恢复处理,把网状复杂性变成线性复杂性。

May 3, 2026 · 1 min · 70 words · 文叨叨

状态机:围绕状态,而不是围绕具体流程

状态机的核心是围绕状态,而不是围绕具体流程。当前状态、输入、转移状态,就是围绕着三要素设计的。它可以把一个超长流程、超多条件判断的流程,解耦为一个个独立的状态。 核心:围绕状态,而不是围绕具体流程 状态机的核心是围绕状态,而不是围绕具体流程。 当前状态、输入、转移状态,就是围绕着三要素设计的。 这个模式,可以把一个超长流程、超多条件判断的流程,解耦为一个个独立的状态。 将时序和条件判断,解耦为独立的状态,状态受输入/事件的影响,转移状态。 是一种分治,是一种将大的易变的脆弱流程,转换为一个个独立的、不易变的状态处理,也就是状态机。 状态机不是一种算法,而是一种通过"降维拆解"来控制复杂度的设计哲学。 它把混沌庞大的系统,解耦成了一个个简单、确定、封闭的小状态。 状态机不是循环,只是表现像循环。 我现在是 State A。如果来信号 X,我就变 State B;如果来信号 Y,我就变 State C。(循环只是状态切换过程中产生的一种表现形式) 状态机的核心四大要素 状态(State):系统当前所处的情境(如"关门"、“开门”); 输入(Input):触发变化的外部信号(如"按下开门按钮"); 转移(Transition):从一个状态变到另一个状态的过程; 输出(Output):系统在特定状态或转移时执行的动作(如"响铃")。 状态机和循环流程的区别 状态机和循环流程看起来确实非常像,但它们在设计思想、状态控制权以及处理复杂度的能力上有本质的区别: 维度 普通循环流程(Loop / Flowchart) 状态机(State Machine) 控制中心 代码行/指令指针(PC):按预设顺序一行行往下走 “状态"本身:当前处于什么状态,直接决定能响应什么信号 执行逻辑 主控驱动:A -> B -> C -> 再循环,路线基本固定 事件驱动:没有固定的下一步,完全看此时来了什么输入 分支处理 靠大量的 if-else 或 switch 嵌套,条件极易膨胀 状态独立隔离,只处理当前状态下合法的事件,拒绝非法输入 记忆能力 依赖全局变量或多层局部变量来标记"走到哪一步了” 状态本身就是记忆,当前处于哪个状态,就代表了过去发生的一切 状态机也是解耦 状态机解耦的是:“行为(做什么)“与"时序/条件判断(什么时候能做)”。 状态机通过把系统拆成"状态"和"事件”,实现了以下四个维度的彻底解耦: 1. 解耦:逻辑与当前上下文(消灭全局 Flag 泥潭) 传统代码(紧耦合):为了记住系统"走到哪一步了",你不得不定义一堆全局变量或布尔标志位(Flag): if is_logged_in and not is_paying and is_verified and has_coupon: # 你必须在每一个逻辑里,手动判断这一大串 Flag 的组合 问题:变量越多,组合情况成指数级爆炸(2^n),你根本记不清 is_paying 为 True 时,has_coupon 能不能为 True。 ...

May 2, 2026 · 2 min · 220 words · 文叨叨

正向流程容易,逆向回滚才是灾难

当流程跨越了多个服务,一旦中途某一步失败,如何把已经扭转的状态干净地恢复原状,是软件架构里最重的活。优秀的架构师会尽量避免物理回滚,把流程设计为只能向前。 核心:正向流程容易,逆向回滚才是灾难 当流程跨越了多个服务(分布式事务)或者多个本地对象时,一旦中途某一步失败,如何把已经扭转的状态、已经扣减的库存、已经划走的资金"干净地"恢复原状,是软件架构里最重的活。 一个设计大智慧:在真正的高并发高可用设计中,优秀的架构师会尽量避免"物理回滚"。 他们会把流程设计为"只能向前"。 比如扣款失败了,不是去把前面的流程撤销,而是生成一张"退款单"走新的正向流程。 用正向的业务去解决逆向的错误,这往往是把"面向对象"和"面向流程"结合得最好的艺术。 业务流失败的逆向控制(回滚) 正向流程容易,逆向清理(异常、回滚)才是分布式与复杂系统里的重活。 根据对"时序"和"状态"的处理方式不同,业界演进出了两类完全对立的治理哲学: 1. 命令式哲学:严格的因果追踪(有状态的设计) 传统的工作流(BPMN)或分布式事务(Saga / 2PC)认为"时间过程是可控的"。 编排式 Saga:由中央指挥官严格记录每一步的进度(Context),成功则前行,失败则按记忆逆向调用补偿接口; 两阶段提交 (2PC):基础设施层强锁定,追求强一致性。 哲学本质:有状态的设计(Stateful)。 它极度依赖对时间轨迹的记忆。一旦回滚流程自身在网络闪断中崩溃,系统必须设计更复杂的"状态来套娃状态",容易陷入脆弱的死锁。 2. 声明式哲学:消除流程的对账自愈(无状态的设计) 以 Kubernetes (K8s) 为代表的现代云原生架构,彻底推翻了"时间流水线",转向了【声明式 API + 状态机 + 最终一致性】。 无回滚流:抛弃"先做 A、再做 B、失败了执行 C"的因果控制,直接定义对象的期望状态 (Desired State); 控制循环 (Control Loop):无数个健忘但勤奋的 Controller 每一秒都在比对"期望状态"与"实际状态",计算出 Diff 并发起正向调节(Reconcile)。 哲学本质:无状态的设计(Stateless)。 控制器没有记忆,每一次触发都是纯函数的计算。它用无限重复的"空间对账",消灭了"时间回滚"的痛苦。 声明式架构(K8s 风格)的边界与盲区 K8s 的"空间切分(容器化)+ 状态治理(etcd)+ 时间治理(控制循环)“在基础设施编排上近乎神作,但这套"无状态对账"并非万灵药。 声明式对账的天然盲区 当业务打破了"结果可定义、过程不重要、失败可重试、状态可覆盖"的假设时,必须坚决退回到命令式流程: 强时序/因果链业务(如银行转账):必须具备完美的隔离性,无法容忍最终一致性走向一致期间的中间不稳定态(防止超卖或负资产); 非幂等/不可逆操作(如发短信、物理扣费):发生过就留下痕迹,无法通过"覆盖快照"来抹去,对账重试会导致严重的业务资损; 高频短暂任务(如 Serverless / 50ms 函数):控制循环的对账代价(数百毫秒)远大于任务执行时间。 动作/流程的"对象化"误区 论点确认:转账、通知、审批、扣费,完全可以(且应该)被抽象为对象(如 DDD 中的交易单实体); 关键分野:即使将它们抽象为了对象,它们也是"事务型对象”,而非 K8s 那样的"资源型对象"。 资源型对象 (Pod/Disk):行为是"修剪",多了可以删,少了可以建,允许短暂混乱; 事务型对象 (转账单/发票):行为是"追加流水",发生了就永远存在,必须用流水线一样严密的命令式流程(Saga)去呵护它的状态机跃迁。 四种回滚方案 1. 事务补偿模式(Saga Pattern) Saga 是分布式事务中最常用的"最终一致性"方案。它的核心思想是:不追求过程中的完美隔离,允许中间状态可见,但如果失败,就按原路反向执行"补偿操作"。 ...

April 26, 2026 · 1 min · 164 words · 文叨叨

面向过程还是面向对象?对象是骨架,流程是血肉

流程是易变的、脆弱的,对象是稳定的、高内聚的。面向对象的流程是分治,将易变的流程拆分成多个稳定的对象,对象之间面向接口协作,复杂性拆分成局部复杂性。 现象 我们 iaas 很多代码都是面向过程,而架构上也有很多是面向过程,而没有面向对象。 比如创建主机,存储资源没有抽象,全都是在过程中各种条件判断来处理的。其实需要的是一个存储系统、存储对象。 创建备份也是一样,创建备份和存储备份耦合在一起,没有区分。 存储备份完全是面向过程,写了两个流程: rsync 上传到 snapshot; snapshot 之间复制副本。 其实创建备份和存储备份完全是独立的两个事务,而且是差别很大的两个事务。 很明显,存储应该是一个独立系统,是一个存储对象。 我们需要的是面向对象,过程应该是在对象内的。 核心:对象是骨架,流程是血肉 流程是易变的、脆弱的,编排流程; 对象是稳定的、高内聚的。 最终: 面向过程的流程,是一个大的、长的、前后依赖的、复杂性指数增加的判断组合; 面向对象的流程,是分治,将易变的、脆弱的,拆分成多个稳定的对象,对象之间面向接口协作,复杂性拆分成局部复杂性,在大流程里是一个线性复杂性,在对象内是常数复杂性。 相比面向过程的流程的指数复杂性,这是数学上可证明的面向对象的优势。 软件工程设计的目标就是 kiss,一切围绕控制复杂性设计。 宏观面向对象,微观面向流程 上层的业务流程确实是面向过程的,但它又不负责具体的业务规则,而是编排。 宏观架构上,要"面向对象":划分好边界,把复杂的业务拆解成一个个职责明确、内聚的对象,以此来抗住需求的变更和系统的规模; 微观执行上,要"面向流程":在 Service 层,清晰、直观地把这些对象串联成一条条业务流,满足用户的实际需求。 对象是骨架,流程是血肉。 面向过程 vs 面向对象 面向过程 优点:逻辑简单直接,符合人类"按步骤做事"的直觉;代码体积小、执行效率高;调试和上手成本低,适合小型程序; 缺点:可维护性差,需求变更时需修改大量相关函数,牵一发动全身;复用性低;扩展性差,难以应对复杂系统的需求迭代。 面向对象 优点:可维护性强,封装使对象内部变化不影响外部,模块化降低耦合;复用性高,继承和多态大幅减少重复代码;扩展性好,新增功能只需定义新类/方法,无需修改原有代码(开闭原则);更贴合复杂问题的本质; 缺点:复杂度高,需设计类的结构、关系,上手门槛高;性能稍低;过度设计风险,简单问题可能被复杂化。 业务的悖论:用"空间对象"承载"时间流程" “业务的本质是流程(时间推进),但为什么现代软件工程推崇对象(空间治理)?” 这是软件设计中最迷人的核心矛盾。 我们通过 DDD(领域驱动设计)思想完成了这两者的空间与时间平衡: 宏观架构上(空间划分)——面向对象:划分出干净、内聚的领域模型(如用户、商品、订单)。这些对象是系统的"零件",负责锁住状态与核心业务规则; 微观执行上(时间推进)——面向流程:在应用服务层,用直观的面向过程逻辑,将这些对象串联成一条条满足用户最终需求的"业务流"。 架构大智慧:对象是骨架(空间),流程是血肉(时间)。 好的代码,是用面向对象的画笔,画出面向过程的蓝图。 为什么业务是流,我们却要用对象? 为了应对"变化"和"规模"。 如果一个业务流程永远不改,面向过程绝对是最高效的。但现实是,业务流程会无限膨胀和变化。 面向过程的灾难:假设一个"下单流程",起初只有 3 步。随着业务发展,变成了 30 步,里面充斥着各种 if-else。最后这个流程长达几千行,没人敢改,因为改了第 5 步,可能会调断第 28 步的数据; 面向对象的解法:面向对象不是消灭流程,而是把流程中的"参与者(实体)“抽离出来,让它们自己负责自己的事情。下单流程不再是一条黑盒管道,而是变成了用户对象、商品对象、营销引擎对象、库存对象之间的交互。 用一个比喻来理解: 面向过程是"流水线工厂”:传送带上一条道走到黑,一旦产品要加个新功能,整条流水线可能都要停工重组; 面向对象是"现代企业组织":划分为财务部、销售部、研发部。公司的业务依然是一个个流程,但流程是由各个部门通过协作完成的。如果财务流程变了,只需要财务部内部调整,销售部根本不需要知道。 小结 流程是易变的、脆弱的,对象是稳定的、高内聚的; 面向对象的流程是分治,复杂性拆分成局部复杂性; 面向过程的流程是指数复杂性,面向对象是线性复杂性; 宏观架构上要面向对象,微观执行上要面向流程; 对象是骨架,流程是血肉; 用面向对象的画笔,画出面向过程的蓝图。

April 25, 2026 · 1 min · 74 words · 文叨叨