C# 初学者的 3 种重构模式

news2025/5/24 3:33:32

(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.coloradmin.cn/o/2384315.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL 数据类型深度全栈实战,天花板玩法层出不穷!

在 MySQL 数据库的世界里&#xff0c;数据类型是构建高效、可靠数据库的基石。选择合适的数据类型&#xff0c;不仅能节省存储空间&#xff0c;还能提升数据查询和处理的性能 目录 ​编辑 一、MySQL 数据类型总览 二、数值类型 三、字符串类型 四、日期时间类型 五、其他…

前端vscode学习

1.安装python 打开Python官网&#xff1a;Welcome to Python.org 一定要点PATH&#xff0c;要不然要自己设 点击install now,就自动安装了 键盘winR 输入cmd 点击确定 输入python&#xff0c;回车 显示这样就是安装成功了 2.安装vscode 2.1下载软件 2.2安装中文 2.2.1当安…

Index-AniSora技术升级开源:动漫视频生成强化学习

B站升级动画视频生成模型Index-AniSora技术并开源&#xff0c;支持番剧、国创、漫改动画、VTuber、动画PV、鬼畜动画等多种二次元风格视频镜头一键生成&#xff01; 整个工作技术原理基于B站提出的 AniSora: Exploring the Frontiers of Animation Video Generation in the So…

ubuntu24.04+RTX5090D 显卡驱动安装

初步准备 Ubuntu默认内核太旧&#xff0c;用mainline工具安装新版&#xff1a; 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预测设备寿命应用——以航空发动机退化数据为例

原文链接&#xff1a;tecdat.cn/?p42189 在工业数字化转型的浪潮中&#xff0c;设备剩余寿命&#xff08;RUL&#xff09;预测作为预测性维护的核心环节&#xff0c;正成为数据科学家破解设备运维效率难题的关键。本文改编自团队为某航空制造企业提供的智能运维咨询项目成果&a…

鸿蒙应用开发:Navigation组件使用流程

一、编写navigation相关代码 1.在index.ets文件中写根视图容器 2.再写两个子页面文件 二、创建rote_map.json文件 三、在module.json5文件中配置路由导航 子页配置信息 4.跳转到其他页面 但是不支持返回到本页面的 用以下方式 以下是不能返回的情况 onClick(()>{this.pag…

【Linux】系统在输入密码后进入系统闪退锁屏界面

问题描述 麒麟V10系统&#xff0c;输入密码并验证通过后进入桌面&#xff0c;1秒左右闪退回锁屏问题 问题排查 小白鸽之前遇到过类似问题&#xff0c;但是并未进入系统桌面内直接闪退到锁屏。 之前问题链接&#xff1a; https://blog.csdn.net/qq_51228157/article/details/140…

微信小程序webview与VUE-H5实时通讯,踩坑无数!亲测可实现

背景&#xff1a;微信小程序、vue3搭建开发的H5页面 在微信小程序开发中&#xff0c;会遇到嵌套H5页面&#xff0c;H5页面需要向微信小程序发消息触发微信小程序某个函数方法&#xff0c;微信开发文档上写的非常不清楚&#xff0c;导致踩了很多坑&#xff0c;该文章总结可直接使…

LeetCode222_完全二叉树的结点个数

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

STM32之温湿度传感器(DHT11)

KEIL软件实现printf格式化输出 一般在标准C库是提供了格式化输出和格式化输入等函数&#xff0c;用户想要使用该接口&#xff0c;则需要包含头文件 #include &#xff0c;由于printf函数以及scanf函数是向标准输出以及标准输入中进行输出与输入&#xff0c;标准输出一般指的是…

在微创手术中使用Kinova轻型机械臂进行多视图图像采集和3D重建

在微创手术中&#xff0c;Kinova轻型机械臂通过其灵活的运动控制和高精度的操作能力&#xff0c;支持多视图图像采集和3D重建。这种技术通过机械臂搭载的光学系统实现精准的多角度扫描&#xff0c;为医疗团队提供清晰且详细的解剖结构模型。其核心在于结合先进的传感器配置与重…

DOM API-JS通过文档对象树操作Doc和CSS

还记得我在之前的前端文章里面老是提及的 DOM 吗&#xff0c;当时只是简单介绍了它的组成以及作用&#xff0c;今天我们就来详细聊聊 Web浏览器 先来聊聊web浏览器&#xff0c;web浏览器是非常复杂的软件&#xff0c;有许多活动部件&#xff0c;许多部件并不能由开发者通过 J…

CYT4BB Dual Bank - 安全启动

本节介绍TRAVEO™ T2G微控制器(MCU)的启动顺序。有关TRAVEO™ T2G微控制器的安全特性、不同的生命周期阶段以及“安全启动”序列的详细描述,请参阅 AN228680 -Secure system configuration in TRAVEO™ T2G family.   TRAVEO™ T2G微控制器(MCU)的启动序列(见图3)基于…

科技行业智能化升级经典案例—某芯片公司

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

Python编程从入门到实践 PDF 高清版

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

系统性能不达标,如何提升用户体验?

当系统性能不达标时&#xff0c;要想有效提升用户体验&#xff0c;必须从性能优化、前后端协同、用户感知改善、监控预警机制四个关键维度切入。其中&#xff0c;性能优化是最直接有效的策略&#xff0c;它通过代码优化、资源压缩、缓存机制、CDN加速等手段&#xff0c;显著提升…

智能守护校园“舌尖安全“:AI视频分析赋能名厨亮灶新时代

引言&#xff1a; 在校园食品安全备受关注的今天&#xff0c;一套融合视频监控管理平台与AI视频分析盒子的智能解决方案正在全国多地学校食堂悄然落地&#xff0c;为传统的"名厨亮灶"工程注入科技新动能。这套系统不仅实现了后厨操作的"透明化"&#xff0…

初步尝试AI应用开发平台——Dify的本地部署和应用开发

随着大语言模型LLM和相关应用的流行&#xff0c;在本地部署并构建知识库&#xff0c;结合企业的行业经验或个人的知识积累进行定制化开发&#xff0c;是LLM的一个重点发展方向&#xff0c;在此方向上也涌现出了众多软件框架和工具集&#xff0c;Dify就是其中广受关注的一款&…

卷积神经网络中的局部卷积:原理、对比与应用解析

【内容摘要】 本文聚焦卷积神经网络中的局部卷积&#xff0c;重点解析全连接、局部连接、全卷积与局部卷积四种连接方式的差异&#xff0c;结合人脸识别任务案例&#xff0c;阐述局部卷积的应用场景及优势&#xff0c;为理解卷积网络连接机制提供技术参考。 关键词&#xff1a…

重拾童年,用 CodeBuddy 做自己的快乐创作者

某个炎炎的夏日午后&#xff0c;阳光透过稀疏的树叶洒落在地上&#xff0c;一道道光影斑驳陆离。那时候的我们&#xff0c;还只是三五个小朋友&#xff0c;蹲坐在村头的一棵老槐树下&#xff0c;手里握着并不属于自己的游戏掌机&#xff0c;轮流按动着手柄的按键&#xff0c;在…