被迫向GOTO黑暗的一面

我们有一个工作环境,在传统(核心)系统上工作的开发人员在向已经感染意大利面代码的现有代码中添加新function时,正在向使用GOTO语句施压。

现在,我明白可能会有使用“只是一个小小的GOTO”的争论,而不是花费时间重构一个更易维护的解决scheme。 问题是,这个孤立的“一个小小的GOTO”并不是那么孤立。 每周至less有一次,有一个新的“一个小GOTO”添加。 由于代码可追溯到1984年或之前,GOTO使许多Pastafarians相信它是受到Flying Spaghetti Monster本身的启发,所以这个代码库已经是可怕的了。

不幸的是,这里写的语言没有任何现成的重构工具,所以它使得难以推动'重构提高生产力以后',因为短期的胜利是唯一赢得关注的胜利…

还有没有其他人经历过这个问题,大家都同意我们不能添加新的GOTO,把2000条线路跳到一个随机部分,但是Anaylsts坚持要这样做,并且pipe理层批准了吗?

tldr;

如何才能解决开发人员被迫(不得不)不断添加GOTO语句的问题(通过添加,我的意思是添加跳转到多行的随机部分),因为它“更快地获得了这个特性”?

我开始害怕我们可能会失去有价值的开发者在猛龙这个…

信用:XKCD

澄清:

转到 here

alsoThere:不,我正在谈论的是,在一个子程序中跳出1000行到另一个中间循环的跳转。 转到 somewhereClose

there:我什至不谈论你可以合理地阅读,并确定一个程序正在做什么样的gotos。 转到 alsoThere

somewhereClose: nextpoint somewhereClose:这是使肉丸midpoint:的代码midpoint: 如果第一次在这里转到下 nextpoint detail:每一个几乎完全不同) 转到 nextpoint 返回

here:在这个问题上,我不是在谈论偶尔使用goto。 去there

tacoBell:刚刚回到制图板。 转到 Jail

elsewhere:当分析师花费数周的时间来解密程序正在做的事情时,每次触摸时,代码库都会出现严重错误。 事实上,我真的到了我的hell: 如果不是最新的goto 4规格的detail 转到 pointlessReturn: tacoBell pointlessReturn: goto tacoBell

Jail:其实,只是一个小小的胜利小更新。 我花了4个小时,一次重构了这个特定程序的一部分,每次都保存在svn中。 每个步骤(其中大约20个)都很小巧,逻辑性强,而且很容易bypass nextpoint:通过一些奇怪的意大利式面条 – 肉丸磁性,自发地跳出餐点,进入屏幕。 转到 elseWhere bypass:合理地validation它不应该引入任何逻辑改变。 使用这个新的更易读的版本,我已经和分析师坐了下来,现在完成了几乎所有的改变。 转到 end

4:第一次*如果第一次到这里, hell ,没有第二次, 如果第一次到这里 hell ,没有第三次, 如果第一次到这里 hell现在最新转到 hell

end:

由于写错了GOTO,导致了多less错误? 他们花费了多less钱? 把这个问题变成具体的东西,而不是“这感觉不好”。 一旦你可以把它认为是负责人的问题,把它变成一个政策,如“除了简化function的退出逻辑,没有任何新的GOTO”,或“没有新的GOTO的任何function不有100%的unit testing覆盖率“。 随着时间的推移,收紧政策。

GOTO不做好意大利面条,还有其他许多因素。 这个linux邮件列表的讨论可以帮助你理解一些东西(Linus Torvalds关于使用gotos的大图)。

试图制定一个没有gotos的政策,从长远来看是没有任何意义的,也不会使你的代码更加可维护。 这些变化将需要更加微妙的重点,以提高代码的整体质量,思考如何使用平台/语言的最佳实践,unit testing覆盖率,静态分析等。

发展的现实是,尽pipe所有关于正确的方式的花言巧语,但大多数客户更快地做到这一点。 一个代码基础迅速走向内爆点的概念以及由此产生的业务影响是他们无法理解的,因为这意味着必须在今天以外进行思考。

你有什么只是一个例子。 你如何站在这一点将决定你将来如何发展。 我想你有4个select:

  1. 接受请求并接受你将一直这样做。

  2. 接受请求,立即开始寻找新的工作。

  3. 拒绝做,并准备争取解决混乱。

  4. 辞职。

你select哪种select取决于你对自己的工作和自尊的重视程度。

也许你可以使用boyscout模式:让这个地方比你find的更干净一点。 所以对于每个function请求:不要引入新的gotos,而是删除一个。

这不会花太多时间来改进,会花更多的时间来find新引入的错误。 也许这不会花费太多的额外时间,如果你从部分中删除了一个goto,虽然不得不花费时间理解,并带来了新的function。

争辩说,重构2小时将在未来15分钟内保存20次,并允许更快更深的改进。

回到原则:

  • 它是否可读?
  • 它工作吗?
  • 它可维护吗?

这是经典的“pipe理”与“技术”冲突。

尽pipe是“技术”队伍,多年来我坚定地把这个辩论的“pipe理”方面解决了。

有许多的原因:

  1. 用gotos很容易阅读正确的结构化程序是很有可能的。 询问任何汇编程序员; 条件分支和原始do循环都是他们必须处理的。 只是因为“风格”不是最新的,并不意味着写得不好。

  2. 如果它是一团糟,那么如果你要完全重写,那么提取业务规则将是一个真正的痛苦,或者,如果你正在对程序进行技术重构,你将永远无法确定重构程序的行为是“正确的”,即它确实是旧程序的行为。

  3. 投资回报 – 坚持原有的编程风格,做出最小的改变将花费最less的精力,快速地满足客户的要求。 花费大量的时间和精力重构将会更加昂贵并且花费更长的时间。

  4. 风险 – 重写和重构很难得到正确的结果,需要对重构代码进行广泛的testing,而像“bug”这样的东西可能是客户所关心的“特性”。 “改进”遗留代码的一个特殊问题是,商业用户可能已经build立了依赖于存在的缺陷的解决方法,或者利用缺陷的实际情况来改变业务stream程而不通知IT部门。

所以总的来说pipe理层面临着一个决定 – “增加一个小小的转变”,testing这个变化,并以双倍的快速时间进行生产,几乎没有风险,或者进入一个主要的编程工作,让企业尖叫在他们面前,当一组新的bug出现时。

如果你决定在五年时间内重构一些鼻子黝黑的大学gradle生,他们会呻吟,说你的重构计划不再符合stream行语,并且要求他可以花上几个星期来重写它。

如果没有破解不要修复它!

PS:即使乔尔认为这是一个坏主意: 你永远不应该做的事情

更新! –

好,如果你想重构和改善你需要正确地去做的代码。

最基本的问题是你对客户说: “我想花上n周的时间在这个项目上工作,如果一切顺利的话,它现在就能做到。” – 这至less可以说是一件难事。

您需要收集关于崩溃和中断数量的长期数据,分析和编程看起来很小的变化的时间,由于过于困难而没有完成的变更请求的数量,由于系统不能快速更换而导致业务运营损失足够。 同时收集一些关于维护结构良好的程序和让它下沉的成本数据。

除非你有一个水密的案例来向beancounters呈现你不会得到预算。 你真的不得不把这个卖给企业pipe理层,而不是你的直属上司。

我最近不得不编写一些本身不是遗留代码的代码,但是以前的开发者的习惯当然是这样,所以GOTO是无处不在的。 我不喜欢GOTO的; 他们做了一个可怕的混乱的事情,使debugging噩梦。 更糟糕的是,用普通代码replace它们并不总是直截了当的。

如果你不能放松你的GOTO我当然build议你不再使用它们。

不幸的是,这个编写的语言没有任何现成的重构工具

你有没有macrosfunction的编辑? 你没有shell脚本吗? 这些年来我做了大量的重构工作,而重构浏览器的工作却很less。

这里的根本问题似乎是,你有'分析师'谁描述,可能是必要的,function上的变化,在一些代码添加转到。 然后你有“程序员”,他们的工作似乎局限于input变化,然后抱怨。

为了做出不同的事情,这些人之间责任分配的不合理需要改变。 有很多不同的方法来分解工作:传统的(很可能是官方的,但是被忽略的)工作中的经典策略是让分析人员编写一个与实现无关的规范文档,程序员将其实现为尽可能保持原状。

这种理论方法的问题是在许多常见的情况下实际上是不切实际的。 特别是要做到“正确”,要求员工视为低级,与更高级的同事争吵,在办公室有更好的社交关系,更有商业头脑。 如果“goto”这个论点不是独立于实现的,那么作为一个分析师,你根本就不能说这个词不会在你的工作空间中飞行,那么可能就是这样。

在很多情况下更好的select是:

  1. 分析师编写面向客户端的代码,开发者编写基础架
  2. 分析师编写用作unit testing参考实现的可执行规范。
  3. 开发人员创build一个特定于领域的语言,供分析员编程。
  4. 你放弃了彼此之间的区别,只有一个混合。

如果对程序的改变需要“只是一个小小的转变”,我会说代码是非常可维护的。

处理遗留代码时,这是一个常见的问题。 坚持程序最初写入的风格或“现代化”代码。 对我来说,答案通常是坚持原来的风格,除非你有一个非常大的变化,certificate一个完整的重写。

另外请注意,诸如java的“throw”声明或SQL“ON ERROR”之类的几个“现代”语言特性实际上是“伪装”的。