过度工程的具体症状

50

我最近的任务是向两位我们公司拟聘用的候选人解释一个我写的(内部)应用程序,以便协助维护和添加一些次要功能。

这是我写的第一个“生产”应用程序,有45k行代码,我花了将近两年的时间进行“独立”开发。我相当年轻(18岁),在被聘为一位离开公司的前任开发人员的替代者时,从头开始编写了该应用程序。由于缺乏设计这种规模应用程序的经验,我尝试使用常见的架构和设计模式。

今天我知道自己做了一些严重的过度工程化,例如使用了断开连接的更改跟踪架构,而不是已实现的工作单元模式。我可能永远不需要真正的三层结构。

两位候选人都具有10年以上的内部应用程序开发经验,并掌握相关平台。作为年龄比他们小且经验较少的人,我尊重他们的意见。当我向他们解释应用程序架构时,他们的评论大致如下:

  • 天哪,没有人会付钱让我做那样的事情,我必须完成任务
  • 坚持使用框架所做的事情,不要使用花哨的库/技术
  • 不要包装框架代码。团队中,每个人都会编写自己的包装器代码。
  • 你在使用.NET 3.5吗?嗯,我们使用2.0。
  • LINQ有什么用?所有这些查询组成和投影似乎太复杂了。
现在我在自问:
我是一个架构宇航员吗?我怎么知道我的架构过于复杂了?过度工程化的常见症状是什么?

3
最好将此设为社区维基页面 - 关于何种情况属于过度设计可能不存在广泛共识。 - Jeff Sternal
20
有时,具有10年以上经验的人可能是团队中最出色的人,有时他可能还是业余爱好者。有时,没有学位的高中生也能表现出高级别的水平,有时他们从未编写过一行代码,认为编程看起来像这样:http://www.youtube.com/watch?v=gwlE1aASc4g。就其价值而言,我们都容易陷入过度工程化的陷阱。不用太担心,特别是如果那些人实际上并没有看到真正的代码。只需进行一些简单的调整,删除任何看起来像DailyWTF材料的东西,然后就可以了。 - Juliet
4
同意朱丽叶的观点——如果你从中学到了东西并且它有效,那就不必担心。几乎每个人在得到更多经验后都会先走向过于复杂的方向,然后才会找到更简单的编码方式。至少你已经考虑过这个问题了... - Paddy
17个回答

131
常见的过度设计症状是什么?
代码解决了你没有遇到的问题。

一些基础设施可能有点过度设计了,我是否应该考虑进行(可能危险/昂贵的)重构? - Johannes Rudolph
2
我同意朱丽叶的观点 - 除非它妨碍了你的代码扩展或维护,否则你不需要过于担心。但是在未来抑制这种冲动是值得的! - Jeff Sternal
2
它能解决实际问题吗? - just somebody
6
可能的推论是“会出现创造了你之前没有的问题的代码。”尽管它可能解决了一个真正的问题,但这个问题是否值得解决或者甚至相关呢? - Justin Johnson
2
@Johannes,你能找个人和你一起重构吗?这是我的建议,因为这样可以给重构带来几个好处:别人可以了解你所做的事情,你可以学习别人如何工作,应用程序也会得到改进,这是三个好处。 - JB King
对于客户而言,当你创造出并不存在的需求时,这被称为营销。如果你同时拥有营销和过度工程化,那就是苹果公司。 - Stathis Andronikos

30

过度设计的一个非常明显的警告信号是,当所有内容都经过大量间接处理时,很难找到实现某个具体的、领域级别的功能的代码片段。如果您发现大部分函数只做一些微不足道的工作,仅调用其他虚拟函数,那么您可能会有问题。


23

无聊

无聊是写出过度工程化代码的好前兆。我承认,当我得到第一份工作时,我感到被低估了。我只是感到很无聊。而当我感到无聊时,我就开始写代码。不仅仅是普通的代码——是巨大的代码大教堂。

不开玩笑,我心中有一个画面,我的代码和抽象概念是高塔,有着金色突起尖顶、玻璃般的黑色飞扶壁、由拱形圆顶支持的壮丽拱廊,上面镶嵌着美丽的几何图案等等。

看到这些模式互相配合的方式真的很吸引人,但回想起来,我对自己留下的混乱代码感到非常惭愧。

如果你在闲暇时间写自己的框架和DSL代码,请立即停止。更好地利用时间是阅读Wards Wiki,或者写一本开源书籍,或者你可能想要向管理层寻求更多的工作。


我可以使用Cathedralsofcode.com作为域名吗?那很漂亮。 - johnny
这个短语“代码大教堂”已经在我心中萦绕多年,以至于我刚刚谷歌搜索找到了我读过它的地方,结果又回到了这里 :) - slugmandrew
也许更像是"代码的脚手架",因为抽象和模式将我们带离了系统架构(那座美丽的大教堂),并在其周围建立了一个非常有组织、运作良好的框架。这是典型的软件工程壮举。 - ciscoheat

11
关于是否你是一个“架构宇航员”的问题:如果你能意识到这会带来危险,那么你比许多人更明智。你也不想走自己的同僚们走过的路,他们中的一些人听起来已经变成了愤世嫉俗的老古董。
过度工程化是一种优先级问题的结果,其中系统的某个部分得到了太多的关注。因此,最明显的过度工程化症状是你可以看到系统其他部分因缺乏关注而受到伤害。
(还有一种趋势是过度工程化会增加系统面临设计不良风险的可能性,因为增加了复杂性和决定哪些方面进行过度工程化时涉及到的大量容易出错的推测,但正如一条评论所指出的那样,这并不是必然的。)

一个单一的“变更单元”需要在多个地方进行更改,并不是过度工程的症状,而是紧耦合/糟糕设计/低效率工程的表现。 - Vinko Vrsalovic
这是一个很好的观点,我认为过度工程化意味着系统的某些部分得到了太多的关注,导致系统的其他部分得不到足够的关注。当我看到某些东西被过度工程化时,通常会发现它的其他部分是欠工程化的。 - Nathan Hughes

11

编写自己的框架

很有可能,其他人已经做过了。而且,他们已经比你做得更好1000倍以上。更重要的是,无论他们做了什么,都可能已经成为行业标准,因此学习这项技术将使您在其他工作中更具竞争力。

在我之前工作的最后一家公司中,一位程序员大部分时间都是独立完成项目的。他编写了公司最受欢迎的应用程序之一,并被广泛认为是团队中最出色的人 - 但在我看来,他有一个不好的习惯,那就是从头开始编写所需的所有内容。

他编写了自己的依赖注入框架、自己的ORM、单元测试框架(不可思议的是,它看起来和NUnit非常相似 - 为什么他不使用NUnit呢?)、创建工厂对象的框架(我会称之为“factory factory”)。

请注意,代码实际上非常出色,但这有什么意义呢?

编写更好的核心库

在我现在的公司,似乎程序员们总是写出无用的代码来复制已经存在于.NET框架中的功能。

除其他事项外,他们编写了:

  • ASP.NET WebForms内置了表单身份验证功能,因此用于表单身份验证的Active Directory框架并不必要。
  • 网站热插拔主题和皮肤也是不必要的,因为代码比内置的ASP.NET主题更加臃肿且功能较少。
  • 他们莫名其妙地编写了自己的类型化数据集和数据适配器。这些对象提供的功能比VS自动生成的类型化数据集要少,同时需要比NHibernate域对象更多的样板代码。

要么他们不太了解框架,要么他们认为它明显不足够好。

我只能想到极少数几个例子,其中重新实现的库比原始库更好(参见Jane Street Core LibraryC5 Generic Collections for .NETa real currency class),但很可能你不会写出更好的标准库。


1
关于“更好的核心库”的评论,一位C++程序员曾经向我解释过,内置的字符串类非常令人沮丧,以至于编写自己的字符串类几乎成为了一种入门仪式。 - Juliet
1
我想那个人肯定有时间去做他所做的事情,而且我敢打赌他知道为什么事情会按照某种方式运作,我敢打赌他将来选购现成产品时会更加娴熟。有时候,你需要会从头开始做事情的人。这是否对他的底线或公司的底线产生贡献是一个不同的问题。 - johnny
可能不公平地说“比你写得好1000倍”,没有暗示“在你的办公桌工作的时间内”。它之所以比你写得好1000倍,可能唯一的原因是他们有更多的时间投入,而不仅仅是无聊/填充时间的编码。 - galois

10

对于大多数企业内部应用程序,你的代码应该关注于实现与业务有关的问题,而不是与业务无关的技术问题(例如你的“断开连接的更改跟踪架构”)。当前可用的框架非常成熟,并支持大多数常见用例。如果你正在发明新技术或者(在业务应用程序开发的背景下)只是为了封装某个其他现有的框架或库,那么你可能做错了。理想情况下,你构建的每个架构都应该可以追溯到某个业务需求。保持简单。


9
在我看来,大多数关于您的应用程序的评论并不是关于过度工程,因为过度工程不涉及技术。它涉及架构。新技术可以在合理的时间内学习和理解。理解一个过度工程化的应用程序通常更加困难,有时甚至是不可能的。这使得第2、4和5点无效。第一点不太有效,因为显然你得到了编写应用程序的报酬,如果它能工作,你就没有问题。
这是我的“快速测试”,以确定应用程序是否倾向于过度工程:
- 对“一切”都进行包装:包装很有用,但容易做过头。检查一下您是否只包装了真正需要包装的内容。(我曾经包装过自己的包装器。我知道我在说什么;-)。) - 重新发明轮子:一个经典案例。这很普遍,您已经提到了它。您是否实现了一些功能,因为您需要或想要?您的框架做了其他可用库所不具备的功能吗? - “感觉”过度工程:这是最重要的一点,但也是最难看出来的一点。看看您的代码,并查找哪些部分感觉过度复杂。然后问问自己,是否有更简单的实现方式,为什么没有选择这种方式。如果您没有好的答案,那么这部分可能是过度工程。
这些只是我用于我的应用程序的快速提示。它们不能保证是“过度工程检测”的全部和结束。

哈哈,“包装你自己的包装”真是太有趣了!但在我看来,包装通常会使代码更易读,所以我认为如果你过度使用它也没关系。 - Camilo Martin
有时候,如果你有时间,你应该重新发明轮子。了解为什么某些东西会转动,而不仅仅是它们会转动,这是很好的。 - johnny

9

当你的同事花费了6个小时试图使用你的荒谬框架使对话框出现但无果,他/她的电脑飞过你的头顶时,这就是一个非常明显的症状。


那其实就是他们告诉我的他们害怕的东西(他们还没有使用过这个代码库)。这是一个 MDI 应用程序,使用一些自定义包装函数来启动子对话框等。 - Johannes Rudolph

8

避免使用YAGNI, DRYKISS等概念,这些概念通常被认为是过度工程化的表现。如果有很多部分似乎只完成了一半,代码中也有很多部分感觉像是“如果发生这种情况怎么办?如果发生那种情况怎么办?”的话,那就是另一个问题。忽略好的OO设计原则SOLID原则也是需要注意的。如果你认为自己编写了完美的代码,那就是另一个麻烦的迹象,因为几乎没有人能够编写出不能在某种程度上得到改进的东西。

我认为需要注意的是,有些人可能会对你的工作过于苛刻,因为任何代码库都可能涉及到人们喜欢以某种方式命名方法、测试和变量等约定。这就是现实。现在,你可能需要想办法处理一些情况,比如冲突说服/影响, 这里有一些工具可以帮助你。


3
您同意违反DRY(不要重复自己)原则很明显是“低级”工程的一种迹象,缺乏设计吗? - Johannes Rudolph
毫无疑问,违反DRY原则是一种“低效”的工程设计,表明缺乏规划。我所描述的部分情况,就是为什么重构有时如此有用的原因之一。 - JB King

7

提供应用程序内在功能的插件

扩展性架构是非常有趣和令人兴奋的编写方式,但这也是你需要问自己“我真的需要这样做吗?”的另一个领域。

如果你不需要对应用程序进行即席添加,也没有人编写第三方扩展,并且你的应用程序范围相当明确,那么你就不需要扩展性架构。

你不应该编写插件来支持应用程序的内在功能。比如说,如果你正在编写一个绘画程序,你可能会支持插件以多种格式保存文件,但你不需要可插拔的撤消管理器或文件浏览对话框。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接