1
0
Fork
You've already forked nice
0
Nice way to handle error
  • Go 100%
Find a file
Antony Ho 121afbab6d
Add GitHub Actions for repo sync ( #10 )
* Add GitHub Actions for repo sync
Add GitHub Actions to synchronise repository with remote repo, Codeberg.
* Add read permission the workflow
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025年07月06日 19:58:13 +02:00
.github/workflows Add GitHub Actions for repo sync ( #10 ) 2025年07月06日 19:58:13 +02:00
go.mod Initial stage ready 2025年06月08日 19:08:36 +02:00
go.sum Initial stage ready 2025年06月08日 19:08:36 +02:00
handle_test.go Set //nolint to intended test code 2025年06月08日 21:13:39 +02:00
LICENSE Initial commit 2025年06月06日 23:37:29 +02:00
nice.go Improve case coverage ( #8 ) 2025年06月17日 15:03:18 +02:00
README.md Update to a comprehensive readme 2025年06月10日 14:17:30 +02:00
tackle_test.go Improve case coverage ( #8 ) 2025年06月17日 15:03:18 +02:00

Nice - Error Handling Library for Go

Go Go Report Card Go Reference License

Nice is a Go library that provides an alternative error handling pattern using Go's built-in panic, defer, and recover mechanisms. It offers a more structured approach to error handling, similar to try-catch patterns found in other programming languages, while maintaining Go's philosophy and idioms.

Table of Contents

Overview

Nice provides a fail-fast error handling pattern for Go applications, particularly useful in scenarios where you want to handle multiple errors in a centralized manner. Instead of checking errors after each function call, Nice allows you to register error handlers and use panic to propagate errors up the call stack.

Why Nice?

Traditional Go error handling:

funcprocessData(a,bstring)error{x,err:=strconv.Atoi(a)iferr!=nil{returnerr}y,err:=strconv.Atoi(b)iferr!=nil{returnerr}result,err:=calculate(x,y)iferr!=nil{returnerr}returnsaveResult(result)}

With Nice:

funcprocessData(a,bstring){defernice.Tackle(errors.New("conversion error"),errors.New("calculation error"),).With(func(errany){log.Printf("Processing failed: %v",err)})x:=mustAtoi(a)y:=mustAtoi(b)result:=mustCalculate(x,y)mustSaveResult(result)}

Features

  • Centralized Error Handling: Handle multiple error types in one place
  • Type-Safe Error Matching: Register specific error types or values to catch
  • Multiple Handler Support: Chain multiple handlers for different error scenarios
  • Custom Error Types: Full support for custom error types and interfaces
  • Fail-Fast Pattern: Stop execution immediately when an error occurs
  • Clean API: Simple and intuitive API design

Installation

go get github.com/antonyho/nice

Quick Start

packagemainimport("errors""log""github.com/antonyho/nice")varErrDivideByZero=errors.New("divide by zero")funcmain(){defernice.Tackle(ErrDivideByZero).With(func(errany){log.Printf("Caught error: %v",err)})result:=divide(10,0)log.Printf("Result: %d",result)}funcdivide(a,bint)int{ifb==0{panic(ErrDivideByZero)}returna/b}

API Reference

Tackle

Tackle is the primary function for registering error handlers. It accepts one or more error values or types to catch.

funcTackle(artefacts...any)Handler

Parameters

  • artefacts: One or more error values, error types, or reflect.Type values to catch.

Returns

  • Handler: A handler instance to attach callback functions.

Example

defernice.Tackle(io.EOF,reflect.TypeFor[os.PathError](),reflect.TypeFor[*MyCustomError](),).With(errorHandler)

Handler.With

With attaches a handler function to be called when a matching error is caught.

func(hHandler)With(handlefunc(any))

Parameters

  • handler: Function to call when a matching error is caught. Receives the error or artefact value.

Returns

  • *Handler: The same handler instance for chaining.

Usage Examples

Basic Error Handling

funcreadFile(filenamestring)[]byte{defernice.Tackle(reflect.TypeFor[os.PathError](),io.EOF,).With(func(errany){log.Printf("File operation failed: %v",err)})file:=mustOpen(filename)deferfile.Close()data:=mustReadAll(file)returndata}funcmustOpen(filenamestring)*os.File{file,err:=os.Open(filename)iferr!=nil{panic(err)}returnfile}

Multiple Error Types

funcprocessRequest(req*Request)*Response{defernice.Tackle(ErrInvalidInput,ErrUnauthorized,ErrDatabaseConnection,).With(func(errany){switcherr{caseErrInvalidInput:respondWithError(400,"Invalid input")caseErrUnauthorized:respondWithError(401,"Unauthorized")caseErrDatabaseConnection:respondWithError(500,"Database error")}})validateInput(req)authenticateUser(req)returnprocessData(req)}

Custom Error Types

typeValidationErrorstruct{FieldstringMessagestring}func(e*ValidationError)Error()string{returnfmt.Sprintf("validation error on field %s: %s",e.Field,e.Message)}funcvalidateForm(datamap[string]string){defernice.Tackle(reflect.TypeFor[ValidationError](),).With(func(errany){ifve,ok:=err.(ValidationError);ok{log.Printf("Validation failed: field=%s, msg=%s",ve.Field,ve.Message)}})ifdata["email"]==""{panic(&ValidationError{Field:"email",Message:"required"})}if!isValidEmail(data["email"]){panic(&ValidationError{Field:"email",Message:"invalid format"})}}

Multiple Handlers

funccomplexOperation(){// First handler for database errorsdefernice.Tackle(reflect.TypeFor[DBError]()),ErrConnectionLost,).With(func(errany){log.Error("Database error:",err)notifyOps(err)})// Second handler for business logic errorsdefernice.Tackle(ErrInsufficientFunds,ErrAccountLocked,).With(func(errany){log.Warn("Business error:",err)auditLog(err)})// Operations that might panic with various errorsperformDatabaseOperation()performBusinessLogic()}

Best Practices

1. Use Specific Error Types

Register specific error types rather than catching all panics:

// Use casedefernice.Tackle(ErrSpecificError).With(handler)// Replace this use casedeferfunc(){ifr:=recover();r!=nil{// Catches everything}}()

2. Define Clear Error Variables

Create well-named error variables for different failure scenarios:

var(ErrInvalidConfig=errors.New("invalid configuration")ErrServiceUnavailable=errors.New("service unavailable")ErrRateLimitExceeded=errors.New("rate limit exceeded"))

3. Place Handlers at Appropriate Levels

Put error handlers at logical boundaries in your application:

funchttpHandler(whttp.ResponseWriter,r*http.Request){defernice.Tackle(ErrBadRequest,ErrUnauthorized,ErrServerError,).With(func(errany){respondWithAppropriateError(w,err)})// Request processing logic}

4. Use for Fail-Fast Scenarios

Nice is ideal for scenarios where you want to stop execution immediately on error:

funcinitializeApp(){defernice.Tackle(ErrConfigError).With(func(errany){log.Fatal("Failed to initialize:",err)})loadConfig()// panic on errorconnectDB()// panic on errorstartServices()// panic on error}

Performance Considerations

Nice uses Go's panic, recover, and reflection mechanisms, which have performance implications:

  • Reflection Cost: Type checking uses reflection, which adds overhead
  • Panic/Recover Cost: These operations are more expensive than regular error returns
  • Best Use Cases:
    • Server-side request handlers
    • Initialization code
    • Batch processing
    • Any scenario where code clarity outweighs microsecond-level performance

For performance-critical code paths (e.g., tight loops, real-time systems), consider using traditional error handling.

Design Philosophy

Nice embraces the idea that panic and recover are legitimate Go features that can be used effectively when applied appropriately. The library aims to:

  1. Reduce Boilerplate: Minimize repetitive error checking code
  2. Improve Readability: Make the happy path more apparent
  3. Centralize Handling: Handle related errors in one place
  4. Maintain Go Idioms: Work within Go's design principles

Nice is NOT trying to turn Go into Java or Python. It's providing an alternative pattern that can coexist with traditional Go error handling.

When to Use Nice

Good Use Cases:

  • Web request handlers
  • CLI applications
  • Service initialization
  • Batch processing
  • Prototyping
  • Any code where fail-fast behavior is desired

Avoid Using Nice For:

  • Library code (return errors instead)
  • Performance-critical paths
  • Goroutines (unless carefully managed)
  • Code that needs fine-grained error handling

Contributing

We welcome contributions! Please submit pull request for any contribution and suggestion.

To contribute:

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please ensure your code:

  • Includes tests
  • Follows Go conventions
  • Updates documentation as needed

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Inspired by exception handling patterns in other languages
  • Built for the Go community as an experiment in alternative error handling approaches
  • Thanks to all contributors who help improve this library