在 C# 中重構條件 if:避免條件雜亂與 Derek Comartin
[[academy-video-youtube({"vid": "QoK7jSZ-viw", "start_time": "0", "title": "枚舉並不可怕。 到處都是條件式", "creator": "Derek Comartin", "length": "11m 03s"})]]
在 C# 中,條件語句如 if 語句、if else 語句和 switch 語句是基本工具。 但是,當這些結構被過度使用時會發生什麼——特別是與枚舉綁定時? Derek Comartin 在他的影片"枚舉並不可怕。到處都是條件式 在這個影片中,Derek帶我們透過一個詳細的重構,用更清晰、更易於維護的模式替換掉廣泛的條件邏輯。
在這篇文章中,我們將一步步走過Derek的思路,使用他的時間戳作為錨點。 我們還會探索他的想法如何應用於 C# 中常見的條件模式,如三元運算符、else 語句和 switch-case 結構——突出它們在大型程式碼庫中的問題以及如何重構以達到更好的設計。
條件式爆炸:真正的問題
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 邏輯出現在多個服務中,當新增新的枚舉值時會造成不一致和錯誤。 例如,當您引入新的產品型別如 Video 時會怎樣? 您將需要記得更新每一個包含此條件表達式的程式碼塊。
重複放大了複雜性
在以下範例中,Derek深入巢狀條件。 在一種方法中,一個 if else 語句檢查相同的枚舉,並將結果傳遞給另一種方法,該方法也包含類似的檢查。
語句檢查 Template 或 Ebook,並返回某些東西——否則返回 null。 Derek 指出,這種冗餘不僅使程式碼更長,還帶來維護風險。 相同的邏輯被複製到多個文件中,導致控制流程混亂。
如果您的系統要求您每次觸摸枚舉時都要新增預設情況,那麼您就知道有些地方出錯了。
改變我們對條件式的思考方式
而不是不斷使用 if else 檢查型別,Derek 建議問一個更好的問題:
產品是否具有可下載的特性?
這對意圖的表達更好。 它使您的程式碼更具可讀性並減少對枚舉的依賴。 而不是編寫一個具有兩個條件的 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())僅在存在可下載資源時返回 true——減少對複雜條件表達式的需求。
從 If 語句到封裝行為
為了解決核心問題,Derek 引入了 DownloadableResource 型別。 此型別包括一個下載 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);
}這大大簡化了邏輯並消除了對 else 語句分支甚至 switch 語句的需要。
運行時超越編譯時:一個戰略轉變
Derek 更進一步,解釋了一個重要的設計選擇:將邏輯從編譯時轉換為運行時。這意味著在運行時查詢系統以查看產品是否存在 DownloadableResource。 如果存在,則採取行動。 如果不,在跳過它。
他指出,此舉將靜態 if else 邏輯轉變為運行時查詢。 這可能會增加一個資料庫調用,但它減少了巢狀的 if else 邏輯並集中行為。 這提高了大規模的可維護性。
使用繼承來管理可下載產品
Derek 探索的另一種方法是繼承。 您可以建立一個抽象基類 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())這將您的邏輯集中化,避免一次又一次地重複大括號和程式碼塊。
避免過度使用三元運算符和 switch
Derek 也警告不要過度使用如三元運算符等簡寫:
string filename = product.Type == ProductType.Template ? "template.pdf" : "default.pdf";string filename = product.Type == ProductType.Template ? "template.pdf" : "default.pdf";雖然這是有效的語法,但在邏輯變得複雜時,它可能會容易出錯且難以閱讀。 尤其是當條件判斷為假時,可能會以微妙的方式分配錯誤的值。
同樣地,帶有 break 語句和預設情況的 switch 也陷入這個陷阱。更好的是請物件給予行為而不是使用 switch-case 邏輯。
結論:更智能的控制與更少的條件群雜
總之,Derek 的影片不僅僅是批評枚舉——而是對我們如何在它們周圍使用條件 if 結構的批判。 通過將 if else 和 switch 語句分散到程式碼庫中,您使得系統更加難以測試、維護和演化。
無論您選擇封裝、運行時查詢、繼承還是簡單的擴展方法,目標仍然是:減少條件式,並將邏輯移動到其應所在的位置。
記住:
條件式並不壞。
條件群雜是問題。
優雅的程式碼不依賴於類中零散的多個 if else 語句。
- 評估您的上下文並據此重構。
正如 Derek 所說,"這取決於您的上下文。" 但有一點是確定的:產品不僅僅只是產品——有時它是重新思考您設計的一個信號。

