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

Избегая ненужных ссылок

По-видимому, подавляющее большинство ошибок в коде являются нулевыми ссылочными исключениями. Существуют ли какие-либо общие методы, чтобы избежать столкновений с нулевыми опорными ошибками?

Если я ошибаюсь, мне известно, что на таких языках, как F #, невозможно получить нулевое значение. Но это не вопрос, я спрашиваю, как избежать нулевых опорных ошибок на таких языках, как С#.

4b9b3361

Ответ 1

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

Моя главная рекомендация для людей, которые заботятся о качестве программного обеспечения, а также использует платформу программирования .NET, заключается в установке и использовании контрактов кода Microsoft (http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx). Он включает возможности проверки времени выполнения и статической проверки. Существенная возможность для создания этих контрактов в вашем коде включена в версию 4.0. Если вы заинтересованы в качестве кода, и это похоже на вас, вам действительно может понравиться использование контрактов кода Microsoft.

С контрактами кода Microsoft вы можете защитить свой метод от нулевых значений, добавив предварительные условия, подобные этому "Contract.Requires(customer!= null);". Добавление предварительного условия, подобного этому, эквивалентно практике, рекомендованной многими другими в их комментариях выше. До кодовых контрактов я бы рекомендовал вам сделать что-то вроде этого

if (customer == null) {throw new ArgumentNullException("customer");}

Теперь я рекомендую

Contract.Requires(customer != null);

Затем вы можете активировать систему проверки времени выполнения, которая как можно скорее поймает эти дефекты, что приведет вас к диагностике и исправлению дефектного кода. Но не позволяйте мне создавать впечатление, что кодовые контракты - просто причудливый способ заменить аргументы null. Они намного сильнее. С контрактами кода Microsoft вы также можете запустить статическую проверку и попросить ее изучить возможные сайты в вашем коде, где могут возникнуть исключения с нулевой ссылкой. Для статической проверки требуется немного больше опыта для использования. Я бы не рекомендовал его сначала для новичков. Но не стесняйтесь попробовать и посмотреть сами.

ИССЛЕДОВАНИЕ О РАСПРОСТРАНЕНИИ ОШИБКИ NULL REFERENCE

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

  • Ведущие исследователи Microsoft в правильность программы на SpeС# и проекты с кодовыми контрактами считают проблема, заслуживающая внимания.
  • Dr. Бертран Мейер и команда разработчиков программного обеспечения в ISE, которые разработали и поддержали Эйфель язык программирования, также верьте это проблема, заслуживающая внимания.
  • В моем собственном коммерческом опыте, разрабатывающем обычное программное обеспечение, я часто видел ошибки с нулевой ссылкой, что я хотел бы решить проблему в своих собственных продуктах и ​​практиках.

В течение многих лет Microsoft инвестировала в исследования, направленные на улучшение качества программного обеспечения. Одним из их усилий был проект SpeС#. Одним из самых захватывающих событий, на мой взгляд, с базой .NET 4.0 является введение контрактов на поставку Microsoft, что является результатом более ранней работы, проделанной исследовательской группой SpeС#.

Относительно вашего замечания "подавляющее большинство ошибок в коде являются нулевыми ссылочными исключениями", я считаю, что это определитель "подавляющее большинство", который вызовет некоторые разногласия. Фраза "Подавляющее большинство" предполагает, что, пожалуй, 70-90% ошибок имеют исключение с нулевой ссылкой в ​​качестве основной причины. Для меня это слишком высоко. Я предпочитаю цитировать результаты исследования Microsoft SpeС#. В своей статье система программирования SpeС#: обзор, Майк Барнетт, К. Рустан М. Лейно и Вольфрам Шульте. В CASSIS 2004 LNCS vol. 3362, Springer, 2004, они писали

1.0 Непустые типы Многие ошибки в современных программах проявляются как ошибки с нулевым разыменованием, предлагая важность программирования язык, обеспечивающий различать выражения, которые могут оценивать значение null и те, которые не обязательно (для некоторых экспериментальных доказательства, см. [24, 22]). На самом деле мы хотел бы искоренить все ошибки разыменования.

Это вероятный источник для людей в Microsoft, которые знакомы с этим исследованием. Эта статья доступна на сайте SpeС#.

Я скопировал ссылки 22 и 24 ниже и включил ISBN для вашего удобства.

  • Мануэль Фахндрих и К. Рустан М. Лейно. Объявление и проверка непустых типов в объектно-ориентированный язык. В трудах конференции ACM 2003 года по объектно-ориентированным Программирование, системы, языки и приложения, OOPSLA 2003, том 38, номер 11 в SIGPLAN Уведомления, стр. 302-312. ACM, ноябрь 2003. isbn = {1-58113-712-5},

  • Кормак Фланаган, К. Рустан М. Лейно, Марк Лиллибридж, Грег Нельсон, Джеймс Б. Сакс, и Raymie Stata. Расширенная статическая проверка для Java. В материалах ACM 2002 года Конференция SIGPLAN по разработке и внедрению языка программирования (PLDI), объем 37, номер 5 в сообщениях SIGPLAN, стр. 234-245. ACM, май 2002 г.

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

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

Я также вспомнил, что увидел что-то об этом в объявлении от ISE о недавнем выпуске Eiffel. Они ссылаются на эту проблему как на "безошибочную безопасность", и, как и многие другие вещи, вдохновленные или разработанные доктором Бертран Мейер, у них есть красноречивое и образовательное описание проблемы и способы предотвращения этого на их языке и инструментах. Я рекомендую вам прочитать их статью http://doc.eiffel.com/book/method/void-safety-background-definition-and-tools, чтобы узнать больше.

Если вы хотите узнать больше о контрактах на код Microsoft, в последнее время появилось множество статей. Вы также можете проверить мой блог по адресу http: SLASH SLASH codecontracts.info, который в основном посвящен разговорам о качестве программного обеспечения с помощью программирования с контрактами.

Ответ 2

В дополнение к вышеуказанным (Null Objects, Empty Collections) существуют некоторые общие методы, а именно Resource Assquisition is Initialization (RAII) из С++ и Design By Contract от Eiffel. Они сводятся к следующему:

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

Я видел много кода, который выглядит так:

if ((value!= null) && (value.getProperty()!= null) & &... && (... doSomethingUseful())

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

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

  • Если нуль представляет пустую коллекцию, используйте пустую коллекцию.
  • Если нуль представляет исключительный случай, выведите исключение.
  • Если значение null представляет собой случайно неинициализированное значение, явно инициализируйте его.
  • Если значение null представляет собой законное значение, проверьте его - или даже лучше используйте NullObject, который выполняет нулевой оператор.

На практике этот стандарт ясности на уровне разработки является нетривиальным и требует усилий и самодисциплины для последовательного применения кодовой базы.

Ответ 3

Нет.

Или, скорее, нет ничего особенного, чтобы попытаться "предотвратить" NREs в С#. По большей части NRE является лишь некоторой логической ошибкой. Вы можете использовать межсетевой экран на границах интерфейса, проверяя параметры и имея много кода, например

void Foo(Something x) {
    if (x==null)
        throw new ArgumentNullException("x");
    ...
}

по всему месту (большая часть .Net Framework делает это), так что, когда вы напортачиваете, вы получаете немного более информативную диагностику (трассировка стека еще более ценна, хотя NRE также обеспечивает это). Но вы все еще просто закончите исключение.

(Помимо этого: исключения, такие как NullReferenceException, ArgumentNullException, ArgumentException,... - как правило, не должны быть улавливаны программой, а просто означает "разработчик этого кода, есть ошибка, пожалуйста, исправьте". ссылайтесь на них как на исключение "время разработки", сравните их с истинными исключениями "времени выполнения", которые происходят в результате среды времени выполнения (например, FileNotFound) и предназначены для потенциального заражения и обработки программой.)

Но в конце дня вам просто нужно правильно его закодировать.

В идеале большинство NRE никогда не произойдет, потому что "null" является бессмысленным значением для многих типов/переменных, и в идеале система статического типа запрещает "null" как значение для этих конкретных типов/переменных. Тогда компилятор помешает вам ввести такую ​​случайную ошибку (исключение некоторых классов ошибок - это то, чем лучше всего подходят компиляторы и системы типов). Это означает, что некоторые языки и системы типов превосходят.

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

Ответ 4

Использование Нулевые шаблоны объектов здесь.

Убедитесь, что вы хотите, чтобы коллекции были пустыми в случае, если они не заполнены, а не null. Использование пустой коллекции, когда пустая коллекция будет делать, путают и часто не нужны.

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

Ответ 5

Одна из наиболее распространенных ошибок нулевой ссылки, которые я видел, - это строки. Будет проверка:

if(stringValue == "") {}

Но строка действительно нулевая. Это должно быть:

if(string.IsNullOrEmpty(stringValue){}

Кроме того, вы можете быть чрезмерно осторожны и проверить, что объект не имеет значения null, прежде чем пытаться получить доступ к элементам/методам этого объекта.

Ответ 6

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

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

Ответ 8

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

Ответ 9

Соответствующее использование структурированной обработки исключений может помочь избежать таких ошибок.

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

Ответ 10

Один из простейших способов избежать NullReferenceExceptions - это агрессивно проверять наличие нулевых ссылок в конструкторах/методах/установщиках классов и привлекать внимание к проблеме.

например.

public MyClass
{
   private ISomeDependency m_dependencyThatWillBeUsedMuchLater 

   // passing a null ref here will cause 
   // an exception with a meaningful stack trace    
   public MyClass(ISomeDependency dependency)
   {
      if(dependency == null) throw new ArgumentNullException("dependency");

      m_dependencyThatWillBeUsedMuchLater = dependency;
   }

   // Used later by some other code, resulting in a NullRef
   public ISomeDependency Dep { get; private set; }
}

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

Вы заметите, что библиотеки .NET Framework почти всегда терпят неудачу рано и часто, если вы предоставляете нулевые ссылки, где это недействительно. Так как исключение, явно заброшенное, говорит: "Вы испортились!" и говорит вам, почему это делает обнаружение и исправление дефектного кода тривиальной задачей.

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

Что бы вы предпочли, ArgumentNullException в методе ввода или неясная ошибка в кишках? Чем дальше вы удаляетесь от источника ошибки, тем труднее ее проследить.

Ответ 11

Хорошие инструменты анализа кода могут помочь здесь. Хорошие модульные тесты также могут помочь, если вы используете инструменты, которые рассматривают null как возможный путь через ваш код. Попытайтесь бросить этот переключатель в своих настройках сборки, который говорит "обрабатывать предупреждения как ошибки" и посмотреть, можете ли вы сохранить # предупреждения в своем проекте = 0. Вы можете обнаружить, что предупреждения сообщают вам много.

Следует иметь в виду, что это может быть хорошая вещь, в которой вы выбрасываете исключение с помощью NULL-ссылки. Зачем? потому что это может означать, что код, который должен был выполнить, не сделал. Инициализация значений по умолчанию - хорошая идея, но вы должны быть осторожны, чтобы не скрывать проблему.

List<Client> GetAllClients()
{
    List<Client> returnList = new List<Client>;
    /* insert code to go to data base and get some data reader named rdr */
   for (rdr.Read()
   {
      /* code to build Client objects and add to list */
   }

   return returnList;
}

Хорошо, так что это может выглядеть нормально, но в зависимости от ваших бизнес-правил это может быть проблемой. Конечно, вы никогда не будете бросать нулевую ссылку, но, может быть, ваша таблица User никогда не должна быть пустой? Вы хотите, чтобы ваше приложение вращалось на месте, генерируя запросы поддержки от пользователей, говорящих "это просто пустой экран", или вы хотите создать исключение, которое может быть зарегистрировано где-нибудь и быстро повысить оповещение? Не забудьте проверить, что вы делаете, а также "обрабатывать" исключения. Это одна из причин, почему некоторые из них не хотят принимать нули из наших языков... это облегчает поиск ошибок, даже если это может вызвать некоторые новые.

Помните: обрабатывайте исключения, не скрывайте их.

Ответ 12

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

В случаях, когда такой объект нельзя построить, поскольку просто невозможно реализовать его обязательные операции, вы можете полагаться на пустые коллекции, например, в Map -Повторить запросы.

Другим решением является Функциональный тип опций, который представляет собой коллекцию с нулевым или одним элементом. Таким образом, у вас будет возможность пропустить операцию, которая не может быть выполнена.

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

Ответ 13

Решение простого кода

Вы всегда можете создать структуру, которая помогает улавливать ошибки нулевой ссылки раньше, отмечая переменные, свойства и параметры как "не обнуляемые". Здесь пример, концептуально смоделированный после использования Nullable<T>:

[System.Diagnostics.DebuggerNonUserCode]
public struct NotNull<T> where T : class
{
    private T _value;

    public T Value
    {
        get
        {
            if (_value == null)
            {
                throw new Exception("null value not allowed");
            }

            return _value;
        }
        set
        {
            if (value == null)
            {
                throw new Exception("null value not allowed.");
            }

            _value = value;
        }
    }

    public static implicit operator T(NotNull<T> notNullValue)
    {
        return notNullValue.Value;
    }

    public static implicit operator NotNull<T>(T value)
    {
        return new NotNull<T> { Value = value };
    }
}

Вы бы очень похожи на то, как вы использовали бы Nullable<T>, кроме как с целью достижения совершенно противоположного - не разрешать null. Вот несколько примеров:

NotNull<Person> person = null; // throws exception
NotNull<Person> person = new Person(); // OK
NotNull<Person> person = GetPerson(); // throws exception if GetPerson() returns null

NotNull<T> неявно отбрасывается в и от T, поэтому вы можете использовать его в любом месте, где это необходимо. Например, вы можете передать объект Person методу, который принимает значение NotNull<Person>:

Person person = new Person { Name = "John" };
WriteName(person);

public static void WriteName(NotNull<Person> person)
{
    Console.WriteLine(person.Value.Name);
}

Как вы можете видеть выше, как с помощью nullable, вы получите доступ к базовому значению с помощью свойства Value. Кроме того, вы можете использовать явный или неявный листинг, вы можете увидеть пример с возвращаемым значением ниже:

Person person = GetPerson();

public static NotNull<Person> GetPerson()
{
    return new Person { Name = "John" };
}

Или вы даже можете использовать его, когда метод просто возвращает T (в данном случае Person), выполнив бросок. Например, следующий код будет просто похож на приведенный выше код:

Person person = (NotNull<Person>)GetPerson();

public static Person GetPerson()
{
    return new Person { Name = "John" };
}

Объединить с расширением

Объедините NotNull<T> с методом расширения, и вы можете охватить еще больше ситуаций. Вот пример того, как выглядит метод расширения:

[System.Diagnostics.DebuggerNonUserCode]
public static class NotNullExtension
{
    public static T NotNull<T>(this T @this) where T : class
    {
        if (@this == null)
        {
            throw new Exception("null value not allowed");
        }

        return @this;
    }
}

И вот пример того, как он может быть использован:

var person = GetPerson().NotNull();

GitHub

Для справки я сделал код выше, доступный на GitHub, вы можете найти его по адресу:

https://github.com/luisperezphd/NotNull

Ответ 14

Инструменты, которые могут помочь

Есть также несколько библиотек, которые могут помочь. Контракты Microsoft Code были упомянуты выше.

Некоторые другие инструменты включают Resharper, который может предоставить вам предупреждения во время написания кода, особенно если вы используете их атрибут: NotNullAttribute

Там также PostSharp, который позволит вам просто использовать такие атрибуты:

public void DoSometing([NotNull] obj)

Сделав это и сделав PostSharp частью вашего процесса сборки, obj будет проверяться на нуль во время выполнения. Смотрите: Ошибка проверки PostSharp

В проекте Fody code-weaving есть плагин для реализации нулевых защит.

Ответ 15

NullReferenceException может быть показано, когда метод не найден в сборке, для ex m0 = mi.GetType(). GetMethod ( "TellChildToBeQuiet" ), где сборка является SportsMiniCar, mi является экземпляром MiniVan и TellChildToBeQuiet - это метод в сборке. Мы можем избежать этого, увидев, что эта сборка Version 2.0.0.0, содержащая указанный выше метод, помещается в GAC. Пример: Вызов методов с параметрами: `

enter code here

using System;
using System.Rwflection;
using System.IO;
using Carlibraries;
namespace LateBinding
{
public class program
{
   static void Main(syring[] args)
   {
         Assembly a=null;
         try
         {
              a=Assembly.Load("Carlibraries");
         }
         catch(FileNotFoundException e)
         {
               Console.Writeline(e.Message);
               Console.ReadLine();
               return;
         }
         Type miniVan=a.GetType("Carlibraries.MiniVan");
         MiniVan mi=new MiniVan();
         mi.TellChildToBeQuiet("sonu",4);
         Console.ReadLine();
       }
   }
   }

Помните обновление сборки MiniSportsCar с помощью TellChildToBeQuiet (строка ChildName, int count)