25

我继承了一个项目,其中类图非常类似于意大利面上的蜘蛛网。在过去的两个月里,我编写了大约 300 个单元测试来给自己一个涵盖主要可执行文件的安全网。

在任何特定时刻,我的敏捷开发书籍库都触手可及:

  • 有效地使用遗留代码
  • 重构
  • 代码完成
  • C# 中的敏捷原则模式和实践
  • 等等

问题是我触摸的所有东西似乎都破坏了其他东西。UI 类混合了业务逻辑和数据库代码。许多类之间存在相互依赖关系。每次我更改任何其他课程时,都会有几个上帝课程中断。还有一个突变的单例/实用程序类,其中包含大约一半的实例方法和一半的静态方法(尽管具有讽刺意味的是,静态方法依赖于实例而实例方法不依赖)。

我的前辈甚至认为向后使用所有数据集会很聪明。每个数据库更新都作为存储过程中的参数直接发送到数据库服务器,然后手动刷新数据集,以便 UI 显示最近的更改。

我有时会认为他们使用了某种形式的弱混淆来保证工作安全,或者作为交出代码之前的最后告别。

有没有什么好的资源来解决这个烂摊子?我的书很有帮助,但似乎只涵盖了我遇到的一半情况。

4

13 回答 13

24

听起来您正在以正确的方式解决它。

  • 测试
  • 重构
  • 再次测试

不幸的是,这可能是一个缓慢而乏味的过程。真的没有什么可以替代挖掘和理解代码试图完成的事情。

我可以推荐的一本书(如果您还没有将其归档在“等”下)是Refactoring to Patterns。它适用于处于您确切情况的人。

于 2009-03-17T16:12:26.967 回答
19

我在类似的情况下工作。

如果它不是一个小型实用程序,而是一个大型企业项目,那么它是:

a) 修复它为时已晚
b) 超出一个人的能力来尝试 a)
c) 只能通过完全重写不可能的东西来修复

在许多情况下,重构只能在您的私人时间尝试,风险自负。如果您没有明确授权将其作为日常工作的一部分,那么您甚至可能不会因此获得任何荣誉。甚至可能被批评为“毫无意义地将时间浪费在已经完美运行了很长时间的事情上”。

只需像以前一样继续破解它,收到你的薪水等等。当您完全感到沮丧或系统达到无法进一步破解的程度时,请找另一份工作。

编辑:每当我试图解决真正架构的问题并以正确的方式做事时,我通常会直接从负责任的经理那里得到大声笑,他们说“我不关心好的架构”(尝试过德语翻译)。我个人将一个非常糟糕的组件带到了不可破解的地步,当然也提前几个月发出了警告。然后,他们不得不取消向客户承诺的一些功能,因为它不再可行。再也没有人碰它了……

于 2009-03-17T16:12:32.020 回答
8

我以前做过这份工作。我花了两年多的时间研究一个非常相似的传统野兽。我们两个人花了一年多的时间来稳定一切(它仍然坏了,但更好了)。

第一件事 - 如果应用程序不存在,则将异常记录到应用程序中。我们使用了FogBugz,我们花了大约一个月的时间将报告集成到我们的应用程序中;它并不完美,但它会自动报告错误。在所有事件中实现 try-catch 块通常是非常安全的,这将涵盖您的大部分错误。

从那里修复首先出现的错误。然后打小战斗,尤其是那些基于错误的战斗。如果您修复了一个意外影响其他内容的错误,请重构该块,使其与其余代码分离。

重写一个对公司成功至关重要的大型应用程序,无论它有多糟糕,都需要采取一些极端措施。即使您获得了这样做的许可,您也将花费太多时间来支持遗留应用程序,以在重写方面取得任何进展。如果您进行许多小的重构,最终要么大的不会那么大,要么您将拥有非常好的基础类来进行重写。

值得一提的是,这是一次很棒的体验。这会令人沮丧,但你会学到很多东西。

于 2009-03-17T16:40:28.787 回答
4

我(曾经)遇到过非常纠结的代码,以至于我无法在合理的时间内用功能副本修复它。不过那是一种特殊情况,因为它是一个解析器,我不知道有多少客户可能正在“使用”它的一些错误。错误地渲染数百个“工作”源文件不是一个好的选择。

大多数时候它是迫在眉睫的,只是令人生畏。通读那本重构书。

我通常会通过稍微移动一些东西来修复错误的代码(实际上不会更改超出要求的实现代码),以便模块和类至少在某种程度上是连贯的。

完成后,您可以使用更连贯的类并重写其内容以完全相同的方式执行,但这次使用合理的代码。这是管理的棘手部分,因为他们通常不喜欢听到您将花费数周时间来编写和调试行为完全相同的东西(如果一切顺利的话)。

在这个过程中,我保证你会发现大量的错误,以及彻头彻尾的设计愚蠢。在重新编码时修复一些小错误是可以的,否则将这些东西留到以后。

使用几个类完成此操作后,您将开始看到可以更好地模块化,设计更好等的地方。此外,在不影响无关事物的情况下进行此类更改会更容易,因为代码现在更加模块化,并且您大概知道透彻。

于 2009-03-17T16:22:53.617 回答
1

大多数情况下,这听起来很糟糕。但我不明白这部分:

我的前辈甚至认为向后使用所有数据集会很聪明。每个数据库更新都作为存储过程中的参数直接发送到数据库服务器,然后手动刷新数据集,以便 UI 显示最近的更改。

这听起来非常接近我经常写东西的方式。这有什么问题?正确的方法是什么?

于 2009-03-17T16:15:56.800 回答
1

请参阅博客文章Anatomy of an Anti-Corruption Layer,第 1 部分Anatomy of an Anti-Corruption Layer,第 2 部分

它引用了 Eric Evans,领域驱动设计:解决软件核心的复杂性

访问门面后面的废话

于 2009-03-17T16:21:16.587 回答
1

没有一本书能够涵盖所有可能的情况。它还取决于您对项目的期望以及是否有任何类型的外部规范。

  • 如果您只需要偶尔进行一些小的更改,只需进行这些更改,而不必费心开始重构。
  • 如果有规范(或者你可以找人来编写),如果可以通过可预见的项目更改量来证明它是合理的,请考虑完全重写
  • 如果“实现就是规范”并且计划进行很多更改,那么您就很烦了。编写大量单元测试并以小步骤开始重构。

实际上,无论您做什么,单元测试都将是无价的(如果您可以将它们写入一个不会因重构或重写而发生太大变化的接口,也就是说)。

于 2009-03-17T16:25:18.760 回答
1

如果您的重构正在破坏代码,尤其是似乎不相关的代码,那么您一次尝试做的事情太多了。

我建议您进行一次重构,其中您所做的只是 ExtractMethod:目标只是为代码中的每个步骤命名,而无需进行任何合并尝试。

之后,考虑打破依赖关系,替换单例,合并。

于 2009-03-17T16:31:23.723 回答
1

如果您的重构正在破坏事物,那么这意味着您没有足够的单元测试覆盖率 - 因为单元测试应该首先破坏。我建议您在异常记录到位之后获得更好的单元测试覆盖率。

然后我建议你先做小的重构——提取方法把大的方法分解成可以理解的部分;引入变量以删除方法中的一些重复;如果您发现调用者和被调用者使用的变量之间存在重复,则可能引入参数。

并在每次重构或一组重构之后运行单元测试套件。我会说全部运行它们,直到您确信每次都需要重新运行哪些测试。

于 2009-03-17T17:01:03.697 回答
0

祝你好运,这是作为开发人员的艰难部分。

我认为您的方法很好,但您需要专注于交付业务价值(单元测试的数量不是衡量业务价值的标准,但它可能会告诉您您是否处于正轨)。确定需要改变的行为、确定优先级并关注最重要的行为是很重要的。

另一个建议是保持谦虚。意识到如果你在真正的截止日期前写了这么大的东西,而其他人看到了你的代码,他们可能也会在理解它时遇到问题。有一个技巧是写干净的代码,还有一个更重要的技巧是处理别人的代码。

最后一条建议是尝试利用团队的其他成员。过去的成员可能知道有关您可以学习的系统的信息。此外,他们可能能够帮助测试行为。我知道理想的情况是进行自动化测试,但如果有人可以通过手动为您验证事情来提供帮助,请考虑获得他们的帮助。

于 2009-03-17T17:13:19.533 回答
0

我特别喜欢Code Complete中的图表,其中您只从遗留代码开始,即模糊灰色纹理的矩形。然后当你替换其中一些时,底部有模糊的灰色,顶部有纯白色,以及代表两者之间界面的锯齿线。

也就是说,一切要么是“讨厌的旧东西”,要么是“好的新东西”。线的一侧或另一侧。

这条线是锯齿状的,因为您正在以不同的速率迁移系统的不同部分。

当你工作时,锯齿线逐渐下降,直到你的白色多于灰色,最终只有灰色。

当然,这不会让您更轻松地了解细节。但它确实为您提供了一个模型,您可以使用它来监控您的进度。在任何时候,你都应该清楚地了解界限在哪里:哪些是新的,哪些是旧的,以及双方如何通信。

于 2009-03-17T17:25:12.250 回答
0

您可能会发现以下帖子很有用: http ://refactoringin.net/?p=36

正如帖子中所说,不要轻易丢弃完全覆盖。此外,如果可能的话,尝试用第三方解决方案替换整个层或层,例如用于持久性的ORM或新代码。但最重要的是,尝试理解代码背后的逻辑(问题域)。

于 2009-03-22T00:05:05.660 回答
0

您可以提取并重构其中的某些部分,以打破依赖关系并将层隔离到不同的模块、库、程序集、目录中。然后,您使用扼杀应用策略将清理过的部分重新注入应用程序。起泡,冲洗,重复。

于 2009-11-06T12:55:20.967 回答