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/