C# 初學者的 3 種重構模式

(Martin Fowler's Example)

1. 積極使用 Guard Clause(保護語句)

"如果條件不滿足,立即返回。將核心邏輯放在最少縮進的地方。"

概念定義

Guard Clause(保護語句) 是一種在函數開頭檢查特定條件是否滿足,如果不滿足則立即退出(return) 的方法。
它的目的是減少不必要的 if 嵌套,使代碼更加線性和平坦(flat)

Before(不好的示例)

if (user != null)
{if (user.IsActive){Process(user);}
}
  • 代碼嵌套過多,核心邏輯 Process() 被隱藏在最內層。

  • 隨著條件的增加,代碼會形成“金字塔代碼(Pyramid of Doom)”,變得越來越復雜。

  • 當測試條件增多時,代碼覆蓋率和調試的可讀性都會變差。

After(好的示例)

if (user == null) { return; }
if (!user.IsActive) { return; }Process(user);
  • 在條件不滿足的情況下,通過提前返回 快速整理流程。

  • 核心邏輯 Process() 位于最少縮進的中央部分 ,可讀性更高。

  • 在測試/重構時,可以輕松追蹤條件與結果

反對意見:“否定條件違背直覺”

一些開發者可能會提出以下觀點:

“比起 if (!condition) return;if (condition) { ... } 更加直觀。
否定條件會讓代碼難以理解。”

回應反對意見:
  • 關鍵在于條件的正/負,而不是‘意圖’和‘重要性’的排序。

  • 如果“在這種情況下不應該執行操作”是明確的,那么否定條件更為清晰

指南:
  • 不重要的條件 = 否定形式 + 提前返回

  • 重要的條件 = 肯定形式 + 執行核心邏輯

補充提示:Guard 子句不僅可以使用 return,還可以使用 throwcontinue

if (value == null) { throw new ArgumentNullException(nameof(value)); }foreach (var item in collection)
{if (item.IsEmpty) { continue; }Process(item);
}

什么時候應該積極使用 Guard 子句?

情況

描述

包含大量驗證的函數

通過提前返回來清理條件

包含許多狀態分支的循環

使用continue作為 Guard 子句

存在未檢查的異常風險

清理nullrangepermission等潛在問題

方法開始變長時

使用 Guard 子句整理條件過濾,簡化代碼

實戰技巧

  • Guard Clause 通過去除不必要的嵌套 + 提前退出 ,使代碼變得更加平坦(flat)

  • 這是一種設計策略,旨在將核心邏輯放在縮進最少、最顯眼的位置。

  • 條件越多 失敗案例越明確 測試越復雜 ,Guard Clause 的優勢就越明顯。

Dictionary<TKey, TValue> 在內部使用哈希表(Hash Table) 實現。

因此,鍵查找的平均時間復雜度為 O(1) ,這在很多情況下比 switch-case 更快

2. 戰略性地將 Switch 轉換為 Dictionary 映射

"條件分支越復雜,代碼的責任應被分解得越清晰。"

概念定義

在 C# 中,switch-case 語句對簡單的分支處理非常有用,但當分支數量增加時,在維護性和擴展性方面會暴露出局限性
在這種情況下,使用 Dictionary<Enum, Action>Dictionary<Enum, Func<T>> 構建顯式映射 ,可以顯著提升可讀性和功能擴展性

Before(不好的示例)

switch (state)
{case UserState.Idle:HandleIdle();break;case UserState.Running:HandleRunning();break;case UserState.Dead:HandleDead();break;default:throw new InvalidOperationException();
}
  • 隨著分支增多,代碼變得冗長。

  • 如果在 switch 內部處理過多邏輯,容易違反單一職責原則(SRP)

  • 當新增狀態時,需要找到對應的分支、添加邏輯并測試,修改分散且繁瑣。

After(好的示例)

private static readonly Dictionary<UserState, Action> stateHandlers = new()
{{ UserState.Idle, HandleIdle },{ UserState.Running, HandleRunning },{ UserState.Dead, HandleDead }
};public void Handle(UserState state)
{if (stateHandlers.TryGetValue(state, out Action action)){action.Invoke();}else{throw new InvalidOperationException($"No handler for {state}");}
}
  • 狀態與操作的關系通過映射明確管理

  • 新增狀態時只需在字典中注冊即可完成。

  • 測試時可以獨立驗證每個處理器。

使用場景示例

UI 狀態機(State Machine)
Dictionary<GameState, Action> renderState = new()
{{ GameState.MainMenu, DrawMainMenu },{ GameState.InGame, DrawGame },{ GameState.Paused, DrawPauseScreen },
};

什么時候適合使用?

情況

描述

基于 Enum 的分支較多時

switch-case變得過于冗長時

命令/輸入處理分支

鍵/按鈕輸入 → 動作映射

狀態機

狀態 → 行為對應結構

需要分離出可測試的邏輯時

switch單元測試困難 → 將處理器拆分為獨立函數后易于測試

缺點及注意事項

缺點

應對措施

未注冊的鍵沒有對應處理

TryGetValue失敗時明確拋出異常或執行空操作(No-op)

不保證順序

使用OrderedDictionary或重構 Enum 以體現順序

復雜條件分支難以處理

僅適用于簡單條件邏輯,復雜邏輯仍需使用if/switch

實戰技巧

  • 命令模式(Command Pattern)的簡易實現
    可以像 Dictionary<string, ICommand> 一樣,將命令和執行對象進行映射。

  • 向函數式編程(FP)靠攏的信號
    基于字典的映射實際上是將代碼轉換為數據驅動的表格式結構 ,這與 FP(函數式風格命令調度)的理念更為接近。

(MSDN Magazine Issues Volume 32 Number 3)

(Read only, frozen, and immutable collections - Developer Support)

3. 不可變數據(Immutable Data)習慣

“調試地獄從何開始?——正是從那些意外的值變更開始。”


概念定義

不可變(Immutable)數據 是指一旦定義后,其值不會改變的狀態
在 C# 中,可以通過 readonlyconstrecord 等方式實現有意圖的不可變設計

Before(不好的示例)

public class Player
{public int health;public void TakeDamage(int amount){health -= amount;}
}
  • 在這種結構中,很難追蹤 health 是在哪里被修改的。
  • 特別是在多線程或事件驅動系統中,副作用 的累積會使調試難度急劇上升。

After(好的示例)

public class Player
{private readonly int maxHealth = 100;private int currentHealth;public int GetHealth() => currentHealth;public void SetHealth(int value){currentHealth = Math.Clamp(value, 0, maxHealth);}
}
  • maxHealth 是一個永遠不會改變的常量 → 使用 readonly 聲明。
  • 狀態修改僅通過 SetHealth() 方法完成 → 訪問控制與不可變性分離

為什么在游戲開發中很重要?

如果狀態(State)變更以不透明的方式擴散
  • UI 更新延遲
  • Bug 不規則發生
  • 多人環境中同步問題

解決方法:將狀態本身建模為不可變對象 進行管理。

public readonly struct PlayerState
{public readonly int health;public readonly bool isDead;public PlayerState(int health, bool isDead){this.health = health;this.isDead = isDead;}
}

狀態不是用來修改的,而是‘重新創建’的。→ 函數式編程模式

Unity 開發者可以這樣使用

  • 使用 ScriptableObject 存儲配置數據時 → 只讀結構化
  • 避免使用 public int value; 形式,改為沒有 setter 的 SerializedField
  • 推薦將狀態對象存儲在不可變的 struct + Copy-on-Write 模式中,而不是放在可變的 MonoBehaviour 中。
[SerializeField] private int initialHealth = 100;public int InitialHealth => initialHealth; // 只允許讀取

從 C# 9 開始引入了 record

  • record 默認是不可變對象
  • 使用 with 表達式進行修改時會創建新對象(immutable-safe)
var newStatus = status with { Health = status.Health - 10 };

高級技巧:并行處理與架構設計建議

readonly + volatile 組合
private volatile bool isGameOver;
private readonly object lock = new();public void SetGameOver()
{lock (lock){isGameOver = true;}
}
  • 將看似不可變的值在多線程環境中保持一致性保護
  • 這種組合也常用于雙重檢查模式。

注意:所有內容都必須不可變嗎?

  • 如果在游戲循環中性能至關重要的結構 ,需要考慮 struct 不可變對象的創建成本。
  • 對于需要頻繁狀態變更的對象,可以采用“內部不可變性(internal immutability)”作為折衷方案。

什么時候適合使用不可變模式?

場景

描述

需要跟蹤狀態時

難以追蹤狀態變更的結構會導致調試噩夢

線程間共享數據

不可變性設計可以在無鎖的情況下保證穩定性

可測試的狀態建模

基于對象復制的測試和時間點比較更加容易

UI 狀態更新

在 ViewModel 中便于變更跟蹤和綁定

實戰技巧

  • 不可變數據是降低調試成本的最佳結構
  • 狀態不應修改,而應替換 :覆蓋對象的方式對追蹤和恢復更有利
  • 在 C# 中,可以通過 readonlyrecordScriptableObject + Getter 設計來實現。

結論:

我們了解了在 C# 中經常使用的基礎重構方法。
實際上,詳細撰寫這種入門級別的文章對我也有幫助,因此我重新整理了一遍。
除此之外,還有很多其他模式,但我選出了三個我認為重要的基礎概念。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/84371.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/84371.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/84371.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于51單片機和8X8點陣屏、獨立按鍵的滑動躲閃類小游戲

目錄 系列文章目錄前言一、效果展示二、原理分析三、各模塊代碼1、8X8點陣屏2、獨立按鍵3、定時器04、定時器1 四、主函數總結 系列文章目錄 前言 用的是普中A2開發板。 【單片機】STC89C52RC 【頻率】12T11.0592MHz 【外設】8X8點陣屏、獨立按鍵 效果查看/操作演示&#xff…

Java面向對象 一

系列文章目錄 Java面向對象 二-CSDN博客 Java面向對象 三-CSDN博客 目錄 系列文章目錄 前言 一、初步認識面向對象 1.類和對象的簡單理解 2.類的構成 二、類的實例化 1.對象的創建 2.對象的初始化 三、this引用的作用 四、構造方法 1.構造方法的提供 2.對象的構…

深度學習Y8周:yolov8.yaml文件解讀

&#x1f368; 本文為&#x1f517;365天深度學習訓練營中的學習記錄博客&#x1f356; 原作者&#xff1a;K同學啊 本周任務&#xff1a;根據yolov8n、yolov8s模型的結構輸出&#xff0c;手寫出yolov8l的模型輸出、 文件位置&#xff1a;./ultralytics/cfg/models/v8/yolov8.…

【RocketMQ 生產者和消費者】- 生產者啟動源碼 - MQClientInstance 定時任務(4)

文章目錄 1. 前言2. startScheduledTask 啟動定時任務2.1 fetchNameServerAddr 拉取名稱服務地址2.2 updateTopicRouteInfoFromNameServer 更新 topic 路由信息2.2.1 topic 路由信息2.2.2 updateTopicRouteInfoFromNameServer 獲取 topic2.2.3 updateTopicRouteInfoFromNameSer…

解決Docker容器內yum: not found、apt: not found、apk: command not found等命令找不到問題

Linux有很多發行版&#xff0c;各發行版的包管理工具不一定相同。 Alpine的包管理工具是 apk Debian/Ubuntu的包管理工具是 apt Centos/RHEL的包管理工具是 yum 在安裝軟件之前&#xff0c;需要先查看Docker容器內的Linux是什么發行版&#xff0c;可使用 cat /etc/os-rele…

每日c/c++題 備戰藍橋杯(修理牛棚 Barn Repair)

修理牛棚 Barn Repair 題解 問題背景與挑戰 在一個暴風雨交加的夜晚&#xff0c;Farmer John 的牛棚遭受了嚴重的破壞。屋頂被掀飛&#xff0c;大門也不翼而飛。幸運的是&#xff0c;許多牛正在度假&#xff0c;牛棚并未住滿。然而&#xff0c;為了保護那些還在牛棚里的牛&am…

鴻蒙版Flutter庫torch_light手電筒功能深度適配

鴻蒙版Flutter庫torch_light手電筒功能深度適配&#xff1a;跨平臺開發者的光明之路 本項目作者&#xff1a;kirk/堅果 適配倉庫地址 作者倉庫&#xff1a;https://github.com/svprdga/torch_light# 在數字化浪潮的推動下&#xff0c;跨平臺開發框架如 Flutter 憑借其高效、…

【信息系統項目管理師】一文掌握高項常考題型-項目進度類計算

更多內容請見: 備考信息系統項目管理師-專欄介紹和目錄 文章目錄 一、進度類計算的基本概念1.1 前導圖法1.2 箭線圖法1.3 時標網絡圖1.4 確定依賴關系1.5 提前量與滯后量1.6 關鍵路徑法1.7 總浮動時間1.8 自由浮動時間1.9 關鍵鏈法1.10 資源優化技術1.11 進度壓縮二、基本公式…

深入了解linux系統—— 操作系統的路徑緩沖與鏈接機制

前言 在之前學習當中&#xff0c;我們了解了被打開的文件是如何管理的&#xff1b;磁盤&#xff0c;以及ext2文件系統是如何存儲文件的。 那我們要打開一個文件&#xff0c;首先要先找到這個文件&#xff0c;操作系統又是如何去查找的呢&#xff1f; 理解操作系統搜索文件 …

Docker Hub倉庫介紹

Docker Hub倉庫全解析&#xff1a;從公共市場到私有化部署指南 一、Docker Hub公共鏡像市場 1.1 核心功能解析 全球最大容器鏡像庫&#xff1a;累計托管超500萬鏡像核心服務矩陣&#xff1a; #mermaid-svg-CAMkhmtSWKEUw7z0 {font-family:"trebuchet ms",verdana,a…

redis使用RDB文件恢復數據

設置存盤間隔為120秒且10個key改變數據自動存盤使用RDB文件恢復數據 IP地址主機名192.168.10.170redis170 [rootredis170 ~]# yum install -y redis [rootredis170 ~]# systemctl start redis步驟一&#xff1a;設置存盤間隔為120秒且10個key改變自動存盤 [rootredis170 ~]#…

SpringBoot多環境配置文件切換

resources下application.yml、application-dev.yml、application-prod.yml多個配置文件。 spring:profiles:active: devspring:profiles:active: prod一般都是通過修改spring.profiles.active值來修改加載不同環境的配置信息&#xff0c;可以把切換的dev/prod放到pom.xml文件來…

Java 并發編程高級技巧:CyclicBarrier、CountDownLatch 和 Semaphore 的高級應用

Java 并發編程高級技巧&#xff1a;CyclicBarrier、CountDownLatch 和 Semaphore 的高級應用 一、引言 在 Java 并發編程中&#xff0c;CyclicBarrier、CountDownLatch 和 Semaphore 是三個常用且強大的并發工具類。它們在多線程場景下能夠幫助我們實現復雜的線程協調與資源控…

【Java多線程】多線程狀態下如何安全使用ArrayList以及哈希表

&#x1f50d; 開發者資源導航 &#x1f50d;&#x1f3f7;? 博客主頁&#xff1a; 個人主頁&#x1f4da; 專欄訂閱&#xff1a; JavaEE全棧專欄 多線程安全使用ArrayList 手動加鎖 日常中最常用的方法&#xff0c;使用synchronized進行加鎖&#xff0c;把代碼打包成一份&a…

InnoDB引擎底層解析(二)之InnoDB的Buffer Pool(三)

Buffer Pool 實例 我們上邊說過&#xff0c;Buffer Pool 本質是 InnoDB 向操作系統申請的一塊連續的內存空間&#xff0c;在多線程環境下&#xff0c;訪問 Buffer Pool 中的各種鏈表都需要加鎖處理&#xff0c;在Buffer Pool特別大而且多線程并發訪問特別高的情況下&#xff0…

Netty學習專欄(三):Netty重要組件詳解(Future、ByteBuf、Bootstrap)

文章目錄 前言一、Future & Promise&#xff1a;異步編程的救星1.1 傳統NIO的問題1.2 Netty的解決方案1.3 代碼示例&#xff1a;鏈式異步操作 二、ByteBuf&#xff1a;重新定義數據緩沖區2.1 傳統NIO ByteBuffer的缺陷2.2 Netty ByteBuf的解決方案2.3 代碼示例&#xff1a;…

Vue3逐步拋棄虛擬Dom,React如何抉擇

虛擬DOM&#xff1a;前端界的替死鬼 這玩意兒就是個前端開發的充氣娃娃&#xff01; 你以為它很牛逼&#xff1f;無非是給真DOM當替死鬼&#xff01; 每次數據變&#xff0c;虛擬DOM先擱內存里自嗨一頓&#xff0c;diff算法跟便秘似的算半天&#xff0c;最后才敢碰真DOM。 說白…

分布式鎖總結

文章目錄 分布式鎖什么是分布式鎖&#xff1f;分布式鎖的實現方式基于數據庫(mysql)實現基于緩存(redis)多實例并發訪問問題演示項目代碼(使用redis)配置nginx.confjmeter壓測復現問題并發是1&#xff0c;即不產生并發問題并發30測試,產生并發問題(雖然單實例是synchronized&am…

解決自簽名證書HTTPS告警:強制使用SHA-256算法生成證書

解決自簽名證書HTTPS告警&#xff1a;強制使用SHA-256算法生成證書 一、問題場景 在使用OpenSSL生成和配置自簽名證書時&#xff0c;常遇到以下現象&#xff1a; 瀏覽器已正確導入根證書&#xff08;.pem文件&#xff09;&#xff0c;但訪問HTTPS站點時仍提示不安全連接或證…

線上 Linux 環境 MySQL 磁盤 IO 高負載深度排查與性能優化實戰

目錄 一、線上告警 二、問題診斷 1. 系統層面排查 2. 數據庫層面分析 三、參數調優 1. sync_binlog 參數優化 2. innodb_flush_log_at_trx_commit 參數調整 四、其他優化建議 1. 日志文件位置調整 2. 生產環境核心參數配置模板 3. 突發 IO 高負載應急響應方案 五、…