How to simplify a series of consecutive error checking

Hi,

I’m new to Go and now trying to write a small web application backend API with Go. I’ve got some codes as below. Is there any way to simplify it, for example, a try-catch to cover all the error checking like Java.

Here goes my code block:

func GetContextUser(c *gin.Context) (*dto.UserInfoDto, error) {
	token, err := uses.GetRequestToken(c.Request)
	if err != nil {
		return nil, err
	}
	user, err := dao.GetUserByToken(token)
	if err != nil {
		return nil, err
	}
	addresss, err := dao.GetUserAddress(user.Id)
	if err != nil {
		return nil, err
	}
	estates, err := dao.GetAllEstates()
	if err != nil {
		return nil, err
	}
	var res = dto.UserInfoDto{
		Info:      *user,
		Addresses: addresss,
		Config: dto.Config{
			Estates: estates,
		},
	}
	return &res, nil
}

Go doesn’t have (thanks God) the concept of exceptions.
The code you show is perfectly idiomatic in Go.

There’re some discussion to create a simplified syntax to allow less verbose code but at the moment this is the way to go.

TIP: you can enrich the returned error “wrapping” the existing one (for instance using fmt.Errorf())

1 Like

hi,
In fact, these are two issues. If go returns an error, it means that the exception in the method you called has been caught in advance, and there is no exception happening here, just like in Java, when a try catch catches an exception and returns null. If you want to include all methods in a try catch like way, then the method you call should not catch exceptions or not return an error, but should directly panic () throw exceptions, depending on the design of Func

It is your responsibility as a developer to think about everything that could go wrong in your code - and decide about the right reaction to take.

Go makes this very easy, by requiring you to handle each error individually. While this might seem cumbersome for a small example project, it is absolutely necessary for anything else.

When you just throw the error up the call chain, your user will get a generic error message like “Error: Value cannot be null” - without any indication what to do, or any context where exactly the error happened.

Typical error causes

In my experience most errors fall into one of three categories, depending on what needs to happen to fix it:

  1. User/Input Error: The user tries something which cannot work unless he changes something. They need to take a certain action (e.g. login, change their query, select a different product, because this one is out of stock…) — In this case you need to provide the user with a clear message, what they need to do to proceed (ideally with a link).

  2. Temporary condition error: The user just needs to try again and it will probably work (e.g. a network request failed, operation timeout because the server was busy, optimistic locking exception…) - your code should ideally retry again immediately if there is a chance it will just work. Or provide the user with a clear message - e.g. “please try again in a few minutes”

  3. Permanent/Code Error: Something is broken, which only a technical person can fix before it will work again (database broken or a bug in the code) - Here the user will need to contact support (or a monitoring service will alert support automatically) - these errors need to be logged with details, so the bug can easily be reproduced, analyzed and fixed.

Example with useful error handling

token, err := uses.GetRequestToken(c.Request)
	if err != nil || token == nil {
        // No Token? User is not logged in
		return nil, ErrNoLoginToken // Redirect to login page
	}
	user, err := dao.GetUserByToken(token)
	if err != nil {
        // Cannot find User for Token: Either session expired or hacking attempt
		return nil, ErrSessionInvalid // Redirect to login page with Error Message
	}
	addresss, err := dao.GetUserAddress(user.Id)
    if errors.Is(err, sql.ErrNotFound) {
        // Display Link to Page, where User can add an Address to his account
        return nil, ErrNoAddress
    } else if err != nil {
        // generic db-error, provide context for bug search
		return nil, fmt.Errorf("could not load address for user %+v: %w", user, err)
	}
	estates, err := dao.GetAllEstates()
	if err != nil {
        // generic db-error, provide context for bug search
		return nil, fmt.Errorf("could not load estates for user: %+v %w", user, err)
	}
2 Likes