11000行C ++源文件怎么办?


229

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

由于该文件是如此之大且庞大,因此它会不断积累越来越多的代码,我想不出一种使它真正开始缩小的好方法。

该文件已在我们产品的多个(> 10)维护版本中使用并进行了有效更改,因此实际上很难对其进行重构。如果要“简单地”将其拆分为3个文件(例如,一开始),那么将维护版本中的更改合并回来将成为一场噩梦。而且,如果您拆分了具有如此长而丰富的历史记录的文件,则跟踪和检查SCC历史记录中的旧更改会突然变得困难得多。

该文件基本上包含我们程序的“主类”(主要内部工作的调度和协调),因此,每次添加功能时,它也会影响此文件及其每次扩展。:-(

在这个情况下,你会怎么做?关于如何将新功能移动到单独的源文件而不弄乱SCC工作流程的任何想法?

(有关工具的注意事项:我们将C ++与一起使用Visual Studio;我们使用AccuRevSCC但我认为这里的类型SCC并不重要;我们用于Araxis Merge进行文件的实际比较和合并)


15
@BoltClock:实际上Vim会很快打开它。
ereOn 2010年

58
69305线和计数。我们的应用程序中的一个文件,我的同事将其大部分代码转储到了该文件中。我公司中没有任何人要向此举报。
Agnel Kurian'9

204
我不明白 “辞职”评论如何获得如此多的赞誉?有些人似乎生活在一个仙境中,那里的所有项目都是从头开始编写的,并且/或者使用了100%敏捷的TDD ...(在这里输入您的流行语)。
Stefan 2010年

39
@Stefan:当遇到类似的代码库时,我确实做到了。我并不喜欢花我的时间95%左右的工作渣滓在一个10岁的代码库,而5%的实际编写代码。实际上,不可能测试系统的某些方面(我不是说单元测试,而是指实际上运行代码以查看它是否有效)。我没有经历6个月的试用期,我厌倦了打拼和编写无法忍受的代码。
Binary Worrier 2010年

50
关于拆分文件的历史记录跟踪方面:使用版本控制系统的copy命令复制整个文件,无论您要拆分多少次,然后从每个不需要的副本中删除所有代码在那个文件中。这保留了整个历史记录,因为每个拆分文件都可以追溯到拆分后的历史记录(这看起来像是文件大部分内容的巨大删除)。
rmeador'9

Answers:


86
  1. 在文件中找到一些相对稳定的代码(变化不快,分支之间的变化不大),并且可以作为一个独立的单元使用。将其移到其自己的文件中,并在所有分支中移至其自己的类中。因为它是稳定的,所以当您将更改从一个分支合并到另一个分支时,这将不会导致(许多)“笨拙”的合并,而该合并必须应用于与原始文件不同的文件。重复。

  2. 在文件中找到一些代码,这些代码基本上仅适用于少量分支,并且可以独立存在。由于分支数量少,它是否快速变化都无关紧要。将其移到其自己的类和文件中。重复。

因此,我们摆脱了到处都是相同的代码以及特定于某些分支的代码。

这给您留下了管理不善的代码的核心-到处都需要它,但是每个分支都不同(和/或它不断变化,以便某些分支在其他分支后面运行),但是它位于单个文件中尝试在分支之间合并失败。别那样做。永久分支文件,也许通过在每个分支中重命名它。它不再是“主要”,而是“配置X的主要”。好的,因此您失去了通过合并将相同的更改应用于多个分支的能力,但这在任何情况下都是合并无法很好工作的代码核心。如果您仍然必须手动管理合并以处理冲突,那么在每个分支上独立地手动应用合并就不会有损失。

我认为您错误地说SCC的类型无关紧要,因为例如git的合并功能可能比您使用的合并工具更好。因此,对于不同的SCC,核心问题“合并困难”在不同的时间发生。但是,您不太可能更改SCC,因此该问题可能无关紧要。


至于合并:我看过GIT,我看过SVN,我看过Perforce,让我告诉你,在我们所做的工作中,我看不到任何地方能胜过AccuRev + Araxis。:-)(尽管GIT可以做到这一点[ stackoverflow.com/questions/1728922/… ],而AccuRev却不能-每个人都必须自己决定这是合并还是历史分析的一部分。)
Martin Ba

足够公平-也许您已经拥有最好的工具。Git能够将分支X的文件A中发生的更改合并到分支Y的文件B中,这应该使拆分分支文件更加容易,但是想必您使用的系统具有您所喜欢的优点。无论如何,我不是建议您切换到git,而只是说SCC在这里确实有所作为,但是即使如此,我也同意您可以忽略不计:-)
Steve Jessop 2010年

129

合并不会像将来那样麻烦,因为将来您将获得30000 LOC文件。所以:

  1. 停止向该文件添加更多代码。
  2. 拆分它。

如果你不能只停留在重构过程中的编码,你可以离开这个大文件作为是一会儿至少不增加更多的代码是:因为它包含一个“主类”,你可以从它继承并保留继承的类( es)在几个新的经过精心设计的新文件中具有重载功能。


@Martin:幸运的是您还没有将文件粘贴到这里,所以我不知道它的结构。但是一般的想法是将其分为逻辑部分。这些逻辑部分可以包含“主类”中的功能组,也可以将其分为几个辅助类。
Kirill V. Lyadvinsky'9

3
使用10个维护版本和许多活跃的开发人员,不太可能将文件冻结足够长的时间。
科比

9
@Martin,您有几个可以解决问题的GOF模式,一个Facade映射了mainmodule.cpp的功能,或者(下面我建议)创建了一套Command类,每个类都映射到一个功能/ mainmodule.app的功能。(我已经在回答中扩展了此内容。)
ocodo 2010年

2
是的,我完全同意,在某个时候,您必须停止向其中添加代码,否则最终它将变成30k,40k,50k,kaboom主模块仅出现段故障。:-)
克里斯

67

在我看来,您在这里面临着许多代码异味。首先,主要阶级似乎违反了开放/封闭原则。听起来好像它正在处理太多的职责。因此,我认为代码比所需的要脆弱。

虽然我可以理解您对重构后的可追溯性的担忧,但我希望此类很难维护和增强,并且您所做的任何更改都可能导致副作用。我认为这些成本超过了重构类的成本。

无论如何,由于代码的气味只会随着时间的流逝而变得更糟,至少在某些时候,它们的代价将超过重构的代价。根据您的描述,我认为您已经超过了临界点。

重构此过程应分几步完成。如果可能,请重构任何内容之前添加自动测试以验证当前行为。然后挑选出隔离功能的小区域并将其提取为类型,以委派职责。

无论如何,这听起来像是一个重大项目,祝您好运:)


18
它闻起来很香:闻起来像Blob反模式在房子里…… en.wikipedia.org/wiki/God_object。他最喜欢的一餐是意大利面条代码:en.wikipedia.org/wiki/Spaghetti_code :-)
jdehaan 2010年

@jdehaan:我想对此进行外交:)
Brian Rasmussen 2010年

+1也从我这里,我什至不敢触摸我编写的没有测试的复杂代码。
丹尼·托马斯

49

我曾经想过的唯一解决方案如下。所描述的方法的实际收益是进化的进步。这里没有革命,否则您将很快陷入困境。

在原始主类上方插入一个新的cpp类。目前,它基本上会将所有调用重定向到当前的主类,但目的是使此新类的API尽可能清晰明了。

完成此操作后,您就可以在新类中添加新功能。

至于现有功能,您必须在它们变得足够稳定后逐步将它们移入新类。您将失去这段代码的SCC帮助,但是对此您将无能为力。只要选择正确的时间。

我知道这不是完美的方法,尽管我希望它可以提供帮助,并且必须使该过程适应您的需求!

附加信息

请注意,Git是一个SCC,可以将代码片段从一个文件传输到另一个文件。我听说过有关它的好消息,因此在您逐步移动工作时可能会有所帮助。

Git是围绕Blob概念构建的,如果我理解正确的话,它代表代码文件。将这些片段移动到不同的文件中,即使您对其进行了修改,Git也会找到它们。除了下面的评论中提到的Linus Torvalds的视频外,我还无法找到一些清楚的信息。


在参考如何 GIT这是否/如何使用Git做这将是最欢迎的。
Martin Ba 2010年

@Martin Git自动执行。
马修(Matthew)2010年

4
@Martin:Git自动执行它-因为它不跟踪文件,所以它跟踪内容。在git中,仅“获取一个文件的历史记录”实际上更困难。
Arafangion'9

1
@Martin youtube.com/watch?v=4XpnKHJAok8是Torvalds谈论git的话题。他稍后在谈话中提到了这一点。
马修2010年

6
@马丁,看看这个问题:stackoverflow.com/questions/1728922/...
Benjol


25

让我猜测:十个具有不同功能集的客户和一个促进“定制”的销售经理?我以前从事过类似产品的工作。我们基本上有同样的问题。

您认识到拥有一个巨大的文件是很麻烦的,但是更麻烦的是必须保持“最新”的十个版本。那是多重维护。SCC可以使这一过程变得更容易,但是却无法实现。

在尝试将文件分成几部分之前,需要使十个分支彼此重新同步,以便您可以一次查看和修改所有代码。您可以一次执行一个分支,并针对同一主代码文件测试两个分支。要强制执行自定义行为,可以使用#ifdef和friends,但是最好对定义的常量使用普通的if / else。这样,您的编译器将验证所有类型,并且无论如何都可能消除“死”目标代码。(不过,您可能要关闭有关无效代码的警告。)

一旦所有分支隐式共享该文件的一个版本,那么开始传统的重构方法就变得更加容易。

#ifdefs主要适用于受影响的代码仅在其他按分支的自定义环境下才有意义的部分。有人可能会争辩说,这些也为相同的分支合并方案提供了机会,但不要胡乱。请一次完成一个庞大的项目。

在短期内,该文件似乎会增大。还行吧。您正在做的是将需要整合在一起的事物整合在一起。之后,您将开始看到明显不同的区域,无论版本如何;这些可以单独放置或随意重构。其他区域显然会根据版本而有所不同。在这种情况下,您有很多选择。一种方法是将差异委派给每个版本的策略对象。另一个是从通用抽象类派生客户端版本。但是,只要您在不同分支机构中有十个“发展秘诀”,这些转换就不可能实现。


2
我同意目标应该是拥有一个版本的软件,但是使用配置文件(运行时)而不是编译时间聚集不是更好吗
Esben Skov Pedersen

甚至每个客户构建的“配置类”。
tc。

我认为编译时或运行时配置在功能上无关紧要,但我不想限制这种可能性。编译时配置的优点是,客户端无法随意使用配置文件来激活其他功能,因为它会将所有配置放在源树中,而不是作为可部署的“文本对象”代码。不利的一面是,如果运行时,您倾向于使用AlternateHardAndSoftLayers。
伊恩(Ian)2010年

22

我不知道这是否可以解决您的问题,但是我想您要做的是将文件的内容迁移到彼此独立的较小文件中(汇总)。我还得到的是,您大约有10种不同版本的软件在浮动,您需要在不混乱的情况下全力支持它们。

首先,这是没有办法很容易的,并且在几分钟的头脑风暴中就能解决自己。文件中链接的功能对于您的应用程序都是至关重要的,仅将其切掉并将其迁移到其他文件并不能解决您的问题。

我认为您只有以下选择:

  1. 不要迁移并保持现有财产。可能辞职并开始使用具有良好设计的严肃软件。如果您正在使用足够的资金来承受一两次崩溃的长期项目,那么极限编程并不总是最好的解决方案。

  2. 制定一个布局,安排文件分割后的外观。创建必要的文件并将其集成到您的应用程序中。重命名函数或重载它们以使用其他参数(也许只是一个简单的布尔值?)。一旦需要处理代码,就将需要处理的功能迁移到新文件,并将旧功能的功能调用映射到新功能。您仍然应该以这种方式拥有您的主文件,并且一旦涉及到特定功能,您仍然可以看到对其所做的更改,您确切地知道了何时将其外包等等。

  3. 尝试用一些不错的蛋糕说服您的同事,他们夸大了工作流程,并且您需要重写应用程序的某些部分才能进行认真的业务。


19

正是在“使用旧版代码有效工作”一章中(http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052)处理了此问题。


notifyit.com/store/product.aspx?isbn=0131177052使您可以查看本书的目录(以及2个示例章节)。第20章要多长时间?(只是感觉它可能有用。)
Martin Ba

17
第20章的长度为10,000行,但作者正在研究如何将其拆分为可消化的块... 8)
Tony Delroy 2010年

1
它约23页,但有14张图像。我认为您应该做到这一点,尝试决定做恕我直言时,您会更有信心。
Emile Vrijdags'9

关于问题的出色书籍,但是它提出的建议(以及该线程中的其他建议)都具有一个共同的要求:如果要为所有分支重构此文件,则唯一的方法是冻结所有分支的文件,并进行初始结构更改。没有办法解决。本书概述了一种迭代方法,该方法通过创建重复的方法和委派调用来安全地提取子类,而无需自动重构支持,但是如果您不能修改文件,那么所有这些都无济于事。
丹·布莱恩特

2
@Martin,这本书非常好,但是它确实在很大程度上依赖于测试,重构和测试周期,从现在开始,这可能会非常困难。我遇到过类似的情况,这本书是我发现的最有帮助的书。对于您遇到的丑陋问题,有很好的建议。但是,如果您无法获得某种形式的测试工具,那么世界上所有的重构建议都将无济于事。

14

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

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

当然,使用11个KLOC的单个类,其中的代码可能高度耦合且脆弱,但是创建单独的命令类将比其他任何代理/外观策略提供更多帮助。

我并不羡慕这个任务,但是随着时间的流逝,这个问题只会在不解决的情况下变得更糟。

更新资料

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

最好在(相对)整体式Facade上维护/组织很多不同的Command类。将单个Facade映射到11 KLOC文件本身可能需要分为几个不同的组。

为什么要花时间找出这些门面组呢?使用Command模式,您将能够有机地对这些小类进行分组和组织,因此您具有更大的灵活性。

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


+1提出了我提出的解决方案的替代方案,其思路相同:更改API,以将大问题分解为小问题。
伯努瓦

13

重要建议之一:不要混用重构和错误修正。您想要的是程序的一个版本,该版本与先前的版本相同,只是源代码不同。

一种方法可能是开始将最小的功能/部分拆分到它自己的文件中,然后包含一个标头(因此将main.cpp变成#include列表,这本身听起来就有代码味*我不是虽然是C ++专家),但至少现在已拆分为文件)。

然后,您可以尝试将所有维护版本切换到“新” main.cpp或任何结构。再次:没有其他更改或错误修正,因为跟踪这些更改或修正会令人感到困惑。

另一件事:尽管您希望一次通过整个过程就可以重构整个过程,但是您可能会咬得更多,无法咀嚼。也许只选择一个或两个“部分”,将其放入所有发行版中,然后为您的客户增加一些价值(毕竟,重构不会增加直接价值,因此这是必须证明的成本),然后再选择另一个一两个部分。

显然,这需要团队中的某些纪律真正使用拆分文件,而不仅仅是一直在main.cpp中添加新内容,但是再次尝试进行一次大规模重构可能不是最佳的做法。


1
+1表示分解,然后#include重新加入。如果您对所有10个分支(那里的工作量很大,但可管理)都这样做了,您仍然会遇到另一个问题,那就是在所有分支上发布更改,但是这个问题不会t已扩展(必要)。丑吗?是的,它仍然是,但是它可能会给这个问题带来一点点理性。我花了几年的时间对一个非常大的产品进行维护和保养,我知道维护会带来很多痛苦。至少要从中学习,并作为对其他人的警示故事。
杰伊(Jay

10

Rofl,这让我想起了我的旧工作。看来,在我加入之前,所有内容都在一个大文件(也包括C ++)中。然后,他们将其分成了三个文件(仍然是巨大的文件)(使用include在完全随机的位置)。如您所料,该软件的质量令人震惊。该项目的总LOC约为4万。(几乎没有注释,但有很多重复代码)

最后,我对项目进行了完整的重写。我从头开始重做了项目中最糟糕的部分。当然,我想到了这个新零件与其余零件之间可能的(小)接口。然后,我确实将此部分插入了旧项目。我没有重构旧代码来创建必要的接口,而是将其替换了。然后,我从那里采取了一些小步骤,重写了旧代码。

我不得不说,这花了大约半年的时间,在此期间,除了错误修复之外,没有开发旧的代码库。


编辑:

大小保持在大约4万个LOC,但新应用程序包含的功能比8岁的软件还多,并且其初始版本中的错误可能更少。重写的原因之一是我们需要新功能,并且几乎不可能在旧代码中引入它们。

该软件用于嵌入式系统,即标签打印机。

我应该补充的一点是,理论上该项目是C ++。但是它根本不是面向对象的,它可能是C。新版本是面向对象的。


9
每当我听到有关重构的话题“从头开始”时,我都会杀死一只小猫!
库格尔2010年

我一直处在一个非常相似的情况下,尽管我必须处理的主程序循环只有〜9000 LOC。这已经够糟糕了。
AndyUK

8

好的,因此在大多数情况下,重写生产代码的API首先是一个坏主意。需要发生两件事。

第一,您实际上需要让您的团队决定对此文件的当前生产版本进行代码冻结。

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

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

要么

一种更耗时但健壮的方法是设计一种内部依赖项结构,它在事物如何包含方面具有双重间接性。这将使您可以拆分事物,并且仍然可以照顾到相互依赖性。请注意,此方法需要位置编码,因此应结合适当的注释。

这种方法将包括根据要编译的变体使用的组件。

基本结构是您的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更改(生产过程中的噩梦)。

生产完成后,您就可以进行重新设计了。


这看起来像是经验的作品,但最初是痛苦的结果。
JBRWilkinson

实际上,它是Borland C ++编译器中用来模仿Pascal样式用于管理头文件的一种技术。尤其是当他们完成基于文本的Windowing系统的初始移植时。
精灵王

8

好吧,我理解您的痛苦:)我也参与了一些此类项目,而且还不够好。对此没有简单的答案。

一种可能对您有用的方法是开始在所有函数中添加安全防护,即检查方法中的参数,前置条件/​​后置条件,然后最终添加所有单元测试以捕获源的当前功能。一旦有了这个,您就可以更好地重构代码,因为如果您忘记了某些内容,将会弹出断言和错误来提醒您。

有时候,尽管有时候重构只是带来了痛苦而不是好处。然后最好只保留原始项目并处于伪维护状态,然后从头开始,然后从野兽中逐步添加功能。



4

您拥有的是一个经典示例,称为设计反模式,称为 blob。花一些时间阅读我在此处指出的文章,也许您会发现有用的东西。此外,如果这个项目看起来很大,那么您应该考虑一些设计,以防止其发展为您无法控制的代码。


4

这不是解决大问题的答案,而是针对特定问题的理论解决方案:

  • 找出要将大文件拆分为子文件的位置。在每一点上都以某种特殊格式添加注释。

  • 编写一个相当琐碎的脚本,将这些文件分解为子文件。(也许特殊注释嵌入了文件名,脚本可以将其用作拆分方法的说明。)在拆分过程中,应保留注释。

  • 运行脚本。删除原始文件。

  • 当您需要从分支合并时,请先通过将片段重新连接在一起来重新创建大文件,进行合并,然后重新拆分。

另外,如果您想保留SCC文件的历史记录,我希望做到这一点的最佳方法是告诉您的源代码管理系统,单个文件是原始文件的副本。然后,它将保留该文件中保留的节的历史记录,尽管当然也将记录“已删除”的大部分。


4

在没有太大危险的情况下拆分它的一种方法是对所有线路更改进行历史考察。是否有某些功能比其他功能更稳定?如果愿意的话,变化的热点。

如果几年来没有更改任何行,则可以不用过多担心将其移动到另一个文件。我将看一下带有最后一个修订版本的源代码,该版本触及给定的行,并查看是否有可以提取的功能。


我认为其他人也提出了类似的建议。这是简短的要点,我认为这可能是原始问题的有效起点。
Martin Ba 2010年

3

哇,听起来不错。我认为向您的老板解释说,您需要大量时间来重构野兽值得尝试。如果他不同意,则可以选择退出。

无论如何,我建议基本上是放弃所有实现并将其重新组合到新模块中,我们将其称为“全局服务”。“主模块”将仅转发到那些服务,您编写的任何新代码都将使用它们,而不是“主模块”。这应该在合理的时间内是可行的(因为主要是复制和粘贴),您不会破坏现有代码,并且可以一次完成一个维护版本。而且,如果还有剩余时间,您可以将其花费在重构所有旧的依赖模块上,以也使用全局服务。


3

我的同情-在上一份工作中,我遇到了类似的情况,该文件的大小比您要处理的文件大几倍。解决方案是:

  1. 编写代码以详尽地测试有关程序中的功能。听起来好像您手头已经没有了...
  2. 标识一些可以抽象为helper / utilities类的代码。不必太大,只要不是真正属于您的“主要”课程的内容即可。
  3. 将2.中标识的代码重构为一个单独的类。
  4. 重新运行测试以确保没有损坏。
  5. 有时间时,转到2.并根据需要重复操作以使代码易于管理。

您在第3步中构建的类。迭代可能会吸收更多适合其新近使用的函数的代码。

我还可以添加:

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

不幸的是,这种类型的工作太普遍了,但是我的经验是,能够在使代码正常工作的同时保持其正常工作的同时,减少其工作量,这具有很大的价值。


2

考虑以更明智的方式重写整个应用程序的方法。也许将其中的一小部分重写为原型,以查看您的想法是否可行。

如果您确定了可行的解决方案,请相应地重构应用程序。

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


+1-在您自己的时间重写它,但是否则有人可能会吐他们的假人。
乔恩·布莱克2010年

2

我的0.05欧分:

重新设计整个混乱,并考虑到技术和业务需求将其分为子系统(=多个并行维护轨道,每个轨道可能具有不同的代码库,显然需要高可修改性,等等)。

拆分为子系统时,分析变化最大的位置并将其与不变的部分分开。这应该向您显示问题点。将变化最大的部分分离到自己的模块(例如dll)中,以使模块API保持完整,并且您无需一直破坏BC。这样,如果需要,可以在不更改核心的情况下为不同的维护分支部署模块的不同版本。

重新设计可能需要是一个单独的项目,将其尝试到一个移动的目标将无法正常工作。

至于源代码的历史,我的观点是:为新代码而忘记它。但是将历史记录保存在某个地方,以便在需要时可以进行检查。我敢打赌,从一开始你就不需要那么多了。

您很可能需要获得该项目的管理层支持。您可以通过更快的开发时间,更少的错误,更容易的维护和更少的总体混乱来争论。遵循“主动实现我们的关键软件资产的面向未来和维护生存能力”的思路:)

这就是我至少将开始解决该问题的方式。


2

首先添加评论。关于调用函数的位置以及是否可以移动的内容。这可以使事情前进。您确实需要评估代码的脆弱程度。然后将功能的通用位放在一起。一次小的变化。



2

我发现要做的事情是有用的(尽管现在还没有达到您的要求,我现在正在做)是将方法提取为类(方法对象重构)。不同版本之间不同的方法将成为不同的类,可以将这些类注入一个通用库中,以提供所需的不同行为。


2

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

>该文件已在我们产品的多个(> 10)维护版本中使用并进行了有效更改,因此很难对其进行重构

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

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

我已经感觉到你已经畏缩了!但是由于缺少功能,或者您的源代码管理不适用于您的情况,或者未正确使用它。

现在到您从事的分支机构-在您认为合适的情况下对其进行重构,请放心,因为您不会打扰产品的其他9个分支机构。

我会有点担心您的main()函数中有太多内容。

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

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

最后,在主程序中,我还将在预处理器块中添加泄漏检测代码,以确保仅在DEBUG构建中启用泄漏检测代码。这就是我要添加到main()中的所有内容。Main()应该很短!

你这样说

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

听起来这两个任务可以分为两个单独的对象-协调员和工作调度员。

拆分这些文件时,您可能会弄乱“ SCC工作流程”,但听起来像严格遵守SCC工作流程会导致软件维护问题。现在就抛弃它,不要回头,因为一旦修复它,您就会开始入睡。

如果您无法做出决定,请与您的经理费尽周折-您的应用程序需要重构-听起来很糟糕!不要拒绝!


据我了解,问题是这样的:如果您硬着头皮进行重构,那么您将无法再在两个版本之间携带补丁。SCC可能已完美设置。
peterchen'9

@peterchen-正是问题所在。SCC确实在文件级别进行合并。(三向合并)如果在文件之间移动代码,则必须开始手动将修改后的代码块从一个文件移到另一个文件。(在另一条评论中提到的其他人的GIT功能仅对历史有益,而就我所知,不适合合并)
Martin Ba

2

正如您所描述的,主要问题是区分拆分前与拆分后,合并错误修复等。围绕它的工具。用Perl,Ruby等对脚本进行硬编码将花费很长时间,从而消除了将预分割与后分割的级联进行比较的大部分噪音。在处理噪音方面,请执行最简单的操作:

  • 在连接之前/期间删除某些行(例如,包括警卫队)
  • 如有必要,从diff输出中删除其他内容

您甚至可以做到这一点,只要有签入,连接就会运行,并且您已经准备好与单文件版本进行比较。


2
  1. 请勿再触摸此文件和代码!
  2. 对待就像是您坚持不懈的事情。开始为在那里编码的功能编写适配器。
  3. 用不同的单元编写新代码,并且只与封装怪物功能的适配器通信。
  4. ...如果以上条件之一不可行,请辞职并重新找工作。

2
+/- 0-认真的说,您生活在哪里的人们建议您根据这样的技术细节离职?
马丁·巴

1

“文件基本上包含了程序的“主类”(主要内部工作的调度和协调),因此,每添加一个功能,也会影响该文件以及文件的每次扩展。”

如果那个大的SWITCH(我认为是这样)成为主要的维护问题,则可以将其重构为使用字典和Command模式,并将所有开关逻辑从现有代码移至加载器,以填充该映射,即:

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

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

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

2
不,实际上没有大的开关。这句话是我能用来形容这个烂摊子的最接近的句子:)
Martin Ba

1

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

  1. 使用SCM系统提供的任何保留历史记录的复制命令复制原始源代码。此时,您可能需要提交,但是无需将新文件告知构建系统,因此应该可以。
  2. 从这些副本中删除代码。这不会破坏您保留的台词的历史记录。

“使用您的SCM系统提供的任何保留历史记录的复制命令”……不好的是它不提供任何内容
Martin Ba 2010年

太糟糕了。仅此一项听起来就是切换到更现代的东西的一个很好的理由。:-)
Christopher Creutzig 2010年

1

我认为在这种情况下我会做些什么,并且:

  1. 弄清楚我如何拆分文件(基于当前开发版本)
  2. 在文件上设置管理锁定(“星期五下午5点后没有人可以触摸mainmodule.cpp !!!”
  3. 花费漫长的周末,将更改应用到> 10个维护版本(从最旧到最新),直至并包括当前版本。
  4. 从软件的所有受支持版本中删除mainmodule.cpp。这是一个新时代-不再有mainmodule.cpp。
  5. 让管理层说服您不应该支持该软件的多个维护版本(至少没有大量的$$$支持合同)。如果您的每个客户都有各自独特的版本,.... yeeeeeshhhh。我会添加编译器指令,而不是尝试维护10个以上的fork。

跟踪旧文件更改仅通过您的第一个签到注释中说的类似“从mainmodule.cpp拆分”即可解决。如果您需要返回最近的内容,大多数人会记住该更改,如果距现在2年,则注释会告诉他们要去哪里。当然,回顾两年多的时间来查看谁更改了代码以及为什么更改,这将具有多大的价值?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.