我们的代码很烂,而我又没有能力去改善它。求救!

25

我们的代码很烂。实际上,让我澄清一下。我们旧的代码很烂。它很难调试,并且充满了很少有人了解或甚至记得的抽象概念。就在昨天,我花了一个小时在我已经工作了一年多的某个区域进行调试,并发现自己想:“哇,这真的很痛苦。”这不是任何人的错——我相信最初所有东西都很完美合理。最糟糕的部分通常是它只是能够正常工作...只要你不让它做任何超出其舒适区域的事情。

我们的新代码还不错。我认为我们正在做很多好事情。它清晰、一致,而且(希望)易于维护。我们有一个Hudson服务器用于持续集成,并且我们已经开始编写单元测试套件。问题在于我们的管理层专注于编写新代码。没有时间给予旧代码(甚至是旧的新代码)必要的关注和维护。在任何时候,我们的Scrum待办事项列表(针对六名开发人员)大约有140个项目和大约十几个缺陷。而且这些数字几乎没有变化。我们正在尽可能快地添加新功能。

那么,我应该如何避免在旧代码的深处陷入马拉松式调试会话的头疼?每个迭代都充满了新开发和关键性缺陷。具体来说...

  • 我可以做什么来帮助维护和重构任务获得足够高的优先级以得到解决?
  • 有没有任何C++特定的策略你采用,以帮助防止新代码很快就过时?

2
你的旧代码有文档吗?你可以在新代码中添加有用的注释/文档,以防止它过快地腐烂。 - In silico
24
我之前从未这样做过,因此不想将其作为答案发布,但也许如果您记录一下在旧代码中进行调试浪费的时间,您可以向管理层展示一些数字,并说服他们如果他们先让您修复旧代码,他们实际上会得到更多他们想要的东西(新代码)。 - Cascabel
10
通常最糟糕的部分是它只是工作了... 实际上,那不是最糟糕的地方。最糟糕的不是它难看,或者你不能提到它:而是你需要花时间调试它。最终目标是能够运行。可维护性只是达成这个目标的手段,仅此而已。以这种方式思考可能有助于让你从管理者的角度看待事物,并为积极维护的好处提出案例,如果这项工作通过减少调试工作而实现自我回报。 - Steve Jessop
5
例如,你能展示新代码中的错误修复和新功能添加比旧代码中的错误更快/更容易/更便宜吗?提高了多少效率?会进行多少此类更改?要将旧代码达到与新代码相当的状态需要多长时间?在没有做任何特殊计划的情况下,旧代码将在多少年后变得过时或自然替换为新代码?如果一个问题本来会自己解决,那么花费6个月时间去做一些只能节省1个月工作时间的事情是没有多大意义的... - Steve Jessop
4
程序员定律:旧代码总是烂的。 :) - Zan Lynx
显示剩余2条评论
10个回答

22

你的管理层可能会专注于将工作功能引入产品并保持其正常运行。在这种情况下,你需要为重构旧代码提出商业理由,即通过X时间和精力的投入,可以在Z期间内减少Y的必要维护时间。或者你的管理层可能根本无知(这种情况很少发生,但大多数开发人员似乎认为会经常发生),在这种情况下,你将永远得不到许可。

你需要了解商业角度。对最终用户来说,代码是丑陋还是优雅并不重要,只关心软件执行什么功能。糟糕代码的成本是潜在的不可靠性和更难以更改;它给程序员带来的情感困扰很少得到考虑。

如果你不能获得重构的许可,你总可以自己尝试一点点地进行修改。每当你修复一个错误时,可以稍微重写一些东西以使事情更清晰。这可能比最小可能的修复更快,特别是在验证代码现在起作用方面。即使不是这样,通常也可以在修复错误时多花一点时间而不会遇到麻烦。只是不要太过分。

如果你每次进去时都能留下一点更好的代码,你会感觉好很多。


我同意这是一个好策略,这也是我过去在那些让我头痛的旧代码上采用的策略。虽然现在它比过去好多了,但仍然很痛苦。一个开发者只能通过缓慢改进来取得有限的进展。 - Michael Kristofik
1
我以前使用过“不仅仅是修复”的方法,效果很好。我也同意不要过度,我只是讨厌糟糕的代码 :/ - Matthieu M.
1
+1 表示赞同,但比大多数开发人员认为的要少。 - Binary Worrier

12

站立式会议

我可能会去找我的机械师,我们早上有一个小型的站立式会议:

我告诉他我想要轮毂对齐、轮胎旋转和换油。我顺便提了一下: "顺便说一下,我开车过来时刹车感觉有点软,能不能检查一下?我需要什么时候拿回我的车,因为我需要回去工作。"

他把头伸到我的车底下,然后又抬起身来说道:“我的刹车正在漏油,快要失效了。他需要等一个备件,10:30am才能到货,他的员工在中午之前赶不完,但是1:30pm左右我应该可以拿回车子。他的预约已经排满了,所以今天不能做其他的事情,我必须再次预约。”

我问他是否可以做其他的事情,等制动器好了再回来。他告诉我,因为这些制动器可能会导致事故,所以他实在不能让我离开那里,但如果我想去找另外一个机械师,他可以叫人拖车。

由于车很快就会在午餐后完成,我问他的员工是否可以推迟午餐时间,这样我就可以提前一个小时拿回我的车。

他告诉我,他的员工早上8点钟进来,通常工作到晚上。他们赚得每一分休息时间,并且他的员工应该和其他人一起休息。

那些都不是我想要听到的。我希望听到的是,在半个小时内我可以开着我的车子出去,轮毂、轮胎和油都换好了。

我的机械师对我很直接和诚实。你对你的管理层也是这样直接和诚实吗?还是你避免告诉他们他们不想听到的事情?

单元测试

我不会碰那些我不理解的代码行,也不会提交我没有彻底测试过的新代码。(至少是无意地)

你的问题似乎暗示了一个大量文档不好的代码库通过审核并没有进行任何单元测试。也许你参与了其中,也可能没有。每个人都需要承担责任,包括管理层。无论如何,已经发生了的事情就是这样。你无法回头去改变它。

然而,现在,当下,每个人都有责任停止导致问题的行为。你说你花了一年的时间在难以理解且没有单元测试的代码中工作。在那一年中,当你努力提高自己的理解时,你写了多少个单元测试来记录和验证这些理解呢?

在你艰苦地学习代码的过程中,你又添加了多少注释,以便下次不必再费力?

Scrum Backlog

就我个人而言,我认为“Scrum backlog”这个术语是个错误用词。待办事项列表只是一个列表——如果你愿意,可以称之为购物清单。我去修车时有个清单。我与修车工人的站立会议实际上更像是一个冲刺计划会议。

一个冲刺计划会议是一次谈判。如果你的管理人员进行时间盒处理却没有这样的谈判,他们什么也没有管理。他们只是试图将10磅的粪便塞进5磅的袋子里,而你有责任告诉他们这一点。

当你参加一个冲刺计划会议时,你需要承诺完成一项工作,这是你的责任,也要为此做好准备。准备意味着对每个列表项完成所需的工作有一些想法——包括理解模糊代码所需的时间和编写单元测试所需的时间。

如果有人邀请你参加一个你没有时间做准备的计划会议,请拒绝并建议何时重新安排以便你有时间准备。

如果你有一组没有单元测试的现有代码,并且某个功能可能会影响该代码的操作,那么你需要为可能受到影响的旧代码编写单元测试。当你承诺编写该功能时,你就承诺要做这项工作。如果这使你没有足够的时间去承诺其他功能,只需说出来。不要承诺其他功能。

当你承诺修复错误时,你承诺测试你的工作。显然,这意味着为缺陷编写单元测试。但是,如果它涉及没有单元测试的旧代码,这也意味着编写单元测试以防止因你的更改而导致的潜在问题。否则,你该如何测试修复?

如果你由于承诺了太多功能而未能编写这些单元测试,那么责任是谁的呢?

重构

当你重构代码时,你必须测试所有代码,这意味着为所有代码编写单元测试。如果你有一大堆没有单元测试的代码,那么在重构之前,你必须先编写所有这些单元测试。

我建议你等到这些单元测试就位后再进行重构。同时,如果你坚持将单元测试纳入你承诺的工作估计中,最终所有这些单元测试都将到位。然后你就可以进行重构了。

唯一的例外是为了可测试性而进行的重构。你可能会发现,有些代码没有设计用于测试,你必须先进行重构,例如依赖注入等操作,然后才能创建你的单元测试。当你承诺编写需要单元测试的功能时,你就承诺使代码可测试。在承诺该功能时,请包括此内容在内。

承诺+责任=权力

你说你无权无助。当你承担责任并承诺做必要的事情时,我认为你会发现你拥有所需的所有权力。

附言:如果有人抱怨在修复单个缺陷时编写多个单元测试浪费时间,请向他们展示这个视频中的80:20规则并将“缺陷簇”深入他们的脑海。


我不反对你的诚实评估。我不会因为我们现在的处境而责怪任何人。敏捷软件开发(Scrum + TDD + CI)对我们团队来说是一种文化转变。我们正在朝着正确的方向前进,尽管速度非常缓慢。 - Michael Kristofik
如果您的缺陷清单保持不变,那么您的团队修复了多少缺陷,就会出现同样数量的回归。我认为列表保持恒定大小意味着新的功能请求和缺陷被识别的速度与它们被处理的速度一样快,而不一定是以此速率创建新的缺陷。显然,从长远来看,这种匹配的源/汇率是理想的 - 您不能清除已提出的更多项目,因此达到平衡点就是最好的结果。问题在于典型的队列大小是否“应该”为140个项目、14个项目或1400个项目。 - Steve Jessop
Kristo,我的回答与责备无关。它与权力有关,责任、承诺和力量。Steve,问题不在于持续的140个缺陷;而是那些持续的“几十个缺陷”。当然,相比增加的情况,保持稳定仍然是更好的。但是,稳定意味着团队会引入与修复一样多的缺陷。否则,随着缺陷总数的减少,发现的数量也会减少。 - bbadour
对于开发人员来说,这是一个艰难但绝对重要的教训:“你是否直言不讳地与管理层沟通?还是避免告诉他们他们不想听的事情?” - Dan
@丹,我们正在考虑这个问题,但这并不重要。许多新代码都来自于在最后期限之前一个迭代中出现的弹出任务。我们可以推迟,但暗示是“完成它,否则我们会找到其他能够完成它的人。” - Michael Kristofik

2
从您提供的信息很难得出太多结论。 写新代码的逻辑原因是要替换旧代码。 如果这就是您正在做的事情,请放弃旧代码。
这也是旧代码具有致命缺陷吗? 如果是这样,它们是从哪里来的? 旧代码不会有“致命”缺陷,通常只会越来越慢而已。 毕竟,它是旧代码 - 它应该有相同的老缺陷和相同的老限制,而不是必须立即查看的东西。 致命缺陷是新代码中的缺陷。 看起来旧代码正在积极开发中。
如果您正在在烂代码之上编写所有这些新代码,而没有计划彻底修复它,抱歉,当你忙于为自己埋葬时,你只能做那么多事情。
如果是后者,请认识到你正在走向何方,并尝试多分离一些时间。 如果你打算留下来,它最终会全部崩溃,所以为值得的战斗保存你的力量。
同时尝试学习一些设计模式。 有几种可以帮助保护你的新代码免受旧代码的影响,但最终,对付烂代码还是很难写出好的代码。
你们的sprint听起来可能有些混乱。 没有总体方向吗? 那应该确定你有多少待办事项,尽管情况可能会在月度之间发生变化,但是否有一个明确的朝着某个最终目标前进的感觉?
新代码腐烂? 你预防它的方法是拥有有意义的设计,有意义的方向以及致力于工作质量和设计愿景的优秀团队。 如果你拥有这些,纪律就是保持质量的关键。 如果没有,抱歉,你已经在没有目的地编写代码了。 它基本上已经腐烂了。
并不是批评,只是想诚实些。 深呼吸。 放慢速度。 看看您在这里写的内容。 它什么也没说。 您谈论重构,scrum,showstopper,defect,old code,new code。 这一切都混在一起。
“新倡议与传统系统”怎么样? “需要根据最新理解早期sprint周期代码进行重构等。” Showstopper实际上是“当前企业倡议的早期组件已发布但遇到问题,因为有新开发而没有预算”。这些都是有意义的概念。 您并没有给我们任何东西。 我了解这很紧张。 我们的sprint也很疯狂,因为我们无法从一开始就获得很多要求(我许多新要求的来源都是必须处理外部监管机构,通常业务流程不可用)。

但与此同时,我被所要完成的任务量和时间所压垮。我的待办事项列表中的每一项都必须存在于那里。这很疯狂,但同时我非常清楚自己曾经走过的路、需要前往的地方以及为什么道路变得更加艰难。

退后一步,清空思维,弄清自己曾经所处的位置和目标的相同之处。因为如果你知道了这些,它就不是那么显而易见了。如果你无法向同事传达任何他们能理解的内容,那么你在商业管理方面会有多少进展呢?


2

旧代码总是不行的。可能会有某些罕见的例外,由像Kernighan或Thompson这样的人编写,但对于典型的“办公室编写的代码”,随着时间的推移,它们会变得越来越糟糕。开发人员变得更加有经验。新的实践,例如持续集成,改变了游戏规则。有些东西会被遗忘。新维护者无法理解设计并希望重写。所以最好把这看作是正常现象。

以下是一些可能有用的随机建议...

  • 与团队讨论。分享你的经验和担忧,同时避免“老代码不行”(出于显而易见的原因),看看共识是什么。你可能不是唯一一个有这种问题的人。
  • 忘掉你的经理。不要让他们接触到这种细节 - 他们不需要考虑新代码与旧代码的差异,如果他们确实考虑了,他们可能也不会理解。这是你的团队需要解决的问题,必要时需要让PO意识到。
  • 接受有可能可以扔掉一些东西的可能性。一些旧代码可能涉及到已不再使用或未能被用户采用的功能。要使这对你起作用,你需要从更高的层次上思考,在哪些代码中真正提供了用户或业务价值,而在哪些代码中只是一团乱麻,没有人敢做出决策。谁敢尝试就会获胜。
  • 放宽你对架构一致性的看法。总有一种方法可以使用新代码连接到工作系统的某个地方,并且这可能允许您慢慢迁移到更新的、更智能的方法,同时保留旧有的东西而不破坏现有的东西。

总的来说,在这种情况下取得胜利的方式与编码技能关系较小,而与明智的选择和处理人为因素有关。

希望这些有所帮助。


1

我建议你跟踪一下有多少错误和代码更改涉及到你的“旧代码”,并在下次团队会议上向经理或其他开发人员展示这些情况。拿着这些报告,你应该能够很容易地说服他们,需要对你的“旧代码”进行重构,使其与你的“新代码”保持同步。

此外,还应该明智地记录下最难理解的“旧代码”部分。一旦得到批准,那么你应该首先对这些部分进行重构。


1

尝试一些事情:将你的班级分成最差的10%,最好的10%和其余部分。将这些列表交给你的管理层,并说:“我预测在接下来的一个季度中,大多数错误将在第一组中发现。”根据长度,圈复杂度,测试覆盖率 - 无论使用什么工具都方便和舒适。然后坐下来观察 - 并且是正确的。现在你有了一些信誉,当你说:“我想投资一些资源来改善我们的糟糕代码,以减少错误和维护成本 - 我知道在哪里投资那种能量,看到了吗?”


0
你可以创建新代码的图表和草图,以及类和函数之间的关系。你可以使用FreeMind或者Dia。我完全同意记录和注释你的代码。
我曾经也遇到过这个问题。我为自己的语言编写了一个J2ME字体类。它很糟糕,可能是因为以下原因,你在你的代码中也可能会看到。
  • 没有注释或文档
  • 面向对象程度较低
  • 变量/函数名称不好
  • ...
但是几个月后,我被迫重新编写整个东西。现在我学会了使用有意义的变量名,有时非常长。写注释比写代码更多。并使用项目类及其关系的图表。
我不知道这是否是真正的答案,但对我来说绝对有效。对于旧代码,你可能需要重新阅读整个代码,并在记得功能时添加注释。
希望能帮到你。

Hudson每晚为我们运行Doxygen构建,所以就像我所说的,新的东西我们处于相当不错的状态。需要真正清晰文档的是旧代码,而我们没有时间编写它。 - Michael Kristofik

0

与您的产品负责人交流!解释一下,投入时间进行旧代码的重构将为他带来更高的团队速度,一旦这个障碍被消除,就可以在新功能上获得更多的好处。


0

除了上述提到的好方法,您还可以尝试以下方法:

为保持未来代码的清洁

  • 尝试配对编程,至少对于有意义的部分。这是一种有效的方式,可以得到经过审查、重构的代码实践。
  • 尝试将重构纳入“完成”的定义中。然后它将成为估算过程的一部分,并相应地分配。因此,“完成”的定义可能包括:编码、单元测试、功能测试、性能测试、代码审查、重构和集成(或类似内容)。

用于清理旧代码:

  • 单元测试非常适合帮助您重构和弄清楚事情的工作原理。
  • 我同意评论中需要为大规模重构制定商业案例。但是,小规模重构可以轻松地包含在估算中,并提供即时回报。例如:我花费2个小时重写一个部分,但我本来也会花时间寻找错误。

您还可以考虑让产品负责人和Scrum Master捕获旧代码与新代码的不同速度,并相应地使用它们。


0
如果有一个期望的新功能,而且你能够划分出一段不会过于复杂的代码,那么你可能会得到管理层的认可,用具备所需新功能的新代码替换旧代码。当我这样做时,我不得不编写一个相当丑陋的桥接层来满足我不打算修改的软件部分的旧接口。还要编写一个测试工具,可以对现有代码和新代码进行测试,以确保通过桥接层看到的新代码可以欺骗应用程序的其余部分,使其认为没有发生任何变化。通过重新设计我们重构的部分,我们能够展示巨大的性能优势、与所需新硬件的兼容性、减少每个现场需要专业知识来管理应用程序空间的需求,而且新代码更易于维护。最后一点对用户来说并不重要,但是重构带来的其他优势足以“卖”给用户一个有点痛苦的数据库转换的优点。
另一个更为谨慎的成功案例:我们有一个相当不错的故障跟踪系统,它拥有多年的历史记录。我们的应用程序中有一个子系统以其让维护程序员崩溃的速度而闻名。显然(在我看来很明显),它需要进行重大改写,但管理层对此并不热衷。我们能够通过故障跟踪数据中的历史记录来查明维护该模块所需的人员水平,并且尽管付出了所有这些努力,每月针对该模块的故障单数量仍以恒定的速度到达。当面对这样的实际数据时,即使是一直对该子系统的重新分配人员持紧握预算的管理者也能看到将工作人员分配到重写该模块的价值。
以前的方法是不改变该模块的输入和输出。好消息是,将虚拟内存应用于新代码及其新的数据结构确实使该模块的性能有了明显的提升。坏消息是,在重新实现之前,我们几乎完成了对原始实现中出现问题的真正理解,因为它大多数时候都能正常工作,但在某些交易日上会失败。第一次尝试忠实地复制了这些错误,但在重新编写的代码中更容易理解这些错误,因此我们现在有机会真正解决真正的问题。回想起来,也许我们应该更聪明地捕获产生问题的数据,并更好地确保重新编写的版本不会再次出现该问题。但事实是,在重新编写的过程中,直到我们已经进行了相当长的时间,没有人真正理解这个问题。因此,重新编写为用户提供了改进的性能,并为当前程序员提供了更好的理解,以便最终真正解决真正的问题。
一个失败的例子:还有一个非常丑陋的模块一直是个痛点。可惜,我不够聪明,无法在名义发布时间的时间框架内理解这个可悲的罪恶之地的事实接口。我希望如果有更多时间,我们可以找到一个适当的计划来重新设计系统的那部分,并且一旦我们理解了它,我们甚至可以确定用户期望的改进,以便将其纳入重写中。但我不能保证你会在每个盒子里都找到奖品。如果整个盒子对你来说完全模糊,那么切掉一块并用干净的代码替换它就很难做到。负责该模块的人可能是最适合制定攻击计划的人,但他认为频繁崩溃和现场寻求帮助的呼叫是“工作保障”。我认为管理层从未真正意识到他需要被推到一边,让渴望变革的人来接替他,但这可能是必要的。
Drew

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