---
# System prepended metadata

title: 打造可維護軟體｜編寫可維護程式碼的10項法則 (C#版) 讀書筆記
tags: [編程哲學]

---

---
tags: 編程哲學
---
# 打造可維護軟體｜編寫可維護程式碼的10項法則 (C#版) 讀書筆記

國際標準 ISO/IEC 25010:2011（本書簡稱作 ISO 250101）將軟體品質劃分為八個特徵 :可維護性、功能可適性、效能、兼容性、可用性、可靠性、安全性及可移植性，而本書聚焦於可維護性。

>記住，你不只是在為自己寫程式，還要考慮到後面接手而又缺乏經驗的開發人員，這樣的思維有助於簡化你的編程解決方案。
## 撰寫簡短的程式碼單元（第2章） 
> 簡短的程式碼單元（亦即方法與建構式）比較容易分析、測試和重利用。

> 傻瓜也可寫出電腦能懂的程式碼，但優秀的工程師才能寫出人類能夠理解的程式碼。─ Martin Fowler

程式碼要簡短到多短呢？作者的建議是不要超過15行，以下是一段超過15行的範例。
```csharp=
public void Start() {
  if (inProgress) {
    return;
  }
  inProgress = true; // 如果玩家掛掉，告知所有觀察者：    
  if (!IsAnyPlayerAlive()) {
    foreach(LevelObserver o in observers) {
      o.LevelLost();
    }
  } // 如果豆子全部吃光，告知所有觀察者：    
  if (RemainingPellets() == 0) {
    foreach(LevelObserver o in observers) {
      o.LevelWon();
    }
  }
}
```
### 重構技巧：提取方法
就是把程式碼拆到不同的Function裡，讓所有的的Function行數都小於15行。
```csharp=
public void Start() {
  if (inProgress) {
    return;
  }
  inProgress = true;
  UpdateObservers();
}

private void UpdateObservers() {
  // 如果玩家掛掉，告知所有觀察者：
  if (!IsAnyPlayerAlive()) {
    foreach(LevelObserver o in observers) {
      o.LevelLost();
    }
  } // 如果豆子全部吃光，告知所有觀察者：    
  if (RemainingPellets() == 0) {
    foreach(LevelObserver o in observers) {
      o.LevelWon();
    }
  }
}
```
拆得更小，有沒有必要拆到這麼小呢？我覺得可以的，只要每個Function 名字都起得好，程式的可讀性會更好，看得人更容易理解，讀超來就像在讀一本故事書。也更容易測試及重覆利用。
```csharp=
public void UpdateObservers() {
  UpdateObserversPlayerDied();
  UpdateObserversPelletsEaten();
}

private void UpdateObserversPlayerDied() {
  if (!IsAnyPlayerAlive()) {
    foreach(LevelObserver o in observers) {
      o.LevelLost();
    }
  }
}

private void UpdateObserversPelletsEaten() {
  if (RemainingPellets() == 0) {
    foreach(LevelObserver o in observers) {
      o.LevelWon();
    }
  }
}
```
### 重構技巧：以方法物件取代方法
考慮以下超過15行的方法
```csharp=
public Board CreateBoard(Square[, ] grid) {
  Debug.Assert(grid != null);
  Board board = new Board(grid);
  int width = board.Width;
  int height = board.Height;
  for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
      Square square = grid[x, y];
      foreach(Direction dir in Direction.Values) {
        int dirX = (width + x + dir.DeltaX) % width;
        int dirY = (height + y + dir.DeltaY) % height;
        Square neighbour = grid[dirX, dirY];
        square.Link(neighbour, dir);
      }
    }
  }
  return board;
}
```
用**提取方法**重構後，SetLink 參數還是很長
```csharp=
private void SetLink(Square square, Direction dir, int x, int y, int width, int height, Square[, ] grid) {
  int dirX = (width + x + dir.DeltaX) % width;
  int dirY = (height + y + dir.DeltaY) % height;
  Square neighbour = grid[dirX, dirY];
  square.Link(neighbour, dir);
}

public Board CreateBoard(Square[, ] grid) {
  Debug.Assert(grid != null);
  Board board = new Board(grid);
  int width = board.Width;
  int height = board.Height;
  for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
      Square square = grid[x, y];
      foreach(Direction dir in Direction.Values) {
        SetLink(square, dir, x, y, width, height, grid);
      }
    }
  }
  return board;
}
```
把方法變成物件
```csharp=
internal class BoardCreator {
  private Square[, ] grid;
  private Board board;
  private int width;
  private int height;
    
  internal BoardCreator(Square[, ] grid) {
    Debug.Assert(grid != null);
    this.grid = grid;
    this.board = new Board(grid);
    this.width = board.Width;
    this.height = board.Height;
  }
    
  internal Board Create() {
    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        Square square = grid[x, y];
        foreach(Direction dir in Direction.Values) {
          SetLink(square, dir, x, y);
        }
      }
    }
    return this.board;
  }

  private void SetLink(Square square, Direction dir, int x, int y) {
    int dirX = (width + x + dir.DeltaX) % width;
    int dirY = (height + y + dir.DeltaY) % height;
    Square neighbour = grid[dirX, dirY];
    square.Link(neighbour, dir);
  }
}
```

```csharp=
public Board CreateBoard(Square[, ] grid) {
  return new BoardCreator(grid).Create();
}
```

>切勿為了最佳化效能而犧牲可維護性，除非有可靠的效能測試能夠證明效能問題確實存在，而且你的效能最佳化措施真的有效果。
## 撰寫簡單的程式碼單元（第3章）
程式碼單元包含越少決策點就越容易分析和測試。
就是少一點If , Case 判斷的語句。
> 每個問題都是由一些小問題組成的。— Martin Fowler

考慮以下程式, case 好長, 少打一個break 就出錯了,
```csharp=
public IList < Color > GetFlagColors(Nationality nationality) {
  List < Color > result;
  switch (nationality) {
  case Nationality.DUTCH:
    result = new List < Color > {
      Color.Red,
      Color.White,
      Color.Blue
    };
    break;
  case Nationality.GERMAN:
    result = new List < Color > {
      Color.Black,
      Color.Red,
      Color.Yellow
    };
    break;
  case Nationality.BELGIAN:
    result = new List < Color > {
      Color.Black,
      Color.Yellow,
      Color.Red
    };
    break;
  case Nationality.FRENCH:
    result = new List < Color > {
      Color.Blue,
      Color.White,
      Color.Red
    };
    break;
  case Nationality.ITALIAN:
    result = new List < Color > {
      Color.Green,
      Color.White,
      Color.Red
    };
    break;
  case Nationality.UNCLASSIFIED:
  default:
    result = new List < Color > {
      Color.Gray
    };
    break;
  }
  return result;
}
```
用 **Dictionary** 取締 **Case**
```csharp=
private static Dictionary < Nationality, IList < Color >> FLAGS = new Dictionary < Nationality, IList < Color >> ();
static FlagFactoryWithMap() {
  FLAGS[Nationality.DUTCH] = new List < Color > {
    Color.Red,
    Color.White,
    Color.Blue
  };
  FLAGS[Nationality.GERMAN] = new List < Color > {
    Color.Black,
    Color.Red,
    Color.Yellow
  };
  FLAGS[Nationality.BELGIAN] = new List < Color > {
    Color.Black,
    Color.Yellow,
    Color.Red
  };
  FLAGS[Nationality.FRENCH] = new List < Color > {
    Color.Blue,
    Color.White,
    Color.Red
  };
  FLAGS[Nationality.ITALIAN] = new List < Color > {
    Color.Green,
    Color.White,
    Color.Red
  };
}

public IList < Color > GetFlagColors(Nationality nationality) {
  IList < Color > colors = FLAGS[nationality];
  return colors ?? new List < Color > {
    Color.Gray
  };
}
```
更進一步, 把每個旗都物件化
```csharp=
public interface IFlag {
  IList < Color > Colors {
    get;
  }
}

public class DutchFlag: IFlag {
  public IList < Color > Colors {
    get {
      return new List < Color > {
        Color.Red,
        Color.White,
        Color.Blue
      };
    }
  }
}

public class ItalianFlag: IFlag {
  public IList < Color > Colors {
    get {
      return new List < Color > {
        Color.Green,
        Color.White,
        Color.Red
      };
    }
  }
}

private static readonly Dictionary < Nationality, IFlag > FLAGS = new Dictionary < Nationality, IFlag > ();
static FlagFactory() {
  FLAGS[Nationality.DUTCH] = new DutchFlag();
  FLAGS[Nationality.GERMAN] = new GermanFlag();
  FLAGS[Nationality.BELGIAN] = new BelgianFlag();
  FLAGS[Nationality.FRENCH] = new FrenchFlag();
  FLAGS[Nationality.ITALIAN] = new ItalianFlag();
}

public IList < Color > GetFlagColors(Nationality nationality) {
  IFlag flag = FLAGS[nationality];
  flag = flag ?? new DefaultFlag();
  return flag.Colors;
}
```
考慮以下更複雜的程式碼, 好多 if else 分支
```csharp=
public static int CalculateDepth(BinaryTreeNode < int > t, int n) {
  int depth = 0;
  if (t.Value == n) {
    return depth;
  } else {
    if (n < t.Value) {
      BinaryTreeNode < int > left = t.Left;
      if (left == null) {
        throw new TreeException("Value not found in tree!");
      } else {
        return 1 + CalculateDepth(left, n);
      }
    } else {
      BinaryTreeNode < int > right = t.Right;
      if (right == null) {
        throw new TreeException("Value not found in tree!");
      } else {
        return 1 + CalculateDepth(right, n);
      }
    }
  }
}
```
重構如下, 早一點 return , 減少 else, 
```csharp=
public static int CalculateDepth(BinaryTreeNode < int > t, int n) {
  int depth = 0;
  if (t.Value == n) {
    return depth;
  }
  if ((n < t.Value) && (t.Left != null)) {
    return 1 + CalculateDepth(t.Left, n);
  }
  if ((n > t.Value) && (t.Right != null)) {
    return 1 + CalculateDepth(t.Right, n);
  }
  throw new TreeException("Value not found in tree!");
}
```
再進一步, 更容易測試
```csharp=
public static int CalculateDepth(BinaryTreeNode < int > t, int n) {
  int depth = 0;
  if (t.Value == n) {
    return depth;
  } else {
    return TraverseByValue(t, n);
  }
}
private static int TraverseByValue(BinaryTreeNode < int > t, int n) {
  BinaryTreeNode < int > childNode = GetChildNode(t, n);
  if (childNode == null) {
    throw new TreeException("Value not found in tree!");
  } else {
    return 1 + CalculateDepth(childNode, n);
  }
}
private static BinaryTreeNode < int > GetChildNode(BinaryTreeNode < int > t, int n) {
  if (n < t.Value) {
    return t.Left;
  } else {
    return t.Right;
  }
}
```
## 不撰寫重複的程式碼（第4章） 
任何時候都應避免原始碼重複，因為一旦需要修改，就得處理每一份副本。重複程式碼也是遞迴性臭蟲（regression bug）的滋生根源。
## 保持單元介面單純（第5章） 
程式碼單元（方法和建構式）包含越少參數，就越容易測試及重利用。
>四處分散的相關資料應該被封裝成一個物件。— Martin Flowler

考慮以下6個參數的方法

```csharp=
/// <summary>
/// 在指定的長方形上繪製方塊
///
/// <param name="square"> 要繪製的方塊 </param>
/// <param name="g"> 要進行繪製的圖形上下文 </param>
/// <param name="x"> 開始繪製的 x 位置 </param>
/// <param name="y"> 開始繪製的 y 位置 </param>
/// <param name="w"> 方塊的寬度（以像素為單位）</param>
/// <param name="h"> 方塊的高度（以像素為單位）</param>
private void Render(Square square, Graphics g, int x, int y, int w, int h) {
  square.Sprite.Draw(g, x, y, w, h);
  foreach(Unit unit in square.Occupants) {
    unit.Sprite.Draw(g, x, y, w, h);
  }
}
```
新增**Rectangle**類別, 把參數減到3個
```csharp=
public class Rectangle {
  public Point Position {
    get;
    set;
  }
  public int Width {
    get;
    set;
  }
  public int Height {
    get;
    set;
  }
  public Rectangle(Point position, int width, int height) {
    this.Position = position;
    this.Width = width;
    this.Height = height;
  }
}
/// <summary>
/// 在指定的長方形上繪製方塊
///
/// <param name="square"> 要繪製的方塊 </param>
/// <param name="g"> 要進行繪製的圖形上下文 </param>
/// <param name="r"> 方塊的位置與尺寸 </param>
private void Render(Square square, Graphics g, Rectangle r) {
  Point position = r.Position;
  square.Sprite.Draw(g, position.X, position.Y, r.Width, r.Height);
  foreach(Unit unit in square.Occupants) {
    unit.Sprite.Draw(g, position.X, position.Y, r.Width, r.Height);
  }
}
```
整個方法更簡潔
```csharp=
private void Render(Square square, Graphics g, Rectangle r) {
  square.Sprite.Draw(g, r);
  foreach(Unit unit in square.Occupants) {
    unit.Sprite.Draw(g, r);
  }
}
```
考慮另一個方法, 參數更多
```csharp=
public void DoBuildAndSendMail(MailMan m, string firstName, string lastName, string division, string subject, MailFont font, string message1, string message2, string message3) {
  // 格式化電子郵件地址    
  string mId = $ "{firstName[0]}.{lastName.Substring(0, 7)}" + $ "@{division.Substring(0, 5)}.compa.ny";
  // 根據指定的內容型態與原始訊息進行格式化    
  MailMessage mMessage = FormatMessage(font, message1 + message2 + message3);
  // 發送訊息
  m.Send(mId, subject, mMessage);
}
```
相關的參數都整到一個類別去
```csharp=
public void DoBuildAndSendMail(MailMan m, MailAddress mAddress, MailBody mBody) {
  // 建構電子郵件    
  Mail mail = new Mail(mAddress, mBody);
  // 發送電子郵件    
  m.SendMail(mail);
}
public class Mail {
  public MailAddress Address {
    get;
    set;
  }
  public MailBody Body {
    get;
    set;
  }
  public Mail(MailAddress mAddress, MailBody mBody) {
    this.Address = mAddress;
    this.Body = mBody;
  }
}
public class MailBody {
  public string Subject {
    get;
    set;
  }
  public MailMessage Message {
    get;
    set;
  }
  public MailBody(string subject, MailMessage message) {
    this.Subject = subject;
    this.Message = message;
  }
}
public class MailAddress {
  public string MsgId {
    get;
    private set;
  }
  public MailAddress(string firstName, string lastName, string division) {
    this.MsgId = $ "{firstName[0]}.{lastName.Substring(0, 7)}" + $ "@{division.Substring(0, 5)}.compa.ny";
  }
}
```
考慮另一個更多參數的方法
```csharp=
public static void DrawBarChart(Graphics g, CategoryItemRendererState state, Rectangle graphArea, CategoryPlot plot, CategoryAxis domainAxis, ValueAxis rangeAxis, CategoryDataset dataset) {
  // ..
}
```
利用多載添加預設值, 現在C# 也可以直接在參數聲明上直接給定預設值, 呼叫的時候可以省略輸入有預設值的參數, 參考[具名和選擇性引數](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments)
```csharp=
public static void DrawBarChart(Graphics g, CategoryDataset dataset) {
  Charts.DrawBarChart(g, CategoryItemRendererState.DEFAULT, new Rectangle(new Point(0, 0), 100, 100), CategoryPlot.DEFAULT, CategoryAxis.DEFAULT, ValueAxis.DEFAULT, dataset);
}
```
更進一步, 使用*流暢介面*, 方法return 當前物件
```csharp=
public class BarChart {
  private CategoryItemRendererState state = CategoryItemRendererState.DEFAULT;
  private Rectangle graphArea = new Rectangle(new Point(0, 0), 100, 100);
  private CategoryPlot plot = CategoryPlot.DEFAULT;
  private CategoryAxis domainAxis = CategoryAxis.DEFAULT;
  private ValueAxis rangeAxis = ValueAxis.DEFAULT;
  private CategoryDataset dataset = CategoryDataset.DEFAULT;
  public BarChart Draw(Graphics g) {
    // ..        
    return this;
  }
  public ValueAxis GetRangeAxis()

  {
    return rangeAxis;
  }
  public BarChart SetRangeAxis(ValueAxis rangeAxis) {
    this.rangeAxis = rangeAxis;
    return this;
  }
  // 更多 getter 與 setter
}
```
流暢地呼叫
```csharp=
private void ShowMyBarChart() {
  Graphics g = this.CreateGraphics();
  BarChart b = new BarChart().SetRangeAxis(myValueAxis).SetDataset(myDataset).Draw(g);
}
```
## 不同模組之間的關注點分離（第6章） 
鬆散耦合的模組（類別）比較容易修改，並且促成更模組化的系統。
>在既複雜又緊密耦合的系統中，意外無可避免。─ Charles Perrow 的正常意外理論（Normal Accidents）

一開始的時候並沒有太多功能
```csharp=
public class UserService {
  public User LoadUser(string userId) {
    // ...        
  }
  public bool DoesUserExist(string userId) {
    // ...        
  }
  public User ChangeUserInfo(UserInfo userInfo) {
    // ...    
  }
}
```

```csharp=
public class UserController: System.Web.Http.ApiController {
  private readonly UserService userService = new UserService();
  // ...    
  public System.Web.Http.IHttpActionResult GetUserById(string id) {
    User user = userService.LoadUser(id);
    if (user == null) {
      return NotFound();
    }
    return Ok(user);
  }
}
```
日子久了, 添加了一些功能
```csharp=
public class UserService {
  public User LoadUser(string userId) {
    // ...
  }
  public bool DoesUserExist(string userId) {
    // ...       
  }
  public User ChangeUserInfo(UserInfo userInfo) { // ...  
  }
  public List < NotificationType > GetNotificationTypes(User user) {
    // ...
  }
  public void RegisterForNotifications(User user, NotificationType type) {
    // ...
  }
  public void UnregisterForNotifications(User user, NotificationType type) {
    // ...        
  }
}
```
```csharp=
public class NotificationController: System.Web.Http.ApiController {
  private readonly UserService userService = new UserService();
  // ...
  public System.Web.Http.IHttpActionResult Register(string id, string notificationType) {
    User user = userService.LoadUser(id);
    userService.RegisterForNotifications(user, NotificationType.FromString(notificationType));
    return Ok();
  }

  [System.Web.Http.HttpPost]
  [System.Web.Http.ActionName("unregister")]
  public System.Web.Http.IHttpActionResult Unregister(string id, string notificationType) {
    User user = userService.LoadUser(id);
    userService.UnregisterForNotifications(user, NotificationType.FromString(notificationType));
    return Ok();
  }
}
```
又增加了一些功能
```csharp=
public class UserService {
  public User LoadUser(string userId) {
    // ...    
  }
  public bool DoesUserExist(string userId) {
    // ...      
  }
  public User ChangeUserInfo(UserInfo userInfo) { // ...       
  }
  public List < NotificationType > GetNotificationTypes(User user) {
    // ...
  }
  public void RegisterForNotifications(User user, NotificationType type) {
    // ...      
  }
  public void UnregisterForNotifications(User user, NotificationType type) {
    // ...       
  }
  public List < User > SearchUsers(UserInfo userInfo) { // ...  
  }
  public void BlockUser(User user) {
    // ...      
  }
  public List < User > GetAllBlockedUsers() {
    // ...       
  }
}
```
應該把不相關的功能分開
```csharp=
public class UserNotificationService {
  public IList < NotificationType > GetNotificationTypes(User user) {
    // ...   
  }
  public void Register(User user, NotificationType type) {
    // ...  
  }
  public void Unregister(User user, NotificationType type) { // ...   
  }
}

public class UserBlockService {
  public void BlockUser(User user) {
    // ... 
  }
  public IList < User > GetAllBlockedUsers() {
    // ...  
  }
}

public class UserService {
  public User LoadUser(string userId) {
    // ... 
  }
  public bool DoesUserExist(string userId) {
    // ...  
  }
  public User ChangeUserInfo(UserInfo userInfo) {
    // ...   
  }
  public IList < User > SearchUsers(UserInfo userInfo) {
    // ...  
  }
}
```

```csharp=
public class DigitalCamera {
  public Image TakeSnapshot() {
    // ...    
  }
  public void FlashLightOn() {
    // ...   
  }
  public void FlashLightOff() {
    // ...  
  }
}
```
### 將特定實作隱藏在介面背後
```csharp=
public class SmartphoneApp {
  private static DigitalCamera camera = new DigitalCamera();
  public static void Main(string[] args) {
    // ...     
    Image image = camera.TakeSnapshot();
    // ... 
  }
}
```
```csharp=
public class DigitalCamera {
  public Image TakeSnapshot() {
    // ...
  }
  public void FlashLightOn() {
    // ...
  }
  public void FlaslLightOff() {
    // ...
  }
  public Image TakePanoramaSnapshot() {
    // ...
  }
  public Video Record() {
    // ... 
  }
  public void SetTimer(int seconds) {
    // ...
  }
  public void ZoomIn() {
    // ...
  }
  public void ZoomOut() {
    // ...
  }
}
```
降低耦合度, 使用了ISimpleDigitalCamera 介面後(line 10), 保證了只用到基本的功能(ISimpleDigitalCamera的 function), 而且就算DigitalCamera 被修改 或者SDK.GetCamera() 返回的是其他Object, SmartphoneApp 也不要修改, 只要返回的object實現了ISimpleDigitalCamera就可以.
```csharp=
public interface ISimpleDigitalCamera {
  Image TakeSnapshot();
  void FlashLightOn();
  void FlashLightOff();
}
public class DigitalCamera: ISimpleDigitalCamera {
  // ...
}
public class SmartphoneApp {
  private static ISimpleDigitalCamera camera = SDK.GetCamera();
  public static void Main(string[] args) {
    // ...
    Image image = camera.TakeSnapshot();
    // ...
  }
}
```
## 讓架構元件保持鬆散耦合（第7章） 
系統的頂層元件之間越是鬆散耦合，就越容易修改，也會導致更加模組化的系統。
```csharp=
public interface ICloudServerFactory {
  ICloudServer LaunchComputeServer();
  ICloudServer LaunchDatabaseServer();
  ICloudStorage CreateCloudStorage(long sizeGb);
}
```
```csharp=
public class AWSCloudServerFactory: ICloudServerFactory {
  public ICloudServer LaunchComputeServer() {
    return new AWSComputeServer();
  }
  public ICloudServer LaunchDatabaseServer() {
    return new AWSDatabaseServer();
  }
  public ICloudStorage CreateCloudStorage(long sizeGb) {
    return new AWSCloudStorage(sizeGb);
  }
}
public class AzureCloudServerFactory: ICloudServerFactory {
  public ICloudServer LaunchComputeServer() {
    return new AzureComputeServer();
  }
  public ICloudServer LaunchDatabaseServer() {
    return new AzureDatabaseServer();
  }
  public ICloudStorage CreateCloudStorage(long sizeGb) {
    return new AzureCloudStorage(sizeGb);
  }
}

public class ApplicationLauncher {
  public static void Main(string[] args) {
    ICloudServerFactory factory;
    if (args[1].Equals("-azure")) {
      factory = new AzureCloudServerFactory();
    } else {
      factory = new AWSCloudServerFactory();
    }
    ICloudServer computeServer = factory.LaunchComputeServer();
    ICloudServer databaseServer = factory.LaunchDatabaseServer();
  }
}
```
```mermaid
flowchart TD
    subgraph 設計從分層到分層的單向依賴
    使用者介面-->服務層-->業務邏輯層-->資料抽象層-->資料庫分層
    end
```
```mermaid
flowchart TD
    subgraph 隨著時間,違反依賴原則,直接調用,導致纏結與混亂
    使用者介面-->服務層-->業務邏輯層-->資料抽象層-->資料庫分層
    使用者介面-->業務邏輯層
    服務層-->資料抽象層
    使用者介面-->資料抽象層
    end
```
## 保持架構元件平衡（第8章） 
平衡度良好的架構擁有不多不少的元件、尺寸一致的程式碼單元，以及最大程度的模組化，並且透過關注點分離，讓修改工作變得很容易。
> 建構封裝邊界是設計軟體架構的重要技巧。— George H. Fairbanks，《Just Enough Architecture》
## 保持小規模的程式碼基礎（第9章） 
大型系統很難維護，因為更多程式碼需要分析、修改及測試，同時，在大型系統中，每一行程式碼的維護效率也比小型系統來得低。
## 自動化開發與測試（第10章）
自動化測試（亦即，無需人工干預即可執行測試）能夠針對修改是否有效，獲得近乎即時的回饋。手動測試無法形成規模。撰寫乾淨的程式碼（第11章）程式碼基礎存在越多 TODO（待辦事項）、無用程式碼等不相關的遺留物，新加入的團隊成員就越難保持生產力，進而影響維護工作的執行效率。

不同類型的測試需要不同的自動化框架。對單元測試來說，有一些著名的 C# 框架，像是 NUnit（http://nunit.org）；對自動化的端到端測試來說，你需要某種模擬使用者輸入並且捕捉輸出的框架。例如，在 Web 開發中相當有名的 Selenium 框架（http://www.seleniumhq.org）。對整合測試來說，這完全依賴你所在的環境及測試的品質特徵。SoapUI（http://www.soapui.org）是聚焦於 web service 和訊息傳遞中間件的整合測試框架，而 Apache JMeter（http://jmeter.apache.org）框架用來測試 C# 應用程式在高負載下的效能。

一般來說，使用 Moq（https://github.com/Moq/moq4）之類的mocking框架最有效率。mocking框架利用 .Net 執行時期環境的特性，自動從普通介面或類別建立 mock 物件，並且提供各種辦法測試 mock 物件的某些方法是否被調用，同時記錄調用引數。有些 mocking 框架還能夠為 mock 物件指定預先定義的行為，讓它們同時具有 stub 和 mock 的特徵。