Guter Stil für "Fehlerbehandlungen"?

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Guter Stil für "Fehlerbehandlungen"?

Beitrag von starcow »

Abend liebe ZFX'ler :-)

Erfreulicherweise macht mein BMP-Parser gute Fortschritte. Nun gibt es verschiedene Stellen in meinem Code, an welchen ich auf "Ausnahmen" reagieren muss - oder zumindest sollte. Zum Einen versuche ich ein File zu öffnen und reserviere im Anschluss mittels malloc dynamisch Speicher. Beides kann ja bekanntermassen auch mal fehlschlagen kann. Zum Anderen sind da noch diverse "Sonderfälle" in den BMP-Spezifikationen, die mein Parser nicht abdeckt.
Kurzum: ich muss an etlichen Stellen im Code auf allfällige Ausnahmen reagieren und kann nur fortfahren, wenn diese ausbleiben.

An diesem Punkt hat sich dann die Frage aufgedrängt, was ein guter (sinnvoller) Stil sein könnte, um den Code weiterhin gut leserlich und einfach erweiterbar zu halten.
Grob umrissen sind mir dazu vier verschiedene Methoden eingefallen. Mich würde an dieser Stelle interessieren, was ihr davon haltet und wie ihr solche, oder ähnliche Situationen implementiert.
(Es ist übrigens ein reines C Projekt)

Möglichkeit 1 - if else Verschachtelungen
Es wird mittels if-Statement geprüft ob alles ok ist. Die Fortsetzung des Programmablaufs befindet sich dann innerhalb eines if-Blocks - die Fehlerbehandlung innerhalb des else-Blocks (oder andersrum).
Dadurch weist der Code mehrere Verschachtelungs-Ebenen auf.
Aufgeräumt wird in den entsprechenden if-else-Blöcken.

Möglichkeit 2 - setzen einer error oder goOn Variable
Jeder Code-Abschnitt ist von einem if-Statement eingeschlossen, das eine error- oder goOn Variable prüft. Geht etwas schief, wird die Variable entsprechend geändert und damit alle weiteren Abschnitte in Folge übersprungen.
Somit weist der Code grundsätzlich nur eine Verschachtelungs-Ebene auf.
Aufgeräumt (fclose(), free(), etc.) wird dann am Schluss - an einer einzelnen, zentralen Stelle.

Möglichkeit 3 - Vorzeitiges return (an mehreren Stellen)
Ein if-Statement wird geprüft. Geht etwas schief, wird aufgeräumt und mittels return frühzeitig zurückgekehrt. Somit gäbe es nicht nur ein einzelnes return, sondern mehrere.

Möglichkeit 4 - Mittels goto die hinfälligen Code-Abschnitte überspringen
Ein if-Statement wird geprüft. Geht etwas schief, wird mittels goto zu einer Aufräum-und-return Marke gesprungen. Diese ist am Ende der Funktion platziert. Das goto scheint ja keinen sonderlich guten Ruf zu geniessen, doch vielleicht wäre es hier eine adäquate Lösung?

Was denkt ihr dazu? Gibt es vielleicht eine Methode, die in der Praxis häufiger verwendet wird, da sie bestimmte Vorteile bietet?

Ergänzend wollte ich noch, je nach auftretendem Fehler ein string literal zurückgeben. In diesem Zusammenhang habe ich dann realisiert, dass ich mir gar nicht sicher bin, ob ich das so machen darf, wie ich mir das vorgestellt hatte:

Fall 1:

Code: Alles auswählen

const char* msg;
...
if(1)
{
	msg = "ZFX";    // string literal nicht im gleichen Block (scope) wie der pointer msg
}
Fall 2:

Code: Alles auswählen

void foo(const char** msg)
{
	*msg = "Alles hat geklappt";    // string literal mit "Herkunft" aus einer anderen Funktion als der pointer msg
	return;
}
Fall 3:

Code: Alles auswählen

const char* foo(void)
{
       return "Ist das noch legal?";    // Adresse des string literals mit "Herkunft" aus einer anderen Funktion
}
Die string literale liegen ja eigentlich alle auf dem Data-Segment, oder? Da dieses ja unabhängig und beständig ist (nicht wie lokale Variablen einer Funktion auf dem Stack), müssten eigentlich somit alle drei Fälle in Ordnung sein. Oder übersehe ich da vielleicht etwas?

Gruss und guten Rutsch ins neue Jahr! :-)

LG, starcow
Zuletzt geändert von starcow am 26.02.2024, 14:05, insgesamt 1-mal geändert.
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Krishty »

starcow hat geschrieben: 30.12.2022, 22:57Was denkt ihr dazu? Gibt es vielleicht eine Methode, die in der Praxis häufiger verwendet wird
Wo sind C++-Ausnahmen?

Ja, die String-Literale liegen im Datenabschnitt und sind von überall zugreifbar. Ich empfehle dir aber, dir des Unterschieds klar zu werden zwischen Fehlererkennung (Palette zu kurz), Fehlermeldung (Konsolenausgabe auf Englisch oder lokalisierter String in asynchroner GUI?), und Fehlerbehandlung (Abbrechen oder für die fehlenden Farben Schwarz annehmen?). Ich fahre extrem gut damit, dass meine Loader nur Fehlererkennung enthalten.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Jonathan
Establishment
Beiträge: 2353
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Jonathan »

Naja, ich benutze halt C++ und Exceptions. Das hast du ja gewissermaßen schon ausgeschlossen, deshalb weiß ich nicht wie nützlich mein Beitrag hier ist, aber vielleicht sind meine Argumente dafür ja doch interessant.

Ich entwickel hauptsächlich Spiele und keine Serversoftware, meine Anforderungen an Stabilität sind also anders. Außerdem ist mir robuster Code wichtiger als maximale Effizienz. Ich komme eh schon nicht dazu alles umzusetzen, was ich mir vornehme, da will ich es vermeiden unnötigen Code zu schreiben und vor allen Dingen will ich unnötiges Debuggen mit sehr hoher Priorität vermeiden. Wenn ich 20% weniger Objekte rendern kann und dafür eine Woche debuggen spare, ist mir das die Sache wert.

Meine Strategie ist daher: Ich will maximal nützliche Fehlermeldungen und mir ist egal ob mein Programm danach abstürzt. Denn Ausnahmen sind Ausnahmen die im fertigen Spiel nicht auftreten sollen. Wie gesagt, ich mache keine Serversoftware, ich kann es mir leisten bei Fehlern alles zu beenden, das Problem zu fixen und neu zu starten.

Ich mag Error codes einfach nicht, weil man bei jedem Aufruf alles checken muss. Vergisst man das einmal und hat dort einen Fehler kann man den ggf. ewig suchen. Das ist kein robuster Code weil er erfordert, dass an jeder Stelle die Fehlerbehandlung richtig implementiert wird, und soweit vertraue ich mir nicht.

Was an Exceptions schön ist: Man kann an mehreren Stellen die Fehlermeldungen weiter ausbauen. Eigentlich alle catch Blöcke in meinem Programm nehmen einfach das Exception-Objekt, erweitern die Fehlermeldung um nützliche Informationen und schmeißen sie danach direkt wieder. Dann sehe ich z.B. welches Level welches Asset lädt, dass dann eine Textur lädt deren Dekodierung einen Fehler produzieren. Im Texture-Loader selber hat man all diese Informationen nicht und kann keine sinnvollen Fehlermeldungen produzieren.

Ich schätze, man könnte auch konsequent Breakpoints setzen und sich das Programm im Debug-Build anschauen und hätte die selben Infos ohne Exceptions. Aber zumindest C++ unter VC ist im Debug-Build halt mitunter sau langsam, also bin ich 95% der Zeit nur im Release unterwegs und kriege dann halt mit Exceptions meine nützlichen Fehlermeldungen. Und es ist halt super einfach und bequem zu implementieren. Fehler? throw. Fertig.

Ich weiß, dass alleine die Möglichkeit zu Exceptions Code langsamer macht und das in manchen (meist arg konstruierten? Wer weiß) wohl auch "extreme" Unterschiede machen kann. Aber wenn ich maximale Performance wollte müsste ich wohl eh andere und effizientere Algorithmen implementieren und z.B. Vulcan statt OpenGL verwenden und so weiter. Was ich damit sagen will: Mein Hobbyisten-Code hat eh viel Optimierungsspielraum, da werde ich gewiss nicht damit anfangen Fehlermeldungen weniger bequem zu machen und damit meine Entwicklung weniger effizient machen.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Alexander Kornrumpf »

Das eigentliche Problem, das du hier nicht nennst, ist mMn, dass der korrekte "Aufräumcode" im allgemeinen Fall davon abhängt, was genau wo schief gegangen ist, denn du musst erstmal wissen ob du eine Resource je hattest bevor du sie freigeben kannst. Wenn die Resource Speicher ist, kommst du möglicherweise damit durch, dass free(NULL) legal ist, aber wenn es eine Datei oder ein Socket oder so ist ... Das ist nach meinem Verständnis das Problem (EDIT: eines der Probleme, das Hauptproblem in C++ ist dass eine Exception fliegen kann während man eine Resource hält), das RAII in C++ löst und ich habe keine Ahnung was man da in C macht. Krishty wird es wissen.

Das gesagt habend: In Sprachen, die nicht C sind erzeugt (3) meiner Erfahrung nach den lesbarsten Code, das generalisiert aber nicht auf C, weil ein return in C eben gerade nicht aufräumt. Das ist ja gerade dein Problem.

Das es mich selbst interessiert hat, habe ich mal kurz gegooglet. Das hattest du gefunden? https://resources.sei.cmu.edu/asset_fil ... 484207.pdf
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von starcow »

Interessant! Danke für den Einblick Jonathan. :-)

Also löst ihr solche Dinge grundsätzlich mit C++ exeptions...
Krishty hat geschrieben: 30.12.2022, 23:30
starcow hat geschrieben: 30.12.2022, 22:57Was denkt ihr dazu? Gibt es vielleicht eine Methode, die in der Praxis häufiger verwendet wird
Wo sind C++-Ausnahmen?

Ja, die String-Literale liegen im Datenabschnitt und sind von überall zugreifbar. Ich empfehle dir aber, dir des Unterschieds klar zu werden zwischen Fehlererkennung (Palette zu kurz), Fehlermeldung (Konsolenausgabe auf Englisch oder lokalisierter String in asynchroner GUI?), und Fehlerbehandlung (Abbrechen oder für die fehlenden Farben Schwarz annehmen?). Ich fahre extrem gut damit, dass meine Loader nur Fehlererkennung enthalten.
Ich muss zugeben, dass ich nie etwas mit den C++ exeptions gemacht habe. Wird dieses Thema nicht oft kontrovers diskutiert? (vielleicht bringe ich das aber auch durcheinander).
Was vergleichbares zu C++ expetions gibts aber für plain C nicht - oder doch?

LG, starcow

Edit:
Alexander Kornrumpf hat geschrieben: 30.12.2022, 23:49 Das eigentliche Problem, das du hier nicht nennst, ist mMn, dass der korrekte "Aufräumcode" im allgemeinen Fall davon abhängt, was genau wo schief gegangen ist, denn du musst erstmal wissen ob du eine Resource je hattest bevor du sie freigeben kannst. Wenn die Resource Speicher ist, kommst du möglicherweise damit durch, dass free(NULL) legal ist, aber wenn es eine Datei oder ein Socket oder so ist ... Das ist nach meinem Verständnis das Problem (EDIT: eines der Probleme, das Hauptproblem in C++ ist dass eine Exception fliegen kann während man eine Resource hält), das RAII in C++ löst und ich habe keine Ahnung was man da in C macht. Krishty wird es wissen.

Das gesagt habend: In Sprachen, die nicht C sind erzeugt (3) meiner Erfahrung nach den lesbarsten Code, das generalisiert aber nicht auf C, weil ein return in C eben gerade nicht aufräumt. Das ist ja gerade dein Problem.

Das es mich selbst interessiert hat, habe ich mal kurz gegooglet. Das hattest du gefunden? https://resources.sei.cmu.edu/asset_fil ... 484207.pdf
Stimmt schon mit dem Aufräumen, gut erkannt!
Das ganze ist jetzt in diesem konkreten Fall halt noch sehr überschaubar. Das lässt sich dann dadurch umgehen, dass ich einfach free() auf einen Nullpointer in kauf nehme (wie du bereits gesagt hast) oder alternativ mittels:
if(ptr) free(ptr);
Aber das Grundsatzproblem bei grösseren Geschichten bleibt natürlich.
(Danke für den Link! Hatte ich noch nicht!)
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Krishty »

Alexander Kornrumpf hat geschrieben: 30.12.2022, 23:49Das ist nach meinem Verständnis das Problem […] das RAII in C++ löst und ich habe keine Ahnung was man da in C macht. Krishty wird es wissen.
Nein. Aber ich nutze in meinem C-artigen Code Garbage Collection via Region-based Memory Allocation; das vereinfacht solche Dinge krass. Ist aber ein anderes Thema.

Tatsächlich hatte ich auch das
starcow hat geschrieben: 30.12.2022, 22:57(Es ist übrigens ein reines C Projekt)
überlesen m[

Möglichkeit 3 (vorzeitiges return) ist nur haltbar, so lange keine Ressourcen verwaltet werden müssen. Sonst häuft man quadratisch Aufräum-Befehle an. Völlig unwartbar.

Möglichkeiten 1 und 4 (geschachtelte ifs und goto zum Aufräumen) sind in C Äquivalent und erzeugen den besten Code. goto ist außerdem die kürzeste Variante. Beide sind unhaltbar, wenn die Funktion mehr als einen erfolgreichen Ausführungspfad hat; in C++ sind sie unterschiedlich weil goto keine Variablen-Initialisierung überspringen darf. Mein super effizienter und kurzer C-Code konnte unter C++ einfach nicht mehr kompiliert werden.

Ich nutze üblicherweise Möglichkeit 3 (vorzeitiges return) und 1 (geschachteltes if) im Wechsel; je nachdem, wie viele Pfade es durch die Funktion gibt und wie viele Ressourcen zu verwalten sind. C++-Ausnahmen nutze ich nicht, weil ich unsichtbare Ausführungspfade ablehne; für die meisten Probleme anderer Programmierer als mir sind sie aber eine tolle Lösung.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Alexander Kornrumpf »

Siehe auch Slide 39ff in dem pdf, das ich oben verlinkt hatte.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Krishty »

Siehe auch Slide 65, um C hassen zu lernen.

(Das Testing or Production? finde ich übrigens heikel, weil ich bisher ständig gesehen habe, dass irgendein Test-Code nach Production kopiert und ausgeliefert wurde. Oder Entwickler und Projektleiter hatten unterschiedliche Ansichten, ob man gerade an Testing arbeitet oder an Production.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Lord Delvin
Establishment
Beiträge: 574
Registriert: 05.07.2003, 11:17

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Lord Delvin »

Anm. Vorab: Im Kern stimme ich den Vorrednern zu; ich denke aber, dass das eigentliche Problem der Frage nicht bearbeitet wurde; nichts von dem was ich sage meine ich irgendwie persönlich; ist mehr eine frustrierte Zusammenfassung jahrelangen Schmerzes mit Fehlerbehandlung.
Krishty hat geschrieben: 31.12.2022, 00:42 Siehe auch Slide 65, um C hassen zu lernen.

(Das Testing or Production? finde ich übrigens heikel, weil ich bisher ständig gesehen habe, dass irgendein Test-Code nach Production kopiert und ausgeliefert wurde. Oder Entwickler und Projektleiter hatten unterschiedliche Ansichten, ob man gerade an Testing arbeitet oder an Production.)
Das Phänomen kenne ich so auch. Eine Reaktion darauf wäre insbesondere auch, dass es keine Prototypen oder PoCs oder sowas gibt, weil das einfach immer in Production geht, wenn es irgendwie funktioniert. Wie es ist. Jedes mal :(

Eine Ausnahme sind imho assertions, die nur dann greifen, wenn man ohnehin nicht durch die Testpipeline käme und letztlich dazu dienen, entweder Invarianten zu dokumentieren oder wiederkehrende Fehleinschätzungen abzufangen.

starcow hat geschrieben: 30.12.2022, 22:57 (Es ist übrigens ein reines C Projekt)
Das ist vollkommen egal. Letztlich erklärt es nur, warum deine Einschätzungen so sind, wie sie sind. Das hätte man sich allerdings auch schon anhand deiner Optionsliste denken können; hätte dann auch Rust oder Go sein können.

Jonathan hat geschrieben: 30.12.2022, 23:35 Ich entwickel hauptsächlich Spiele und keine Serversoftware, meine Anforderungen an Stabilität sind also anders. Außerdem ist mir robuster Code wichtiger als maximale Effizienz. Ich komme eh schon nicht dazu alles umzusetzen, was ich mir vornehme, da will ich es vermeiden unnötigen Code zu schreiben und vor allen Dingen will ich unnötiges Debuggen mit sehr hoher Priorität vermeiden.
HAHAHAHAHAHAHA. (tschuldigung, aber wenn du das nochmal liest verstehst du bestimmt warum ich es lustig finde)

starcow hat geschrieben: 30.12.2022, 22:57 Somit gäbe es nicht nur ein einzelnes return, sondern mehrere.
Das ist letztlich meine Motivation gewesen, überhaupt zu reagieren; habe die anderen Antworten nur überflogen; wirkten aber nicht so, als wären sie den Schritt zurückgetreten und hätten sich Fehlerbehandlung an sich angeschaut. Das Slide 65 oben geht in die richtige Richtung, ist letztlich aber auf hahnebüchene Weise falsch.

Lasst mich versuchen zu erklären, warum das so ist.

Also treten wir einen Schritt zurück und überlegen uns, worum es eigentlich geht.
Du willst eine Datei laden, sie interpretieren und dann das interpretierte Ergebnis zurückgeben.
Aus Nicht deines Anwenders bist du einfach eine Funktion von Pfad auf Bitmapobjekt.
Was kann alles schiefgehen?
Spontan fallen mir ein paar Sachen ein, du wirst den Rest in deinem Code einfach sehen, weil du das behandeln müssen wirst.

Der Pfad den du bekommst musst du in Bytes einer Datei umwandeln.
Alles, was bedeutet, dass du eine nicht lesbare Datei hast, ist die Schuld deines Aufrufers.
Das musst du ihm mitteilen.
Du kannst nicht selbst recovern.
Das Ergebnis muss also ein behandelbarer Fehler sein.
Der Aufrufer will vielleicht den Nutzer auffordern einen richtigen Pfad anzugeben; die Recovery selbst muss dir egal sein.

Wenn du Dateien liest kann es sein, dass du sie nicht ganz lesen kannst oder irgendein obskurer IO-Error auftritt oder sowas.
Das würde ich in aller Regel gar nicht behandeln und einfach so tun, als würde das nicht passieren können.
Ausnahme wäre irgendein high availability oder echtzeit Kram; wenn du sowas baust gibt es für sowas in der Regel auch Richtlinien die dir sagen wie du reagierst.

Out of memory würde ich in aller Regel auch wie einen IO-Error behandeln.
Soll der User einfach mehr RAM kaufen; soll dein Aufrufer einfach irgendwas algorithmisch verbessern.
RAM ist erstmal unendlich und kommt aus malloc raus.

Jetzt kommt die eigentliche Arbeit. Wenn beim interpretieren der Bytes irgendwas schiefgeht, dann musst du wissen, wie du reagierst. Ob du recovern kannst oder nicht kannst nur du entscheiden; es ergibt sich oft aus dem Problem.
Ich würde das mal im großen und ganzen als recoverable parse error zusammenfassen.

Jetzt bist du fertig; du kannst die hilfsstrukturen aufräumen, ressourcen freigeben und das ergebnis zurückgeben.
Alles was dabei schiefgeht ist irgendwie problematisch. In deinem Fall wird das einfach sein, aber wenn du echte Ressourcen hast, bei deren Freigabe echte Fehler auftreten können muss man sich oft hinsetzen und länger an der Fehlerbehandlung arbeiten als am "happy path". Da kommt dann auch manchmal code raus, der nicht mehr wirklich gut zu verstehen ist.

Wie du das genau realisierst ist letztlich komplett egal. Die Kosten von return vs exception auch. Je größer das Projekt wird, desto wahrscheinlicher kommst du nurnoch mit Exceptions klar. Bei Fehlerwerten hat man schlicht das Problem, dass man sie bei nichtbehandlung unterdrückt, was nahezu immer falsch ist.

Bei behandelbaren Fehlern muss es immer so sein, dass du netto keine Ressourcen verbrauchst, d.h. du musst alle von dir angeforderten Ressourcen wieder freigeben. Hier Speicher und Dateihandles, wenn du die Datei selbst öffnest oder per Vertrag schließt.
Behandelbare Fehler kannst du als Exceptions oder Rückgabeobjekte implementieren. Schau dir an, was die Standardbibliothek macht; in der Regel will man nicht davon abweichen. Bei manchen Funktionen kann null/Defaultwert statt einem Zeiger besser sein als eine Exception; das siehst du denen in der Regel an. Stringfunktionen sind oft so, also String.trim() oder Object.toString().
In C ist das afaik errno + return.
In jeder produktiv nutzbaren Sprache (€/Zeit) kannst du an solche Fehler Stacktraces und Fehlerbeschreibungen hängen, die es dem verwendenen Programmierer erlauben zu verstehen, was das Problem ist und seinerseits angemessen darauf zu reagieren.

Terminate ist btw. nahezu immer falsch. Schon einfach weil die meisten Testbibliotheken nicht damit klarkommen. Es ist auch nicht immer ganz klar, ob ein fataler Fehler wirklich fatal ist. Sigsegv kann z.B. durch valgrind von außen teilweise recovered werden.

Insgesamt: Wenn du es kannst, bau deine Fehlerbehandlung so, dass du den "happy path" im root Block der Funktion hast und du nach der Prüfung sicher sein kannst, dass der Fehler nicht auftritt, z.B. weil du ein return gemacht hast. Verwende keine Gotos. Verwende statische Hilfsfunktionen und lass den Compiler das machen.

EDIT: https://www.reddit.com/r/ProgrammerHumo ... e_or_gtfo/
XML/JSON/EMF in schnell: OGSS
Keine Lust mehr auf C++? Versuche Tyr: Get & Get started
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Alexander Kornrumpf »

Lord Delvin hat geschrieben: 31.12.2022, 12:40 Wenn du Dateien liest kann es sein, dass du sie nicht ganz lesen kannst oder irgendein obskurer IO-Error auftritt oder sowas.
Das würde ich in aller Regel gar nicht behandeln und einfach so tun, als würde das nicht passieren können.
Ausnahme wäre irgendein high availability oder echtzeit Kram; wenn du sowas baust gibt es für sowas in der Regel auch Richtlinien die dir sagen wie du reagierst.

Out of memory würde ich in aller Regel auch wie einen IO-Error behandeln.
Soll der User einfach mehr RAM kaufen; soll dein Aufrufer einfach irgendwas algorithmisch verbessern.
RAM ist erstmal unendlich und kommt aus malloc raus.
Ich hätte das auch gesagt: Wenn es drauf ankommt, läufst du wahrscheinlich eh auf einem NoSwap-System in den OOM-Killer, und wenn es nicht drauf ankommt, kommt es nicht drauf an. Embedded wieder ausgeklammert, nicht meine Baustelle.

ABER: Ich weiß wir haben undefined behaviour Fans hier, aber in C kannst du den Rückgabewert von malloc offenbar nie ignorieren, weil du dich nicht darauf verlassen darfst, dass das Programm halt einfach abstürzt, wenn du den NULL Zeiger dereferenzierts. Ich halte das für einen echten Nachteil. Denn dass ich wäre bereit das Programm abstürzen zu lassen heißt ja nicht, dass ich bereit wäre es beliebige Dinge tun zu lassen (wo die UB Fans aus mir unerfindlichen Gründen so stolz drauf sind).
...
hätte dann auch Rust oder Go sein können.
...
Je größer das Projekt wird, desto wahrscheinlicher kommst du nurnoch mit Exceptions klar. Bei Fehlerwerten hat man schlicht das Problem, dass man sie bei nichtbehandlung unterdrückt, was nahezu immer falsch ist.
Ich weiß, du magst Rust nicht, aber bei Rust ist das nicht so. Der Compiler zwingt dich, den Fehlerwert zu behandeln. Du kannst vermutlich einen leeren Handler schreiben, das ist dann aber Bösartigkeit kein Versehen. Du kannst dem Compiler sagen, dass du den Wert ignorieren willst, aber wenn es dann zur Laufzeit doch einen Fehler gibt, wird das Program abstürzen. Ich habe die Rust Nomenklatur noch nicht auswendig drauf, aber so das Konzept.

Ob das skaliert kann ich nicht sagen. Von den Codebases mit denen ich zu tun habe sind die mit Exception ungefähr um einen Faktor 10000 größer als die mit Rust.
Matthias Gubisch
Establishment
Beiträge: 470
Registriert: 01.03.2009, 19:09

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Matthias Gubisch »

+1 für die Ausführungen von Lord Delvin, schön erklärt und beschrieben.

Es gibt nicht die eine Methode für Fehlerbehandlung. Das ist immer sehr stark Fehler und Kontext abhängig....
Bevor man den Kopf schüttelt, sollte man sich vergewissern einen zu haben
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4254
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Chromanoid »

Zunindest bei "managed Sprachen" mit Exceptions ist der Spruch "throw early, catch late" ja die Devise. Die zugrunde liegende Idee Fehlerbehandlung möglichst an Stellen zu tun, die den übergeordneten Kontext einer Fehlersituation kennen, macht wohl immer Sinn.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2353
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Jonathan »

Lord Delvin hat geschrieben: 31.12.2022, 12:40
Jonathan hat geschrieben: 30.12.2022, 23:35 Ich entwickel hauptsächlich Spiele und keine Serversoftware, meine Anforderungen an Stabilität sind also anders. Außerdem ist mir robuster Code wichtiger als maximale Effizienz. Ich komme eh schon nicht dazu alles umzusetzen, was ich mir vornehme, da will ich es vermeiden unnötigen Code zu schreiben und vor allen Dingen will ich unnötiges Debuggen mit sehr hoher Priorität vermeiden.
HAHAHAHAHAHAHA. (tschuldigung, aber wenn du das nochmal liest verstehst du bestimmt warum ich es lustig finde)
Ehrlich gesagt, noch nicht so ganz. Vielleicht weil es auf den ersten Blick wie ein Widerspruch klingt? Aber was ich sagen wollte war, dass es mir egal ist, wenn mein Spiel abstürzt, ich den Fehler beheben kann, und es danach dann nicht mehr abstürzt. Also im Idealfall sollte es natürlich überhaupt nicht abstürzen und so, aber ich will eben nur sehr wenig Zeit darein investieren, Code zu schreiben, der das Programm nach einen erkannten Fehler wieder in einen sinnvollen Zustand überführt. Wenn ich Beispielsweise einen Spielstand laden will und der kaputt ist und dann ein halbes Level geladen ist, könnte man ja entweder genau den Zustand vor dem Laden wiederherstellen (wo man noch im vorherigen Level steht oder so), oder halt zumindest ins Hauptmenü zurückkehren und dem Spieler die Gelegenheit geben, einen anderen Spielstand zu laden. Ich mache nichts von dem, sondern zeige eine Fehlermeldung an und beende das Programm.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Krishty »

Jonathan hat geschrieben: 01.01.2023, 22:51ich will eben nur sehr wenig Zeit darein investieren, Code zu schreiben, der das Programm nach einen erkannten Fehler wieder in einen sinnvollen Zustand überführt.
Das Thema finde ich interessant.

Auf der einen Seite neigen sehr kleine Entwicklerteams hier zu fail-fast à assert(): Wenn etwas im Spiel nicht stimmt, sofort Fehler anzeigen und beenden. Dadurch wird es langfristig stabiler. Der Kernel Developer Approach.

Auf der anderen Seite habe ich von mehreren Veteranen aus großen Teams gehört, dass das das Schlimmste ist, was man machen kann und dass das Spiel um jeden Preis weiterlaufen muss – egal, wie kaputt der interne Zustand. Grund ist wohl, dass man nicht zehn Gameplay-Designer für zwei Tage arbeitsunfähig machen möchte, wenn das neue Materialsystem nicht mit alten Speicherständen zurechtkommt. Dann zeig die Polygone halt einfach nicht an. Wenn die KI abstürzt, lass das Monster einfach still stehen. Aber halt das verdammte Spiel spielbar.

Ich sehe auf beiden Seiten Vor- und Nachteile, und habe für mich noch keine Strategie festlegen können. Team-blockierende Probleme lassen sich ja durchaus mit anderen Hebeln lösen (bspw. organisatorisch, Continuous Integration), aber man kann schlecht leugnen, dass große Projekte grundsätzlich immer irgendwo kaputt sind und dass für sie damit die selben Regeln gelten wie für die anderen Systeme im Artikel.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
smurfer
Establishment
Beiträge: 195
Registriert: 25.02.2002, 14:55

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von smurfer »

Interessante Diskussion.

Ich habe das Fehlerverhalten schon immer sehr stark an das Logging angelehnt, also z.B.
debug, info: Keine Fehler, einfach Information
notice: Hinweise, dass etwas passiert, möglicherweise Fallbacks genutzt werden, etwas nicht so gut läuft, wie es könnte, aber das grundsätzliche Verhalten als solches sollte "normal" sein.
warning: Da stimmt was nicht, das Programm kann weiterlaufen, aber unter Umständen nicht, wie gewollt.
error: Das Programm muss beendet werden.

Das passt ein wenig zum zweiten von @Krishty beschriebenen Fall: "Mach weiter, wenn möglich, auch wenn etwas nicht passt".

Gerade die letzten drei (notice, warning, error) nehme ich auch als Grundlage für das Fehlerverhalten -- wenn ich nicht zu faul bin ;-)
Exceptions versuche ich zu meiden, warum auch immer, bin ich nie mit warm geworden.
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Alexander Kornrumpf »

Krishty hat geschrieben: 01.01.2023, 23:04
Jonathan hat geschrieben: 01.01.2023, 22:51ich will eben nur sehr wenig Zeit darein investieren, Code zu schreiben, der das Programm nach einen erkannten Fehler wieder in einen sinnvollen Zustand überführt.
Das Thema finde ich interessant.

Auf der einen Seite neigen sehr kleine Entwicklerteams hier zu fail-fast à assert(): Wenn etwas im Spiel nicht stimmt, sofort Fehler anzeigen und beenden. Dadurch wird es langfristig stabiler.
Ja, es wird "langfristig stabiler" aber nicht unendlich. Wenn der kaputte Zustand durch einen Bug in deinem Code kommt, findest du so den Bug. Das meintest du wahrscheinlich. Wenn der kaputte Zustand aus dem Environment im weitesten Sinne kommt stürzt du halt einfach ab. [Dass man nicht von allen Fehlern recovern kann wurde schon rauf und runter diskutiert, mir geht es um die UX im Fehlerfall.] Ich vermute der Kernel kommt damit durch, weil er größtenteils das Environment _ist_ und der User ansonsten nicht direkt auf den Kernel zugreift(?). Wenn das Environment vom Kernel aka Hardware kaputt ist, ist eh alles egal.

Ich würde es als Einzelkämpfer auch so machen, aber eher aus den von Jonathan genannten Erwägungen zum Aufwand und gerade nicht um den Code "robuster" (Jonathan) oder "stabiler" (Krishty) zu machen. Auf der Robustheitsskala ist Code der "korrekt" auf Fehler reagiert, was auch immer das heißen mag, sicher noch robuster als Code der immerhin kontrolliert abstürzt. Nur halt auch sehr sehr sehr viel schwerer zu erreichen.
Auf der anderen Seite habe ich von mehreren Veteranen aus großen Teams gehört, dass das das Schlimmste ist, was man machen kann und dass das Spiel um jeden Preis weiterlaufen muss – egal, wie kaputt der interne Zustand. Grund ist wohl, dass man nicht zehn Gameplay-Designer für zwei Tage arbeitsunfähig machen möchte, wenn das neue Materialsystem nicht mit alten Speicherständen zurechtkommt. Dann zeig die Polygone halt einfach nicht an. Wenn die KI abstürzt, lass das Monster einfach still stehen. Aber halt das verdammte Spiel spielbar.

Ich sehe auf beiden Seiten Vor- und Nachteile, und habe für mich noch keine Strategie festlegen können. Team-blockierende Probleme lassen sich ja durchaus mit anderen Hebeln lösen (bspw. organisatorisch, Continuous Integration), aber man kann schlecht leugnen, dass große Projekte grundsätzlich immer irgendwo kaputt sind und dass für sie damit die selben Regeln gelten wie für die anderen Systeme im Artikel.
Ich persönlich glaube, dass sehr viel an diesem fefe-rant dran ist https://blog.fefe.de/?ts=9ec23e6e (kein Endorsement für fefe generell).

Ich sage nicht, dass die Veteranen mit denen du gesprochen hast unfähig sind oder lügen. Es wird sicherlich stimmen, dass die "production pressure" so ist, dass das Spiel um jeden Preis spielbar bleibt wie du es beschreibst. Passt ja auch zum Cargo Cult um das Konzept "blocked" zu sein. Andere Teams zu "blocken" ist in einer "agilen" Welt natürlich eine Todsünde.

Das Ergebnis ist aber dann halt Cyberpunk 2077 (das Spiel als Artefakt, nicht das fiktive Universum). Die "durchaus anderen Hebel", die du nennst, sind faktisch die einzigen Hebel die funktionieren.

Am Ende des Tages ist das genau dein "Testversionen gehen immer in Produktion" Argument von oben (dem ich voll zustimme): Das was du beschreibst ist eine Strategie die von den Anforderungen der Entwicklungsphase ausgeht, führt aber praktisch zwangsläufig dazu, dass der Kunde in Produktion dann zu sehen bekommt, wie es aussieht, wenn das Spiel "die Polygone halt einfach nicht an[zeigt]" oder "das Monster einfach still stehen [bleibt]". Macht auch gute Memes.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Krishty »

Ich habe damit Probleme auf etwas anderer Ebene, und werde die in einen anderen Thread auslagern um starcows Fragen nicht komplett entgleisen zu lassen :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
udok
Beiträge: 40
Registriert: 01.02.2022, 17:34

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von udok »

Meine Meinung als Anwender von CAD/PCB Software:
Das Programm muss laufen, solange es nur irgendwie geht. Es gibt nichts schlimmeres, als ein Abgabetermin für das Projekt, und dann stürzt die Software kurz davor ab... Sowas erzählt der Anwender auch noch in 5 Jahren im Bekanntenkreis herum, und die Reputation der SW-Firma ist dahin.
Die wenigsten Bugs in einer GUI sind wirklich tödlich und rechtfertigen eine harten Abbruch.

Meine Meinung als Entwickler:
Fehler die nicht sichtbar sind werden halt nicht gefunden und repariert. Irgendwelche Log Files schaut sich kein Mensch an.
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von starcow »

@Lord Delvin
War interessant zu lesen - danke für Deine Einschätzung! Auch wenn ich zugeben muss, dass ich noch nicht überall mitkomme.
Lord Delvin hat geschrieben: 31.12.2022, 12:40
starcow hat geschrieben: 30.12.2022, 22:57 (Es ist übrigens ein reines C Projekt)
Das ist vollkommen egal. Letztlich erklärt es nur, warum deine Einschätzungen so sind, wie sie sind. Das hätte man sich allerdings auch schon anhand deiner Optionsliste denken können; hätte dann auch Rust oder Go sein können.
Krishty hatte ja richtig erkannt, das die C++ exeptions in meiner Aufzählung fehlen. Aus diesem Grund hatte ich erwähnt, dass es ein Plain C Projekt ist. Abgesehen davon spielt es natürlich prinzipiell keine Rolle.
Den Satz zu meinen Einschätzungen versteh ich aber ehrlich gesagt nicht. Sind meine Fragen naiv? Und wenn ja, wieso?
LG, starcow
Krishty hat geschrieben: 31.12.2022, 00:25 Möglichkeiten 1 und 4 (geschachtelte ifs und goto zum Aufräumen) sind in C Äquivalent und erzeugen den besten Code. goto ist außerdem die kürzeste Variante. Beide sind unhaltbar, wenn die Funktion mehr als einen erfolgreichen Ausführungspfad hat; in C++ sind sie unterschiedlich weil goto keine Variablen-Initialisierung überspringen darf. Mein super effizienter und kurzer C-Code konnte unter C++ einfach nicht mehr kompiliert werden.
Wie meinst Du das bezüglich der Äquivalenz? Bei Möglichkeit 1 (if-else Verschachtelung) räumst du ja jeweils nur genau die "Posten" wieder auf, die beim Erreichen der Verzweigung tatsächlich nötig sind. Und das werden ja in der Regel beim Fortschreiten im Code zunehmends mehr.
Dagegen räumt man ja bei Möglichkeit 4 (goto) an einer zentralen Stelle auf und muss somit erst in Erfahrung bringen, welche Resource bereits "reserviert" wurden. Oder seh ich das falsch?

Code: Alles auswählen

if(ptr) free(ptr);
LG, starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Krishty »

starcow hat geschrieben: 03.01.2023, 21:03
Krishty hat geschrieben: 31.12.2022, 00:25 Möglichkeiten 1 und 4 (geschachtelte ifs und goto zum Aufräumen) sind in C Äquivalent und erzeugen den besten Code. goto ist außerdem die kürzeste Variante. Beide sind unhaltbar, wenn die Funktion mehr als einen erfolgreichen Ausführungspfad hat; in C++ sind sie unterschiedlich weil goto keine Variablen-Initialisierung überspringen darf. Mein super effizienter und kurzer C-Code konnte unter C++ einfach nicht mehr kompiliert werden.
Wie meinst Du das bezüglich der Äquivalenz? Bei Möglichkeit 1 (if-else Verschachtelung) räumst du ja jeweils nur genau die "Posten" wieder auf, die beim Erreichen der Verzweigung tatsächlich nötig sind. Und das werden ja in der Regel beim Fortschreiten im Code zunehmends mehr.
Dagegen räumt man ja bei Möglichkeit 4 (goto) an einer zentralen Stelle auf und muss somit erst in Erfahrung bringen, welche Resource bereits "reserviert" wurden. Oder seh ich das falsch?

Code: Alles auswählen

if(ptr) free(ptr);
Die beiden Konzepte sind nicht automatisch äquivalent, aber meist so ähnlich, dass du den Maschinen-Code nicht mehr unterscheiden kannst. Zur Klarheit:

Code: Alles auswählen

// 1 (geschachtelt)
DB_STATUS fillDatabase(DB * db, int * data, size_t size) {
    DB_STATUS result;

    result = DB_STATUS_OVERFLOW;
    if(size < 0x80000000) {

        result = DB_STATUS_NO_MEM;
        int * stage = (int *)malloc(size);
        if(stage) {

            result = DB_STATUS_BUSY;
            void * dblock = lock(db, stage);
            if(dblock) {

                memcpy(stage, data, size); // just any transformation

                result = DB_STATUS_OK;

                // Cleanup:
                unlock(dblock);
            }

            free(stage);
        }

    }

    return result;
}

// 4 (goto)
DB_STATUS fillDatabase(DB * db, int * data, size_t size) {
    DB_STATUS result;

    result = DB_STATUS_OVERFLOW;
    if(size >= 0x80000000)
        goto overflow;

    result = DB_STATUS_NO_MEM;
    int * stage = (int *)malloc(size);
    if(!stage)
        goto nomem;

    result = DB_STATUS_BUSY;
    void * dblock = lock(db, stage);
    if(!dblock)
        goto busy;

    memcpy(stage, data, size); // just any transformation

    result = DB_STATUS_OK;
    goto ok;

    // Cleanup:
ok:
    unlock(dblock);
busy:
    free(stage);
nomem:
overflow:
    return result;
}
Die goto-Form ist etwas mehr Low-Level und wird vom Compiler oft sowieso aus der if-Form erzeugt.

Weshalb ich das schrieb: Die goto-Form ist nur in C gültig, und nicht in C++. Das goto nomem; springt nämlich über die Initialisierung von void * dblock, und das verbietet C++. Es wird nicht kompilieren. Es gibt Mittel und Wege drum herum (Deklaration und Initialisierung trennen wie Old-School-C; mit geschweiften Klammern zusätzliche Scopes ziehen; …), aber danach ist das einfach nicht mehr lesbar. Sei deshalb auf der Hut, wenn du jetzt so C schreibst und dich an der geringen Verschachtelungstiefe erfreust, und erwartest, dass es irgendwann auch einfach so als C++ kompiliert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von starcow »

Ok, sehr gut. Danke für den wichtigen Hinweis Krishty!

Was ist eigentlich mit Möglichkeit 2 (goOn oder error Variable)? Das fand bei euch keine Erwähnung. Gilt es einfach als schlecht lesbar oder unelegant?

Code: Alles auswählen

bool goOn = check0();
if (goOn) {
    ...
    goOn = check1();
}
if (goOn) {
    ...
    goOn = check2();
}
if (goOn) {
    ...
}
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Alexander Kornrumpf »

starcow hat geschrieben: 03.01.2023, 21:53 Ok, sehr gut. Danke für den wichtigen Hinweis Krishty!

Was ist eigentlich mit Möglichkeit 2 (goOn oder error Variable)? Das fand bei euch keine Erwähnung. Gilt es einfach als schlecht lesbar oder unelegant?

Code: Alles auswählen

bool goOn = check0();
if (goOn) {
    ...
    goOn = check1();
}
if (goOn) {
    ...
    goOn = check2();
}
if (goOn) {
    ...
}
Ich kann anekdotisch berichten, dass Refactoring dadurch unglaublich nervig wird. Aber das ist es in C vermutlich sowieso.
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Alexander Kornrumpf »

Krishty hat geschrieben: 02.01.2023, 17:56 Ich habe damit Probleme auf etwas anderer Ebene, und werde die in einen anderen Thread auslagern um starcows Fragen nicht komplett entgleisen zu lassen :)
Sehr gerne!
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Krishty »

Alexander Kornrumpf hat geschrieben: 03.01.2023, 22:18
Krishty hat geschrieben: 02.01.2023, 17:56 Ich habe damit Probleme auf etwas anderer Ebene, und werde die in einen anderen Thread auslagern um starcows Fragen nicht komplett entgleisen zu lassen :)
Sehr gerne!
https://zfx.info/viewtopic.php?t=5340

Es ist nicht im Ansatz das geworden, was ich wollte. Sorry, bin wohl überarbeitet. Wurmt mich aber zu sehr, um es in der Schublade verschwinden zu lassen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Jonathan
Establishment
Beiträge: 2353
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Jonathan »

Alexander Kornrumpf hat geschrieben: 03.01.2023, 22:18
starcow hat geschrieben: 03.01.2023, 21:53 Ok, sehr gut. Danke für den wichtigen Hinweis Krishty!

Was ist eigentlich mit Möglichkeit 2 (goOn oder error Variable)? Das fand bei euch keine Erwähnung. Gilt es einfach als schlecht lesbar oder unelegant?

Code: Alles auswählen

bool goOn = check0();
if (goOn) {
    ...
    goOn = check1();
}
if (goOn) {
    ...
    goOn = check2();
}
if (goOn) {
    ...
}
Ich kann anekdotisch berichten, dass Refactoring dadurch unglaublich nervig wird. Aber das ist es in C vermutlich sowieso.
Das ist halt so der Punkt, weswegen ich C absolut nicht mag. Weil ich an so vielen Stellen Kleinigkeiten nicht vergessen darf. Was ich aber natürlich ständig tue.
Ich hab mal einen operator< für eine struct mit mehreren Elementen falsch implementiert, was zu unfindbaren Abstürzen in einer A* Implementierung führte, da dort die Endlosschleife genau dann vermieden wird, wenn eine bestimmte Liste sortiert ist und ich hatte sie ja sortiert. Ich musste einfach nur < für alle Member aufrufen, aber halt richtig geschachtelt. Das hatte ich nicht und hatte Code der richtig aussaht, aber nicht funktionierte. Implementiert man den Operator via std::tie funktioniert alles sofort und immer richtig.
Ich musste auch neulich einen Bug in einer Soundbibliothek fixen, weil jemand in C++ seinen Speicher für Arrays unbedingt von Hand managen wollte und das natürlich falsch gemacht hat, was zu Abstürzen führte. Einfach durch std::vector ersetzen und es war weniger Code und keine Abstürze mehr. Ok, an ein, zwei Stellen muss man wohl aufpassen, dass man nicht versehentlich implizit den Vektor einmal zu viel kopiert. Ob die Art von Effizienzverlust besser oder schlimmer als Abstürze ohne Fehlermeldungen sind muss wohl jeder für sich entscheiden. Wenn man alles an jeder Stelle immer richtig macht, hat man vermutlich in beiden Sprachen keinen der Nachteile. Aber das tut man ja nicht.

Ich mag Code, der funktioniert, wenn er auf den ersten Blick richtig aussieht. Wenn ich Rückgabewerte von Funktionen nicht auf Fehlercodes prüfe sieht mein Code auch erstmal richtig aus, funktioniert aber nicht. Wenn ich irgendwo im Fehlerfalle eine Exception schmeiße, kann ich mich darauf verlassen, dass sie irgendwie bearbeitet wird. Vielleicht nicht auf die beste Art, vielleicht wäre es manchmal tatsächlich angenehmer, den Fehler zu ignorieren oder einfach in irgendeinen Log zu schreiben.
Aber unterm Strich habe ich bei den meisten Menschen einfach nicht das Vertrauen dass sie gut genug C schreiben als dass sie nicht C++ benutzen sollten. Und insbesondere bei mir habe ich das nicht.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Lord Delvin
Establishment
Beiträge: 574
Registriert: 05.07.2003, 11:17

Re: Guter Stil für "Fehlerbehandlungen"?

Beitrag von Lord Delvin »

Jonathan hat geschrieben: 01.01.2023, 22:51
Lord Delvin hat geschrieben: 31.12.2022, 12:40 HAHAHAHAHAHAHA. (tschuldigung, aber wenn du das nochmal liest verstehst du bestimmt warum ich es lustig finde)
Ehrlich gesagt, noch nicht so ganz.
Na du willst ja z.B. in deinem Onlinebanking auch, dass du nach der Anmeldung angemeldet bist und dass der Kontostand, den du siehst der richtige ist. Dass das vermutlich in 100ms geht, aber in 15s implementiert ist, ist da noch viel mehr akzeptiert als in deinem Szenario. Meine Wahrnehmung ist, dass bei Serversoftware mit hohem Durchsatz trotzdem fast immer die Stabilität und Korrektheit wichtiger ist.

Krishty hat geschrieben: 01.01.2023, 23:04 Auf der einen Seite neigen sehr kleine Entwicklerteams hier zu fail-fast à assert(): Wenn etwas im Spiel nicht stimmt, sofort Fehler anzeigen und beenden. Dadurch wird es langfristig stabiler. Der Kernel Developer Approach.

Auf der anderen Seite habe ich von mehreren Veteranen aus großen Teams gehört, dass das das Schlimmste ist, was man machen kann und dass das Spiel um jeden Preis weiterlaufen muss – egal, wie kaputt der interne Zustand. Grund ist wohl, dass man nicht zehn Gameplay-Designer für zwei Tage arbeitsunfähig machen möchte, wenn das neue Materialsystem nicht mit alten Speicherständen zurechtkommt.
Das wollte ich noch kurz kommentieren, weil auch vielen Veteranen der Schritt zurück einfach fehlt und zu beurteilen, was die konkrete Situation eigentlich bedeutet. Man kann mit "flexibler" Fehlerbehandlung noch viel mehr kaputt machen und die Leute genauso Blockieren, weil die Systeme zwar scheinbar arbeiten, aber eben Müll produzieren. Dann sind die Leute vielleicht beschäftigt, aber sie produzieren keinen Mehrwert. Für das Gesamtunternehmen ist das in der Regel nicht besser. Idiotischerweise ist es in der Praxis aber oft akzeptierter, weil ja Fehler irgendwie passieren :-/

Lass mich zum besseren Verständnis und um die Ursprungsfrage ein letztes mal zu kommentieren nochmal ein paar Beispiele machen, die ich jetzt aus Tyr nehme, aber auch sonst schon oft gesehen habe.
Da gibt etwa 200 asserts, die letztlich dazu da sind, sicherzustellen, dass man nicht ein scheinbar funktionsfähiges Produkt erzeugt, bei dem in späteren Phasen nicht mehr klar erkennbar ist, was das Problem ist. Vieles davon hat mit Graphtransformationen zu tun. Ich weiß, dass es manche von denen gibt, die Beispiele haben, bei denen man auch zum Ziel kommt, wenn die assertion einen rauswerfen würde. Der Punkt ist, dass es in jedem Fall ein strukturelles Problem der Implementierung in der Phase ist und man Garantieen, die man an spätere Phasen gibt, nicht einhält. Wie z.B. dass der IR-Graph für's Backend tatsächlich zusammenhängend oder konsistent ist. Das sollte der Nutzer nie sehen und ist wie ein Crash. Wenn das passiert halte ich Entwickler nicht auf, weil was auch immer danach passieren würde ohnehin nicht korrekt wäre. Wenn ich das nicht täte könnten sie womöglich die resultierende Bibliothek in das lokale Repository installieren und würden vielleicht noch abhängige Bibliotheken vergiften und dann vielleicht tagelang in ihrem Code suchen, wenn das Problem eigentlich ein Bug in der Übersetzung einer Abhängigkeit ist. Nicht hilfreich; wir streben nach Produktivität und nicht nach Aktivität.

Interessanterweise habe ich genauso eine Category BUG und eine TODO. Das sind beides Gruppen, die ich verwende um dem Nutzer klarzumachen, dass er sich aus den definierten Grenzen der vom Compiler akzeptierten Sprache entfernt hat. Bug, weil er vermutlich was geschafft hat, worüber ich noch nicht nachgedacht habe, von dem ich aber weiß, dass es nirgendwohin führt. IdR muss man dann ein Ticket aufmachen oder halt eine Release-Version nehmen.

Dann gibt es errors, das wären im Prinzip Dateien, die zu kurz sind, also weniger Pixeldaten haben als sie müssten. Hier sind quasi die Veteranen im Recht, weil assert hier absolut falsch wäre; wie auch bei allem Folgenden.

Warnings wären im Prinzip Dateien, die zu lang sind. Du kannst ein vollständiges Bild produzieren, aber irgendwie bist du nicht am Ende der Datei, was irgendwie verdächtig ist. Wenn du keinen Kanal mit Warnungen hast würde ich das eher zum Error machen, als zur Info.

Infos wäre dann sowas wie "habe Datei x angefangen zu laden". Das hat nurnoch mit der Fehlerbehandlung zu tun, wenn man irgendwo einen Fehler sucht und du daran beteiligt sein könntest. Meine Position dazu ist, dass man infos möglichst nur dann in einen Fehlerkanal, wie z.B. ein Log, kippen sollte, wenn man davon ausgeht, dass es einen Fehler geben wird oder gegeben hat. Einzige Ausnahme ist eine Systembeschreibung beim Start mit Version etc.

Das ist jetzt von der ursprünglichen, recht kontrollflusslastigen, Frage aber schon ein ganzes Stück entfernt.
XML/JSON/EMF in schnell: OGSS
Keine Lust mehr auf C++? Versuche Tyr: Get & Get started
Antworten