Почему обработка исключений использования в по-видимому “безопасном” коде?

Кто-то может объяснить меня, что может повысить исключение в этом коде?

function CreateBibleNames: TStrings;
begin
  Result := TStringList.Create;
  try
    Result.Add('Adam');
    Result.Add('Eva');
    Result.Add('Kain');
    Result.Add('Abel');
  except
    Result.Free;
    raise;
  end;      
end;

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

Кроме того, когда что-то перестало работать, я получаю описание исключения....

Спасибо

6
задан menjaraz 15 March 2012 в 13:20
поделиться

8 ответов

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

Во-первых, вот почему необычная конструкция в блоке try ... except. Функция создает TStringList, но если что-то пойдет не так в ходе его заполнения, то созданный TStringList будет «потерян» в куче и будет утечкой памяти. Таким образом, исходный программист был защищен и позаботился о том, чтобы в случае возникновения исключения TStringList был освобожден, а затем исключение возникло снова.

А теперь о «плохой предпосылке». Функция возвращает экземпляр TStrings. Это не всегда лучший способ сделать это. При возврате экземпляра такого объекта возникает вопрос: «Кто будет избавляться от этой вещи, которую я создал?». Это создает ситуацию, когда на вызывающей стороне может быть легко забыть, что экземпляр TStrings был выделен.

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

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

procedure CreateBibleNames(aStrings: TStrings);
begin
  if aStrings <> nil then
  begin
    aStrings .Add('Adam');
    aStrings .Add('Eva');
    aStrings .Add('Kain');
    aStrings .Add('Abel');
  end;      
end;

Это не означает, что каждый раз возвращать экземпляр объекта - это плохо - например, это единственная функция очень полезного шаблона Factory. Но для более «общих» функций, подобных этой, описанный выше способ является «лучшим».

12
ответ дан 8 December 2019 в 02:46
поделиться

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

Чрезмерное и неоправданное использование try, за исключением того, что загромождает код и затрудняет чтение

4
ответ дан 8 December 2019 в 02:46
поделиться

Самым очевидным исключением будет то, что Delphi использует для OutOfMemoryException . Если памяти недостаточно для добавления пары строк в список, освободите весь список и повторно вызовите исключение.

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

2
ответ дан 8 December 2019 в 02:46
поделиться

Without the source for TStringList, or authoritative documentation, you can't really know. Just for illustrative purposes, let's suppose that TStringList is written such that when memory gets tight, it starts swapping portions of the list to and from disk, allowing you to manage a list as large as your disk. Now the caller is also susceptible to the gamut of I/O errors: out of disk space, bad block encountered, network timeout, etc.

Is handling such exceptions overkill? Judgement call based upon scenario. We could ask NASA's programmers what they think.

2
ответ дан 8 December 2019 в 02:46
поделиться

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

-2
ответ дан 8 December 2019 в 02:46
поделиться

Такой код является классическим «фабричным» шаблоном. Этот пример тривиален и может не требовать обработки исключений, поскольку он может вызывать исключения в крайних случаях. Но если фабричный код намного сложнее, правильно освободить экземпляр, созданный , до повторного вызова любого обнаруженного исключения, потому что вызывающий не может знать, был ли экземпляр создан или нет до того, как исключение было возбуждено. . Гораздо лучше предположить, что экземпляр не был создан или освобожден, если возвращается какое-либо исключение. Фабрики полезны, когда возвращаемый экземпляр может не всегда быть одним и тем же (т. Е. Любой класс в данной иерархии), и когда параметры для создания экземпляра «неизвестны» вызывающему, но не фабрике. В таких случаях вызывающая сторона не может передать уже созданный экземпляр только для того, чтобы «заполнить» надлежащими параметрами, а должна просто потребовать экземпляр - конечно, должно быть ясно, что вызывающий объект становится «владельцем» созданного экземпляра. .

2
ответ дан 8 December 2019 в 02:46
поделиться

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

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

9
ответ дан 8 December 2019 в 02:46
поделиться

Я бы написал код таким образом по нескольким причинам:

1) Стандартизируя внешний вид распределения памяти, можно легко проверить исходный код, чтобы увидеть, не что-то отсутствует, без читать все строки. Здесь вы просто видите .Create и замечаете, что обработка исключений хороша, поэтому вам не нужно читать часть между попытками ... except.

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

3) Если вы позже измените исходный код, вставите какой-то код, измените способ работы .Add, замените TStringList чем-то другим и т. д., то это не нарушит этот код.

4) Это означает, что вам не нужно думать о том, будет ли TStringList.Add () генерировать исключения или нет, освобождая ваш мозг для другой работы, которая обеспечивает большую ценность.

4
ответ дан 8 December 2019 в 02:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: