所以我们在项目中有这个巨大的mainmodule.cpp源文件(11000行很大吗?),每次我不得不触摸它时,我都会畏缩。

由于这个文件是如此的核心和大,它不断积累越来越多的代码,我想不出一个好方法来让它实际上开始缩小。

该文件在我们产品的几个(> 10)维护版本中被使用和积极更改,因此很难重构它。如果我“简单地”将其拆分为3个文件,那么从维护版本合并回更改将成为一场噩梦。而且,如果您拆分具有如此长而丰富历史的文件,跟踪和检查SCC历史中的旧更改突然变得非常困难。

这个文件基本上包含了我们程序的“主类”(主要的内部工作调度和协调),所以每次添加一个特性,它也会影响这个文件,每次它的增长。:-(

在这种情况下你会怎么做?关于如何在不打乱SCC工作流程的情况下将新特性移动到单独的源文件中,您有什么想法吗?

(注意:我们使用c++和Visual Studio;我们使用AccuRev作为SCC,但我认为SCC的类型在这里并不重要;我们使用Araxis Merge来做实际的文件比较和合并)


当前回答

我发现这句话是你帖子中最有趣的部分:

>该文件在我们产品的几个(> 10)维护版本中被使用和积极更改,因此很难重构它

首先,我建议您使用源代码控制系统来开发这10多个支持分支的维护版本。

其次,我将创建10个分支(每个分支对应一个维护版本)。

我已经感觉到你在畏缩了!但是,要么是因为缺少特性,你的源代码控制不能满足你的情况,要么是因为它没有被正确地使用。

现在来看看您正在处理的分支——按照您认为合适的方式对其进行重构,确保不会打乱产品的其他九个分支。

我有点担心你的main()函数中有这么多。

在我编写的任何项目中,我都会使用main()只执行核心对象的初始化——比如模拟或应用程序对象——这些类才是真正的工作应该进行的地方。

我还将在main中初始化一个应用程序日志对象,以便在整个程序中全局使用。

最后,在main中,我还在预处理器块中添加了泄漏检测代码,以确保它只在DEBUG版本中启用。这是我要添加到main()的所有内容。Main()应该很短!

你这么说

>该文件基本包含了我们程序的“主类”(主要的内部工作调度和协调)

听起来这两个任务可以分成两个单独的对象——一个协调器和一个工作分派器。

当你把它们分开的时候,你可能会弄乱你的“SCC工作流”,但是听起来像严格遵守你的SCC工作流会导致软件维护问题。抛弃它,现在就不要回头,因为一旦你解决了它,你就会开始睡得很舒服。

如果您不能做出决定,那么就与您的经理进行激烈的斗争——您的应用程序需要重构——听起来很糟糕!不要接受拒绝!

其他回答

我认为在拆分文件时跟踪源文件历史的最简单的方法是这样的:

使用SCM系统提供的任何保存历史的拷贝命令来复制原始源代码。此时您可能需要提交,但还不需要告诉构建系统关于新文件的信息,因此这应该是可以的。 从这些副本中删除代码。这应该不会打破你所保持的历史。

好吧,我理解你的痛苦:)我也参与过一些这样的项目,它并不漂亮。这个问题没有简单的答案。

一种可行的方法是开始在所有函数中添加安全保护,也就是说,检查方法中的参数、前置/后置条件,然后最终添加单元测试,以便捕获源的当前功能。一旦你有了这些,你就可以更好地重构代码,因为如果你忘记了什么,你就会有断言和错误弹出来提醒你。

有时候,重构带来的痛苦可能会大于好处。那么,最好是让原始项目处于伪维护状态,从头开始,然后增量地添加野兽的功能。

“这个文件基本上包含了我们程序的‘主类’(主要的内部工作调度和协调),所以每次添加一个特性,它也会影响这个文件,每次它的增长。”

如果这个大的SWITCH(我认为是有的)成为主要的维护问题,你可以重构它,使用字典和命令模式,并从现有的代码中删除所有的开关逻辑到加载器,加载器填充该映射,即:

    // declaration
    std::map<ID, ICommand*> dispatchTable;
    ...

    // populating using some loader
    dispatchTable[id] = concreteCommand;

    ...
    // using
    dispatchTable[id]->Execute();

这是一个经典的例子,一个已知的反设计模式,称为blob。花点时间阅读我在这里指出的文章,也许你会发现一些有用的东西。此外,如果这个项目像它看起来的那样大,您应该考虑一些设计来防止发展成您无法控制的代码。

哇,听起来不错。我认为向你的老板解释,你需要很多时间来重构这头野兽值得一试。如果他不同意,你可以选择辞职。

Anyway, what I suggest is basically throwing out all the implementation and regrouping it into new modules, let's call those "global services". The "main module" would only forward to those services and ANY new code you write will use them instead of the "main module". This should be feasible in a reasonable amount of time (because it's mostly copy and paste), you don't break existing code and you can do it one maintenance version at a time. And if you still have any time left, you can spend it refactoring all old depending modules to also use the global services.