如何应对不良代码

时间:2010-01-20 08:41:36

标签: refactoring

作为开发人员,我在一整天的生活中遇到的最令人不快(也是最不常见的)情况之一就是我必须修复错误或在设计糟糕的代码中添加功能。现在作为一个优秀的工匠,我想让代码保持比我发现的更好的状态。如果我不重构设计,通常无法实现新功能。嗯 - 他们可以,但这会使代码更糟糕。

不幸的是,这正是我经常遇到的困难。我觉得如果有一件事情很难,那就是重构坏代码,尤其是在你有最后期限的情况下。触及或多或少工作的糟糕而复杂的代码是可怕的。因此,当我在代码中修改新功能而不修改现有代码时,我会引入更多混乱。

现在我的问题是我如何学会应对不良代码?我怎样才能学会理解巨大的代码库,然后在不破坏已经工作且不超过截止日期的情况下重构其中的部分代码?你有没有可以推荐的文献?你有什么一般的技巧吗?

9 个答案:

答案 0 :(得分:24)

一般提示:

if (it is working)
   Do (not touch it);
else
{
   Make (as few modifications as possible)
   Or (it will stop working at all);
}

这是几代人的经历。

答案 1 :(得分:21)

Michael Feathers正好写了一本关于这个主题的好书。

Working Effectively with Legacy Code

另一本伟大的着作是Martin Fowler,Kent Beck和其他人:

Refactoring: Improving the Design of Existing Code

答案 2 :(得分:9)

重构需要单元测试套件的安全带来消除“我有没有破坏它?”感觉。在一系列测试中覆盖不良代码将有助于您在努力获得良好的清洁代码时。

Pex是一个我觉得有用的工具(如果你在.NET世界中),用于为遗留代码创建测试。

旧版代码==没有测试的代码!

善,

答案 3 :(得分:7)

当我必须处理向坏代码添加功能时,我通常的做法是:

  • 为必须工作的每个重要功能编写自动化测试(因为大多数错误代码没有任何测试)。
  • 代码更改。
  • 确保测试仍然有效。

这至少让你有信心,你没有打破一切。至于如何学习应对不良代码,我想这只是经验。

答案 4 :(得分:4)

好吧,如果您要在项目中重构大量代码,我建议使用一些不错的版本控制,这样您就可以轻松地分支和回退。鉴于,这可能是一个敞开的大门,但至关重要的imo。

此外,在开始进入复杂的OO之前,尝试将方法和功能分解为较小的OO。确保每个函数的某种程度的原子性,这使得代码更易于维护,读取和管理。 这些都是关于小东西,将其分解为逻辑操作单元,我正在对1k行方法进行重构操作。它确实有很多奇特的东西。 我的第一个目标是用更小的东西从中获取尽可能多的东西,当它完成时我将开始考虑更好的OO设计,这更容易,因为我对事物有了更好的把握。

阿司匹林也很好用。

答案 5 :(得分:3)

我目前处于这种情况。我的方法是在触摸代码之前回答一些问题:

  1. 代码真的那么糟糕吗?如果是,常见错误是什么? ==>也许专注于那些第一次
  2. 代码中的主要运行时流程是什么?也许你可以从中丢弃相当多的结构。
  3. 尝试对代码进行分层/模块化而不更改代码。这导致一些相互依赖性的减少
  4. 尝试用测试戳代码。如果代码库纠缠不已:使用像PowerMock之类的东西来模拟那些尚未(还)需要改变的对象
  5. 提供可用的集成环境,您可以在其中测试生产环境中的更改。
  6. 不要回避重写部分代码库。但是尽量不要在其中实现太多新东西
  7. 尝试合作,讨论设计,原则和解决方案
  8. 这是艰苦的工作,没有人会感谢你。为小改进而自豪并享受完成的工作:)

答案 6 :(得分:1)

我认为,大致了解一切如何在您正在开发/改进的软件中运行总是好的。这就是设计文档和其他文档在开发过程之后或开发过程中产生的地方。我相信如果有人在你之前没有做过适当的文档,那么至少你应该在某个地方写几行关于你在整个开发过程中经历的内容。我通常使用OneNote或其他东西来记录我遇到的内容,并且通常会列出我认为需要重构的内容。如果在项目期间有一些停机时间,我通常会回到该列表并尝试逐步改进。

所以基本上,如果你之前有人没有做到这一点,那么至少你可以帮助减少遇到相同代码的任何其他开发人员的问题会很好。

答案 7 :(得分:0)

答案 8 :(得分:0)

这取决于多种因素,但最重要的是如果您有权修改它。

如果你这样做,重构它。例如,重命名类/函数/变量。提取和概括功能。请参阅重构:Improving the Design of Existing Code(主题为圣经)。 在开始之前,请确保代码处于正确的版本控制(VC)中,并且具有一组良好的测试用例。 VC让你回滚,测试案例有助于捕捉意想不到的副作用。

我建议使用像Mercurial / Bazaar和Git这样的分布式版本控制,因为它非常重构并不像添加功能那样结构化。

如果没有测试(常见),则必须创建它们。阅读Working Effectively With Legacy Code。特别是关于“印章点”(不是关于暹罗猫:p)。

如果您没有创建更清洁的包装器API。

例如:


Old code ====================
const ACT_SHOW = 'show';
const ACT_HIDE = 'hide';
function int DoThing(Object $Obj, Stirng $Action, Object $Param1, Object $Param1) {
     ....;
}
Added code ==================
enum Actions {
    show, hide;
};
class ActionDoer {
    private obj;
    ActionDoer($Object) {
        this.obj = $Object;
    }
    function int act(Actions $Action, $Param1, $Param1) {
        this.act($Action.toString(), $Param1, $Param1) ;
    }
    function int act(String $Action, $Param1, $Param1) {
        DoThing(this.obj, $Action, $Param1, $Param1) ;
    }
    function int show() {
        this.act(Actions.show, null, null);
    }
    function int hide(Color $ToBGColor, long $FadeTime) {
        this.act(Actions.hide, $ToBGColor, $FadeTime);
    }
}

这样,不会触及旧代码,可以使用新代码完成扩展。这个方法的好例子是jQuery,其中访问DOM的旧(默认)方式很痛苦。

希望这有帮助。