刚入行的时候,我就知道“高内聚、低耦合”这六个字,面试时背得滚瓜烂熟。但说实话,真正理解它是什么、为什么要这样做,是在写了三年代码之后。
不懂的时候,以为只是抽象的概念
前两年写代码,我对这六个字的理解停留在字面意思上:高内聚就是把相关的东西放一起,低耦合就是少依赖别人。听起来很简单对吧?
那时候写代码的思路很简单:产品提了需求,我就在现有的Service里加方法,或者在Controller里直接处理新逻辑。Spring Boot的自动装配太方便了,@Autowired一加,哪里需要点哪里。一个Service类从最初的几百行,慢慢膨胀到上千行,订单逻辑、用户逻辑、库存逻辑、物流逻辑全搅在一起。
当时觉得这样没问题啊,代码能跑,功能正常,单元测试覆盖率也还可以。直到有一次产品需求变更,要调整订单状态的流转逻辑。我花了整整三天改代码,改完这一块,发现另一块逻辑不工作了。修好了那一块,又发现报表数据不对了。牵一发而动全身,改得我头皮发麻。
那一刻我才意识到:我的代码出了问题。可是问题在哪?我翻出设计模式的书,重新看到那六个字——高内聚、低耦合。之前是背下来的,这次好像有点感觉了,但又说不太清楚。
一个重构让我真正懂了
真正让我开窍的是一次重构。当时要把一个复杂的订单处理模块拆开,我尝试按职责把原来的大Service拆成几个独立的类:
OrderValidator——负责订单校验
OrderPriceCalculator——负责价格计算
OrderStatusManager——负责状态流转
OrderRepository——负责数据持久化
拆完之后发现,每个类做的事情变得特别清晰。改价格计算逻辑的时候,我只需要看OrderPriceCalculator这一个类,不用担心影响到状态流转。因为OrderStatusManager只依赖OrderRepository和几个外部接口的抽象,不依赖具体的价格计算实现。
这就是“高内聚”——一个类只做一类事,相关的数据和方法都封装在内部,对外只暴露必要的方法。产品改需求了,我能快速定位到要改哪个模块,而且改动范围可控,不怕改出连锁反应。也正因为模块之间依赖的是接口而不是具体实现,改价格算法时只需要换一个实现类注入进去就行,完全不用动OrderStatusManager的代码——这就是“低耦合”。
那一刻我明白了一件事:我以前是在一个巨大的Service里用“注释分区”假装模块化,但编译器根本不知道这些分区,改动时所有代码都在同一个文件里,彼此之间没有任何隔离。而真正的模块化,是用类和接口的边界做隔离。
理解后的三个判断标准
现在我看一个项目的代码质量,会用三个标准判断它是否符合高内聚低耦合的原则:
第一,改动一个功能时,需要改几个文件?如果需求变更是家常便饭,每次改动都涉及七八个甚至十几个文件,那耦合度一定高了。好的设计应该让90%的需求变更只影响一到两个模块。
第二,这个类我能用一句话说清楚它是干什么的吗?如果你只能用“它负责订单相关的所有事”来描述一个类,那它的内聚度一定不够。高内聚的类应该能用一句话精准描述它的单一职责。
第三,新同事上手改代码,敢不敢只改一个文件就提交?如果新同事每次改完都要问“我改了这里,会不会影响那里”,说明模块之间的边界不够清晰,耦合太紧。
这六个字到底在说什么
说到底,“高内聚、低耦合”这套原则,追求的不是代码本身的某种美学,而是——当变化发生时,你需要付出的代价有多小。
商业世界里的需求永远在变,一个好的软件设计,不是把未来所有变化都预测到,而是让未来发生的变化尽可能被限制在局部,不会引起全局的连锁反应。高内聚保证了这个“局部”足够小且足够完整,低耦合保证了这些“局部”之间不会互相传染。
现在回过头看,那六个字从来不是背出来就完事的。它需要在一次次重构中、在一次次被需求变更折磨之后,才能真正刻进代码里。如果你现在还觉得它只是一句口号,可能只是因为——还没遇到那个让你头皮发麻的需求变更。