Neuigkeiten:

Ist euer Problem gelöst, dann bitte den Knopf "Thema gelöst" drücken!

Mobiles Hauptmenü

Thread/Post gesucht

Begonnen von Beaker s.a., April 09, 2025, 15:37:02

⏪ vorheriges - nächstes ⏩

Beaker s.a.

Hallo,
Dummerweise habe ich vergessen, ob es hier oder im MOF war. Jedenfalls
hat Christian vor Kurzem einen Post zu GOTO und einem geordneten Ausstieg
aus Prozeduren geschrieben. Der war etwas OT, weshalb ich nicht weiss wo
nach ich suchen soll.
Kann mir da jemand helfen?

@christian
Falls du dich berufen fühlst, gerne auch als PN.

gruss ekkehard
Alles, was geschieht, geschieht. - Alles, was während seines Geschehens etwas anderes geschehen lässt, lässt etwas anderes geschehen. - Alles, was sich selbst im Zuge seines Geschehens erneut geschehen lässt, geschieht erneut. - Allerdings tut es das nicht unbedingt in chronologischer Reihenfolge.
(Douglas Adams, Mostly Harmless)


Bitsqueezer

Hallo Ekkehard,

kann sein, daß es in MOF war. Es ging darum, daß im Allgemeinen "GoTo" als verpönt dargestellt wird, weil dessen unbedachter Einsatz sehr chaotischen Code erzeugen kann.

Meine Aussage dazu war, daß man deshalb aber nicht grundsätzlich darauf verzichten sollte. Ein Beispiel ist alleine schon "On Error GoTo", was nichts Anderes ist als ein kontrollierter Sprung zu einem Label, in dem Fall bei Auftreten eines Fehlers.

Wenn man GoTo selbst auch genau so einsetzt, nämlich grundsätzlich und ausschließlich nur für Vorwärtssprünge, kann das die Lesbarkeit von Code deutlich erhöhen, statt etwa am Anfang eines größeren Code-Blocks ein "If.." zu schreiben und ganz am Ende ein "End If", von dem dann keiner weiß, zu welchem If es gehört und man Scrollorgien benötigt, um das wiederzufinden. Stattdessen ein "If Irgendeine Validierung Then" und dann ein "Goto ProzedurEnde" sagt sofort, was hier passiert. Es ersetzt damit das "Exit Sub/Function/Property", was so gern eingesetzt wird, um mitten aus einer Prozedur diese zu beenden - und dabei das Aufräumen am Ende der Prozedur ebenfalls zu übergehen.

Leider sehr beliebt in Sprachen wie Python, wo "Return" an allen möglichen Stellen in einer Prozedur stehen kann.
Wenn man sich dagegen an die alte Weisheit hält, eine Prozedur beginnt an genau einer Stelle und endet an genau einer Stelle, inklusive der Fehlerbehandlung, die immer mit "Resume LabelEnde" beendet wird und damit ebenfalls an der gleichen Stelle die Prozedur verläßt, ist der Code sehr viel einfacher zu warten und weitaus weniger fehleranfällig, weil man sich darauf verlassen kann, daß hier aufgeräumt wird. Wenn man also dem Aufräumen noch etwas hinzufügen will, dann an dieser Stelle und es wird garantiert am Ende ausgeführt.

Und dazu dann eben Goto, um ebenfalls bei Auftreten einer Bedingung, die die weitere Ausführung unnötig macht, zum Aussprungpunkt zu springen.
Wie gesagt: On Error GoTo und Resume sind ebenfalls nur zwei weitere Formen von GoTo mit besonderen Vorbedingungen, hier stört sich nur keiner dran.
Vermieden werden sollte eben lediglich, GoTo für Rückwärtssprünge wie bei einer Schleife zu verwenden, das gab es früher, als es noch keine strukturierten Schleifen gab, als einzige Möglichkeit. Da man hier aber schnell auch an die falsche Stelle springen und mit vielen GoTos kreuz und quer den Code nahezu unmöglich zu debuggen oder dessen Ablauf zu verstehen macht, sollte man GoTo sparsam einsetzen, nur für Vorwärtssprünge zum Aussprungpunkt und nicht zur Gestaltung des Code-Ablaufs.
Ich versehe die Zeile immer zusätzlich noch mit "'-------------------->", um auch optisch den Aussprung deutlich zu machen.

Aussprünge aus Schleifen sollte man ebenfalls vermeiden, da ist ein "Exit For" oder "Exit Do" besser geeignet, da oft am Ende noch etwas geschlossen wird.

Ist natürlich nur meine Philosophie des Themas und hat demnach keine Allgemeingültigkeit.

Gruß

Christian

Knobbi38

Zitat von: Bitsqueezer... statt etwa am Anfang eines größeren Code-Blocks ein "If.." zu schreiben und ganz am Ende ein "End If", von dem dann keiner weiß, zu welchem If es gehört und man Scrollorgien benötigt, um das wiederzufinden.
So etwas läßt sich auch noch vermeiden, wenn man sich an die Regel hält, daß eine Sub/Function nicht über eine sichtbare Seite hinausgehen sollte. Das zähle ich jetzt mal zum Thema Programmierstil.

Obwohl ich Vorwärtssprünge nicht für sonderlich glücklich halte, kann ich Christian ansonsten nur beipflichten.

Gruß
Knobbi38

PhilS

Zitat von: Bitsqueezer am April 09, 2025, 18:22:25Wie gesagt: On Error GoTo und Resume sind ebenfalls nur zwei weitere Formen von GoTo mit besonderen Vorbedingungen, hier stört sich nur keiner dran.
Moment! Ich störe mich massiv daran. - Es gibt nur keine (eingebaute) Alternative zur Fehlerbehandlung.
Neue Videoserie: Windows API in VBA

Klassische CommandBars visuell bearbeiten: Access DevTools CommandBar Editor

Bitsqueezer

Hallo,

ZitatSo etwas läßt sich auch noch vermeiden, wenn man sich an die Regel hält, daß eine Sub/Function nicht über eine sichtbare Seite hinausgehen sollte. Das zähle ich jetzt mal zum Thema Programmierstil.

Ja, kann man machen. Allerdings sehe ich persönlich auch keinen Sinn darin, einen sauberen Programmfluß in auch einer längeren Prozedur unterzubringen, statt etliche kleine Teile daraus zu machen und in zusätzliche Prozeduren zu zerfleddern, was es auch schwerer macht, zu verfolgen, was hier genau passiert, ganz davon abgesehen, daß man der betreffenden Prozedur die entsprechenden Variablen dann alle per Parameter zur Verfügung stellen muß.

Das lohnt sich nur dann, wenn man Teile wiederverwenden will. Aber das ist meine Vorgehensweise, Programmierstil, da hat halt jeder seine eigenen Vorstellungen.

ZitatMoment! Ich störe mich massiv daran. - Es gibt nur keine (eingebaute) Alternative zur Fehlerbehandlung.

OK, ich revidiere, immerhin einer stört sich daran... ;)
"Resume" ist dann auch das einzige "GoTo", bei dem Rückwärtssprung erlaubt ist.

Ich persönlich finde die Fehlerbehandlung von VBA gar nicht übel, gegenüber einem Try/Catch habe ich die Möglichkeit, ein "Resume" (ohne Parameter) am Ende hinzuzufügen, mit dem ich per STRG-F9 hinspringen und dann zur genauen Fehlerstelle im laufenden Programm zurückspringen und den Fehler dann genau untersuchen kann.

Gruß

Christian

Beaker s.a.

Hallo,

Vielen Dank für eure Kommentare. Vor allem an Christian.

@Ulrich
Der Link funzt ja leider z.Zt. nicht. Hoffen wir, dass es da wieder
weitergeht.

Ansonsten werde ich mal versuchen es in meinem akt. Projekt umzusetzen.

gruss ekkehard
Alles, was geschieht, geschieht. - Alles, was während seines Geschehens etwas anderes geschehen lässt, lässt etwas anderes geschehen. - Alles, was sich selbst im Zuge seines Geschehens erneut geschehen lässt, geschieht erneut. - Allerdings tut es das nicht unbedingt in chronologischer Reihenfolge.
(Douglas Adams, Mostly Harmless)

PhilS

Zitat von: Bitsqueezer am April 10, 2025, 10:39:05Ich persönlich finde die Fehlerbehandlung von VBA gar nicht übel, gegenüber einem Try/Catch habe ich die Möglichkeit, ein "Resume" (ohne Parameter) am Ende hinzuzufügen, mit dem ich per STRG-F9 hinspringen und dann zur genauen Fehlerstelle im laufenden Programm zurückspringen und den Fehler dann genau untersuchen kann.
Das ist zwar ein Vorteil, aber nur für die eher ungewöhnliche Situation, des debuggenden Entwicklers.
Die meisten Fehler treten beim Benutzer auf. Da ist mir eine detaillierte Exception, die ich auch in ein Log serialisieren kann aber 1000 mal lieber.
Der Stacktrace einer (.Net) Exception sollte eigentlich auch einen Rücksprung in die fehlerauslösende Zeile erlauben. Ich weiß aber nicht, ob man das ähnlich einfach wie ein Resume in VB Classic anwenden kann.

Neue Videoserie: Windows API in VBA

Klassische CommandBars visuell bearbeiten: Access DevTools CommandBar Editor

Knobbi38

Hallo,

Debuggen und Logausgaben sind aber jetzt ein anderes Thema, worüber man sicherlich trefflich diskutieren kann, nur wäre das hier sicherlich etwas OT.

Ich würde sowieso das Thema Errorhandling in VBA nicht direkt mit dem Thema "Strukturierte Programmierung vs. Goto" verknüpfen wollen, weil das nun mal eine Vorgabe von VBA ist.

Gruß
Knobbi38

Josef P.

#9
Hallo!

Vorab: eigentlich ist hier fast alles OT zum Thema "Thread/Post gesucht" ;)


Zum Thema Goto, Gosub usw. bin ich der Meinung: wenn es den Code besser lesbar macht, spricht nichts dagegen.

Ich kann mir sogar vorstellen, dass man Gosub CleanUp verwendet, wenn man das an mehreren Stellen benötigt, falls das die Lesbarkeit erhöht.
Allerdings hab ich in meinem Code bisher noch keinen Bedarf gesehen (etwas mehr als 1-2-3... Jahre Erfahrung mit VBA hab ich mittlerweile schon sammeln dürfen). ;)


ZitatAllerdings sehe ich persönlich auch keinen Sinn darin, einen sauberen Programmfluß in auch einer längeren Prozedur unterzubringen, statt etliche kleine Teile daraus zu machen und in zusätzliche Prozeduren zu zerfleddern, was es auch schwerer macht, zu verfolgen, was hier genau passiert, ganz davon abgesehen, daß man der betreffenden Prozedur die entsprechenden Variablen dann alle per Parameter zur Verfügung stellen muß.

Ich finde das Argument, dass in mehreren Prozeduren unterteilter Code schlechter zu verfolgen ist, immer wieder interessant.
Wenn ich einen Code lese und dort steht z. B. nur SaveAsPath = GetSaveAsPathFromDocument(....), ist es mir beim Lesen der Zeile nämlich ziemlich egal, was in GetSaveAsPathFromDocument abläuft. Würde statt diesem Funktionsaufruf direkt der Inhalt dieser Prozedur stehen, würde mich das mehr stören. Dabei spielt es für mich an dieser Stelle gar keine Rolle, ob die Funktion GetSaveAsPathFromDocument  wiederverwendbar ist.
Weiterer Vorteil: ich kann die Funktion direkt testen, ohne den vollständigen Code davor laufen lassen zu müssen.

Wenn der Code für die Umsetzung der einen Aufgabe der Prozedur allerdings nicht teilbar ist, würde ich nicht wegen der Prozedurlänge versuchen, irgendwelche Unterprozeduren zu erfinden, nur um die Prozedur zu "kürzen".

Beispiel für eine Aufteilung:

Public Sub RunAfterVcsExport(Optional ByVal VCS As Object)

    If MsgBox("Create backup of VCS folder?", vbYesNo, "VCS (Run After Export): " & CurrentProject.Name) = vbYes Then
        SaveExportFolderToZip VCS
    End If

End Sub

Private Sub SaveExportFolderToZip(Optional ByVal VCS As Object)

    Dim ExportFolder As String
    Dim TargetZipFile As String
   
    ExportFolder = GetVcsExportFolder(VCS)
    TargetZipFile = GetTargetZipFile(ExportFolder)
    ZipWithPowerShell ExportFolder, TargetZipFile

End Sub
Der Inhalt der Funktionen GetVcsExportFolder, GetTargetZipFile, ZipWithPowerShell ist hier nicht zu sehen. Ich nehme aber an, dass man trotzdem erkennen kann, was passieren wird, um den Ablauf zu verstehen. ;)

Die Inhalte dieser Funktionen sehe ich mir an, wenn ich etwas ändern will bzw. ein Fehler auftritt.
Würde alles in einer Prozedur stehen, wird es nicht so einfach, nur die Verzeichnispfade zu ermitteln ohne die Zip-Datei zu erstellen.
Ich kann die 3 Funktionen unabhängig voneinander testen.  Wenn alles in einer Prozedur gestaltet wird (oftmals mit dem Argument untermauert: es gehört ja zusammen), kann man immer nur alles zusammen testen. Mittels Haltepunkten den Code anhalten und dann durchspringen kann man natürlich auch in einer großen Prozedur. Ich will allerdings testen und nicht F8-Klicken üben. ;)

Gruß
Josef



Bitsqueezer

Hallo,

ZitatDa ist mir eine detaillierte Exception, die ich auch in ein Log serialisieren kann aber 1000 mal lieber.

Was nicht da ist, baut man sich. Ich habe dazu eine Fehlerklasse, die ich mit "ObjErr" aufrufen kann und per Default einen Unterordner "Errors" im Acces-Ordner der Anwendung erzeugt und dort je aufgetretenem Tag eine HTML-Datei mit allen Fehlermeldungen aufzeichnet, die man sich dann im Browser ansehen kann. Die Fehlermeldungen werden damit außerdem als HTML-Messagebox dem Benutzer angezeigt, der sie dann auch per Mail oder als Excel-Export usw. an den Entwickler weiterreichen kann. Da Fehlermeldungen halt oft ignoriert werden, kann man dann als Entwickler einfach in den Errors-Ordner schauen und sich alles ansehen, was so passiert ist. Auch solche Fehler, die dem Benutzer nicht angezeigt werden.
Die Klassen kann man datenbankunabhängig in jede Datenbank integrieren und somit schließen sich alle Lücken zur Fehlerbehandlung oder komfortablen Informationsausgabe. Die HTML-Messagebox ist auf meiner Downloadseite zu finden, wer's benutzen mag.


ZitatWürde statt diesem Funktionsaufruf direkt der Inhalt dieser Prozedur stehen, würde mich das mehr stören. Dabei spielt es für mich an dieser Stelle gar keine Rolle, ob die Funktion GetSaveAsPathFromDocument  wiederverwendbar ist.
Weiterer Vorteil: ich kann die Funktion direkt testen, ohne den vollständigen Code davor laufen lassen zu müssen.

So sind die Programmierstile eben verschieden.. :)
Speziell in Deinem Beispiel oben würde ich es als sinnlos ansehen, eine Prozedur "SaveExportFolderToZip" zu haben, wenn sie ausschließlich in der Prozedur darüber aufgerufen wird - wird sie mehr als einmal verwendet, ist es immer sinnvoll. So muß man beim Debuggen erst mal in diese Prozedur wechseln, dazu noch drei weitere Prozeduren durchsteppen, um zu sehen, warum zum Beispiel ein Fehler in der letzten auftrat, die (ohne Prüfung) "TargetZipFile" weiterreicht, das auch leer sein könnte (mal abgesehen davon, daß ich Variablen grundsätzlich namentlich typisiere, also "strTargetZipFile"). Nur um zu sehen, daß das Problem in "GetVcsExportFolder" auftrat. Jedes davon kann eine eigene Fehlerbehandlung haben, die Fehler unterdrückt usw. Es gibt VIEL mehr Testarbeit, weil die Funktion vielleicht ordentlich getestet zu sein schien - aber halt nicht in dem einen Fall, den ich gerade habe.
Abgesehen davon, daß ich es vermeide, "Object" zu benutzen, was einem dann nicht einmal sagt, was "VCS" sein soll, auch dem Compiler nicht.

Da Du in Deiner Prozedur keine Fehlerbehandlung hast und wenn die darin aufgerufenen auch keine haben, wird ein aufgetretener Fehler immer weiter nach oben gereicht. Deine "RunAfter.."-Prozedur hat wiederum keine, und so landet Dein Fehler dann "irgendwo" - oder VBA gibt am Ende eine Debugger-Fehlermeldung aus. Aber wenn es dann doch noch eine Fehlerbehandlung gibt, hilft auch der "Resume"-Trick oben nicht, denn ich lande dann nur auf der Zeile, die z.B. die "RunAfter.."-Prozedur aufruft und muß dann erst mal dort wieder rein und alles erneut ausführen, bis ich endlich in der Prozedur lande, die den Fehler wirft - habe ich zu schnell gedrückt, bin ich wieder "ganz oben" und kann von vorn anfangen. All diese Dinge halten mehr auf als sie nützen.
Die beiden vorgestellten Prozeduren können auch aufgrund der geringen Länge problemlos in einer zusammengefaßt werden, ohne die Lesbarkeit zu verringen, dafür aber die Testbarkeit zu verbessern (wie gesagt, wenn die zweite nicht anderweitig gebraucht werden kann).

Einzeltests: Ist ja in Ordnung, wenn die Funktion so simpel ist, daß sie keine besonderen Voraussetzungen benötigt, also mit ein paar Testparametern wie einem Foldernamen usw. unabhängig getestet werden kann.

Wenn eine Prozedur aber z.B. ein komplexes Objekt einer Klasse übergeben bekommt oder eine Collection mit nicht-homogenen Inhalt usw., dann muß man auch erst eine Testsituation mit passenden Parametern zusammenstellen, was viel zeitaufwendiger ist, als den betreffenden Code einfach da zu lassen, wo er semantisch hingehört. Wie gesagt, nochmal betone, wenn die Prozedur mehr als einmal verwendet werden kann aus verschiedenen Aufrufsituationen, dann ist es immer sinnvoll, eine eigene dafür anzulegen. Wenn es aber nur ein Auseinandernehmen von Codeblöcken in Prozeduren ist, macht es für mich keinen Sinn, erschwert nur die Entwicklung und das Testen.

Aber ich sage ja auch nur, wie ich das sehe und sage nicht, daß das der einzige oder "wahre" Weg sei. Wenn Du gerne alles in Kleinstprozeduren zerfleddern magst und das für Dich sinnvoller ist - why not.

Gruß

Christian

Josef P.

Hallo!

ZitatSo sind die Programmierstile eben verschieden.
Das macht eine Diskussion darüber auch interessant, weil man andere Sichtweisen erfährt. :)

Zum Thema Testen:
Angenommen ich nutze das so wie von dir vorgeschlagen:
Public Sub RunAfterVcsExport(Optional ByVal VCS As Object)
' Anm.: VCS as Object, da Latebinding durch Aufruf vom msaccess-VCS Add-In

    Dim ExportFolder As String
    Dim TargetZipFile As String

    If MsgBox("Create backup of VCS folder?", vbYesNo, "VCS (Run After Export): " & CurrentProject.Name) = vbYes Then
        ExportFolder = GetVcsExportFolder(VCS)
        TargetZipFile = GetTargetZipFile(ExportFolder)
        ZipWithPowerShell ExportFolder, TargetZipFile
    End If

End Sub

Wie schreibe ich nun ein Testroutine für einen automatisierten Test, damit ich die MsgBox nicht manuell klicken muss? => Bleibt nur das Überschreiben der MsgBox-Funktion übrig. (Das ist auch möglich, ist aber umständlicher als die folgende Variante.)

Nutze ich das:
Public Sub RunAfterVcsExport(Optional ByVal VCS As Object)

    If MsgBox("Create backup of VCS folder?", vbYesNo, "VCS (Run After Export): " & CurrentProject.Name) = vbYes Then
        SaveExportFolderToZip VCS
    End If

End Sub

Private Sub SaveExportFolderToZip(Optional ByVal VCS As Object)
...
.. dann habe ich eine vom Userinput entkoppelte Funktion, die ich testen kann.
Die Funktion RunAfterVcsExport ist so trivial, dass ich mir einen Test dafür ersparen kann.

Im gezeigten Fall gibt es aber einen anderen Grund: die Erkennbarkeit der Zuständigkeit.
RunAfterVcsExport ist die Prozedur, die durch das Add-In msaccess-vcs durch die Einstellung "Run Sub After Export" aufgerufen werden soll, indem ich dort den Namen der aufzurufenden Prozedur schreibe.
Dafür wählte ich "RunAfterVcsExport". Damit ist für mich klar erkennbar, wofür ich die Prozedur verwende.

Innerhalb dieser Prozedur kann ich dann mehrerer Prozeduren ausführen lassen.
Eine davon ist die SaveExportFolderToZip.

Anm.:
Mir ging es aber bei letzten Post von mir gar nicht um die vollständig gezeigten Prozeduren, sondern um die nicht gezeigten Prozeduren, weil der Code-Ablauf trotz nicht sichbaren Code dieser Prozeduren zu erkennen ist.

ZitatWenn es aber nur ein Auseinandernehmen von Codeblöcken in Prozeduren ist, macht es für mich keinen Sinn, erschwert nur die Entwicklung und das Testen.
Ich versuche je Prozedur nur eine konkrete Aufgabe umzusetzen.
SaveExportFolderToZip hat z. B. als Aufgabe die Hauptsteuerung der Zipdatei-Erstellung zu übernehmen. Sie beschäftigt sich aber nicht mit den Details, wie z. B. die Zip-Datei erstellt wird.

Auch fürs Debugen mit F8 & Co. ist für mich eine Nutzung von kleineren Funktionen einfacher, da ich dann mit Shift-F8 einfach darüber springen kann und nicht die nächsten passenden Haltepunkte zum Setzen suchen muss, um nicht jede einzelne Anweisung mit F8 bestätigen zu müssen.

=> Es ist nicht nur der unterschiedliche Programmierstil, sondern vermutlich auch die Arbeitsweise. :)

Gruß
Josef