Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Sonstige Fragen zu Delphi (https://www.delphipraxis.net/19-sonstige-fragen-zu-delphi/)
-   -   Guter Stil oder 'Wie programmiere ich vernünftig?' (https://www.delphipraxis.net/67715-guter-stil-oder-wie-programmiere-ich-vernuenftig.html)

TPok 18. Apr 2006 23:19


Guter Stil oder 'Wie programmiere ich vernünftig?'
 
Hallo,

leider ist mir kein besserer Threadtitel eingefallen.
In letzter Zeit bin ich beim Programmieren immer wieder auf Konstrukte gestoßen, bei denen ich mir unsicher bin, wie ich sie in sauberen Quellcode presse. Natürlich habe ich mir dazu Gedanken gemacht und werde diese hier darstellen. Ich bitte euch, meine Gedanken zu kommentieren und mir ggf. einen besseren Weg vorzuschlagen.

Ich verwende in meinen Beispielen hier aufgrund meines aktuellen Projektes .Net-Code. Die Probleme lassen sich aber ebenso in Win32-Code darstellen.

Fall 1:
Ich führe eine Prozedur aus, die auf verschiedene Werte aus einer Ini-Datei angewiesen ist. Fehlt einer der Werte, kann die Aufgabe nicht erfüllt werden. Es soll dann eine Fehlermeldung ausgegeben und die Verarbeitung abgebrochen werden. Prinzipiell läßt sich dieses Problem auf jeden Programmteil verallgemeinern, der linear abgearbeitet und im Fehlerfall unterbrochen werden soll.

Lösungsidee:
Einlesen aller Werte nacheinander. Falls ein Wert fehlt -> Ausgabe einer Meldung und Prozedurabbruch per exit
Beispiel:
Delphi-Quellcode:
If not ReportDoc.GetLogOnData(ReportDoc.GetIniValue('AutoDB'), loServer,
  loDB, loUser, loPass) then
begin
  MessageBox.Show('Es konnten keine Logindaten für die Datenbank ' +
    'gefunden werden.' + #13#10 + 'Ini-Datei, Key: AutoDB',
    'Fehler', MessageboxButtons.OK, MessageBoxIcon.Error);
  exit
end;

If Directory.Exists(OutputPath) then
begin
  MessageBox.Show('Das Ausgabeverzeichnis existiert bereits.',
   'Fehler', MessageboxButtons.OK, MessageBoxIcon.Error);
  exit
end
else
  try
    Directory.CreateDirectory(OutputPath)
  except
    MessageBox.Show('Fehler beim Erstellen des Ausgabeverzeichnisses.' +
      Environment.NewLine + '(' + OutputPath + ')',
      'Fehler', MessageboxButtons.OK, MessageBoxIcon.Error);
    exit
  end;
Kann man das so machen?
Ein Problem fällt mir dabei sofort auf. Diesen handle ich mir durch das exit ein. Falls ich vor diesem Abschnitt z.B. einen Button deaktiviere und danach wieder aktivieren will, wird dies im Fehlerfall nicht ausgeführt, da die Prozedur einfach abgebrochen wird.

Fall 2:
Es ist ja hinlänglich bekannt, dass man zum Ressourcenschutz try..finally..end-Blöcke verwendet. Was mache ich aber, wenn ich gleich mehrere Objekte zur Laufzeit erstellen muß? Es kann ja prinzipiell überall ein Fehler auftreten. Das einzige, was mir einfällt, wäre ein Schachteln der Blöcke.

Hier ein Beispiel:
Delphi-Quellcode:
SqlConn := SqlConnection.Create;
try
  SqlConn.ConnectionString := 'Application Name=' + self.Text + ';' +
    'Persist Security Info=True;' +
    'Data Source=' + loServer + ';' +
    'Initial Catalog=' + loDB + ';' +
    'User Id=' + loUser + ';' +
    'Password=' + loPass + ';';

  SqlComm := SqlCommand.Create(SqlAbfrage, SqlConn);
  try
    LogLines := StringCollection.Create;
    try
      try
        SqlConn.Open;
      except
        MessageBox.Show('Fehler bei der Anmeldung an der Datenbank.', 'Fehler',
        MessageboxButtons.OK, MessageBoxIcon.Error);
        exit
      end;

      try
        SqlDR := SqlComm.ExecuteReader;
      except
        // Fehlermeldung ausgeben...
      end;
       
      try
        // Daten hier verarbeiten
      finally
        SqlDR.Free
      end;
    finally
      LogLines.Free
    end;
  finally
    SQLComm.Free
  end:;
finally
  SQLConn.Free
end;
Also wenn das nich grausam aussieht... Geht sowas nicht hübscher?

Das waren jetzt erstmal die 2 Fälle, die mir besonders auf dem Herzen lagen. Ich hoffe, ihr könnt mir weiterhelfen, damit sich mein Programmierstil wieder ein Stück verbessern kann. Falls mir noch etwas einfällt, weiß ich ja, wo ich Hilfe bekomme. :wink:

Danke für eure Hilfe,
Stephan

Luckie 18. Apr 2006 23:34

Re: Guter Stil oder 'Wie programmiere ich vernünftig?'
 
Zum ersten Fall:
Ich werfe in so einem Fall eine Exception, dich ich dann im Aufrufer abfange. Beispielsweise habe ich eine Klasse, in dieser Klasse wird einer Eigenschaft eine Datei zugewiesen, existiert diese Datei nicht, werfe ich eine Exception, da ohne diese Datei alles weitere sinnlos ist. Somit bricht die Klasse selber alles weiter ab und ich mussmich nur noch darum kümmern die Exception zu behandeln.

Zum zweiten Fall:
Brauche ich meherer Objekte, erstelle ich alle Objekte gleich am Anfang und gebe sie auch alle im gleichen finally-Block wieder frei.

jbg 18. Apr 2006 23:35

Re: Guter Stil oder 'Wie programmiere ich vernünftig?'
 
Zitat:

Zitat von TPok
Ein Problem fällt mir dabei sofort auf. Diesen handle ich mir durch das exit ein. Falls ich vor diesem Abschnitt z.B. einen Button deaktiviere und danach wieder aktivieren will, wird dies im Fehlerfall nicht ausgeführt, da die Prozedur einfach abgebrochen wird.

Du kannst dein Disable/Enable des Buttons mittels einer try/finally "schützen". Das Exit springt dann in das finally.

Zitat:

Fall 2:
Es ist ja hinlänglich bekannt, dass man zum Ressourcenschutz try..finally..end-Blöcke verwendet. Was mache ich aber, wenn ich gleich mehrere Objekte zur Laufzeit erstellen muß? Es kann ja prinzipiell überall ein Fehler auftreten. Das einzige, was mir einfällt, wäre ein Schachteln der Blöcke.
Alternativ kannst du auch alle Variablen vor dem ersten try auf NIL setzen und dann in einem einzigen finally diese freigeben, ohne dich um "if variable <> nil then..." zu kümmern, weil das .Free bzw. FreeAndNil selbst abfrägt. Dabei sollte aber vom Destruktor keine Exception geworfen werden, weil sonst alle folgenden Free Aufrufe nicht mehr durchgeführt werden.

TPok 18. Apr 2006 23:56

Re: Guter Stil oder 'Wie programmiere ich vernünftig?'
 
Hallo,

Zitat:

Zitat von Luckie
Zum ersten Fall:
Ich werfe in so einem Fall eine Exception, dich ich dann im Aufrufer abfange.

Da fallen mir dann 2 Realisierungsmöglichkeiten ein. Angenommen ich lagere so eine Abarbeitung in eine eigene Prozedur aus und im Fehlerfall werfe ich eine Exception. Wenn ich jetzt die einzelnen Fehler unterscheiden möchte, könnte ich entweder verschiedene Exception-Typen ableiten und verwenden oder einen Typ und die Art des Fehlers dann in der Message der Exception hinterlegen.
Eingene Exception-Typen wärend da sauberer wegen einer möglichen Behandlung durch:
Delphi-Quellcode:
except
  on ... do
  on ... do
Richtig?

Zitat:

Zitat von Luckie
Zum zweiten Fall:
Brauche ich meherer Objekte, erstelle ich alle Objekte gleich am Anfang und gebe sie auch alle im gleichen finally-Block wieder frei.

Kann nicht auch z.B. die Erstellung des 3. Objektes fehlschlagen? Das wäre dann nicht abgefangen. Ich meine unter .NET hat man ja immernoch den Garbage Collector als Sicherheitsnetz...

Zitat:

Zitat von jbg
Alternativ kannst du auch alle Variablen vor dem ersten try auf NIL setzen...

Jetzt muß ich nochmal dumm nachfragen. Sind Objektvariablen direkt nach der Deklaration ohne vorherige Verwendung nicht immer mit NIL initialisiert?


Dazu fällt mir noch eine Frage ein:
Wenn ich mir z.B. im MSDN .NET-Beispielcode anschaue (meist in C#), werden da ja eigentlich nie Destruktoren aufgerufen. Also sowas wiw Free bzw. FreeAndNil. Bei Forms und einigen wenigen Sachen gibts zwar ein Dispose, aber auch das wird nie explizit aufgerufen. Lassen die alles über den Garbage Collector aufräumen? Sind wir Delphianer die auberen Programmierer? Und kann ich bei Objekten, die kein Dispose anbieten den Aufruf von Free auch einfach weglassen, weil dieser sowieso nix macht?

Gruß,
Stephan

Lemmy1 19. Apr 2006 00:02

Re: Guter Stil oder 'Wie programmiere ich vernünftig?'
 
Wenn Du in .Net arbeitest, brauchst Du eh nicht alle Objekte freigeben. Ein Free bringt effektiv nur etwas auf einem Objekt, dass
a) entweder Free selbst implemetiert (kommt selten vor)
b) IDisposable implementiert

In Delphi.Net hat allerdings jede Klasse eine Free-Methode, da hier objekt-helpers / compiler magic einschreitet.

Ansonsten finde ich das Schachteln von try/finally Blöcken aber das Sauberste. Wenn man das auch nur noch bei Objekten aufruft, wo es auch nötig ist, dann wird die Verschachtelungstiefe auch nicht sooo tief (SqlCommand braucht zum Beispiel kein Free, da nicht IDisposable).

Wenn eine Klasse aber IDisposable implementiert, dann sollte man das Dispose auch aufrufen. In Delphi kann man dazu Free aufrufen. In anderen .Net Sprachen wird Dipose meistens schon aufgerufen, man muss nur wissen, wo man gucken muss.

Wenn man es vergisst, ist es aber meistens auch nicht ganz so schlimm, da irgendwann der Garbage Collector einschreitet.

Grüße

jbg 19. Apr 2006 00:07

Re: Guter Stil oder 'Wie programmiere ich vernünftig?'
 
Zitat:

Zitat von TPok
Jetzt muß ich nochmal dumm nachfragen. Sind Objektvariablen direkt nach der Deklaration ohne vorherige Verwendung nicht immer mit NIL initialisiert?

Nur Variablen, die eine Referenzzählung haben: String, dyn. Array, IInterface, Variant.


Zitat:

Dazu fällt mir noch eine Frage ein:
Wenn ich mir z.B. im MSDN .NET-Beispielcode anschaue (meist in C#)
Unter .NET gibt es keine deterministischen Destruktoren. Diese werden aufgerufen, wenn der GC meint, er müsste jetzt Rechenzeit verbrauchen. Ein (natives) Delphi.Win32 Programm besitzt keinen GC. Somit musst du dich selbst ums Aufräumen kümmern. Hast dann aber auch den Vorteil, dass du deine Destruktoren "deterministisch" aufrufen kannst. Um wenigstens ein wenig Determinismus bei .NET Destruktoren zu haben (die übrigens Finalize() heißen), hat man das IDispose Interface implementiert. Das muss man dann aber auch selbst aufrufen. Bei Datei-Streams ist es zum Beispiel etwas blöd, wenn die Datei erst geschlossen wird, wenn der GC mal Zeit hat.


Mittels Interfaces kann man sich AutoPtr (C++) basteln. (muss ich mal schnell was zusammenschreiben...)

Luckie 19. Apr 2006 00:08

Re: Guter Stil oder 'Wie programmiere ich vernünftig?'
 
Zitat:

Zitat von TPok
Kann nicht auch z.B. die Erstellung des 3. Objektes fehlschlagen?

Dann wird aber automatisch der Destructor aufgerufen.

Lemmy1 19. Apr 2006 00:14

Re: Guter Stil oder 'Wie programmiere ich vernünftig?'
 
Ein Blick in Borland.Delphi.System bringt übrigens auch etwas Klarheit dahin, was .Free eigentlich bewirkt:

Delphi-Quellcode:
procedure TObjectHelper.Free;
var
  FreeNotify: IFreeNotify;
begin
  if (Self <> nil) and (Self is IDisposable) then
  begin
    // ... snip ... (copyrighted high-tech code)
    (Self as IDisposable).Dispose;
  end;
end;

jbg 19. Apr 2006 00:18

Re: Guter Stil oder 'Wie programmiere ich vernünftig?'
 
Hier mal meine schnell zusammengetippte AutoPtr Klasse:
Delphi-Quellcode:
unit AutoPtr;

interface

type
  IAutoPtr = interface
    function AsPointer: Pointer;
    function AsObject: TObject;
  end;

function CreateAutoPtr(Value: TObject): IAutoPtr;

implementation

type
  TAutoPtr = class(TInterfacedObject, IAutoPtr)
  private
    FValue: TObject;
  public
    constructor Create(AValue: TObject);
    destructor Destroy; override;
    function AsPointer: Pointer;
    function AsObject: TObject;
  end;

function CreateAutoPtr(Value: TObject): IAutoPtr;
begin
  Result := TAutoPtr.Create(Value);
end;

{ TAutoPtr }

constructor TAutoPtr.Create(AValue: TObject);
begin
  inherited Create;
  FValue := AValue;
end;

destructor TAutoPtr.Destroy;
begin
  FValue.Free;
  inherited Destroy;
end;

function TAutoPtr.AsObject: TObject;
begin
  Result := FValue;
end;

function TAutoPtr.AsPointer: Pointer;
begin
  Result := FValue;
end;

end.
Und hier, wie man den AutoPtr nutzt
Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
var
  List, List2, List3: TList;
begin
  List := CreateAutoPtr(TList.Create).AsPointer;
  List2 := CreateAutoPtr(TList.Create).AsPointer;
  List3 := CreateAutoPtr(TList.Create).AsPointer;

  List.Add(nil);
  List2.Add(nil);
  List3.Add(nil);
end; // hier räumt Delphi dann automatisch auf

TPok 19. Apr 2006 00:21

Re: Guter Stil oder 'Wie programmiere ich vernünftig?'
 
Alles klar. Auf den Blick in die Borland.Delphi.System hätte ich auch kommen können. Das heißt also, wenn das Objekt IDisposable nicht implementiert, ist ein Free überflüssig. Ansonsten sollte man es aufrufen. So dachte ich mir das auch.

Ich werde mich mal an die Umstrukturierung meines Queltextes machen. Bei neuen Frage, melde ich mich bestimmt wieder.

Bis dahin,
Stephan


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:38 Uhr.
Seite 1 von 2  1 2      

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