Exceptions in MSElang

Forum für alles rund um die MSEide und MSEgui
Antworten
mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Exceptions in MSElang

Beitrag von mse »

Die Ausnahmebehandlungen wurden implementiert:
https://gitlab.com/mseide-msegui/mselan ... exceptions
https://gitlab.com/mseide-msegui/mselan ... ng_objects

Ein Beispiel:

Code: Alles auswählen

 
type
 ea = class()[virtual,except]
  constructor create(const message: string8);
  destructor destroy() [default];
  property message: string8 read fmessage;
 
  protected
   fmessage: string8;
 end;
 
 ea1 = class(ea)
 end;
 
 ea2 = class(ea1)
 end;
 
 eb = class()[virtual,except]
  constructor create();
  destructor destroy() [default];
 end;
 
constructor ea.create(const message: string8);
begin
 fmessage:= message;
end;
 
destructor ea.destroy();
begin
end;
 
constructor eb.create();
begin
end;
 
destructor eb.destroy();
begin
end;
 
var
 e: ea;
 
begin
 try
  raise ea2.create('abc');
 
 except
  if getexceptobj(e) then
   writeln(e.message);
  end;
 
  ea2:
   exitcode:= 10;
  eb,ea1:
   exitcode:= 20;
  else
   exitcode:= 30;
 end;
 
[...]
 

Klassen welche als exceptions dienen sollen müssen einen default-destructor haben und der Wurzel-Objekttyp muss das "virtual" Attribut besitzen. Zudem muss das "except" Attribut gesetzt sein.

Was meint ihr dazu?

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Exceptions in MSElang

Beitrag von braunbär »

Ich finde, dass die Exceptions, wie sie in Delphi und auch in FPC realisiert sind, grundlegend schlecht (umständlich, mühsam) konzipiert sind.
Wenn du eine neue Sprache entwickelst und keinen gesteigerten Wert auf Delphi Kompatibilität legst (die ja auch bei deinem Ansatz in Wirklichkeit nicht mehr gegeben ist) , würde ich ganz anders herangehen und für Exceptions nicht den strikten Klassenformalismus verwenden. Ein Konstruktor create, der ohnedies nichts anderes zu tun hat, als die Parameter in lokalen Feldern zu speichern, die dann wieder als properties ausgelesen werden, ist schlicht überflüssiger Codieraufwand, all das kann ohne Einschränkung der Flexibilität die "Compiler Magic" erledigen.

Ich würde von einem eigenen Basistyp Exception (der so etwas ähnliches wie eine Klasse ist, aber ohne die ganze aufwändige Syntax rundherum) ausgehen, der neben dem "Parameter" message noch zwei Felder line und procname hat, die bei einem raise automatisch gesetzt werden. Ich sehe überhaupt keinen Nutzen darin, "beliebige" Klassen, die nicht von diesem Basistyp abgeleitet sind, für das Exception-Handling zuzulassen.

Ich stelle mir da etwas auf die Art vor:

Code: Alles auswählen

 
type
ea = exception (x: integer);   // Exception mit zusätzlicherm Integer Parameter
eb = ea (y: real);          // Exception mit integer (von ea geerbt) und real Parameter
ec = exception;            // Exception nur mit dem Parameter message, direkt von exception abgeleitet
ed = eb;                    //  Exception von eb abgeleitet
 
var a: real;
 
begin
a:=23.444;
...
try
raise exception('Das ist eine normale Exception, braucht gar keine separate Deklaration');
...
raise ea('', 26);
...
raise eb('eb', 15, 17.3); // Parameterreihenfolge entsprechend der Vererbungshierarchie
...
raise ec('Hier wurde die exception ec ausgelöst');
...
raise ed('Exception ed', 3, a);
 
except
   // Im Exception Block sind die Felder message, line und procname automatisch bekannt
   writeln (message + ' at line ' + line +' in procedure ' + procname);
   // In jedem on-Block sind die entsprechenden Felder ebenfalls direkt zugänglich 
   on ed do showmessage (format('Exceptiontyp ed - y = %1.4f   x=%d',[y,x]))
   else 
   on eb do showmessage (format('Exceptiontyp eb - y = %1.4f   x=%d',[y,x]))
   else 
   on ea do showmessage (format('Exceptiontyp ea - x=%d',[x]))
end;
 

In der Praxis wird man dann für 99% der Exceptions gar keine Zusatzdeklaration brauchen, sondern einfach raise exception('messagestring') verwenden.

edit: Der Mix aus writeln und showmessage ist natürlich Unsinn, aber es sollte verständlich sein, was gemeint ist.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Exceptions in MSElang

Beitrag von mse »

In MSElang kann man eine minimale Exception-Klasse definieren und diese als Basis benutzen. Die Minimalexception besteht aus lediglich einem pointer zur Objekttyp- Beschreibung. Ums "create()" kommt man nicht herum, da so bestimmt wird, dass die Instanz im heap alloziert wird. Wenn man keine Nutzlast benötigt, kann man im "except"-Teil aufgrund des Object-Typ verzweigen. Da lediglich pointer verglichen werden, ist die performance optimal. MSElang verwendet übrigens "zero cost exceptions".

Code: Alles auswählen

 
type
 e = class()[virtual,except]
  constructor create();
  destructor destroy() [default];
 end;
 e1 = class(e)
 end;
 e2 = class(e)
 end;
 e3 = class(e)
 end;
 e4 = class(e)
 end;
 e4a = class(e4)
 end;
 
 
constructor e.create();
begin
end;
 
destructor e.destroy();
begin
end;
 
begin
 try
  raise e3.create();
 except
  e1,e2,e4a:
   exitcode:= 10;
  e3,e4:
   exitcode:= 20;
  else
   exitcode:= 30;
 end;
[...]
 

Ich versuche so wenig verschiedene Programmkonstrukte wie möglich einzuführen, daher das "except" Attribut in einer normalen Klasse statt eines speziellen "exception" Typ. Auch den Anteil von "compiler magic" versuche ich so gering wie möglich zu halten, damit die Flexibilität so gross wie möglich ist und die tatsächliche Wirkungsweise nicht unnötig verborgen wird.
Gegen einen festen Klassentyp als Exceptionobjekt wie von dir vorgeschlagen sträubt sich in mir alles. ;-)
Ich versuche die Sprache so flexibel wie möglich zu gestalten, die Spezialisierung und Zuschneidung auf das "Übliche" kann dann in supportunits geschehen.
Zuletzt geändert von mse am Fr 28. Jul 2017, 15:06, insgesamt 1-mal geändert.

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Exceptions in MSElang

Beitrag von braunbär »

Nachdem MSElang "dein Baby" ist, kannst du natürlich tun, was dir am besten gefällt.
Denke aber deine Argumentation noch einmal in Ruhe durch:

mse hat geschrieben:Ums "create()" kommt man nicht herum, da so bestimmt wird, dass die Instanz im heap alloziert wird

raise ist ein eigenes Sprachkonstrukt speziell für das Exception-Handling. Was spricht dagegen, dass raise automatisch ein geeignetes Objekt am Heap alloziert? Wie du richtig schreibst, führt ohnedies kein Weg daran vorbei, das muss daher nicht explizit im Programm stehen. Und für den Programmierer ist völlig egal, wo genau dieses Objekt sitzt, ob im Heap oder sonstwo. Die Compiler Magic kümmert sich ja sogar um die korrekte Freigabe des Objekts, während bei "normalen" Heap-Objekten der Programmierer verantwortlich ist.
Und mir fällt, außer der Zuweisung von Parametern an lokale Felder, überhaupt nichts sinnvolles ein, was man im Create einer Exception machen könnte.

Ich versuche so wenig verschiedene Programmkonstrukte wie möglich einzuführen, daher das "except" Attribut in einer normalen Klasse statt eines speziellen "exception" Typ.

Im Prinzip ist der Ansatz für mich nachvollziehbar. Aber die Behandlung von Exceptions IST etwas völlig anderes als alles, was irgend eine "normale" Klasse leistet, und es gibt mit raise und try-except schon zwei dedizierte Sprachkonstrukte, die nur dafür zuständig sind. Da kommt es auf einen separaten, von normalen Klassen abweichenden Datentyp (der nebenbei die ganze Schreibweise wesentlich vereinfachen würde), wirklich auch nicht mehr an, insbesondere, weil für diesen Typ von den vielen anderen Features, die Klassen bieten, gar keine gebraucht werden.

mse hat geschrieben:Wenn man keine Nutzlast benötigt, kann man im "except"-Teil aufgrund des Object-Typ verzweigen. Da lediglich pointer verglichen werden, ist die performance optimal.

Wenn man anständigen Code schreibt, dann ist eine Exception immer die seltene Ausnahme, für den Fall, dass etwas schiefgelaufen ist, was eigentlich nicht passieren dürfte und der Programmierer nicht wirklich vorgesehen hat, meistens ein Fehler in einem anderen Programmmodul, der sich an dieser Stelle auswirkt. "Normale" Fehlerbedingungen sollte man m.E. vorher abfragen und nicht über ein try-Except abfangen. Deshalb hat die Performance gerade des raise-Statements und des except-Blocks keine Relevanz. Auch wenn das etwas langsamer ist, sollte das auf ein ordentlich designtes Programm unter normalen Umständen überhaupt keinen Einfluss haben, weil der Block im Normalfall gar nicht zur Ausführung kommen dürfte.

Gegen einen festen Klassentyp als Exceptionobjekt wie von dir vorgeschlagen sträubt sich in mir alles.

Ich würde diesen Exception-Typ gar nicht als (festen) Klassentyp bezeichnen, sondern als eigenen Typ, der nur in Verbindung mit raise und try-except zu verwenden ist, während normale Klassen an der Stelle gar nichts verloren haben. Wie ich oben geschrtieben habe, sind raise und try-except dedizierte Sprachkonstrukte nur für das Exception-Handling, denen kann man dann schon auch einen eigenen, für die Aufgabe besser geeigneten Datentyp gönnen :wink:
Ob der Datentyp intern als Klassentyp implementiert ist, kann dem Programmierer egal sein.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Exceptions in MSElang

Beitrag von mse »

braunbär hat geschrieben:Aber die Behandlung von Exceptions IST etwas völlig anderes als alles, was irgend eine "normale" Klasse leistet, und sie hat mit raise und try-except schon zwei dedizierte Sprachkonstrukte, die nur dafür zuständig sind. Da kommt es auf einen separaten, von normalen Klassen abweichenden Datentyp (der nebenbei die ganze Schreibweise wesentlich vereinfachen würde), wirklich auch nicht mehr an, insbesondere, weil für diesen Typ von den vielen anderen Features, die Klassen bieten, gar keine gebraucht werden.

Z.B. eine Datenbank exception kann schon noch etwas komplizierter sein. Und natürlich kommt es auf jedes weitere Sprachkonstrukt an. ;-)
Eine exception-Klasse hat die gleiche Funktion wie z.B. eine message-Klasse. Warum sollte eine exception-Klasse ein spezieller Typ sein?
Ich verstehe nicht, warum du auf den Leistungsumfang von Klassen verzichten willst nur um statt

Code: Alles auswählen

 
 raise e1.create();
 


Code: Alles auswählen

 
 raise e1();
 

schreiben zu können? Weitere Nachteile hat es ja keine.

BeniBela
Beiträge: 308
Registriert: Sa 21. Mär 2009, 17:31
OS, Lazarus, FPC: Linux (Lazarus SVN, FPC 2.4)
CPU-Target: 64 Bit

Re: Exceptions in MSElang

Beitrag von BeniBela »

Warum überhaupt einen Konstruktor?

Man könnte doch auch alle Objekte mit "Klasse(...)" statt "Klasse.create(...)" erstellen

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Exceptions in MSElang

Beitrag von braunbär »

@Beni Bela
Bei normalen Klassen macht es oft Sinn, unterschiedliche Konstruktoren zu definieren. und in den Konstruktoren kann eine ganze Menge Zeug passieren.

Eine Exception dient dazu, Fehlerbedingungen abzufangen, entsprechende Meldungen auszugeben und sicher zu stellen, dass das Programm an einer definierten Stelle sinnvoll fortgesetzt wird. Im Konstruktor einer Exception wird nichts weiter gemacht, als nähere Informationen zu den Fehlerbedingen zu speichern. Ich kenne kein Beispiel und kann mir auch keines vorstellen, bei dem im Konstruktor einer Exception irgend etwas darüber hinaus sinnvoll sein könnte, denn die eigentliche Behandlung des Fehlers erfolgt an Hand der übergebenen Parameter im Except-Block.

mse hat geschrieben:Warum sollte eine exception-Klasse ein spezieller Typ sein?

Die Exception-Behandlung ist ein ganz spezielles Sprachkonstrukt. Ich kann mir z.B. auch keine im Entferntesten sinnvolle Anwendung von Methoden (außer dem Create, und das nur zum Daten schaufeln) in einer Exceptionklasse vorstellen. Für die Aufgabe, für die Exceptions gedacht sind, ist das alles ein Overkill. Da geht es ums "Aufräumen" nach einer Fehlerbedingung, und das passiert im Except-Block. Die Exception-Klasse ist nur dazu da, nähere Informationen über die Fehlerbedingungen an den Except-Block zu übergeben, mehr braucht die NIE zu können.
Und das Exception-Objekt IST kein gewöhnliches Klassenobjekt, sondern ein "spezieller Typ", weil es automatisch durch Compiler-Magic aus dem Heap entfernt wird, was bei anderen Klasseninstanzen nicht der Fall ist (Um die Entfernung von Komponenten aus dem Heap braucht sich der Anwender von Komponenten zwar auch nicht zu kümmern, aber das ist Aufgabe des Programmcodes des Owners und erfolgt nicht durch Compiler Magic).

mse hat geschrieben:Weitere Nachteile hat es ja keine.

Du brauchst für jede Exception Klasse eine eigene Create-Routine, die NIE etwas anderes zu tun hat, als die Parameter umzuschaufeln. Es ist ganz einfach eine Menge IMMER unnötiger Code.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Exceptions in MSElang

Beitrag von mse »

braunbär hat geschrieben:
mse hat geschrieben:Warum sollte eine exception-Klasse ein spezieller Typ sein?

Die Exception-Behandlung ist ein ganz spezielles Sprachkonstrukt. Ich kann mir z.B. auch keine im Entferntesten sinnvolle Anwendung von Methoden (außer dem Create, und das nur zum Daten schaufeln) in einer Exceptionklasse vorstellen. Für die Aufgabe, für die Exceptions gedacht sind, ist das alles ein Overkill. Da geht es ums "Aufräumen" nach einer Fehlerbedingung, und das passiert im Except-Block. Die Exception-Klasse ist nur dazu da, nähere Informationen über die Fehlerbedingungen an den Except-Block zu übergeben, mehr braucht die NIE zu können.
Und das Exception-Objekt IST kein gewöhnliches Klassenobjekt, sondern ein "spezieller Typ", weil es automatisch durch Compiler-Magic aus dem Heap entfernt wird, was bei anderen Klasseninstanzen nicht der Fall ist (Um die Entfernung von Komponenten aus dem Heap braucht sich der Anwender von Komponenten zwar auch nicht zu kümmern, aber das ist Aufgabe des Programmcodes des Owners und erfolgt nicht durch Compiler Magic).

Das sind grosse Worte, woher weisst du so genau was alle Programmierer brauchen? MSE-Produkte sind genau nicht in der Meinung konstruiert, dass der Tool-Autor am besten weiss wie es gemacht werden soll. Das ist bei MSEide+MSEgui so und das soll auch bei MSElang so sein. Wenn ein User in MSEgui eine Einschränkung findet, welche verhindert, dass er ein Problem auf seine Art lösen kann, versuche ich die Einschränkung zu entfernen statt dem User zu Verstehen zu geben, dass er dumm programmiert.
Bei einer Universal-Programmiersprache ist die Offenheit und Unvoreingenommenheit noch viel wichtiger. Auch Orthogonalität ist enorm wichtig.
mse hat geschrieben:Weitere Nachteile hat es ja keine.

Du brauchst für jede Exception Klasse eine eigene Create-Routine, die NIE etwas anderes zu tun hat, als die Parameter umzuschaufeln. Es ist ganz einfach eine Menge IMMER unnötiger Code.

Hier z.B. econnectionerror:

Code: Alles auswählen

 
{ econnectionerror }
 
constructor econnectionerror.create(const asender: tcustomsqlconnection;
   const amessage: ansistring; const aerrormessage: msestring;
    const aerror: integer);
begin
 fsender:= sender;
 ferror:= aerror;
 ferrormessage:= aerrormessage;
 if asender <> nil then begin
  inherited create(asender.name+': '+amessage);
 end
 else begin
  inherited create(amessage);
 end;
end;
 

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Exceptions in MSElang

Beitrag von braunbär »

Wie schon geschrieben, du sollst natürlich machen, was du willst.
Du hast nach Meinungen gefragt, darauf habe ich geantwortet.

Dein Beispiel untermauert eigentlich nur meine Sicht, denn in Wirklichkeit macht auch dieses Create mit allergrößter Wahrscheinlichkeit nichts anderes als die Parameter in lokale Felder zu schaufeln, nur etwas undurchsichtiger - das Inherited create wird nämlich wohl auch nur das machen und sonst nichts.
Ob "Connectionname:" erst im create an den String amessage angehängt wird, oder schon beim raise, oder überhaupt nur via fsender/asender an den Except-Block weitergegeben wird, dürfte ziemlich egal sein. Letzteres wäre für den Programmierer, der die Komponente benutzt, die flexibelste Variante.
Wenn du aber den Connection-namen vor den Messagetext setzen willst, kein create zur Verfügung steht und die Exception potentiell an mehreren Stellen "ge-raised" wird, kannst du eine
function AddConnectionNameToMessage(const msg: ansistring; conn:tcustomsqlconnection ): ansistring; (oder so :D )
schreiben, die dir den Connection-Namen vor den Messagestring setzt, wenn die Connection nicht nil ist, und erzeugst mit dieser Funktion den endgültigen Messagetext beim Raise. Ist immer noch etwas weniger Code als deine Create Routine. Aber, noch besser, überlässt du es eben überhaupt dem Programmierer der Anwendung, ob und vor allem in welcher Form er dem User den Namen der Connection weitergibt, der ihm ja im Except-Block via fsender ohnedies zur Verfügung steht. Vielleicht möchte er den Namen ja lieber in einer separaten Zeile ausgeben. :wink:

Das sind grosse Worte, woher weisst du so genau was alle Programmierer brauchen?

Es geht doch darum, wozu die verschiedenen Sprachkonstrukte dienen. Daraus ergibt sich, was notwendig ist. Ein While-Schleife braucht auch keine Methodendeklarationen vorzusehen. Das weiß ich genau, auch ohne zu wissen, was alle Programmierer brauchen.

Antworten