如何阻止临时解决方案永远持续下去?

假设一个问题有两种可能的解决方案: 第一种方案是快速但是粗糙的; 第二种方案是可取的,但是需要更长的时间来实现。您需要快速解决问题,因此您决定尽可能快地将黑客技术运用到位,并计划以后开始制定更好的解决方案。问题是,一旦问题得到缓解,它就会直线下降。您仍然计划在某个时候放入更好的解决方案,但是现在很难证明实现它的合理性。突然间,你发现自己已经花了五年时间使用这个不太完美的解决方案,同时还在诅咒它。

听起来耳熟吗?我知道我工作的地方不止一次发生过这种事。一位同事描述说,他故意制作了一个糟糕的 GUI,这样它就不会被意外地长期采用。你有更好的策略吗?

2235 次浏览

这是一个主要问题时,做最后期限驱动的工作。我发现添加关于为什么选择这种方式的非常详细的注释,以及一些关于如何编写代码的提示。通过这种方式,人们可以查看代码并保持其新鲜。

另一个可行的选择是在跟踪框架中添加 bug.Feature (您确实有一个,对吗?)详细说明了重做的细节。这样,它是可见的,并可能迫使问题在某个时候。

这是个艰难的决定。我个人曾经做过这样的事情,有时候你把 的产品推出门外,落入客户的手中。然而,我处理这件事的方法就是直接去做。

告诉项目负责人、你的老板或者客户: 有一些地方需要清理,并且编写更好的代码。我需要一周的时间来完成,而且现在做这件事的成本会更低,那么从现在开始的6个月后,当我们需要在子系统上实现一个扩展的时候,我们就可以做这件事了。

  • 我确信我对于长期修复的优先级是直言不讳的,尤其是在短期修复进入之后。我负责提出一个长期的解决方案,并确保它得到部署

策略1(几乎从未选择) : 不要实施 Kluge。别让别人知道有这种可能。只要在第一次的时候用正确的方法。就像我说的,由于时间的限制,这个几乎从来没有被选中过。

策略2(不诚实) : 谎言和欺骗。告诉管理层,黑客攻击中存在漏洞,这些漏洞可能会在以后造成重大问题。不幸的是,大多数时候,经理们只是说等到 bug 变成问题,然后再修复它。

策略2a: 和策略2一样,除了确实存在 bug,但问题是一样的。

策略3(也是我个人的最爱) : 尽可能地设计解决方案,并且做得足够好,让实习生或代码工程师都能做到。花少量的编程人员的钱比花自己的薪水更容易,所以这件事可能就这么完成了。

策略4: 等待重写。继续等。迟早(可能是迟些) ,总有人要重写这个东西。不如现在就做。

这样的问题通常是由于与管理层或客户沟通不畅而引起的。如果解决方案对客户有效,那么他们就没有理由要求对其进行更改。因此,需要事先告诉他们您所做的权衡,这样他们就可以在您实现快速解决方案之后计划额外的时间来修复问题。

如何解决这个问题有点取决于为什么这是一个糟糕的解决方案。如果您的解决方案不好,因为很难更改或维护,那么在第一次必须进行维护并有更多时间的时候,就是升级到更好的解决方案的正确时间。在这种情况下,如果你首先告诉客户或你的老板你走了捷径,就会有所帮助。这样他们就知道他们不能指望下次会有一个快速的解决方案。破坏用户界面可以是确保客户回来修理东西的一个好方法。

如果因为有风险或者不稳定而导致解决方案不好,那么你真的需要和制定计划的人谈谈,并且花些时间尽快解决问题。

问得好。这也让我很困扰——大多数时候,我是唯一一个负责在我自己的项目(是的,小型企业)中确定问题优先级的人。

我发现需要解决的问题通常只是问题的一个子集。IOW,需要紧急解决的客户不需要解决整个问题,只需要解决问题的一部分——或大或小。这有时使我能够创建一个解决方案,它不是完整问题的解决方案,而只是针对客户的子集,并且允许我在问题跟踪器中保留更大的问题。

当然,这可能根本不适用于你的工作环境:

你不能做临时解决方案。

有时候我觉得程序员需要被告知这一点。

对此我很抱歉,但是说真的——一个蹩脚的解决方案是毫无价值的,即使在第一次迭代中也可能花费比正确执行解决方案的一部分更长的时间。

请不要再给我留下你要维护的垃圾代码了。一定要把代码写对。不管要花多长时间,不管谁对你大喊大叫。

当你提前交稿后坐在那里无所事事而其他人都在调试他们愚蠢的黑客程序时,你会感谢我的。

即使你不认为你是一个伟大的程序员,始终努力做到最好,永远不要走捷径——这不会花费你任何时间去做正确的事情。如果你不相信我,我可以为自己辩护。

突然间,你发现自己已经花了五年时间使用这个不太完美的解决方案,同时还在诅咒它。

如果你诅咒它,为什么它在 TODO 列表的底部?

  • 如果它不影响你,你为什么要诅咒它?
  • 如果它正在影响你,那么这是一个问题,需要立即解决。

我尝试构建这个粗糙的解决方案,以便能够尽可能轻松地将其迁移到长期方法。假设有一个人正在 SQL Server 中构建数据库,因为那是他最强大的数据库,但是你的企业标准是 Oracle。使用尽可能少的不可转移特性(比如 Bit 数据类型)构建 db。在这个例子中,避免位类型并不难,但是它使得以后的转换过程更加容易。

唯一一次你可以证明修复这些东西是正确的(因为它们并没有真正坏掉,只是很丑陋)是当你有另一个特性或者 bug 修复涉及到相同的代码部分时,你可以重写它。

您必须计算开发人员的时间成本。如果软件需求得到了满足,而唯一的错误是代码在引擎盖下令人尴尬,那么它就不值得修复。

整个公司可能会倒闭,因为过于热心的工程师们每年左右都会在坐立不安的时候坚持对架构进行重新设计。

如果它没有错误并且符合要求,那么它就完成了。发布它。继续前进。

[编辑]

当然,我并不是提倡所有的东西都被黑进去。在正常的开发过程中,您必须仔细地设计和编写代码。但是,当你最终不得不快速完成黑客攻击时,你必须做一个成本-收益分析,看看清理代码是否值得。如果在应用程序的整个生命周期中,您将花费更多的时间来编写一个混乱的代码,而不是修复它,那么当然要修复它。但是如果没有,仅仅因为查看源代码会使您生病而重新编写 正常工作,没有窃听器应用程序的代价太高,风险太大。

这里有一篇关于 技术债务的相关文章。

基本上,这是债务与所有技术决策的类比。有好的债务和坏的债务... ... 你必须选择那些能以最低的长期成本实现你想要的目标的债务。

最糟糕的债务类型是类似于信用卡债务的小的积累的捷径... 每一个都不会受到伤害,但是很快你就会进入贫困家庭。

祝你好运,以我的经验,这几乎是不可能实现的。

一旦你因为压力太大而陷入实施黑客行为的泥潭,那么你最好还是习惯于一直忍受这种行为。几乎从来没有足够的时间来重新工作,已经工作的东西,无论如何糟糕的实施内部。是什么让你觉得自己会“在以后的某个时候”神奇地有更多的时间来修复黑客行为呢?

我能想到的唯一例外是,如果黑客完全阻止您实现客户所需的另一项功能。那你别无选择,只能重做了。

我们不得不这样做一次-制作一个短期演示版本,我们知道我们不想保留。客户希望它在一个 winTel 盒子上,所以我们在 SGI/XWindows 中开发了原型。(我们两种语言都很流利,所以这不是问题)。

编写一个黑客失败的测试用例。

如果您不能编写一个黑客失败的测试,那么要么这个黑客没有任何问题,要么您的测试框架是不适当的。如果是前者,在你把生命浪费在不必要的优化上之前,赶紧跑吧。如果是后者,则寻求另一种方法(或者是标记黑客,或者是测试...)

忏悔:

为了从其他代码层读取数据,我在 C + + 中使用了“ # Definition private public”。它作为一个黑客进入,但工作得很好,修复它从来没有成为一个优先事项。现在三年过去了。

黑客没有被删除的一个主要原因是在修复黑客的过程中可能会引入新的 bug。(特别是在处理 TDD 之前的代码库时。)

这让我想起了“ CTool”的故事。一开始 CTool 是由我们的一个开发人员提出的,我叫他 Don,作为一种可能的方法来解决我们遇到的问题。作为一个认真勤奋的人,唐努力工作,并提供了一个工作原型。你知道我要说什么。一夜之间,CTool 成为公司工作流程的一部分,整个部门都依赖于它。到了第二天或第三天,针对 CTool 缺点的尖锐抱怨开始不断涌现。用户质疑唐的能力、承诺和智商。唐抗议说这不应该是一个生产应用程序,但是他的抗议被置若罔闻。好几年了就是这样。最后,在唐离开之后,终于有人重写了这个应用程序。此时,对 CTool 这个名称的厌恶已经如此之深,以至于将其命名为 CTool version 2是不可能的。甚至还为 CTool 举行了一个正式的葬礼,有点像复印机(或者是打印机?)办公空间中的行刑场景。

有些人可能会说,唐应该得到这样的莎翁秘辛,因为他没有正确地修复 CTool。我唯一的观点是,说你应该 永远不会砍出一个解决方案在现实世界中可能是不合理的。但是如果你是那个做这件事的人,小心行事。

我的回答与其他人有点不同。我的经验是,下面的实践可以帮助你保持敏捷,并从粗糙的第一次迭代/alpha 解决方案转移到测试/生产准备阶段:

  1. 测试驱动开发

  2. 重构的小单元

  3. 持续积分
  4. 好组态管理
  5. 敏捷数据库技术/数据库重构

不言而喻,你必须得到利益相关者的支持才能正确地完成这些任务。但是,有了这些产品,你就有了正确的工具和流程,可以自信地快速改变产品的主要方式。有时候,你的变化能力就是你管理变化风险的能力,从开发的角度来看,这些工具/技术给了你更稳固的基础。

  • 写下来(电子邮件)。因此,当它成为一个问题后,管理不“忘记”,它应该是暂时的。

  • 使其对用户可见。危机越明显,人们就越不可能在危机结束后忘记回到过去,以正确的方式做事。

  • 在项目、资源和时间线的临时解决方案到位之前进行协商,以获得真正的解决方案。实际解决方案的工作可能应该在临时解决方案完成后立即开始。

教育那些负责做出最终决定的人,让他们明白为什么从长远来看,这种做事方式是不好的。

  • 用他们能够联系到的术语描述问题。
  • 包括成本、生产率和收入曲线图。
  • 教他们 技术债务
  • 如果被推进,则定期重构。
  • 永远不要在非技术人员面前称之为“重构”或“返回并清理”。相反,可以称之为“适应”系统来处理“新特性”。

基本上,不懂软件的人不会理解重访已经工作的东西的概念。在他们看来,开发人员就像机械师一样,每次有人想添加一个功能时,他们就想拆开并重新组装整辆车,这听起来很疯狂。

它有助于对日常事物进行类比。向他们解释当你做出临时解决方案时,你是如何做出适合快速构建的选择,而不是稳定的、可维护的,等等。这就像选择用木材而不是钢材来建造,因为木材更容易切割,因此,你可以更快地建造临时的解决方案。然而,这种木材根本无法支撑20层楼的地基。

您针对自己的“修复”提交了第二个非常具有描述性的 bug,并在受影响的区域中放置了一个 to-do 注释,该注释说,“这个区域需要大量工作。请参阅缺陷 # 555”(使用正确的号码)。那些说“不要进行黑客攻击”的人似乎不明白这个问题。假设你有一个系统,现在需要启动和运行,你的非黑客解决方案是8天的工作,你的黑客是38分钟的工作,黑客的存在是为了给你争取时间来做这项工作,而不是损失金钱,当你这样做。

现在你仍然需要让你的客户或管理层同意安排 N * 100分钟的时间来做真正的修复,除了 N 分钟需要现在来修复它。如果你必须在得到这样的协议之前拒绝实施黑客攻击,那么也许这就是你必须要做的,但是在这方面我和一些理解的人一起工作。

引入快速解决方案的真正代价是,当其他人需要引入第二个快速解决方案时,他们会根据你自己的快速解决方案来引入。因此,权宜之计实施的时间越长,它就会变得越根深蒂固。通常,一个黑客只需要比正确处理事情稍微长一点的时间,直到您遇到第二个以第一个为基础的黑客。

因此,很明显,有时有必要(或似乎有必要)引入一个快速解决方案。

假设您的版本控制支持它,一个可能的解决方案是每当您进行这样的修改时都从源代码引入 fork。如果鼓励人们避免在这些特殊的“完成工作”分支中编写新特性,那么最终把新特性与分支集成起来的工作量将超过摆脱黑客的工作量。然而,更有可能的是,“好”的叉子将被丢弃。如果你离发布还有很长的路要走,那么制作这样一个分叉是不切实际的(因为它不值得做上面提到的双重集成) ,那么你可能根本就不应该使用黑客技术。

非常理想主义的方法。

一个更现实的解决方案是将程序分割成尽可能多的正交组件,并偶尔对其中的一些组件进行完全重写。

一个更好的问题是,为什么粗糙的解决方案是坏的。如果因为它降低了灵活性而使它变得不好,那么忽略它,直到您需要灵活性为止。如果它是坏的,因为它影响了程序的行为,忽略它,最终它将成为一个错误修复,并将得到解决。如果因为它看起来很丑而不好,那么忽略它,只要黑客攻击是本地化的。

以下是我过去看到的一些解决方案:

  • 在代码中用注释 HACK标记它(或者类似的方案,如 XXX)
  • 有一个自动报告运行和电子邮件每周给那些关心计数多少次的 HACK评论出现
  • 在你的臭虫回报页面中添加一个新的条目,并附上行号和正确解决方案的描述(这样就不会丢失编写黑客程序之前从研究中获得的知识)
  • 编写一个测试用例,演示黑客如何失败(如果可能的话) ,并将其签入到适当的测试套件中(也就是说,它会抛出错误,最终有人想要清除这些错误)
  • 一旦黑客安装和压力关闭,立即开始正确的解决方案

问得好。随着经验的增加,我注意到一件事: 黑客可以为你争取很短的时间,但往往会让你付出更多的代价。密切相关的是“快速修复”,解决你认为是问题的东西——只有当它爆炸时才发现根本不是问题。

撇开关于你是否应该这样做的争论不谈,让我们假设你必须这样做。现在的技巧是以一种最小化远距离影响的方式来做它,它很容易在以后被撕掉,并且使它本身成为一个讨厌的东西,所以你要记得去修复它。

麻烦的部分很简单: 每次执行组装程序时,让它发出一个警告。

被删除的部分可以很容易: 我喜欢这样做,把组装后面的子程序名称。这使得更新变得更加容易,因为您划分了代码。当您得到永久的解决方案时,您的子例程可以实现它,也可以不执行。有时,子类也可以很好地解决这个问题。尽管如此,不要让其他人依赖于你的快速解决方案。在没有看到情况的情况下,很难推荐任何特定的技术。

如果代码的其余部分很好,那么最小化远程效果应该很容易。总是通过已发布的接口,以此类推。

尽量让商界人士清楚黑客攻击的成本。然后他们可以做出明智的决定。

我们使用 Java 和 Hudson 进行持续集成。“临时解决方案”必须注明:

// TODO: Better solution required.

每次 Hudson 运行构建时,它都会提供每个 待命项目的报告,这样我们就可以对任何需要改进的未完成项目进行最新的、高度可见的记录。

您可以有意地使用过度限制和单一目的的方式来编写它,并且需要重新编写以进行修改。