вторник, 3 февраля 2015 г.

Briefly. About triggering the exceptions

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:

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”

Комментариев нет:

Отправить комментарий