Golden Master Pattern :一种在.NET Core中重构遗留代码的利器
在软件开发领域中工作的任何人都将需要在旧代码中添加功能,这些功能可能是从先前的团队继承而来的,您需要对其进行紧急修复。
可以在文献中找到许多遗留代码的定义,我更喜欢的定义是:“通过遗留代码,我们指的是我们害怕改变的有利可图的代码”。
该定义包含两个基本概念:
- 该代码必须有利可图。如果不是这样,我们将无意对其进行更改。
- 它必须引起对修改它的恐惧,因为我们可以引入新的bug或依赖影子的东西。
在以下情况下,更容易出错:
- 测试未涵盖该代码。
- 代码不干净;不遵守单一责任原则。
- 该代码的设计不正确,或者随着时间的流逝其结构变得不合理:对一段代码进行更改可能会产生一些副作用。
- 您没有时间全面了解正在修改的内容。
测试是我们作为开发人员可用的强大武器。这些测试为我们提供了结果的安全性,并且是一种快速检测错误的方法。但是,我们如何测试未知的代码?构建单元测试套件将为我们提供有关该项目的深入知识,但它将使长时间保持高成本。如果我们无法测试细节,则可以使用Characterization Test,它是描述软件行为的测试。
在这种情况下起重要作用的模式是“ 黄金大师模式”。基本思想很简单:如果我们无法深入了解,我们需要一些有关整个执行过程的指标。我们捕获正确执行的输出(stdout,图像,日志文件等),这就是我们的Golden Master,可用于预期输出。如果当前执行的输出匹配,我们可以确信我们的更改没有引入新的错误。
为了展示Golden Master Pattern的用法,让我们从一个示例开始(完整的代码可以在此处找到)。我们公司开发了用于命令行的游戏,包括井字游戏(该游戏的实现从此处获取),我们的老板要求我们更改游戏以提供调整游戏板尺寸的能力。让我们看一下代码:
1 | namespace Tris |
快速阅读后,代码看上去很混乱,职责没有正确分开,变量名也没有意义。
经过准确的阅读后,我们可以找到游戏板,该游戏板存储在“ static char [] arr”中。向阵列添加新元素没有任何效果,因为该阵列直接在PrintBoard和CheckWin函数中访问。现在我们知道要调整游戏板的大小,必须更改大部分代码。
创建一个新项目并运行游戏:
1 | class Program |
一旦我们印刷了棋盘,游戏就会要求用户输入。我们可以通过从文件中读取输入来实现自动化。
1 | class Program |
所有输入的集合太大,无法使用蛮力测试。我们可以做的就是对输入进行采样。为此,我们考虑井字游戏的最终得分:
- 玩家1获胜
- 玩家2获胜
- 绘制图形
选择覆盖这三种情况的最低限度的测试集,在文本文件中编写路径,并在golendenMaster文件夹中收集结果:
1 | class Program |
这三个结果文件代表了我们的Golden Master,我们可以在此基础上进行一些特性测试:
1 | [Test] |
只要测试是绿色的,我们就可以重构而不必担心破坏某些东西。一种可能的结果可能是:
1 | public static void run() |
从这段代码中可以看出Board的概念及其职责。让我们尝试在新的Board类中提取行为。新Board应能够:
- 印刷板
- 标记玩家的选择
- 检查是否有赢家
使用TDD(更多详情,请阅读这篇文章)制定一个可调整大小的Board(发现测试的完整代码在这里和阶级的一个位置)。现在尝试将它们插入游戏中,并检查Golden Master是否保持绿色:
1 | private const int Boardsize = 3; |
此时,我们可以恢复标准输入/标准输出并从用户那里读取电路板的尺寸:
1 | class Program |
如您所见,多亏了Golden Master Pattern,我们能够控制遗留代码并进行重构,而无需担心。但是,所有闪闪发光的东西都不是金子:在“噪声输出”的情况下使用Golden Master可能会很困难,“噪声输出”对于执行无用,但会随时间(例如时间戳记,线程名等)而变化。在这种情况下,我们可以过滤输出并仅考虑重要部分。
我希望它在您下次重写旧项目时对您有用:毕竟,我们担心我们的代码失去控制!