反模式:神仙大类和黄金大锤

数学中有正数和负数

物理学有『物质』和『反物质』的存在

武侠小说中有九阳神功也有九阴真经

生活中有婚姻也有出轨

......

事物总是充满这种相互矛盾而统一的有趣现象。

对于GoF提出的23种设计模式,是否也有反模式呢?答案是显而易见的。

一个设计模式在特定的场合下是积极并且显现优势的,但是在偏离最佳适合场景下,它本身就会转变为了一个反模式,从而导致不良的影响,就像现实世界中没有所谓的纯粹的好人或者坏人一样。

反模式

Andrew Koenig在1995年首创了anti-pattern这个词,三年后anti-pattern因《AntiPatterns》这本书而获得普及,而它的使用也从软件设计领域逐渐扩展到了日常的社会互动中。

反模式其实体现的是一种积极反思的行为,通过对不断出现,糟糕透顶的解决方案反思之后的深刻总结。让我们能够从错误或者失败中学习提高,避免犯相同的或者类似的问题,提升效率。理解了反模式,有助于我们在实际工作中预防或者改正它们。

有一篇文章,讲到用了5种设计模式来完成一个Hello Word的打印,使用关键字「write Hello Word with design patterns」谷歌一下就能找到(请使用正确的姿势访问谷歌),甚是有趣,然而有趣的背后反思,却是『为了设计模式而设计模式』,成了一种典型的反模式「为了XX而XX」。

从图书网站上,也可以搜索到一些反模式的书籍,比如测试反模式,SQL反模式,Python反模式等,连最近很火的微服务也有了反模式,这些书中都总结了一些最差实践,让我们学习并避免之。

反模式有很多种,我这里想给大家介绍其中的两种,分别是神仙大类和黄金大锤,我这里把他们比喻成倚天剑和屠龙刀,意思是这种大杀器一般要谨慎使用,因为威力太大,用得不好的话,就极有可能伤及自身,或者没有伤到自身,伤到边上无辜的花花草草也是不好的。

神仙大类

神仙大类,又称God Class, Blob Class等,就是一个类拥有太多属性和方法(比如超过20个),它能处理的事务涉及方方面面(比如员工类,涉及工资计算、税务计算、入职离职、数据库读写、请假报销等等),它所占用的代码行数从数百到上万行。

「神仙大类」是KISS原则和SRP原则的反模式。

我自己虽然未曾有幸见识过上万行的大类(倒是听说过不少),但是确实亲自见证过超过2000行的只有一个main函数的大类。

针对这种大类,我是该献上我的膝盖还是我的口水呢,我想刚开始是膝盖,因为不觉明历,后来铁定就是WTF口水了,因为我不幸接手了并负责维护它,搞得每次只要一改它,在上线的时候我都会心惊胆战,晚上睡不好觉。

当然,对于一锤子买卖,不再需要维护的代码,或者需求绝对不会变更的代码,这种神仙大类就让它逍遥去吧。

唯一的不足之处就是,不能为下一个项目提供可复用的单元,只能看着年龄在增长,技能和效率却没有什么提升了。

普通程序员只是年复一年地完成日常的业务需求,没什么代码复用可言,好的程序员却可以在完成日常业务开发的同时,不断地总结并丰富自己的代码工具箱,代码复用率很高,真正需要写的业务代码也写得非常少,有时候只需要做一下配置,就可以完成类似的事情。

有一种声音,就是神仙大类的拥趸说,你看,我一个大功能就一个大类搞定了,一个大类就一个文件,如果按照你的那套所谓的SRP/KISS,至少要不下20个类的小文件了,我不也算符合简单化原则了啊,一个文件还不算简单嘛。

乍一听,我竟无言以对,如果你一个类文件里面也是分了各种层次,做了各种不同抽象设计的话,好像也不无道理。只是,这种情况下,一个文件里面那么多功能,如果想重用其中的一个,咋个办呢,是不是得把整个大类照单全收,还是把要重用的那个小函数拷贝一份出来?

只是同一段代码一旦重复拷贝,就违反了DRY(Don’t Repeat Yourself )干燥不惨水原则,而被 WET(Write Everything Twice)湿漉漉反模式给恨恨地砸脸了。

神仙大类,本质上就是一个『集大成』的大胖子,在这个以瘦为美以减肥为时尚的今天,确实不受欢迎,感情好的时候说胖子是潜力股还亲切地喊小胖胖,感情破裂了转口就骂人家死胖子了。

你虽然不能像林丹那样拥有拥有8块完美腹肌,但是你可以让你的代码做到啊,只需要远离神仙大类,或者使用『人挡杀人佛挡杀佛』的重构「拆」字诀把遇到的神仙大类就地拆成大约8个各司其职的小类就可以了。

黄金大锤

黄金大锤,Golden Hammer,指使用相同的工具、产品或技术,解决几乎所有的问题。

如果你只有一个关系型数据库,那么任何问题都看上去是其中的一张关系表。或者我们学习了设计模式,然后就开始肆无忌惮地到处用设计模式,就连最简单的打印一个Hello World的入门程序也都能用上几个设计模式的话,那就是把设计模式当成黄金大锤了。

有一种『面向接口编程』滥用的反模式,就是『一个服务一个接口』。这种常见的就是所有的服务类都有一个所谓的XxxService及XxxServiceImpl,前者是一个接口,后者是对应的惟一实现。

问题的关键在于,这种XxxService和XxxServiceImpl竟然一一对应,也就是说一个XxxService其实只有一个XxxServiceImpl与其对应。

这如果不是对『面向接口编程』的一种曲解与滥用,那就是『夸夸其谈的未来性(Speculative Generality)』的代码坏味。

『接口』在面向对象的设计中,是属于抽象层面的东西,那什么时候需要抽象呢?一定是两种及以上事物拥有共同的一些特征时,才能形成抽象(自底向上),或者从高层定义一些抽象特征,由两种或以上事物来体现这个特州,这种抽象才有意义(自顶向下)。

比如光喊我一个人吃饭,你根本不需要抽象,喊我的名字我就来了(我的思路是,吃饭不积极肯定有问题?),但是我跟很多男的在一起的时候,你喊『IT男们,走啰』,我们就一起过来了。

那么这种『IT男们』就是一种抽象,只当有两个以上的实体的时候,这种抽象才有必要和更有意义。

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

我曾见识过一个根据电话号码段查找归属地的实现,可以说是堪称经典。

原始实现是,将数据库包含起始号码、结束号码以及归属地市几个字段的表中所有记录,按起始号码排序后一次性地读入到程序内存中,然后每次查找特定号码的归属地时,在数据结构中顺序查找比对,使用的数据结构是ArrayList<HashMap<String, String>>的模式。

这个在数据量较小时,好像不是什么问题,因此也在线上欢快地运行了那么若干年。直到表记录达到十万多条时,经常出现程序加载缓慢导致发布经常性失败,或者查找归属地十分缓慢的性能问题时,深藏了那么多年的实现问题才浮出水面。

有一个反模式叫做『过早优化』,还有一个兄弟反模式叫做『过晚优化』。这个案例,不只是『黄金大锤』的反模式,还是『过晚优化』的反模式。

这个实现我后来Review了一下,首先是十多万条记录有太多零星的单个号码成一个号段的记录,实际上可以归并到三万多条记录左右,其次是电话号码不要使用String转用Long来表示,再次是可以使用TreeMap来完成快速定位查找而不是List顺序查找,具体实现可以参考https://gist.github.com/bingoohuang/5916691。

在阅读代码时,还经常会看到这种函数的入参和返回的类型都是Map,OMG感觉就是一个黑洞,没有任何语义,只知道是一个大麻袋,里面是什么,深不可测。

比如下面这个activeVcher函数,短短几行代码中里面就出现了4处Map,我想写这个代码的人一定是丐帮中的四袋弟子。

当然这也怪我当初刚入道JAVA时留下的一个『债务』,没想到6年以后,债务依然还在向前滚动。

当年我还不知道代码可读性,更不知道POJO的意义,以及语义化的涵义,只是感觉Map好灵活好喜欢,只是今日回头一看,竟然有种在浓浓的雾霾中看不清前路的感觉,看到Map好懵逼,浑然不觉里面到底兜了些什么,套句流行的话说『雾是String的浓,霾是Map的厚』。

总结

入了设计模式的门,又摸了一下反模式的皮毛,到此我们终于大概摸清了模式这头大象比较完整的轮廓了,大家可以在尝试骑象远行了。

在日常工作中,我们也可以尝试把一些常见的写法进行归纳总结,好的命名成XXX模式,不好的就命名成YYY反模式。只要坚持不懈,最终我们可以超越模式本身,进一步提升认识,跨越到『码可码、非常码,道可道、非常道』的境界之中了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值