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

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

@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 ]

4 голосов
/ 19 апреля 2011

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

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

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

В вашем примере, эффект метода, если имя файла неверно , определено (оно должно выдать 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 нулевым, он может проверить его сам, а не вызывать наш метод и перехватывать исключение NullPointerException!).

3 голосов
/ 20 апреля 2011

Хорошо, вот что я узнал:

Фон

На основе следующих принципов, как описано в книге Бертран Мейер Конструкция объектно-ориентированного программного обеспечения :

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

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

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

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

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

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

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

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

Ответы

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

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

2 голосов
/ 20 апреля 2011

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...