Pull to refresh
-3
0

Программист

Send message
Про плюсы говорить не буду, но в Delphi работает так:
1. При создании память объекта заполняется нулями.
2. При выбросе исключения в конструкторе автоматически вызывается деструктор.
3. Деструктор умеет уничтожать недосозданный объект (благодаря пункту 1).
В результате бросать исключение в конструкторе можно без всяких ограничений.
А откуда у вас 1000 исключений при штатной работе сервиса?
По моему опыту использовать в таких случаях null — явный антипаттерн, приводящий к гекатомбам лишних проверок с одной стороны и ошибкам периорда выполнения в местах, далеких от ошибок кода — с другой. Можешь не вернуть результат — пиши if (TryGetResult(Result))… — и всем все будет ясно уже по сингнатуре. Не зря Хоар само введение null считал одним из самых дорогих решений в IT.
Есть очень простой универсальный критерий, выводящийся из самого смысла понятия «исключительная ситуация» — используйте исключения только для сбоев.
Ничего из того, что вы сейчас написали, из приведенного вами кода не следует и все это обязано было быть описано явно. С такими требованиями к данной функции никто в здравом уме исключения использовать не будет, так как бизнес-логика на исключениях — грубая ошибка. Правда на кодах ошибок там тоже код очень так себе с неявными соглашениями и возвратом невалидных данных.
В моей практике явный флаг наличия-отсутствия результата всегда лучше особого невалидного значения. А грядки возникают только у забивающих гвозди микроскопом.
С планетами было сложно — априори нужен был полноценный не-скриптовый AI.
Не справится — а хаскель даст ошибку компиляции, если при разборе Maybe пропустить ветку с Null.
Громоздкий же некрасивый код будет априори хуже читаться.
Хотя сама техника очень симпатичная и изящная, не вопрос.
С неизменяемыми данными (и объектами) — двумя руками за, экономит просто дикое количество времени как на проектирование, так и на отладку, плюс еще и памяти частенько меньше жрет.
Спасибо.
Если я правильно понял, то вы полагаете, что ваш пример говорит сам за себя.
Давайте разберем его.
Мелкие хитрости вроде записи if в одну строчку оставим за кадром.
Крупный косяк — версия на исключениях почему сама возвращает невалидные данные, т.е. написана в стиле кодов ошибок.
Еще более крупный косяк — не указано, является ли загрузка из дополнительных источников аварийным вариантом или полноценной альтернативой, а также является ли невозможность загрузки из всех трех мест сбоем.
Если все это — штатные альтернативы, а не сбой, то я бы написал так:
function TryGetConfig(out AConfig: IXML): Boolean;
begin
  Result := 
    TryReadXmlFromFile( userProfile.go( 'config.xml' ), AConfig) or
    TryReadXmlFromFile( defaultProfile.go( 'config.xml' ), AConfig) or
    TryDomParse( '<config/>', AConfig);
end;

Чем это лучше варианта по ссылке?
1. Сама сигнатура функции явно говорит о возможности не вернуть результат.
2. Если результат возвращается — он всегда валиден.
3. Внешний код делает вызов и проверку одним оператором if.
4. Никаких неявных условий про 0 ни в коде функции, ни в сигнатуре.

Если же неудачная из первичного источника — сбой, то в этом случае буду писать на исключениях:
function GetConfig: IXML;
begin
  try
    Result := ReadXmlFromFile(userProfile.go( 'config.xml' );
  except
    on E: XMLError do
    try
      Result := ReadXmlFromFile(defaultProfile.go('config.xml'));
    except
      on E: XMLError do
      begin
        Result := DomParse('<config/>', AConfig);
      end;
    end;
  end;
end;

Чем это лучше варианта по ссылке?
Обрабатывается только конкретный класс ошибок.
Чем это лучше варианта с кодами ошибок?
1. Явно виден бархатный путь — чтение основного конфига.
2. Сбои можно сразу фиксировать в логе со стеками.
3. Диагностика ошибок времени исполнения и выбранная ветка легко делаются на основе логов.
Как его можно улучшить?
Перевыбрасывать исключение EConfigError, если ошибки XML вызывающему коду неинтересны.
«Не ухудшают, представьте себе.»
Не представляю — разве что в качетве толстого троллинга. Исключения были введены в структурное программирование именно как средство избегать многоступенчатого защитного кода, в котором непонятно, что собственно защищают.
«где уже нет четкого представления ни о внутренних структурах, ни об особенностях кода.»
Выбрасываемые исключения описываются на уровне интерфейса, внутренние структуры и особенности кода там без разницы. Там, где само исключение теряет прежний смысл — его ловят и перевыбрасывают в новом значении. Только вот это надо куда реже, чем «простая проверка». И цепочку исключений отследить куда проще, чем цепочку проверок.
«Опять-таки, на уровне обертки не может быть 100% четкого понимания ни внутренних структур оборачиваемого кода, ни особенностей используемых алгоритмов.»
Обертка затем и нужна, чтобы инкапсулировать в себе изъяны дизайна того, что оборачивается, а снаружи мне особенности чужого кода даром не сдались. Мне задачи надо решать, а не мучиться с инструментом, городя гекатомбы проверок на каждый чих.
Про хаскель чистая правда — там коды ошибок благодаря алгебраическим типам и do-нотации делают исключения скорее вредными чем бесполезными.
Есть другая гарантия — необработанное исключение вылезет наверх по стеку и больно покусает. Непроверенный код ошибки тихонько нагадит и, мерзко хихикая, будет провоцировать сбои в самых неожиданных местах.
«Проверка кодов ошибок никогда ничего не «ухудшала» и не «маскировала».»
+ 3 строки кода и + 1 отступ на каждую проверку кода ошибки внутри реализации алгоритма никак не ухудшают читабельность кода только в фантастике.
Пропуск хотя бы одной проверки кода ошибок на ревью будет наглухо замаскирован при выполнении. Идеальные ревьюеры, которые выловят все-все-все — снова только в фантастике.
«А вы поддерживаете только тот код, что сдизайнен вами лично?»
Плохо сдизайненый чужой код оборачивается в хорошо сдизайненый свой. После чего везде используется обертка.
Если у вас try..finally в большом цикле — значит имеется кривость с многократным выделением-освобождением одного и того же ресурса вместо переиспользования.
А я где-то говорил про перехват всего? Я говорил что жду от библиотеки валидных данных или исключений. Первое позволит мне работать без лишних проверок, второе — демаскирует сбой и я смогу его быстро найти и понять, что является его причиной.
«Если вы пишете с проверкой кодов возврата, то, увидев вызов без последующей проверки, вы сразу задумаетесь: «а почему? Нет ли здесь возможной ошибки?».»
Ага — необработанные ошибки видны на ревью. Зато что делает код, когда все идет по плану — не видно от слова «совсем». В случае, если ревью таки пропустит косяк — при выполнении коды ошибок замаскируют его надежнее чем хамелеона. Исключение же заявит о себе прямо и недвусмысленно.
В результате:
1. Коды ошибок облегчают контроль кода на предмет обработки особых случаев.
2. Коды ошибок ухудшают контроль кода на предмет выполнения основной задачи.
3. Во время выполнения коды ошибок маскируют сбои.
«Толку от этого? Исключение было штатно обработано, а ошибка при отображении этого icon может возникнуть за тысячу миль от этого исключения»
Вы умеете читать то, что написано? Я этот случай разобрал комментарием выше. Ваш пример — ошибка дизайна.
Вот именно, что я пишу про особенности Delphi, которые позволяют эффективно использовать исключения, а вы — про особенности C++, которые этому мешают. Только я, в отличие от вас, кривости конкретной реализации на всю концепцию не натягиваю.
«Это не исключительная ситуация, это исключительно кривые руки.»
Это исключительная ситуация «невалидный параметр», причиной которой таки да, являются кривые руки :) Такие исключения очень даже можно бросать, но нельзя перехватывать.
«И пихать их везде в логику, как делает автор поста, не нужно и даже вредно.»
Где вы увидели «везде» и «в логику»? Автор привел в качестве примера функции, которые по явно указанной им спецификации могут не вернуть валидный результат.
«Заблуждаетесь таки вы, сводя к одному и тому же обе ситуации.»
Откуда вы вообще это взяли?
«Но вы слишком категоричны, следовательно, ошибаетесь.»
Это вряд ли. Я уж точно не писал ничего подобного безапелляционному
«Никогда еще от исключений код не становился элегантным.»
А указывал, в каких случаях по моему мнению целесообразно использовать исключения в Delphi. Вы же обобщили свой плюсовый опыт на все программирование разом в виде безусловного догмата.
Такой функциональный вариант очень хорош, но увы, прежде всего в функциональных языках, позволяя возложить контроль наличия обработки особых случаев на компилятор. Без алгебраических типов результат получается громоздкий и ненадежный.
Итак, еще раз.
try..except на каждый чих писать НЕ НАДО, в отличие от обработки кодов ошибок. Более того, при использовании исключений чистый алгоритм и обработку ошибок можно разнести по разным программным единицам, что, кстати, для Java советуется прямо.
Про видимый косяк вы ошибаетесь — в Delphi исключение можно залогировать вместе со стеком в момент выброса.
Вдобавок все, что может сделать пропущенное исключение — это всплыть строго наверх, демаскировав ошибку. Пропущенный код ошибки может всплыть в любом коде впоследствии.
«например, в примере Чена это может вызвать попытку обращения к несуществующей иконке невесть где, возможно, даже в сторонней библиотеке.»
Это уже косяк дизайна. Если создаваемый объект без иконки невалиден — то он не должен конструироваться без нее. А если валиден — то код обязан учитывать этот случай. Исключения тут вообще не при чем.
«но при отсутствии чистки icon»
Отсутствие такой чистки есть косяк библиотечного кода, исключения тут снова не при чем. Если же создаваемый класс иконкой не владеет — то весь приведенный пример есть ошибка создания объекта, которым никто не владеет.
Чен говорит про сложности, которых в Delphi просто нет. Советую посмотреть последнее предложение в статье:
«Yes, there are programming models like RAII and transactions, but rarely do you see sample code that uses either.» — так вот, в Delphi оно так и есть изначально. Любой конструктор либо создает валидный объект, либо бросает исключение, автоматически вызывая деструктор, очищая все захваченные в конструкторе перед сбоем ресурсы.
1. При том, что в топике идет речь об исключениях без привязки к языку. Если в плюсах они, возможно, кривые, то в Delphi не использовать исключения нет никаких оснований.
2. «Простая проверка» нужна всегда и везде, из-за нее основной алгоритм читается отвратительно, пропуск «простой проверки» может дать видимый косяк совсем не в том месте, где реально допущена ошибка. Исключение же сразу даст в лоб и точно обозначит место.
На каждый чих в Delphi ничего писать не надо: например, выброс исключения в конструкторе автоматически вызовет деструктор, который освободит все выделенные ресурсы. Для использования try..except есть ровно три основания, я их перечислил в одном из комментариев выше.
1. Таки не нужна.
2. На Delphi будет заметно нарушение паттерна создающей функции.
Result := TX.Create;
try
  Result.Y := Z;
except
  FreeAndNil(Result);
  raise;
end;


На той же Delphi будет заметен косяк уже дизайна с присваиванием свойству Icon — такие вещи либо создаются внутри объекта, либо он ими не владеет.

Information

Rating
Does not participate
Location
Россия
Registered
Activity