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

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

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

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

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

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


当前回答

让我猜猜:10个拥有不同功能集的客户和一个提倡“定制化”的销售经理?我以前做过这样的产品。我们遇到了同样的问题。

您认识到拥有一个巨大的文件是很麻烦的,但更麻烦的是您必须保持10个版本的“最新”。这是多重维护。SCC可以使这更容易,但它不能使它正确。

Before you try to break the file into parts, you need to bring the ten branches back in sync with each other so that you can see and shape all the code at once. You can do this one branch at a time, testing both branches against the same main code file. To enforce the custom behavior, you can use #ifdef and friends, but it's better as much as possible to use ordinary if/else against defined constants. This way, your compiler will verify all types and most probably eliminate "dead" object code anyway. (You may want to turn off the warning about dead code, though.)

一旦所有分支隐式地共享了该文件的一个版本,那么就更容易开始使用传统的重构方法。

#ifdefs主要适用于受影响的代码只在其他分支自定义上下文中有意义的部分。有人可能会说,这也为相同的分支合并方案提供了机会,但不要太疯狂。一次只做一个大项目。

In the short run, the file will appear to grow. This is OK. What you're doing is bringing things together that need to be together. Afterwards, you'll begin to see areas that are clearly the same regardless of version; these can be left alone or refactored at will. Other areas will clearly differ depending on the version. You have a number of options in this case. One method is to delegate the differences to per-version strategy objects. Another is to derive client versions from a common abstract class. But none of these transformations are possible as long as you have ten "tips" of development in different branches.

其他回答

一种不太危险的方法是对所有的线变化进行历史性的观察。有没有特定的函数比其他函数更稳定?可以说是变化的热点。

如果某一行在几年内没有被更改过,你可以将它移到另一个文件中,而不用太担心。我会看一下用最后一次修改注释的源代码,看看是否有任何函数可以提取出来。

一个重要的建议:不要将重构和错误修复混合在一起。您需要的是程序的版本与以前的版本相同,只是源代码不同。

一种方法是开始将最小的函数/部分分割到它自己的文件中,然后使用头文件include(从而将main.cpp转换为#includes列表,这本身听起来有点代码味道*尽管我不是c++专家),但至少现在它被分割为文件)。

然后,您可以尝试将所有维护版本切换到“新的”main.cpp或任何您的结构。再次重申:没有其他更改或错误修复,因为跟踪这些是令人困惑的地狱。

另一件事:尽管您可能希望一次性完成整个重构,但您可能会贪多嚼不烂。也许只是选择一两个“部分”,把它们放到所有的版本中,然后为你的客户增加一些更多的价值(毕竟,重构并不会直接增加价值,所以它是一种成本,必须被证明是合理的),然后再选择另外一两个部分。

显然,这需要团队中的一些纪律来实际使用拆分文件,而不是一直向main.cpp中添加新内容,但是,尝试进行一次大规模的重构可能不是最佳的行动方案。

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

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

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

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

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

我不知道这是否解决了您的问题,但我猜您想要做的是将文件的内容迁移到彼此独立的更小的文件中(合计)。 我还了解到,你有大约10个不同版本的软件,你需要在不搞砸的情况下支持它们。

首先,这是不可能的简单,将解决自己在几分钟的头脑风暴。文件中链接的函数对应用程序都非常重要,简单地将它们删除并迁移到其他文件中并不能解决问题。

我认为你只有这些选择:

Don't migrate and stay with what you have. Possibly quit your job and start working on serious software with good design in addition. Extreme programming is not always the best solution if you are working on a long time project with enough funds to survive a crash or two. Work out a layout of how you would love your file to look once it's split up. Create the necessary files and integrate them in your application. Rename the functions or overload them to take an additional parameter (maybe just a simple boolean?). Once you have to work on your code, migrate the functions you need to work on to the new file and map the function calls of the old functions to the new functions. You should still have your main-file this way, and still be able to see the changes that were made to it, once it comes to a specific function you know exactly when it was outsourced and so on. Try to convince your co-workers with some good cake that workflow is overrated and that you need to rewrite some parts of the application in order to do serious business.

下面是我所想到的解决这些问题的唯一办法。所述方法的实际增益是演化的累进性。这里没有革命,否则你很快就会陷入麻烦。

在原来的主类上面插入一个新的cpp类。目前,它基本上会将所有调用重定向到当前的主类,但目标是使这个新类的API尽可能清晰和简洁。

一旦完成了这些,就可以在新类中添加新功能。

至于现有的功能,当它们变得足够稳定时,您必须逐步将它们移动到新的类中。对于这段代码,您将失去SCC帮助,但是对此没有太多办法。只要选择合适的时机。

我知道这并不完美,但我希望它能有所帮助,这个过程必须适应您的需要!

额外的信息

注意,Git是一个SCC,它可以从一个文件跟踪代码片段到另一个文件。我听说过关于它的好东西,所以它可以帮助你逐步转移你的工作。

Git是围绕blob的概念构建的,如果我理解正确的话,blob表示代码文件的片段。在不同的文件中移动这些片段,Git会找到它们,即使您修改了它们。除了下面评论中提到的Linus Torvalds的视频之外,我还没有找到关于这个问题的一些清楚的东西。