3、代码坏味道(下):全局数据、可变数据、发散式变化、霰弹式修改、依恋情结
好,咱们接着聊代码坏味道。上一章讲了几个常见的,这一章我挑了几个在实际项目中特别容易踩坑的。说实话,这些坏味道往往不是一眼就能看出来的,它们藏得比较深,但破坏力一点都不小。
全局数据:看似方便,实则毒药
全局数据,说白了就是谁都能改、谁都能读的数据。我见过不少项目,一开始图省事,把配置信息、用户状态、甚至数据库连接都扔到全局变量里。嗯,刚开始确实爽,哪里需要哪里取,不用传参,代码看起来也简洁。
但问题来了——你根本不知道这个数据什么时候被谁改过。我曾经在一个遗留系统里排查过一个bug,某个全局计数器莫名其妙地跳变。我花了整整两天,最后发现是一个第三方库在某个回调里偷偷改了它。你想想看,这种问题有多难定位。
怎么解决?其实很简单:
- 能用局部变量就别用全局的
- 实在需要共享数据,用单例模式或者依赖注入
- 如果必须用全局,至少加个读写锁,别让谁都能随便改
可变数据:改着改着就乱了
可变数据跟全局数据有点像,但范围更广。它指的是那些在生命周期内可以被随意修改的数据。比如一个对象的属性,你传进去之后,方法内部把它改了,调用方还不知道。
我记得有一次,团队里一个同事写了个工具类,里面有个缓存Map。他本意是好的,想提高性能。但问题是,这个Map是静态的,而且没有清理机制。结果呢?测试环境跑得好好的,一到生产环境,数据越来越多,最后内存溢出了。更坑的是,因为Map里的数据是共享的,不同请求之间还会互相干扰,出现了一些诡异的“灵异bug”。
你可能会说,这样性能会不会受影响?说实话,对于绝大多数业务系统来说,这点性能损耗完全可以忽略。真正影响性能的,往往是那些不合理的算法和数据库查询,而不是不可变对象。
发散式变化:一个类,多个理由改它
发散式变化,说白了就是一个类因为不同的原因被频繁修改。比如一个订单类,今天因为支付逻辑要改它,明天因为物流逻辑要改它,后天因为优惠券逻辑还要改它。你想想看,这个类得有多“忙”?
为什么会这样?因为职责没有分离。一个类承担了太多不同的责任。我见过最夸张的一个类,里面既有数据库操作,又有业务逻辑,还有UI渲染。嗯,这种类我一般称之为“上帝类”。
怎么判断一个类是不是有发散式变化的问题?很简单:
- 看它的修改历史。如果每次需求变更都要改它,那八成有问题
- 看它的方法数量。如果一个类有超过20个public方法,建议拆一拆
- 看它的依赖。如果它依赖了太多不同的模块,说明它耦合太重
霰弹式修改:改一个功能,要改N个地方
霰弹式修改跟发散式变化正好相反。发散式变化是一个类被频繁修改,霰弹式修改是一个功能要改很多个类。比如你要改一个支付流程,结果发现要改订单类、用户类、支付类、日志类、通知类……改完一个漏一个,上线就出问题。
我在项目中遇到过这种情况。有一次改一个优惠券的过期逻辑,我改了5个文件,结果还是漏了一个。那个漏掉的地方在定时任务里,优惠券过期后没有及时释放库存。上线后,用户领不了新优惠券,客服电话被打爆了。
霰弹式修改的根源是什么?是逻辑分散。同一个业务逻辑,被分散到了多个类里。比如优惠券的过期逻辑,可能在订单类里有一份,在用户类里有一份,在定时任务里还有一份。改的时候,你得把所有地方都找到,都改对。
依恋情结:一个方法,总想用别人的数据
依恋情结,说的是一个方法对另一个类的数据特别“依恋”。比如一个方法,明明属于A类,但里面大量使用了B类的getter和setter。说白了,这个方法应该待在B类里,而不是A类里。
我记得有一次code review,看到一个工具类里有个方法,里面全是调用订单类的getter。我当时就问:“这个方法为什么不放在订单类里?”写代码的同事愣了一下,说:“习惯了,觉得工具类方便。”嗯,这种习惯其实不太好。
怎么判断一个方法是不是有依恋情结?看它用了多少个其他类的getter。如果一个方法里,调用其他类getter的次数比调用自己类方法的次数还多,那它八成是“身在曹营心在汉”。
你可能会问,那如果这个方法就是需要多个类的数据怎么办?嗯,这时候可以考虑引入一个参数对象,或者用依赖注入的方式,把需要的类传进来。但不管怎么弄,别让一个方法对太多类产生依恋。
知识体系总览
下面这张图,我把这五种坏味道的核心逻辑和重构方向整理了一下。你可以对照着看看,自己项目里有没有类似的问题。
这五种坏味道,说白了都是“耦合”和“内聚”的问题。全局数据和可变数据是数据层面的耦合,发散式变化和霰弹式修改是职责层面的耦合,依恋情结是方法层面的耦合。解决它们的方法也很统一:让每个模块、每个类、每个方法只做一件事,并且做好。
嗯,这一章的内容就到这里。代码坏味道还有很多,但掌握了这几种,你写代码的时候就会多留个心眼。下次改代码之前,先问问自己:这个改动会不会引发霰弹式修改?这个类是不是承担了太多职责?这个数据是不是不该被随便改?养成这个习惯,你的代码质量会慢慢好起来。