30

想象一下,你 90% 的工作只是在一个非常庞大、非常破碎的网站上对问题进行分类。想象一下,这个网站是用你所见过的最紧密耦合、最不内聚的 PHP 代码编写的,这种代码类型会将原始开发人员添加到你的“一见钟情”列表中。想象一下,这个 Web 应用程序由 4 个非常不同的部分(1 个商业部分、2 个“重新利用”和 1 个定制部分)和一大堆虚拟胶带和垫片组成。想象一下,它包含一种编程实践,其中网站的主要组件实际上依赖于无法正常工作的东西,而修复这些损坏的东西通常会破坏其他东西。想象一下,您从太多糟糕的经历中了解到,更改网站中看似无害的部分,例如拆分“名称” 字段分为两个单独的“第一个”和“最后一个”字段,这将使网站陷入瘫痪,需要数小时的回滚、合并和补丁。想象一下,多年来一直恳求客户放弃代码并重新开始,但却遇到了企业级的绝望和绞尽脑汁。然后想象一下获得 ASAP/EMERGENCY 票来实现在任何其他网站上需要 4 小时的新功能,但您对这个网站了解得更好,所以您报价 40 小时,然后按此收费并收取 80 小时的费用,但这没关系,因为客户习惯了他们的网站。想象一下,多年来一直恳求客户放弃代码并重新开始,但却遇到了企业级的绝望和绞尽脑汁。然后想象一下获得 ASAP/EMERGENCY 票来实现在任何其他网站上需要 4 小时的新功能,但您对这个网站了解得更好,所以您报价 40 小时,然后按此收费并收取 80 小时的费用,但这没关系,因为客户习惯了他们的网站。想象一下,多年来一直恳求客户放弃代码并重新开始,但却遇到了企业级的绝望和绞尽脑汁。然后想象一下获得 ASAP/EMERGENCY 票来实现在任何其他网站上需要 4 小时的新功能,但您对这个网站了解得更好,所以您报价 40 小时,然后按此收费并收取 80 小时的费用,但这没关系,因为客户习惯了他们的网站。

以下是您还应该想象的其他一些事情:

  • 现在根本没有测试
  • 有 googleteen 不同的登录层。有些客户实际上对网站的不同部分有 3 个不同的帐户
  • 当我说“紧密耦合”时,我的意思是 include/require 语句的循环可能会像凯尔特结一样映射出来
  • 当我说“最不凝聚力”时,我的意思是有些东西的组织方式有点像 MVC,但它并不是真正的 MVC。在某些情况下,您可能需要几个小时才能了解 URI A 是如何映射到文件 B 的
  • UI 写得像“突兀”和“不可访问”是当时的流行语

想象一下,是否值得尝试达到中等水平的测试覆盖率?或者你是否应该,在这个想象的场景中,继续尽你所能用你得到的东西,希望,祈祷,甚至牺牲,客户会同意在这些日子里重写,然后你就可以开始写作了测试?

附录

自从你们中的许多人提出以来:我已经接近了在我必须约会的每一次机会中重写的可能性。与我一起工作的营销人员知道他们的代码是垃圾,他们知道这是他们最初选择的“最低出价”公司的错。我可能已经超越了我作为承包商的界限,指出他们花了一大笔钱在我身上为这个网站提供临终关怀,而且通过从头开始重新开发,他们会很快看到投资回报率。我也说过我拒绝按原样重写网站,因为它并没有真正做到他们想要它做的事情。计划是重写 BDD 风格,但将所有关键玩家集中在一个地方很难,我仍然不确定他们是否知道自己需要什么。无论如何,我完全希望这是一个非常大的项目。

感谢您迄今为止的所有反馈!

4

13 回答 13

12

您可能在一段时间内无法获得全面覆盖。但是您可以为您实现的新代码/功能编写测试。从小处着手。不要试图一次做所有事情。

也许《有效地使用遗留代码》这本书值得一读?

编辑

我还建议观看鲍勃叔叔的这个演示文稿,其中涉及这种情况以及如何使用“渐进式扩展”将糟糕的代码库转换为好的代码库

编辑 2

首先找到任何自包含的功能。除了传入的参数之外不引用任何内容的函数。将它们移动并组织到辅助类中。这可能只是暂时的,因为许多人将在以后进入不同的类,但这将有助于识别一些重复的代码,并开始组织事情。然后,看看这些是如何使用的,根据这些用途编写测试。拍拍自己的背。您现在已经开始使您的代码可维护。

编辑 3

InfoQ 刚刚发布了另一篇文章How To Do Large Scale Refactoring专门针对这类事情,以及另一篇更早的文章,名为Refactor or Rewrite?还有像天皇法这样的技术,你必须意识到你不能总是一步到位地做出你想要的动作,你必须做出其他动作来设置和实现你的目标。

于 2010-08-23T15:57:03.830 回答
11

绝对不。

如果你说多件事情依赖于其他事情特别不工作,那么你怎么能开始测试它呢?

就个人而言,我会说废弃它并重新开始。四个小时的功能需要 80?我希望这是夸大其词。你必须有的头痛。

我会从一个非常坚定的建议开始,重新编写代码库。有时必须将直言不讳的事实告诉令人心痛的客户。有多少其他开发人员会使用损坏的基础?制作一些漂亮的成本/收益图表。

一定要为您编写的代码编写测试。不要忽视这一点。我是说我不会尝试在现有代码库上编写测试。

于 2010-08-23T15:52:43.477 回答
7

搏一搏

编写测试使您能够重构。如果您以足够高的级别编写测试,您可能会设法做到这一点,这样您就可以进行重构,而不必每次都重新编写测试。

在站点的一小部分上,这是最不值得一试的(我知道您将无法完全隔离任何部分,但您仍然可以定位部分)。

也许为自己设定一个时间预算(由你决定什么是负担得起/值得的),然后进行一些测试和一些重构。如果不成功,就回滚。如果是,请继续。

祝你好运!

于 2010-08-23T15:52:48.420 回答
6

首先,如果您的客户习惯于您的估计是实际需要的一半,请修正您的估计!很高兴客户对估计值表示“OK”——但至关重要的是,您的估计值更符合实际需要的工作量。如果没有这些,客户会同意进行重大重构——更不用说重写了。因此,获得一些正确估计的历史,然后重新设计项目。

至于写测试。这对于您所描述的内容比对于新建项目更为重要。在你接触的每一段代码中,问问自己是否有可能将应该存在的行为与存在的行为分离。以应有的方式编写代码(通过测试),然后添加一个抽象层以使其成为当前的方式(并对其进行测试!)。你会觉得自己在添乱,而且你会——但慢慢地,随着时间的推移,测试会让你对这些领域充满信心。

如果它像我一直在处理的那样,它将按照将单个方法拉出到辅助类中并将其修补回现有代码的顺序,这似乎不值得——但它确实每次都能得到回报你必须再次触摸系统的那个部分。就像他们说的那样——“让它比你找到它更好”,每次你回到它时,你都会开始发现它的形状更好。测试是让它比你发现的更好的最好方法。

但说真的,在他们对你处理返工的能力充满信心之前,需要让客户对你的估计的准确性充满信心。

于 2010-08-23T18:01:33.003 回答
5

绝对写测试。尤其是在紧耦合环境中,测试将变得更加关键(因为一个区域的错误修复可能会由于紧耦合而严重影响其他区域)。

现在,意识到这可能不是一项微不足道的任务。为了编写测试,您需要修改代码以使其可测试。为了修改代码,您需要进行测试。所以你陷入了一个依赖循环......

但是,看看潜在的好处。这应该告诉你它是否真的值得。

如果您确实开始,请从小处着手。选择一个看起来松散耦合的小块,并对其进行测试。然后找到其他不那么纠结的东西。首先测试所有最松散的部分(低垂的果实)。然后,一旦你到达真正紧张的部分,你们都会感觉更舒服,并且(希望)对你真正需要做的事情有更多的了解。

请记住,您不需要 100% 的覆盖率来获得收益。每个测试都增加了意义......

于 2010-08-23T16:00:33.270 回答
3

你不能废弃它。客户不会让你这样做,而且这可能不是最好的方法。

因此,不要引用 40 小时来修复本应花费几分钟的时间……引用 60。客户似乎对此表示同意。使用 40 进行修复,使用 20 进行重构……并针对您重构的内容编写测试。如果60跑到100,那就花120;80 修复,40 重构/测试。

及时将事情改进为您的正常估计,或找到新工作;听起来,目前的情况会让你讨厌我们的领域。

于 2010-08-23T18:08:24.100 回答
2

最重要的事情(购买后有效地使用遗留代码)是从小处着手。我在几个项目上工作,每个项目都有几千行 PHP 代码,而且通常没有一个函数(甚至不考虑对象),每当我必须更改代码时,我都会尝试将部分重构为函数并为其编写测试. 这与对该部分的广泛手动测试相结合,因此我可以确定它可以像以前一样工作。当我有多个类似事物的函数时,我将它们作为静态方法移动到一个类中,然后一步一步地用适当的面向对象代码替换它们。

从把它变成一个函数到把它变成一个真正的类的每一步都被单元测试所包围(不是很好,因为 90% 的代码都是 SQL 查询,几乎不可能建立一个可靠的测试数据库,但我仍然可以测试行为)。

由于很多代码重复(我发现一个 SQL 查询在单个文件中重复了 13 次,在该项目的其他 50 个文件中重复了很多次)我可以更改所有其他地方,但我不这样做,因为这些都没有经过测试我也不能确定周围的代码不会以某种奇怪的方式依赖于该代码(想想global)。无论如何,只要我必须触摸该代码,就可以更改该代码。

这是一项漫长而乏味的工作,每次我看到代码时,我都会感到更接近精神崩溃的一步(或者更确切地说是飞跃),但代码质量会提高(缓慢但主要是可靠的)。

您的情况似乎很相似,所以也许我的想法可能会对您的代码有所帮助。

简而言之

从小处着手,只改变你正在做的事情,开始只编写有限的单元测试,并随着你对系统的了解越多来扩展它们。

于 2010-08-24T14:01:35.667 回答
2

这听起来像是为了让它完全可测试你必须从头开始重写系统的某些部分——这不可避免地会在这个过程中造成大量的错误。

根据您的描述,旧系统不值得投入这种努力。

在任何情况下,我都不会尝试为此引入测试,而是尝试尽快获得重写的许可。

如果您的客户没有看到光明,请考虑重构项目是否值得您自己花一些时间:使用干净的代码对一个人的幸福感要好得多......

于 2010-08-23T15:54:00.947 回答
1

从您的场景中,您应该有一长串易受无害更改影响的代码脆弱区域(或至少是绝对必须工作的区域)。如果您可以针对此列表进行测试,您可以快速找出您正在实施的更改何时破坏了某些东西。

于 2010-08-23T19:09:56.767 回答
1

首先进行黑盒、功能测试、连接部件或零碎的零碎。这使得持续开发和重构/重写变得更加容易。

去过那里,正在这样做。

我们花了一段时间才可以开始添加单元测试,但最终还是到了那里。

它仍然远非万无一失,但当您知道有一个测试套件等待尝试验证您的代码更改时,所有开发人员都更有信心敢于更改/修复问题。

于 2010-08-23T16:06:55.117 回答
0

理论上是肯定的。维护过程耦合得越紧密,漏洞越多,那么测试就越重要。在实践中,走开,再过一天!

于 2010-08-23T15:55:01.467 回答
0

您可能需要考虑再为 40 小时/迭代计费,以创建一个很好的 BDD(域)模型来说明应用程序如何工作或更好:应该工作。这创建了一个很好的框架,您可以在其中记录所需的功能。当模型足够完整时,您可以估计将其转换为工作应用程序所需的时间。

于 2010-08-24T14:22:58.397 回答
0

如果事情运行可靠,您可以测试它们,对吗?您的系统大部分时间都在工作,因此您可以测试这些成功条件。

..网站的无害部分,例如将“名称”字段拆分为两个单独的“第一”和“最后”字段,将使网站瘫痪并需要数小时的回滚

将一个字段(例如名字和姓氏)分开听起来像是一件潜在的大事——但听起来你已经吸取了教训。至少尝试为全尺寸测试系统获得一些资金,并制定程序以自动将生产数据转移到它,这样你就可以完全测试这个东西。

不过听起来很可怕。是时候尘埃落定了?

于 2010-08-24T00:22:49.663 回答