Errorx - Bibliothek zum Arbeiten mit Fehlern in Go

    Was ist Errorx und wie ist es nützlich?


    Errorx ist eine Bibliothek zum Arbeiten mit Fehlern in Go. Es bietet Tools zum Lösen von Problemen, die mit dem Fehlermechanismus in großen Projekten zusammenhängen, sowie eine einzige Syntax, um damit zu arbeiten.


    Bild


    Die meisten Joom-Serverkomponenten werden seit Gründung des Unternehmens auf Go geschrieben. Diese Entscheidung hat sich in den Anfangsstadien der Entwicklung und der Lebensdauer des Dienstes als gerechtfertigt erwiesen, und angesichts der Ankündigungen über die Aussichten für Go 2 sind wir sicher, dass wir dies in Zukunft nicht bereuen werden. Eine der Haupttugenden von Go ist Einfachheit, und ein Fehleransatz zeigt dieses Prinzip als nichts anderes. Nicht jedes Projekt hat einen ausreichenden Umfang, so dass die Fähigkeiten der Standardbibliothek nicht mehr ausreichen, sodass sie nach eigenen Lösungen in diesem Bereich suchen. Wir haben zufällig einige Ansätze zur Arbeit mit Fehlern durchlaufen, und die errorx-Bibliothek spiegelt das Ergebnis dieser Entwicklung wider. Wir sind davon überzeugt, dass dies für viele von Nutzen sein kann - auch für diejenigen, die noch keine schweren Beschwerden bei der Arbeit mit Fehlern in ihren Projekten haben.


    Fehler in los


    Bevor Sie sich der Geschichte von errorx zuwenden, sollten Sie einige Erklärungen abgeben. Was stimmt am Ende mit den Fehlern?


    type error interface {
       Error() string
    }

    Sehr einfach, nicht wahr? In der Praxis bringt eine Implementierung oft nichts anderes mit sich als eine String-Beschreibung des Fehlers. Ein solcher Minimalismus ist mit dem Ansatz verbunden, dass ein Fehler nicht unbedingt etwas Außergewöhnliches bedeutet. Die am häufigsten verwendeten Fehler. New () aus der Standardbibliothek entspricht dieser Idee:


    funcNew(text string)error {
        return &errorString{text}
    }

    Wenn wir uns daran erinnern, dass Fehler in der Sprache keinen besonderen Status haben und gemeinsame Objekte sind, stellt sich die Frage: Was ist die Besonderheit der Arbeit mit ihnen?


    Fehler sind keine Ausnahmen . Es ist kein Geheimnis, dass viele Menschen, die Go kennenlernen, diesen Unterschied mit etwas Widerstand begegnen. Es gibt viele Publikationen, die den in Go gewählten Ansatz erläutern, unterstützen und kritisieren. Trotzdem dienen Fehler in Go vielen Zwecken, und mindestens einer davon entspricht genau den Ausnahmen in einigen anderen Sprachen: Fehlerbehebung. Daher ist es natürlich, von ihnen dieselbe Ausdruckskraft zu erwarten, auch wenn der Ansatz und die Syntax, die mit ihrer Verwendung verbunden sind, sehr unterschiedlich sind.


    Was ist los?


    Viele Projekte verwenden Fehler in Go, so wie sie sind, und haben dabei nicht die geringsten Schwierigkeiten. Mit zunehmender Komplexität des Systems beginnen sich jedoch eine Reihe von Problemen zu manifestieren, die auch ohne hohe Erwartungen auffallen. Ein gutes Beispiel ist eine ähnliche Zeile im Protokoll Ihres Dienstes:


    Error: duplicate key


    Hier ist das erste Problem sofort offensichtlich: Wenn Sie sich nicht bewusst darum kümmern, ist es fast unmöglich zu verstehen, was in einem großen System schief gelaufen ist, nur durch die erste Nachricht. Diesem Beitrag fehlen die Details und der umfassendere Kontext des Problems. Dies ist ein Fehler des Programmierers, aber er kommt zu oft vor, um vernachlässigt zu werden. Der Code, der den "positiven" Zweigen des Kontrollgraphen gewidmet ist, verdient in der Praxis immer mehr Aufmerksamkeit und wird besser mit Tests abgedeckt als der "negative" Code, der mit einer Unterbrechung der Ausführung oder externen Problemen verbunden ist. Wie oft das Mantra if err != nil {return err}in Go-Programmen wiederholt wird, macht diesen Fehltritt noch wahrscheinlicher.


    Als kleiner Exkurs betrachten wir folgendes Beispiel:


    func(m *Manager)ApplyToUsers(action func(User)(*Data, error), ids []UserID) error {
        users, err := m.LoadUsers(ids)
        if err != nil {
            return err
        }
        var actionData []*Data
        for _, user := range users {
            data, err := action(user)
            if err != nil {
                return err
            }
            ok, err := m.validateData(data)
            if err != nil {
                returnnil
            }
            if !ok {
                log.Error("Validation failed for %v", data)
                continue
            }
            actionData = append(actionData, data)
        }
        return m.Apply(actionData)
    }

    Wie schnell haben Sie einen Fehler in diesem Code gefunden? Es wurde jedoch mindestens einmal von einem Go-Programmierer ausgeführt. Hinweis: Ausdrucksfehler if err != nil { return nil }.


    Wenn wir mit einer vagen Nachricht im Protokoll auf das Problem zurückkommen, ist in dieser Situation natürlich jeder auch passiert. Es ist sehr unangenehm, den Fehlerbehandlungscode bereits zu Beginn des Problems zu korrigieren. Laut den Quelldaten aus dem Protokoll ist es außerdem überhaupt nicht klar, von welcher Seite aus die Suche nach dem Teil des Codes beginnt, der tatsächlich verbessert werden muss. Dies scheint eine weit hergeholte Komplexität für Projekte zu sein, deren Codegröße und Anzahl der externen Abhängigkeiten gering sind. Bei Großprojekten ist dies jedoch ein völlig reales und schmerzliches Problem.


    Angenommen, ein Programmierer mit bitterer Erfahrung möchte den Fehler im Voraus mit einem Kontext versehen, den er zurückgibt. Der naive Weg, dies zu tun, ist etwa so:


    funcInsertUser(u *User)error {
        err := usersTable.Insert(u)
        if err != nil {
            return errors.New(fmt.Sprintf("failed to insert user %s: %v", u.Name, err)
        }
        returnnil
    }

    Es ist besser geworden. Der umfassendere Kontext ist noch unklar, aber es ist jetzt viel einfacher, zumindest den Code zu finden, in dem der Fehler aufgetreten ist. Nachdem wir jedoch ein Problem gelöst haben, haben wir versehentlich ein anderes erstellt. Der hier erzeugte Fehler behielt die Diagnosemeldung im Original bei, aber alles andere, einschließlich Typ und zusätzlicher Inhalt, ging verloren.


    Betrachten Sie den folgenden Code im Datenbanktreiber, um zu sehen, wie gefährlich es ist:


    var ErrDuplicateKey = errors.New("duplicate key")
    func(t *Table)Insert(entity interface{})error { 
        // returns ErrDuplicateKey if a unique constraint is violated by insert 
    }  
    funcIsDuplicateKeyError(err error)bool {
        return err == ErrDuplicateKey
    }

    Jetzt ist der Scheck IsDuplicateKeyError()zerstört, obwohl wir zu dem Zeitpunkt, als wir unseren Fehler hinzugefügt haben, nicht die Absicht hatten, seine Semantik zu ändern. Dadurch wird der Code gebrochen, der auf diese Prüfung angewiesen ist:


    funcRegisterUser(u *User)error {
        err := InsertUser(u)
        if db.IsDuplicateKeyError(err) {
            // find existing user, handle conflict
        } else {
            return err
        }
    }

    Wenn wir intelligenter werden und unseren eigenen Fehlertyp hinzufügen möchten, der den ursprünglichen Fehler speichert und in der Lage ist, ihn beispielsweise durch eine Methode zurückzugeben Cause() error, lösen wir das Problem auch nur teilweise.


    1. Bei der Fehlerbehandlung müssen Sie jetzt wissen, dass der wahre Grund darin liegt Cause()
    2. Es gibt keine Möglichkeit, externen Bibliotheken dieses Wissen beizubringen, und die darin geschriebenen Hilfsfunktionen sind nutzlos.
    3. Unsere Implementierung kann erwarten, Cause()dass die unmittelbare Fehlerursache zurückgegeben wird (oder null, wenn es keine gibt), während die Implementierung in einer anderen Bibliothek erwartet, dass die Methode die nicht-null-Fehlerursache zurückgibt. Das Fehlen von Standardwerkzeugen oder ein allgemein akzeptierter Vertrag stößt auf sehr unangenehme Überraschungen

    Diese Teillösung wird jedoch in vielen Fehlerbibliotheken verwendet, darunter auch in gewissem Umfang unsere. In Go 2 gibt es Pläne, diesen Ansatz zu popularisieren. In diesem Fall sollte es einfacher sein, die oben beschriebenen Probleme zu lösen.


    Errorx


    Im Folgenden werden wir über die von errorx angebotenen Lösungen sprechen, aber zuerst versuchen wir, die Überlegungen zu formulieren, die der Bibliothek zugrunde liegen.


    • Diagnose ist wichtiger als Ressourcenschonung. Die Leistung beim Erstellen und Anzeigen von Fehlern ist wichtig. Sie stellen jedoch einen negativen und keinen positiven Weg dar und dienen in den meisten Fällen als Signal für das Problem. Umso wichtiger ist das Vorhandensein fehlerhafter Diagnoseinformationen.
    • Stack-Trace standardmäßig Damit der Fehler mit der Fülle der Diagnose verschwindet, sollte er sich nicht anstrengen. Im Gegenteil: Um einen Teil der Informationen (aus Gründen der Kürze oder aus Gründen der Leistung) auszuschließen, können zusätzliche Maßnahmen erforderlich sein.
    • Fehlersemantik Es sollte einen einfachen und zuverlässigen Weg geben, die Bedeutung des Fehlers zu überprüfen: Typ, Typ und Eigenschaften.
    • Einfache Zugabe. Das Hinzufügen von Diagnoseinformationen zu einem Passing-Fehler sollte einfach sein und die Überprüfung der Semantik nicht beeinträchtigen.
    • Einfachheit Fehlercodes werden häufig und routinemäßig geschrieben, daher sollte die Syntax für grundlegende Manipulationen mit ihnen einfach und präzise sein. Dies reduziert die Anzahl der Fehler und erleichtert das Lesen.
    • Weniger ist mehr. Die Verständlichkeit und Einheitlichkeit des Codes ist wichtiger als die optionalen Funktionen und Erweiterungsmöglichkeiten (von denen möglicherweise niemand Gebrauch macht).
    • Fehlersemantik ist Teil der API. Fehler, die eine gesonderte Verarbeitung im aufrufenden Code erfordern, sind de facto Teil der öffentlichen API des Pakets. Sie müssen nicht versuchen, sie auszublenden oder weniger offensichtlich zu machen, aber Sie können die Verarbeitung bequemer gestalten und die externen Abhängigkeiten weniger anfällig machen.
    • Die meisten Fehler sind undurchsichtig. Je mehr Fehlertypen für den externen Benutzer nicht zu unterscheiden sind, desto besser. Das Laden der API durch die Arten von Fehlern, die eine spezielle Behandlung erfordern, sowie das Laden der Fehler selbst mit den für die Verarbeitung erforderlichen Daten - ein Konstruktionsfehler, der vermieden werden sollte.

    Am schwierigsten war für uns die Frage der Erweiterbarkeit: Sollte errorx Institutionen mit beliebig unterschiedlichen benutzerdefinierten Fehlertypen Primitive bereitstellen oder eine Implementierung, mit der Sie alles bekommen, was Sie benötigen? Wir haben die zweite Option gewählt. Erstens löst errorx ein recht praktisches Problem - und unsere Erfahrung zeigt, dass es besser ist, eine Lösung für diesen Zweck zu haben und keine Ersatzteile für seine Erstellung. Zweitens ist die Berücksichtigung der Einfachheit sehr wichtig: Da den Fehlern weniger Aufmerksamkeit gewidmet wird, sollte der Code so gestaltet werden, dass ein Fehler beim Arbeiten mit ihnen schwieriger ist. Die Praxis hat gezeigt, dass es wichtig ist, dass der gesamte Code gleich aussieht und funktioniert.


    TL; DR nach Funktionen der Hauptbibliothek:


    • Stack-Trace-Erstellung standardmäßig in allen Fehlern
    • Typprüfungen auf Fehler, verschiedene Varianten
    • Möglichkeit, Informationen zu einem vorhandenen Fehler hinzuzufügen, ohne etwas zu beschädigen
    • Verwalten Sie die Typsichtbarkeit, wenn Sie den ursprünglichen Grund für den Anrufer ausblenden möchten
    • Der Mechanismus zur Verallgemeinerung von Fehlerbehandlungscode (Typhierarchie, Merkmale)
    • Dynamische Eigenschaften für benutzerdefinierte Fehler
    • Standardfehlerarten
    • Syntax-Dienstprogramme zur Verbesserung der Lesbarkeit von Fehlerbehandlungscode

    Einleitung


    Wenn wir das oben analysierte Beispiel mit errorx überarbeiten, erhalten wir Folgendes:


    var (
       DBErrors        = errorx.NewNamespace("db")
       ErrDuplicateKey = DBErrors.NewType("duplicate_key")
    )
    func(t *Table)Insert(entity interface{})error {
       // ...return ErrDuplicateKey.New("violated constraint %s", details)
    }
    funcIsDuplicateKeyError(err error)bool {
       return errorx.IsOfType(err, ErrDuplicateKey)
    }

    funcInsertUser(u *User)error {
       err := usersTable.Insert(u)
       if err != nil {
          return errorx.Decorate(err, "failed to insert user %s", u.Name)
       }
       returnnil
    }

    Der aufrufende Code, der verwendet IsDuplicateKeyError()wird, ändert sich nicht.


    Was hat sich in diesem Beispiel geändert?


    • ErrDuplicateKeywurde ein Typ, keine Fehlerinstanz; Wenn Sie prüfen, ob ein Fehler kopiert werden kann, besteht keine fragile Abhängigkeit von der exakten Gleichheit
    • Angezeigter Namespace für Datenbankfehler; höchstwahrscheinlich werden auch andere Fehler auftreten, und eine solche Gruppierung ist für die Lesbarkeit hilfreich und kann in einigen Fällen im Code verwendet werden
    • Insert gibt für jeden Aufruf einen neuen Fehler zurück:
      • Der Fehler enthält weitere Details. Dies ist natürlich ohne errorx möglich, aber es ist nicht möglich, wenn jedes Mal dieselbe Fehlerinstanz zurückgegeben wird, die zuvor erforderlich warIsDuplicateKeyError()
      • Diese Fehler können eine andere Stapelablaufverfolgung tragen, was nützlich ist, weil Diese Situation ist nicht für alle Aufrufe der Insert-Funktion gültig
    • InsertUser() ergänzt den Fehlertext, wendet jedoch den ursprünglichen Fehler an, der für nachfolgende Vorgänge vollständig gespeichert wird
    • IsDuplicateKeyError() Jetzt funktioniert es: Es kann weder durch Kopieren des Fehlers noch durch Dekorate () - Ebenen beschädigt werden.

    Es ist nicht notwendig, immer genau diesem Muster zu folgen:


    • Der Fehlertyp ist nicht immer eindeutig: Die gleichen Typen können an vielen Stellen verwendet werden.
    • Falls gewünscht, kann die Stack-Trace-Sammlung deaktiviert werden, und es wird nicht jedes Mal ein neuer Fehler erstellt, sondern derselbe wie im ursprünglichen Beispiel zurückgegeben. Hierbei handelt es sich um sogenannte Sentinel-Fehler, deren Verwendung nicht empfohlen wird. Dies kann jedoch nützlich sein, wenn der Fehler nur als Markierung im Code verwendet wird und Sie bei der Erstellung von Objekten speichern möchten.
    • Es gibt eine Möglichkeit, den Test errorx.IsOfType(err, ErrDuplicateKey)einzustellen, wenn Sie die Semantik der Grundursache vor neugierigen Blicken verbergen möchten
    • Für die eigentliche Typprüfung gibt es neben dem Vergleich für den genauen Typ andere Möglichkeiten.

    Godoc enthält ausführliche Informationen zu all dem. Nachfolgend werden die Hauptmerkmale, die für die tägliche Arbeit ausreichend sind, etwas detaillierter beschrieben.


    Typen


    Jeder errorx-Fehler gehört zu einem Typ. Typ ist wichtig, weil vererbte Fehlereigenschaften können durchlaufen werden; Durch ihn oder seine Eigenschaften wird gegebenenfalls eine Prüfung der Semantik vorgenommen. Darüber hinaus ergänzt der Name des Ausdruckstyps die Fehlermeldung und ersetzt sie in einigen Fällen.


    AuthErrors = errorx.NewNamespace("auth")
    ErrInvalidToken    = AuthErrors.NewType("invalid_token")

    return ErrInvalidToken.NewWithNoMessage()

    Die Fehlermeldung enthält auth.invalid_token. Die Fehlererklärung kann anders aussehen:


    ErrInvalidToken    = AuthErrors.NewType("invalid_token").ApplyModifiers(errorx.TypeModifierOmitStackTrace)

    In dieser Variante ist die Stack-Trace-Sammlung mit dem Typmodifizierer deaktiviert. Der Fehler hat eine Markierungssemantik: Sein Typ wird dem externen Benutzer des Dienstes übergeben, und die Aufrufliste in den Protokollen wäre nicht nützlich, da Dies ist kein Problem, das repariert werden muss.


    Hier kann gesagt werden, dass Fehler in mehreren Aspekten einen doppelten Charakter haben. Der Fehlerinhalt wird sowohl für die Diagnose als auch manchmal als Information für einen externen Benutzer verwendet: einen API-Client, einen Bibliotheksbenutzer usw. Der Fehlercode wird sowohl als Mittel zur Kommunikation der Semantik des Vorfalls als auch als Mechanismus zur Übertragung der Kontrolle verwendet. Bei der Verwendung von Fehlertypen sollte dies berücksichtigt werden.


    Einen Fehler machen


    return MyType.New("fail")

    Es ist völlig unnötig, für jeden Fehler einen eigenen Typ zu erstellen. Jedes Projekt kann über ein eigenes allgemeines Fehlerpaket verfügen, und ein Satz ist zusammen mit errorx Teil des allgemeinen Namensraums. Es enthält Fehler, die in den meisten Fällen keine Verarbeitung im Code erfordern und sich für "außergewöhnliche" Situationen eignen, in denen etwas schiefgelaufen ist.


    return errorx.IllegalArgument.New("negative value %d", value)

    In einem typischen Fall ist die Aufrufkette so angeordnet, dass der Fehler ganz am Ende der Kette erstellt und ganz am Anfang verarbeitet wird. In Go ist es nicht ohne Grund, dass es falsch ist, einen Fehler zweimal zu behandeln, d. H. Einen Fehler in das Protokoll zu schreiben und ihn höher in den Stapel zu setzen. Sie können dem Fehler jedoch selbst Informationen hinzufügen, bevor Sie ihn weitergeben:


    return errorx.Decorate(err, "failed to upload '%s' to '%s'", filename, location)

    Der zum Fehler hinzugefügte Text wird im Protokoll angezeigt. Es kann jedoch nicht schaden, den Typ des ursprünglichen Fehlers zu überprüfen.


    Manchmal entsteht ein umgekehrtes Bedürfnis: Unabhängig von der Art des Fehlers sollte der externe Benutzer des Pakets diesen nicht kennen. Wenn er die Gelegenheit dazu bekam, könnte er eine zerbrechliche Abhängigkeit von einem Teil der Implementierung schaffen.


    return service.ErrBadRequest.Wrap(err, "failed to load user data")

    Ein wichtiger Unterschied, der Wrap zur bevorzugten Alternative zu New macht, besteht darin, dass der ursprüngliche Fehler vollständig in den Protokollen angezeigt wird. Und es wird insbesondere einen nützlichen Anfangsstapel mit sich bringen.


    Eine weitere nützliche Technik, mit der Sie alle möglichen Informationen zum Aufrufstack speichern können, sieht folgendermaßen aus:


    return errorx.EnhanceStackTrace(err, "operation fail")

    Wenn der ursprüngliche Fehler von einem anderen Gorutina stammte, enthält das Ergebnis eines solchen Aufrufs die Stack-Spur beider Gorutins, was seine Nützlichkeit ungewöhnlich erhöht. Die Notwendigkeit, einen solchen Anruf zu tätigen, hängt eindeutig von Leistungsproblemen ab: Dieser Fall ist relativ selten und die Ergonomie, die ihn selbst erkennen würde, würde den üblichen Wrap verlangsamen, wenn dies überhaupt nicht erforderlich ist.


    Godoc enthält weitere Informationen und beschreibt zusätzliche Funktionen wie DecorateMany.


    Fehlerbehandlung


    Das Beste von allem, wenn die Fehlerbehandlung folgendes bewirkt:


    log.Error("Error: %+v", err)

    Je weniger Fehler erforderlich sind, mit Ausnahme des Druckvorgangs im Protokoll auf der Systemebene des Projekts, desto besser. In der Realität reicht dies manchmal nicht aus, und Sie müssen Folgendes tun:


    if errorx.IsOfType(err, MyType) { /* handle */ }

    Diese Prüfung wird sowohl beim Typfehler MyTypeals auch bei den untergeordneten Typen erfolgreich durchgeführt und ist resistent gegen errorx.Decorate(). Hier besteht jedoch eine direkte Abhängigkeit von der Art des Fehlers, was innerhalb des Pakets ganz normal ist, es kann jedoch frustrierend sein, wenn es außerhalb des Pakets verwendet wird. In einigen Fällen ist der Typ eines solchen Fehlers Teil einer stabilen externen API, und manchmal möchten wir diese Prüfung durch eine Eigenschaftsprüfung anstelle des genauen Fehlertyps ersetzen.


    Bei klassischen Go-Fehlern würde dies über eine Schnittstelle erfolgen, deren Typ als Indikator für die Art des Fehlers dient. Errorx-Typen unterstützen diese Erweiterung nicht, es kann jedoch ein Mechanismus verwendet werden Trait. Zum Beispiel:


    funcIsTemporary(err error)bool {
       return HasTrait(err, Temporary())
    }

    Diese eingebaute Funktion errorx prüft, ob ein Fehler eine Standardeigenschaft hat Temporary, d. H. ob es vorübergehend ist. Das Markieren von Fehlertypen mit Merkmalen liegt in der Verantwortung der Fehlerquelle. Durch sie kann ein Nutzsignal übertragen werden, ohne dass bestimmte interne Typen in die externe API aufgenommen werden.


    return errorx.IgnoreWithTrait(err, errorx.NotFound())

    Diese Syntax ist nützlich, wenn eine bestimmte Art von Fehler erforderlich ist, um den Steuerungsfluss zu unterbrechen, jedoch nicht an die aufrufende Funktion übergeben werden sollte.


    Trotz der Fülle von Bearbeitungswerkzeugen, von denen nicht alle hier aufgeführt sind, ist es wichtig zu wissen, dass das Arbeiten mit Fehlern so einfach wie möglich bleiben sollte. Ein Beispiel für die Regeln, die wir zu befolgen versuchen:


    • Der Code, der den Fehler erhalten hat, sollte ihn immer vollständig protokollieren. Wenn einige der Informationen redundant sind, überlassen Sie den fehlererzeugenden Code
    • Sie können niemals einen Fehlertext oder das Ergebnis einer Funktion verwenden Error(), um ihn im Code zu verarbeiten. Hierfür eignen sich nur Typ- / Eigenschaftsprüfungen, oder Typ-Assertion bei Nicht-errorx-Fehlern
    • Der Benutzercode sollte nicht beschädigt werden, wenn ein Fehler nicht speziell behandelt wird, selbst wenn eine solche Verarbeitung möglich ist und zusätzliche Funktionen zur Verfügung stehen.
    • Fehler, die durch Eigenschaften geprüft werden, sind besser als die sogenannten Sentinel-Fehler, da solche Kontrollen sind weniger anfällig

    Jenseits von errorx


    Hier haben wir beschrieben, was dem Benutzer der Bibliothek sofort zur Verfügung steht, aber in Joom ist die Durchdringung des mit Fehlern verbundenen Codes sehr groß. Das Protokollierungsmodul akzeptiert explizit Fehler in seiner Signatur und druckt diese selbst aus, um die Möglichkeit einer fehlerhaften Formatierung zu beseitigen und die optional verfügbaren Kontextinformationen aus der Fehlerkette abzurufen. Das Modul, das für paniksicheres Arbeiten mit Gorutiny verantwortlich ist, entpackt den Fehler, wenn es zusammen mit Panik eintritt, und weiß auch, wie man Panik mithilfe der Fehlersyntax darstellt, ohne den ursprünglichen Stack-Trace zu verlieren. Einige davon werden wir vielleicht auch veröffentlichen.


    Kompatibilitätsprobleme


    Trotz der Tatsache, dass wir mit errorx sehr fehlerfrei arbeiten können, ist die Situation mit dem Bibliothekscode, der diesem Thema gewidmet ist, alles andere als ideal. Wir bei Joom lösen ganz spezifische praktische Probleme mit Hilfe von errorx, aber aus Sicht des Go-Ökosystems wäre es vorzuziehen, dieses gesamte Werkzeugset in der Standardbibliothek zu haben. Ein Fehler, dessen Quelle tatsächlich oder möglicherweise einem anderen Paradigma angehört, muss als fremd betrachtet werden, d. H. möglicherweise keine Informationen in der Form, wie es im Projekt üblich ist.


    Einige Dinge wurden jedoch so gemacht, dass sie nicht mit anderen bestehenden Lösungen in Konflikt stehen.


    Das Format wird '%+v'verwendet, um einen Fehler zusammen mit dem Stack-Trace zu drucken, falls vorhanden. Dies ist der De-facto-Standard im Go-Ökosystem und ist sogar im Entwurf für Go 2 enthalten.


    Die Methode Cause() errormacht errorx-Fehler theoretisch kompatibel mit Bibliotheken, die auf der Causer-Schnittstelle basieren, obwohl ihr Ansatz den errorx-Vertrag über die Möglichkeit eines undurchsichtigen Wrappings durch Wrap () verletzt.


    Zukunft


    In einer kürzlich veröffentlichten Ankündigung wurde über die in Go 2 geplanten Änderungen berichtet. Das Thema Fehler ist dort weit verbreitet. Das Beschreiben von Problemen mit Typenprüfungen kann eine nützliche Ergänzung zu diesem Artikel sein.


    Nehmen wir an, dass der aktuelle Status von errorx den Fehlerstatus in Go 1 widerspiegelt. Wir schließen nicht aus, dass eine Version der Bibliothek erscheint, die für Go 2 benutzerfreundlicher ist, wenn Änderungen in der Syntax auftreten. Auf den ersten Blick stehen die Unterschiede in dieser Version, obwohl sie aus der täglichen Arbeit mit Fehlern im Code signifikant sind, nicht im Widerspruch zu den ökologischen Nischen- und Fehlermerkmalen.


    Das Check-Handle-Idiom widerspricht in keiner Weise der heutigen Verwendung von errorx; a Unwrap() errorkann sowohl bei der Beibehaltung Wrap()der errorx-Semantik (d. H. Um die Erweiterung der Fehlerkette unter dem Punkt, an dem sie gemacht wurde Wrap, zu verweigern ) als auch ohne unterstützt werden. Im Moment scheint diese Entscheidung ebenso wie die Entscheidung über die begleitende Syntax verfrüht zu sein.


    Wenn die Syntax des aktuellen Entwurfs in Go 2 beibehalten wird, ist es angebracht, errorx.Is()sie errorx.As()mit derselben Semantik hinzuzufügen , wenn die Verwendung von Standardfunktionen aus dem Fehlerpaket nicht ausreichend ist.


    Fazit


    Wir laden alle diejenigen ein, die sich mit den in diesem Artikel beschriebenen Problemen auskennen und hier etwas Nützliches beschrieben haben, um sich mit der Bibliothek vertraut zu machen und sie zu nutzen. Wir sind offen für Vorschläge und Änderungen, sodass die aktuelle Version der API nicht als endgültig stabil betrachtet werden kann. Möglicherweise gibt es Vorschläge, die uns dazu bringen, einige Merkmale der Syntax zu überarbeiten. Version 1.0 kann in wenigen Monaten erwartet werden, aber die aktuelle Version ist recht langwierig und in Joom poliert. Es ist unwahrscheinlich, dass wir einige der derzeit verfügbaren Funktionen entfernen möchten.


    Repository: https://github.com/joomcode/errorx


    Vielen Dank für Ihre Aufmerksamkeit und behandeln Sie immer Fehler!


    Bild


    Jetzt auch beliebt: