软件工程/架构/反模式简介
如果设计模式是好人,那么反模式就是坏人。有时好人也会变成坏人。这种情况在好莱坞电影中会发生,但也会在软件工程中发生。
“金锤”是解决这个问题的一个常见概念:你在一个环境中学会了使用一个工具(金锤),现在因为你对学会使用这个复杂的工具感到自豪,所以突然间你到处都看到了金钉子。
一个很好的例子是单例模式:它非常容易,它是大多数初级软件工程师理解的第一个模式,因此,假设它是好人,他们会在任何可能的场合使用它。然而,单例模式的问题是它违反了信息隐藏。现在,信息隐藏是现代软件工程中的一条金科玉律,只有在有充分理由的情况下才能违反它。仅仅是了解了单例模式并不是一个充分的理由!
在软件工程中,反模式是一种在实践中可能被普遍使用但效率低下和/或适得其反的模式。[1][2]这个词由安德鲁·科尼格在 1995 年创造,[3]灵感来自于四人帮的著作设计模式,这本书在软件领域发展了设计模式的概念。这个词在三年后由反模式一书广泛普及,[4]这本书将这个词的使用范围扩展到软件设计领域之外,进入到一般社会互动中。根据后者的作者,至少需要存在两个关键要素才能正式区分一个实际的反模式与一个简单的坏习惯、坏做法或坏主意
- 一些重复的行动、过程或结构模式,最初看起来是有益的,但最终产生的负面后果多于积极结果,以及
- 存在一个经过重构的解决方案,该解决方案有明确的文档记录,在实际实践中得到验证并且可重复。
通过正式描述重复的错误,人们可以识别导致这些错误重复的力量,并学习如何从这些破坏性模式中重构自己。
为了更好地理解反模式,让我们看几个例子。通过研究这些例子,你可能会识别出你在某个时间点可能犯过的一些违反软件工程原则的行为。其中一些反模式有非常有趣的名字。
我们已经讨论过这个问题:你立即理解的第一个模式,并且你大量使用它。但要注意它违反了信息隐藏。因此,简单的规则是:如果存在疑问,就不要使用它。我的经验是,项目越大,出现的单例模式就越多。
如何检测单例模式?查看类图。所有引用自身(或其基类)的类都是潜在的单例模式。如果你想摆脱它们,Kerievsky 向你展示了治疗这种疾病的药物。 [5]
尽管曾经非常流行,但在现代面向对象的语言中,已经没有功能分解的空间了。它是 C 或 Pascal 等过程语言的残留。通常它表示被集成到新项目或迁移的旧软件。
这种反模式以三种方式表现出来:类名听起来像函数名(例如 CalculateInterest)。或者类只有一个动作,也就是说它们只做一件事。或者所有类属性都是私有的(这是可以的),但它们只在类中使用。
人们喜欢这种反模式,因为它有一个有趣的名字。它指的是那些短暂出现然后消失得无影无踪的类。要么没有人知道它们到底做什么,要么它们的 功能非常有限。通常它们是 不需要的,或者可以被其他类吸收。
通常通过以’*controller’或’*manager’结尾的类名来识别这种反模式。
通常是“敏捷”方法的结果,在这些方法中,沉思比设计更受重视。
意大利面条代码就像面条一样:非常长。虽然面条很好吃,但代码越长就越不好。
Blob 是一个有很多属性和方法的类。通常它们甚至不相关。你可以使用你最喜欢的代码分析工具来检测这种异味,方法是列出具有大量属性和方法或大量代码行的类。通常将这个类拆分为几个更小的类会有所帮助。
顾名思义,有人从某个地方复制了一些代码到另一个地方。这是复制功能最简单的方法,但由于许多原因应该避免。最简单的解决方案是将代码变成一个方法,或者使用继承。
要检测几乎完全相同的代码,你可以使用 PMD 的代码复制/粘贴检测器之类的工具。 [6][7]
什么是熔岩流?“熔岩流是火山喷发时流出的熔岩,它是在非爆炸性溢流式喷发中形成的。当它停止流动时,熔岩会凝固形成火成岩。”[8]在软件工程中,这意味着代码很古老,没有人触碰过它,而且没有人敢触碰它(永远不要触碰正常工作的类……)。
你可以使用源代码控制系统找到这些类。只需列出那些长时间未被检出和修改的类。
代码异味类似于反模式,但没有那么正式。如果代码有异味,那么这种异味可能是好的(像某些奶酪一样),也可能是坏的,可能表明存在更深层次的问题。肯特·贝克在 20 世纪 90 年代后期提出了这个概念,马丁·福勒在他的著作重构:改善既有代码的设计中使它流行起来。 [9]你可以使用 FindBugs、Checkstyle 或 PMD 等工具来查找异味。通常使用重构来消除异味。马丁·福勒和乔舒亚·凯里夫斯基等人在他们的著作中提供了相应的重构方法。
这种气味与复制粘贴反模式非常相似。你可以使用 PMD 工具复制/粘贴检测器[6]来找出有问题的区域。
长方法
[edit | edit source]与意大利面条式代码反模式相关。具有超过 50 行代码的方法绝对值得怀疑。
过度暴露
[edit | edit source]在当前信息隐藏的维多利亚时代,过度暴露自然是一件坏事。如果一个类有太多方法,或者更糟糕的是,有任何公共属性,那么我们就称之为过度暴露。你可以通过检查类的公共方法来发现这种气味。如果一个类拥有超过 50% 的公共方法,那么它可能不符合信息隐藏策略。
懒惰类
[edit | edit source]让我想起了幽灵反模式:这是一个功能过于简单,以至于没有存在理由的类。尝试将其吸收进另一个类中。
大型类
[edit | edit source]大型类与懒惰类正好相反。你可以通过类似的方法找到它,查找具有太多方法或太多语句的类。通常情况下,一个类不应该拥有超过 30 个方法或超过 400 个语句。此外,具有太多属性的类也可能是大型类。Kerievsky 展示了多种可能的方法来减少这种气味。[5]
实际上是指没有按照代码规范进行编码。请参考 Meyer、MISRA 等规范。
已知反模式
[edit | edit source]存在许多已知的反模式。以下列出了一些反模式及其简要描述,供您参考。
组织反模式
[edit | edit source]- 分析瘫痪:将过多的精力投入到项目的分析阶段。
- 摇钱树:一项利润丰厚的传统产品,往往会导致对新产品的懈怠。
- 委员会设计:由许多贡献者参与设计,但没有统一的愿景的结果。
- 承诺升级:当决策被证明错误时,未能撤回决策。
- 管理者至上:管理风格独断专行,不容许异议。
- 矩阵管理:组织结构缺乏重点,导致忠诚度分散,缺乏方向。
- 道德风险:将决策者与决策的后果隔离开来。
- 蘑菇管理:对员工进行隐瞒和误导(蒙在鼓里,喂食粪便)。
- 烟囱式结构或孤岛式结构:支持数据主要向上和向下流动,但阻碍跨组织交流的结构。
- 供应商锁定:使系统过度依赖外部提供的组件[10]。
项目管理反模式
[edit | edit source]- 死亡行军:每个人都知道项目将会是一场灾难,除了 CEO。然而,真相仍然隐藏,项目被人工维持下去,直到“大爆炸”的零点到来。另一个定义:员工被迫在项目中加班加点,面对不合理的截止日期。
- 群体思维:在群体思维中,群体成员避免提出超出共识思维舒适区的观点。
- 露易丝·莱恩计划:管理层/销售人员过度承诺,导致需要 IT 部门救援,这往往会导致开发匆忙、错误、疲劳、更重要的长期项目被延迟或停止,以及决策者变得更加大胆,重复这种冒险行为。
- 烟雾和镜子:展示尚未实现的功能将如何呈现。
- 软件膨胀:允许系统在后续版本中不断需求更多资源。
- 瀑布模型:一种较旧的软件开发方法,无法充分应对意外变化。
分析反模式
[edit | edit source]- 旁观者冷漠:当需求或设计决策错误时,发现问题的人却无所作为,因为这会影响更多的人。
软件设计反模式
[edit | edit source]
- 抽象反转:没有公开用户所需已实现的功能,因此他们使用更高级别的函数重新实现它。
- 观点模糊:展示一个模型(通常是面向对象分析和设计 (OOAD)),但没有指定其观点。
- 一团乱麻:一个没有可识别结构的系统。
- 数据库作为 IPC:使用数据库作为常规进程间通信的消息队列,而更轻量级的机制更适合。
- 镀金:在额外努力不再增加价值的情况下,继续进行一项任务或项目。
- 内部平台效应:一个系统可定制性极强,以至于成为软件开发平台的糟糕复制品。
- 输入权宜之计:未能指定和实现对可能无效输入的处理。
- 接口膨胀:使接口功能过于强大,以至于难以实现。
- 神奇按钮:在接口代码中直接编码实现逻辑,而不使用抽象。
- 竞争性风险:未能看到不同事件顺序造成的后果。
- 烟囱式系统:几乎无法维护的,由彼此关系不大的组件组成的集合。
面向对象设计反模式
[edit | edit source]- 贫血领域模型:使用没有业务逻辑的领域模型。领域模型中的对象无法保证其在任何时刻的正确性,因为它们的验证和修改逻辑放置在外部(很可能在多个地方)。
- BaseBean:从实用程序类继承功能,而不是委托给它。
- 调用父类:要求子类调用父类的重写方法。
- 圆形-椭圆问题:基于值子类型对变量类型进行子类型化。
- 循环依赖:在对象或软件模块之间引入不必要的直接或间接相互依赖关系。
- 常量接口:使用接口定义常量。
- 上帝对象:将太多功能集中在设计的单个部分(类)中。
- 对象污水池:重用状态不符合重用(可能是隐式)契约的对象。
- 对象狂欢:未能正确封装对象,允许无限制地访问其内部。
- 幽灵:对象唯一的目的是将信息传递给另一个对象。
- 顺序耦合:一个类要求其方法以特定顺序调用。
- 悠悠球问题:由于过度碎片化,难以理解的结构(例如继承)。
- 急于等待:在一个对象的构造函数中触发一个或多个异步事件。
编程反模式
[edit | edit source]- 意外复杂性:在解决方案中引入不必要的复杂性。
- 远程操作:系统中相隔甚远的两个部分之间出现意外交互。
- 盲目信任:缺乏对以下方面的检查:(a)错误修复的正确性;(b)子程序的结果。
- 沉重的锚:保留一个系统中不再有任何用途的部分。
- 繁忙循环:在等待某个事件发生时占用 CPU,通常是通过重复检查而不是消息传递来实现。
- 缓存故障:在错误被纠正后忘记重置错误标志。
- 迷信编程:在不理解原因的情况下使用模式和方法。
- 按异常编码:在识别到每个特殊情况时,添加新的代码来处理它。
- 错误隐藏:在错误消息显示给用户之前捕获它,并且不显示任何内容或显示无意义的消息。
- 硬编码:将有关系统环境的假设嵌入到其实现中。
- 熔岩流:保留不希望的(冗余或质量低的)代码,因为删除它成本太高或具有不可预测的后果[11][12]。
- 循环-开关序列:使用循环语句中的开关来编码一系列顺序步骤。
- 神奇数字:在算法中包含无法解释的数字。
- 神奇字符串:在代码中包含文字字符串,用于比较、事件类型等。
- 软代码:将业务逻辑存储在配置文件中,而不是源代码中[13]。
- 意大利面条式代码:结构难以理解的程序,尤其是由于代码结构的误用造成的。
- 复制粘贴编程:复制(并修改)现有代码,而不是创建通用的解决方案
- 金锤:假设最喜欢的解决方案普遍适用(参见:银弹)
- 不可能因素:假设已知错误发生的可能性很小
- 非我所创(NIH)综合征:倾向于重新发明轮子(未能采用现有的、足够的解决方案)
- 过早优化:过早地为了感知的效率而编码,牺牲了良好的设计、可维护性,有时甚至牺牲了实际的效率
- 排列编程(或“意外编程”):试图通过连续修改代码来找到解决方案,看看是否有效
- 重新发明轮子:未能采用现有的、足够的解决方案
- 重新发明方形轮子:未能采用现有解决方案,而是采用自定义解决方案,其性能远逊于现有解决方案
- 银弹:假设最喜欢的技术解决方案可以解决更大的过程或问题
- 测试驱动开发:软件项目中,新的需求在错误报告中指定
- 依赖地狱:所需产品的版本问题
- DLL地狱:动态链接库 (DLL) 管理不善,尤其是在 Microsoft Windows 上
- 扩展冲突:Mac OS X 之前的 Mac OS 版本的不同扩展尝试修补操作系统相同的部件时出现的问题
- JAR地狱:过度使用多个 JAR 文件,通常由于对 Java 类加载模型的误解而导致版本和位置问题
- ↑ Budgen, D. (2003). 软件设计. Harlow, Eng.: Addison-Wesley. p. 225. ISBN 0-201-72219-4. "正如 Long (2001) 中所述,设计反模式是 '显而易见但错误的,对反复出现问题的解决方案'。"。
- ↑ Scott W. Ambler (1998). 过程模式:使用面向对象技术构建大型系统. Cambridge, UK: Cambridge University Press. p. 4. ISBN 0-521-64568-9. "...对反复出现的问题,证明是无效的通用解决方案。这些方法称为反模式。"。
- ↑ Koenig, Andrew (1995 年 3 月/4 月)。"模式和反模式"。面向对象编程杂志。8, (1): 46–48.
{{cite journal}}
:|access-date=
requires|url=
(help); Check date values in:|date=
(help)CS1 maint: extra punctuation (link); 后来重新印刷在:Rising, Linda (1998). 模式手册:技术、策略和应用. Cambridge, U.K.: Cambridge University Press. p. 387. ISBN 0-521-64818-1. "反模式就像模式一样,只不过它不提供解决方案,而是提供看起来像是解决方案的东西,但实际上并非如此。"。 - ↑ Brown, William J. (1998). 反模式:重构软件、架构和项目危机. John Wiley & Sons, ltd. ISBN 0471197130.
{{cite book}}
: Unknown parameter|coauthors=
ignored (|author=
suggested) (help) - ↑ a b Kerievsky, Joshua (2004). 重构到模式. Addison-Wesley 专业版. ISBN 0321213351.
- ↑ a b http://pmd.sourceforge.net/cpd.html PMD
- ↑ http://www.onjava.com/pub/a/onjava/2003/03/12/pmd_cpd.html 使用 PMD 的 CPD 检测重复代码
- ↑ http://en.wikipedia.org/wiki/Lava 熔岩
- ↑ Fowler, Martin (1999). 重构。改进现有代码的设计. Addison-Wesley. ISBN 0-201-48567-2.
- ↑ 供应商锁定 在 antipatterns.com
- ↑ 熔岩流 在 antipatterns.com
- ↑ "未记录的 '熔岩流' 反模式使流程复杂化". Icmgworld.com. 2002-01-14. 检索于 2010-05-03.
- ↑ Papadimoulis, Alex (2007-04-10). "软编码". Worsethanfailure.com. 检索于 2010-05-03.
- 书籍
- Laplante, Phillip A. (2005). 反模式:识别、重构和管理. Auerbach 出版社. ISBN 0-8493-2994-9.
{{cite book}}
: 未知参数|coauthors=
忽略(建议使用|author=
) (帮助)
- Brown, William J. (2000). 项目管理中的反模式. John Wiley & Sons, ltd. ISBN 0-471-36366-9.
{{cite book}}
: 未知参数|coauthors=
忽略(建议使用|author=
) (帮助) - Brown, William J. (1998). 反模式:重构软件、架构和危机中的项目. John Wiley & Sons, ltd. ISBN 0471197130.
{{cite book}}
: 未知参数|coauthors=
忽略(建议使用|author=
) (帮助) - Kerievsky, Joshua (2004). 重构到模式. Addison-Wesley Professional. ISBN 0321213351.
- Feathers, Michael (2004). 有效地使用遗留代码. Prentice Hall. ISBN 0131177052.
- 网站
- "糟糕代码探测指南". Diomidis Spinellis. 检索于 2011-03-09.
- 反模式 在 WikiWikiWeb 上
- 反模式目录
- AntiPatterns.com 反模式书籍网站
- 有毒行为模式
- CodeSmell 在 c2.com 上
- 代码异味的分类