Home > Blockchain >  How to avoid excessive if nesting in GoLang when functions can return errors?
How to avoid excessive if nesting in GoLang when functions can return errors?

Time:02-21

Golang best practices say that function that can fail should return a tuple, where the first element of the tuple is a successful result and the second element is the error. The client code should check first for errors before proceeding.

However, when you are invoking within the same function other functions that can fail, this leads to many if/else.

func CreateBillingService(cmd *cobra.Command) (*billing.BillingService, error) {
    token, err := getAuthTokenFromCmd(cmd)
    if err == nil {
        env, err := cmd.Flags().GetString("billing-service-env")
        if err == nil {
            typedEnv, err := billing.ParseEnv(env)
            if err != nil {
                return nil, err
            }
            envUrl, err := typedEnv.ToUrl()
            if err != nil {
                customHost, err := cmd.Flags().GetString("billing-service-custom-host")
                if err == nil {
                    return billing.NewService(customHost, *token), nil
                }
                return nil, fmt.Errorf("billing-service-custom-host can be set only if billing-env is custom ")
            }
            return billing.NewService(*envUrl, *token), nil
        }
    }
    return nil, err
}

Clearly, one option would be to break this down into at least three functions to be executed when err is nil. However, in each of those functions, most of the code would be to deal with errors

func CreateBillingService(cmd *cobra.Command) (*billing.BillingService, error) {
    token, err := getAuthTokenFromCmd(cmd)
    if err == nil {
        return getBillingServiceWithToken(token, cmd)
    }
    return nil, err
}

Is there a more idiomatic way to handle errors in Go?

CodePudding user response:

You can flatten the nested error checks by immediately returning when an error is detected.

func CreateBillingService(cmd *cobra.Command) (*billing.BillingService, error) {
    token, err := getAuthTokenFromCmd(cmd)
    if err!=nil {
       return nil,err
    }
    env, err := cmd.Flags().GetString("billing-service-env")
    if err == nil {
       return nil,err
    }
    typedEnv, err := billing.ParseEnv(env)
    if err != nil {
       return nil, err
    }
    envUrl, err := typedEnv.ToUrl()
    if err != nil {
       customHost, err := cmd.Flags().GetString("billing-service-custom-host")
       if err == nil {
            return billing.NewService(customHost, *token), nil
       }
       return nil, fmt.Errorf("billing-service-custom-host can be set only if billing-env is custom ")
    }
    return billing.NewService(*envUrl, *token), nil
}

CodePudding user response:

There is no try, catch, throw, exception in Go, so errors need to be handled gracefully.

You can avoid deep nested code with by checking whether error is not empty instead of whether error is empty.

func CreateBillingService(cmd *cobra.Command) (*billing.BillingService, error) {
    token, err := getAuthTokenFromCmd(cmd)
    if err != nil {
        return nil, err
    }

    env, err := cmd.Flags().GetString("billing-service-env")
    if err != nil {
        return nil, err
    }

    typedEnv, err := billing.ParseEnv(env)
    if err != nil {
        return nil, err
    }

    envUrl, err := typedEnv.ToUrl()
    if err != nil {
        customHost, err := cmd.Flags().GetString("billing-service-custom-host")
        if err =! nil {
            err = fmt.Errorf("billing-service-custom-host can be set only if billing-env is custom ")
            return nil, err
        }
    }
    
    return billing.NewService(*envUrl, *token), nil
}
  •  Tags:  
  • go
  • Related