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

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

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

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

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

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


当前回答

我猜一下,如果这段代码为10个客户服务,并且包含代码变体,那么您可能有很多针对特定客户的带有变体的代码克隆

我很想对你的11000行文件进行克隆检测。(事实上,如果你把它发给我,我会用我的c++克隆检测器[见生物],并把答案发给你)。

这将显示任何克隆,以及这些克隆是如何变化的。有了这些信息,重构代码就变得相当容易了。

其他回答

你担心文件的大小。

从历史上看,C程序的文件大小是由机器PDP11/40的限制决定的。 我使用的这个可以处理最大4096字节的文件。为了解决这个问题 C编译器使用#include并发明了.h文件来帮助链接器和分段加载器,因为 加载器必须动态交换(因此在Intel架构中使用段寄存器)。

Small files solved the problem but left an historical legacy. Programmers now believe that small files are the only way to program. You have a machine with 4 gigabytes (vs 8 kilobytes on the 11/40). You have a machine with 3 billion instructions per second (vs 500 kilo instructions on the 11/40). You have a compiler that can block optimize code it can see (as opposed to linking .o files which it cannot see). You have a machine that is bandwidth limited by disk I/O but you want to create 500 tiny .c, .h, and .o files, possibly multiple times with the .h includes.

大的C文件绝对没有错。编译器可以大量优化 磁盘I/O最小,链接器时间消失,编辑器可以找到琐碎的东西 一个花哨的IDE,……

11000行对于今天来说是一个微不足道的文件。把自己从历史中解放出来。

所以从一开始重写产品代码的API是一个坏主意。需要做两件事。

首先,您需要让您的团队决定对该文件的当前生产版本进行代码冻结。

第二,您需要使用这个生产版本并创建一个分支,该分支使用预处理指令来管理构建,以分割大文件。使用JUST预处理器指令(#ifdefs, #includes, #endifs)拆分编译比重新编码API更容易。对于您的sla和持续的支持来说,这绝对更容易。

在这里,您可以简单地删除类中与特定子系统相关的函数,并将它们放在一个文件(例如mainloop_foostuff.cpp)中,并将其包含在mainloop.cpp中的正确位置。

OR

一种更耗时但健壮的方法是设计一个内部依赖关系结构,在包含内容的方式上具有双重间接性。这将允许您分割内容,并仍然照顾到共同依赖关系。注意,这种方法需要位置编码,因此应该加上适当的注释。

这种方法将包括基于您正在编译的变体而使用的组件。

基本结构是mainclass.cpp将在如下语句块后包含一个名为MainClassComponents.cpp的新文件:

#if VARIANT == 1
#  define Uses_Component_1
#  define Uses_Component_2
#elif VARIANT == 2
#  define Uses_Component_1
#  define Uses_Component_3
#  define Uses_Component_6
...

#endif

#include "MainClassComponents.cpp"

MainClassComponents.cpp文件的主要结构将在那里计算子组件中的依赖关系,如下所示:

#ifndef _MainClassComponents_cpp
#define _MainClassComponents_cpp

/* dependencies declarations */

#if defined(Activate_Component_1) 
#define _REQUIRES_COMPONENT_1
#define _REQUIRES_COMPONENT_3 /* you also need component 3 for component 1 */
#endif

#if defined(Activate_Component_2)
#define _REQUIRES_COMPONENT_2
#define _REQUIRES_COMPONENT_15 /* you also need component 15 for this component  */
#endif

/* later on in the header */

#ifdef _REQUIRES_COMPONENT_1
#include "component_1.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_2
#include "component_2.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_3
#include "component_3.cpp"
#endif


#endif /* _MainClassComponents_h  */

现在,为每个组件创建一个component_xx.cpp文件。

当然,我使用数字,但你应该使用一些更符合逻辑的基于你的代码。

使用预处理器可以让你把事情分开,而不必担心API的变化,这在生产中是一个噩梦。

一旦你确定了产品,你就可以开始重新设计了。

正如你所描述的,主要的问题是区分拆分前和拆分后,合并bug修复等。围绕它的工具。用Perl、Ruby等语言硬编码一个脚本不会花那么长时间,就可以去除分离前和分离后的连接所产生的大部分噪音。用最简单的方法处理噪音:

在连接前/过程中删除某些行(例如包括警卫) 如果有必要,从diff输出中删除其他内容

您甚至可以这样做,只要有签入,连接就会运行,并且您已经准备好了一些与单文件版本不同的东西。

合并不会像将来获得30000个LOC文件那样是一个大噩梦。所以:

停止向该文件添加更多代码。 把它。


如果你不能在重构过程中停止编码,你可以暂时保留这个大文件,至少不向它添加更多的代码:因为它包含一个“主类”,你可以从它继承,并将继承的带有重载函数的类保留在几个新的小而设计良好的文件中。

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

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

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

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

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