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

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

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

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

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

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


当前回答

我认为最好创建一组映射到mainmodule.cpp的API点的命令类。

一旦它们就位,您将需要重构现有的代码库,以通过命令类访问这些API点,一旦完成,您就可以自由地将每个命令的实现重构为新的类结构。

当然,对于一个11 KLOC的类,其中的代码可能是高度耦合和脆弱的,但是创建单独的命令类比任何其他代理/外观策略都更有帮助。

我并不羡慕这项任务,但随着时间的推移,如果不加以解决,这个问题只会变得更糟。

更新

我建议Command模式比Facade更可取。

在一个(相对)单一的Facade上维护/组织许多不同的命令类是可取的。将一个Facade映射到一个11 KLOC文件本身可能需要分解成几个不同的组。

为什么要费心去弄清楚这些门面组呢?使用命令模式,你将能够对这些小类进行有机分组和组织,因此你有更多的灵活性。

当然,这两种选择都比单一的11 KLOC和不断增长的文件要好。

其他回答

我认为最好创建一组映射到mainmodule.cpp的API点的命令类。

一旦它们就位,您将需要重构现有的代码库,以通过命令类访问这些API点,一旦完成,您就可以自由地将每个命令的实现重构为新的类结构。

当然,对于一个11 KLOC的类,其中的代码可能是高度耦合和脆弱的,但是创建单独的命令类比任何其他代理/外观策略都更有帮助。

我并不羡慕这项任务,但随着时间的推移,如果不加以解决,这个问题只会变得更糟。

更新

我建议Command模式比Facade更可取。

在一个(相对)单一的Facade上维护/组织许多不同的命令类是可取的。将一个Facade映射到一个11 KLOC文件本身可能需要分解成几个不同的组。

为什么要费心去弄清楚这些门面组呢?使用命令模式,你将能够对这些小类进行有机分组和组织,因此你有更多的灵活性。

当然,这两种选择都比单一的11 KLOC和不断增长的文件要好。

考虑以更合理的方式重写整个应用程序的方法。也许可以重写其中的一小部分作为原型,看看你的想法是否可行。

如果您已经确定了一个可行的解决方案,那么相应地重构应用程序。

如果所有产生更合理架构的尝试都失败了,那么至少您知道解决方案可能是重新定义程序的功能。

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

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.

让我猜猜: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.

孔子说:“出洞的第一步是停止挖洞。”