所以我们在项目中有这个巨大的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.
我的同情-在我以前的工作中,我遇到过类似的情况,一个文件比你必须处理的文件大几倍。解决方案是:
编写代码详尽地测试程序中的函数。听起来你还没有掌握这个…
确定一些可以抽象为帮助器/实用程序类的代码。不需要很大,只是一些不是你的“主要”类的真正一部分。
重构2中确定的代码。进入一个单独的班级。
重新运行测试,以确保没有损坏。
当你有时间的时候,去2。并根据需要重复以使代码易于管理。
在第3步中构建的类。迭代可能会增加以吸收更多适合于新清除的函数的代码。
我还可以补充:
0:购买Michael Feathers关于使用遗留代码的书
不幸的是,这种类型的工作太常见了,但我的经验是,在保持工作的同时,能够使工作但可怕的代码逐渐变得不那么可怕是有很大价值的。
这让我想起了我以前的工作。似乎,在我加入之前,所有东西都在一个巨大的文件中(也是c++)。然后他们将其拆分(在完全随机的点上使用include)为大约三个(仍然是巨大的文件)。正如你所预料的那样,这个软件的质量非常糟糕。该项目总标线约为40k。(几乎没有注释,但有大量重复代码)
最后,我完全重写了这个项目。我从头开始重做项目中最糟糕的部分。当然,我想到了这个新部分和其他部分之间可能的(小)接口。然后我把这个部分插入到旧的项目中。我没有重构旧代码来创建必要的接口,只是替换了它。然后我从那里迈出了一小步,重写了旧代码。
我不得不说,这花了大约半年的时间,在此期间,除了修复错误之外,没有开发旧的代码库。
编辑:
它的大小保持在40k LOC左右,但与8年前的软件相比,新应用程序在初始版本中包含了更多的功能,可能bug也更少。重写的一个原因是我们需要新的特性,而在旧代码中引入这些特性几乎是不可能的。
该软件是为一个嵌入式系统,一个标签打印机。
我应该补充的另一点是,理论上这个项目是c++的。但它根本不是面向对象的,它可能是c。新版本是面向对象的。
一个重要的建议:不要将重构和错误修复混合在一起。您需要的是程序的版本与以前的版本相同,只是源代码不同。
一种方法是开始将最小的函数/部分分割到它自己的文件中,然后使用头文件include(从而将main.cpp转换为#includes列表,这本身听起来有点代码味道*尽管我不是c++专家),但至少现在它被分割为文件)。
然后,您可以尝试将所有维护版本切换到“新的”main.cpp或任何您的结构。再次重申:没有其他更改或错误修复,因为跟踪这些是令人困惑的地狱。
另一件事:尽管您可能希望一次性完成整个重构,但您可能会贪多嚼不烂。也许只是选择一两个“部分”,把它们放到所有的版本中,然后为你的客户增加一些更多的价值(毕竟,重构并不会直接增加价值,所以它是一种成本,必须被证明是合理的),然后再选择另外一两个部分。
显然,这需要团队中的一些纪律来实际使用拆分文件,而不是一直向main.cpp中添加新内容,但是,尝试进行一次大规模的重构可能不是最佳的行动方案。