25

我们的代码很烂。实际上,让我澄清一下。我们的代码很糟糕。它很难调试,并且充满了很少有人理解甚至记得的抽象。就在昨天,我花了一个小时在一个我工作了一年多的领域进行调试,发现自己在想,“哇,这真的很痛苦。” 这不是任何人的错——我相信一开始这一切都说得通。最糟糕的部分通常是它只是工作......只要你不要求它做任何超出其舒适区的事情。

我们的新代码非常好。我认为我们在那里做了很多好事。它清晰、一致且(希望)可维护。我们已经运行了一个 Hudson 服务器以进行持续集成,并且我们已经开始使用单元测试套件。问题是我们的管理层专注于编写新代码。没有时间给旧代码(甚至旧的新代码)它迫切需要的 TLC。在任何特定时刻,我们的 scrum backlog(针对 6 名开发人员)都有大约 140 项和大约十几个缺陷。这些数字变化不大。我们正在以尽可能快的速度添加东西。

那么我能做些什么来避免陷入旧代码深处的马拉松式调试会话的头痛呢?每个 sprint 都充满了新的发展和惊人的缺陷。具体来说...

  • 我可以做些什么来帮助维护和重构任务获得足够高的优先级以进行工作?
  • 您是否采用了任何特定于 C++ 的策略来帮助防止新代码如此迅速地腐烂?
4

10 回答 10

22

您的管理层可能专注于将工作功能融入产品并保持它们正常工作。在这种情况下,您将需要为重构旧的东西制定一个商业案例,通过投入 X 的时间和精力,您可以在 Z 期间将必要的维护时间减少 Y。或者您的管理层可能从根本上毫无头绪(发生这种情况,但比大多数开发人员想象的要少),在这种情况下,您将永远不会获得许可。

你需要从业务角度看。对于最终用户来说,代码丑陋还是优雅并不重要,重要的是软件做了什么。坏代码的代价是潜在的不可靠性和更改它的额外困难;它给程序员带来的情绪困扰很少被考虑。

如果您无法获得进入和重构的许可,您可以随时自行尝试,一次一点点。每当您修复错误时,请进行一些重写以使事情更清晰。这可能会比最小可能的修复更快,特别是在验证代码现在是否有效时。即使不是这样,通常也可以花更多时间修复错误而不会遇到麻烦。只是不要得意忘形。

如果您每次进入时都可以将代码保留得更好一点,那么您会感觉好很多。

于 2010-09-01T14:39:11.460 回答
12

站立会议

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

我告诉他我希望我的轮子对齐,我的轮胎旋转,我的油换了。我提到“哦,顺便说一句,我的刹车在进来的时候感觉有点软。[他]可以看看他们吗?我多久可以把车拿回来,因为我需要回去工作?”

他把头探到我的车底下,又跳起来说我的刹车漏油并开始失效。他需要一个在上午 10:30 到达的零件。他的人不会在午餐前完成,但我应该在下午 1 点 30 分左右把车还回来。他已经预约好了,所以他今天不能做任何其他事情,我将不得不再预约一次。

我问他是否可以做其他事情,然后我回来刹车。他告诉我他真的不能让我在不修刹车的情况下开车离开那里,因为它们可能会导致事故,但如果我想去找另一个机械师,他可以要求拖车。

因为午饭后车很快就到了,我问他的人能不能吃晚饭,这样我就可以提前一个小时把车还回来。

他告诉我他的人早上 8 点进来,经常工作到晚上。他们赢得了每一次休息,他的男人应该和其他人一起吃午饭。

这些都不是我想听到的。我想听到我会在半小时内开车离开那里,轮子、轮胎和油都做好了。

我的机械师对我很坦诚。你对你的管理层坦诚和诚实吗?或者你是否避免告诉他们他们不想听到的事情?

单元测试

我不会碰我不理解的代码行,也不会签入我没有彻底测试过的新代码行。(至少,不是故意的。)

您的问题似乎暗示,不知何故,大量记录不良的代码在没有任何单元测试的情况下通过了审查。也许你参与了,也许你没有。每个参与的人都需要为此承担责任——包括管理层。无论如何,已经完成的事情。你不能回去改变它。

然而,就目前而言,每个人都有责任首先停止导致问题的行为。你说你花了一年时间编写你觉得难以理解并且没有单元测试的代码。在那一年,当您努力提高自己的理解力时,您编写了多少单元测试来记录并验证您的理解?

当你努力通过代码慢慢获得理解时,你添加了多少评论,这样你下次就不必费劲了?

Scrum 积压工作

就个人而言,我认为“Scrum backlog”一词用词不当。要做的事情的清单只是一个清单——如果你愿意的话,一个购物清单。我去修理工的时候有一份清单。我与机械师的站立会议实际上更像是一次冲刺计划会议。

冲刺计划会议是一次谈判。如果您的管理层在没有协商的情况下进行时间拳击,那么他们就没有管理任何事情。他们只是想把 10 磅的屎塞进一个 5 磅的袋子里,你有责任告诉他们。

当你参加 sprint 计划会议时,你应该承诺做大量的工作,你有责任为此做好准备。准备意味着对完成列表中的每个项目需要做的事情有所了解——包括理解晦涩代码所花费的时间和编写单元测试所花费的时间。

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

如果您有一个没有单元测试的现有代码体,并且可以想象某个功能可能会影响该代码的操作,那么您需要为可能受到影响的尽可能多的旧代码编写单元测试。当您致力于编写该功能时,您就是在致力于完成这项工作。如果这让您没有时间致力于其他一些功能,那就这么说吧。不要承诺其他功能。

当您承诺修复缺陷时,您承诺测试您的工作。显然,这意味着为缺陷编写单元测试。但是,如果它涉及没有单元测试的旧代码,这也意味着为尚未破坏但可能由于您的更改而破坏的东西编写单元测试。您还将如何测试修复?

如果您的缺陷列表保持不变的大小,那么您的团队会在修复的同时退步。礼貌地向任何需要了解单元测试防止当前使您的缺陷列表缩小的回归的人解释。

如果你因为提交了太多特性而未能编写这些单元测试,那是谁的责任?

重构

当您重构代码时,您必须测试所有代码,这意味着为所有代码编写单元测试。如果您有大量没有单元测试的代码,则必须在重构之前编写所有这些单元测试。

我建议您推迟重构,直到这些单元测试到位。与此同时,如果你坚持在你所承诺的工作的估计中包含单元测试,最终所有这些单元测试都会在那里。然后你可以重构。

一个例外是重构可测试性。您可能会发现某些代码不是为测试而设计的,并且您必须在创建单元测试之前重构依赖注入之类的东西。当您致力于编写需要单元测试的功能时,您就致力于使代码可测试。当您提交该功能时,将其包括在您的估计中。

承诺+责任=力量

你说你无能为力。当你接受责任并承诺做需要做的事情时,我想你会发现你拥有你需要的所有力量。

PS 如果有人抱怨任何人在修复单个缺陷时“浪费时间”编写多个单元测试,请向他们展示这段关于 80:20 规则的视频,并将“缺陷集群”灌入他们的大脑。

于 2010-09-01T17:21:00.920 回答
2

旧代码总是很糟糕。可能有一些罕见的例外是由 Kernighan 或 Thompson 之类的人编写的,但是对于典型的“在办公室编写的代码”的东西,随着时间的推移,它会变得很臭。开发人员变得更有经验。更新的实践,例如持续集成,改变了游戏规则。东西被遗忘了。新的维护者无法掌握设计并希望重写。所以最好接受这是正常的。

一些随机的事情可能会有所帮助......

  • 与您的团队讨论它。分享你的经验和你的担忧,同时避免“你的旧代码很糟糕”(出于显而易见的原因),看看共识是什么。你可能并不孤单。
  • 忘记你的经理。不要让他们接触到这种级别的细节——他们不需要考虑新代码和旧代码,如果他们这样做可能不会理解。这是您的团队需要解决的问题,并在必要时让您的 PO 意识到
  • 对您可能会扔掉东西的可能性持开放态度。其中一些旧代码可能与用户一开始就不再使用或未能采用的功能有关。要使这项工作为您服务,您确实需要更上一层楼,并考虑代码真正交付用户或业务价值的地方,而不是没有人敢于做出决定的泥球。谁敢做谁赢。
  • 放松您对架构一致性的看法。总有一种方法可以在某个地方使用新代码进入工作系统,这可能会让您慢慢迁移到更新、更智能的方法,同时保留旧的足够长的时间,不会破坏现有的东西。

总体而言,在这种情况下取​​胜与编码技能无关,更多的是关于明智的选择和处理人性方面的问题。

希望有帮助。

于 2010-09-02T20:12:31.407 回答
2

从你提供的信息中很难看出很多。我会遇到的一些问题是编写新代码的合乎逻辑的原因是替换旧代码。如果那是您正在做的事情,请放弃旧代码。

旧代码是否也存在显着缺陷?如果是这样,他们来自哪里?旧代码没有“showstopper”缺陷,它通常只是越来越接近停止。毕竟它是旧代码——它应该具有相同的旧缺陷和相同的旧限制,而不是必须立即查看的内容。Showstopper 缺陷是新的代码缺陷。听起来旧代码中正在进行积极的开发。

如果您在糟糕的旧代码之上编写所有这些新代码,并且没有计划一劳永逸地修复它,对不起,当您忙于埋葬自己而无法挖掘自己时,您只能做这么多。

如果是后者。你应该认清你的方向,并试着分离一点。如果您打算在附近为有价值的战斗节省力量,那么最终一切都会崩溃。

同时尝试学习一些设计模式。有几个至少可以帮助您保护新代码免受旧代码的影响,但最终很难编写好代码来对抗坏代码。

你的冲刺听起来可能很混乱。没有大方向吗?这应该决定你有多少积压,虽然事情可能会逐月改变,但是否没有明确的朝着某个最终目标前进的感觉?

新代码腐烂?防止这种情况发生的方法是你拥有一个有意义的设计、一个有意义的方向,以及一个致力于工作质量和设计愿景的质量团队。如果你有这个,纪律就是保持质量的东西。如果您没有那么抱歉,那么您基本上是在毫无目的地编写代码。它基本上在藤上腐烂了。

不挑剔,只想说实话。深吸一口气。减速。你似乎需要它。看看你在这里写了什么。它什么也没说。你谈到重构、scrums、showstoppers、缺陷、旧代码、新代码。这意味着什么?一切都乱七八糟了。

“新计划与旧系统”呢?“需要根据最新的理解等来重构早期的 sprint 周期代码。” 事实上,“当前企业计划的早期组件已经发布,但遇到了问题,并且由于新的开发而没有时间预算”。

这些将是有意义的概念。你什么都没给我们。我明白这很激烈。我的 sprint 也很疯狂,我们添加了很多 back;pg 项目,因为我们无法预先获得很多需求(我的很多新需求也是由于不得不与外部监管机构竞争,正常的业务流程并不总是可用)。

但与此同时,我被必须要做的事情的规模和时间所震撼。添加到我的待办事项中的所有内容都需要在那里。这很疯狂,但同时我非常清楚我去过哪里,我需要去哪里,以及为什么这条路越来越难。

退后一步,清理你的想法,弄清楚同样的事情——你去过哪里,你要去哪里。因为如果你知道这一点,那肯定是不明显的。如果您无法与同事交流任何可以理解的内容,那么您将与业务经理合作多远?

于 2010-09-01T14:05:10.297 回答
1

我建议跟踪有多少错误和代码更改涉及您的“旧代码”,并在下次团队会议上将其提交给您的经理或其他开发人员。有了这个,它应该足够简单,以说服他们需要做更多的工作来重构您的“旧代码”并使其与您的“新代码”相提并论。

记录“旧代码”中最难理解的部分也是谨慎的做法。这些也将是您的“旧代码”的部分,一旦您获得批准,您应该首先重构它们。

于 2010-09-01T13:50:20.277 回答
1

可以尝试的方法:将你的班级分为——比如说——最差的 10%、最好的 10%,以及其他。将列表提交给您的管理层,并说:“我预测下一季度的大多数错误将在第一组中找到。” 基于长度、圈复杂度、测试覆盖率——任何对您来说方便和舒适的工具。然后坐下来观看 - 并且是正确的。现在,当您说:“我想投入一些资源来改进我们的糟糕代码,以减少错误和维护成本时,您已经有了一些可信度和一些影响力——而且我知道将这些精力投入到哪里,明白吗?”

于 2010-09-01T16:11:23.397 回答
0

您可以创建新代码如何工作以及类和函数如何相互关联的图表和草图。你可以使用 FreeMind 或者 Dia。我绝对同意记录和评论您的代码。我曾经也遇到过这个问题。我为我自己的语言为 J2ME 编写了一个字体类。由于这些原因,您可能还会在代码中看到这很糟糕。

  • 没有评论或文档
  • 较少面向对象
  • 错误的变量/函数名称
  • ...

但几个月后,我被迫重新写了整本书。现在我学会了使用有时很长的有意义的变量名。写评论而不是写代码。并为项目的类及其关系使用图表。

我不知道这是否是一个真正的答案,但它肯定对我有用。对于旧代码,您实际上可能需要重新阅读整个内容并在记住功能时添加注释。

希望它有所帮助。

于 2010-09-01T14:58:51.503 回答
0

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

为了保持未来的代码干净

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

清理旧代码:

  • 单元测试非常适合帮助你重构和弄清楚事情是如何工作的。
  • 我同意需要为大规模重构制定业务案例的意见。但是,小规模的重构可以很容易地包含在估算中,并将立即提供回报。即:我花了 2 个小时重写一篇文章,但无论如何我都会花时间寻找错误。

您可能还需要考虑让产品负责人和 scrummaster 为旧代码和新代码捕获单独的速度,并相应地使用它。

于 2010-10-13T13:38:34.653 回答
0

如果有一个所需的新功能,并且您可以描述一个非压倒性的代码块,那么您可能会得到管理层的祝福,用具有所需新功能的新代码替换旧代码。当我这样做时,我不得不编写一个有点丑陋的垫片层来满足我不会接触的软件部分的旧接口。一个测试工具可以运行现有代码并运行新代码,以确保通过 shim 层看到的新代码可以欺骗应用程序的其余部分,使其认为没有任何变化。通过重做我们重做的部分,我们能够展示出巨大的性能优势、与所需新硬件的兼容性、减少我们每个现场站点对应用程序管理空间的专业知识的需求——以及新代码易于维护。最后一点对用户来说无关紧要,但返工的其他优势足以吸引用户“推销”用户,因为数据库转换有点痛苦。

另一个较为温和的成功案例:我们有一个相当不错的故障跟踪系统,它已经有多年的历史了。我们的应用程序有一个子系统,它以烧毁维护程序员的速度而闻名。显然(好吧,在我看来很清楚)它需要进行重大改写,但管理层对此并不热衷。我们能够挖掘故障跟踪数据中的历史记录,以显示维护该模块的人员配备水平,尽管付出了所有努力,但每月针对该模块的故障单仍以恒定的速度到达。当面对这样的实际数据时,即使是那些长期对子系统的人员配置返工不情愿的管理人员也能看到指派人员返工该模块的好处。

以前的方法是不理会该模块的输入和输出。好消息是,在具有花哨的新数据结构的新代码中投入虚拟内存确实给模块带来了显着的性能改进。坏消息是,在我们真正了解原始实现中的错误之处之前,我们几乎完成了重新实现,因此它在大多数情况下确实有效,但在某些日子里在某些事务上失败了。第一次剪辑忠实地再现了这些错误,但在重新编写的代码中这些错误更容易理解,所以我们现在有机会真正解决真正的问题。回想起来,也许我们会更聪明地捕获产生问题的数据,并更加注意确保重新设计的版本不会重现该问题。但,事实是,在我们进行重写之前,没有人理解这个问题。因此,重写为用户提供了改进的性能,并提高了当前程序员的理解,从而最终真正解决了真正的问题。

一个失败的例子:还有另一个令人难以置信的丑陋模块一直是一个痛处。唉,我不够聪明,无法理解这个由人渣和恶棍组成的可怜蜂巢的实际接口,至少在名义发布时间表的时间范围内是这样。我愿意相信,如果有更多的时间,我们也可以想出一个合适的计划来重新设计系统的那部分,也许一旦我们理解了它,我们甚至可以确定我们可以适应的用户期望的改进。改写。但我不能保证你会在每个盒子里找到奖品。如果这个盒子对你来说完全是模糊的,那么切掉它的一大块并用干净的代码替换那块是很难做到的。负责那个模块的人可能是最有能力制定攻击计划的人,但他将频繁的撞车事故和来自现场的求助电话视为“工作保障”。我认为管理层从来没有真正意识到他需要为渴望改变的人而放松,但这可能是需要的。

德鲁

于 2012-03-12T01:04:32.140 回答
0

与您的产品负责人交谈!说明一旦消除了这个障碍,投入到重构旧代码上的时间将为他带来更高的团队在新功能上的速度的好处。

于 2010-09-02T11:14:04.673 回答