反模式

[TOC]

反模式

Intro

軟體工程中,一個反面模式(anti-pattern或antipattern)指的是在實踐中經常出現但又低效或是有待優化的設計模式[1][2],是用來解決問題的帶有共同性的不良方法。它們已經經過研究並分類,以防止日後重蹈覆轍,並能在研發尚未投產的系統時辨認出來。

神仙大类

神仙大类,又称God Class, Blob Class等,是一类拥有太多属性和方法(比如超过20个)的模式,它能处理的事务涉及方方面面(比如员工类,涉及工资计算、税务计算、入职离职、数据库读写、请假报销等等),它所占用的代码行数从数百到上万行。“神仙大类”是KISS原则和SRP原则的反模式。我自己虽然未曾有幸见识过上万行的大类(倒是听说过不少),但确实见过超过2000行的只有一个main函数的大类。针对这种大类,我是该献上我的膝盖还是我的口水呢?我想刚开始是膝盖,因为不明觉历,后来铁定就是口水了,因为我不幸接手并负责维护它,每次只要一改它,在上线的时候我都会心惊胆战,晚上睡不好觉。

当然,对于一锤子买卖,如不再需要维护的代码,或者需求绝对不会变更的代码,这种神仙大类就让它逍遥去吧。唯一的不足之处就是,不能为下一个项目提供可复用的单元,只能看着年龄在增长,技能和效率却没有什么提升。普通程序员只是年复一年地完成日常的业务需求,没什么代码复用可言。好的程序员却可以在完成日常业务开发的同时,不断地总结并丰富自己的代码工具箱,代码复用率很高,真正需要写的业务代码也写得非常少,有时只需要做一下配置,就可以完成类似的事情。

神仙大类的拥趸者说,你看,我一个大功能一个大类就搞定了,一个大类就一个文件,如果按照你的那套所谓的SRP/KISS,至少要不下20个类的小文件,我不也算符合简单化原则了,一个文件还不算简单嘛。乍一听,我竟无言以对,如果你一个类文件里面也是分了各种层次,做了各种不同抽象设计的话,好像不无道理。只是,这种情况下,一个文件里面那么多功能,如果想重用其中的一个,咋办呢?是不是得把整个大类照单全收,还是把要重用的那个小函数拷贝一份出来?只是同一段代码一旦重复拷贝,就违反了DRYDont Repeat Yourself )干燥不渗水原则,而被 WETWrite Everything Twice)湿漉漉反模式给狠狠地砸脸了。

神仙大类,本质上就是一个“集大成”的大胖子,在这个以瘦为美以减肥为时尚的今天,确实不受欢迎。你虽然不能像林丹那样拥有8块完美腹肌,但是你可以让你的代码做到啊,只需要远离神仙大类,或者使用“人挡杀人佛挡杀佛”的重构“拆”字诀把遇到的神仙大类就地拆成大约8个各司其职的小类就可以了。

黄金大锤

黄金大锤,Golden Hammer,指使用相同的工具、产品或技术,解决几乎所有的问题。如果你只有一个关系型数据库,那么任何问题都看上去是其中的一张关系表。或者学习了设计模式后就开始肆无忌惮地到处用设计模式,就连最简单的打印一个Hello Word的入门程序也都能用上几个设计模式的话,那就是把设计模式当成黄金大锤了。

有一种“面向接口编程”滥用的反模式,就是“一个服务一个接口”。这种常见的就是所有的服务类都有一个所谓的XxxServiceXxxServiceImpl,前者是一个接口,后者是对应的惟一实现。问题的关键在于,这种XxxServiceXxxServiceImpl竟然一一对应,也就是说一个XxxService其实只有一个XxxServiceImpl与其对应。这如果不是对“面向接口编程”的一种曲解与滥用,那就是“夸夸其谈的未来性(Speculative Generality)”的代码坏味。“接口”在面向对象的设计中,是属于抽象层面的东西,那什么时候需要抽象呢,一定是两种及以上事物拥有一些共同特征时,才能形成抽象(自底向上);或者从高层定义一些抽象特征,由两种或以上事物来体现这个特征,这种抽象才有意义(自顶向下)。比如光喊我一个人吃饭,你根本不需要抽象,喊我的名字我就来了(我的思路是,吃饭不积极肯定有问题),但是我跟很多男的在一起的时候,你喊:“IT男们,走啰”,我们就一起过来了。那么这种“IT男们”就是一种抽象,只当有两个以上的实体的时候,这种抽象才有必要和更有意义。

在数据类型使用方面往往也有类似的锤子问题。比如涉及到List的统统都是ArrayList,涉及到Map那就都是HashMap了,其它类型那就统统String,好像其它的都不存在了一样,这就相当于把StringArrayListHashMap当成了处理全部数据类型的黄金大锤了。

我曾见识过一个根据电话号码段查找归属地的实现,堪称经典。原始实现是,将数据库包含起始号码、结束号码以及归属地市几个字段的表中所有记录,按起始号码排序后一次性地读入到程序内存中,然后每次查找特定号码的归属地时,在数据结构中顺序查找比对,使用的数据结构是ArrayList<HashMap<String, String»的模式。这个在数据量较小时,好像不是什么问题,因此也在线上欢快地运行了若干年。直到表记录达到十万多条,经常出现程序加载缓慢导致发布经常性失败,或者查找归属地十分缓慢的性能问题时,深藏了那么多年的实现问题才浮出水面。有一个反模式叫做“过早优化”,还有一个兄弟反模式叫做“过晚优化”。这个案例,不只是“黄金大锤”的反模式,还是“过晚优化”的反模式。这个实现我后来review了一下,首先是十多万条记录有太多零星的单个号码成一个号段的记录,实际上可以归并到三万多条左右记录;其次是电话号码不要使用String,转用Long来表示;再次是可以使用TreeMap来完成快速定位查找而不是List顺序查找,具体实现可以参考https://gist.github.com/bingoohuang/5916691

在阅读代码时,还经常会看到这种函数的入参和返回的类型都是MapOMG感觉就是一个黑洞,没有任何语义,只知道是一个大麻袋,里面是什么,深不可测。比如下面这个activeVcher函数,短短几行代码中里面就出现了4Map,我想写这个代码的人一定是丐帮中的四袋弟子。当然这也怪我当初刚入道JAVA时留下的一个“债务”,没想到6年以后,债务依然还在向前滚动。当年我还不知道代码可读性,更不知道POJO的意义,以及语义化的涵义,只是感觉Map好灵活好喜欢。只是今日回头一看,竟然有种在浓浓的雾霾中看不清前路的感觉,看到Map摸不清里面到底兜了些什么,套句流行的话说“雾是String的浓,霾是Map的厚”。

ref

  1. https://blog.csdn.net/jiangpingjiangping/article/details/78067595
  2. AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis”,
  3. “Antipatterns: Identification, Refactoring, and Management”,
  4. “J2EE AntiPatterns
  5. https://zh.wikipedia.org/wiki/%E5%8F%8D%E9%9D%A2%E6%A8%A1%E5%BC%8F
  6. https://web.archive.org/web/20060412123350/http://www.antipatterns.com/briefing/

Powered by Jekyll and Theme by solid

本站总访问量