Например что использовать, когда нужно создавать колонку в таблице только в том случае, если такой колонки в ней нет?
Я могу, как написать код, который будет проверять, существует ли колонка, и лиш
потом добавлять без ошибок, так могу и без проверки пытаться добавить колонку, обернув метод в try-catch (если есть — перехватится исключение; если нет — колонка добавится).
Результат работы будет одинаковым.
И таких примеров можно привести массу, например можно проверять файлы на существование, и только потом делать копирование, а можно перехватывать исключения.
Какой метод более грамотный, или правильный?
Ответы
Ответ 1
Исключения позволяют сделать код чище и понятнее, поскольку с их помощью можно разделит
выполнение действий и обработку ошибок. В книге Мартина «Чистый код» этот аспект описан самым первым.
При этом, по моему опыту, надо именно разделять код: выносить блок try/catch в отдельны
метод. Программист, который будет разбираться с вашей программой, скажет вам «спасибо». Эта конструкция весьма громоздка, даже в простой форме try/finally, и в середине большого метода озадачит кого угодно.
Второй плюс исключений в том, что они позволяют передавать дополнительную информацию
Функция atoi из C ничего не могла сказать о том, почему именно не удалось конвертировать строку в целое число.
int result;
result = atoi("123"); /* в result 123 */
. . .
result = atoi("foo"); /* что в result? */
В таких языках, как Java и C# вы можете добавить в свой класс исключений необходимы
свойства, сочетая какие-нибудь коды ошибок с контекстами вызова и ещё чем-нибудь. Пример:
. . .
catch (SqlException e)
{
Console.WriteLine("Ошибка '{2}' в строке {0} процедуры {1}", e.LineNumber, e.Procedure, e.Message);
}
. . .
Разработчики, которые давно и прочно перешли на исключения, делают свой код ещё чище, не возвращая никаких кодов ошибок, в частности, пресловутого null:
// Что будет, если в хранилище нет пользователя с указанным userid?
// Вернёт null или сгенерирует исключение?
User user = userRepository.GetById(userId);
В настоящее время считается, что правильнее создавать исключение (за аргументам
снова отсылаю к книге «Чистый код»). Если метод используется для проверки наличия пользователя, его рекомендуют переделать в форму TryX. Она неуклюжа, но уже привычна, по крайней мере, для программистов .NET:
User user;
if (userRepository.TryGetById(userId, out user))
{
. . .
}
Что более ценно, она однозначна: смотря на код, вы не думаете: «а что, если такого пользователя нет?»
Теперь посмотрим на ситуацию с другой стороны — а когда не надо использовать исключения
На мой взгляд, тогда, когда они затрудняют понимание кода. Если ситуация не исключительная, тогда в тексте программы должна быть обычная проверка.
Например, приложение для нового документа генерирует имена Untitled.foo, Untitled1.foo, Untitled2.foo и т.д.
Та ситуация, что файл с таким именем уже существует, является вполне обычной, н
исключительной, поэтому и реализовать код корректнее с помощью обычной проверки:
public string GetNewDocumentName(string prefix)
{
var filename = prefix + ".foo";
if (!File.Exists(filename))
return filename;
int suffix = 0;
do
{
filename = prefix + (++suffix).ToString() + ".foo";
} while (File.Exists(filename));
return filename;
}
Этот код не только быстрее, чем аналогичный с использованием исключений, но, чт
важнее, понятнее другим программистам, потому что неявно передаёт им дополнительную информацию: эта штука будет случаться регулярно, и мы к этому готовы.
А вот, например, невероятная ситуация, что в папке скопилось 2 миллиарда untitled-файлов — несомненное исключение.
public string GetNewDocumentName(string prefix)
{
var filename = prefix + ".foo";
if (!File.Exists(filename))
return filename;
int suffix = 0;
do
{
if (suffix == int.MaxValue)
throw new InvalidOperationException("You're crazy!");
filename = prefix + (++suffix).ToString() + ".foo";
} while (File.Exists(filename));
return filename;
}
Такой код выглядит более запутанным. К счастью, мы можем часть проверок возложить на компилятор C#:
public string GetNewDocumentName(string prefix)
{
var filename = prefix + ".foo";
if (!File.Exists(filename))
return filename;
int suffix = 0;
do checked
{
filename = prefix + (suffix++).ToString() + ".foo";
} while (File.Exists(filename));
return filename;
}
Откуда возникает ещё одно правило: код можно сделать чище, если знать язык, платформу, библиотеку, и опираться на их исключения.
Выше я написал, что исключения выполняются медленнее, чем проверки, и хочу уточнит
свою мысль: не надо опираться на производительность при принятии решения. Правильна
передача смысла другому программисту, чистота кода — то, к чему следует стремится. Разница в производительности, хотя и существует, никогда не была настолько большой, чтобы пользователи её замечали. Ну, если только вы не пишите код для самого вложенного цикла в каком-нибудь графическом движке.
Ответ 2
Предварительная проверка быстрее работает и зачастую является более точной. Но только обработка исключений дает полную гарантию.
Пример: простое создание файла. Ошибок тут возможно - море:
в имени могут быть запрещенные символы (набор разрешенных символов зависит от файловой системы!);
полный путь к файлу может оказаться слишком длинным;
файл мог быть уже создан;
может не быть доступа для создания файлов;
диск может быть защищен от записи или вообще не поддерживать ее (CD-ROM тому пример).
Все эти варианты можно проверить заранее - и выдать пользователю понятное сообщение на русском языке.
Но есть ситуации, которые ни одна предварительная проверка не отловит:
файл может быть создан другой программой сразу после проверки;
файл может быть заблокирован антивирусом;
если файл создается на сетевом ресурсе - может "отвалиться" сеть.
Комментариев нет:
Отправить комментарий