Vor kurzem wurden Entwürfe für das Design der neuen Fehlerbehandlung in Go 2 veröffentlicht. Es ist sehr erfreulich, dass die Sprache nicht an einem Ort steht - sie entwickelt sich und wächst jedes Jahr sprunghaft besser.
Nur jetzt, während Go 2 nur am Horizont sichtbar ist, ist es sehr schmerzhaft und traurig zu warten. Deshalb nehmen wir die Sache selbst in die Hand. Ein bisschen Codegenerierung, ein bisschen Arbeit mit Ast und mit einer leichten Handbewegung verwandeln sich Panik, Panik ... in elegante Ausnahmen!
Und sofort möchte ich eine sehr wichtige und absolut ernsthafte Aussage machen.
Diese Entscheidung ist ausschließlich unterhaltsamer und pädagogischer Natur.
Ich meine nur 4 Spaß. Dies ist in Wahrheit im Allgemeinen ein Proof-of-Concept. Ich habe gewarnt :)
Also, was ist passiert?
Das Ergebnis war ein kleiner solcher Bibliothekscodegenerator . Und Codegeneratoren tragen, wie jeder weiß, Güte und Anmut in sich. Eigentlich nicht, aber in der Go-Welt sind sie ziemlich beliebt.
Wir setzen einen solchen Codegenerator auf go-raw. Er analysiert es für die Hilfe des Standard- go/ast
Moduls, macht einige nicht Bei schlauen Transformationen wird das Ergebnis neben die Datei geschrieben und das Suffix _jex.go
. Die resultierenden Dateien möchten, dass eine winzige Laufzeit funktioniert.
Auf diese einfache Weise fügen wir Go Ausnahmen hinzu.
Wir benutzen
Wir verbinden den Generator mit der Datei in dem Header (vor dem package
), den wir schreiben
Wenn Sie jetzt den Befehl go generate -tags jex
jex
go generate -tags jex
, wird das Dienstprogramm jex
ausgeführt. Sie nimmt den Dateinamen von os.Getenv("GOFILE")
, isst ihn, verdaut ihn und schreibt {file}_jex.go
. Die neugeborene Datei enthält bereits //+build !jex
im Header (das Tag ist invertiert). go build
, und im Kompartiment berücksichtigen die anderen Befehle wie go test
oder go install
nur neue , korrekte Dateien. Lepota ...
Jetzt Punkt-Import github.com/anjensan/jex
.
Ja, während der Import durch einen Punkt obligatorisch ist. In Zukunft ist es geplant, genauso zu gehen.
import . "github.com/anjensan/jex"
Großartig, jetzt können Sie Aufrufe der Stub-Funktionen TRY
, THROW
, EX
in den Code einfügen. Bei alledem bleibt der Code syntaktisch gültig und wird sogar in unverarbeiteter Form kompiliert (es funktioniert einfach nicht), sodass die automatische Vervollständigung verfügbar ist und Linters nicht wirklich schwören. Die Redakteure würden auch eine Dokumentation für diese Funktionen anzeigen, wenn sie nur eine hätten.
Eine Ausnahme auslösen
THROW(errors.New("error name"))
Fangen Sie die Ausnahme
if TRY() {
Unter der Haube wird eine anonyme Funktion erzeugt. Und darin defer
. Und es hat noch eine Funktion. Und darin recover
Sie recover
... Nun, es gibt immer noch ein bisschen Ast-Magie, um mit return
und defer
.
Und ja, sie werden übrigens unterstützt!
Zusätzlich gibt es eine spezielle Makrovariable ERR
. Wenn Sie ihm einen Fehler zuweisen, wird eine Ausnahme ausgelöst. Es ist einfacher, Funktionen aufzurufen, die auf die alte Weise immer noch einen error
file, ERR := os.Open(filename)
Zusätzlich gibt es ein paar kleine Utility-Taschen, die ex
und must
, aber es gibt nicht viel zu erzählen.
Beispiele
Hier ist ein Beispiel für den richtigen, idiomatischen Go-Code
func CopyFile(src, dst string) error { r, err := os.Open(src) if err != nil { return fmt.Errorf("copy %s %s: %v", src, dst, err) } defer r.Close() w, err := os.Create(dst) if err != nil { return fmt.Errorf("copy %s %s: %v", src, dst, err) } if _, err := io.Copy(w, r); err != nil { w.Close() os.Remove(dst) return fmt.Errorf("copy %s %s: %v", src, dst, err) } if err := w.Close(); err != nil { os.Remove(dst) return fmt.Errorf("copy %s %s: %v", src, dst, err) } }
Dieser Code ist nicht so schön und elegant. Das ist übrigens nicht nur meine Meinung!
Aber jex
wird uns helfen, es zu verbessern.
func CopyFile_(src, dst string) { defer ex.Logf("copy %s %s", src, dst) r, ERR := os.Open(src) defer r.Close() w, ERR := os.Create(dst) if TRY() { ERR := io.Copy(w, r) ERR := w.Close() } else { w.Close() os.Remove(dst) THROW() } }
Aber zum Beispiel das folgende Programm
func main() { hex, err := ioutil.ReadAll(os.Stdin) if err != nil { log.Fatal(err) } data, err := parseHexdump(string(hex)) if err != nil { log.Fatal(err) } os.Stdout.Write(data) }
kann umgeschrieben werden als
func main() { if TRY() { hex, ERR := ioutil.ReadAll(os.Stdin) data, ERR := parseHexdump(string(hex)) os.Stdout.Write(data) } else { log.Fatal(EX()) } }
Hier ist ein weiteres Beispiel, um die vorgeschlagene Idee besser zu fühlen. Originalcode
func printSum(a, b string) error { x, err := strconv.Atoi(a) if err != nil { return err } y, err := strconv.Atoi(b) if err != nil { return err } fmt.Println("result:", x + y) return nil }
kann umgeschrieben werden als
func printSum_(a, b string) { x, ERR := strconv.Atoi(a) y, ERR := strconv.Atoi(b) fmt.Println("result:", x + y) }
oder sogar das
func printSum_(a, b string) { fmt.Println("result:", must.Int_(strconv.Atoi(a)) + must.Int_(strconv.Atoi(b))) }
Ausnahme
Das Endergebnis ist eine einfache Wrapper-Struktur über einer error
.
type exception struct {
Ein wichtiger Punkt ist, dass gewöhnliche Panikattacken nicht als Ausnahmen wahrgenommen werden. Daher sind alle Standardfehler wie runtime.TypeAssertionError
keine Ausnahme. Dies steht im Einklang mit den in Go akzeptierten Best Practices. Wenn wir beispielsweise keine Dereferenzierung haben, lassen wir den gesamten Prozess fröhlich und fröhlich fallen. Zuverlässig und vorhersehbar. Obwohl ich mir nicht sicher bin, lohnt es sich vielleicht, diesen Moment zu überprüfen und solche Fehler aufzufangen. Vielleicht optional?
Und hier ist ein Beispiel für eine Ausnahmekette
func one_() { THROW(errors.New("one")) } func two_() { THROW(errors.New("two") } func three() { if TRY() { one_() } else { two_() } }
Hier behandeln wir ruhig die Ausnahme one
, als plötzlich bam ... und die Ausnahme two
geworfen. Die Quelle one
suppress
im suppress
angehängt. Nichts geht verloren, alles geht in die Protokolle. Daher ist es nicht besonders erforderlich, die gesamte fmt.Errorf("blabla: %v", err)
mithilfe des sehr beliebten Musters fmt.Errorf("blabla: %v", err)
direkt in den Nachrichtentext zu verschieben. Obwohl natürlich niemand seine Verwendung hier nicht verbietet, wenn Sie wirklich wollen.
Wenn vergessen zu fangen
Ah, ein weiterer sehr wichtiger Punkt. Um die Lesbarkeit zu verbessern, gibt es eine zusätzliche Prüfung: Wenn eine Funktion eine Ausnahme auslösen kann, muss ihr Name mit _
enden. Ein absichtlich krummer Name, der dem Programmierer sagt: "Sehr geehrter Herr, hier in Ihrem Programm kann etwas schief gehen, bitte seien Sie vorsichtig und fleißig!"
Eine Prüfung für transformierte Dateien wird automatisch gestartet und kann in einem Projekt auch manuell mit dem Befehl jex-check
gestartet werden. Vielleicht ist es sinnvoll, es als Teil des Erstellungsprozesses zusammen mit anderen Lintern auszuführen.
Die Überprüfung //jex:nocheck
Kommentaren wird //jex:nocheck
. Dies ist übrigens die einzige Möglichkeit, Ausnahmen von einer anonymen Funktion auszulösen.
Dies ist natürlich nicht bei allen Problemen ein Allheilmittel. Checker wird dies vermissen
func bad_() { THROW(errors.New("ups")) } func worse() { f := bad_ f() }
Andererseits ist es nicht viel schlimmer als die Standardprüfung für err declared and not used
, die sehr leicht zu umgehen ist.
func worse() { a, err := foo() if err != nil { return err } b, err := bar()
Im Allgemeinen ist diese Frage eher philosophisch. Was ist besser zu tun, wenn Sie vergessen haben, den Fehler zu verarbeiten - ignorieren Sie ihn leise oder werfen Sie eine Panik aus ... Übrigens könnten die besten Testergebnisse durch die Implementierung der Ausnahmeunterstützung im Compiler erzielt werden, dies geht jedoch weit über den Rahmen dieses Artikels hinaus .
Einige mögen sagen, dass dies zwar eine wunderbare Lösung ist, aber keine Ausnahme mehr darstellt, da Ausnahmen jetzt eine sehr spezifische Implementierung bedeuten. Nun, da Stack-Traces nicht an die Ausnahmen angehängt sind oder es einen separaten Linter zum Überprüfen von Funktionsnamen gibt oder dass die Funktion mit _
enden kann, aber keine Ausnahmen auslöst, oder es keine direkte Unterstützung in der Syntax gibt oder dass es wirklich Panik ist, und Panik ist überhaupt keine Ausnahme, denn Gladiolen ... Sporen können so heiß wie wertlos und sinnlos sein. Daher werde ich sie hinter der Tafel des Artikels zurücklassen und die beschriebene Lösung weiterhin als "Ausnahmen" bezeichnen.
Über Stackraces
Um das Debuggen zu vereinfachen, kleben Entwickler häufig einen Stack-Trace an benutzerdefinierte error
. Dafür gibt es sogar mehrere beliebte Bibliotheken. Glücklicherweise erfordert dies mit Ausnahmen keine zusätzlichen Aktionen aufgrund einer interessanten Funktion von Go - während der Panik werden defer
im Stapelkontext des Codes ausgeführt, der die Panik ausgelöst hat. Deshalb hier
func foo_() { THROW(errors.New("ups")) } func bar() { if TRY() { foo_() } else { debug.PrintStack() } }
Ein vollwertiger Stack-Trace wird gedruckt, wenn auch etwas ausführlich (ich schneide die Dateinamen aus).
runtime/debug.Stack runtime/debug.PrintStack main.bar.func2 github.com/anjensan/jex/runtime.TryCatch.func1 panic main.foo_ main.bar.func1 github.com/anjensan/jex/runtime.TryCatch main.bar main.main
Es tut nicht weh, einen eigenen Helfer für das Formatieren / Drucken eines Stack-Trace zu erstellen, wobei Ersatzfunktionen berücksichtigt und zur besseren Lesbarkeit ausgeblendet werden. Ich denke eine gute Idee, schrieb in.
Oder Sie können den Stapel greifen und ihn mit ex.Log()
an die Ausnahme ex.Log()
. Dann darf eine solche Ausnahme auf ein anderes Horoutin übertragen werden - Strextraces gehen nicht verloren.
func foobar_() { e := make(chan error, 1) go func() { defer close(e) if TRY() { checkZero_() } else { EX().Log(debug.Stack())
Leider
Eh ... natürlich würde so etwas viel besser aussehen
try { throw io.EOF, "some comment" } catch e { fmt.Printf("exception: %v", e) }
Aber leider ist die Syntax von Go nicht erweiterbar.
[nachdenklich] Obwohl es wahrscheinlich zum Besseren ist ...
In jedem Fall muss man pervers sein. Eine der alternativen Ideen war zu machen
TRY; { THROW(io.EOF, "some comment") }; CATCH; { fmt.Printf("exception: %v", EX) }
Aber ein solcher Code sieht nach go fmt
ziemlich dumm aus. Und der Compiler schwört, wenn er in beiden Zweigen eine return
sieht. Es gibt kein solches Problem mit if-TRY
.
Es wäre cool, das ERR
Makro durch die MUST
Funktion zu ersetzen (besser als nur). Um zu schreiben
return MUST(strconv.Atoi(a)) + MUST(strconv.Atoi(b))
Im Prinzip ist dies immer noch möglich. Wenn Sie ast analysieren, können Sie den Typ der Ausdrücke ableiten, da alle Arten von Typen eine einfache Wrapper-Funktion generieren, wie sie im must
Paket deklariert must
, und dann MUST
durch den Namen der entsprechenden Ersatzfunktion ersetzen. Dies ist nicht ganz trivial, aber durchaus möglich ... Nur Redakteure / Ideen können einen solchen Code nicht verstehen. Schließlich ist die Signatur der MUST
Stub-Funktion im Go-Typ-System nicht ausdrückbar. Und deshalb keine Autovervollständigung.
Unter der Haube
Allen verarbeiteten Dateien wird ein neuer Import hinzugefügt.
import _jex "github.com/anjensan/jex/runtime"
Der THROW
Aufruf THROW
durch panic(_jex.NewException(...))
. EX()
auch durch den Namen der lokalen Variablen ersetzt, die die abgefangene Ausnahme enthält.
Aber if TRY() {..} else {..}
etwas komplizierter verarbeitet wird. Erstens erfolgt eine spezielle Verarbeitung für alle return
und defer
. Dann werden die verarbeiteten if-Zweige in anonyme Funktionen gestellt. Und dann werden diese Funktionen an _jex.TryCatch(..)
. Hier ist
func test(a int) (int, string) { fmt.Println("before") if TRY() { if a == 0 { THROW(errors.New("a == 0")) } defer fmt.Printf("a = %d\n", a) return a + 1, "ok" } else { fmt.Println("fail") } return 0, "hmm" }
verwandelt sich in so etwas (ich habe die //line
Zeilenkommentare entfernt):
func test(a int) (_jex_r0 int, _jex_r1 string) { var _jex_ret bool fmt.Println("before") var _jex_md2502 _jex.MultiDefer defer _jex_md2502.Run() _jex.TryCatch(func() { if a == 0 { panic(_jex.NewException(errors.New("a == 0"))) } { _f, _p0, _p1 := fmt.Printf, "a = %d\n", a _jex_md2502.Defer(func() { _f(_p0, _p1) }) } _jex_ret, _jex_r0, _jex_r1 = true, a+1, "ok" return }, func(_jex_ex _jex.Exception) { defer _jex.Suppress(_jex_ex) fmt.Println("fail") }) if _jex_ret { return } return 0, "hmm" }
Viel, nicht schön, aber es funktioniert. Okay, nicht alle und nicht immer. Beispielsweise können Sie innerhalb von TRY keine defer-recover
, da der Funktionsaufruf in ein zusätzliches Lambda umgewandelt wird.
Bei der Anzeige des Ast-Baums wird außerdem die Option "Kommentare speichern" angezeigt. Theoretisch sollte go/printer
sie drucken ... Was er ehrlich tut, ist die Wahrheit sehr, sehr krumm =) Ich werde keine Beispiele nennen, nur krumm. Im Prinzip ist ein solches Problem vollständig lösbar, wenn Sie die Positionen für alle Ast-Knoten sorgfältig angeben (jetzt sind sie leer), aber dies ist definitiv nicht in der Liste der für den Prototyp erforderlichen Dinge enthalten.
Versuchen Sie es
Aus Neugier schrieb ich einen kleinen Benchmark .
Wir haben eine hölzerne qsort-Implementierung, die nach Duplikaten in der Ladung sucht. Gefunden - ein Fehler. Eine Version wirft es einfach durch return err
, die andere klärt den Fehler durch Aufrufen von fmt.Errorf
. Und man benutzt noch Ausnahmen. Wir sortieren Slices unterschiedlicher Größe, entweder ohne Duplikate (kein Fehler, das Slice ist vollständig sortiert) oder mit einer Wiederholung (die Sortierung bricht etwa zur Hälfte ab, was an den Timings erkennbar ist).
Ergebnisse ~ > cat /proc/cpuinfo | grep 'model name' | head -1 model name : Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz ~ > go version go version go1.11 linux/amd64 ~ > go test -bench=. github.com/anjensan/jex/demo goos: linux goarch: amd64 pkg: github.com/anjensan/jex/demo BenchmarkNoErrors/_____10/exception-8 10000000 236 ns/op BenchmarkNoErrors/_____10/return_err-8 5000000 255 ns/op BenchmarkNoErrors/_____10/fmt.errorf-8 5000000 287 ns/op BenchmarkNoErrors/____100/exception-8 500000 3119 ns/op BenchmarkNoErrors/____100/return_err-8 500000 3194 ns/op BenchmarkNoErrors/____100/fmt.errorf-8 500000 3533 ns/op BenchmarkNoErrors/___1000/exception-8 30000 42356 ns/op BenchmarkNoErrors/___1000/return_err-8 30000 42204 ns/op BenchmarkNoErrors/___1000/fmt.errorf-8 30000 44465 ns/op BenchmarkNoErrors/__10000/exception-8 3000 525864 ns/op BenchmarkNoErrors/__10000/return_err-8 3000 524781 ns/op BenchmarkNoErrors/__10000/fmt.errorf-8 3000 561256 ns/op BenchmarkNoErrors/_100000/exception-8 200 6309181 ns/op BenchmarkNoErrors/_100000/return_err-8 200 6335135 ns/op BenchmarkNoErrors/_100000/fmt.errorf-8 200 6687197 ns/op BenchmarkNoErrors/1000000/exception-8 20 76274341 ns/op BenchmarkNoErrors/1000000/return_err-8 20 77806506 ns/op BenchmarkNoErrors/1000000/fmt.errorf-8 20 78019041 ns/op BenchmarkOneError/_____10/exception-8 2000000 712 ns/op BenchmarkOneError/_____10/return_err-8 5000000 268 ns/op BenchmarkOneError/_____10/fmt.errorf-8 2000000 799 ns/op BenchmarkOneError/____100/exception-8 500000 2296 ns/op BenchmarkOneError/____100/return_err-8 1000000 1809 ns/op BenchmarkOneError/____100/fmt.errorf-8 500000 3529 ns/op BenchmarkOneError/___1000/exception-8 100000 21168 ns/op BenchmarkOneError/___1000/return_err-8 100000 20747 ns/op BenchmarkOneError/___1000/fmt.errorf-8 50000 24560 ns/op BenchmarkOneError/__10000/exception-8 10000 242077 ns/op BenchmarkOneError/__10000/return_err-8 5000 242376 ns/op BenchmarkOneError/__10000/fmt.errorf-8 5000 251043 ns/op BenchmarkOneError/_100000/exception-8 500 2753692 ns/op BenchmarkOneError/_100000/return_err-8 500 2824116 ns/op BenchmarkOneError/_100000/fmt.errorf-8 500 2845701 ns/op BenchmarkOneError/1000000/exception-8 50 33452819 ns/op BenchmarkOneError/1000000/return_err-8 50 33374000 ns/op BenchmarkOneError/1000000/fmt.errorf-8 50 33705994 ns/op PASS ok github.com/anjensan/jex/demo 64.008s
Wenn der Fehler nicht ausgelöst wurde (der Code ist stabil und Stahlbeton), ist die Garantie mit dem Ausnahmewurf ungefähr vergleichbar mit der return err
und fmt.Errorf
. Manchmal etwas schneller. Wenn der Fehler ausgelöst wurde, stehen die Ausnahmen an zweiter Stelle. Aber alles hängt vom Verhältnis von "nützlicher Arbeit / Fehler" und der Tiefe des Stapels ab. Bei kleinen Slices geht return err
der Lücke voraus, bei mittleren und großen Slices sind Ausnahmen bereits gleichbedeutend mit manueller Weiterleitung.
Kurz gesagt, wenn Fehler äußerst selten auftreten, können Ausnahmen den Code sogar etwas beschleunigen. Wenn wie alle anderen, dann wird es so etwas sein. Aber wenn sehr oft ... dann sind langsame Ausnahmen weit entfernt von dem wichtigsten Problem, über das man sich Sorgen machen sollte.
Als Test habe ich eine echte Gosh- Bibliothek für Ausnahmen migriert .
Zu meinem großen Bedauern hat es nicht funktioniert, 1-in-1 neu zu schreibenGenauer gesagt hätte es sich herausgestellt, aber das muss gestört werden.
So scheint beispielsweise die Funktion rpc2XML
error
... ja, sie gibt ihn einfach nie zurück. Wenn Sie versuchen, einen nicht unterstützten Datentyp zu serialisieren - kein Fehler, nur leere Ausgabe. Vielleicht war es das, was beabsichtigt war? Nein, das Gewissen erlaubt es nicht, es so zu lassen. Hinzugefügt von
default: THROW(fmt.Errorf("unsupported type %T", value))
Es stellte sich jedoch heraus, dass diese Funktion auf besondere Weise verwendet wird
func rpcParams2XML(rpc interface{}) (string, error) { var err error buffer := "<params>" for i := 0; i < reflect.ValueOf(rpc).Elem().NumField(); i++ { var xml string buffer += "<param>" xml, err = rpc2XML(reflect.ValueOf(rpc).Elem().Field(i).Interface()) buffer += xml buffer += "</param>" } buffer += "</params>" return buffer, err }
Hier durchlaufen wir die Liste der Parameter, serialisieren sie alle, geben aber nur für letztere einen Fehler zurück. Die restlichen Fehler werden ignoriert. Seltsames Verhalten leichter gemacht
func rpcParams2XML_(rpc interface{}) string { buffer := "<params>" for i := 0; i < reflect.ValueOf(rpc).Elem().NumField(); i++ { buffer += "<param>" buffer += rpc2XML_(reflect.ValueOf(rpc).Elem().Field(i).Interface()) buffer += "</param>" } buffer += "</params>" return buffer }
Wenn mindestens ein Feld für die Serialisierung nicht funktioniert hat - ein Fehler. Das ist besser. Es stellte sich jedoch heraus, dass diese Funktion auch in besonderer Weise genutzt wird.
xmlstr, _ = rpcResponse2XML(response)
Auch dies ist für den Quellcode nicht so wichtig, da dort Fehler ignoriert werden. Ich fange an zu raten, warum manche Programmierer die explizite Fehlerbehandlung so gern durchgehen, if err != nil
... Aber mit Ausnahmen ist es immer noch einfacher, sie weiterzuleiten oder zu verarbeiten, als sie zu ignorieren
xmlstr = rpcResponse2XML_(response)
Und ich habe nicht begonnen, die "Fehlerkette" zu entfernen. Hier ist der Originalcode
func DecodeClientResponse(r io.Reader, reply interface{}) error { rawxml, err := ioutil.ReadAll(r) if err != nil { return FaultSystemError } return xml2RPC(string(rawxml), reply) }
hier ist das umgeschrieben
func DecodeClientResponse_(r io.Reader, reply interface{}) { var rawxml []byte if TRY() { rawxml, ERR = ioutil.ReadAll(r) } else { THROW(FaultSystemError) } xml2RPC_(string(rawxml), reply) }
Hier geht der ursprüngliche Fehler (den ioutil.ReadAll
zurückgegeben hat) nicht verloren, er wird an die Ausnahme im suppress
angehängt. Auch hier kann es wie im Original gemacht werden, aber es muss besonders verwirrt sein ...
Ich habe die Tests neu geschrieben und if err != nil { log.Error(..) }
durch einen einfachen Ausnahmefall ersetzt. Es gibt einen negativen Punkt - die Tests fallen auf den ersten Fehler und funktionieren nicht weiter "zumindest irgendwie gut". Nach Meinung des Geistes wäre es notwendig, sie in Untertests zu unterteilen ... Was es im Allgemeinen sowieso wert ist, getan zu werden. Es ist jedoch sehr einfach, den richtigen Stackrace zu erhalten
func errorReporter(t testing.TB) func(error) { return func(e error) { t.Log(string(debug.Stack())) t.Fatal(e) } } func TestRPC2XMLConverter_(t *testing.T) { defer ex.Catch(errorReporter(t))
Im Allgemeinen sind Fehler sehr leicht zu ignorieren. Im Originalcode
func fault2XML(fault Fault) string { buffer := "<methodResponse><fault>" xml, _ := rpc2XML(fault) buffer += xml buffer += "</fault></methodResponse>" return buffer }
hier wird der rpc2XML
von rpc2XML
wieder leise ignoriert. Es ist so geworden
func fault2XML(fault Fault) string { buffer := "<methodResponse><fault>" if TRY() { buffer += rpc2XML_(fault) } else { fmt.Printf("ERR: %v", EX()) buffer += "<nil/>" } buffer += "</fault></methodResponse>" return buffer }
Nach meinen persönlichen Gefühlen ist es einfacher, ein "halbfertiges" Ergebnis mit Fehlern zurückzugeben.
Zum Beispiel eine halb konstruierte Antwort. Die Ausnahmen sind komplizierter, da die Funktion entweder ein erfolgreiches Ergebnis oder gar nichts zurückgibt. Eine Art Atomizität. Andererseits ist es schwieriger, Ausnahmen zu ignorieren oder die Grundursache in der Ausnahmekette zu verlieren. Schließlich müssen Sie dies noch gezielt versuchen. Bei Fehlern geschieht dies einfach und natürlich.
Anstelle einer Schlussfolgerung
Beim Schreiben dieses Artikels wurde kein Gopher verletzt.
Vielen Dank für das Foto des goffer-alkoholischen http://migranov.ru
Ich konnte nicht zwischen den Hubs "Programming" und "Abnormal Programming" wählen.
Eine sehr schwierige Wahl, die zu beiden hinzugefügt wurde.