簡體   English   中英

如何管理無效檢查的沖擊?

[英]How can I manage the onslaught of null checks?

通常,在編程中我們會遇到null檢查顯示特別大的情況。 我說的是:

if (doc != null)
{
  if (doc.Element != null)
  {
    ... and so on
  }
  else
    throw new Exception("Element cannot be null");
} else {
  throw new Exception("document cannot be null");
}

基本上,整個事情變成了一個難以理解的噩夢,所以我想知道:有沒有更簡單的方法來描述我上面要做的事情? (除了空檢查,我還會不時得到string.IsNullOrEmpty東西。)

接受的答案:我接受了具有此鏈接的答案,因為所描述的方法具有創新性,正是我想要的。 謝謝肖恩!

看看這篇文章: 一種流暢的C#參數驗證方法

它由一個Paint.NET開發人員編寫。 他使用擴展方法來簡化和清理空檢查代碼。

將它們向前推到函數的開頭,並將它們從執行工作的代碼部分中取出。 像這樣:

if (doc == null)
    throw new ArgumentNullException("doc");
if (doc.Element == null)
    throw new ArgumentException("Element cannot be null");
AndSoOn(); 

如果你只是想拋出異常,為什么不讓語言運行時為你拋出它?

doc.Element.whatever("foo");

您仍將獲得具有完整回溯信息的NullPointerException(或C#中的任何內容)。

您可能對Spec#感興趣:

Spec#是API契約的形式語言,它使用非空類型 ,前置條件,后置條件,對象不變量和模型程序(將整個運行歷史考慮在內的行為契約)構造擴展C#。

你可以讓調用者負責確保參數不為null。 Spec#使用感嘆號表示:

public static void Clear(int[]! xs) // xs is now a non-null type
{
    for (int i = 0; i < xs.Length; i++)
    {
        xs[i] = 0;
    }
}

現在是Spec#編譯器,它將檢測可能的空引用:

int[] xs = null;
Clear(xs);   // Error: null is not a valid argument

作為旁注,您可能還想確保您沒有違反得墨忒耳法則

單獨(靜態?)函數調用:

public static void CheckForNullObject( object Obj, string Message) {
    if(Obj == null){
        throw new Exception(Message);
    }
}

雖然這不是最好的選擇,但它會更具可讀性。

也許類的構造函數應該只在屬性初始化為適當的值時才創建對象? 也就是說,如果一個實例在創建時具有最小數量的屬性,那么只能創建一個實例,而不是創建一個基本上可以執行相同操作的Validate(Doc doc)方法,即檢查對象的有效性。

您可能對Null Object Pattern感興趣。

它幫助我過去在很多實例中擺脫了空檢查。

示例(C ++)

   class IThing
   { 
        public:
          virtual void DoThing() = 0;
   }; 

   class NullThing : public IThing
   { 
        public:
          virtual void DoThing() { /*no-op*/}
   }; 

   class RealThing : public IThing
   { 
        public:
          virtual void DoThing() { /*does something real*/}
   }; 

   int main()
   {
         NullThing theNullInstance; /* often a singleton or static*/
         IThing* thingy = &theNullInstance; /*the null value*/

         // Do stuff that may or may not set  thingy to a RealThing

         thingy->DoThing(); // If is NullThing, does nothing, otherwise does something

         // Can still check for null 
         // If NullThing is a singleton
         if (thingy == &theNullInstance)
         {
              printf("Uhoh, Null thingy!\n"); 
         }
   }

如果一個典型的NullReferenceException會做,請不要打擾檢查,只是讓運行時拋出它。 如果您需要添加上下文錯誤信息,以進行日志記錄或調試(或其他),請繼續將驗證分解為不同的方法。 在這種情況下,我仍然鼓勵你拋出NullReferenceException ,並嵌入原始異常。

當我被迫手動挖掘深度XML文檔時,我不得不做類似的事情。

通常,我嘗試在類的接口邊界強制執行null-correctness。 然后,我可以在我的私有方法中忽略空檢查。

如果你因為嵌套的ifs而感覺它是不可讀的,我的建議是重寫如下:

if (doc == null)
{
  throw new Exception("document cannot be null");
}

if (doc.Element == null)
{
    throw new Exception("Element cannot be null");
}

doc.Element.someMethod()

該代碼示例幾乎不可讀...當變量可能為null時,您必須檢查空值。 但是,如果要減少此操作,請確保返回對象的方法永遠不會返回null並始終返回完全有效且構造的對象。 如果它返回null,它會拋出異常。 返回null或-1或其他一些奇怪的約定不應該替代錯誤處理。

看看“ 也許莫納德 ”。

它解決了您希望以可讀的方式在C#中進行詳細的Null檢查。

還有也許是codeplex項目

有幾個選項(其中一些已被其他人提及),所以我只是添加到列表中。

  1. 對於某些類型,使用null對象是有意義的。 在這種情況下,您必須確保方法永遠不會返回一個簡單的null,但總是返回一個實例(可能是null對象)。

  2. 如果你想使用Paige建議的靜態方法,你甚至可以把它變成一個擴展方法。 你可以做類似的事情:

      private static void ThrowIfNull(this object o, string message) { if (o != null) return; throw new ArgumentNullException(message); } 

合同是減少與空相關的問題和超級檢查的關鍵因素。

在某些情況下,有些方法可能/應該返回null。 如果你調用這樣的方法,那么你只需要檢查 最好盡早檢查。

並且有些方法不允許返回null。 不要檢查他們的返回值。 每種方法都有責任確保它履行自己的合同,因此作為該方法的調用者,您不必關心

有工具和語言功能可以幫助您記錄和檢查空檢查和合同的正確性。 抱歉,我不能再解釋了,因為我不是C#程序員。

如果你想深入了解,我推薦這四個問題。 它們主要以Java為中心,但C#也幾乎都是如此,有時答案甚至可以自定義為.net和c#。

除非你能夠用它們做一些聰明的事情,否則不要捕捉異常。

在這種情況下,您的異常處理程序在默認值上添加的值很少 - 也就是說,讓異常傳播回調用鏈。

在您的應用程序/線程的頂級,應始終有異常處理程序來處理這些未捕獲的異常。

編輯:投票失敗,我感到被誤解,也許我太敏感了;-)。 原始海報拋出的例外情況沒有任何價值。 他們沒有幫助最終用戶,他們也沒有幫助開發人員。

應用程序中的頂級異常處理程序應該捕獲這樣的異常並記錄它。 日志應包括堆棧跟蹤。 這告訴開發人員錯誤的來源,並消除了許多真正沒有用處的代碼。

如果異常增加了一些價值,那么我同意拋出它是合理的。 但這不是這種情況。 更糟糕的是,一旦你聲明這是一個很好的原則,你會看到更多的代碼行檢查空引用,以至於代碼會被它們弄得亂七八糟。

解決那些主張允許運行時拋出NullReferenceException的人:

開始討論一個主題 ,即主動拋出ArgumentNullException或讓運行時拋出NullReferenceException是一個好主意。 基本的共識是采用主動方法而不是NullReferenceException方法是個好主意。 我並不一定說他們是對的,而在這里提倡否則的人是錯的。 我只是說社區可能不同意你的看法。

指出的是,如果你做了很多這類檢查,那么你做錯事的機會很大。 要么你的方法做得太多,要么你傳遞了太多的“tramp”參數(除了被傳遞給另一個函數之外沒有其他目的的參數)。 也許您應該考慮是否可以將代碼更多地分解為單獨的方法,或者將某些參數封裝到對象中。

這是一個使用LINQ表達式的解決方案,檢查鏈中的所有成員訪問,如果一切有效,則返回實際值:

public static T CheckedGet<T>(Expression<Func<T>> expr) where T : class
{
    CheckAccess(expr);
    return expr.Compile().Invoke();
}

public static void CheckAccess(Expression expr)
{
    switch (expr.NodeType)
    {
        case ExpressionType.Lambda:
            CheckAccess((expr as LambdaExpression).Body);
            break;
        case ExpressionType.MemberAccess:
            {
                CheckAccess((expr as MemberExpression).Expression);
                var value = Expression.Lambda(expr).Compile().DynamicInvoke();
                if (value == null)
                {
                    throw new NullReferenceException(expr.ToString());
                }
            }
            break;
        case ExpressionType.ArrayIndex:
            {
                var binaryExpr = expr as BinaryExpression;
                CheckAccess(binaryExpr.Left);
                var arrayLength = (int)Expression.Lambda(Expression.ArrayLength(binaryExpr.Left)).Compile().DynamicInvoke();
                var arrayIndex = (int)Expression.Lambda(binaryExpr.Right).Compile().DynamicInvoke();
                if (arrayIndex >= arrayLength)
                {
                    throw new IndexOutOfRangeException(expr.ToString());
                }
                var value = Expression.Lambda(expr).Compile().DynamicInvoke();
                if (value == null)
                {
                    throw new NullReferenceException(expr.ToString());
                }
            }
            break;
        case ExpressionType.Constant:
            return;
    }
}

用法,例如:

var val = CheckedGet(() => classA.PropertyA.ArrayB[0].FieldC);

它將通過適當的異常檢查所有成員訪問null和有效數組長度。

如果你恰好使用C#考慮可以為

如果您要檢查傳遞給部分公共API的參數,那么您應該在方法的最開始使用“保護條款”,如Dan Monego所寫。 如果您使用IsNotNull等方法創建或重用一些輔助類(如Assert)(或許您只需要其中一個),那么代碼將很容易閱讀。

您的代碼將如下所示:

Assert.IsNotNull(doc, "document");
Assert.IsNotNull(doc.Element", "Element");

//... and so on

如果您要檢查傳遞給您的某個私有方法的參數,那么您不應該進行此類檢查,而應該添加一個合同(在方法文檔中或者如果您使用某些工具,則使用某些特殊聲明)並始終期望使用正確的參數調用此方法。

你總是可以寫:

if (doc == null && doc.Element == null
{

}

但是你在我上面的一個帖子中丟失了粒度(例如Exception中的單獨消息)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM