Error Managment

  • Hi,

    ich wollte mal grundsätzlich Fragen wie ihr das mit dem Errorhandling im Zusammenhang mit euren eigenen UDF macht.
    z.B. wenn ich mehrere UDF's verwende und viele verschachtelte Funktionen benutzte wird es etwas unübersichtlich die eigentliche Error-Nachricht bis ins Hauptskript zu transportieren.
    Ich mache das derzeit ca. so:
    Hauptscript:

    Spoiler anzeigen
    [autoit]


    #include <Array.au3>
    #include <File.au3>

    [/autoit] [autoit][/autoit] [autoit]

    #include <Nuts_custom1.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Global $logfile=@ScriptDir &"\test.log"
    Global $writelog=Int(Iniread("test.ini", "Settings", "writelog", 1))
    Global $i_modus=Int(Iniread("test.ini", "Settings", "modus", 1))

    [/autoit] [autoit][/autoit] [autoit]

    Global $h_file=FileOpen("testfile.txt")
    Global $s_Filereadline=FileReadLine($h_file, 1)
    FileClose($h_file)

    [/autoit] [autoit][/autoit] [autoit]

    _Errorhandling($i_modus)

    [/autoit] [autoit][/autoit] [autoit]

    Exit

    [/autoit] [autoit][/autoit] [autoit]

    Func _Errorhandling($i_flag) ; Funktion im Hauptskript

    [/autoit] [autoit][/autoit] [autoit]

    local $error

    [/autoit] [autoit][/autoit] [autoit]

    switch $i_flag
    case 1
    $error=_Action_Y($s_Filereadline)
    if @error and $writelog then _FileWriteLog($logfile, $error) ; hier soll jetzt die Fehlermeldung aus den Funktionen landen z.B. "TCP Connect fehlgeschlagen" oder "IP nicht gefunden"
    case 2
    $error=_Action_Z($s_Filereadline)
    if @error and $writelog then _FileWriteLog($logfile, $error)
    case else
    if $writelog then _FileWriteLog($logfile, "ungültiger Modus")
    endswitch

    [/autoit] [autoit][/autoit] [autoit]

    endfunc

    [/autoit]

    includes:

    Spoiler anzeigen
    [autoit]


    #include <Nuts_custom2.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Func _Action_Y($s_data) ; <Nuts_custom1.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Local $IP, $socket, $error

    [/autoit] [autoit][/autoit] [autoit]

    if StringInStr($s_data, "192.168.1.1") then
    $IP=Stringright($s_data, 11)
    $error=_Sende_TCPBefehl($IP)
    if @error then Return SetError(1, 0, $error)
    else
    Return SetError(1, 0, "IP nicht gefunden")
    endif

    [/autoit] [autoit][/autoit] [autoit]

    endfunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _Action_Z($s_data) ; <Nuts_custom1.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Local $IP, $socket, $error

    [/autoit] [autoit][/autoit] [autoit]

    if StringInStr($s_data, "192.168.1.1") then
    $IP=Stringleft($s_data,11)
    $error=_Sende_TCPBefehl($IP)
    if @error then Return SetError(1, 0, $error)
    else
    Return SetError(1, 0, "IP nicht gefunden")
    endif

    [/autoit] [autoit][/autoit] [autoit]

    endfunc

    [/autoit]
    Spoiler anzeigen
    [autoit]


    Func _Sende_TCPBefehl($s_IP) ; <Nuts_custom2.au3>

    [/autoit] [autoit][/autoit] [autoit]

    local $socket = TCPConnect($s_IP, 23)
    if @error then
    Return SetError(1, 0, "TCP Connect fehlgeschlagen") ; diese Fehlermeldung soll jetzt in der Funktion "_Errorhandling" im Hauptskript ankommen und in ein logfile geschrieben werden
    else
    TCPSend($socket, "hallo")
    endif
    endfunc

    [/autoit]

    D.h. ich schleppe die Fehlermeldung, die später im Log des Hauptskripts landen soll, von der tiefsten Ebene (hier _Sende_TCPBefehl($s_IP) bis hin zur höchsten Ebene (Hauptskript).
    Bei diesem Beispiel ist das noch gut zu machen, aber bei größeren Skripten muss man höllisch (in jeder Funktion) aufpassen die Fehlermeldung der tieferen Ebene nicht zu verlieren.
    Und wenn das passiert ist geht die Fehlersuche los, wo die Nachricht verloren gegangen ist. :(

    Das Logfile von der obersten Ebene (Hauptskript) an die includes zu übergeben ist auch keine Lösung.
    Vielleicht habt ihr eine einfachere und bessere Lösung?

    Ich würde gerne die Fehlermeldung von jeder Ebene direkt ins Hauptskript bringen und innerhalb der UDF's nur @error Codes verwenden.
    Vergleichbar mit dem Errorhandler für COM

    [autoit]

    ObjEvent("AutoIt.Error", "_ErrFunc")

    [/autoit]

    Würde mich über Vorschläge freuen. :thumbup:

    Gruß nuts

    Einmal editiert, zuletzt von nuts (10. Januar 2015 um 17:18)

  • Hi,

    ich fange jede Errormeldung im Fehlerfall in einer Msgbox ab:
    Msgbox(0,"Funktionsname","Fehlercode Nr. " & @error & " in Modul Modulname in Funktion Funktionsname")
    Das Errormanagement bzw. dessen Unterstützung ist anderen Sprachen gegenüber von AutoIt sehr stiefmütterlich umgesetzt.
    Wer gewohnt ist, mit Fehlerbeschreibungen uvm. vollgeschmissen zu werden, steht bei AutoIt im Regen.
    Mal davon abgesehen, bei compilierten Scripten immer noch mit der Fehlermeldung "Error 0 in Zeile 5234" abgespeist zu werden.

    Daher schreibt man sich bzw. in verwendeten UDF´s die Fehlerbehandlung selbst!

    Vergleichbar mit dem Errorhandler für COM


    Ja, der ist klasse umgesetzt!

    Wenn die Errorcodes wenigstens durchgehend nach einem System durchnummeriert wären, hätte man auch kein Problem, eine simple Funktion
    OnError(@error) anzuspringen, in der dann die gesamte Auswertung ablaufen würde.
    Denn auf das seit 35 Jahren benutzte und bewährte "On Error Gosub" können wir lange warten, das ist nämlich BASIC-like und somit seitens Dev´s lame und hat in einer modernen Sprache nichts zu suchen....
    Dann lieber GARKEIN oder von den Usern selbstgeschnitztes Errormanagement, das ist hip und passt auch zur Zielgruppe!

  • Naja "msgbox" hat imho in "includes" nichts verloren.
    Die sind bei mir allgemein gehalten und auch ohne Benutzeraktion verwendbar.

    z.B. "<Nuts_custom2.au3>" wird in mehreren Hauptskripten verwendet (mal mit, mal ohne Benutzeraktion).
    Sonst müsste man sich den Spaß ja nicht machen und Funktionen auf includes auslagern.
    Glaube ich führe eine global "Error" Variable je "include" ein.
    Ist auch nicht schön, aber besser gehts wohl nicht.

  • Naja "msgbox" hat imho in "includes" nichts verloren.


    Wer Lesen kann, ist klar im Vorteil, ich schrieb "im FEHLERFALL".
    Jetzt kommt der Einwand, mit @error doch sowieso nur Fehler abzufangen. FALSCH!
    @error gibt keinesfalls Fehlercodes zurück, sondern ausschließlich ( DAS ist der Punkt ! ) selbstdefinierte Zustandsflags des Programmcodes. Viel schlimmer noch, irgendwelche Fehlercodes der internen Funktionen.
    Und diese Flags abzufragen ist die Kunst.

    Ich löse das in AutoIt-Scripten so, dass in globalen Variablen der Funktions- und Modulname gesetzt wird. Wenn man das auf die Spitze treiben will, auch das Programmsegment innerhalb des Moduls/Funktion.
    Bei jedem Funktionseinsprung und innerhalb der Funktion werden diese Variablen entsprechend gesetzt.

    Spoiler anzeigen
    [autoit]


    ;includefile blablub.au3

    [/autoit] [autoit][/autoit] [autoit]

    Func _Funktion_123(..., $Dateiname, ..);funktion
    $NAME_MODUL = "blablub.au3" ;modulname
    $NAME_FUNCTION = "_Funktion_123";funktionsname
    ..
    ..
    $NAME_SEGMENT = "Datei einlesen";segmentname
    $dateiinhalt = _FileREAD($Dateiname) ;eigene Funktion, mit FEHLERMELDUNG, nicht mit Errorcode!
    ..
    $NAME_SEGMENT = "Dateiinhalt auswerten";segmentname
    For ... To ...
    $bla_array = StringSplit(...)
    $blub_string &= StringMid($bla_array...)
    Next
    ..
    $NAME_SEGMENT = "Auswertung" ;segmentname
    While $wum
    If $wendelin Then
    $return = ...
    Else
    $return = ...
    EndIf
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    Return $return ;rückgabe

    [/autoit] [autoit][/autoit] [autoit]

    EndFunc ;==>_Funktion_123

    [/autoit] [autoit][/autoit] [autoit]

    Func _Funktion_456(..., $Dateiname, ..);funktion
    $NAME_MODUL = "blablub.au3" ;modulname
    $NAME_FUNCTION = "_Funktion_456";funktionsname
    ..
    $NAME_SEGMENT = "abcde"
    ..
    $NAME_SEGMENT = "xyzuvw"
    ..
    EndFunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _Funktion_789(..., $Dateiname, ..);funktion
    $NAME_MODUL = "blablub.au3" ;modulname
    $NAME_FUNCTION = "_Funktion_789";funktionsname
    ..
    $NAME_SEGMENT = "abcde"
    ..
    $NAME_SEGMENT = "xyzuvw"
    ..
    EndFunc

    [/autoit]

    Hat den unermesslich großen Vorteil, dem ANWENDER eine Fehlermeldung in einer Msgbox präsentieren zu können:
    "Die Datei qwerty.txt kann nicht bearbeitet werden." Im unteren Teil der Msgbox steht dann:
    "Der Fehler /Die Datei qwerty.txt kann nicht geöffnet werden/ ist aufgetreten in Modul blablub.au3 in Funktion _Func_123() im Segment xxxxx. Funktionsparameter sind aaa,bbb,ccc,ddd ".

    Diese Infos kann der Anwender dem Programmierer dann am Telefon mitteilen. Mittlerweile handhabe ich es so, dass ich in der Firma im FEHLERFALL automatisiert eine e-mail mit dieser Beschreibung und vom Arbeitsplatz bzw. Anwender bekomme. Inclusive die letzten 50 Zeilen Logfile (von diesem Arbeitsplatz).

    DAS nenne ich Fehlermanagement, und nicht ein "Error 28 in Line 43841". Die der Anwender einfach nur wegklickt und dann 20 mal versucht, die Anwendung nochmal laufen zu lassen und entnervt anruft und "DEIN PROGRAMM FUNKTIONIERT NICHT" ins Telefon schreit!

    Und bzgl. MsgBox in Includes....geh mal ganz tief in dein Inneres und frag dich dann mal in welcher Form AutoIt einen FEHLER wirft!


    Letztendlich kommt es darauf an, dem Anwender eine Information zukommen zu lassen, mit der er die Möglichkeit hat zu entscheiden, was er weiter tun muss!
    Ich habe innerhalb eines Produktionsunternehmens u.a. ein selbstgeschriebenes, scannergestütztes Monitoring- und Ablauforganisationssystem eingeführt und vorgehabt, dieses mit AutoIt umzusetzen. Da stehen Mitarbeiter vor Maschinen und haben (Takt-)Zeitvorgaben von unter einer Minute. Das ist ein Unterschied zu einem Bürobetrieb, wo du nach der Anzeige einer "Fehlermeldung" erstmal einen Kaffee holen gehst und dann "nochmal probierst" ob sich der Fehler in Luft aufgelöst hast....
    Mittlerweile verwende ich AutoIt in der Firma NUR NOCH zur "Fernsteuerung" bestehender Branchensoftware, und das mit dem Hintergrund, dass imho das AutoIt-Errormanagemend (und nicht nur das ! ) für meine professionellen Zwecke unzureichend ist!

  • <OT>
    @Andy: Es ist doch immer wieder unfassbar, wie einfach sowas sein kann. Seit ich hier angefangen hab, "rumzuspielen" hab ich überlegt, wie ich denn bei Fehlern den Funktionsnamen erhalten kann.
    Und NIE (!!!) bin ich auf diese einfache Lösung gekommen. Ich mach das hier wirklich nur nebenbei, weil ich das interessant finde. Beruflich brauch ich AutoIt überhaupt nicht und hab auch damals in der Schule
    zwar Informatik gehabt (das war ´93, glaub ich) doch da hiess es nur: "... und so sieht ein DOS Fenster aus..."

    Vermutlich liegts auch daran, dass ich nicht so umfassend über diesen Kram nachdenke. Hab das Gefühl, dass hier jeder viel motivierter ist als ich :) </OT>

    Ich hatte mir überlegt eine Error_UDF zu basteln, in der ich mir eigene Codes setze mit entsprechender Erklärung und diese dann im Hauptscript zurück bekomme. Doch das hab ich schnell aufgegeben.
    Wurde mir zu umfangreich und zu umständlich jedesmal diese UDF zu erweitern. Dachte mir, dass es auch einfacher gehen muss. Und TADAAAA, Danke Andy :thumbup:


  • Wer Lesen kann, ist klar im Vorteil, ich schrieb "im FEHLERFALL".


    Hier ist eine Stimmung ... wo hast du denn beschrieben wo deine msgbox eingesetzt wird? ?(


    Hat den unermesslich großen Vorteil, dem ANWENDER eine Fehlermeldung in einer Msgbox präsentieren zu können:
    "Die Datei qwerty.txt kann nicht bearbeitet werden." Im unteren Teil der Msgbox steht dann:
    "Der Fehler /Die Datei qwerty.txt kann nicht geöffnet werden/ ist aufgetreten in Modul blablub.au3 in Funktion _Func_123() im Segment xxxxx. Funktionsparameter sind aaa,bbb,ccc,ddd ".


    Natürlich und das versuche ich auch so zu machen (im Hauptskript, daher der Thread ;) ), aber wenn man in den UDF's Funktionen auslagert, die universell einsetzbar sein sollen (z.B. auch für Skripte, die im Systemkonto laufen), sollte man keine msgboxen quer durch die includes verteilen!


    Und bzgl. MsgBox in Includes....geh mal ganz tief in dein Inneres und frag dich dann mal in welcher Form AutoIt einen FEHLER wirft!


    Eigentlich nur wenn externe Daten oder Schnittstellen verwendet werden auf die das Skript keinen Einfluss hat.

    Zusammengefasst verwendest du globale Variablen in den includes richtig? ?(

  • Das mit der globalen Variable in einer eigenen Error UDF gefällt mir glaube ich.
    Was haltet ihr davon:

    Spoiler anzeigen
    [autoit]


    #include <Array.au3>
    #include <File.au3>

    [/autoit] [autoit][/autoit] [autoit]

    #include <myError.au3>
    ;#include <Nuts_custom1.au3>
    ;#include <Nuts_custom2.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Global $logfile=@ScriptDir &"\test.log"
    Global $writelog=1 ;Int(Iniread("test.ini", "Settings", "writelog", 1))
    Global $i_modus=1; Int(Iniread("test.ini", "Settings", "modus", 1))

    [/autoit] [autoit][/autoit] [autoit]

    ;Global $h_file=FileOpen("testfile.txt")
    Global $s_Filereadline="hallo"
    Global $s_Filereadline="192.168.1.1"
    ;FileClose($h_file)

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, "Error", _Get_myError())
    _Errorhandling($i_modus)
    MsgBox(0, "Error", _Get_myError())
    Exit

    [/autoit] [autoit][/autoit] [autoit]

    Func _Errorhandling($i_flag) ; Funktion im Hauptskript

    [/autoit] [autoit][/autoit] [autoit]

    local $error

    [/autoit] [autoit][/autoit] [autoit]

    switch $i_flag
    case 1
    $error=_Action_Y($s_Filereadline)
    if @error and $writelog then MsgBox(0, "Error", _Get_myError()) ;_FileWriteLog($logfile, $error) ; hier soll jetzt die Fehlermeldung aus den Funktionen landen z.B. "TCP Connect fehlgeschlagen" oder "IP nicht gefunden"
    case 2
    $error=_Action_Z($s_Filereadline)
    if @error and $writelog then MsgBox(0, "Error", _Get_myError())
    case else
    if @error and $writelog then MsgBox(0, "Error", "ungültiger Modus")
    endswitch

    [/autoit] [autoit][/autoit] [autoit]

    endfunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _Action_Y($s_data) ; <Nuts_custom1.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Local $IP, $socket, $error

    [/autoit] [autoit][/autoit] [autoit]

    if StringInStr($s_data, "192.168.1.1") then
    $IP=Stringleft($s_data,11)
    _Sende_TCPBefehl($IP)
    if @error then Return SetError(1, 0, 0)
    else
    _Set_myError("IP nicht gefunden")
    Return SetError(1, 0, 0)
    endif

    [/autoit] [autoit][/autoit] [autoit]

    endfunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _Action_Z($s_data) ; <Nuts_custom1.au3>

    [/autoit] [autoit][/autoit] [autoit]

    Local $IP, $socket, $error

    [/autoit] [autoit][/autoit] [autoit]

    if StringInStr($s_data, "192.168.1.1") then
    $IP=Stringleft($s_data,11)
    _Sende_TCPBefehl($IP)
    if @error then Return SetError(1, 0, 0)
    else
    _Set_myError("IP nicht gefunden")
    Return SetError(1, 0, 0)
    endif

    [/autoit] [autoit][/autoit] [autoit]

    endfunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _Sende_TCPBefehl($s_IP) ; <Nuts_custom2.au3>

    [/autoit] [autoit][/autoit] [autoit]

    local $socket = TCPConnect($s_IP, 23)
    if @error then
    _Set_myError("TCP Connect fehlgeschlagen")
    Return SetError(1, 0, 0) ; diese Fehlermeldung soll jetzt in der Funktion "_Errorhandling" im Hauptskript ankommen und in ein logfile geschrieben werden
    else
    TCPSend($socket, "hallo")
    endif
    endfunc

    [/autoit]
    Spoiler anzeigen
    [autoit]


    #include-once

    [/autoit] [autoit][/autoit] [autoit]

    Global $s_my_errormessage=false
    Global $a_my_errorinfos[4][2]

    [/autoit] [autoit][/autoit] [autoit]

    Func _Get_myError($i_flag=1, $b_reset=1)

    [/autoit] [autoit][/autoit] [autoit]

    local $return

    [/autoit] [autoit][/autoit] [autoit]

    switch $i_flag
    case 1
    $return=$s_my_errormessage
    case 2
    $return=$a_my_errorinfos
    EndSwitch

    [/autoit] [autoit][/autoit] [autoit]

    if $b_reset then _Clear_myError()

    [/autoit] [autoit][/autoit] [autoit]

    Return $return
    endfunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _Set_myError($s_message, $i_flag=1, $s_includename="", $s_functionname="", $i_line=0)
    switch $i_flag
    case 1
    $s_my_errormessage=$s_message

    [/autoit] [autoit][/autoit] [autoit]

    case 2
    $a_my_errorinfos[0][0]="Error Message"
    $a_my_errorinfos[0][1]=$s_message

    [/autoit] [autoit][/autoit] [autoit]

    $a_my_errorinfos[1][0]="Error in include"
    $a_my_errorinfos[1][1]=$s_includename

    [/autoit] [autoit][/autoit] [autoit]

    $a_my_errorinfos[2][0]="Error in Function"
    $a_my_errorinfos[2][1]=$s_functionname

    [/autoit] [autoit][/autoit] [autoit]

    $a_my_errorinfos[3][0]="Error in Line"
    $a_my_errorinfos[3][1]=$i_line
    EndSwitch
    endfunc

    [/autoit] [autoit][/autoit] [autoit]

    Func _Clear_myError()

    [/autoit] [autoit][/autoit] [autoit]

    $s_my_errormessage=false

    [/autoit] [autoit][/autoit] [autoit]

    $a_my_errorinfos[0][0]="Error Message"
    $a_my_errorinfos[0][1]=""

    [/autoit] [autoit][/autoit] [autoit]

    $a_my_errorinfos[1][0]="Error in include"
    $a_my_errorinfos[1][1]=""

    [/autoit] [autoit][/autoit] [autoit]

    $a_my_errorinfos[2][0]="Error in Function"
    $a_my_errorinfos[2][1]=""

    [/autoit] [autoit][/autoit] [autoit]

    $a_my_errorinfos[3][0]="Error in Line"
    $a_my_errorinfos[3][1]=0

    [/autoit] [autoit][/autoit] [autoit]

    endfunc

    [/autoit] [autoit][/autoit] [autoit][/autoit]