Подтвердить что ты не робот

Как я могу управлять атакой нулевых проверок?

Довольно часто при программировании мы получаем ситуации, когда проверки 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.)

Принятый ответ: Я принял ответ, который эта ссылка, потому что описанный подход новаторский и точно что я хочу. Спасибо Шон!

4b9b3361

Ответ 2

Нажимайте их вперед до начала функции и сохраняйте их вне той части кода, которая выполняет эту работу. Вот так:

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

Ответ 3

Если вы все равно собираетесь выбросить исключение, почему бы не позволить языковой среде бросить его для вас?

doc.Element.whatever("foo");

Вы все равно получите исключение NullPointerException (или что бы то ни было на С#) с полной информацией о трассировке.

Ответ 4

Вас может заинтересовать SpeС#:

SpeС# является формальным языком для контрактов API, который расширяет С# с помощью конструкций для непустые типы, предварительные условия, постусловия, инварианты объектов, и модельные программы (поведенческие контракты, которые берут историю всего с учетом).

Вы можете сделать это ответственностью вызывающего абонента, чтобы убедиться, что аргумент не равен нулю. SpeС# использует восклицательный знак, чтобы обозначить это:

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

Теперь, компилятор SpeС#, он обнаружит возможные нулевые варианты:

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

В качестве побочного примечания вы также можете убедиться, что не нарушаете Закон Деметры.

Ответ 5

Вызов функции Seperate (static?):

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

Хотя это не лучший выбор, было бы немного более читаемым.

Ответ 6

Может быть, конструктор класса должен создавать объект только в том случае, если свойства инициализируются соответствующими значениями? При этом экземпляр будет создан только в том случае, если он имеет минимальное количество свойств при создании, отличном от того, что вы могли бы создать метод Validate (Doc doc), который будет по существу делать то же самое, то есть проверить правильность объекта.

Ответ 7

Вам может быть интересен Нулевой шаблон объекта.

Это помогло мне в прошлом избавиться от нулевых проверок во многих случаях.

Пример (С++)

   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"); 
         }
   }

Ответ 8

Если типичный NullReferenceException будет делать, не утруждайте себя проверкой и просто позволяйте runtime бросать его. Если вам нужно добавить информацию о контекстной ошибке, для ведения журналов или отладки (или что-то еще), продолжайте и выставляйте свою валидацию другим способом. Я бы все же рекомендовал вам бросить NullReferenceException с исходным исключением, вложенным в этом случае.

Мне приходилось делать подобные вещи, когда мне приходилось вручную выполнять глубокие XML-документы.

В общем, я пытаюсь обеспечить нулевую корректность на границах интерфейса в моих классах. Затем я могу игнорировать нулевые проверки в своих личных методах.

Ответ 9

Если вы считаете, что это невозможно прочитать из-за вложенных 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()

Ответ 10

Этот пример кода вряд ли нечитабелен... Вам нужно будет проверить наличие нулей, когда существует возможность того, что переменная имеет значение null. Однако, если вы хотите сократить это, убедитесь, что методы, возвращающие объект, никогда не возвращают null и всегда возвращают полностью действующий и построенный объект. Попросите это сделать исключение в случае, если у вас есть он, который возвращает null. Возвращаемое значение null или -1 или другое нечетное соглашение не должно заменять обработку ошибок.

Ответ 12

Есть несколько вариантов (некоторые уже упоминались другими), поэтому я просто добавляю в список.

  • Для некоторых типов имеет смысл использовать нулевой объект. В этом случае вы должны убедиться, что методы никогда не возвращают простой null, но всегда возвращают экземпляр (который может быть нулевым объектом).

  • Если вы хотите использовать статический метод, предложенный Paige, вы можете даже превратить его в метод расширения. Вы можете сделать что-то похожее на:

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

Ответ 13

Контракты являются ключевым элементом в устранении проблем, связанных с нулем, и надёжных проверок.

В некоторых случаях есть методы, которые могут/должны возвращать значение null. Если вы вызываете метод, подобный этому, вам просто нужно проверить. Лучше всего проверить как можно раньше.

И есть методы, которым не разрешено возвращать null. Не проверяйте их возвращаемые значения. Каждый метод несет ответственность за то, чтобы он выполнял свой собственный контракт, так как вызывающий этот метод вам не нужно.

Существуют инструменты и языковые функции, которые помогут вам документировать и проверять правильность нулевой проверки и контрактов. Извините, я больше не могу объяснить, потому что я не программист на С#.

Если вы хотите погрузиться глубже, я рекомендую эти четыре вопроса. Они в основном ориентированы на Java, но почти все верно для С#, а иногда ответы даже настраиваются на .net и С#.

Ответ 14

НЕ улавливайте исключения, если вы не можете сделать что-то умное с ними.

В этом случае обработчики исключений добавят небольшое значение по умолчанию, то есть допуская, что исключения распространяются на цепочку вызовов.

На верхнем уровне (уровнях) вашего приложения/потока всегда должны быть обработчики исключений, чтобы справляться с этими неперехваченными исключениями.

EDIT: Будучи проголосовавшим, я чувствую себя непонятым, может быть, я слишком чувствителен;-). Исключения, которые бросает оригинальный плакат, не имеют значения. Они не помогают конечному пользователю, и они не помогают разработчику.

Обработчики исключений верхнего уровня в приложении должны искать исключение, подобное этому, и регистрировать его. Журнал должен содержать трассировку стека. Это говорит разработчику, откуда возникла ошибка, и устраняет многие строки кода, которые действительно не имеют никакой цели.

ЕСЛИ исключение добавит некоторую ценность, тогда я бы согласился, что разумно его бросить. Но это не так. Хуже того, как только вы заявите, что это хороший принцип, вы увидите еще много строк проверки кода для нулевых ссылок, до такой степени, что код будет загроможден ими.

Ответ 15

Чтобы обратиться к людям, которые выступают за то, чтобы позволить runtime вызывать исключение NullReferenceException:

I запустил тему по вопросу о том, целесообразно ли проактивно генерировать исключение ArgumentNullException или позволить runtime вызывать исключение NullReferenceException. Основной консенсус заключался в том, что рекомендуется использовать проактивный подход, а не подход NullReferenceException. Я не обязательно говорю, что они правы, а люди, которые здесь выступают, ошибаются. Я просто говорю, что сообщество может не согласиться с вами.

То, что я хотел бы отметить, состоит в том, что если вы делаете много этих проверок, шансы на то, что вы делаете что-то неправильно. Либо ваши методы слишком много делают, либо вы обходите слишком много аргументов "tramp" (аргументы, которые не имеют никакой цели, кроме передачи другой функции). Возможно, вам стоит подумать, можете ли вы больше разбить свой код на отдельные методы или инкапсулировать некоторые из аргументов в объект.

Ответ 17

Если вы собираетесь проверить параметр, переданный части вашего общедоступного API, тогда вы должны использовать "защитные предложения" в самом начале вашего метода, как писал Дэн Монего. Код будет легко читаться, если вы создадите или повторно используете некоторый вспомогательный класс, например Assert, с помощью методов IsNotNull и других (возможно, вам действительно нужен только один из них).

Ваш код будет выглядеть так:

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

//... and so on

Если вы собираетесь проверить параметр, переданный одному из ваших личных методов, тогда вам не следует иметь такие проверки, вместо этого вы должны добавить контракт (в документацию по методу или с некоторыми специальными объявлениями, если вы используете какой-либо инструмент для этого) и всегда ожидаем, что этот метод будет вызываться с правильными параметрами.

Ответ 18

Вот решение, использующее LINQ Expressions, проверяющее доступ всех членов в цепочке и возвращающее фактическое значение, если все действительно:

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 и допустимую длину массива.

Ответ 19

Вы всегда можете написать:

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

}

Но вы теряете зернистость с одним сообщением выше моего (например, отдельные сообщения в Exception).