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
,还可以使用 throw
或 continue
if (value == null) { throw new ArgumentNullException(nameof(value)); }foreach (var item in collection)
{if (item.IsEmpty) { continue; }Process(item);
}
什么时候应该积极使用 Guard 子句?
情况 | 描述 |
---|---|
包含大量验证的函数 | 通过提前返回来清理条件 |
包含许多状态分支的循环 | 使用 |
存在未检查的异常风险 | 清理 |
方法开始变长时 | 使用 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 的分支较多时 |
|
命令/输入处理分支 | 键/按钮输入 → 动作映射 |
状态机 | 状态 → 行为对应结构 |
需要分离出可测试的逻辑时 |
|
缺点及注意事项
缺点 | 应对措施 |
---|---|
未注册的键没有对应处理 |
|
不保证顺序 | 使用 |
复杂条件分支难以处理 | 仅适用于简单条件逻辑,复杂逻辑仍需使用 |
实战技巧
-
命令模式(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# 中,可以通过 readonly
、const
、record
等方式实现有意图的不可变设计 。
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# 中,可以通过
readonly
、record
和ScriptableObject + Getter
设计来实现。
结论:
我们了解了在 C# 中经常使用的基础重构方法。
实际上,详细撰写这种入门级别的文章对我也有帮助,因此我重新整理了一遍。
除此之外,还有很多其他模式,但我选出了三个我认为重要的基础概念。
相关文章:

C# 初学者的 3 种重构模式
(Martin Fowlers Example) 1. 积极使用 Guard Clause(保护语句) "如果条件不满足,立即返回。将核心逻辑放在最少缩进的地方。" 概念定义 Guard Clause(保护语句) 是一种在函数开头检查特定条件是否满足&a…...

MySQL 数据类型深度全栈实战,天花板玩法层出不穷!
在 MySQL 数据库的世界里,数据类型是构建高效、可靠数据库的基石。选择合适的数据类型,不仅能节省存储空间,还能提升数据查询和处理的性能 目录 编辑 一、MySQL 数据类型总览 二、数值类型 三、字符串类型 四、日期时间类型 五、其他…...

前端vscode学习
1.安装python 打开Python官网:Welcome to Python.org 一定要点PATH,要不然要自己设 点击install now,就自动安装了 键盘winR 输入cmd 点击确定 输入python,回车 显示这样就是安装成功了 2.安装vscode 2.1下载软件 2.2安装中文 2.2.1当安…...
自动驾驶传感器数据处理:Python 如何让无人车更智能?
自动驾驶传感器数据处理:Python 如何让无人车更智能? 1. 引言:为什么自动驾驶离不开数据处理? 自动驾驶一直被誉为人工智能最具挑战性的应用之一,而其背后的核心技术正是 多传感器融合与数据处理。 一辆智能驾驶汽车,通常搭载: 激光雷达(LiDAR) —— 3D 环境感知,…...
从电商角度设计大模型的 Prompt
从电商角度设计大模型的 Prompt,有一个关键核心思路:围绕具体业务场景明确任务目标输出格式,帮助模型为运营、客服、营销、数据分析等工作提效。以下是电商场景下 Prompt 设计的完整指南,包含通用思路、模块范例、实战案例等内容。…...
利用 SQL Server 作业实现异步任务处理:一种简化系统架构的实践方案
在中小型企业系统架构中,很多业务场景需要引入异步任务处理机制,例如: 订单完成后异步生成报表; 用户操作后触发异步推送; 后台批量导入数据后异步校验; 跨系统的数据同步与转换。 传统做法是引入消息…...
平安健康2025年一季度深耕医养,科技赋能见成效
近日,平安健康医疗科技有限公司(股票简称“平安好医生”,1833.HK)公布截至2025年3月31日止三个月的业绩报告,展现出强劲的发展势头与潜力。 2025年一季度,中国经济回升向好,平安健康把握机遇&a…...

Index-AniSora技术升级开源:动漫视频生成强化学习
B站升级动画视频生成模型Index-AniSora技术并开源,支持番剧、国创、漫改动画、VTuber、动画PV、鬼畜动画等多种二次元风格视频镜头一键生成! 整个工作技术原理基于B站提出的 AniSora: Exploring the Frontiers of Animation Video Generation in the So…...
LLVM编译C++测试
安装命令 sudo apt install clang sudo apt-get install llvm 源码 hello.cpp #include <iostream> using namespace std; int main(){cout << "hello world" << endl;return 0; }编译 clang -emit-llvm -S hello.cpp -o hello.ll 执行后&#…...

ubuntu24.04+RTX5090D 显卡驱动安装
初步准备 Ubuntu默认内核太旧,用mainline工具安装新版: sudo add-apt-repository ppa:cappelikan/ppa sudo apt update && sudo apt full-upgrade sudo apt install -y mainline mainline list # 查看可用内核列表 mainline install 6.13 # 安装…...

MATLAB贝叶斯超参数优化LSTM预测设备寿命应用——以航空发动机退化数据为例
原文链接:tecdat.cn/?p42189 在工业数字化转型的浪潮中,设备剩余寿命(RUL)预测作为预测性维护的核心环节,正成为数据科学家破解设备运维效率难题的关键。本文改编自团队为某航空制造企业提供的智能运维咨询项目成果&a…...

鸿蒙应用开发:Navigation组件使用流程
一、编写navigation相关代码 1.在index.ets文件中写根视图容器 2.再写两个子页面文件 二、创建rote_map.json文件 三、在module.json5文件中配置路由导航 子页配置信息 4.跳转到其他页面 但是不支持返回到本页面的 用以下方式 以下是不能返回的情况 onClick(()>{this.pag…...
javaweb的拦截功能,自动跳转登录页面
我们开发系统时候,肯定希望用户登录后才能进入主页面去访问其他服务,但要是没有拦截功能的话,他就可以直接通过url访问或者post注入攻击了。 因此我们可以通过在后端添加拦截过滤功能把没登录的用户给拦截下来,让他去先登录&#…...

【Linux】系统在输入密码后进入系统闪退锁屏界面
问题描述 麒麟V10系统,输入密码并验证通过后进入桌面,1秒左右闪退回锁屏问题 问题排查 小白鸽之前遇到过类似问题,但是并未进入系统桌面内直接闪退到锁屏。 之前问题链接: https://blog.csdn.net/qq_51228157/article/details/140…...
当物联网“芯”闯入纳米世界:ESP32-S3驱动的原子力显微镜能走多远?
上次咱们把OV2640摄像头“盘”得明明白白,是不是感觉ESP32-S3这小东西潜力无限?今天,咱们玩个更刺激的,一个听起来就让人肾上腺素飙升的挑战——尝试用ESP32-S3这颗“智慧芯”,去捅一捅科学界的“马蜂窝”,…...

微信小程序webview与VUE-H5实时通讯,踩坑无数!亲测可实现
背景:微信小程序、vue3搭建开发的H5页面 在微信小程序开发中,会遇到嵌套H5页面,H5页面需要向微信小程序发消息触发微信小程序某个函数方法,微信开发文档上写的非常不清楚,导致踩了很多坑,该文章总结可直接使…...
Web请求与相应
目录 HTTP协议 一、协议基础特性 二、协议核心组成 三、完整通信流程(TCP/IP层) 1. 基础方法 2. 扩展方法 3. 安全性与幂等性 4. 应用场景示例 三、关键版本演进 四、典型工作流程 HTTP状态码 一、状态码分类体系 二、详细状态码表格&#…...

LeetCode222_完全二叉树的结点个数
LeetCode222_完全二叉树的结点个数 标签:#位运算 #树 #二分查找 #二叉树Ⅰ. 题目Ⅱ. 示例 0. 个人方法 标签:#位运算 #树 #二分查找 #二叉树 Ⅰ. 题目 给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。 完全二叉树 的定义如下&…...

STM32之温湿度传感器(DHT11)
KEIL软件实现printf格式化输出 一般在标准C库是提供了格式化输出和格式化输入等函数,用户想要使用该接口,则需要包含头文件 #include ,由于printf函数以及scanf函数是向标准输出以及标准输入中进行输出与输入,标准输出一般指的是…...

在微创手术中使用Kinova轻型机械臂进行多视图图像采集和3D重建
在微创手术中,Kinova轻型机械臂通过其灵活的运动控制和高精度的操作能力,支持多视图图像采集和3D重建。这种技术通过机械臂搭载的光学系统实现精准的多角度扫描,为医疗团队提供清晰且详细的解剖结构模型。其核心在于结合先进的传感器配置与重…...
2025版 JavaScript性能优化实战指南从入门到精通
JavaScript作为现代Web应用的核心技术,其性能直接影响用户体验。本文将深入探讨JavaScript性能优化的各个方面,提供可落地的实战策略。 一、代码层面的优化 1. 减少DOM操作 DOM操作是JavaScript中最昂贵的操作之一: // 不好的做法&#x…...
FluxCD入门操作文档
文章目录 FluxCD使用文档一、入门1.1 什么是FluxCD1.2 什么是GitOps1.3 什么是持续交付1.4 什么是**Source(源)**1.5 **什么是Reconciliation(协调)**1.6 什么是**Kustomization****与 kustomize 工具的区别**1.7 什么是**Bootstrap(引导)**1.8 安装Flux CLI1.9 配置flux…...

DOM API-JS通过文档对象树操作Doc和CSS
还记得我在之前的前端文章里面老是提及的 DOM 吗,当时只是简单介绍了它的组成以及作用,今天我们就来详细聊聊 Web浏览器 先来聊聊web浏览器,web浏览器是非常复杂的软件,有许多活动部件,许多部件并不能由开发者通过 J…...
实现了TCP的单向通信
1. 客户端代码:Client.java package com.xie.javase.net1;import java.io.*; import java.net.*;public class Client {public static void main(String[] args) {Socket socket = null;BufferedWriter bw = null;try {// 1. 获取本机IP地址对象InetAddress localHost = Inet…...
PostgreSQL中通过查询数据插入到表的几种方法( SELECT INTO和INSERT INTO ... SELECT)
使用 SELECT INTO 创建新表 在PostgreSQL中,SELECT INTO语法有两种主要用途:创建新表和将查询结果存储到变量中(在PL/pgSQL函数或存储过程中)。以下是详细介绍: 1. 创建新表并复制数据(类似SQL标准) SELECT * INTO new_table FROM existing_table WHERE condition;说…...
STM32项目实战:ADC采集
STM32F103C8T6的ADC配置。PB0对应的是ADC1的通道8。在标准库中,需要初始化ADC,设置通道,时钟,转换模式等。需要配置GPIOB的第0脚为模拟输入模式,然后配置ADC1的通道8,设置转换周期和触发方式。 接下来是I2C…...

CYT4BB Dual Bank - 安全启动
本节介绍TRAVEO™ T2G微控制器(MCU)的启动顺序。有关TRAVEO™ T2G微控制器的安全特性、不同的生命周期阶段以及“安全启动”序列的详细描述,请参阅 AN228680 -Secure system configuration in TRAVEO™ T2G family. TRAVEO™ T2G微控制器(MCU)的启动序列(见图3)基于…...
Windows系统下MySQL 8.4.5压缩包安装详细教程
一、MySQL 8.4.5新特性概览 相较于旧版本,MySQL 8.4.5在性能与功能上实现了显著提升: 性能优化:官方测试显示,在高并发场景下,其读写性能较5.7版本提升近2倍,尤其在处理热点数据竞争问题时表现更为出色。…...

科技行业智能化升级经典案例—某芯片公司
案例标题 CSGHub赋能某芯片公司:国产AI芯片全链路管理平台的高效落地与生态共建 执行摘要 某芯片公司在开发内部模型管理平台时,选择AgenticOps体系中的CSGHub作为核心工具,通过其本地化部署能力、中文支持及RESTful API接口,解决…...

Python编程从入门到实践 PDF 高清版
各位程序员朋友们,还在为找不到合适的Python学习资料而烦恼吗?还在为晦涩难懂的编程书籍而头疼吗?今天,就给大家带来一份重磅福利——237完整版PDF, 我用网盘分享了「Python编程:从入门到实践__超清版.pdf…...