所以我们在项目中有这个巨大的mainmodule.cpp源文件(11000行很大吗?),每次我不得不触摸它时,我都会畏缩。
由于这个文件是如此的核心和大,它不断积累越来越多的代码,我想不出一个好方法来让它实际上开始缩小。
该文件在我们产品的几个(> 10)维护版本中被使用和积极更改,因此很难重构它。如果我“简单地”将其拆分为3个文件,那么从维护版本合并回更改将成为一场噩梦。而且,如果您拆分具有如此长而丰富历史的文件,跟踪和检查SCC历史中的旧更改突然变得非常困难。
这个文件基本上包含了我们程序的“主类”(主要的内部工作调度和协调),所以每次添加一个特性,它也会影响这个文件,每次它的增长。:-(
在这种情况下你会怎么做?关于如何在不打乱SCC工作流程的情况下将新特性移动到单独的源文件中,您有什么想法吗?
(注意:我们使用c++和Visual Studio;我们使用AccuRev作为SCC,但我认为SCC的类型在这里并不重要;我们使用Araxis Merge来做实际的文件比较和合并)
你担心文件的大小。
从历史上看,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行对于今天来说是一个微不足道的文件。把自己从历史中解放出来。
我的同情-在我以前的工作中,我遇到过类似的情况,一个文件比你必须处理的文件大几倍。解决方案是:
编写代码详尽地测试程序中的函数。听起来你还没有掌握这个…
确定一些可以抽象为帮助器/实用程序类的代码。不需要很大,只是一些不是你的“主要”类的真正一部分。
重构2中确定的代码。进入一个单独的班级。
重新运行测试,以确保没有损坏。
当你有时间的时候,去2。并根据需要重复以使代码易于管理。
在第3步中构建的类。迭代可能会增加以吸收更多适合于新清除的函数的代码。
我还可以补充:
0:购买Michael Feathers关于使用遗留代码的书
不幸的是,这种类型的工作太常见了,但我的经验是,在保持工作的同时,能够使工作但可怕的代码逐渐变得不那么可怕是有很大价值的。
在我看来,您现在面临的是一堆代码异味。首先,主类似乎违反了开/闭原则。这听起来也像是承担了太多责任。因此,我认为代码比实际需要的更加脆弱。
虽然我可以理解您对重构后可跟踪性的担忧,但我认为该类相当难以维护和增强,而且您所做的任何更改都可能导致副作用。我假设这些的成本超过了重构类的成本。
在任何情况下,由于代码气味只会随着时间的推移而变得更糟,至少在某些时候,这些成本将超过重构的成本。根据你的描述,我认为你已经过了临界点。
重构应该分小步骤进行。如果可能的话,在重构任何东西之前添加自动测试来验证当前行为。然后挑选出独立功能的小区域,并将其提取为类型,以便委派职责。
无论如何,这听起来像是一个大项目,所以祝你好运:)
这让我想起了我以前的工作。似乎,在我加入之前,所有东西都在一个巨大的文件中(也是c++)。然后他们将其拆分(在完全随机的点上使用include)为大约三个(仍然是巨大的文件)。正如你所预料的那样,这个软件的质量非常糟糕。该项目总标线约为40k。(几乎没有注释,但有大量重复代码)
最后,我完全重写了这个项目。我从头开始重做项目中最糟糕的部分。当然,我想到了这个新部分和其他部分之间可能的(小)接口。然后我把这个部分插入到旧的项目中。我没有重构旧代码来创建必要的接口,只是替换了它。然后我从那里迈出了一小步,重写了旧代码。
我不得不说,这花了大约半年的时间,在此期间,除了修复错误之外,没有开发旧的代码库。
编辑:
它的大小保持在40k LOC左右,但与8年前的软件相比,新应用程序在初始版本中包含了更多的功能,可能bug也更少。重写的一个原因是我们需要新的特性,而在旧代码中引入这些特性几乎是不可能的。
该软件是为一个嵌入式系统,一个标签打印机。
我应该补充的另一点是,理论上这个项目是c++的。但它根本不是面向对象的,它可能是c。新版本是面向对象的。
下面是我所想到的解决这些问题的唯一办法。所述方法的实际增益是演化的累进性。这里没有革命,否则你很快就会陷入麻烦。
在原来的主类上面插入一个新的cpp类。目前,它基本上会将所有调用重定向到当前的主类,但目标是使这个新类的API尽可能清晰和简洁。
一旦完成了这些,就可以在新类中添加新功能。
至于现有的功能,当它们变得足够稳定时,您必须逐步将它们移动到新的类中。对于这段代码,您将失去SCC帮助,但是对此没有太多办法。只要选择合适的时机。
我知道这并不完美,但我希望它能有所帮助,这个过程必须适应您的需要!
额外的信息
注意,Git是一个SCC,它可以从一个文件跟踪代码片段到另一个文件。我听说过关于它的好东西,所以它可以帮助你逐步转移你的工作。
Git是围绕blob的概念构建的,如果我理解正确的话,blob表示代码文件的片段。在不同的文件中移动这些片段,Git会找到它们,即使您修改了它们。除了下面评论中提到的Linus Torvalds的视频之外,我还没有找到关于这个问题的一些清楚的东西。
这并不是一个大问题的答案,而是一个具体问题的理论解决方案:
Figure out where you want to split the big file into subfiles. Put comments in some special format at each of those points.
Write a fairly trivial script that will break the file apart into subfiles at those points. (Perhaps the special comments have embedded filenames that the script can use as instructions for how to split it.) It should preserve the comments as part of the splitting.
Run the script. Delete the original file.
When you need to merge from a branch, first recreate the big file by concatenating the pieces back together, do the merge, and then re-split it.
另外,如果您想要保存SCC文件历史,我认为最好的方法是告诉您的源代码控制系统各个片段文件都是原始文件的副本。然后,它将保存该文件中保存的部分的历史,当然,它也将记录大部分被“删除”。