Golden Master Pattern :一种在.NET Core中重构遗留代码的利器
在軟件開發領域中工作的任何人都將需要在舊代碼中添加功能,這些功能可能是從先前的團隊繼承而來的,您需要對其進行緊急修復。
可以在文獻中找到許多遺留代碼的定義,我更喜歡的定義是:“通過遺留代碼,我們指的是我們害怕改變的有利可圖的代碼”。
該定義包含兩個基本概念:
1.該代碼必須有利可圖。如果不是這樣,我們將無意對其進行更改。2.它必須引起對修改它的恐懼,因為我們可以引入新的bug或依賴影子的東西。
在以下情況下,更容易出錯:
?測試未涵蓋該代碼。?代碼不干凈;不遵守單一責任原則。?該代碼的設計不正確,或者隨著時間的流逝其結構變得不合理:對一段代碼進行更改可能會產生一些副作用。?您沒有時間全面了解正在修改的內容。
測試是我們作為開發人員可用的強大武器。這些測試為我們提供了結果的安全性,并且是一種快速檢測錯誤的方法。但是,我們如何測試未知的代碼?構建單元測試套件將為我們提供有關該項目的深入知識,但它將使長時間保持高成本。如果我們無法測試細節,則可以使用Characterization Test,它是描述軟件行為的測試。
在這種情況下起重要作用的模式是“?黃金大師模式”。基本思想很簡單:如果我們無法深入了解,我們需要一些有關整個執行過程的指標。我們捕獲正確執行的輸出(stdout,圖像,日志文件等),這就是我們的Golden Master,可用于預期輸出。如果當前執行的輸出匹配,我們可以確信我們的更改沒有引入新的錯誤。
為了展示Golden Master Pattern的用法,讓我們從一個示例開始(完整的代碼可以在此處[1]找到)。我們公司開發了用于命令行的游戲,包括井字游戲(該游戲的實現從此處獲取[2]),我們的老板要求我們更改游戲以提供調整游戲板尺寸的能力。讓我們看一下代碼:
namespace Tris {public class Game{//making array and //by default I am providing 0-9 where no use of zero static char[] arr = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };static int player = 1; //By default player 1 is set static int choice; //This holds the choice at which position user want to mark // The flag variable checks who has won if its value is 1 then someone has won the match if -1 then Match has Draw if 0 then match is still running static int flag = 0;public static void run(){do{Console.Clear();// whenever loop will be again start then screen will be clear Console.WriteLine("Player1:X and Player2:O");Console.WriteLine("\n");if (player % 2 == 0)//checking the chance of the player {Console.WriteLine("Player 2 Chance");}else{Console.WriteLine("Player 1 Chance");}Console.WriteLine("\n");Board();// calling the board Function choice = int.Parse(Console.ReadLine());//Taking users choice // checking that position where user want to run is marked (with X or O) or not if (arr[choice] != 'X' && arr[choice] != 'O'){if (player % 2 == 0) //if chance is of player 2 then mark O else mark X {arr[choice] = 'O';player++;}else{arr[choice] = 'X';player++;}}else //If there is any position where user wants to run and that is already marked then show message and load board again {Console.WriteLine("Sorry the row {0} is already marked with {1}", choice, arr[choice]);Console.WriteLine("\n");Console.WriteLine("Please wait 2 second board is loading again.....");Thread.Sleep(2000);}flag = CheckWin();// calling of check win } while (flag != 1 && flag != -1);// This loof will be run until all cell of the grid is not marked with X and O or some player is not winner Console.Clear();// clearing the console Board();// getting filled board again if (flag == 1)// if flag value is 1 then someone has win or means who played marked last time which has win {Console.WriteLine("Player {0} has won", (player % 2) + 1);}else// if flag value is -1 the match will be drawn and no one is the winner {Console.WriteLine("Draw");}Console.ReadLine();}// Board method which creats board private static void Board(){Console.WriteLine(" | | ");Console.WriteLine(" {0} | {1} | {2}", arr[1], arr[2], arr[3]);Console.WriteLine("_____|_____|_____ ");Console.WriteLine(" | | ");Console.WriteLine(" {0} | {1} | {2}", arr[4], arr[5], arr[6]);Console.WriteLine("_____|_____|_____ ");Console.WriteLine(" | | ");Console.WriteLine(" {0} | {1} | {2}", arr[7], arr[8], arr[9]);Console.WriteLine(" | | ");}private static int CheckWin(){#region Horzontal Winning Condtion//Winning Condition For First Row if (arr[1] == arr[2] && arr[2] == arr[3]){return 1;}//Winning Condition For Second Row else if (arr[4] == arr[5] && arr[5] == arr[6]){return 1;}//Winning Condition For Third Row else if (arr[6] == arr[7] && arr[7] == arr[8]){return 1;}#endregion#region vertical Winning Condtion//Winning Condition For First Column else if (arr[1] == arr[4] && arr[4] == arr[7]){return 1;}//Winning Condition For Second Column else if (arr[2] == arr[5] && arr[5] == arr[8]){return 1;}//Winning Condition For Third Column else if (arr[3] == arr[6] && arr[6] == arr[9]){return 1;}#endregion#region Diagonal Winning Conditionelse if (arr[1] == arr[5] && arr[5] == arr[9]){return 1;}else if (arr[3] == arr[5] && arr[5] == arr[7]){return 1;}#endregion#region Checking For Draw// If all the cells or values filled with X or O then any player has won the match else if (arr[1] != '1' && arr[2] != '2' && arr[3] != '3' && arr[4] != '4' && arr[5] != '5' && arr[6] != '6' && arr[7] != '7' && arr[8] != '8' && arr[9] != '9'){return -1;}#endregionelse{return 0;}}} }快速閱讀后,代碼看上去很混亂,職責沒有正確分開,變量名也沒有意義。
經過準確的閱讀后,我們可以找到游戲板,該游戲板存儲在“ static char [] arr”中。向陣列添加新元素沒有任何效果,因為該陣列直接在PrintBoard和CheckWin函數中訪問。現在我們知道要調整游戲板的大小,必須更改大部分代碼。
創建一個新項目并運行游戲:
class Program {static void Main(string[] args){Game.run();} }一旦我們印刷了棋盤,游戲就會要求用戶輸入。我們可以通過從文件中讀取輸入來實現自動化。
class Program {private const string InputPath = "input.txt";public static void Main(string[] args){var input = new StreamReader(new FileStream(InputPath, FileMode.Open));Console.SetIn(input);Game.run();input.Close();} }所有輸入的集合太大,無法使用蠻力測試。我們可以做的就是對輸入進行采樣。為此,我們考慮井字游戲的最終得分:
?玩家1獲勝?玩家2獲勝?繪制圖形
選擇覆蓋這三種情況的最低限度的測試集,在文本文件中編寫路徑,并在golendenMaster文件夾中收集結果:
class Program {private const string InputFolderPath = "input/";private const string OutputFolderPath = "goldenMaster/";public static void Main(string[] args){int i = 1;foreach (var filePath in Directory.GetFiles(InputFolderPath)) {var input = new StreamReader(new FileStream(filePath, FileMode.Open));var output = new StreamWriter(new FileStream(OutputFolderPath + "output" + i.ToString() + ".txt" , FileMode.CreateNew));Console.SetIn(input);Console.SetOut(output);Game.run();input.Close();output.Close();i++;}} }這三個結果文件代表了我們的Golden Master,我們可以在此基礎上進行一些特性測試:
[Test] public void WinPlayerOne() {inputPath = InputFolderPath + "input1.txt";outputPath = OutputFolderPath + "output.txt";var goldenMasterOutput = GoldenMasterOutput + "output1.txt";var input = new StreamReader(new FileStream(inputPath, FileMode.Open));var output = new StreamWriter(new FileStream(outputPath, FileMode.CreateNew));Console.SetIn(input);Console.SetOut(output);Game.run();input.Close();output.Close();Assert.True(AreFileEquals(goldenMasterOutput, outputPath)); }private bool AreFileEquals(string expectedPath, string actualPath) {byte[] bytes1 = Encoding.Convert(Encoding.ASCII, Encoding.ASCII, Encoding.ASCII.GetBytes(File.ReadAllText(expectedPath)));byte[] bytes2 = Encoding.Convert(Encoding.ASCII, Encoding.ASCII, Encoding.ASCII.GetBytes(File.ReadAllText(actualPath)));return bytes1.SequenceEqual(bytes2); }只要測試是綠色的,我們就可以重構而不必擔心破壞某些東西。一種可能的結果可能是:
public static void run() {char[] board = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };int actualPlayer = 1;while (CheckWin(board) == 0){PrintPlayerChoise(actualPlayer);PrintBoard(board);var choice = ReadPlayerChoise();if (isBoardCellAlreadyTaken(board[choice])){PrintCellIsAlreadyMarketMessage(board[choice], choice);continue;}board[choice] = GetPlayerMarker(actualPlayer);actualPlayer = UpdatePlayer(actualPlayer);}PrintResult(board, actualPlayer); }從這段代碼中可以看出Board的概念及其職責。讓我們嘗試在新的Board類中提取行為。新Board應能夠:
?印刷板?標記玩家的選擇?檢查是否有贏家
使用TDD(更多詳情,請閱讀這篇文章[3])制定一個可調整大小的Board(發現測試的完整代碼在這里[4]和階級的一個位置[5])。現在嘗試將它們插入游戲中,并檢查Golden Master是否保持綠色:
private const int Boardsize = 3;public static void run() {Board board = new Board(Boardsize);int actualPlayer = 1;while (board.CheckWin() == -1){PrintPlayerChoise(actualPlayer);Console.WriteLine(board.Print());var choice = ReadPlayerChoise();if (!board.UpdateBoard(actualPlayer, choice)){PrintCellIsAlreadyMarketMessage(board.GetCellValue(choice), choice);continue;}actualPlayer = UpdatePlayer(actualPlayer);}PrintResult(board, actualPlayer); }此時,我們可以恢復標準輸入/標準輸出并從用戶那里讀取電路板的尺寸:
class Program {public static void Main(string[] args){Console.WriteLine("Insert Diagonal dimension of Board: ");var boardSize = int.Parse(Console.ReadLine());Game.run(boardSize);} }如您所見,多虧了Golden Master Pattern,我們能夠控制遺留代碼并進行重構,而無需擔心。但是,所有閃閃發光的東西都不是金子:在“噪聲輸出”的情況下使用Golden Master可能會很困難,“噪聲輸出”對于執行無用,但會隨時間(例如時間戳記,線程名等)而變化。在這種情況下,我們可以過濾輸出并僅考慮重要部分。
我希望它在您下次重寫舊項目時對您有用:畢竟,我們擔心我們的代碼失去控制!
References
[1]?此處:?https://github.com/ntonjeta/GoldenMasterExample
[2]?此處獲取:?https://www.c-sharpcorner.com/UploadFile/75a48f/tic-tac-toe-game-in-C-Sharp/
[3]?這篇文章:?https://www.blexin.com/en-US/Article/Blog/TDD-the-whole-code-is-guilty-until-proven-innocent-38
[4]?在這里:?https://github.com/ntonjeta/GoldenMasterExample/blob/master/test/GoldenMasterExampleTest/BoardShould.cs
[5]?位置:?https://github.com/ntonjeta/GoldenMasterExample/blob/master/lib/Game/Board.cs
總結
以上是生活随笔為你收集整理的Golden Master Pattern :一种在.NET Core中重构遗留代码的利器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谷歌提议更改Istio指导委员会
- 下一篇: 【Ids4实战】最全的 v4 版本升级指