Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
[[academy-video-youtube({"vid":"QoK7jSZ-viw", "start_time":"0", "title":"枚举并不邪恶。 处处有条件,"创作者": "Derek Comartin", "长度": "11分03秒"})]]
在 C# 中,if 语句、if else 语句和 switch 语句等条件语句是必不可少的工具。 但是,如果过度使用这些构造,尤其是与枚举绑定时,会发生什么情况呢? Derek Comartin 在他的视频"枚举并不邪恶。 条件无处不在》通过详细的重构,用更简洁、可维护性更强的模式取代了普遍存在的条件逻辑。
在本文中,我们将以 Derek 的时间戳为锚,一步一步地进行推理。 我们还将探讨他的观点如何应用于 C# 中的常见条件模式,如三元运算符、else 语句和 switch-case 结构--强调这些在大型代码库中可能导致问题的地方,以及如何重构以实现更好的设计。
Derek 首先展示了检查产品类型的 if 语句:
if (productType == ProductType.Template || productType == ProductType.Ebook)If productType = ProductType.Template OrElse productType = ProductType.Ebook Then乍一看,上面的条件 if 简单明了。 但 Derek 警告说,这种语句会对给定条件进行评估,然后仅在条件为真的情况下执行代码块--如果这种逻辑到处重复,就会出现问题。
您可能会在另一个方法或类中再次遇到这个块:
if (offeringType == ProductType.Template || offeringType == ProductType.Ebook)If offeringType = ProductType.Template OrElse offeringType = ProductType.Ebook ThenDerek 解释说,这种模式很快就会在大型系统中传播开来。 多个服务中出现了相同的 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 OrElse product.Type = ProductType.Ebook Then您只需将该逻辑封装在一个模型中并编写即可:
if (product.HasDownloadableResource())If product.HasDownloadableResource() Then只有当存在可下载资源时,翻译才会返回真实内容,从而减少对复杂条件表达式的需求。
为了解决核心问题,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 Then
' Generate file name
ElseIf product.Type = ProductType.Ebook Then
' Generate file name
End If你来写
var downloadable = product.GetDownloadableResource();
if (downloadable != null)
{
Console.WriteLine(downloadable.FileName);
}Dim downloadable = product.GetDownloadableResource()
If downloadable IsNot Nothing Then
Console.WriteLine(downloadable.FileName)
End If在翻译过程中,翻译人员必须使用.NET、Java、Python 或 Node js 等开发工具,这样可以大大简化逻辑,不需要 else 语句分支,甚至不需要 switch 语句。
Derek 进一步解释了一个重要的设计选择:将逻辑从编译时转移到运行时。这意味着要在运行时查询系统,查看产品是否存在可下载资源。 如果可以,就请付诸行动。 否则,请跳过。
他指出,此举将静态的 if else 逻辑转化为运行时查询。 它可能会增加数据库调用,但会减少嵌套的 if else 逻辑并集中行为。 这样可以提高大规模的可维护性。
德里克探索的另一条途径是继承。 您可以创建一个抽象基类 Product,然后定义派生类型,如 Ebook、Template 或 OfflineCourse。
每一个都要覆盖以下方法
public virtual string GetDownloadUrl() { ... }Public Overridable Function GetDownloadUrl() As String
' ...
End Function这种方法允许每个产品处理自己的逻辑。 虽然这避免了 switch 语句或多个条件语句,但 Derek 指出,如果您不小心,最终还是会在内部编写条件表达式。
如果感觉继承过于繁琐,Derek 建议使用像 DownloadableProduct 这样的显式类型,它包含自己的属性和方法,而不受制于层次结构。
在您的程序中,这可能是这样的
var downloader = new DownloadableProduct(product);
Console.WriteLine(downloader.GetDefaultFileName());Dim 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 Shared Function IsDownloadable(type As ProductType) As Boolean
Return type = ProductType.Template OrElse type = ProductType.Ebook
End Function现在不用写
if (product.Type == ProductType.Template || product.Type == ProductType.Ebook)If product.Type = ProductType.Template OrElse product.Type = ProductType.Ebook Then您可以将其简化为
if (product.Type.IsDownloadable())If product.Type.IsDownloadable() Then这样可以集中逻辑,避免重复大括号和代码块。
Derek 还提醒大家不要过度使用三元运算符等速记符号:
string filename = product.Type == ProductType.Template ? "template.pdf" : "default.pdf";Dim filename As String = If(product.Type = ProductType.Template, "template.pdf", "default.pdf")虽然这是有效的语法,但当逻辑变得复杂时,可能会容易出错,而且难以阅读。 特别是当您的条件评估为 false 时,可能会以微妙的方式分配错误的值。
同样,带有 break 语句和默认情况的 switch 也会落入这个陷阱。与其使用 switch-case 逻辑,不如询问对象的行为。
总之,Derek 的视频并不是对枚举的攻击,而是对我们如何围绕枚举使用条件 if 结构的批评。 在代码库中散布 if else 和 switch 语句会增加系统的测试、维护和发展难度。
无论您是选择封装、运行时查找、继承还是简单的扩展方法,目标都是一样的:减少条件式,将逻辑移到它该去的地方。
请记住
条件式也不错。
条件杂乱。
简洁的代码不依赖于分散在各个类中的多个 if else 语句。
评估上下文并进行相应的重构。
正如德里克所说,"这取决于你的语境"。但有一点是肯定的:产品并不总是单纯的产品--有时,它是一个重新思考设计的信号。
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy