- Go 100%
|
Antony Ho
121afbab6d
* 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> |
||
|---|---|---|
| .github/workflows | Add GitHub Actions for repo sync ( #10 ) | |
| go.mod | Initial stage ready | |
| go.sum | Initial stage ready | |
| handle_test.go | Set //nolint to intended test code | |
| LICENSE | Initial commit | |
| nice.go | Improve case coverage ( #8 ) | |
| README.md | Update to a comprehensive readme | |
| tackle_test.go | Improve case coverage ( #8 ) | |
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
- Features
- Installation
- Quick Start
- API Reference
- Usage Examples
- Best Practices
- Performance Considerations
- Design Philosophy
- Contributing
- License
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)HandlerParameters
artefacts: One or more error values, error types, orreflect.Typevalues 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:
- Reduce Boilerplate: Minimize repetitive error checking code
- Improve Readability: Make the happy path more apparent
- Centralize Handling: Handle related errors in one place
- 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:
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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