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

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

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

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

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

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


当前回答

这是一个困难而有趣的重构。

首先,将实现与接口分离。将这个巨大的文件转换为一个只转发调用和参数的空shell。这样,你可以创建责任有限的组件,而不会影响任何调用者(他们仍然调用巨大的文件/模块/类)。

为了做到这一点,您还需要寻找新的潜在组件的创建时间。根据构造函数的发送方式,在拥有所有参数之前,对参数进行堆叠可能非常棘手。

然后,您可以查找调用者并让他们调用您的组件。这是简单的部分。

其他回答

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

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

你担心文件的大小。

从历史上看,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行对于今天来说是一个微不足道的文件。把自己从历史中解放出来。

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

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


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

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

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

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

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

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

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

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

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

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

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

你这么说

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

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

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

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

我的同情-在我以前的工作中,我遇到过类似的情况,一个文件比你必须处理的文件大几倍。解决方案是:

编写代码详尽地测试程序中的函数。听起来你还没有掌握这个… 确定一些可以抽象为帮助器/实用程序类的代码。不需要很大,只是一些不是你的“主要”类的真正一部分。 重构2中确定的代码。进入一个单独的班级。 重新运行测试,以确保没有损坏。 当你有时间的时候,去2。并根据需要重复以使代码易于管理。

在第3步中构建的类。迭代可能会增加以吸收更多适合于新清除的函数的代码。

我还可以补充:

0:购买Michael Feathers关于使用遗留代码的书

不幸的是,这种类型的工作太常见了,但我的经验是,在保持工作的同时,能够使工作但可怕的代码逐渐变得不那么可怕是有很大价值的。