重构 C# 中的条件 if:与 Derek Comartin 一起避免条件混乱
在 C# 中,if 语句、if else 语句和 switch 语句等条件语句是必不可少的工具。 但是,如果过度使用这些构造,尤其是与枚举绑定时,会发生什么情况呢? Derek Comartin 在他的视频"枚举并不邪恶。 条件无处不在》通过详细的重构,用更简洁、可维护性更强的模式取代了普遍存在的条件逻辑。
在本文中,我们将以 Derek 的时间戳为锚,一步一步地进行推理。 我们还将探讨他的观点如何应用于 C# 中的常见条件模式,如三元运算符、else 语句和 switch-case 结构--强调这些在大型代码库中可能导致问题的地方,以及如何重构以实现更好的设计。
条件 If 爆炸:真正的问题
Derek 首先展示了检查产品类型的 if 语句:
if (productType == ProductType.Template || productType == ProductType.Ebook)if (productType == ProductType.Template || productType == ProductType.Ebook)乍一看,上面的条件 if 简单明了。 但 Derek 警告说,这种语句会对给定条件进行评估,然后仅在条件为真的情况下执行代码块--如果这种逻辑到处重复,就会出现问题。
您可能会在另一个方法或类中再次遇到这个块:
if (offeringType == ProductType.Template || offeringType == ProductType.Ebook)if (offeringType == ProductType.Template || offeringType == ProductType.Ebook)Derek 解释说,这种模式很快就会在大型系统中传播开来。 多个服务中出现了相同的 if else 逻辑,导致在添加新枚举值时出现不一致和错误。 例如,当您引入一个新的产品类型(如视频)时,会发生什么情况? 您必须记住更新存在此条件表达式的每一个代码块。
重复会增加复杂性
在下面的示例中,Derek 深入探讨了嵌套条件式。 在一个方法中,if else 语句检查相同的枚举,然后将结果传递给另一个方法,后者也包含类似的检查。
语句检查模板或 Ebook,并返回内容,否则返回空。 Derek 指出,这种冗余不仅会使代码变长,还会带来维护方面的隐患。 相同的逻辑被复制到多个文件中,导致控制流混乱。
如果您的系统要求您在每次接触枚举时都添加默认情况,那么您就知道有问题了。
改变我们思考条件句的方式
Derek 建议提出一个更好的问题,而不是不断地用 if else 检查类型:
产品是否具有可下载的特性?
这样才能更好地表达意图。 它使您的代码更具可读性,并减少对枚举的完全依赖。 而不是写一个包含两个条件的 if 语句,如
if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)您只需将该逻辑封装在一个模型中并编写即可:
if (product.HasDownloadableResource())if (product.HasDownloadableResource())只有当存在可下载资源时,翻译才会返回真实内容,从而减少对复杂条件表达式的需求。
从 If 语句到封装行为
为了解决核心问题,Derek 引入了可下载资源类型。 该类型包括下载 URL 和默认文件名。 它将成为您领域的一流组成部分,而不是依靠 if 语句来推导。
现在,我不再重复这些了:
if (product.Type == ProductType.Template)
{
// Generate file name
}
else if (product.Type == ProductType.Ebook)
{
// Generate file name
}if (product.Type == ProductType.Template)
{
// Generate file name
}
else if (product.Type == ProductType.Ebook)
{
// Generate file name
}你来写
var downloadable = product.GetDownloadableResource();
if (downloadable != null)
{
Console.WriteLine(downloadable.FileName);
}var downloadable = product.GetDownloadableResource();
if (downloadable != null)
{
Console.WriteLine(downloadable.FileName);
}在翻译过程中,翻译人员必须使用.NET、Java、Python 或 Node js 等开发工具,这样可以大大简化逻辑,不需要 else 语句分支,甚至不需要 switch 语句。
运行时重于编译时:战略转移
Derek 进一步解释了一个重要的设计选择:将逻辑从编译时转移到运行时。这意味着要在运行时查询系统,查看产品是否存在可下载资源。 如果可以,就请付诸行动。 否则,请跳过。
他指出,此举将静态的 if else 逻辑转化为运行时查询。 它可能会增加数据库调用,但会减少嵌套的 if else 逻辑并集中行为。 这样可以提高大规模的可维护性。
为可下载产品使用继承
德里克探索的另一条途径是继承。 您可以创建一个抽象基类 Product,然后定义派生类型,如 Ebook、Template 或 OfflineCourse。
每一个都要覆盖以下方法
public virtual string GetDownloadUrl() { ... }public virtual string GetDownloadUrl() { ... }这种方法允许每个产品处理自己的逻辑。 虽然这避免了 switch 语句或多个条件语句,但 Derek 指出,如果您不小心,最终还是会在内部编写条件表达式。
无需继承即可实现更好的封装
如果感觉继承过于繁琐,Derek 建议使用像 DownloadableProduct 这样的显式类型,它包含自己的属性和方法,而不受制于层次结构。
在您的程序中,这可能是这样的
var downloader = new DownloadableProduct(product);
Console.WriteLine(downloader.GetDefaultFileName());var downloader = new DownloadableProduct(product);
Console.WriteLine(downloader.GetDefaultFileName());不需要 if else 或 switch 语句来确定行为--每个对象都知道该做什么。
轻量级修复:枚举上的扩展方法
如果您还没有准备好放弃枚举,Derek 会提出一个轻量级的解决方案--创建一个扩展方法:
public static bool IsDownloadable(this ProductType type)
{
return type == ProductType.Template || type == ProductType.Ebook;
}public static bool IsDownloadable(this ProductType type)
{
return type == ProductType.Template || type == ProductType.Ebook;
}现在不用写
if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)您可以将其简化为
if (product.Type.IsDownloadable())if (product.Type.IsDownloadable())这样可以集中逻辑,避免重复大括号和代码块。
避免过度使用三元操作符和开关
Derek 还提醒大家不要过度使用三元运算符等速记符号:
string filename = product.Type == ProductType.Template ? "template.pdf" : "default.pdf";string filename = product.Type == ProductType.Template ? "template.pdf" : "default.pdf";虽然这是有效的语法,但当逻辑变得复杂时,可能会容易出错,而且难以阅读。 特别是当您的条件评估为 false 时,可能会以微妙的方式分配错误的值。
同样,带有 break 语句和默认情况的 switch 也会落入这个陷阱。与其使用 switch-case 逻辑,不如询问对象的行为。
结论:用更少的杂乱条件实现更智能的控制
总之,Derek 的视频并不是对枚举的攻击,而是对我们如何围绕枚举使用条件 if 结构的批评。 在代码库中散布 if else 和 switch 语句会增加系统的测试、维护和发展难度。
无论您是选择封装、运行时查找、继承还是简单的扩展方法,目标都是一样的:减少条件式,将逻辑移到它该去的地方。
请记住
条件式也不错。
条件杂乱。
简洁的代码不依赖于分散在各个类中的多个 if else 语句。
- 评估上下文并进行相应的重构。
正如德里克所说,"这取决于你的语境"。但有一点是肯定的:产品并不总是单纯的产品--有时,它是一个重新思考设计的信号。

