Когда добавлять предварительное условие, а когда (только) генерировать исключение?

Я узнаю о предпосылках и когда их использовать. Мне сказали, что предварительное условие

@pre fileName must be the name of a valid file

не подходит в следующем коде:

/**
Creates a new FileReader, given the name of file to read from.
@param fileName- the name of file to read from
@throw FileNotFoundException - if the named file does not exist,
is a directory rather than a regular file, or for some other reason cannot
be opened for reading.
*/
public FileReader readFile(String fileName) throws FileNotFoundException {
. . .
}//readFile

Почему это?

Изменить: еще один пример

Мы предполагаем, что следующее, в качестве примера, сделано "правильным" способом. Обратите внимание на IllegalArgumentException и предварительное условие. Обратите внимание, как правильно определено поведение и как выполняется объявление throws, даже если задано предварительное условие. Самое главное, обратите внимание, что он не содержит предварительное условие для исключения NullPointerException. Еще раз, почему не так?

/**
* @param start the beginning of the period
* @param end the end of the period; must not precede start
* @pre start <= end
* @post The time span of the returned period is positive.
* @throws IllegalArgumentException if start is after end
* @throws NullPointerException if start or end is null
*/
public Period(Date start, Date end) f

Эти примеры избегают использования дополнительных предварительных условий? Можно утверждать, что если мы избегаем предварительных условий, то зачем их вообще иметь? То есть, почему бы не заменить все предварительные условия объявлениями @throws (если их избегать, то, что здесь делается)?

3 ответа

Решение

Итак, вот что я узнал:

Фон

Основываясь на следующих принципах, как описано в книге Бертрана Мейера " Создание объектно-ориентированного программного обеспечения":

"Принцип не избыточности. Ни при каких обстоятельствах тело процедуры не должно проверять предварительные условия процедуры". - Бертран Мейер

"Правило доступности предварительных условий Каждая функция, появляющаяся в предварительном условии подпрограммы, должна быть доступна каждому клиенту, которому эта подпрограмма доступна". - Бертран Мейер

эти два пункта отвечают на этот вопрос:

  1. Чтобы предварительные условия были полезны, клиент (пользователь метода) должен иметь возможность их проверить.
  2. Сервер никогда не должен проверять предварительное условие, потому что это усложнит систему. Хотя для этого тестирования включены утверждения при отладке системы.

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

"Центральным в разработке по контракту является идея, выраженная в виде принципа отсутствия резервирования, что для любого условия согласованности, которое может поставить под угрозу надлежащее функционирование рутины, вы должны назначить выполнение этого условия только одному из двух партнеров по контракту.? В каждом случае у вас есть две возможности: • Либо вы назначаете ответственность клиентам, и в этом случае условие будет отображаться в качестве предварительного условия процедуры. • Или вы назначаете поставщика, и в этом случае условие появляется в условной инструкции формы if условие then... или эквивалентной управляющей структуры в теле процедуры.

Мы можем назвать первое отношение требовательным, а второе - терпимым ". - Бертран Мейер

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

ответы

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

Что касается нулевого указателя; это показывает, что ответственность за нулевой указатель назначается серверу. То есть, используя "терпимое отношение", а не "требовательное отношение". Это совершенно нормально. Если кто-то решит реализовать требовательное отношение, он удалит объявление throws (но что более важно; не проверяет его) и добавит объявление предварительного условия (и, возможно, утверждение).

Википедия определяет предусловие как:

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

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

В вашем примере, эффект метода, если имя файла недопустимо , определяется (он должен бросить FileNotFoundException).

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

Редактировать:

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

Конечно, но тогда это больше не является предварительным условием, определенным Хоаром. Формально говоря, что метод имеет предварительное условие pre и постусловие post означает, что для каждого выполнения метода, запущенного в состоянии prestate и закончился в состоянии poststate

pre(prestate) ==> post(poststate)

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

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

Исключение не совсем недостижимо, правда?

Если предложение throws является частью postcondtion, у вас есть что-то вроде:

pre(prestate) ==> (pre(prestate) and return_valid) or (not pre(prestate) and throws_ exception)

что логически эквивалентно

pre(prestate) ==> (pre(prestate) and return_valid)

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

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

Нет; пункт о бросках является частью договора, и, как таковой, не имеет значения, если договор расторгнут.

Конечно, можно определить, что условия @throws должны выполняться независимо от предварительного условия, но полезно ли это? Рассматривать:

@pre foo != null
@throws IllegalStateException if foo.active

Должно быть выброшено исключение, если foo является null? В классическом определении это не определено, потому что мы предполагаем, что никто не пройдет null за foo, В вашем определении мы должны явно повторить это в каждом предложении throws:

@pre foo != null
@throws NullPointerException if foo == null
@throws IllegalStateException if foo != null && foo.active

Если я не знаю, разумный программист собирается пройти null к этому методу, почему я должен быть вынужден указать этот случай в моей спецификации? Какая польза от описания поведения, которое бесполезно для звонящего? (Если вызывающий хочет узнать, имеет ли значение foo значение null, он может проверить это сам, а не вызывать наш метод и перехватывать исключение NullPointerException!).

Я думаю, что дизайн по контрактной идее (я сам еще не использую его) и предварительные / последующие условия предназначены для гарантии особых условий, входящих и исходящих из метода. В частности, компилятор (в данном случае, теоретически, поскольку Java не имеет этого встроенного) должен был бы иметь возможность проверить условия контракта. В случае вашего условия файла это не может быть выполнено, так как файл является внешним ресурсом, класс может перемещаться, и тот же файл может отсутствовать и т. Д. Как компилятор (или препроцессор) может гарантировать такой контракт?

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

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

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

Другие вопросы по тегам