A Unified Approach to Error Handling in Go Backend Services
The article presents a unified error‑handling strategy for Go backend services that combines idiomatic internal checks, Go 1.13’s %w wrapping with errors.Is/As, and a compact 4‑character base‑36 hash code system to deliver consistent machine‑readable codes and user‑friendly messages while preserving full log details.
In Go‑based backend services, error handling has long suffered from fragmented solutions. This article proposes a complete, unified scheme that covers error propagation, return, and back‑trace from inside a service to its callers.
Problem statement : three dimensions need to be addressed – (1) handling errors inside a function, (2) returning error information from functions or modules, and (3) returning user‑friendly error messages from services or systems.
1. Function‑internal error handling : Procedural code often writes if err != nil { return err } , while object‑oriented code may need different branches for different error types. Go lacks a built‑in assert statement, so developers resort to one‑line checks or panic/recover patterns.
One‑line check example:
if err != nil { return err }Using panic and recover to emulate an assertion:
func SomeProcess() (err error) {
defer func() {
if e := recover(); e != nil {
err = e.(error)
}
}()
// ...
if err = DoSomething(); err != nil {
return
}
// ...
}Both approaches have trade‑offs: the one‑liner is idiomatic but debated for brace placement; panic/recover provides early exit but adds overhead and is usually reserved for truly fatal situations (e.g., tests).
2. Go’s lack of try…catch : Go deliberately omits try…catch . Developers either use the one‑liner above or the panic/recover pattern, each with its own drawbacks.
3. Function/module error return patterns before Go 1.13 :
Enum‑style errors: var ( ErrRecordNotExist = errors.New("record not exist") ErrConnectionClosed = errors.New("connection closed") // ... )
Type‑assertion style with custom error structs: type ErrRecordNotExist errImpl type errImpl struct { msg string } func (e *errImpl) Error() string { return e.msg }
Wrapping with fmt.Errorf (no %w support): if err := DoSomething(); err != nil { return fmt.Errorf("DoSomething() error: %v", err) }
These methods either lose type safety or make downstream error classification brittle.
4. After Go 1.13 : fmt.Errorf gains %w for error wrapping, and the errors package provides Is and As for reliable inspection. The recommended pattern is to wrap errors with %w and let callers use errors.Is or errors.As .
5. Service/system error return : Most APIs use a code‑message model. code is machine‑readable (numeric or enum), message is human‑readable. Problems arise when codes are incomplete, messages expose sensitive details, or users cannot act on opaque codes.
6. Proposed short‑hash error code solution : Generate a 4‑character code (base‑36) from the error string using MD5, take the high 20 bits, and encode. This yields up to 1.6 million distinct codes, easy for users to remember and for developers to look up in logs.
import (
// ...
"github.com/martinlindhe/base36"
)
var replacer = strings.NewReplacer(
" ", "0",
"O", "0",
"I", "1",
)
func Err2Hashcode(err error) (uint64, string) {
u64 := hash(err.Error())
codeStr := encode(u64)
u64, _ = decode(codeStr)
return u64, codeStr
}
func encode(code uint64) string {
s := fmt.Sprintf("%4s", base36.Encode(code))
return replacer.Replace(s)
}
func decode(s string) (uint64, bool) {
if len(s) != 4 { return 0, false }
s = strings.Replace(s, "l", "1", -1)
s = strings.ToUpper(s)
s = replacer.Replace(s)
code := base36.Decode(s)
return code, code > 0
}
func hash(s string) uint64 {
h := md5.Sum([]byte(s))
u := binary.BigEndian.Uint32(h[0:4])
return uint64(u & 0xFFFFF)
}This code produces a compact, case‑insensitive hash like 30EV . The front‑end shows a generic message such as “Unknown error, code 30EV. Contact support.” while the back‑end logs the full error and the hash, enabling fast lookup.
Limitations :
Avoid hashing non‑deterministic data (timestamps, user IDs) to keep the same error producing the same code.
Normalize ambiguous characters (I/O) to prevent confusion.
Overall, the short‑hash scheme is a supplemental tool for catching uncovered error cases; a complete code‑message matrix should still be maintained for production‑grade APIs.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.