15. März 2023

Laufzeitfehler vermeiden und benutzerdefiniert abfangen

Dieser Tipp & Trick beschreibt eine Vielzahl von Methoden zur Vermeidung und Behandlung von Laufzeitfehlern in Ihrem VBA-Code. Sie werden mit den FindColumn– und FindRow-Methoden von Tabellenobjekten und der Exists -Methode von Dictionary-Objekten vertraut gemacht. Darüber hinaus wird der Einsatz von Variablen-Info-Funktionen wie IsNumeric  beschrieben. Dadurch können Sie einige Laufzeitfehler vermeiden, andere lassen sich jedoch nicht verhindern. Das Standard-Fehlerbehandlungsverhalten von VBA besteht dann darin, die Simulation abzubrechen, sodass Sie nach der Behebung der Ursache die Simulation neu starten müssen. Dieser Tipp & Trick führt Sie in die benutzerdefinierten Fehlerbehandlungsfunktionen von VBA (oder, genauer gesagt, die WWB-VBA-Variante, die mit INOSIM geliefert wird) ein, wie die TryCatchFinally -Anweisung und die On Error -Anweisung.

Das Demo-Modell (verfügbar im Download-Bereich) wurde entwickelt, um Laufzeitfehler zu erzeugen und zu zeigen, wie diese vermieden werden können. Nehmen Sie sich Zeit und „spielen“ Sie mit dem Modell, löschen Sie die „Ergebnis“-Blätter im Parameter-Arbeitsbuch, um zu sehen, was passiert. Die folgenden Codebeispiele sind Teil einer Operationsparametersteuerung oder EndSim-Prozedur oder werden von diesen aufgerufen.

FindColumn- und FindRow-Methoden von Tabellenobjekten

Der Versuch, über einen nicht vorhandenen String-Index auf eine Zelle eines Tabellenobjekts zuzugreifen, führt zu einem Laufzeitfehler. Daher stehen die Methoden FindColumn und FindRow zur Verfügung, um zu überprüfen, ob ein bestimmter String-Index existiert. Die Methode gibt die Spalten- oder Zeilennummer (>0) oder -1 zurück, falls sie nicht existiert.

Im folgenden Beispiel wird der Name der Operation als Zeilenindex verwendet. Wenn er nicht existiert, sollte stattdessen der String aus der Operationsbeschreibung verwendet werden.

Dim duration As Double, row_index As String
If operation_parameters.FindRow(mop.Name) > 0 Then
    row_index = mop.Name
Else
    row_index = mop.Description
End If
duration = operation_parameters(row_index,"Duration")

Exists-Methode für Dictionary-Objekte

Der Zugriff auf einen nicht vorhandenen Eintrag eines Dictionary-Objekts würde einen Nullwert zurückgeben, was zu Laufzeitfehlern in den folgenden Zeilen führen kann. Daher steht die Exists-Methode für Dictionary-Objekte im Allgemeinen zur Verfügung. Da es sich bei der Liste der CustomAttributes eines INOSIM-Objekts um ein Dictionary handelt, wird im folgenden Beispiel mit der Exists-Methode geprüft, ob ein bestimmtes CustomAttribute für die Operation erstellt wurde. Das Sub Initialize_CA_randomly verwendet einen Zufallswert, um entweder den CA “Duration Factor” mit dem Zufallswert zu initialisieren oder gar nicht zu initialisieren.

'Called sub will decide randomly if the CA "Duration Factor" will be initialized, 
'and if so, assign a random value. If not initialized, CA will not exist.

Initialize_CA_randomly(cop, "Duration Factor")  

Dim duration_factor As Double
If cop.CustomAttributes.Exists("Duration Factor") Then
    duration_factor = cop.CustomAttributes("Duration Factor")
Else
    duration_factor = 1
End If
cop.Duration = duration * duration_factor * 3600

Verwendung von Variable-Info-Funktionen

Um zu überprüfen, ob eine Variable einen bestimmten Datentyp enthält, gibt es mehrere Variablen-Info-Funktionen wie IsDate, IsNumeric und IsDBNull (.NET) oder IsNull (COM). (In der WinWrapBasic-Dokumentation finden Sie eine vollständige Liste der verfügbaren Funktionen, die Sie in der Online-Hilfe der Software oder als PDF in unserem Download-Bereich finden.)

In diesem Beispiel können Benutzer des Modells Zahlen und das Schlüsselwort „content“ in die folgende Operationsparametertabelle (siehe Abbildung) eingeben, um die Transfermenge der Operationen Out 1 und Out 2 festzulegen. Der folgende Code verwendet die IsNumeric -Funktion, um zu überprüfen, ob der Inhalt der Tabellenzelle eine Zahl ist.

 

'Check if table entry is numeric for transfer amount (number or content), 
'using the IsNumeric-function

Dim amount_info As Object
amount_info = operation_parameters(row_index,"Amount")
If IsNumeric(amount_info) Then
    cop.Amount = - CDbl(amount_info)
ElseIf amount_info = "content" Or amount_info = "-content" Then
    cop.AmountType = AmountType.amContent
Else
    Console.Error("Specify a number or the keyword >content< in row " & row_index)
End If

Leere Tabellenzellen sind DBNull in WWB.NET (und Null in WWB-COM). Daher kann die IsDBNull-Funktion (oder IsNull-Funktion für WWB-COM) verwendet werden, um zu überprüfen, ob eine Zelle leer ist. Im folgenden Code wird der Nicht-Null-Wert in die Variable name geschrieben, während eine Warnung ausgegeben wird, wenn die Zelle Null ist.

'Check if transfer target in table is not null, using the IsDBNull-function
Dim transfer_target As Unit, unit_name As String
If Not IsDBNull(operation_parameters(row_index,"to/from")) Then
    unit_name = operation_parameters(row_index,"to/from")
Else
    Console.Warning("Enter valid unit name for the transfer target in row "&row_index)
End If

Benutzerdefinierte Fehlerbehandlung

Selbst mit den besten Absichten und Sorgfalt ist es nicht immer möglich, alle Laufzeitfehler zu vermeiden. Manchmal muss man sich auf externe Daten verlassen, manchmal entstehen Fehler aus komplexen Szenarien. In vielen dieser Fälle ist es vorteilhaft, den allgemeinen Fehlerfall explizit zu behandeln. Die folgenden Beispiele zeigen Ihnen, wie das geht. Die günstigste Methode, das Try-Catch-Finally-Statement, steht nur in WWB.NET zur Verfügung, das mit INOSIM 13 eingeführt wurde. Die On Error-Anweisung ist sowohl in WWB.NET als auch in WWB-COM verfügbar, aber weniger leistungsfähig.

Generell sind Fehlerbehandlungsstrategien nur dann ratsam, wenn einfache If-Anweisungen nicht möglich sind, da letztere in der Ausführungsgeschwindigkeit deutlich schneller sind. (Versuchen Sie es mit unserem T&T Ausführungsdauer von VBA-Steuerungen messen.) In den folgenden Abschnitten werden drei durchdachte Anwendungsfälle für die individuelle Fehlerbehandlung vorgestellt.

Anwendungsfall: Prüfen ob Objekt mit bestimmtem Namen existiert

Der Versuch, ein Objekt aus einer Collection unter Verwendung seines String-Namens abzurufen, kann beispielsweise bei Tippfehlern zu einem Laufzeitfehler führen. Daher ist es sinnvoll, vor der Zuordnung zu prüfen, ob das Objekt existiert.

Allerdings haben Objektklassen wie Units, Aufträge, Rezepte etc. derzeit keine Exists-Methode, im Gegensatz zu den oben gezeigten Dictionaries. Aber Sie können ganz einfach Ihre eigene Funktion für diesen Zweck erstellen, wie unten gezeigt. Alle drei (alternativen!) Funktionen geben true zurück, wenn die Teilanlage existiert, so dass die Zuweisung in der folgenden Zeile sicher ohne Laufzeitfehler ausgeführt werden kann.

Anschließend wird mit der Funktion IsNothing überprüft, ob der Variablen transfer_target ein Objekt zugewiesen wurde. Wenn ja, wird die Teilanlage als Transferziel verwendet, andernfalls wird die Senke verwendet.

If Does_Unit_Exist_OnErrorResumeNext(unit_name) Then
    transfer_target = Units(unit_name)
End If
If Does_Unit_Exist_OnErrorGoTo(unit_name) Then
    transfer_target = Units(unit_name)
End If
If Does_Unit_Exist_TryCatchFinally(unit_name) Then
    transfer_target = Units(unit_name)
End If

cop.SourceSinkUnits.Clear
If Not IsNothing(transfer_target) Then
    cop.SourceSinkUnits.Add transfer_target
Else
    cop.SourceSinkUnits.Add Units("Sink")
End If

Try-Catch-Finally-Statement

WWB.NET, in INOSIM 13 eingeführt, unterstützt die Try-Catch-Finally-Anweisung, die nach Möglichkeit den Anweisungen On Error vorzuziehen ist. Tritt im Try-Block eine Ausnahme auf, wird der Catch-Block ausgeführt. Der optionale Finally-Block wird immer ausgeführt. Es ist möglich – und empfehlenswert – mehrere Catch-Blöcke zu implementieren, um verschiedene Arten von Ausnahmen abzufangen. Ein Beispiel finden Sie am Ende dieses Tipps & Tricks.

Function Does_Unit_Exist_TryCatchFinally(unit_name As String) As Boolean
    Does_Unit_Exist_TryCatchFinally = True
    Dim u As Unit
    Try
        u = Units(unit_name)
    Catch 
        Does_Unit_Exist_TryCatchFinally = False
    Finally
        '
    End Try
End Function

On Error-Anweisung

In WWB-COM können Sie die Standardfehlerbehandlung mit der On Error Resume Next-Anweisung deaktivieren. Von nun an werden alle Fehler ignoriert (statt die Simulation abzubrechen) bis zur Zeile On Error Goto 0. Das bedeutet für das folgende Beispiel: Tritt beim Zuweisen des Objekts ein Fehler auf, wird das (letzte) Fehlerereignis im Err-Objekt gespeichert. Durch Überprüfen der Err.Number-Eigenschaft gibt die Funktion false oder true zurück. Err.Clear löscht den Fehler.

Alternativ kann ein benutzerdefinierter Fehlerhandler mit einer On Error GoTo [errhandler]-Anweisung erstellt werden. Ein Beispiel finden Sie in dem Modell im Download-Bereich. Die Verwendung von GoTo-Anweisungen ist jedoch generell nicht ratsam. Während sich die Verwendung von GoTo zur strikten Trennung von Geschäftslogik und Fehlerbehandlung bei kleineren Funktionen lohnt, ist dies bei komplexeren Methoden nicht machbar.

 

Function Does_Unit_Exist_OnErrorResumeNext(unit_name As String) As Boolean
    Dim u As Unit
    On Error Resume Next  'sets error handler to continue with next line
        u = Units(unit_name)
        If Err.Number <> 0 Then
            Does_Unit_Exist_OnErrorResumeNext = False
        Else
            Does_Unit_Exist_OnErrorResumeNext = True
        End If
        Err.Clear
    On Error GoTo 0	   'resets error handler to default
End Function

Anwendungsfall: Auf Worksheet-Existenz prüfen mit Try-Catch-Finally

Bei der EndSim-Prozedur werden einige Ergebnisse auf ein Sheet geschrieben, dessen Name sich aus dem Versuchsnamen und einem String zusammensetzt. Es wäre sehr unschön, wenn Sie vergessen hätten, dieses Sheet für einen langen Simulationslauf zu erstellen, und dessen Ergebnisse nun aufgrund eines Laufzeitfehlers verloren gehen.

Der folgende Code verwendet einen Try-Block, um ein Sheet mit dem Namen sheet_name zuzuweisen. Tritt eine Ausnahme auf, wird mit der Funktion Create_Sheet (siehe unten) eine Tabelle mit dem richtigen Namen erstellt und zugewiesen. Der Finally-Block wird unabhängig vom Auftreten einer Ausnahme ausgeführt und ruft den Sub Write_Results_to_Sheet auf, um die Ergebnisse zu schreiben.

Dim ResultsSheet1 As Sheet
Dim experiment_name As String, sheet_name As String
experiment_name = ActiveExperiment.Name
sheet_name = ActiveExperiment.Name & " Results 1"

Try 	'exceptions allowed
    ResultsSheet1 = Parameters.Sheets(sheet_name)
Catch 	'executed if exception occured
    Debug.Print Ex.ToString
    Create_Sheet(sheet_name)
    ResultsSheet1 = Parameters.Sheets(sheet_name)
Finally 	'always executed 
    Write_Results_to_Sheet(ResultsSheet1)
End Try

Bitte beachten Sie, dass die Excel-Referenz unter Tools -> Referenzen aktiviert sein muss, damit der folgende WWB.NET Code funktioniert.

Imports Microsoft.Office.Interop
Imports Microsoft.Office.Interop.Excel

Sub Create_Sheet(sheet_name)
    Dim objApp As Excel.Application
    Dim objWorkBook As Excel._Workbook
    Dim objSheets As Excel.Sheets
    Dim objSheet As Excel._Worksheet
    objWorkBook = Parameters.ExcelWorkbook
    objSheets = objWorkBook.Worksheets
    objSheet = objSheets.Add
    objSheet.Name = sheet_name
End Sub

Anwendungsfall: Kontrolliertes “Aufräumen” bei Laufzeitfehlern

Die COM-Schnittstelle von INOSIM ermöglicht das Arbeiten mit externen Dateien wie Textdateien oder Excel-Arbeitsmappen, siehe T&T Externe Excel-Arbeitsmappen, das Lesen von Simulationsparametern oder das Schreiben von Simulationsergebnissen. Die Verbindung zu externen Dateien muss vor dem Ende der Simulation beendet werden. Andernfalls bleibt die Dateiverbindung im Hintergrund aktiv und verhindert, dass Sie die Datei anschließend manuell öffnen können.

Nehmen wir an, Sie möchten Ihre Simulationsergebnisse in eine externe Excel-Arbeitsmappe schreiben. Während des Schreibens, bevor die Close-Anweisung erreicht wird, tritt ein Fehler auf, der dazu führt, dass die Simulation abgebrochen wird. Somit bleibt die Dateiverbindung aktiv und muss manuell durch Abbrechen des Prozesses im Windows Task Manager geschlossen werden. Andernfalls erhalten Sie beim Versuch, die Datei zu öffnen, eine Warnung, die besagt, dass das Dokument gerade verwendet wird. Um dies zu verhindern, kann eine benutzerdefinierte Fehlerbehandlung für ein kontrolliertes “Aufräumen” verwendet werden: Tritt beim Öffnen ein Fehler auf, wird die Datei automatisch geschlossen.

Wenn Sie „External“ für das benutzerdefinierte Attribut „Results Workbook“ anstelle von „Parameters“ wählen, werden Sie in der Simulation aufgefordert, eine Excel-Datei für die Ergebnisausgabe auszuwählen.

Das folgende Codebeispiel zeigt auch, wie die Try-Catch-Anweisung mit mehreren Catch-Anweisungen für verschiedene Fehlerquellen erweitert werden kann:

  • Tritt beim Schreiben* ein Fehler auf, d. h. während die Datei geöffnet ist, wird eine Fehlermeldung auf die Konsole ausgegeben und die Datei wird gespeichert und geschlossen.
  • Wenn keine Excel-Arbeitsmappe ausgewählt wurde, wird eine Fehlermeldung ausgegeben.

(* In diesem Beispiel wird der Fehler absichtlich durch Aufrufen der Err.Raise-Methode ausgelöst.)

Sub Write_Results_to_External_Workbook
    
    'Write to external excel file (WBB.NET)
    Dim FullFilePath As String
    FullFilePath = GetFilePath("Select File","xlsx",,"Select Excel Workbook",0)

    Dim myWorkbook As Excel._Workbook
    Try
        myWorkbook = OpenExternalWorkbook(FullFilePath)
        Write_Results_to_External_Worksheet(myWorkbook.Worksheets(1))

        myWorkbook.Save    ' Optional
        myWorkbook.Close

    Catch ErrorWriting As System.Exception When Not myWorkbook Is Nothing
Console.Error "An error occured while writing simulation results to the external file."
        myWorkbook.Save    ' Optional
        myWorkbook.Close

    Catch NoFile As System.Exception When myWorkbook Is Nothing
        Console.Error "No Excel-file was selected."

    Finally
        
    End Try

End Sub

Downloads

(für registrierte Anwendende von INOSIM)

  • Beispielmodell Custom Error Handling.ixml
  • PDF-Ausdruck für diesen Tipp & Trick

Fragen?

Möchten Sie mehr über dieses Thema erfahren oder haben weitere Fragen? Bitte kontaktieren Sie uns.

Array ( [posts_per_page] => 3 [post_type] => [category__in] => Array ( [0] => 171 ) [orderby] => rand [order] => ASC )

Mehr Tipps & Tricks

Intervall-Auswertung des Transfer-Reports In diesem Tipp stellen wir Ihnen vor, wie Sie aus den von INOSIM erzeugten Reports automatisiert per VBA weitere Daten berechnen, um…

Mit Hilfsfunktionen, die Sie als Basic-Macro in INOSIM importieren können, hauchen Sie Ihrem Anlagenlayout Leben ein: Durch Animationen stellen Sie Veränderungen von Füllständen, Transportbewegungen von Objekten oder…

In diesem Tipp & Trick geht es um den Einsatz eines VBA Timers zur Messung der Ausführungsdauer von VBA Steuerungen. Um die Perfomance eines Modells…

mehr

INOSIM Kontakt

Zu den lokalen Geschäftszeiten

Deutschland +49 231 97 00 250