41

您将如何将相当大(>300K)、相当成熟的 C 代码库转换为 C++?

考虑到的 CI 被拆分为大致对应于模块的文件(即比典型的基于 OO 类的分解更细化),使用内部链接代替私有函数和数据,以及公共函数和数据的外部链接。全局变量广泛用于模块之间的通信。有一个非常广泛的集成测试套件可用,但没有单元(即模块)级别的测试。

我想到了一个总体策略:

  1. 编译 C++ 的 C 子集中的所有内容并使其正常工作。
  2. 将模块转换为巨大的类,以便所有交叉引用都由类名限定,但将所有函数和数据保留为静态成员,并使其正常工作。
  3. 使用适当的构造函数和初始化的交叉引用将大类转换为实例;酌情用间接访问替换静态成员访问;并让它工作。
  4. 现在,将项目作为一个错误因素的 OO 应用程序来处理,并在依赖项易于处理的情况下编写单元测试,并在它们不易于处理的情况下分解为单独的类;这里的目标是在每次转换时从一个工作程序转移到另一个工作程序。

显然,这将是相当多的工作。有没有关于这种翻译的案例研究/战争故事?替代策略?其他有用的建议?

注 1:该程序是一个编译器,可能有数百万其他程序依赖于它的行为不变,因此大规模重写几乎不是一种选择。

注意 2:源代码已有近 20 年的历史,每年可能有 30% 的代码流失(修改的行数 + 添加的行数/之前的总行数)。换句话说,它被大量维护和扩展。因此,目标之一是提高可维护性。

[为了这个问题,假设翻译成C++是强制性的,并且不能将其留在 C。添加此条件的目的是清除“将其保留在 C 中”的答案。]

4

11 回答 11

15

几个月前刚刚开始做几乎相同的事情(在一个有十年历史的商业项目上,最初是用“C++ 只不过是 C 和 smart structs”哲学编写的),我建议使用与你相同的策略' d 习惯吃大象:一次吃一口。:-)

尽可能将其分成几个阶段,这些阶段可以在对其他部分的影响最小的情况下完成。正如Federico Ramponi所建议的那样,构建外观系统是一个好的开始——一旦所有东西都具有 C++ 外观并通过它进行通信,您就可以相当肯定地更改模块的内部结构,确保它们不会影响外部的任何东西。

我们已经有了一个部分 C++ 接口系统(由于之前的重构工作较小),所以这种方法在我们的案例中并不困难。一旦我们将所有东西都作为 C++ 对象进行通信(这花了几周时间,在一个完全独立的源代码分支上工作,并在获得批准后将所有更改集成到主分支),我们很少能编译出一个完全在我们离开之前的工作版本。

转换还没有完成——我们已经暂停了两次临时发布(我们的目标是每隔几周发布一次),但它正在顺利进行,没有客户抱怨任何问题。我们的 QA 人员也只发现了一个我记得的问题。:-)

于 2008-10-14T03:19:28.517 回答
12

关于什么:

  1. 编译 C++ 的 C 子集中的所有内容并使其正常工作,并且
  2. 实现一组外观保持 C 代码不变?

为什么“必须翻译成 C++”?您可以包装 C 代码,而无需将其转换为巨大的类等。

于 2008-10-14T00:57:39.847 回答
7

您的应用程序有很多人在处理它,并且需要不被破坏。如果您对大规模转换为 OO 风格很认真,那么您需要大量的转换工具来自动化工作。

基本思想是将数据组指定为类,然后使用工具重构代码以将该数据移动到类中,将仅针对该数据的函数移动到这些类中,并将对该数据的所有访问修改为对类的调用.

您可以进行自动预分析以形成统计集群以获得一些想法,但您仍然需要一位具有应用意识的工程师来决定应该对哪些数据元素进行分组。

能够完成这项任务的工具是我们的DMS Software Reengineering Toolkit。DMS 具有强大的 C 解析器来读取您的代码,将 C 代码捕获为编译器抽象语法树,(与传统编译器不同)可以计算整个 300K SLOC 的流分析。DMS 有一个可以用作“后端”的 C++ 前端;一个人编写将 C 语法映射到 C++ 语法的转换。

大型航空电子系统上的一项主要 C++ 重新设计任务提供了一些关于将 DMS 用于此类活动的概念。请参阅 www.semdesigns.com/Products/DMS/DMSToolkit.html 上的技术论文,特别是通过自动程序转换重新设计 C++ 组件模型

这个过程不适合胆小的人。但是,任何考虑手动重构大型应用程序的人已经不怕辛苦了。

是的,我是公司的首席架构师。

于 2009-06-14T04:32:57.020 回答
5

我会在 C 接口上编写 C++ 类。不接触 C 代码将减少搞砸的机会并显着加快进程。

一旦你的 C++ 接口启动;那么将代码复制+粘贴到您的类中是一项微不足道的任务。正如您所提到的 - 在此步骤中,进行单元测试至关重要。

于 2008-10-14T00:54:57.233 回答
4

GCC 目前正处于从 C 向 C++ 的过渡阶段。显然,他们首先将所有内容都移入了 C 和 C++ 的公共子集。当他们这样做时,他们向 GCC 添加了针对他们发现的所有内容的警告,在-Wc++-compat. 这应该让您踏上旅程的第一部分。

对于后面的部分,一旦你真的用 C++ 编译器编译了所有东西,我将专注于替换具有惯用 C++ 对应物的东西。例如,如果您正在使用使用 C 宏定义的列表、映射、集合、位向量、哈希表等,那么通过将它们迁移到 C++ 可能会收获很多。与 OO 类似,您可能会在已经使用 C OO 习惯用法(如结构继承)的地方找到好处,并且 C++ 将在您的代码上提供更高的清晰度和更好的类型检查。

于 2009-08-29T15:06:47.610 回答
3

您的列表看起来不错,但我建议先查看测试套件并在进行任何编码之前尝试使其尽可能紧凑。

于 2008-10-14T01:19:39.087 回答
3

让我们抛出另一个愚蠢的想法:

  1. 编译 C++ 的 C 子集中的所有内容并使其正常工作。
  2. 从一个模块开始,将其转换为一个巨大的类,然后在一个实例中,并从该实例中构建一个 C 接口(与您开始时的接口相同)。让剩余的 C 代码与该 C 接口一起工作。
  3. 根据需要进行重构,一次将 OO 子系统从 C 代码中扩展一个模块,并在 C 接口变得无用时删除部分 C 接口。
于 2008-10-14T02:03:52.820 回答
3

除了你想如何开始之外,可能还有两件事要考虑,那就是你想关注什么,以及你想在哪里停止

你说有一个大的代码流失,这可能是你集中精力的关键。我建议你选择代码中需要大量维护的部分,成熟/稳定的部分显然工作得很好,所以最好保持原样,除了一些带有外墙的门面装饰等。

您要停止的位置取决于要转换为 C++ 的原因。这本身很难成为一个目标。如果是由于某些第 3 方依赖,请将精力集中在该组件的接口上。

我工作的软件是一个庞大的旧代码库,多年前已经从 C 语言“转换”为 C++。我认为这是因为 GUI 被转换为 Qt。即使是现在,它仍然看起来像一个带有类的 C 程序。打破公共数据成员造成的依赖关系,将具有过程怪物方法的巨大类重构为更小的方法和类从未真正起飞,我认为原因如下:

  1. 无需更改正在运行且无需增强的代码。这样做会引入新的错误而没有添加功能,最终用户不会欣赏这一点;
  2. 可靠地进行重构非常非常困难。许多代码是如此之大,也如此重要,以至于人们几乎不敢碰它。我们有一套相当广泛的功能测试,但很难获得足够的代码覆盖率信息。因此,很难确定是否已经有足够的测试来检测重构期间的问题;
  3. 投资回报率很难确定。最终用户不会从重构中受益,因此它必须降低维护成本,最初会增加,因为重构会在成熟的代码中引入新的错误,即相当无错误的代码。重构本身也将是昂贵的......

注意。我想您知道“有效地使用旧代码”一书吗?

于 2008-10-14T07:42:42.230 回答
2

您提到您的工具是一个编译器,并且:“实际上,模式匹配,而不仅仅是类型匹配,在多重调度中会更好”。

You might want to take a look at maketea. It provides pattern matching for ASTs, as well as the AST definition from an abstract grammar, and visitors, tranformers, etc.

于 2009-08-29T15:12:36.320 回答
1

如果您有一个小型或学术项目(例如,少于 10,000 行),那么重写可能是您的最佳选择。您可以根据需要考虑它,并且不会花费太多时间。

如果您有一个真实世界的应用程序,我建议将其编译为 C++(这通常意味着主要修复函数原型等),然后进行重构和 OO 包装。当然,我不赞同代码必须是面向对象的结构才能成为可接受的 C++ 代码的理念。我会根据需要进行逐个转换、重写和重构(用于功能或合并单元测试)。

于 2008-10-14T01:01:18.700 回答
1

这是我要做的:

  • 由于代码已有 20 年的历史,因此废弃解析器/语法分析器并将其替换为较新的基于 lex/yacc/bison(或任何类似的)等 C++ 代码,更易于维护和理解。如果您手边有 BNF,开发速度也会更快。
  • 一旦对旧代码进行了改造,就开始将模块包装到类中。用接口替换全局/共享变量。
  • 现在你所拥有的将是一个 C++ 编译器(虽然不完全)。
  • 画出系统中所有类的类图,看看它们是如何通信的。
  • 使用相同的类绘制另一个,看看它们应该如何通信。
  • 重构代码以将第一个图表转换为第二个图表。(这可能是混乱和棘手的)
  • 请记住对添加的所有新代码使用 C++ 代码。
  • 如果您还有一些时间,请尝试将数据结构一一替换以使用更标准化的 STL 或 Boost。
于 2008-10-14T16:41:55.760 回答