Ни для кого наверное не открою Америку, но всё же напишу.
Можно сделать так:
А можно так:
Вроде "то же на то же", и оба варианта - одинаковые.
Но по мне - второй вариант - "вкуснее". Да и читабельнее.
И в отладке - полезнее.
Почему в отладке полезнее? Потому, что можно поставить один break-point в EmyException.Check, а не множество по коду.
Возможно в новых версиях Delphi это входит в стандартную библиотеку. Не знаю, честно - не проверял. Но мы этим подходом пользуемся уже очень давно. И он нам нравится.
И его понятное дело - можно расширять и усовершенствовать.
Например - не передавать строку, а "генерировать" её внутри Check. Ну в общем - вариантов масса.
Ну и ещё отмечу - я наблюдал достаточное количество людей, которые забывали raise.
Т.е. писали так:
а не так:
Ну и про "расширения".
Можно например так:
-- ну понятное, дело, что это "макет", а не реально рабочий код.
Чем этот "макет" хорош? Тем, что SomeComplexExpression - вычислится один раз.
Понятное дело, что можно и "локальной переменной" обойтись.
Ну это в простейших случаях.
Да и потом, ведь можно написать и так:
Update.
По мотивам комментария - http://programmingmindstream.blogspot.ru/2014/08/blog-post_85.html?showComment=1408566592655#c1216957169742679866
Про "просто процедуру" - мы конечно же тоже ими пользовались:
И ещё.
Что случится, если мы напишем так:
-- исключение какого класса возбудится?
EmyException или EmyException2?
EmyException2 :-) что и "следовало ожидать".
Это к вопросу - "почему метод класса, а не просто функция".
Update.
(В некотором роде в ответ на - http://programmingmindstream.blogspot.ru/2014/08/blog-post_85.html?showComment=1408649403323#c8271514992700352647)
Вот кстати пример, того что написано в самом начале:
- IsValidPosition и IsValidLink это предикаты.
Т.е. function (aData: Int64): Boolean of object;
Check в данном случае выглядит так:
-- зачем ещё это нужно?
Ну чтобы с форматками в функции Format не напутать.
И не отлаживать "наведённый" exception который вылезет у пользователя, но "не расскажет правду".
Понятное дело, что пример кода - далеко не самый "красивый", но я специально ничего не рафинировал.
Конечно - на вкус и цвет - "все фломастеры разные", но мне лично это нравится больше, чем с "вязанкой" raise.
Попробуйте написать с raise и покажите - что он "будет короче". Я буду рад поучиться.
И кстати да - вывод стека в лог - помогает идентифицировать строку - откуда полетело исключение. Но про это я может быть ещё когда-нибудь напишу отдельно.
(Пока вот тут - идёт некоторая дискуссия)
Update.
Да и оговорюсь.
Можно было бы конечно "сочинить" что-то вроде:
-- но я осознанно этого не делаю.
Т.к. конечно запись короче и "читабельнее".
Но! В таком варианте - сложнее искать реальный источник ошибки.
Ну и with конечно тоже можно было бы написать. Я знаю. Но я лично - "идеологический противник" with.
Особенно в варианте Delphi, где нет "обратной устойчивости".
Слова "обратная устойчивость" понятны? Или я опять "выдумываю свои термины"?
На всякий случай написал вот что - Коротко. Об "обратной устойчивости"
Можно сделать так:
type EmyException = class(Exception) end;//EmyException ... if not Condition1 then raise EMyException.Create('Some string1'); ... if not Condition2 then raise EMyException.Create('Some string2');
А можно так:
type EmyException = class(Exception) public class procedure Check(aCondition: Boolean; const aMessage: String); end;//EmyException ... class procedure EmyException.Check(aCondition: Boolean; const aMessage: String); begin if not aCondition then raise Self.Create(aMessage); end; ... EMyException.Check(Condition1, 'Some string1'); ... EMyException.Check(Condition2, 'Some string2');
Вроде "то же на то же", и оба варианта - одинаковые.
Но по мне - второй вариант - "вкуснее". Да и читабельнее.
И в отладке - полезнее.
Почему в отладке полезнее? Потому, что можно поставить один break-point в EmyException.Check, а не множество по коду.
Возможно в новых версиях Delphi это входит в стандартную библиотеку. Не знаю, честно - не проверял. Но мы этим подходом пользуемся уже очень давно. И он нам нравится.
И его понятное дело - можно расширять и усовершенствовать.
Например - не передавать строку, а "генерировать" её внутри Check. Ну в общем - вариантов масса.
Ну и ещё отмечу - я наблюдал достаточное количество людей, которые забывали raise.
Т.е. писали так:
Exception.Create('aMessage');
а не так:
raise Exception.Create('aMessage');
Ну и про "расширения".
Можно например так:
type TMyPredicate = reference to function (aData: Integer): Boolean; EmyException = class(Exception) public class procedure Check(aCondition: Boolean; const aMessage: String); overload; class procedure Check(aCondition: TMyPredicate; const aMessage: String; aData: Integer); overload; end;//EmyException ... class procedure EmyException.Check(aCondition: Boolean; const aMessage: String); begin if not aCondition then raise Self.Create(aMessage); end; class procedure EmyException.Check(aCondition: TMyPredicate; const aMessage: String; aData: Integer); begin if not aCondition(aData) then raise Self.Create('InvalidData: ' + IntToStr(aData) + aMessage); end; ... EMyException.Check(Condition1, 'Some string1'); ... EMyException.Check( function (aData: Integer): Boolean; begin Result := IsValid(aData); end;, 'Some string2', SomeComplexExpression);
-- ну понятное, дело, что это "макет", а не реально рабочий код.
Чем этот "макет" хорош? Тем, что SomeComplexExpression - вычислится один раз.
Понятное дело, что можно и "локальной переменной" обойтись.
Ну это в простейших случаях.
Да и потом, ведь можно написать и так:
var SomeLocalData : Integer; EMyException.Check( function (aData: Integer): Boolean; begin Result := (aData = SomeLocalData); end;, 'Some string2', SomeComplexExpression);
Update.
По мотивам комментария - http://programmingmindstream.blogspot.ru/2014/08/blog-post_85.html?showComment=1408566592655#c1216957169742679866
Про "просто процедуру" - мы конечно же тоже ими пользовались:
function Ht(ID : LongInt) : LongInt; {var nDosError : SmallInt; // Сюда занесут код, возвращенный ДОС nOperation: SmallInt; // Сюда занесут код операции, приведшей к ошибке lErrstr : array[0..1000] of AnsiChar; lErrstr2 : PAnsiChar; } begin Result := ID; if lNeedStackOut_ErrNum <> 0 then begin l3System.Stack2Log(Format('HTERROR = %d STACK OUT', [lNeedStackOut_ErrNum])); lNeedStackOut_ErrNum := 0; end; { if ID = -1 then lErrstr2 := htExtError(nDosError, nOperation, @lErrstr[0]); } if ID < 0 then raise EHtErrors.CreateInt(ID); end; .... Ht(htOpenResults(Masks,ROPEN_READ,@FldArr,FldCount)); .... Ht(htDeleteRecords(TmpList)); .... Ht(htOpenResults(ValList,ROPEN_READ,nil,0));
И ещё.
Что случится, если мы напишем так:
type EmyException2 = class(EmyException) end;//EmyException2 ... EmyException2.Check(aCondition, aMessage);
-- исключение какого класса возбудится?
EmyException или EmyException2?
EmyException2 :-) что и "следовало ожидать".
Это к вопросу - "почему метод класса, а не просто функция".
Update.
(В некотором роде в ответ на - http://programmingmindstream.blogspot.ru/2014/08/blog-post_85.html?showComment=1408649403323#c8271514992700352647)
Вот кстати пример, того что написано в самом начале:
Em3InvalidStreamPos.Check(Self.IsValidPosition, aHeader.f_Name, l_Pos); Em3InvalidStreamSize.Check(Self.IsValidPosition, aHeader.f_Name, aHeader.f_TOCItemData.rBody.rRealSize); Em3InvalidStreamPos.Check(Self.IsValidLink, aHeader.f_Name, aHeader.f_TOCItemData.rBody.RTOCBuffRootPosition); Em3InvalidStreamPos.Check(Self.IsValidLink, aHeader.f_Name, aHeader.f_TOCItemData.rBody.RTOCItemListPosition); Em3InvalidStreamPos.Check(Self.IsValidLink, aHeader.f_Name, aHeader.f_TOCItemData.RNextPosition);
- IsValidPosition и IsValidLink это предикаты.
Т.е. function (aData: Int64): Boolean of object;
Check в данном случае выглядит так:
type TInt64Predicate = function (aData: Int64): Boolean of object; ... class procedure Em3InvalidStreamData.Check(aCondition: TInt64Predicate; aName : String; aData : Int64); begin if not aCondition(aData) then raise Self.CreateFmt('Invalid data %d in file %s', [aName, aData]); end; ... Em3InvalidStreamPos = class(Em3InvalidStreamData); Em3InvalidStreamSize = class(Em3InvalidStreamData);
-- зачем ещё это нужно?
Ну чтобы с форматками в функции Format не напутать.
И не отлаживать "наведённый" exception который вылезет у пользователя, но "не расскажет правду".
Понятное дело, что пример кода - далеко не самый "красивый", но я специально ничего не рафинировал.
Конечно - на вкус и цвет - "все фломастеры разные", но мне лично это нравится больше, чем с "вязанкой" raise.
Попробуйте написать с raise и покажите - что он "будет короче". Я буду рад поучиться.
И кстати да - вывод стека в лог - помогает идентифицировать строку - откуда полетело исключение. Но про это я может быть ещё когда-нибудь напишу отдельно.
(Пока вот тут - идёт некоторая дискуссия)
Update.
Да и оговорюсь.
Можно было бы конечно "сочинить" что-то вроде:
Em3InvalidStreamPos.Check(Self.IsValidPosition, aHeader.f_Name, [l_Pos, aHeader.f_TOCItemData.rBody.rRealSize, aHeader.f_TOCItemData.rBody.RTOCBuffRootPosition, aHeader.f_TOCItemData.rBody.RTOCItemListPosition, aHeader.f_TOCItemData.RNextPosition]);
-- но я осознанно этого не делаю.
Т.к. конечно запись короче и "читабельнее".
Но! В таком варианте - сложнее искать реальный источник ошибки.
Ну и with конечно тоже можно было бы написать. Я знаю. Но я лично - "идеологический противник" with.
Особенно в варианте Delphi, где нет "обратной устойчивости".
Слова "обратная устойчивость" понятны? Или я опять "выдумываю свои термины"?
На всякий случай написал вот что - Коротко. Об "обратной устойчивости"
Хорошая штука. Мне понравилось. Как-то не приходил в голову такой подход.
ОтветитьУдалить;-) ну надо же! :-) понравилось. Пользуйся на здоровье :-)
УдалитьРом, а я ведь сомневался - писать или нет. Боялся показаться банальным. :-)
УдалитьКлассовая процедура - это хорошо, в последнее время я им отдаю предпочтение. А касательно исключений - идея очень хорошая, в сторонних библиотеках я такого не встречал, обычно используется просто процедура...
ОтветитьУдалить"обычно используется просто процедура..."
Удалить:-) это мы тоже "проходили"
понятно ведь, чем "просто процедура" хуже?
тем, что наследования нет :-)
"Классовая процедура - это хорошо, в последнее время я им отдаю предпочтение"
Удалить-- я тоже. Ведь "классовая процедура" это по сути "фабричный метод" :-)
http://18delphi.blogspot.ru/2013/04/blog-post_7483.html
Удалить"Да и кеширование в фабричный метод всегда можно потом безболезненно воткнуть, или логирование, ну или бизнес-логику какую. Или например граничные условия обработать."
УдалитьКруто, я про Exception никогда не думал, так его расширять.
ОтветитьУдалитьЕдинообразие нарушается. Везде raise, а тут вдруг Check().
ОтветитьУдалитьОтличная идея!
ОтветитьУдалитьИ кстати, принимая во внимание комментарий pda, я б назвал этот метод raiseIfNotTrue или raise (в идеале), но не факт что компилятор позволит.
raise не дадут
ОтветитьУдалитьНасчёт "единообразия" верно заметил один мой знакомый - "если довести до абсурда - все функции нарушают единообразие, так как вместо везде используемых ключевых слов языка начинаем использовать какие-то пользовательские функции". И я с этим - полностью согласен.
ОтветитьУдалитьВсё ниже - моё скромное IMHO.
ОтветитьУдалитьНикого не критикую, пытаюсь понять, как мог бы сам это использовать...
<code title='Вариант A'>
EMyException.Check(
function (aData: Integer): Boolean;
begin
Result := IsValid(aData);
end;,
'Some string2',
SomeComplexExpression);
</code>
vs
<code title='Вариант B'>
if IsValid(SomeComplexExpression) then
raise EmyException.Create('Some string2');
</code>
Вариант B представляется более компактным (в три раза) и, как минимум, не проигрывает в наглядности.
Интересно понять стремление к группировке различных ситуаций в один класс исключения.
Классический подход подразумевает идентифицируемость исключений, например, для удобства их обработки. Блок except при обработке исключений, в частности, заточен под это, позволяя фильтровать исключения по типам с учётом их иерархии.
В идеале (т. е. я следую этому не всегда) это означает, что каждой исключительной ситуации в коде сопоставлен соответствующий тип исключения. Мне всегда казалось, что следование этой схеме не сулит неудобств.
Здесь же, как мне показалось, предлагается следовать обратной схеме: исключение одного типа возбуждается в различных контекстах. Да, и здесь можно обеспечить идентификацию (с помощью «кода ошибки» в EMyException, например), но при стандартном подходе это уже есть «из коробки».
Так что мы выигрываем с этими классовыми методами и замыканиями?
Ну не только же защищаемся от того, что кто-то забудет написать raise?...
Насчёт «точки останова на Check» тоже не всё ясно. На этапе локализации добиваемся исключения, по стеку находим место его возбуждения, и там ставим точку останова, или ещё лучше, в начале блока кода, вызвавшего это исключение...