Summary
Errors are created with inconsistent messages. Each package has it own set of styles, constants, or custom error types. Error codes are added arbitrarily. No easy way to tell which errors may be returned from which function.
Go allows each package to define its own error-handling strategy. The common practice is to return an error value that implements the error interface. This lets the caller decide what to do next.
Include stacktrace using pkg/errors, a popular package at that time.Export constants or variables for package-specific errors.Use errors.Is() to check for specific errors.Wrap errors with a new messages and context.
Over time, error types and codes were added without a clear plan. This led to inconsistencies and duplication. No standardization led to improper error handling.
All errors must implement the Error interface. This creates a clear boundary between third-party libraries and our internal Errors. The framework will map each error code to the corresponding Protobuf ErrorCode.
Package errors/E exports all error codes and common types. A Code is implemented as anuint16. A Group is implemented in a struct.
Type QueryUserByUsername struct { username: " username", password: "password" } type UserRepository struct { user: "User", username: 'User', password: 'password', user: 'user' }); type UserService struct { UserReporter struct {User:'user', UserReporters: 'users', UserService:'User', User: 'Users', UserServices: 'UUser', User Services: 'UAUser', userService: '[User], User: User], UserService': 'User,' user: User', userServicing: 'Uservers', UserServ
Import errors/E from "connectly.ai" and "connect.ai/go" from "http://www.connect.ly/go/policies/pcs/errors/E" and then "logging/logging" from 'logging' "logger/logger" and 'logger://www/.logger.com/loggers/loggling/logged/logistics/logistication/logistical/logistically.logistics.log?logistics=logistic.logistic&logistic=logistics&logisticsId=logistical.
Error is an extension of Go’s standard error interfacetype. Different types: Error0, VlError, ApiError.
Error0 is the default Error type. It contains a base and an optional sub-error. VlError is for validation errors. It can contain multiple sub-errors.
This type is now deprecated. Instead, we will declare all the mapping (ErrorType, ErrorCode, gRPC, HTTP code) in a central place. Every ErrorCode becomes a ZZZ.API_TODO code. ApiError is created as an adapter.
A Code is implemented as anuint16 value, which has a corresponding string presentation. To store those strings, there is an array of all available CodeDesc:const MaxCode = 321.
Each Error type has a corresponding Code type, and each can have its own extra extra methods. Each Code type returns a different Error type, which can have their own extra methods, such as VlCode and VlBuilder. Use a code-code to mark the codes available for external API.
Convert third-party errors to internal namespace errors and map error codes from inner layers to outer layers. Each layer usually has 2 generic codes UNEXPECTED and UNKNOWN.
The framework provides specialized helpers like ΩxError() to make writing and asserting error conditions in tests easier. The above code can be rewritten as: user, err := queryUser(ctx, userReq) if err!= nil.
Migrate one package at a time. Start with the lowest-level packages and work your way up. If parts of the codebase lack tests, add them. If you are not confident in your changes, add more tests.
The s.repo.CreateUser() call still returns the old error type. The Transaction() method needs to return the new Error type. When you encounter an error that doesn’t fit into the existing ones, add a new code. This will help you build a comprehensive set of error codes over time.
If you like the post, subscribe to my newsletter to get latest updates. I enjoy learning and seeing a better version of myself each day. Occasionally spin off new open source projects.