是否值得尝试为世界上耦合最紧密的站点编写测试?
想象一下,您 90% 的工作只是对一个非常庞大、非常破碎的网站上的问题进行分类。想象一下,这个网站是用您所见过的最紧密耦合、最缺乏凝聚力的 PHP 代码编写的,这种代码类型会将原始开发人员添加到您的“一见钟情”列表中。想象一下,这个 Web 应用程序由 4 个完全不同的部分(1 个商业部分、2 个“重新用途”部分和 1 个定制部分)以及大量虚拟胶带和垫片组成。想象一下,它包含一种编程实践,其中网站的主要组件实际上依赖于无法正常工作的东西,而修复这些损坏的东西通常会破坏其他东西。想象一下,您从太多糟糕的经历中知道,更改网站中看似无害的部分,例如将“名称”字段拆分为两个单独的“第一个”和“最后一个”字段,将使网站崩溃并需要数小时的时间回滚、合并和补丁。想象一下多年来恳求客户放弃代码并从头开始的情况,但却遇到了企业级的绝望和绝望。然后想象一下,获得 ASAP/EMERGENCY 票据来实施新功能,这在任何其他网站上都需要 4 小时,但您对这个网站更了解,因此您报价 40 小时,然后直接忽略并计费 80 小时,但这没关系,因为客户已经习惯了他们的网站。
您还应该想象以下其他一些事情:
- 现在根本没有测试,
- 有 googleteen 不同的登录层。 当我说“紧密耦合”时,一些客户实际上对网站的不同部分有 3 个不同的帐户
- ,我的意思是,当我说“最缺乏内聚”时,include/require 语句的循环可能会像凯尔特结一样映射出来,
- 我的意思是有些东西是组织方式有点像 MVC,但它并不是真正的 MVC。在某些情况下,您可能需要几个小时才能弄清楚 URI A 是如何映射到文件 B 的
- UI 写得像“引人注目”和“无法访问”是当天的流行语
想象一下这一切,是否值得尝试实现甚至中等水平的测试覆盖率?或者,在这个想象的场景中,你应该继续尽你所能,用你所得到的东西,希望、祈祷,甚至牺牲,客户会同意在这些日子里重写,然后你就可以开始写作测试?
附录
既然你们中的许多人提出了这个问题:我已经抓住了每次有机会重写的可能性。和我一起工作的营销人员知道他们的代码很糟糕,而且他们知道这是他们最初选择的“最低出价”公司的错。我可能已经超越了我作为承包商的界限,指出他们在我身上花了一大笔钱来为这个网站提供临终关怀,并且通过从头开始重新开发它,他们很快就会看到投资回报率。我还说过我拒绝按原样重写该网站,因为无论如何它并没有真正实现他们想要的功能。计划是重写 BDD 风格,但是将所有关键参与者集中到一个地方是很困难的,而且我仍然不确定他们知道自己需要什么。无论如何,我完全希望这将是一个非常大的项目。
感谢迄今为止的所有反馈!
Imagine that 90% of your job is merely to triage issues on a very massive, very broken website. Imagine that this website is written in the most tightly coupled, least cohesive PHP code you've ever seen, the type of code that would add the original developers to your "slap on sight" list. Imagine that this web application is made up of 4 very disparate parts (1 commercial, 2 "repurposed", and 1 custom) and a crap-ton of virtual duct tape and shims. Imagine that it contains the type of programming practices in which major components of the website actually rely on things NOT working properly, and fixing these broken things usually breaks other things. Imagine that you know from too many bad experiences that changing one seemingly innocuous part of the website, such as splitting a "name" field into two separate "first" and "last" fields, will bring the site to its knees and require hours of rollbacks, merges and patches. Imagine pleading with the customer for years to just ditch the code and start all over but being met with Enterprise-Grade despair and hand wringing. Then imagine getting ASAP/EMERGENCY tickets to implement new features that in any other web site would take 4 hours but you know better with this site so you quote 40 hours, then blow right by that and bill 80 hours, but it's OK because the client is used to that with their website.
Here are some other things that you should also imagine:
- there are no tests at all right now
- there are googleteen different layers of logins. Some customers actually have 3 different accounts for different sections of the website
- when I say "tightly coupled", I mean the loops of include/require statements would probably map out like a celtic knot
- when I say "least cohesive" I mean some stuff is organized sort of like MVC, but it's not really MVC. In some cases it may take you several hours just to find out how URI A is mapped to file B
- the UI was written like "obtrusive" and "inaccessible" were the buzzwords of the day
Imagining all that, is it even worth trying to achieve even a moderate level of test coverage? Or should you, in this imaginary scenario, just keep doing the best you can with what you've been given and hoping, praying, maybe even sacrificing, that the client will agree to a rewrite one of these days and THEN you can start writing tests?
ADDENDUM
Since many of you brought it up: I have approached the possibility of a re-write at every chance I've had to date. The marketing people I work with know that their code is crap, and they know it's the fault of the "lowest bid" firm they went with originally. I've probably overstepped my bounds as a contractor by pointing out that they spend a crap ton of money on me to provide hospice care for this site, and that by redeveloping it from scratch they would see an ROI very quickly. I've also said that I refuse to rewrite the site as-is, because it doesn't really do what they want it to do anyway. The plan is to rewrite it BDD style, but getting all the key players in one place is tough, and I'm still not sure they know what they need. In any case, I fully expect that to be A Very Big Project.
Thanks for all the feedback so far!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
您可能在一段时间内无法获得全面的保障。但是您可以为您实现的新代码/功能编写测试。从小处开始。不要试图一次完成所有事情。
也许还有书“有效地使用遗留代码 “值得一读吗?
编辑
我还建议观看这个来自鲍勃叔叔的演示它涉及到这种情况以及如何使用“渐进加宽”将糟糕的代码库转换为好的代码库
编辑2
首先找到任何独立的函数。除了传入的参数之外不引用任何内容的函数。将它们移动并组织到辅助类中。这可能只是暂时的,因为许多人稍后会进入不同的类,但这将有助于识别一些重复的代码,并开始组织起来。然后,看看它们是如何使用的,根据这些用途编写测试。并拍拍自己的背。您现在已经开始使代码变得可维护了。
编辑 3
InfoQ 刚刚发布了另一篇文章 如何进行大规模重构 就是这类事情,还有另一篇较旧的文章,名为 重构还是重写?并且有诸如Mikado Method 你必须意识到你不能总是一步步做出你想要的举动,你必须采取其他行动来设置和实现你的目标。
You'll likely not get full coverage for some time. But you can write tests for new code/features you implement. Start small. Don't try to do everything all at once.
And perhaps the book "Working effectively with Legacy Code" is worth a read?
Edit
I also would recommend watching this presentation from Uncle Bob which touches on this scenario and how to transform a bad code base, into a good one using "Progressive Widening"
Edit 2
Start by finding any self contained functions. Functions that don't reference anything but the arguments passed in. Move and organize them into helper classes. This is likely just temporary, as many will end up in different classes later, but this will help identify some duplicated code, and start getting things organized. Then, look at how these are used, write tests based on these uses. And pat yourself on the back. You've now started making your code maintainable.
Edit 3
With great timing InfoQ just posted another article How To Do Large Scale Refactoring which is specifically this sort of thing, and another, older article called Refactor or Rewrite? and there are techniques like the Mikado Method where you have to realize you can't always make the move you want in one step, you have to make other moves to setup and realize your goal.
绝对不是。
如果您说多个事物依赖于其他事物,特别是不起作用,那么您如何开始测试它呢?
就我个人而言,我会说放弃并重新开始。四小时的专题需要 80 个小时?我希望这是夸张的说法。你一定有过的头痛。
我会从一个非常坚定的建议开始重写代码库。有时必须告诉焦急的客户一些直白的事实。有多少其他开发人员会在破损的基础上工作?制作一些漂亮的成本/收益图表。
无论如何都要为您编写的代码编写测试。不要忽视这一点。我是说我不会尝试在现有代码库上编写测试。
Absolutely not.
If you say that multiple things rely on other things specifically not working then how can you even begin to test it?
Personally I would say scrap it and start over. Four hour features that take 80? I hope this is an exaggeration. The headaches you must have.
I would start with a very firm proposal to re-write the code base. Hand-wringing clients must be told the blunt truth some times. How many other developers will work with a broken base? Make some pretty cost / benefit charts.
By all means write tests for code you write. Don't neglect that. I'm saying I wouldn't try to write tests on the existing code base.
尝试一下
编写测试使您能够进行重构。如果您以足够高的级别编写测试,您可能会设法进行重构,而不必每次都重新编写测试。
在网站的一小部分上,这是最不值得一试的(我知道您无法完全隔离任何部分,但您仍然可以定位部分)。
也许为自己设定一个时间预算(由您决定什么是负担得起的/值得的),然后尝试一些测试和一些重构。如果不起作用,请回滚。如果是,请继续。
祝你好运!
Give it a go
Writing tests enables you to refactor. If you write your tests at a high-enough level, you might manage to make it so you can refactor without having to re-write the tests every time.
It's a least worth a go, on a small part of the site (I know you won't be able to fully isolate any part, but you can still target part).
Maybe set yourself a time budget (it's down to you to work out what's affordable/worth it), then have a go with some tests and some refactorings. If it doesn't work out, roll back. If it does, carry on.
Good luck!
首先,如果您的客户习惯于您的估算是实际需要的一半,请修正您的估算!很高兴客户对预估的偏差表示“同意”,但重要的是您的预估与实际需要的工作更加一致。如果没有这一点,客户将不会同意重大重构——更不用说重写了。因此,了解一些正确估计的历史,然后重新设计该项目。
至于编写测试。这对于您所描述的内容比绿地项目更为重要。在您接触的每一段代码中,问问自己是否可以将应该存在的行为与存在的行为分离。按照应有的方式编写代码(通过测试),然后添加一个抽象层以使其保持当前的方式(并对其进行测试!)。你会觉得自己把事情搞得一团糟,而且你确实会这样——但慢慢地,随着时间的推移,测试会给你在这些领域的信心。
如果它与我一直在处理的事情类似,那么它将按照将单个方法拉出到辅助类中并将其修补回现有代码的顺序,这似乎不值得 - 但它每次都会得到回报你必须再次接触系统的那部分。就像他们说的——“让它比你发现它时更好”,每次你回来时你都会发现它处于更好的状态。测试是让它比你发现时更好的最好方法。
但说实话,在客户对您处理返工的能力充满信心之前,需要让客户对您的估算的准确性充满信心。
First, if your customer is use to your estimates being half what it actually takes, fix your estimates! It is nice the customer is 'OK' with the estimates being off -- but it is critical you get your estimates more in line with effort actually needed. Without that, what customer would ever consent to a major refactoring -- let alone a rewrite. So get some history of being right with estimates, then move to rework the project.
As for writing tests. That is even more vital for what you describe than for a green-field project. In every piece of code you touch ask yourself if it is possible to decouple the behavior that should be there from the behavior that is there. Write the code the way it should be (with tests) and then add a layer of abstraction to make it the way it currently is (and test that too!). You will feel like your adding to the mess, and you will be -- but slowly, over time, the tests will give you confidence in these areas.
If it's anything like what I've been dealing with, it will be on the order of pulling a single method out into a helper class and patching it back into the existing code, hardly seems worth it -- but it does pay off every time you have to touch that part of the system again. Like they say -- "leave it better than you found it" and you'll start finding it in better shape each time you come back to it. Tests are the best way to leave it better than you found it.
But seriously, getting the client confident in the accuracy of your estimates is required before they will be fully confident in your ability to handle a rework.
绝对要写测试。特别是在紧耦合环境中,测试将变得更加重要(因为一个区域的错误修复可能会由于紧耦合而极大地影响其他区域)。
现在,请意识到这可能不是一项微不足道的任务。为了编写测试,您需要修改代码以使其可测试。为了修改代码,您需要进行测试。所以你陷入了依赖循环......
但是,看看潜在的好处。这应该告诉你这是否真的值得。
如果你确实要开始,那就从小事做起。选择一个看起来松散耦合的小部件,然后进行测试。然后再找一些不那么纠结的东西。首先测试所有最松散的部分(容易实现的目标)。然后,一旦你到达真正紧张的部分,你就会感觉更舒服,并且(希望)对你真正需要做什么有更多的了解。
请记住,您不需要 100% 的承保才能获得好处。每个测试都增加了意义...
Absolutely write tests. Especially in a tight-coupled environment the tests are going to be even more critical (since a bug fix in one area can drastically affect other areas due to the tight coupling).
Now, realize that this will likely not be a trivial task. In order to write tests, you'll need to modify the code to be testable. In order to modify the code, you need to have tests. So you're caught in a dependency cycle...
However, look at the potential benefits. That should tell you if it is really worth it or not.
If you do start out, start small. Pick one tiny piece that looks loosely-coupled, and test that. Then find something else that's not that tangled. Test all the loosest pieces first (the low hanging fruit). Then, once you get to the really tight parts, you'll both feel more comfortable and (hopefully) have more insight as to what you really need to do.
Remember, you don't need 100% coverage to reap the benefits. Each test adds meaning...
你不能废弃它。客户不会让你这么做,而且这也可能不是最好的路径。
因此,与其引用 40 小时来修复本应花费几分钟的时间,不如引用 60 小时。客户似乎对此表示同意。使用 40 进行修复,使用 20 进行重构……并对重构的内容编写测试。如果60跑到100,那就花120; 80 个需要修复,40 个需要重构/测试。
及时将事情改进到你的正常估计中,或者找到新的工作;听起来,目前的情况会让你讨厌我们的领域。
You can't scrap it. The customer isn't going to let you, and it might not be the best path anyways.
So instead of quoting 40 hours for a fix that should have taken minutes... quote 60. The customer seems A-OK with that. Use 40 to fix, and 20 to refactor... and write tests on what you refactor. If the 60 runs to 100, then spend 120; 80 to fix, and 40 to refactor/test.
Build in time to improve the thing into your normal estimates, or find new work; the current situation, it sounds like, will drive you into hating our field.
这听起来像是为了使其可测试,您必须从头开始重写系统的某些部分 - 在这个过程中不可避免地会导致大量错误。
从你的描述来看,旧系统不值得投入这样的精力。
我在任何情况下都不会尝试对此进行测试,但会尝试尽快获得重写的许可。
如果您的客户没有看到光明,请考虑重构项目是否值得您自己花一些时间:使用干净的代码对一个人的福祉来说要好得多......
This sounds like in order to make it testable at all, you'd have to rewrite parts of the system from scratch - unavoidably causing tons of bugs in the process.
From what you describe, the old system is not worth putting that kind of effort into.
I would under no circumstances try and introduce testing for this, but try to get permission to rewrite as soon as possible.
If your client doesn't see the light, consider whether refactoring the project is worth giving some time of your own: Working with clean code is so much better for one's well-being...
最重要的事情(购买高效使用遗留代码之后)就是从小事做起。我从事多个项目,每个项目都有数千行 PHP 代码,并且通常没有单个函数(甚至不考虑对象),每当我必须更改代码时,我都会尝试将部分重构为函数并为其编写测试。这与该部分的大量手动测试相结合,因此我可以确保它像以前一样工作。当我有类似事物的多个函数时,我将它们作为静态方法移动到类中,然后逐步用适当的面向对象代码替换它们。
从将其移入函数到将其更改为真正的类的每一步都围绕着单元测试(这不是很好,因为 90% 的代码都是 SQL 查询,并且几乎不可能建立可靠的测试数据库,但我仍然可以测试行为)。
由于大量代码重复(我发现单个 SQL 查询在单个文件中重复了 13 次,在该项目的其他 50 个文件中重复了多次),我可以更改所有其他位置,但我不会,因为这些都没有经过测试我也不能确定周围的代码不以某种奇怪的方式依赖于该代码(想想
global
)。无论如何,只要我必须触及该代码,就可以更改该代码。这是一项漫长而乏味的工作,每次我看到代码时,我都会感觉自己离精神崩溃更近了一步(或者更确切地说是一个飞跃),但代码质量会提高(缓慢但大多可靠)。
您的情况似乎非常相似,所以也许我的想法可以帮助您编写代码。
简而言之
从小处开始,仅更改您正在处理的内容,并开始仅编写有限的单元测试,并随着您对系统了解的深入而扩展它们。
The most important thing (After buying Working efficiently with legacy code) is to start small. I work on several projects, each several thousand PHP lines long and often without a single function (and don't even think of objects) and whenever i have to change code i try to refactor the part into a function and write a test for it. This is combined with extensive manual testing of that part so i can be sure it works as before. When i have multiple functions for similar things i move them as static methods into a class and then, step by step, replace them with proper object-oriented code.
Every step from moving it into a function to changing it into a real class is surrounded by unit testing (not very good one as 90% of the code are SQL queries and it's nearly impossible to set up a reliable testing database, but i can still test the behaviour).
Since a lot of code repeats (i found a single SQL query repeated 13 times in a single file and many times more in the other 50 files of that project) i could change all other places, but i don't since those are neither tested nor can i be sure the surrounding code doesn't depend on that code in some wierd way (think
global
). That code can be changed as soon as i have to touch that code anyways.It's a long and tedious work and every time i see the code i feel a step (or rather a leap) closer to mental breakdown, but the code quality will improve (slowly but mostly reliably).
Your situation seems to be quite similar, so maybe my ideas might help you with your code.
In short
Start small, change only what you work on and begin to write only limited unit tests and expand them the more you learn about the system.
首先进行黑盒测试、功能测试、连接部件或各处的零碎内容。这使得持续开发和重构/重写变得更加容易。
去过那里,这样做过。
我们花了一段时间才开始添加单元测试,但最终还是做到了。
它仍然远非万无一失,但当您知道有一个测试套件等待尝试验证您的代码更改时,所有开发人员都会更有信心敢于更改/修复问题。
Start by doing black box, functional testing, connected parts or bits and pieces here and there. This makes continued development and refactoring/rewriting much easier.
Been there, doing that.
Took a while till we could start adding unit testing, but got there eventually.
It's still far from bulletproof but all developers are much more confident to dare to change/fix things when you know that there is a test suite waiting to try to verify your code changes.
从您的场景来看,您应该有一长串容易受到无害更改影响的代码脆弱区域(或者至少是绝对必须工作的区域)。如果您可以针对此列表编写测试,您就可以快速找出您正在实施的更改何时破坏了某些内容。
From your scenario, you should have a long list of fragile areas of the code that tend to be affected by innocuous changes (or at least areas that absolutely must work). If you can wright tests against this list, you have a quick way to find out when a change you're implementing has broken something.
从理论上讲,肯定是这样。维护过程耦合得越紧密,错误越多,那么测试就越重要。实际上,走开,再过一天!
In theory, definitely. The more tightly coupled, bug ridden the maintenance process then the more important the tests. In practise, walk away and live another day!
如果事情表现可靠,你就可以测试它们,对吗?您的系统大部分时间都在运行,因此您可以测试这些成功条件。
将字段分开(例如名字和姓氏)听起来像是一件潜在的大事 - 但听起来您已经吸取了教训。至少尝试为全尺寸测试系统获得一些资金,并制定适当的程序,使生产数据自动转移到其中,这样您就可以全面测试这个东西。
虽然听起来很可怕。是时候把 ole 简历尘埃落定了吗?
If things behave reliably, you can test them, right? Your system works the majority of the time, so you can test for those success conditions.
Splitting a field apart such as first and last name sounds like a potential massive thing - but sounds like you've learned your lesson. At least try to get some funding for a full size test system and put the procedures in place to make moving production data to it automatic, so you can fully test this thing.
Sounds pretty horrible though. Time to dust of the ole resume?
您可能需要考虑再收取 40 小时/迭代的费用,以创建一个很好的 BDD(域)模型来说明应用程序如何工作或更好:应该工作。这创建了一个很好的框架,您可以在其中记录所需的功能。当模型足够完整时,您可以估计将其转换为工作应用程序需要多少时间。
You might want to consider billing another 40 hours/iteration to create a nice BDD (domain) model of how the application works or better: should work. That creates a nice framework where you can document the needed features. When the model is complete enough, you can estimate how much time you'd need to convert it to a working application.