1. Introduction
1.1 Background
Recently, I’ve been working on a service that directly interacts with front-end and third-party platforms (which can be simply understood as other departments of the company or client software), involving modules such as user registration, login, data processing, etc. The architecture diagram is roughly as follows. The architecture diagram is roughly as follows:
After getting the requirements, combined with the team’s internal familiar technology stack, we determined that the backend service [business logic layer] using Golang language to develop, the framework used are Gin to do HTTP interaction, Swaggo automatic generation of interface documents, Redis and MySQL as K-V and DB storage.
It is worth noting that the application requires us to specify and normalize the errors on the third-party platform and the Web side, for example: the error code information on the Web side is also available to the third-party platform.
Therefore, the design and management of error code specification becomes our primary problem.
1.2 Features
The Go language provides a simple error handling mechanism: error 类型
. error is an interface type defined as follows:
1 | type error interface { |
The use of error can be seen everywhere in the code, e.g., the database triple Gorm auto-incrementing tables, Gin getting parameters, etc:
1 |
|
In addition to Go itself and the use of three-way packages, we can also implement specific error messages through errors.New()
:
1 | func div(a, b int) (float, error) { |
However, a new problem arises.
If we define the same error with a similar errors.New()
definition every time we encounter it, we will not only have a lot of duplicate code, but it will also be very difficult to sort out our error messages for web development or third party platforms. Not only would there be a lot of duplicate code, but it would also be very difficult to comb through our error messages to web-side development or third-party platforms.
**Imagine 100,000 lines of code, and it would be more or less unseemly to go through them one by one looking for errors.New()
information! **
2. Defining Error Codes and Messages
2.1 Error Code Design Specifications
So we thought of unifying error messages and uniquely identifying them with error codes. That is: ** an error code corresponds to an error message **, every time you need it, just use the error code directly.
The error code in the industry adopts 5~7
bit integer (space-saving) constants to define, so we adopt 5 bit numeric error code, Chinese error message, and divide the range of error code according to the business module.
Module Description
Module description 11 starts with service level error code, such as internal service error, wrong parameter information, etc. 22 starts with: business module level error code 201201 starts with error code of dataset module 202202: user management module 203***203: pre-training management module
2.2 Error code definition
Create a new err_code
package with a new error_handle.go
file:
1 | package err_code |
Added error codes and error messages:
1 | type ErrCode int |
2.2 Map Mapping Error Messages
Based on the error code, we use Map mapping to define the Chinese error message:
1 |
|
Use the error code information:
1 |
|
In this way, our error code mechanism is effectively set up, with the benefits of:
- Solve the problem of difficult to manage error information: all in a
err_code
package, at a glance you can know what error information the service has, ** easy to collect and error localization **; - solved the problem of uneven error code, arbitrary definition: according to the business module divided into different numerical ranges of error code, ** according to the error code you can know which module is the problem, to avoid tearing the skin **;
However, some smart and studious friends may have found it. Every time you define a new error code, you need to add the error code number and Map mapping error information, is there a more concise way to define it?
The answer is yes! As a programmer who often tries to be lazy, simple and efficient automation is the goal we are pursuing.
3. Automated generation of error codes and error messages
3.1 stringer
stringer
is a toolkit open-sourced for the Go language, and the installation command is:
go install golang.org/x/tools/cmd/stringer
In addition to the toolkit, we also need Go’s iota
counter for automatic accumulation of constant numbers:
PS:
iota
is the go language’s constant counter and can only be used in constant expressions.
Its value starts at 0, and grows by 1 for each new line in const. iota increases its value by 1 until it encounters the next const keyword, when its value is reset to 0.
3.2 Defining Error Messages
1 | package err_code |
With the above definition of the error code const constant + error code name + error message comment, where iota
is automatically constant-accumulated.
I.e. ParamBindError
is 10002
and TokenAuthFail
is 10003
:
1 |
|
There are two ways we can generate error messages for error code mapping.
1) Run the stringer
utility in `Goland
2) Execute the command to run the stringer
utility
We run the following command on the err_code/error_handle.go
file:
go generate internal/protocols/err_code/error_handle.go
This generates a new errcode_string.go
file with a mapping of err_code
to err_msg
:
1 | // Code generated by "stringer -type ErrCode -linecomment" |
This way, we don’t have to manually create a new Map to maintain the mapping relationship!
Note: After each addition, deletion or modification of error codes, you need to execute
go generate
to generate a new mapping fileerrcode_string.go
.
This file is the mapping file for error codes and error messages, do not modify or delete it manually!
4. Error Code Practice
In summary, we have defined the error code message. Next, interface to briefly demonstrate the usage.
A portion of the go.mod
dependencies are listed below:
1 | module wanx-llm-server |
Add main.go
as the service startup entry with the following code:
1 | package main |
server.go
As a HTTP
request entry, the key code is as follows:
1 | func SetupRouter() *gin.Engine { |
service/user.go
To realize the specific business, the key code is as follows:
1 |
|
In the example, by making direct calls to the error code, we avoid the frequent steps of throwing and receiving errors, followed by error_code
collocation.
In this way, a standardized system of error codes is created!
End of story, sprinkle flowers!