Ни для кого наверное не открою Америку, но всё же напишу.
Можно сделать так:
А можно так:
Вроде "то же на то же", и оба варианта - одинаковые.
Но по мне - второй вариант - "вкуснее". Да и читабельнее.
И в отладке - полезнее.
Почему в отладке полезнее? Потому, что можно поставить один 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» тоже не всё ясно. На этапе локализации добиваемся исключения, по стеку находим место его возбуждения, и там ставим точку останова, или ещё лучше, в начале блока кода, вызвавшего это исключение...