Что определяет надежный код?

42

Мой профессор продолжает ссылаться на этот пример Java, когда говорит о «надежном» коде:

if (var == true) {
    ...
} else if (var == false) {
    ...
} else {
    ...
}

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

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

«Отсутствие такой вещи как ошибка» также кажется смешным; даже приложения Google показывают ошибки непосредственно пользователю вместо того, чтобы молча глотать их или как-то считать их действительным состоянием. И это хорошо - мне нравится знать, когда что-то идет не так. И кажется, что заявление о том, что приложение никогда не будет иметь ошибок, вполне оправдано.

Итак, каково фактическое определение «надежного кода»?

Lotus Notes
источник
4
Это будет иметь место только в не-типизированном языке. В строго типизированном языке переменная типа boolean (не целое число, выдаваемое за логическое значение), может быть только true или false, третьего варианта нет ...
Marjan Venema
23
спросите его, как бы вы протестировали покрытие в 3-м случае, потому что надежный код должен обязательно требовать тестирования, и если вам не удастся протестировать 3-й случай, вы не сможете найти никаких ошибок, которые могли бы там скрыться.
gbjbaanb
2
@Marjan - на языке, не являющемся строго типизированным, скорее всего, просто пишут: if (var) {} else {}
kevin cline
2
Я не знаю ни одного языка, где бы х и! Х могли быть правдой. Обратите внимание, что я не предложил "if (x == true) ..."; Я ненавижу такие сравнения.
Кевин Клайн

Ответы:

33

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

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

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

Если вы думаете, что здесь есть одно общепринятое определение «робастный», удачи. Там могут быть некоторые синонимы, такие как бомба или идиот. Duct Tape Programmer был бы примером кого-то, кто обычно пишет надежный код, по крайней мере, в моем понимании терминов.

JB King
источник
13
Если бы это было логическое значение Nullable, то и Java, и c # выдавали бы, поэтому сначала следует проверить null.
Эсбен Сков Педерсен
Кажется, нет единого мнения о том, что такое кошка или собака.
Тулаинс Кордова
11

Ради моей дискуссии у Bool может быть 2 состояния: True или False. Все остальное не соответствует спецификации языка программирования. Если ваша цепочка инструментов не соответствует ее спецификации, вы будете заняты независимо от того, что вы делаете. Если разработчик создал тип Bool с более чем двумя состояниями, это будет последнее, что он когда-либо сделает на моей базе кода.

Вариант А.

if (var == true) {
    ...
} else if (var == false) {
    ...
} else {
    ...
}

Вариант Б

if (var == true) {
    ...
} else {
    ...
}

Я утверждаю, что вариант B является более надежным .....

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

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

Вариант B тривиален для тестирования.

Следующая проблема, задайте вам, профессор, этот вопрос: «Что вы хотите, чтобы я сделал с этим, если логическое значение не является ни истинным, ни ложным?» Это должно привести к очень интересной дискуссии .....

В большинстве случаев дамп ядра является оценочным, в худшем случае он раздражает пользователя или стоит больших денег. Что, если, скажем, модуль представляет собой систему расчета повторного входа космического корабля в реальном времени? Любой ответ, каким бы неточным он ни был, не может быть хуже, чем прерывание, которое убьет пользователей. Так что делать, если вы знаете, что ответ может быть неправильным, пойти на 50/50 или прервать и перейти к 100% -ой ошибке. Если бы я был членом экипажа, я бы взял 50/50.

Вариант А убивает меня Вариант Б дает мне равные шансы на выживание.

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

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

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

if (var != true || var != false) {
    errorReport("Hell just froze over, var must be true or false")
}
......
if (var == true){
 .... 
} else {
 .... 
}

Дальнейшее чтение - аппарат Therac-25 Xray, сбой Ariane 5 Rocket и другие (в Link много неработающих ссылок, но достаточно информации, чтобы помочь Google)

mattnz
источник
1
«... неожиданные ошибки. Их обычно тривиально легко обнаружить, если подумать о них», но когда вы думаете о них, они больше не являются неожиданными.
gbjbaanb
7
Есть некоторый вопрос относительно того, должен ли ваш код if (var != true || var != false) {быть &&вместо этого.
1
Я легко могу думать о буле, который не является ни истинным, ни ложным, но все же неожиданным. В случае, если вы говорите, что bool не может быть чем-то еще, если я проверяю, является ли буква символом цифры, а затем преобразовываю ее в целочисленное значение, я могу легко представить, что это целочисленное значение меньше 0 или больше 9, но все равно неожиданный.
gnasher729
1
Нулевые логические значения поддерживаются в Java и C # и имеют реальное приложение. Рассмотрим базу данных, содержащую список людей. Через некоторое время вы решаете, что вам нужно поле пола (isMale). Нуль означает «никогда не спрашивал, поэтому не знаю»; истина означает мужчину, а ложь означает женщину. (ОК, трансгендерные опущены для простоты ...).
Кивирон
@kiwiron: не будет ли лучше использовать тип перечисления: "Мужской", "Женский", "Не спрашивать". Перечисления лучше - могут быть расширены, когда возникнет такая необходимость (например, у вас, например, асексуал, гермафродит, «отказался отвечать»).
Mattnz
9

На самом деле ваш код не более надежный, но МЕНЬШЕ надежный. Финал else- просто мертвый код, который вы не можете протестировать.

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

mouviciel
источник
Я не понимаю, в космических кораблях мертвый код запрещен? т.е. ты не можешь написать последнее? так как вы не можете проверить это, вы не можете вставить его? но тогда что это значит: «Если SEU активирует часть надежного кода, (неожиданное) поведение остается под контролем».
Потрепанный
5
Да, в критически важных для космоса программных тестах охват должен быть 100%, и, следовательно, недоступный код (он же мертвый код) запрещен.
mouviciel
7

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

Я согласен, что пример кода профессора глуп, но не так глуп, как мой.

// Assign 3 to x
var x = 3;
x = 3;   // again, just for sure
while (x < 3 or x > 3) { x = 3; }  // being robust
if (x != 3) { ... }  // this got to be an error!
Дэвид Андерссон
источник
1
Последнее, если, конечно, сработает, это не потребует действительно много усилий. Любой опытный программист на Си видел, как значения внезапно меняются. Конечно, логично, что в контролируемой однопоточной среде этого никогда не должно происходить. В реальной жизни код внутри if в конечном итоге произойдет. Если в этом нет ничего полезного, то вы можете не кодировать! (У меня был забавный опыт во время конкретной разработки программного обеспечения, когда я выдвинул исключение с ругательствами на случай, если что-то невозможное произошло ... угадайте, что случилось?).
Алекс
2
Правдивая история:boolean x = something(); if (x) { x = True // make sure it's really true, ... }
Андрес Ф.
6

Не существует согласованного определения Robust Code , так как для многих вещей в программировании оно более или менее субъективно ...

Пример, который дает ваш профессор, зависит от языка:

  • В Haskell, a Booleanможет быть или, Trueили Falseнет третьего варианта
  • В C ++ a boolможет быть true, falseили (к сожалению) происходить от какого-то сомнительного броска, который помещает его в неизвестный случай ... Это не должно происходить, но может произойти в результате предыдущей ошибки.

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

В случае с университетом вы могли бы даже увеличить его, приняв стратегию «Дизайн по контракту»:

  • Установить инварианты для классов (например, sizeэто количество элементов в dataсписке)
  • Установите предварительные условия и постусловия для каждой функции (например, эта функция может быть вызвана только с aменьшим, чем 10)
  • Проверьте каждый из них в точках входа и выхода каждой из ваших функций

Пример:

class List:
  def __init__(self, items):
    self.__size = len(items)
    self.__data = items

  def __invariant(self):
    assert self.__size == len(self.__data)

  def size(self):
    self.__invariant()

    return self.__size

  def at(self, index):
    """index should be in [0,size)"""
    self.__invariant()
    assert index >= 0 and index < self.__size

    return self.__data[index]

  def pushback(self, item):
    """the subsequent list is one item longer
       the item can be retrieved by self.at(self.size()-1)"""
    self.__invariant()

    self.__data.append(item)
    self.__size += 1

    self.__invariant()
    assert self.at(self.size()-1) == item
Матье М.
источник
Но профессор специально сказал, что это Java, и конкретно НЕ сказал, что это за тип var. Если это логическое значение, оно может быть истинным, ложным или нулевым. Если что-то еще, оно может быть неравным как истинным, так и ложным. Да, пересечение между крепким, оборонительным и параноидальным.
Энди Кэнфилд
2
В C, C ++ и Objective-C, bool может иметь неопределенное значение, как и любой другой тип, но любое присваивание будет устанавливать его в true или false и ничего больше. Например: bool b = 0; б ++; б ++; установит b в true.
gnasher729
2

Подход вашего профессора полностью ошибочен.

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

Spec: If var is false then the function does "this", otherwise it does "that". 

Затем вы пишете функцию:

if (var == false) dothis; else dothat; 

и код соответствует спецификации. Итак, ваш профессор говорит: а что если var == 42? Посмотрите на спецификацию: там написано, что функция должна делать "это". Посмотрите на код: функция делает «это». Функция соответствует спецификации.

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

gnasher729
источник
1

Я согласен с утверждением @ gnasher729: подход вашего профессора полностью ошибочен.

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

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

Вектор
источник
0

Надежный код - это просто код, который хорошо обрабатывает сбои. Ни больше ни меньше.

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

Спарки
источник
0

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

for (int i = 0; i != sequence.length(); ++i) {
    // do something with sequence[i]
}

Или:

for (int i = 0; i < sequence.length(); ++i) {
    // do something with sequence[i]
}

(Если у вас возникли проблемы с различием, проверьте тест цикла: первый использует !=, второй использует <).

Теперь при большинстве обстоятельств оба цикла будут вести себя одинаково. Тем не менее, первое (по сравнению с !=) делает предположение, что iбудет увеличиваться только один раз за итерацию. Если оно пропускает значение, sequence.length()цикл может продолжаться за пределами последовательности и вызывать ошибку.

Таким образом, вы можете аргументировать, что вторая реализация является более надежной: она не зависит от предположений о том, изменяется ли тело цикла i(примечание: на самом деле оно все еще делает предположение, что iоно никогда не бывает отрицательным).

Чтобы мотивировать, почему вы не захотите делать такое предположение, представьте, что цикл сканирует строку и выполняет некоторую обработку текста. Вы пишете цикл, и все в порядке. Теперь ваши требования меняются, и вы решаете, что вам нужно поддерживать escape-символы в текстовой строке, поэтому вы изменяете тело цикла таким образом, чтобы, если он обнаруживает escape-символ (скажем, обратный слеш), он постепенно увеличивал iпропуск символа, следующий сразу за escape. Теперь в первом цикле есть ошибка, потому что если последний символ текста имеет обратную косую черту, тело цикла будет увеличиваться, iи цикл продолжится после конца последовательности.

Джон Варфоломей
источник
-1

Я лично описываю код как «надежный», который имеет следующие важные атрибуты:

  1. Если моя мама сидит перед ней и работает с ней, она не может сломать систему

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

Саид Нямати
источник