Original in russian: http://programmingmindstream.blogspot.ru/2014/08/blog-post_85.html
I wіll definitely not break new ground, but I will write.
It can be done in this way:
Or in this way:
Both these options seem identical.
But, as for me, the second one is more “tasty” as well as more readable.
In debugging – more useful.
Why useful? Because you can set one break-point to EmyException.Check compared to the bags of them throughout the code.
Perhaps, it is included in standard library on new versions of Delphi. I actually did not found out. But we have been using this approach already for a long time and we like it.
Of course, it can be enhanced and improved.
For example, you could not pass the string but “generate” it inside Check. Generally, there is lots of alternatives.
I will also note that I’ve seen enough people who’ve forgotten raise.
It means they wrote like this:
But not like this:
And about the "expansions”.
For example, you can do like this:
Of course, it is only a “prototype”, not a true working code.
What’s so good of the “prototype”? It is that SomeComplexExpression will be evaluated only once.
Of course, you could do with a “locale variable” – in the simplest cases.
Besides, we can write like this:
In response to the commentary: http://programmingmindstream.blogspot.ru/2014/08/blog-post_85.html?showComment=1408566592655#c1216957169742679866
As for “using just a procedure”, we’ve used them too, of course:
One more thing.
What will happen if we write like this:
Exception of which class will trigger: EmyException or EmyException2?
As could be expected, it is EmyException2 :-)
It concerns “why class method and not just function”.
That is in some way an answer to: http://programmingmindstream.blogspot.ru/2014/08/blog-post_85.html?showComment=1408649403323#c8271514992700352647)
By the way, here is an example of what was written at the beginning:
IsValidPosition and IsValidLink are predicates.
In other words, function (aData: Int64): Boolean of object;
Check in this case looks like this:
What else do we need it for?
So that not to make a mess in the function Format and not to debug the raised exception, that will appear but not “tell the truth”.
It is obvious that the example of the code does not look good, but I did not refined it for purpose.
Of course, “tastes differ”, but I like it more than with a “band” of raise.
Try to write with raise and show that it will “be shorter”. I’ll be glad to learn.
By the way, directing of stack to the log file helps to identify the string from which an exception sprang. May be, I will write about it some time.
(Meanwhile here a discussion takes place).
And I will note: we could write something like:
But I do not do it consciously, since writing is surely shorter and more readable.
But! Using this approach it is more difficult to find out the real source of an error.
I could also write with, I know it. But I personally am an “ideological enemy” of with, especially in Delphi version without “reverse stability”.
Do you understand what "reverse stability" is? Or do I “think up my own terms” again?
Just in case I’ve written this one: Briefly. About “reverse stability”
I wіll definitely not break new ground, but I will write.
It can be done in this way:
type
EmyException = class(Exception)
end;//EmyException
...
if not Condition1 then
raise EMyException.Create('Some string1');
...
if not Condition2 then
raise EMyException.Create('Some string2');
Or in this way:
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');
Both these options seem identical.
But, as for me, the second one is more “tasty” as well as more readable.
In debugging – more useful.
Why useful? Because you can set one break-point to EmyException.Check compared to the bags of them throughout the code.
Perhaps, it is included in standard library on new versions of Delphi. I actually did not found out. But we have been using this approach already for a long time and we like it.
Of course, it can be enhanced and improved.
For example, you could not pass the string but “generate” it inside Check. Generally, there is lots of alternatives.
I will also note that I’ve seen enough people who’ve forgotten raise.
It means they wrote like this:
Exception.Create('aMessage');
But not like this:
raise Exception.Create('aMessage');
And about the "expansions”.
For example, you can do like this:
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);
Of course, it is only a “prototype”, not a true working code.
What’s so good of the “prototype”? It is that SomeComplexExpression will be evaluated only once.
Of course, you could do with a “locale variable” – in the simplest cases.
Besides, we can write like this:
var SomeLocalData : Integer; EMyException.Check( function (aData: Integer): Boolean; begin Result := (aData = SomeLocalData); end;, 'Some string2', SomeComplexExpression);
In response to the commentary: http://programmingmindstream.blogspot.ru/2014/08/blog-post_85.html?showComment=1408566592655#c1216957169742679866
As for “using just a procedure”, we’ve used them too, of course:
function Ht(ID : LongInt) : LongInt;
{var
nDosError : SmallInt; // Here the code returned by DOS will be entered
nOperation: SmallInt; // Here the code of the operation causing an error will be entered
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));
One more thing.
What will happen if we write like this:
type EmyException2 = class(EmyException) end;//EmyException2 ... EmyException2.Check(aCondition, aMessage);
Exception of which class will trigger: EmyException or EmyException2?
As could be expected, it is EmyException2 :-)
It concerns “why class method and not just function”.
That is in some way an answer to: http://programmingmindstream.blogspot.ru/2014/08/blog-post_85.html?showComment=1408649403323#c8271514992700352647)
By the way, here is an example of what was written at the beginning:
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 and IsValidLink are predicates.
In other words, function (aData: Int64): Boolean of object;
Check in this case looks like this:
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);
What else do we need it for?
So that not to make a mess in the function Format and not to debug the raised exception, that will appear but not “tell the truth”.
It is obvious that the example of the code does not look good, but I did not refined it for purpose.
Of course, “tastes differ”, but I like it more than with a “band” of raise.
Try to write with raise and show that it will “be shorter”. I’ll be glad to learn.
By the way, directing of stack to the log file helps to identify the string from which an exception sprang. May be, I will write about it some time.
(Meanwhile here a discussion takes place).
And I will note: we could write something like:
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]);
But I do not do it consciously, since writing is surely shorter and more readable.
But! Using this approach it is more difficult to find out the real source of an error.
I could also write with, I know it. But I personally am an “ideological enemy” of with, especially in Delphi version without “reverse stability”.
Do you understand what "reverse stability" is? Or do I “think up my own terms” again?
Just in case I’ve written this one: Briefly. About “reverse stability”
Комментариев нет:
Отправить комментарий