Исключения и специальные возвращаемые значения - PullRequest
8 голосов
/ 23 марта 2009

Какая практика лучше программировать?

Я не говорю о полной исключительности. Это больше для таких вещей, как:

list<T>.Find, где вместо ValueNotFound (пример) вы получите default(T) или ваше значение.

или

list<T>.IndexOf, где вы получите -1 или правильный индекс.

Ответы [ 15 ]

16 голосов
/ 23 марта 2009

Ну, ответ зависит.

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

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

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

11 голосов
/ 23 марта 2009

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

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

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

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

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

6 голосов
/ 23 марта 2009

Практическое правило - использовать исключения только тогда, когда происходит что-то, что «не должно происходить».

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

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

3 голосов
/ 23 марта 2009

Что бы вы предпочли использовать?

A:

item = list.Find(x);

B

If (list.Contains(x))
    item = list.Find(x);
else
    item = null;

C

try {
   item = list.Find(x);
}
catch {
     item = null;
}

Я готов поспорить, что ответ А. Поэтому в этом случае верный выбор по умолчанию (T) - правильный выбор.

3 голосов
/ 23 марта 2009

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

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

Пример взаимодействия между языковыми функциями и дизайном библиотеки, часть вторая

Я добавлю, что я думаю, что многие ответы на этот вопрос плохие (я проголосовал за многие из моих когорт). Особенно плохи API по линии

if (ItWillSucceed(...)) {
    DoIt(...)
}

, которые создают всевозможные проблемы (подробности см. В моем блоге).

3 голосов
/ 23 марта 2009

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

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

2 голосов
/ 23 марта 2009

Лучшие языки позволяют вам делать то, что соответствует вашим потребностям. Как в Smalltalk-80:

Следующее вызовет исключение, если для идентификатора нет пользователя:

user := userDictionary at: id

Этот будет оценивать данный Блок, который является функцией высокого уровня:

user := userDictionary at: id ifAbsent: [
    "no such id, let's return the user named Anonymous"
    users detect: [ :each | each name = 'Anonymous' ] ]

Обратите внимание, что фактический метод по адресу: ifAbsent :.

1 голос
/ 23 марта 2009

Как и во многих вопросах, связанных с программированием, все зависит ...

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

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

Затем сохраните исключения для общих сбоев, которых нельзя избежать, например, сбой соединения с БД, сбой оптимистической транзакции, сбой записи на диск.

Эти исключения обычно не нужно перехватывать, пока они не достигнут «пользователя». И в результате пользователь должен будет повторить попытку.

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

Здесь также полезно наложение приложений. Итак, давайте возьмем пример перевода денег с одного счета на другой:

transferInternal( int account_id_1, int account_id_2, double amount )
{
   // This is an internal function we require the client to provide us with
   // valid arguments only. (No error handling done here.)
   REQUIRE( accountExists( account_id_1 ) ); // Design by contract argument checks.
   REQUIRE( accountExists( account_id_2 ) );
   REQUIRE( balance( account_id_1 ) > amount );
   ... do the actual transfer work
}

string transfer( int account_id_1, int account_id_2, double amount )
{
   DB.start(); // start transaction
   string msg;
   if ( !checkAccount( account_id_1, msg ) ) return msg; // common checking code used from multiple operations.
   if ( !checkAccount( account_id_2, msg ) ) return msg;
   if ( !checkBalance( account_id_1, amount ) ) return msg;
   transferInternal( account_id_1, account_id_2, amount );
   DB.commit(); // This could fail with an exception if someone else changed the balance while this transaction was active. (Very unlikely but possible)
   return "cash transfer ok";
}
1 голос
/ 23 марта 2009

Более эффективный C # Билл Вагнер дал отличную рекомендацию в отношении исключений. Смысл в том, чтобы идти вперед и генерировать исключение при выполнении операции, просто убедитесь, что вы предоставили хуки, чтобы проверить, будет ли значение генерировать исключение.

Например

// if this throws an exception
list.GetByName("Frank") // throws NotFound exception
// provide a method to test 
list.TryGetByName("Frank")  // returns false

таким образом, вы можете отказаться от исключения, написав что-то вроде

MyItem item;
if (list.TryGetByName("Frank"))
  item = list.GetByName("Frank");
1 голос
/ 23 марта 2009

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

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

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