избегать исключений нулевой ссылки - PullRequest
21 голосов
/ 22 декабря 2009

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

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

Ответы [ 15 ]

30 голосов
/ 01 февраля 2010

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

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

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

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

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

Contract.Requires(customer != null);

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

ИССЛЕДОВАНИЯ ПО РАСПРОСТРАНЕНИЮ НУЛЕВОЙ СПРАВОЧНОЙ ОШИБКИ

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

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

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

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

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

Это вероятный источник информации для сотрудников Microsoft, знакомых с данным исследованием.Эта статья доступна на сайте Spec #.

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

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

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

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

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

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

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

21 голосов
/ 22 декабря 2009

В дополнение к вышесказанному (нулевые объекты, пустые коллекции), есть несколько общих методов, а именно: Приобретение ресурсов - инициализация (RAII) из C ++ и Проектирование по контракту из Eiffel.Они сводятся к следующему:

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

Я видел много кода, который выглядит следующим образом:

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

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

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

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

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

7 голосов
/ 22 декабря 2009

Вы не.

Или, скорее, нет ничего особенного, чтобы попытаться «предотвратить» NRE в C #. По большей части NRE - это просто логическая ошибка. Вы можете отключить их на границах интерфейса, проверив параметры и имея много кода, подобного

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

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

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

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

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

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

5 голосов
/ 22 декабря 2009

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

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

5 голосов
/ 22 декабря 2009

Использование Шаблоны нулевых объектов является ключевым здесь.

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

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

4 голосов
/ 22 декабря 2009

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

if(stringValue == "") {}

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

if(string.IsNullOrEmpty(stringValue){}

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

4 голосов
/ 22 декабря 2009

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

3 голосов
/ 22 декабря 2009

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

2 голосов
/ 22 декабря 2009

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

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

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 никогда не должна быть пустой? Вы хотите, чтобы ваше приложение вращалось на месте, генерируя звонки в службу поддержки от пользователей, говорящих «это просто пустой экран», или вы хотите вызвать исключение, которое могло бы где-нибудь войти в систему, и быстро выдать предупреждение? Не забудьте проверить, что вы делаете, а также «обрабатывать» исключения. Это одна из причин, почему некоторые не любят выводить нули из наших языков ... это облегчает поиск ошибок, даже если они могут вызывать некоторые новые.

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

2 голосов
/ 22 декабря 2009

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

* 1003 Е.Г. *

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

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

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

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

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

...