跳過到頁腳內容
Iron Academy Logo
C#常見問題

C#中的條件重構:避免Derek Comartin的條件混亂

Derek Comartin
11m 03s

在 C# 中,像 if 陳述式、if else 陳述式和 switch 陳述式這樣的條件陳述式是基本工具。 但是當這些結構被過度使用時會發生什麼事——特別是與枚舉綁定時? Derek Comartin 在他的影片中"Enums Aren't Evil. Conditionals Everywhere Are",帶領我們詳盡地重構,將廣泛的條件邏輯替換為更乾淨、更易維護的模式。

在這篇文章中,我們將逐步走過 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 陳述式檢查同一個枚舉,然後將結果傳遞給另一個方法,而該方法也包含類似的檢查。

該陳述式檢查 Template 或 Ebook,並返回某個值——否則返回 null。 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())

這只在存在可下載資源時返回 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";

雖然它是有效語法,但當邏輯變得複雜時可能容易出錯且難以閱讀。 尤其是當您的條件評估為 false 時,錯誤的值可能會在不明顯的方式中被賦予。

同樣,帶有 break 陳述式和預設情況的 switch 也會陷入這個陷阱。最好問對象該如何行為,而不是使用 switch-case 邏輯。

結論:以更少的條件混亂實現更智能的控制

總之,Derek 的 影片 並不是對枚舉的攻擊——而是對我們在枚舉周圍使用條件 if 結構的批判。 通過在您的代碼庫中分散 if else 和 switch 陳述式,您使系統更難以測試、維護和發展。

無論您選擇封裝、運行時查詢、繼承還是簡單的擴展方法,目標都保持不變:減少條件,並將邏輯移動到應在的位置。

請記住:

  • 條件並不是壞的。

  • 條件混亂才是。

  • 乾淨的代碼不依賴散佈在類之間的多個 if else 陳述式。

  • 評估您的情境並相應地重構。

正如 Derek 所說,"這取決於您的上下文。"但有一點是肯定的:產品並非總是僅僅是產品——有時它是一個重新思考您設計的信號。

Hero Worlddot related to C#中的條件重構:避免Derek Comartin的條件混亂
Hero Affiliate related to C#中的條件重構:避免Derek Comartin的條件混亂

通過分享您所愛的東西賺得更多

您是否在為使用.NET、C#、Java、Python或Node.js的開發者創建內容?將您的專業知識轉化為額外收入!

鋼鐵支援團隊

我們每週 5 天,每天 24 小時在線上。
聊天
電子郵件
打電話給我