Results 1 to 4 of 4

Thread: EExceptionForward: Exception inpakken en doorsturen.

  1. #1

    EExceptionForward: Exception inpakken en doorsturen.

    Meer bedoeld als tip dan als artikel, al blijkt het toch een aardige lap tekst geworden...

    Intro

    Altijd een groot vraagstuk: hoe toon je nou een exception aan je gebruiker. Je wilt niet dat de gebruiker schrikt van een technisch rapport, maar je wilt die informatie zelf wel kunnen zien.

    Detail-schermpje (of loggen)
    Vaak wordt er dan gegrepen naar een soort 'Detail' scherm: de gebruiker krijgt een algemene melding, en kan op 'Details' klikken om de rest te zien. Het valt te betwijfelen of die oplossing vanuit een UX-design standpunt nou zo geweldig is, maar het opent in ieder geval de weg naar het loggen van de details en het tonen van een gebruikersvriendelijke melding.

    Zelf heb ik ook nog zo'n details-schermpje rondhangen. Met behulp van een TApplicationEvents object wordt het OnException event afgevangen, en worden exceptions daar apart verwerkt. Bij bepaalde exceptions toon ik een afwijkend venstertje dat de details toont. Bovendien kun je daar ook inspringen op bepaalde exception classes, zodat je ook de aanvullende informatie (zoals de errorcode van een ADO exception) kunt loggen.

    Helaas is dat ook niet al te makkelijk. Je wilt de gebruikersvriendelijke melding ook wel een beetje specifiek hebben, en geen 'Er is iets fout gegaan' in je algemene exception handler zetten. Je hebt dus een exception class nodig waarin je de gebruikersmelding en de technische melding kunt zetten.

    DetailException
    Je zou je voor kunnen stellen dat je een detail-exception maakt waarin je de aanvullende informatie apart kunt meegeven:

    Delphi Code:
    1. type
    2.   EDetailedException = class(Exception)
    3.     // Alle constructors, zodat er een detail-rapport aan
    4.     // meegegeven kan worden.
    5.   end;

    Een leuk begin, maar dan moet je steeds de vorige exception gaan uitlezen en meegeven. Eventueel kun je de constructor nog uitbreiden dat deze ook een exception slikt, maar dat maakt het er nog niet per se makkelijker op. Exception heeft zelf een stuk of 10 constructors en bovendien raak je dan mogelijk allerlei details kwijt over de aard van de message. Bepaalde message classes bevatten tenslotte aanvullende informatie, en je wilt die ook weer niet op elke plaats apart moeten verwerken.

    EExceptionForward
    Nou zocht ik eigenlijk een manier om een exception mee te geven aan een andere exception, en deze eigenlijk als het ware in te pakken:

    Delphi Code:
    1. type
    2.   EExceptionForward = class(Exception)
    3.     property OriginalException: Exception;
    4.   end;
    Op die manier zou ik een EExceptionForward kunnen gooien, die verwijst naar de oorspronkelijke exception. Helaas, wanneer een Exception object is geraised, zal het automatisch vrijgegeven worden aan het eind van het except-blok. In Application.OnException is OriginalException dus al vrijgegeven.

    De oplossing: AcquireExceptionObject
    AcquireExceptionObject is een functie die het laatst geraisde exception object teruggeeft, en voorkomt dat deze automatisch vrijgegeven wordt. De functie is al een enkele keer langsgekomen op NLDelphi, maar wordt in het algemeen weinig gebruikt. Hij is echter wel handig te gebruiken in zo'n EExceptionForward object, door in de constructor het vorige exception object op te vragen. Vanaf dat moment is het EExceptionForward object ook de eigenaar van het exception object, en dus verantwoordelijk voor het vrijgeven ervan.

    AcquireExceptionObject kan aangeroepen worden in de AfterConstruction method, zodat je niet alle constructors van Exception hoeft te overriden.

    Delphi Code:
    1. procedure EExceptionForward.AfterConstruction;
    2. begin
    3.   inherited;
    4.   OriginalException := AcquireExceptionObject;
    5. end;
    6.  
    7. destructor EExceptionForward.Destroy;
    8. begin
    9.   OriginalException.Free;
    10.   inherited;
    11. end;

    Ik zie er wel brood in dat je een exception kan geven waarbij je zelf de details opgeeft, maar ook een variant die de details zelf bepaalt op basis van een andere exception. Daarom een base class die MainMessage (= Message) en DetailMessage als property implementeert. De property FullMessage geeft een combinatie van die twee terug. EExceptionForward hoeft alleen GetDetailedMessage te overriden om de details van de originele message op te vragen.
    Delphi Code:
    1. type
    2.   ECustomDetailedException = class(Exception)
    3.     ...
    4.   public
    5.     property FullMessage: String read GetFullMessage;
    6.     property MainMessage: String read GetMainMessage;
    7.     property DetailedMessage: String read GetDetailedMessage write SetDetailedMessage;
    8.   end;
    9.  
    10.   EDetailedException = class(ECustomDetailedException)
    11.     // Hier ruimte voor het overriden van alle constructors, zodat er een detail-rapport aan
    12.     // meegegeven kan worden.
    13.   end;
    14.  
    15.   EExceptionForward = class(ECustomDetailedException)
    16.     ...
    17.   protected
    18.     function GetDetailedMessage: String; override;
    19.     ...

    Toepassing

    Het doorsturen van zo'n exception is dan heel eenvoudig:
    Delphi Code:
    1. try
    2.   TFileStream.Create('::\/\Niet bestaande file***...', fmOpenRead);
    3. except
    4.   raise EExceptionForward.Create('Het bestand kon niet worden gelezen.');
    5. end;
    In de code hierboven zal EExceptionForward impliciet de geraisede exception opzoeken en 'claimen'. Als hier verder niets mee gedaan wordt, zal de gebruiker alleen de melding 'Het bestand kon niet worden gelezen.' te zien krijgen.

    Als je wél wat met die details wilt doen, kun je het Application.OnException event gebruiken. Daarin kun je controleren op het type van de exception en afhankelijk daarvan een andere melding tonen:

    Delphi Code:
    1. procedure TForm2.ApplicationEvents1Exception(Sender: TObject; E: Exception);
    2. begin
    3.   if E is ECustomDetailedException then
    4.   begin
    5.     with ECustomDetailedException(E) do
    6.     begin
    7.       LogError(FullMessage);
    8.       ShowErrorWithDetails(MainMessage, DetailMessage);
    9.     end;
    10.   end
    11.   else
    12.   begin
    13.     LogError(Message);
    14.     Application.ShowException(E);
    15.   end;
    16. end;
    Het is natuurlijk net zo makkelijk om de DetailMessage (of de FullMessage) te loggen en de exception verder op de gebruikelijke manier te tonen met Application.ShowException.

    Ik zou eigenlijk het liefst hebben dat E.Message geoverride wordt, zodat deze de FullMessage teruggeeft. Op die manier kun je dit type exception al makkelijk gaan inzetten zonder de code voor het tonen en loggen van exceptions aan te hoeven passen. Helaas is Exception een klassiek VCL-object waarin niets virtueel is en alles dichtgetimmerd. GetMessage overriden is er dus niet bij.

    De code

    Delphi Code:
    1. type
    2.   ECustomDetailedException = class(Exception)
    3.   private
    4.     FDetailedMessage: String;
    5.   protected
    6.     function GetDetailedMessage: String; virtual;
    7.     function GetMainMessage: String; virtual;
    8.     procedure SetDetailedMessage(const Value: String); virtual;
    9.     function GetFullMessage: String; virtual;
    10.   public
    11.     property FullMessage: String read GetFullMessage;
    12.     property MainMessage: String read GetMainMessage;
    13.     property DetailedMessage: String read GetDetailedMessage write SetDetailedMessage;
    14.   end;
    15.  
    16.   EDetailedException = class(ECustomDetailedException)
    17.     // Hier ruimte voor het overriden van alle constructors, zodat er een detail-rapport aan
    18.     // meegegeven kan worden.
    19.   end;
    20.  
    21.   EExceptionForward = class(ECustomDetailedException)
    22.   private
    23.     FOriginal: Exception;
    24.   protected
    25.     function GetDetailedMessage: String; override;
    26.   public
    27.     property OriginalException: Exception read FOriginal write FOriginal;
    28.     procedure AfterConstruction; override;
    29.     destructor Destroy; override;
    30.   end;
    31.  
    32. { ECustomDetailedException }
    33.  
    34. function ECustomDetailedException.GetDetailedMessage: String;
    35. begin
    36.   Result := FDetailedMessage;
    37. end;
    38.  
    39. function ECustomDetailedException.GetFullMessage: String;
    40. begin
    41.   Result := Trim(MainMessage + sLineBreak + DetailedMessage);
    42. end;
    43.  
    44. function ECustomDetailedException.GetMainMessage: String;
    45. begin
    46.   Result := inherited Message;
    47. end;
    48.  
    49. procedure ECustomDetailedException.SetDetailedMessage(const Value: String);
    50. begin
    51.   FDetailedMessage := Value;
    52. end;
    53.  
    54. { EExceptionForward }
    55.  
    56. procedure EExceptionForward.AfterConstruction;
    57. begin
    58.   inherited;
    59.   OriginalException := AcquireExceptionObject;
    60. end;
    61.  
    62. destructor EExceptionForward.Destroy;
    63. begin
    64.   OriginalException.Free;
    65.   inherited;
    66. end;
    67.  
    68. function EExceptionForward.GetDetailedMessage: String;
    69. begin
    70.   Result := inherited GetDetailedMessage;
    71.  
    72.   if (Result = '') and Assigned(FOriginal) then
    73.     if OriginalException is ECustomDetailedException then
    74.       Result := ECustomDetailedException(OriginalException).GetFullMessage
    75.     else
    76.       Result := OriginalException.Message;
    77. end;

    Voorbeeld

    De laatste method hierboven geeft het detail-bericht terug. Als de originele exception ook een EExceptionForward is, dan wordt daar de FullMessage van opgevraagd. Dit resulteert uiteindelijk in een soort call stack van exceptions, bijvoorbeeld deze code:
    Delphi Code:
    1. try
    2.     try
    3.       TFileStream.Create('::\/\Niet bestaande file***...', fmOpenRead);
    4.     except
    5.       raise EExceptionForward.Create('Het bestand kon niet worden gelezen.');
    6.     end;
    7.   except
    8.     raise EExceptionForward.Create('De import is mislukt.');
    9.   end;
    Resulteert in deze melding:
    De import is mislukt.
    Het bestand kon niet worden gelezen.
    Cannot open file "::\Niet bestaande file***". The system cannot find the path specified
    Samenvatting
    • EExceptionForward pakt een exception in, in een meer algemene en/of gebruikersvriendelijke melding.
    • EExceptionForward gebruikt AcquireExceptionObject om zelf de getriggerde Exception te bepalen.
    • Je kunt ze nesten om een soort stack van fouten te krijgen, van algemeen tot steeds meer detail.
    • In Application.OnException kun je een generieke handler maken om exceptions te tonen en/of te loggen.
    • Het originele Exception object blijft bewaard binnen de EExceptionForward, zodat je bij het loggen ervan alle informatie nog tot je beschikking hebt.
    • Het is simpel.
    • Het is fun!
    Last edited by GolezTrol; 07-Aug-12 at 17:16.
    1+1=b

  2. #2
    Senior Member
    Join Date
    Sep 2006
    Location
    De Aker
    Posts
    161
    Leuk stuk! Moet je naar Blaise sturen, die publiceren zoiets vast.

    Heb je al eens naar nested exceptions gekeken?

    Interessant artikel hierover: http://eurekalog.blogspot.nl/2010/05...09-and_05.html

    --jeroen

  3. #3
    Bedankt! Interessant, nested exceptions ken ik niet. Op m'n werk heb ik nog Delphi 2007 en heb ik dus niet de mogelijkheden van Delphi 2009, maar het lijkt iets soortgelijks te doen.

    JclDebug wordt daar inderdaad ook gebruikt. Die heb ik ook al eens gebruikt en zou ik hier misschien ook in op kunnen nemen. Voor nu kwam het er niet van. Ik zocht vooral een specifieke oplossing, en heb die toen ik 'm vond maar even in een post geknald.
    1+1=b

  4. #4
    Super! Inderdaad was de AcquireExceptionObject al eens langsgekomen, maar hij was bij mij niet blijven hangen . Je opzet geeft inderdaad een weg naar een fatsoenlijke melding naar de gebruiker zonder je Exception op te eten.
    Marcel

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •