====== Fehlerbehandlung im Alltag ====== Lyx kennt ''try''/''catch'', empfiehlt es aber nicht — und die Standardbibliothek benutzt es nirgends. Stattdessen: **Fehler sind Rückgabewerte.** Diese Seite zeigt, wie man damit im eigenen Code arbeitet, ohne dass jede Funktion in Prüfungen erstickt. Die Konventionen der Standardbibliothek stehen in [[lyx_-_programmiersprache:sprache:std-fehlerkonventionen|Fehler-Konventionen]], die Sprachmittel in [[lyx_-_programmiersprache:sprache:exception-handling|Fehlerbehandlung]]. ---- ===== Die vier Wege ===== ^ Weg ^ Wann ^ | **Rückgabecode** ''0'' = gut, negativ = Fehler | Der Normalfall; Ergebnis geht über ''out_buf'' | | **Tupel** ''(wert, fehler)'' | Wenn Ergebnis und Zustand zusammengehören | | **Sonderwert** (''0'', ''-1'', ''null'') | Bei Zeigern und Handles, wo ein Wert ohnehin ungültig ist | | **''panic()''** | Wenn der Zustand gar nicht auftreten darf — Abbruch mit Exit-Code 1 | Dazu ''defer'' für das Aufräumen, das auf **jedem** Rückgabepfad passieren muss. ---- ===== Beispiel ===== import std.io; import std.alloc; import std.fs; pub con E_OK: int64 := 0; pub con E_UNGUELTIG: int64 := 0 - 22; // -EINVAL pub con E_ZU_KLEIN: int64 := 0 - 7; // -E2BIG-artig: Puffer reicht nicht // 1. Rueckgabecode: 0 = gut, negativ = Fehler. Ergebnis ueber out_buf. fn Kopiere(quelle: pchar, out_buf: int64, max: int64): int64 { if (quelle == 0 as pchar) { return E_UNGUELTIG; } var i: int64 := 0; while (StrCharAt(quelle, i) != 0) { if (i >= max - 1) { return E_ZU_KLEIN; } poke8(out_buf + i, StrCharAt(quelle, i)); i := i + 1; } poke8(out_buf + i, 0); return E_OK; } // 2. Tupel: zwei Werte gehoeren zusammen — Ergebnis und Zustand fn Halbiere(x: int64): (int64, int64) { if (x - (x / 2) * 2 != 0) { return (0, E_UNGUELTIG); } return (x / 2, E_OK); } // 3. defer: raeumt auf, egal welcher Rueckgabepfad genommen wird fn MitAufraeumen(gross: bool): int64 { var p: int64 := alloc(64); defer free(p, 64); if (gross) { return 0 - 1; } // free passiert trotzdem poke64(p, 7); return peek64(p); } fn main(): int64 { var buf: int64 := alloc(8); var rc: int64 := Kopiere("Lyx", buf, 8); PrintLn(StrConcat("Kopiere kurz: rc=", IntToStr(rc))); rc := Kopiere("viel zu lang fuer acht Byte", buf, 8); PrintLn(StrConcat("Kopiere lang: rc=", IntToStr(rc))); var wert, zustand := Halbiere(10); PrintLn(StrConcat(StrConcat("Halbiere(10): ", IntToStr(wert)), StrConcat(" rc=", IntToStr(zustand)))); var w2, z2 := Halbiere(7); PrintLn(StrConcat(StrConcat("Halbiere(7): ", IntToStr(w2)), StrConcat(" rc=", IntToStr(z2)))); PrintLn(StrConcat("MitAufraeumen(false): ", IntToStr(MitAufraeumen(false)))); PrintLn(StrConcat("MitAufraeumen(true): ", IntToStr(MitAufraeumen(true)))); free(buf, 8); return 0; } Kopiere kurz: rc=0 Kopiere lang: rc=-7 Halbiere(10): 5 rc=0 Halbiere(7): 0 rc=-22 MitAufraeumen(false): 7 MitAufraeumen(true): -1 Übersetzt und ausgeführt mit ''lyxc 1.1.3I''. ---- ===== defer statt Aufräumzweigen ===== fn MitAufraeumen(gross: bool): int64 { var p: int64 := alloc(64); defer free(p, 64); // laeuft am Blockende, egal wie er verlassen wird if (gross) { return 0 - 1; } poke64(p, 7); return peek64(p); } Das ist der Ersatz für ''goto cleanup'' aus C und für ''try''/''finally'' aus Pascal. Beide Rückgabepfade im Beispiel geben den Puffer frei — sichtbar an einer Stelle, statt zweimal. ''defer'' läuft am Ende des **Blocks**, in dem es steht, in umgekehrter Reihenfolge der Anmeldung. ---- ===== Fehlercodes, die etwas sagen ===== Drei Gewohnheiten machen den Unterschied: * **Negative Zahlen für Fehler, ''0'' für Erfolg.** Dann ist ''if (rc < 0)'' die einzige Prüfung, die man sich merken muss. * **Bekannte Zahlen wiederverwenden**: Die Standardbibliothek gibt bei Syscalls den negierten ''errno'' zurück (''-2'' = ENOENT, ''-13'' = EACCES, ''-22'' = EINVAL). Eigene Codes daran anzulehnen spart eine Übersetzungstabelle. * **Eigene Codes als ''con'' benennen.** ''E_ZU_KLEIN'' liest sich besser als ''-7'' — und lässt sich später ändern. ---- ===== Fehler weiterreichen ===== Der einfachste brauchbare Stil ist der frühe Ausstieg: fn Verarbeite(pfad: pchar): int64 { var fd: int64 := open(pfad, 0, 0); if (fd < 0) { return fd; } // Fehlercode unveraendert nach oben defer close(fd); var buf: int64 := alloc(4096); defer free(buf, 4096); var n: int64 := read(fd, buf as pchar, 4096); if (n < 0) { return n; } return Auswerten(buf, n); } * **Fehler nicht übersetzen**, solange sie nur durchgereicht werden — sonst geht die Ursache verloren. * **Am Rand des Programms** einmal in eine Meldung wandeln, dort mit Kontext („Datei X nicht lesbar: ENOENT"). * **Nie stillschweigend verschlucken.** Ein ignorierter Rückgabewert ist der Anfang jeder schwer findbaren Fehlersuche. ---- ===== Wann panic() richtig ist ===== ''panic()'' bricht mit Exit-Code 1 ab und ist **nicht** abfangbar — auch nicht in einem ''try''-Block. Es gehört an Stellen, an denen der Zustand einen Programmierfehler bedeutet: * Invariante verletzt („Zähler negativ, obwohl nur erhöht wird") * Ein Zweig, der laut Konstruktion nie erreicht wird * ''malloc_orpanic'', wenn ein fehlender Speicher ohnehin das Ende bedeutet Nicht für Zustände, die die Umwelt herbeiführt: fehlende Datei, abgelehnte Verbindung, ungültige Eingabe. Das sind Fehlercodes. ---- ===== Wo Lyx selbst abbricht ===== ^ Auslöser ^ Verhalten ^ | ''panic()'', ''assert'' | Meldung nach stderr, Exit 1 | | Bereichsprüfung (Range-Typ, Indexgrenze) | derselbe Weg | | NaN/Inf unter ''@flight_crit'' | ''SIGFPE'', Meldung mit Funktionsnamen, Exit 134 | | Nicht deklarierter Syscall unter ''@capabilities'' | seccomp beendet den Prozess (rc 159); mit ''--seccomp-trap'' mit Syscall-Nummer | | Abstrakte Methode ohne Implementierung | ''lyxc: abstrakte Methode X.Y ohne Implementierung aufgerufen'' | Die vollständige Liste der Exit-Codes steht unter [[lyx_-_programmiersprache:sprache:exit-codes|Exit-Codes]]. ---- ===== Fallstricke ===== * **Rückgabewert ignoriert.** Der Compiler verlangt nicht, dass er gelesen wird — die Prüfung ist Disziplin. * **''0'' als Fehler und als gültiger Wert.** Dann braucht es das Tupel oder einen ''out''-Parameter. * **Aufräumen im Fehlerzweig vergessen.** ''defer'' direkt hinter die Anforderung schreiben, nicht ans Blockende. * **''panic()'' für Umweltfehler.** Ein Programm, das bei fehlender Datei abbricht, ist im Betrieb unbrauchbar. * **Fehler mehrfach übersetzt.** ''-2'' → „Fehler 1" → „allgemeiner Fehler" — am Ende weiß niemand mehr, was passiert ist. Letzte Aktualisierung: 2026-08-19 — Beispiel mit ''lyxc 1.1.3I'' übersetzt und ausgeführt.