Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Globale Variablen/Abhängigkeiten = Böse... Und nu? (https://www.delphipraxis.net/180414-globale-variablen-abhaengigkeiten-%3D-boese-und-nu.html)

Dejan Vu 19. Mai 2014 08:20


Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Hi,

Man liest ja immer wieder, das globale Variablen böse sind. Nun, sie sind ja gar nicht böse, diese unschuldigen Dinger, aber die Verwendung. Warum? Wegen der Abhängigkeiten, die man sich damit einhandelt, d.h. die Methode/Klasse, die globale Variablen verwendet, ist ohne diese Variablen nicht lebensfähig. Bestimmt gibt es noch andere Gründe und man könnte ganze Seiten füllen, aber das mache ich hier nicht.

Also soll man gar keine globalen Variablen verwenden? Blöderweise geht es (nicht immer) ohne sie nicht. Wie also dann? Was sind 'best practice'?

Beispiel (nicht von mir): Businessobjekte müssen prüfen, ob der angemeldete Benutzer über das entsprechende Recht verfügt, die Aktion des BO auszuführen. Also sieht die Aktion vielleicht so aus:
Delphi-Quellcode:
Procedure TMyBusinessObject.Action();
Begin
  If not GlobalStuff.CurrentUser.IsGranted (CanExecuteMyBusinessAction) then
    Raise ERightsException.Create('You are not allowed to do this');
 
  PerformAction();
End;
Hier ist der Fehler der, das ich damit das BusinessObject in seiner Verwendung einschränke. Ich werde es nie mit einem anderen User durchführen können, immer nur mit dem angemeldeten Benutzer. Lösung: Der Benutzer, der die Aktion durchführt, wird per DI zur Verfügung gestellt. Einfach ausgedrückt: Das BO bekommt eine Property 'User', die auch im Konstruktor übergeben wird. Der Anwender des BO übergibt dann vielleicht den 'CurrentUser' (und hat dann die Abhängigkeit an der Backe),
aber vielleicht gibt es auch Szenario, in dem gar kein User angemeldet ist (Test, Batch processing etc.)

Anderes Beispiel: Eine Meldung ausgeben. Eine Methode soll bei Erfolg eine Messagebox anzeigen.
Delphi-Quellcode:
Procedure TMyCommand.Execute();
Begin
  Try
    BusinessObject.Action();
    ShowMessage('Success');
  except
    On e:Exception do
       ShowError(e.Message);
  end
End;
Hier habe ich zwei Abhängigkeiten ('ShowMessage') und 'ShowError'. Nehmen wir an, die sind als Prozeduren so in Vcl.Dialogs deklariert. Schon habe ich mich bei 'TMyCommand' auf die Verwendung der VCL festgelegt. Ich kann das zwar auch abstrahieren, aber hier wird es dann albern, weil ich nicht finde, das man -nur weil Gurus sagen, das globale Abhängigkeiten böse sind- alles nachmachen muss, was angeblich richtig ist. Denn irgendwann wird es unübersichtlich und -jetzt kommts- unnötig kompliziert. Ich fange dann nämlich an, einen Wrapper zu schreiben, der z.B. die Messageboxen kapselt. Gut, wenigstens hätte ich dann nur noch eine Abhängigkeit:
Delphi-Quellcode:
Procedure TMyCommand.Execute();
Begin
  Try
    BusinessObject.Action();
    GlobalStuff.UIWrapper.ShowMessage('Success');
  except
    On e:Exception do
       GlobalStuff.UIWrapper.ShowError(e.Message);
  end
End;
So, und nun könnte ich den UIWrapper wieder als DI/Property angeben und wäre dann irgendwie aus dem Schneider. Nur müsste das Kommando nun zwei Abhängigkeiten haben, denn -logisch- der User wird ja auch von außen übergeben.

Delphi-Quellcode:
Constructor TMyCommand.Create (aUser : IUser; aUIWrapper : IUIWrapper ...)
begin
  BusinessObject := TMyBusinessObject.Create (aUser);
  UIWrapper := aUIWrapper;
End;

Procedure TMyCommand.Execute();
Begin
  Try
    BusinessObject.Action();
    UIWrapper.ShowMessage('Success');
  except
    On e:Exception do
       UIWrapper.ShowError(e.Message);
  end
End;
Nun soll das Kommando noch loggen und einen Report als Resultat der Business Action ausgeben. Puh, noch zwei globale Abhängigkeiten (Logger und ReportEngine). Gut, das Pattern ist ja bekannt, ergo
Delphi-Quellcode:
Constructor TMyCommand.Create (aUser : IUser; aUIWrapper : IUIWrapper; aLogger : ILogger; aReportEngine : IReportEngine)
begin
  BusinessObject := TMyBusinessObject.Create (aUser);
  UIWrapper := aUIWrapper;
  ...
End;
Ach, das BO persistiert die Daten ja auch noch in eine Datenbank... Kein Problem, dann wird die Datenbankverbindung hinzu, und zwar ein Wrapper, ist ja klar, denn auf einen Provider will man sich ja nicht festlegen. Und auf die globale Connection will man nicht zugreifen, weil ... böse.
Delphi-Quellcode:
Constructor TMyCommand.Create (aUser : IUser; aUIWrapper : IUIWrapper; aLogger : ILogger; aReportEngine : IReportEngine; aDatabaseConnection : IDbConnection)
begin
  BusinessObject := TMyBusinessObject.Create (aUser, aDatabaseConnection);
  UIWrapper := aUIWrapper;
  ...
End;
Procedure TMyCommand.Execute();
Begin
  Try
    BusinessObject.Action();
    ReportEngine.PrintReport(BusinessObject);

    UIWrapper.ShowMessage('Success');
    Logger.Success('Action');
  except
    On e:Exception do
       UIWrapper.ShowError(e.Message);
  end
End;
Ist das wirklich so gewollt?
Und wenn nicht: Wie macht man das denn richtig? Ich z.B. habe bemerkt, das ich eigentlich fast immer etwas falsch designt habe, wenn ich eine globale Abhängigkeit einführe. Aber in diesem Beispiel? Ich könnte alles, was es so an globalem Zeugs gibt, auch in eine einzige Klasse packen. Dann würde ich nur einen Parameter an den Konstruktor übergeben, aber genau genommen ist das genauso bescheuert wie die Verwendung eines globalen Scopes.

Wie macht ihr das? Oder schreibt ihr Anwendungen, bei denen die Klassen eigentlich nicht/selten wiederverwendet werden, weil sie ja doch sehr problemspezifisch sind?

Sherlock 19. Mai 2014 08:31

AW: Globale Variablen = Böse... Und nu?
 
Albern ist das richtige Wort. Theorie und Praxis wäre eventuell die sachlichere Zusammenfassung der Problematik.

Also: Globales vermeiden wo möglich, nutzen wo nötig.

Sherlock

Phoenix 19. Mai 2014 08:58

AW: Globale Variablen = Böse... Und nu?
 
Ja, das ist wirklich so gewollt.
Du willst das aber - aus guten Gründen - nicht immer alles von Hand zusammenstöpseln.

Ein guter IoC-Container nimmt Dir diese Arbeit ab und löst die Abhängigkeiten selber auf und instanziert Dir diese Klassen mit den Monster-Konstruktoren von alleine.

Du kannst natürlich auch dort, wo es Sinn macht, Argumente zusammenfassen.
z.B. der UIWrapper und den Report (Ein- / Ausgabe für den User) und den CurrentUser, die Connection und das technische Logging (Context).

Die Datenbankverbindung / Transactionhandling und das Logging würde man im übrigen vermutlich eher über Aspekte als über DI lösen, aber prinzipiell passt das sonst so.

Sir Rufo 19. Mai 2014 09:16

AW: Globale Variablen = Böse... Und nu?
 
Kann es sein, dass der Titel falsch ist?

Bis jetzt geht es um globale Abhängigkeiten und nicht um globale Variablen wie der Titel vermuten lässt. :gruebel:

Dejan Vu 19. Mai 2014 09:23

AW: Globale Variablen = Böse... Und nu?
 
Zitat:

Zitat von Sherlock (Beitrag 1259257)
Albern ist das richtige Wort.

Einerseits ja, andererseits nein. Das Dilemma der globalen Abhängigkeiten ist nicht von der Hand zu weisen. Und wenn man den Weg gehen will, seine Anwendungen auch übermorgen noch modern zu halten, führt leider kein Weg daran vorbei.

Zitat:

Zitat von Phoenix (Beitrag 1259262)
Ein guter IoC-Container nimmt Dir diese Arbeit ab und löst die Abhängigkeiten selber auf und instanziiert Dir diese Klassen mit den Monster-Konstruktoren von alleine.

D.h. eine Factory, die mir das gewünschte Objekt -wie auch immer- zusammenbastelt. Ob nun über DI, oder morgen über Delegates, oder Zusammenfassen von Abhängigkeiten ist dann wieder Schnurz.

D.h. (Hab kein aktuelles Delphi mit Generics, d.h. vielleicht Syntaxfehler)
Delphi-Quellcode:
Function TMyIocContainer.CreateCommand<TCommand : ICommand> : TCommand;
Begin
  result := TCommand.Create(CurrentUser, VCL_UIWrapper, TextFileLogger, FastReportReportingEngine, AnyDACConnection);
End;
Dann ist der Anwender des Containers wieder auf eine Globale Instanz des IocContainers angewiesen. Außer, ich gebe ihm das wieder per DI mit, oder wie?

Zitat:

Zitat von Sir Rufo (Beitrag 1259268)
Kann es sein, dass der Titel falsch ist?

Die globale Instanz einer Klasse ist ja eine globale Variable. Aber ich ändere den Titel.

Sir Rufo 19. Mai 2014 09:35

AW: Globale Variablen = Böse... Und nu?
 
Zitat:

Zitat von Dejan Vu (Beitrag 1259270)
Zitat:

Zitat von Sir Rufo (Beitrag 1259268)
Kann es sein, dass der Titel falsch ist?

Die globale Instanz einer Klasse ist ja eine globale Variable. Aber ich ändere den Titel.

Nein muss sie nicht zwangsläufig sein, denn dafür gibt es Möglichkeiten, diese bösen Nebeneffekte (unkontrolliertes und ungeschütztes Überschreiben) von globalen Variablen zu vermeiden:
Delphi-Quellcode:
unit FooUnit;

interface

type
  TFoo = class
  end;

//var
//  Foo : TFoo;

function Foo : TFoo;

implementation

var
  _Foo : TFoo;

function Foo : TFoo;
begin
  if not Assigned( _Foo ) then
    _Foo := TFoo.Create;
  Result := _Foo;
end;

finalization

_TFoo.Free;

end.
oder bei neueren Delphi Versionen
Delphi-Quellcode:
unit FooUnit;

interface

type
  TFoo = class
  private
    class var _Foo : TFoo;
    class destructor Destroy;
  public
    class function Default : TFoo;
  end;

implementation

class function TFoo.Default : TFoo;
begin
  if not Assigned( _Foo ) then
    _Foo := TFoo.Create;
  Result := _Foo;
end;

class destructor TFoo.Destroy;
begin
  _Foo.Free;
end;
Trotz allem bleibt aber immer noch die Abhängigkeit bestehen.

p80286 19. Mai 2014 10:21

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Entschuldigt, aber insbesonders das Problem mit dem "User" kann ich nicht nachvollziehen.
Der Benutzer sitzt vor der Tastatur und ein Anwendungsprogramm hat mit ihm selten etwas zu tun.
Es sei denn es gibt eine Benutzerverwaltung. Und diese sollte den Benutzer "global" bevorraten, und auf Anfrage des "Arbeitsteils" die gewünschten Informationen liefern.

Nicht mehr und nicht weniger.

Gruß
K-H

Phoenix 19. Mai 2014 10:27

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Zitat:

Zitat von p80286 (Beitrag 1259295)
Und diese sollte den Benutzer "global" bevorraten, und auf Anfrage des "Arbeitsteils" die gewünschten Informationen liefern.

Damit hast Du wieder eine Abhängigkeit darauf.
Was ist nun, wenn Du den Code automatisch testen willst? Du hast dann keinen User der sich eingeloggt hat, der hier global bereitstehen könnte.

Du willst den Test ggf. mit mehreren gemockten Usern und entsprechenden mehreren Rechte-Kombinationen testen um sicher zu gehen, dass der Code in allen Fällen mit allen notwendigen oder mit fehlenden Rechten wie entsprechend erwartet funktioniert.

Dazu ist es am einfachsten, den jeweils entsprechend präparierten Fake-User für den Test reinzugeben.
Bei einer globalen Abhängigkeit ist es schwierig bis unmöglich das so zu instrumentieren dass für jeden Test der korrekte User geliefert wird.

p80286 19. Mai 2014 10:36

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Zitat:

Zitat von Phoenix (Beitrag 1259297)
Dazu ist es am einfachsten, den jeweils entsprechend präparierten Fake-User für den Test reinzugeben.

Irgendwas habe ich da nicht verstanden, genau dafür gibt es die Benutzerverwaltung. Eigentlich sollte der Arbeitsteil nur Rechte kennen, der Benutzer dahinter sollte **egal sein.

Gruß
K-H

Sir Rufo 19. Mai 2014 10:55

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Abhängigkeit und schwer testbar
Delphi-Quellcode:
unit FooUnit;

interface

type
  TFoo = class
    property Value : string;
  end;

function Foo : TFoo;

implementation

var
  _Foo : TFoo;

function Foo : TFoo;
begin
  if not Assigned( _Foo ) then
    _Foo := TFoo.Create;
  Result := _Foo;
end;

end.
Delphi-Quellcode:
unit BarUnit;

interface

uses
  FooUnit;

type
  TBar = class
    function GetValue : string;
  end;

implementation

function TBar.GetValue : string;
begin
  Result := Foo.Value;
end;

end.
Das sollte man besser so machen:
Delphi-Quellcode:
unit BarUnit;

interface

uses
  FooUnit;

type
  TBar = class
  private
    FFoo : TFoo;
  public
    constructor Create( AFoo : TFoo );
    function GetValue : string;
  end;

implementation

constructor TBar.Create( AFoo : TFoo );
begin
  inherited;
  FFoo := AFoo;
end;

function TBar.GetValue : string;
begin
  Result := FFoo.Value;
end;

end.
Jetzt kann
Delphi-Quellcode:
TBar
auch mit unterschiedlichen
Delphi-Quellcode:
TFoo
-Instanzen getestet werden.

Dejan Vu 19. Mai 2014 12:26

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Zitat:

Zitat von p80286 (Beitrag 1259301)
genau dafür gibt es die Benutzerverwaltung. Eigentlich sollte der Arbeitsteil nur Rechte kennen, der Benutzer dahinter sollte **egal sein.

Ersetze 'User' durch 'Permissions'. Dann kennt das Businessobjekt nur Permissions. Genaugenommen ist das sogar besser, denn was interessiert es das Businessobjekt was das konkret für ein User ist.

Aber die 'Benutzterverwaltung' ist hier auch viel zu fett. Ich brauch an der Stelle keine Verwaltung, sondern nur den Scope/Kontext, der weiß, mit welchen Rechten gearbeitet wird.

Zitat:

Zitat von Sir Rufo (Beitrag 1259310)
Abhängigkeit und schwer testbar...

Sobald Du ein Mockingframework hast, das Dir deine statische Klassen mocken kann, ist das wurscht (Gibt es das für Delphi?).

Da imho jede sauber designte Klasse gut (ohne Mocks) testbar ist, aber nicht jede gut testbare Klasse automatisch sauber designt ist, würde ich das Argument 'lässt sich gut/schlecht testen' etwas nach hinten schieben, aber wirklich nur ein wenig. Wichtiger ist für mich: Ist die Klasse flexibel, erweiterbar, robust und wiederverwendbar?

Halten wir also fest:
1. Globale Abhängigkeiten auch auf Kosten der Tipparbeit durch DI auflösen.
2. Die nun umständlichen Instantiierungen über eine Factory abbilden und damit den Overhead verbergen.
3. Abhängigkeiten nur durch Interfaces abbilden.

So sieht das doch wirklich gut aus.
Im Falle des 'Users/Permission' sieht es in der Praxis ganz einfach so aus:
Das Businessobjekt (oder einfacher: die konkrete Aktion) kann in der Anwendung nur von einem Benutzer mit ausreichenden Rechten ausgeführt werden. Fein.

Aber in einem Batchprozess möchte ich gerne (oder ich muss) auf diese Rechte pfeifen. Ich will mir keinen Batchuser zusammenfriemeln, der bestimmte Rechte hat, wobei meine 'Benutzerverwaltung' vielleicht so gemein ist, aus Sicherheitsgründen zu verbieten, das jemand zwei bestimmte Rechte gleichzeitig innehat und das genau die sind, die ich gerade benötige.

Also baue ich mir in meinem Batchprozess meinen Pseudouser (also eine Klasse, die das IUser/IPermissions-Interface implementiert) nach und habe genau das, was ich wollte: Einerseits ein BO, das im Kontext der Anwendung und Aufgerufen durch ein Kommando genau die Sicherheit bietet, die ich brauche, aber sonst eine extrem flexible Klasse, deren Abhängigkeiten im Konstruktor für jeden sichtbar sind und die ich so flexibel wie möglich verwenden kann.

Wirklich? Fast, denn die Schnittstelle (hier wirklich: das Interface) der Abhängigkeiten sollte nur genau die Properties beinhalten, die für die Durchführung dieser Aufgabe wirklich notwendig ist.

Stevie 19. Mai 2014 12:31

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Das Thema entwickelt ja langsam eine echte Dynamik ;)

Um es mal vorweg zu schicken, da ich das immer in Verbindung mit diesem Thema (Test- und wartbarer Code) sage:
Es geht nicht um den Selbstzweck, sondern da steckt immer hinter, dass man etwas am Code ändert oder ihn in einer gewissen Form schreibt,
damit er test- und wartbar bleibt. Mit testbar ist hier nicht gemeint, dass ich hinterher 10 Minuten durch die Anwendung klicke bis ich an der Stelle bin, die mich eigentlich interessiert und eventuell noch nen Server und Datenbank aufsetzen muss dafür mit speziellen Daten. Sondern es geht um Unitests. Bei diesen teste ich isoliert ein bestimmtes Modul (meist eine Klasse) ohne externe Abhängigkeiten. Und wenn dann an meiner Klasse noch wie ein riesiges Wurzelwerk noch meine halbe Anwendung dran hängt, ist das große Sch....


Schöne Code Beispiele - da sie auf verschiedenen Ebenen einige Fehler aufzeigen.

Erster Fall. Die Aktion soll überprüfen, ob sie ausgeführt werden darf oder nicht.
Was hat damit ein Benutzer zu tun? Also neben der globalen Abhängigkeit noch eine LoD Verletzung, die es noch schlimmer macht.
Um das Teil zu testen muss ich nicht nur irgendwie den globalen User bei einem Test austauschen sondern dem Benutzer auch noch entsprechende Berechtigungen verpassen oder die IsGranted Funktion ausmocken, damit sie mir das erwartete Ergebnis mitteilt.
Übrigens wurde auch noch das SRP verletzt, denn die Aufgabe meiner Klasse ist nicht die Rechteüberprüfung sondern höchstwahrscheinlich irgendwas anders.

Zweiter Fall, die Kopplung an die GUI - hier sollte man sich überlegen, was genau von meinem Code benötigt GUI Interaktion. Wahrscheinlich habe ich irgendetwas, was durch GUI ausgelöst wird und was Meldungen zurück liefert. Aber auch in diesem Fall ist das die eine Aufgabe dieser Einheit: das Zwischenspiel von GUI und Programmlogik. Wenn ich nunmal die VCL verwende, dann ist es auch nicht böse, in einer solchen Klasse, VCL Elemente zu benutzen. Aber bitte nicht in der Programmlogik, die erstmal nix mit GUI am Hut hat.

D.h. der dritte Fall ergibt sich oft gar nicht, da man für diese GUI-Interaktionsklasse an die entsprechende GUI anpasst. Hier gibt es so viele Möglichkeiten, dass ich nur einfach mal die Begriffe MVP, MVC oder MVVM in den Raum werfe (die zu diskutieren wäre wohl eher ein eigener Thread).

Wenn dann die Anforderungen so implementiert werden, wie du es mit dem Logging, Reporting und Speichern in der Datenbank skizzierst, würde ich mir ernsthaft erneut Gedanken über die Verletzung des SRP machen. Zumindest Persistenz lässt sich prima in eine eigene Klasse zum Speichern auslagern. Logging schreit meist nach AOP, was wir in Delphi aber nicht so nativ haben, wie andere Sprachen. Hier handelt es sich eher um eine optionale Abhängigkeit, die man durchaus per Property Injection angeben kann. Und auch die ReportEngine muss hier wohl kaum mit angegeben werden, wohlmöglich kann man ein irgendwie geartetes Ergebnis der Aktion an das Dingen weitergeben. Deshalb braucht aber die Klasse an sich noch keine Abhängigkeit auf das Teil.

Oft resultieren also augenscheinlich Probleme, denen man sich konfrontiert sieht, und denen man mit Verletzungen von irgendwelchen Prinzipien entgegnen "muss" aus vorrausgehenden Verletzungen dieser oder anderer Prinzipien. Aber wie schon eingangs gesagt, man muss immer abwägen, was erreicht man und welchen Aufwand hat man.

Zitat:

Zitat von Dejan Vu (Beitrag 1259327)
Da imho jede sauber designte Klasse gut (ohne Mocks) testbar ist

Wohl kaum. Denn Mocks werden für die Abhängigkeiten dieser Klasse benutzt. D.h. wenn du keine Mocks brauchst, hast du entweder eine Klasse, die autarg ist, also keine Interaktion jedweder Art mit dem umliegenden Programm durchführt, oder du hast ein Riesenproblem, weil sie das über irgendwelche "spooky actions at a distance" im Hintergrund macht.

p80286 19. Mai 2014 12:43

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Zitat:

Zitat von Dejan Vu (Beitrag 1259327)
Aber in einem Batchprozess möchte ich gerne (oder ich muss) auf diese Rechte pfeifen. Ich will mir keinen Batchuser zusammenfriemeln, der bestimmte Rechte hat, wobei meine 'Benutzerverwaltung' vielleicht so gemein ist, aus Sicherheitsgründen zu verbieten, das jemand zwei bestimmte Rechte gleichzeitig innehat und das genau die sind, die ich gerade benötige.

Warum bitteschön sollten für einen batch"User" andere Regeln gelten wie für einen "normalen Dialog User". Entweder es ist für alle Benutzer bzw. Prozesse ein entsprechendes Regelwerk definierbar oder aber da hat jemand seine Hausaufgaben nicht gemacht/machen können.

Gruß
K-H

Sir Rufo 19. Mai 2014 13:07

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Zitat:

Zitat von p80286 (Beitrag 1259337)
Zitat:

Zitat von Dejan Vu (Beitrag 1259327)
Aber in einem Batchprozess möchte ich gerne (oder ich muss) auf diese Rechte pfeifen. Ich will mir keinen Batchuser zusammenfriemeln, der bestimmte Rechte hat, wobei meine 'Benutzerverwaltung' vielleicht so gemein ist, aus Sicherheitsgründen zu verbieten, das jemand zwei bestimmte Rechte gleichzeitig innehat und das genau die sind, die ich gerade benötige.

Warum bitteschön sollten für einen batch"User" andere Regeln gelten wie für einen "normalen Dialog User". Entweder es ist für alle Benutzer bzw. Prozesse ein entsprechendes Regelwerk definierbar oder aber da hat jemand seine Hausaufgaben nicht gemacht/machen können.

Gruß
K-H

Gut, das ist ja z.B. bei Datenbanken ein bekanntes Szenario: Benutzer X darf die Tabellen nur lesen, aber über eine SP darf er doch etwas hineinschreiben.

Allerdings, wenn für die Aktion TuWas die Berechtigungen egal sind, dann prüft diese Aktion die Berechtigungen einfach nicht.

Dejan Vu 19. Mai 2014 13:19

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
[QUOTE=Sir Rufo;1259345]
Zitat:

Zitat von p80286 (Beitrag 1259337)
Benutzer X darf die Tabellen nur lesen, aber über eine SP darf er doch etwas hineinschreiben.

Und mit meinem bulk load muss ich genau das umgehen.

Zitat:

Zitat von Stevie (Beitrag 1259330)
Das Thema entwickelt ja langsam eine echte Dynamik ;)

Mein Hintergedanke war der, das hier endlich mal darüber gesprochen wird, wie man es richtig macht und nicht immer, das man es so nicht macht :-)
Zitat:

Um es mal vorweg zu schicken,
Genau Begriffe definieren/festklopfen.
1. Testbar = Unittests
2. Mock <> Fake (Mock=nachträgliches Ändern von Verhalten. Fake = Hilfsklasse, um Abhängigkeiten zu kontrollieren)
3. Wartbar = Änderungen ohne Seiteneffekte vornehmen.
4. Robust = Schrotteingaben => wohldefinierte Exceptions. "Keine Überraschungen"
5. Erweiterbar = Erweitern, ohne sich einen Wolf zu tippen.

Zitat:

Schöne Code Beispiele - da sie auf verschiedenen Ebenen einige Fehler aufzeigen.
Sie sind mit Absicht so.
1. Soll das BO die Rechte prüfen? Normalerweise nicht, das macht ein Szenario. Aber wenn die Rechteabfrage systemimmanent ist, z.B. in einer Bank integraler Bestandteil der Aktion ist, dann schon (finde ich). Aber mit Sicherheit nicht so banal wie hier. Insofern => richtig, SRP verletzt.

Zitat:

Zweiter Fall, die Kopplung an die GUI
Daher das 'Command' (in Anlehnung an das ICommand)
Zitat:

Wenn ich nunmal die VCL verwende, dann ist es auch nicht böse
. Also ich würde mich heute nicht mehr darauf festlegen ob VCL oder FMX. Unabhängig davon würde ich das trotzdem kapseln (zumindest die Messageboxen), denn diese Interaktion möchte ich erweiterbar gestalten und vor allen Dingen stringent im Design (Kein Mischmasch aus OK/Cancel Yes/No etc. Vielleicht alle Text aus einer Textrepository, d.h. keine 'echten' Texte wg. Übersetzung usw.) Ach, und falls ich doch Abfragen ("Wollen Sie wirklich die Zentrale in die Luft jagen?") und Meldungen ("The HQ was successfully destroyed") testen will, ist so ein Wrapper wirklich sehr nützlich.

Die restlichen Abhängigkeiten.. Nun ja, klar. Da ich vielleicht doch einen "IoC-Container" habe, der das globale Gedöns schön verbirgt, sollte man das noch anständig herunterbrechen können. Nur muss ich mir dann meinen IoC-Container mocken/faken, was auch kein Zuckerschlecken ist.

Allerdings: Das Kommando hat nun einmal diese Abhängigkeiten: Es wird geprüft, gespeichert, gedruckt und interagiert. Und mit DI müsste das dann entsprechend ausarten, weil in meinem Fall das Kommando nun einmal die 'oberste Instanz ist', die die logische Aktion 'TuWas, aber mit allem Drum und dran' ausführt.

Zitat:

Zitat:

Zitat von Dejan Vu (Beitrag 1259327)
Da imho jede sauber designte Klasse gut (ohne Mocks) testbar ist

Wohl kaum.
Mocks erleichtern nur die Arbeit, sofern ich unter 'Mocking' das gleiche verstehe, wie Du (s.o). Eine saubere (=kleine) Klasse mit DI kommt doch ohne Mocks aus. Ich verwende Mocks bisher nur, weil ich keinen Bock habe, die Abhängigkeiten komplett zu faken. Und da ich in einer Klasse laut Deiner Definition eh nie viele Abhängigkeiten haben kann, ist meine Behauptung auch nicht allzuweit hergeholt. Und die Hintertür ist: Das 'Ohne Mocking' steht in Klammern, weil *festlegen* will ich mich darauf nu auch nich, ne.

p80286 19. Mai 2014 13:36

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Zitat:

Zitat von Sir Rufo (Beitrag 1259345)
Gut, das ist ja z.B. bei Datenbanken ein bekanntes Szenario: Benutzer X darf die Tabellen nur lesen, aber über eine SP darf er doch etwas hineinschreiben.

Es könnte sein, daß über die SP die Art der Daten beschränkt wird, was über die gängigen Berechtigungen nicht vernünftig lösbar sein kann.
Aber abgesehen davon, ist das meiner Meinung nach Schusseligkeit beim Design oder aber die allgegenwärtige Nachfrickelei.
(bevor mir jetzt hier der kragen platzt sehe ich mal zu, daß ich meine Arbeit gemacht kriege)

Gruß
K-H

Sir Rufo 19. Mai 2014 13:39

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Das mit dem BULK LOAD ist sauber nur durch eine Mittelschicht abzubilden (so wie auch die gesamte Berechtigungs-Verwaltung wer wie wann wo was machen darf, bis runter auf Datensatz-/Feld-Ebene runter)

Die Anwendung kommt mit dem SQL-Server gar nicht in Berührung, sondern spricht mit der Mittelschicht.
Die Mittelschicht spricht mit dem SQL-Server und darf dort eh alles (ok, fast alles).

Ok, aber das weiter zu vertiefen sprengt wohl den Rahmen globale Abhängigkeiten

Stevie 19. Mai 2014 13:52

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Zitat:

Zitat von Dejan Vu (Beitrag 1259348)
Zitat:

Um es mal vorweg zu schicken,
Genau Begriffe definieren/festklopfen.
1. Testbar = Unittests
2. Mock <> Fake (Mock=nachträgliches Ändern von Verhalten. Fake = Hilfsklasse, um Abhängigkeiten zu kontrollieren)
3. Wartbar = Änderungen ohne Seiteneffekte vornehmen.
4. Robust = Schrotteingaben => wohldefinierte Exceptions. "Keine Überraschungen"
5. Erweiterbar = Erweitern, ohne sich einen Wolf zu tippen.

zu 2. Ja, aber in beiden Fällen dienen diese test doubles als Arsatz für Abhängigkeiten des system under test. Da in diesem Zusammenhang immer von mock objects bzw Mocking gesprochen wird, will ich gar nicht auf die Feinheiten zwischen Mock, Fake und Stub eingehen, die auch für diese Diskussion eher irrelevant sind. Fakt ist, man ersetzt das richtige Dings durch nen anderes Dings. Und damit man son Dings austauschen kann, muss man sie voneinander entkoppeln.
zu 4. robust eher im Sinne von wenn ich am Code an Stelle a etwas ändere krachts nicht an Stelle b, die auf der anderen Seite des Programms ist (geht auch ein bisschen Richtung Punkt 5)
zu 5. es geht nicht nur ums tippen sondern darum, dass man durch Erweiterungen nicht sein halbes System umstricken muss (Stichworte: OCP, LSP, ISP)

Zitat:

Zitat von Dejan Vu (Beitrag 1259348)
1. Soll das BO die Rechte prüfen? Normalerweise nicht, das macht ein Szenario. Aber wenn die Rechteabfrage systemimmanent ist, z.B. in einer Bank integraler Bestandteil der Aktion ist, dann schon (finde ich). Aber mit Sicherheit nicht so banal wie hier. Insofern => richtig, SRP verletzt.

Du hast mich falsch verstanden, ich sprach eher davon, in die Klassen etwas hineinzugeben, was dir für Aktion XY ja oder nein sagt und nicht hardcodiert auf den User zu gehen. Denn dann hast du ruckzuck die Logik, dass Rechte von Usern abhängen gekapselt, was dein BO nicht die Bohne interessiert. Der will nur wissen, ob er XY machen darf oder nich und nicht, wie er das ermitteln.

Zitat:

Die restlichen Abhängigkeiten.. Nun ja, klar. Da ich vielleicht doch einen "IoC-Container" habe, der das globale Gedöns schön verbirgt, sollte man das noch anständig herunterbrechen können. Nur muss ich mir dann meinen IoC-Container mocken/faken, was auch kein Zuckerschlecken ist.
Nein! Ich sag es oft und auch hier an dieser Stelle nochmal: ein IoC-Container ist keine Medizin für nicht optimalen code und er macht auch bei richtigem Einsatz (ja, auch einen IoC-Container kann man wunderbar falsch einsetzen) nichts, was man nicht mit "poor mans dependency injection" lösen könnte. Denn man sollte grundsätzlich seinen Code so schreiben, dass man ihn auch händisch zusammentackern kann. Der IoC Container nimmt einem am Ende nur diese Arbeit ab und automatisiert einiges. Wenn man ihn falsch einsetzt, hat man sich am Ende nämlich ganz schnell eine Abhängigkeit auf den Container eingehandelt. Das merkt man schnell, wenn man plötzlich für einen Test den container braucht, was nicht der Fall sein sollte.

Dejan Vu 19. Mai 2014 14:03

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Zitat:

Zitat von Stevie (Beitrag 1259363)
Zitat:

2. Mock <> Fake
Fakt ist, man ersetzt das richtige Dings durch nen anderes Dings. Und damit man son Dings austauschen kann, muss man sie voneinander entkoppeln.

Alles klar. Kann man so vereinfachen.
Zitat:

zu 4. robust eher im Sinne von wenn ich am Code an Stelle a etwas ändere krachts nicht an Stelle b, die auf der anderen Seite des Programms ist (geht auch ein bisschen Richtung Punkt 5)
zu 5. es geht nicht nur ums tippen sondern darum, dass man durch Erweiterungen nicht sein halbes System umstricken muss (Stichworte: OCP, LSP, ISP)
'einen Wolf tippen' ist die Quintessenz und der eigentliche Grund (einer davon), weshalb diverse Designregeln, OCP, LSP, ISP... eingeführt wurden. Ich mache etwas, weil ich nicht ewig tippen will, und 100 Jahre entwanzen, weil diese ich diese dämlichen Seiteneffekte nicht vermeiden kann und nicht, weil es gegen Gesetze verstößt. Kommt zwar aufs Gleiche heraus, aber ich habe hier ein paar sehr kluge Programmierer, die nur den Regeln folgen und dabei gar nicht merken, wie verworren, komplex und total überkandidelt das Ganze mittlerweile ist. Aber sie reiten auf den von Dir immer wieder zitierten Regeln herum und finden es total geil, sich 3 Tage über SRP den Kopf zu zerbrechen, anstatt es einfach 'einfach' zu gestalten. Ein sehr schönes Thema für einen anderen Thread.

Zitat:

Der will nur wissen, ob er XY machen darf oder nich und nicht, wie er das ermitteln.
Das war der korrekte Einwand von p80286, woraufhin ich meinte, man sollte 'IUser' durch 'IPermissions' ersetzen. Natürlich kann man das abstrahieren , aber die konkreten Rechte sind Bestandteil der Spezifikation und insofern nicht abstraktionswürdig. Kann man machen, wird man aber nicht. Wir haben Szenarioklassen à la 'Execute<BusinessObject>' die eine 'CanExecute' Methode haben, in der die Rechteabfrage umtergebracht ist.

Zitat:

Ich sag es oft
:oops: mir hast Du das noch nicht gesagt.

Aber dann verstehe ich deinen Einwand auch nicht: Ich habe ein Kommando als oberste Instanz. Dies führt eine Aktion und alle damit verbundenen weiteren Aktionen aus (Speichern, loggen, drucken, validieren etc.) Dem muss ich doch alle Abhängigkeiten übergeben, oder wie geht das sonst?

Stevie 19. Mai 2014 16:19

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Zitat:

Zitat von Dejan Vu (Beitrag 1259365)
ich habe hier ein paar sehr kluge Programmierer, die nur den Regeln folgen und dabei gar nicht merken, wie verworren, komplex und total überkandidelt das Ganze mittlerweile ist. Aber sie reiten auf den von Dir immer wieder zitierten Regeln herum und finden es total geil, sich 3 Tage über SRP den Kopf zu zerbrechen, anstatt es einfach 'einfach' zu gestalten. Ein sehr schönes Thema für einen anderen Thread.

Genau aus diesem Grund hab ich vorhin geschrieben, dass das ganze Clean Code, Principles und Patterns Gedöns kein Selbstzweck ist. Aber jeder, der sich mit der ganzen Thematik wenigstens ein bisschen auseinander gesetzt hat und etwas davon in die Tat umgesetzt hat, hat bestätigt, dass das positiv war. Was raus kommt, wenn mans übertreibt kann man sehr schön an dieser Fizzbuzz Implementierung sehen. ;)

Zitat:

:oops: mir hast Du das noch nicht gesagt.
Prima, dann hab ich ja doch noch jemanden erreicht, dem ich das noch nicht gesagt habe ;)
Ich hab mir das ja auch nicht selber ausgedacht, man kann das auch oft in Artikeln zu dem Thema lesen.
Erstmal die Prinzipien und vor allem das DIP verstehen und manuell anwenden, bevor man sich einem IoC Container zuwendet. Ansonsten wird man nämlich ganz schnell ziemlich böse davon überfahren und benutzt nen IoC Container am Ende als Service Locator. :pale:

Zitat:

Aber dann verstehe ich deinen Einwand auch nicht: Ich habe ein Kommando als oberste Instanz. Dies führt eine Aktion und alle damit verbundenen weiteren Aktionen aus (Speichern, loggen, drucken, validieren etc.) Dem muss ich doch alle Abhängigkeiten übergeben, oder wie geht das sonst?
Wenn man das betrachtet, als ob dein Kommando das alles selbst macht. Macht es aber eventuell gar nicht. Möglich ist an dieser Stelle zum Beispiel, das Chain- of-responsibility Pattern zu verwenden, so dass ebend nicht alles direkt innerhalb des einen Kommandos ausgeführt wird, sondern eins das nächste ausführt. Auch hier wieder beachten: wie flexibel und austauschbar möchte ich die Kommandos haben. Die Aktionen Speichern, Loggen, Drucken, Validieren operieren ja normalerweise auf einem anderen Objekt. Und je nachdem, wie man die geschrieben hat (Speichern ist ja immer eins der Paradebeispiele für "Wat kann ich eijentlich mit dieser RTTI und Attributen so machen?") hat man mehr oder weniger wiederverwendbare Bausteine, die man in verschiedenen Reihenfolgen aneinander ketten kann.

Eins steht aber auf jeden Fall fest - und das mag für manche befremdlich sein: man muss recht viel stumpfen Code für die Erstellung und Zusammentackerei schreiben. Und das ist am Ende das, was der IoC Container einem abnehmen kann.

BUG 19. Mai 2014 20:26

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Zitat:

Zitat von Stevie (Beitrag 1259409)
Was raus kommt, wenn mans übertreibt kann man sehr schön an dieser Fizzbuzz Implementierung sehen. ;)

Das Ding ist super :mrgreen:

implementation 13. Jun 2014 19:31

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Mir war der Begriff "Globale Abhängigkeit" neu, und ich finde auch bei Google nichts darunter. Hat vielleicht jemand ein paar gute Links dazu oder einen alternativen Begriff unter dem etwas zu finden ist?

Mir viel nämlich gerade auf, dass eine Haskell-Bibliothek, die ich aus ganz anderen Gedanken schrieb, eine perfekte Struktur hat, so etwas auch allgemeiner zu bedienen, daher will ich da mal etwas nachforschen.

himitsu 13. Jun 2014 19:41

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
Im Prinzip heißt es, daß der Code von "globalen" Dingen abhängig ist.
Du greifst also von deinem Code auf eine globale Variable zu, bzw. holst dir aus irgendeiner fremden Instanz etwas raus.

Beispiel: die globalen Form-Variablen (von Form2 auf Form1 zugreifen)

Damit bist du davon abhängig. Ohne geht nicht und mit was Anderem auch nicht.



Wenn du dir das jetzt aber geben lässt, anstatt es von irgendwo zu holen, dann bist du nicht mehr abhängig und man kann dich mit sonstwas verwenden.



Man lässt sich Eingabedinge geben und Rückgaben gibt man entweder an ein interface raus, wo sich aber jeder registrieren kann, bzw. es wird über eine Callback-Methode rausgegeben.
* nichts selber holen, außer von frei registrierbaren Quellen, oder sich alles geben lassen
* nicht selber irgendwo an was Festes übergeben, außer an frei registrierbare Ziele, oder die Ergebnisse abholen lassen

* wenn man davon problemlos mehrere Instanzen erstellen kann, ohne daß sich beide Instanzen irgendwie in die Quere kommen, dann gibt es vermutlich keine Abhängigkeiten

Stevie 13. Jun 2014 20:01

AW: Globale Variablen/Abhängigkeiten = Böse... Und nu?
 
"Globale Abhängigkeit" = Abhängigkeit auf einen global State (meist eine globale Variable oder ein Singleton).

Siehe dazu z.B. dieser Clean Code Talk.


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:18 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