如何更新旧的C代码?

Ben*_*313 36 c c++ refactoring

我本周在工作中一直在研究一些已有10年历史的C代码,在实施了一些修改之后,我去了老板,问他是否需要做其他事情.那是他放下炸弹的时候.我的下一个任务是遍历7000行左右,了解更多代码,稍微模块化代码.我问他如何将源代码模块化,他说要开始将旧的C代码放入C++类中.

作为一名优秀的工作者,我点头表示赞成,然后回到我的办公桌,我现在坐在那里,想知道世界上如何采用这些代码,并"模块化"它.它已经有20个源文件,每个文件都有自己的用途和功能.此外,还有三个"主要"结构.这些结构中的每一个都有30多个字段,其中许多是其他较小的结构.尝试理解它是一个完全混乱,但程序中几乎每个函数都传递一个指向其中一个结构的指针并大量使用结构.

有没有什么干净的方式让我把它变成课堂?如果可以的话,我决心这样做,我只是不知道如何开始.

小智 35

首先,你很幸运有一个老板认识到代码重构可以成为一个长期的节省成本的策略.

我已经多次这样做了,就是将旧的C代码转换为C++.这些好处可能让您大吃一惊 完成后,最终代码可能是原始大小的一半,并且更易于阅读.此外,您可能会发现一路上棘手的C错误.以下是我将采取的步骤.小步骤很重要,因为在重构大量代码时,不能从A跳​​到Z. 您必须经历可能永远不会部署的小型中间步骤,但可以在您使用的任何RCS中对其进行验证和标记.

  1. 创建回归/测试套件.每次完成对代码的一批更改时,您将运行测试套件.你应该已经拥有它,它不仅仅是这个重构任务.花点时间让它变得全面.创建测试套件的练习将使您熟悉代码.
  2. 在您选择的修订控制系统中分支项目.有了测试套件和游乐场分支,您将有权对代码进行大量修改.你不会害怕破蛋.
  3. 将这些结构字段设为私有.此步骤只需要很少的代码更改,但可以获得很大的回报.一次进行一个字段.尝试创建每个字段private(是,或受保护),然后隔离访问该字段的代码.最简单,最具侵入性的转换是将代码编写为a friend function.还要考虑将该代码作为一种方法.将代码转换为方法很简单,但您还必须转换所有调用站点.一个不一定比另一个好.
  4. 缩小每个功能的参数.任何函数都不太可能需要访问作为参数传递的结构的所有30个字段.而不是传递整个结构,只传递所需的组件.如果函数确实似乎需要访问结构的许多不同字段,那么这可能是转换为实例方法的一个很好的候选者.
  5. 尽可能多地保留变量,参数和方法.许多旧的C代码无法const自由使用.从底部向上扫描(调用图的底部,即),您将为代码添加更强的保证,并且您将能够从非mutator中识别mutator.
  6. 敏感的引用替换指针.这一步的目的与更多的C++无关 - 就像为了更多的C++一样.目的是识别永远NULL不会重新分配的参数.将引用视为编译时断言,它表示,这是有效对象的别名,并在整个当前作用域中表示相同的对象.
  7. 替换char*std::string.这一步应该是显而易见的.您可能会大大减少代码行.另外,用一行替换10行代码很有趣.有时你可以删除整个函数,其目的是执行C++中标准的C字符串操作.
  8. 将C数组转换为std::vectorstd::array.同样,这一步应该是显而易见的.这个转换是从比转化简单得多charstd::string因为接口std::vectorstd::array被设计成匹配C数组语法.其中一个好处是可以消除length传递给数组旁边的每个函数的额外变量.
  9. 转换malloc/ freenew/delete.此步骤的主要目的是为将来的重构做好准备.仅仅更改C代码malloc,以new不直接获得你多少.此转换允许您向这些结构添加构造函数和析构函数,并使用内置的C++自动内存工具.
  10. 用本系列替换本地化new/ delete操作std::auto_ptr.此步骤的目的是使您的代码异常安全.
  11. 通过冒泡来处理返回代码的任何地方都会抛出异常. 如果C代码通过检查特殊错误代码来处理错误,然后将错误代码返回给其调用者,依此类推,将错误代码冒泡到调用链上,那么该C代码可能是使用异常的候选者.这种转换实际上是微不足道的.只需throw返回代码(C++允许您在最低级别抛出任何类型).try{} catch(){}在代码中处理错误的位置插入语句.如果不存在处理错误的合适位置,请考虑将语句包装main()在一个try{} catch(){}语句中并记录它.

现在退一步看看你有多少改进了代码,而没有将任何内容转换为类.(是的,是的,从技术上讲,你的结构已经是类.)但是你没有触及OO的表面,但设法大大简化并巩固了原始的C代码.

您应该将代码转换为使用类,具有多态性和继承图吗?我拒绝.C代码可能没有适合OO模型的整体设计.请注意,上述每个步骤的目标与将OO原则注入C代码无关.目标是通过实施尽可能多的编译时约束以及通过消除或简化代码来改进现有代码.

最后一步.

考虑添加基准,以便在完成后向老板展示.不仅仅是性能基准.比较代码行,内存使用情况,函数数量等.


小智 20

真的,7000行代码不是很多.对于如此少量的代码,可以按顺序完成重写.但这个代码将如何被调用?大概是调用者期望一个C API?或者这不是图书馆?

无论如何,重写与否,在开始之前,请确保您有一套测试,您可以在现有代码上轻松运行,无需人工干预.然后,在您进行的每个更改中,对新代码运行测试.

  • 它不仅仅是关于代码行的数量,而是它们的长度和复杂程度.这取决于他在做什么.一行的7000行代码可以相当于另一行的70 000行. (2认同)
  • 同意,7000行C听起来并不那么糟糕; 一个人应该可以将头围绕在一起,然后清楚地知道如何重构它. (2认同)

ome*_*med 19

这种对C++的看法似乎是随意的,问你的老板他为什么需要这样做,弄清楚你是否可以不那么痛苦地达到同一目标,看看你是否可以用新的不那么痛苦的方式对一个子集进行原型设计,然后去演示你的老板,并建议你按照不那么痛苦的方式.


Rob*_*boy 12

首先,告诉你的老板你没有继续,直到你有:

http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672

在较小程度上:

http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

其次,没有办法将代码模块化到C++类中.这是一项艰巨的任务,您需要向您的老板传达重构高度重复性代码的复杂性.

它归结为做一个小的改变(提取方法,移动方法等等......)然后进行测试 - 没有这方面的捷径.

我确实感觉到你的痛苦......


djn*_*jna 5

我想这里的想法是增加模块化将隔离代码片段,以便促进未来的变化.我们有信心改变一件,因为我们知道它不会影响其他作品.

我看到两个噩梦场景:

  1. 你有很好的结构化C代码,它很容易转换为C++类.在这种情况下,它可能已经非常模块化,你可能没有做任何有用的事情.
  2. 这是一个互相连接的老鼠窝.在这种情况下,解开它真的很难.增加模块化会很好,但这将是一个漫长的艰难困境.

然而,也许有一个愉快的媒介.可能存在一些重要且概念上孤立的逻辑,但由于缺乏数据隐藏等目前这些逻辑很脆弱(好的C不会受此影响,但我们没有这个,否则我们会留下好的单独).

拉出一个类来拥有那个逻辑及其数据,包含这个部分可能很有用.用C或C++做它是否更好是值得商榷的.(我的愤世嫉俗者说"我是一名C程序员,很棒的C++学习新东西的机会!")

所以:我认为这是一只可以吃的大象.首先要确定它是否应该被吃掉,糟糕的元素只是没有乐趣,结构良好的C应该被单独留下.第二次找到合适的第一口.我会回应尼尔的评论:如果你没有一个好的自动化测试套件,你就注定要失败.


pce*_*ent 5

我认为一个更好的方法可以完全重写代码,但你应该问你的老板他想要你" 开始将旧的C代码放入c ++类 "的目的.你应该询问更多细节