Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Die Delphi-IDE (https://www.delphipraxis.net/62-die-delphi-ide/)
-   -   Verträge für Delphi / Design by Contract (https://www.delphipraxis.net/175501-vertraege-fuer-delphi-design-contract.html)

dominikkv 26. Jun 2013 12:02

Verträge für Delphi / Design by Contract
 
Hallo,

gibt es eine Möglichkeit, in Delphi Verträge (Contracts) benutzen zu können? Ich würde gerne das Konzept Design by Contract ausprobieren, bei dem man bei abstrakten Datentypen (ADT) die Methoden an Vor- und Nachbedingungen binden kann (preconditions/require, postconditions/ensure, invariants).

Für Java gibt es verschiedene Plugins, gibts sowas auch für Delphi?


Grüße
Dominik

jaenicke 26. Jun 2013 12:12

AW: Verträge für Delphi / Design by Contract
 
Require und ensure sind nur in Oxygene (ehemals Delphi Prism / Prism) Teil der Sprache. Wenn du doch dafür interessiert und Object Pascal nutzen willst, würde ich dir raten in die Richtung zu gehen.
http://www.remobjects.com/oxygene/language/special.aspx

In Delphi selbst gibt es dafür nichts, da musst du das selbst im Quelltext umsetzen ohne dafür eine Sprachunterstützung zu haben. Auch Addons kenne ich dafür nicht (kann mir aber auch schwer vorstellen was die dabei helfen sollten).

sx2008 26. Jun 2013 13:06

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von jaenicke (Beitrag 1219786)
In Delphi selbst gibt es dafür nichts

Das ist wirklich sehr schade weil design by contract würde sehr gut zu dem Konzept einer stark typisierten Sprache passen.
Delphi hat in den letzten 12 Jahren leider viel an Boden verloren.
Also Emba: baut das ein, Pre-/Postconditions und Invariants wären doch relativ einfach unzusetzen.

jaenicke 26. Jun 2013 14:05

AW: Verträge für Delphi / Design by Contract
 
Ich sehe allerdings nicht viele Vorteile gegenüber rein eigenem Code. Denn angeben muss man das so oder so und ob man die Bedingungen nun komplett prüft oder nur die Bedingungen hinschreibt... Eine Funktion wie Assert für diese Fälle kann man auch selbst schreiben, die das Ergebnis prüft.

Invariants lassen sich natürlich nicht ganz so einfach umsetzen, weil man deren Prüfung manuell in jede Methode einer Klasse packen müsste...

Phoenix 26. Jun 2013 14:37

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von jaenicke (Beitrag 1219795)
Ich sehe allerdings nicht viele Vorteile gegenüber rein eigenem Code. Denn angeben muss man das so oder so und ob man die Bedingungen nun komplett prüft oder nur die Bedingungen hinschreibt... Eine Funktion wie Assert für diese Fälle kann man auch selbst schreiben, die das Ergebnis prüft.

Moment. Design by Contract geht doch weit über Runtime-Checks und Assertions hinaus.

Bei echtem Design by Contract prüft Dir schon der Compiler ob die Argumente passen. Wenn Du einen Check <> nil drin hast, und die Methode mit einem hart codierten nil in einem ganz seltenen Use-case aufrufst, dann fliegt Dir das mit einer selbstgebastelten Lösung erst beim Kunden um die Ohren wenn der richtig (bzw. falsch) rumklickt.

Mit echten Code Contracts sagt Dir aber schon der Compiler, das der eine Aufruf nicht passt weil er den Contract verletzt. Der Mehrwert ist nicht zu unterschätzen.

jaenicke 26. Jun 2013 14:53

AW: Verträge für Delphi / Design by Contract
 
Dass der Compiler so etwas erkennt, dürfte in echtem Code allerdings gegen Null gehen. Denn um bei deinem Beispiel zu bleiben eine Funktion absichtlich mit nil aufrufen, wenn die Funktion das gar nicht unterstützt, das mag vielleicht mal passieren, fällt aber beim Test auf und ist leicht zu finden.

Viel wahrscheinlicher und vor allem schwerer zu finden sind aber doch Fehler, die durch zur Laufzeit veränderte Variablen entstehen. Und die kann der Compiler ohnehin nicht erkennen.

dominikkv 26. Jun 2013 15:02

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von jaenicke (Beitrag 1219795)
Ich sehe allerdings nicht viele Vorteile gegenüber rein eigenem Code. Denn angeben muss man das so oder so und ob man die Bedingungen nun komplett prüft oder nur die Bedingungen hinschreibt... Eine Funktion wie Assert für diese Fälle kann man auch selbst schreiben, die das Ergebnis prüft.

Bei Objekten kann man natürlich die Preconditions mit Assert() prüfen, auch für die Postconditions kann man den Aufwand treiben. Allerdings ist dies dann Code, der in der Methode selbst ausgeführt wird. Das bedeutet, die Methode wird mit zusätzlichem Code aufgebläht, es vermischt sich Contract-Code mit normalem Code und der Sinn / Zweck des Contract-Codes ist nicht auf den ersten Blick ersichtlich.

Außerdem gehört das dann zum Verhalten des Objektes, und nicht zum Datentyp an sich. Der Vertrag vermischt sich mit der Implementierung.

Des Weiteren gibt es ein Einsatzgebiet bei dem das nicht funktioniert: abstrakte Datentypen. Einem Interface kannst du Pre- und Postconditions nicht beibringen. Da Interfaces für mich immer wichtiger werden (man soll mit Interfaces arbeiten, nicht direkt mit Objekten) habe ich gehofft, dass es ein Plugin / Addon für Delphi gibt. In Java kenne ich eins, dass dies mit Hilfe von Annotations umsetzt. Da man in Delphi auch Annotations einsetzen kann dachte ich, ich frag mal hier :-)

jaenicke 26. Jun 2013 15:44

AW: Verträge für Delphi / Design by Contract
 
Pluginmäßig lässt sich das denke ich in Delphi nicht umsetzen. Annotationen in Java bieten ganz andere Möglichkeiten...

Phoenix 26. Jun 2013 16:03

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von jaenicke (Beitrag 1219801)
Viel wahrscheinlicher und vor allem schwerer zu finden sind aber doch Fehler, die durch zur Laufzeit veränderte Variablen entstehen. Und die kann der Compiler ohnehin nicht erkennen.

Doch. Der Code der diese Variablen verändert verwendet ja auch Code Contracts. Und wenn Du dann eine Methode mit einem Contract hat, der nil als rückgabe erlaubt, und den Output in eine reinschieben willst die nil nicht erlaubt (ohne vorher if (blubb <> nil) zu machen), dann kann auch das der Compiler erkennen.

Genau das ist ja gerade die Mächtigkeit von Design by Contract. Wenn man das konsequent durchzieht werden die Contracts direkt gegeneinander validiert und es ist zur Compilezeit möglich sämtliche Fehler durch falsche Ein- und Ausgabeparameter auszufiltern.

Meflin 26. Jun 2013 16:14

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von Phoenix (Beitrag 1219809)
Genau das ist ja gerade die Mächtigkeit von Design by Contract. Wenn man das konsequent durchzieht werden die Contracts direkt gegeneinander validiert und es ist zur Compilezeit möglich sämtliche Fehler durch falsche Ein- und Ausgabeparameter auszufiltern.

Sehe ich völlig anders. Compile-Time Checking von Contracts ist sicherlich ein nice to have, ist aber in der Perfektion äquivalent zur Lösung des Halteproblems (und ist damit unmöglich). Der wichtige Teil an Contracts ist also durchaus der zur Laufzeit ;) Es sei denn deine Aussage bezog sich jetzt wirklich absolut ausschließlich auf falsche Parameter. Dann kann ich nur sagen: Contracts können weit mehr als das.

Stevie 27. Jun 2013 10:45

AW: Verträge für Delphi / Design by Contract
 
Was die Laufzeit Auswertung angeht, kann man das über AOP machen.

D-User 27. Jun 2013 19:58

AW: Verträge für Delphi / Design by Contract
 
für ein Vorbedingungs- Nachbedingungs- Zwischenbedingungs Sonstiges
Kontroll-Konstrukt kann man schön methodenlokale Prozeduren/
funktionen nutzen( die nat. ggf andere Methoden aufrufen).
Je nach Lage der methodenlokalen Variablen zur o.a. Prozedur/Fkt
kann man dann auf diese Variablen und die Parameter wie gewünscht zugreifen
oder aber aus dem Zugriff raushalten.

Mal so auf die Schnelle:

Delphi-Quellcode:
type
  TMyCheckSituation = (sVB, sSit1, sNB );

procedure TForm3.Mach(aInt: integer);
var
  TestMe: integer;

  procedure Chk( const MySituation: TMySituation);
  begin
    case MySituation of
      sVB: if (aInt<8) and ( TestMe<0) then // Testfehler!:
               // irgendwie Fehlerbedingung händeln, z.B: except.
               raise Exception.Create('Loud Cry');
      sSit1: ;// Check für Sit.1
      sNB: ;// Nachbedingungsckeck;
    end;
    DoMyGeneralTest;
  end;

var
  DontTestMe: integer;
begin
  Chk(sVB); // Situationstest Vorbedingung

  DontTestMe := 1;

  MachWas;
  TestMe := DontTestMe;

  {$REGION 'InnerCheck SaubereMitte'}
  Chk(sSit1); // Situationstest Situation 1
  {$ENDREGION}

  MachNochWas;

  Chk(sNB); // NachBedingung
end;

Namenloser 28. Jun 2013 02:27

AW: Verträge für Delphi / Design by Contract
 
Nur so eine Idee, die mir gerade kam, aber vielleicht könnte man auch records dafür missbrauchen.
Z.B.:
Delphi-Quellcode:
type
  EContractViolation = class(Exception)
  end;

  TObjectNotNil = record
  private
    FObj: TObject;
  public
    operator Implicit(const Obj: TObject): TObjectNotNil;
    operator Implicit(const Obj: TObjectNotNil): TObject;
  end;

operator TObjectNotNil.Implicit(const Obj: TObject): TObjectNotNil;
begin
  if not Assigned(Obj) then
    raise EContractViolation.Create("Reference must not be nil");
  Result.FObj := Obj;
end;

operator TObjectNotNil.Implicit(const Obj: TObjectNotNil): TObject;
begin
  Result := Obj.FObj;
end;

procedure DoSomethingWithObject(Obj: TObjectNotNil);
begin
  Obj.{...}
end;

DoSomethingWithObject(nil);
// => EContractViolation
Keine Ahnung, ob Delphi das Konstrukt überhaupt zulässt... ich hatte mit dem Implicit-Operator unter Delphi 2006 schon bei harmloseren Sachen Probleme, aber bei 2006 waren überladene record-Operatoren generell buggy.
Und natürlich ist das so, wie es hier steht, viel zu aufgebläht... aber vielleicht könnte man ja irgendwie einen Preprocessor schreiben, der den Code vor der Kompilierung generiert, oder vielleicht kann man mit Generics noch was rausholen.

Wahrscheinlich ist das alles nicht wirklich praktikabel, aber ich wollte die Idee gerade mal niederschreiben...

jaenicke 28. Jun 2013 05:35

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von NamenLozer (Beitrag 1219904)
Keine Ahnung, ob Delphi das Konstrukt überhaupt zulässt... ich hatte mit dem Implicit-Operator unter Delphi 2006 schon bei harmloseren Sachen Probleme, aber bei 2006 waren überladene record-Operatoren generell buggy.

Ja, das funktioniert so, ohne es gerade testen zu können, abgesehen von den falschen Stringdelimitern und dass das class vor operator fehlt. Auf diese Weise habe ich in ein Dictionary automatisch Lowercase Strings als Key gebracht.

Das funktioniert so aber auch schon mit Delphi 2006 würde ich behaupten, auch wenn ich das gerade nicht testen kann...
Ich habe dort schon relativ viel mit Klassenoperatoren gemacht und hatte damit keine Probleme.

Stevie 28. Jun 2013 06:59

AW: Verträge für Delphi / Design by Contract
 
Das geht auch mit einem generischen Record, so wie in diesem Blog gezeigt wird (für C#).

Kleiner Unterschied bei dieser Lösung allerdings ist, dass im Falle von nil die Exception vor dem Aufruf und nicht danach bzw innerhalb der aufgerufenen Methode geraised wird.

BUG 28. Jun 2013 17:18

AW: Verträge für Delphi / Design by Contract
 
Solche Lösung sind zwar gut gegen Nullpointer, biete aber keine statische Analysefähigkeit.

Das ist es, was die Design-by-Contract so wertvoll macht. Anfangs- und Endzustände sowie Invarianten checken kann man zwar manuell machen. Aber schon mit dem (erzwungenen) Vererbungen der "Contracts" wird ohne Compiler-Unterstützung kniffelig bis unmöglich.
Es ist wie mit der Typen: Wenn es nicht erzwungen wird, ist nicht so viel Wert.


Das Argument mit dem Halteproblem kann ich nicht nachvollziehen. Abgesehen von dem begrenzten Speicher sollte es problemlos möglich sein, eine Turingmaschine zu realisieren. Schließlich bieten die Contracts keine Aussage über die Laufzeit.

Namenloser 28. Jun 2013 17:50

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von BUG (Beitrag 1219958)
Das Argument mit dem Halteproblem kann ich nicht nachvollziehen. Abgesehen von dem begrenzten Speicher sollte es problemlos möglich sein, eine Turingmaschine zu realisieren. Schließlich bieten die Contracts keine Aussage über die Laufzeit.

Doch, das ist theoretisch schon ein Problem. Angenommen, dein Contract wäre, dass ein kryptographischer Hash des Parameters nicht 0 sein darf. Also z.B. SHA1(Parameter) <> 0. Viel Spaß dabei, zur Compilezeit auszuwerten, welche Werte erlaubt sind.

BUG 29. Jun 2013 16:46

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von NamenLozer (Beitrag 1219959)
Doch, das ist theoretisch schon ein Problem. Angenommen, dein Contract wäre, dass ein kryptographischer Hash des Parameters nicht 0 sein darf. Also z.B. SHA1(Parameter) <> 0. Viel Spaß dabei, zur Compilezeit auszuwerten, welche Werte erlaubt sind.

Hashen zur Compilezeit ist nicht wirklich unmöglich (oder auch nur unüblich).

Aber ich glaube, mir ist eben des Halteproblemargument aufgegangen:
Es ist nicht aufgrund des Halteproblems nicht möglich, herauszubekommen, ob zwei Prozeduren (~> Turingmaschinen) das selbe Ergebnis liefern.
Also müsst der Programmierer im Zweifelsfall vor dem Aufruf explizit auf die gleiche Bedingung prüfen, die in dem Contract spezifiziert ist. Das wäre unschön (keine Perfektion).


Ich glaube aber, dass wirklich praktisch auftretende Problem ist, dass Contracts nur eine weitere Spezifikationen sind,
die unvollständig oder schlicht gänzlich falsch sein können.

Namenloser 30. Jun 2013 22:57

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von BUG (Beitrag 1220065)
Zitat:

Zitat von NamenLozer (Beitrag 1219959)
Doch, das ist theoretisch schon ein Problem. Angenommen, dein Contract wäre, dass ein kryptographischer Hash des Parameters nicht 0 sein darf. Also z.B. SHA1(Parameter) <> 0. Viel Spaß dabei, zur Compilezeit auszuwerten, welche Werte erlaubt sind.

Hashen zur Compilezeit ist nicht wirklich unmöglich (oder auch nur unüblich).

Darum geht es aber nicht. Der Parameter ist ja dynamisch. Sagen wir, der Parameter wäre der Rückgabewert einer Funktion, die alles zwischen 0 und MAXINT zurückliefern kann. Wie soll der Compiler feststellen, ob dabei was herauskommen kann, was von der kryptographischen Hashfunktion auf die 0 abgebildet wird?

Phoenix 1. Jul 2013 14:05

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von NamenLozer (Beitrag 1220193)
Darum geht es aber nicht. Der Parameter ist ja dynamisch. Sagen wir, der Parameter wäre der Rückgabewert einer Funktion, die alles zwischen 0 und MAXINT zurückliefern kann. Wie soll der Compiler feststellen, ob dabei was herauskommen kann, was von der kryptographischen Hashfunktion auf die 0 abgebildet wird?

Genau darum geht es letzten Endes:

Du hast eine Funktion A die gemäß Contract einen beliebigen Wert zwischen 1 und 10 zurückliefern kann.
Du hast eine andere Funktion B, die einen Parameter hat der per Contract nur Werte zwischen 0 und 5 annehmen darf.

Wenn Du nun eine Variable aus A füllst und in B reinwirfst, dann kann Dir der Compiler eine Warnung um die Ohren hauen das dieses Ding Deinen Contract verletzt.

Du kannst nun entweder
* die Fälle von 6-10 mit einer Sonderbehandlung bedienen und nur die Werte bis 5 in B reinwerfen
* oder aber sicherstellen das A nur noch Werte zurückgibt die B auch annehmen kann
* oder aber dafür sorgen das B auch Werte bis 10 verträgt.

Machst Du nichts davon und ignorierst die Warnung einfach, DANN wirst Du möglicherweise zur Laufzeit Probleme bekommen.

Es geht ja nicht darum, vollumfänglich zur Compilezeit sicherzustellen das da nie was falsches reingegeben wird. Es geht darum, das Dir der Compiler vorab ausreichend Hinweise gibt wo es eben potentiell zu Contractverletzungen und damit zu möglichen Fehlerquellen kommen kann. So kann man nämlich vorher mögliche Problemfälle analysieren, automatisch testen und muss nicht erst warten bis es draussen irgendwo wegen einer seltsamen Eingabe mal in einem Edge-Case zu einem Fehler kommt.

stahli 1. Jul 2013 15:14

AW: Verträge für Delphi / Design by Contract
 
[OT]... Danke! Jetzt habe ich endlich ungefähr verstanden, worüber Ihr die ganze Zeit redet. :thumb:[/OT]

Meflin 1. Jul 2013 16:09

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von Phoenix (Beitrag 1220256)
Du hast eine Funktion A die gemäß Contract einen beliebigen Wert zwischen 1 und 10 zurückliefern kann.
Du hast eine andere Funktion B, die einen Parameter hat der per Contract nur Werte zwischen 0 und 5 annehmen darf.

Das Problem ist auch nicht unbedingt die Stelle zwischen "Nachbedingung A" und "Vorbedingung B", sondern die zwiwschen "Methode A" und "Nachbedingung A". Nehmen wir z.B. eine Methode wie (Pseudocode)

Code:
function randInt(max: Int): Int = {
    return Random.nextInt(max);
}
ensures: result <= max
Kann das dein konkreter Compile-Time Analyzer korrekt erkennen, oder liefert er, wovon ich in diesem Beispiel spontan ausgehen würde, ein false positive (im Sinne einer Warnung: Nachbedingung ist nicht sichergestellt). Dann will ich das nicht wirklich haben, weil wenn dann die statische Analyse zu einem Haufen Warnungen führt, die keine sein sollten, neigt man ja schnell wieder dazu, solche Warnungen gleich allgemein tendentiell eher zu ignorieren. Und selbst wenn es in diesem konkreten Beispiel funktioniert, gibt es doch genug (oder bessergesagt unendlich viele) Fälle, in denen es einfach nicht funktionieren kann.

Zur Compilezeit hilft mir aber eine Analyse hauptsächlich, wenn sie entweder zu 100% ausschließen kann, dass ein Vertrag verletzt wird (denn dann kann ich den Vertrag eliminieren) oder aber erkennen kann, dass ein Vertrag zwangsläufig verletzt wird. Den Mehrwert von Erkenntnissen in der Welt dazischen zur Compilezeit wage ich zu bezweifeln. Denn wenn ich o.g. Beispiel dann so schreibe, dass der Vertrag immer erfüllt wird, dann führt das zu absolut sinnbefreitem Code.

Apropos: die bisher genannten Beipsiele sprechen für mich eher gegen das verwendete Typsystem als für Verträge. Denn wenn es ein gutes Typsystem wäre, könnte ich dann nicht Dinge wie "kann oder kann nicht nil sein" oder aber "Zahl im Wertebereich X" auch direkt über die Typen ausdrücken ;) ?

Phoenix 1. Jul 2013 17:15

AW: Verträge für Delphi / Design by Contract
 
Die korrekte Funktion der Methode und die Voraussetzung das sie ihren Contract einhält würdest Du mit Unit-Tests abdecken.

Daher ist die Prüfung des 'Innenverhältnisses' des Vertrages nicht so relevant. Das 'Aussenverhältnis' ist das, was für eine solche Codeanalyse wirklich relevant ist.

Wenn die Nachbedingung nicht passt weil Dein Random.NextInt(max) sich falsch verhält und Werte > max liefert, dann wird das klar erst zur Laufzeit auffallen. Aber in aller Regel wird sowas eben entsprechend gut getestet sein damit das nicht passiert.

himitsu 29. Nov 2013 10:16

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von jaenicke (Beitrag 1219786)
Auch Addons kenne ich dafür nicht

Da es in Delphi leider "keine" Möglichkeit gibt, um einen PreCompiler reinzuhängen, wird es sowas auch nie geben können.

Wobei ich mich grade frage, wie das Eurekalog und vorallem AQTime machen?

jaenicke 29. Nov 2013 13:19

AW: Verträge für Delphi / Design by Contract
 
Zitat:

Zitat von himitsu (Beitrag 1237895)
Zitat:

Zitat von jaenicke (Beitrag 1219786)
Auch Addons kenne ich dafür nicht

Da es in Delphi leider "keine" Möglichkeit gibt, um einen PreCompiler reinzuhängen, wird es sowas auch nie geben können.

Gibt es durchaus. Siehe DLangExtensions usw.
Und über die Tools-API ist glaube ich nun mittlerweile noch mehr möglich.


Alle Zeitangaben in WEZ +1. Es ist jetzt 09:58 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz