Это защитное Программирование кода или плохая практика? [закрытый]

у меня есть эта дискуссия с моим коллегой об этом фрагменте кода:

var y = null;
if (x.parent != null)
    y = x.parent.somefield;

моя точка зрения заключается в том, что в том месте, где код x.parent не должно быть null. И когда он равен нулю, у нас есть серьезная проблема, и я хочу это знать! Следовательно, проверка null не должна быть там и позволить исключению потока вниз произойти.

мой коллега говорит, что это защитное Программирование. И проверка null гарантирует, что код не нарушит приложение.

мой вопрос, это защитное Программирование? Или плохая практика?

Примечание: дело не в том, кто прав. Я пытаюсь извлечь уроки из этого примера.

5 ответов


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

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

Я вижу две разные вещи, о которых стоит подумать.

откуда берутся данные?

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

  • противоположный случай был бы, если бы это данные, введенные пользователем, или чтение из файла, или из URL-адреса, что-нибудь внешнее вообще говоря. Поскольку вы не можете контролировать, с чем имеет дело программа: обязательно проверьте ее как можно тщательнее, прежде чем использовать ее каким-либо образом, поскольку вы подвергаетесь поддельной/отсутствующей/неполной/неправильной/вредоносной информации, которая может вызвать проблемы по пути.

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

Что делать с проверкой?

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

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

  • исключение всегда является выбором, когда" невозможно " действительно произошло. Это в тех случаях, когда вы не можете предоставить разумный ответ для продолжения с некоторыми изменениями или отменить только текущий процесс, это может быть связано к ошибке или плохому входу где-то, но важно то, что вы хотите знать его и иметь все подробности о нем, поэтому лучший способ-сделать так, чтобы он взорвался как можно громче, чтобы исключение пузырилось и достигало глобального обработчика, который прерывает все, сохраняет в файл журнала/DB/whatever, отправляет отчет о сбоях вам и находит способ возобновить выполнение, если это возможно или безопасно. По крайней мере, если ваше приложение аварийно завершает работу, сделайте это самым изящным образом и оставьте следы для дальнейший анализ.

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


похоже, ваш коллега неправильно понимает "оборонительное программирование" и / или исключения.

Защитное Программирование

оборонительное программирование - это защита от определенных видов ошибок.

в этом случае x.parent == null - Это ошибка, потому что ваш метод должен использовать x.parent.SomeField. И если parent были null, то значение SomeField явно неверный. Любое вычисление или задача, выполняемая с использованием недопустимого значения, приведет к ошибкам и непредсказуемые результаты.

поэтому вам нужно защитить от этой возможности. Очень хороший способ защитить, бросив NullPointerException если вы обнаружите, что x.parent == null. Исключение остановит вас от получения недопустимого значения из SomeField. Это остановит вас делать какие-либо вычисления или выполнять какие-либо задачи с недопустимым значением. И он будет прерывать всю текущую работу до тех пор, пока ошибка не будет подходящей разрешить.

Примечание исключение не ошибка; недопустимое значение в parent что это фактический ошибка. Исключение, по сути, является механизмом защиты. Исключение составляют техника оборонительного программирования, их не следует избегать.

поскольку C# уже выдает исключение, вам на самом деле не нужно делать что-нибудь. На самом деле получается, что усилия вашего коллеги "во имя оборонительного программирования", это на самом деле отмена встроенное оборонительное Программирование, предоставляемое языком.

исключения

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

ваш коллега говорит: "нулевая проверка гарантирует, что код не ломается приложения". Это говорит о том, что он считает, что исключения нарушают приложения. Они вообще не "ломают" целое приложение.

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

исключения, однако, нарушат (прервут) текущую операцию. И это то, что они должны do. Потому что, если вы кодируете метод под названием DoSomething которых звонки DoStep1; an на DoStep1 означает, что DoSomething не может сделать свою работу правильно. Нет смысла продолжать звонить DoStep2.

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

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

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

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

  • запись в файл журнала.
  • отправить информацию стека вызовов для устранения неполадок.
  • игнорировать определенные классы исключений. (Напр. Abort может означать, что вам даже не нужно говорить пользователю, возможно, потому, что вы ранее отображали сообщение.)
  • etc.

Обработка Исключений

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

Примечание: обработчики исключений сервера двух различных целей: Сначала они предоставляют вам место для выполнения очистки (или откат код) именно потому, что там был ошибка/исключение. Во-вторых, они предоставляют место для устранения ошибки и Ласточка исключение. NB: в первом случае очень важно, чтобы исключение было повторно поднято / брошено, потому что оно имеет не было разрешить.

в комментарии о создании исключения и его обработке вы сказали:"я хотел это сделать, но мне сказали создает код обработки исключений везде."

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

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

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

так что не "не бросать исключения " во избежание чрезмерно обработка исключений. Я согласен, что чрезмерная обработка исключений плоха (см. рассмотрение дизайна выше). Но гораздо хуже!--28-->не откат когда вы должны, потому что вы даже не знаю произошла ошибка.


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

очевидно, мы понятия не имеем, что будет дальше в вашем коде, но так как вы не включили else предложение, я могу только предположить, что ваш код просто продолжается даже в том случае, если x.parent is на самом деле null.

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

тогда возникает вопрос - что более приемлемо в контексте проблемы, которую вы пытаетесь решить ("домен"), и это зависит от того, что вы собираетесь делать с на.

  • если y будучи null после этого кода не проблема (допустим, вы делаете оборонительную проверку позже для y!=null) тогда все в порядке-хотя лично мне не нравится этот стиль-вы в конечном итоге оборонительно проверяете каждую ссылку, потому что вы никогда не можете быть уверены, что вы нулевая ссылка от сбоя...

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


короче говоря, я бы сказал, что это не оборонительное Программирование. Я согласен с теми, кто считает, что этот код скрывает системную ошибку, а не разоблачает и исправляет. Этот код нарушает принцип "fail fast".

Это верно, если и только если x.родитель является обязательным ненулевым свойством (что кажется очевидным из контекста.) однако, если x.родитель является необязательным свойством (т. е. может разумно иметь нулевое значение), тогда этот код может быть allright в зависимости от вашего бизнеса логика выражена.

Я всегда рассматриваю использование пустых значений (0, "", пустые объекты) вместо нулей, которые требуют тонны нерелевантных операторов if.


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

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

public string Divide(int numA, int numB, out double division)
{
    division = 0;
    if (numB == 0)
        return Log.Write(Level.Error, "Divide - numB-[{0}] not valid.", numB);

    division = numA / numB;
    return string.Empty;
}

тогда я использую его:

private string Process(int numA, int numB)
{
    double division = 0;
    string result = string.Empty;
    if (!string.IsNullOrEmpty(result = Divide(numA, numB, out divide))
        return result;

    return SendResult(division);
}

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