diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ee7d6a5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/README.md b/README.md index 707195f..4a3ea40 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ 1. Format all code with `black`. -1. Put all code in functions. If you are writing a script, put the script in a - function called `main` and call `main()` at the end of the script using - the standard invocation: +2. Put all code in functions. If you are writing a script, put the script + in a function called `main` and call `main()` at the end of the script + using the standard invocation: ```python if __name__ == "__main__": @@ -15,23 +15,23 @@ ## Golang -1. Any project that has more than 2 or 3 modules should use the `uber/fx` DI - framework to keep things tidy. +1. Any project that has more than 2 or 3 modules should use the `uber/fx` DI + framework to keep things tidy. -1. Obviously, don't commit anything that hasn't been `go fmt`'d. The only - exception is when committing things that aren't yet syntactically valid, - which should only happen pre-v0.0.1 or on a non-main branch. +2. Don't commit anything that hasn't been `go fmt`'d. The only exception is + when committing things that aren't yet syntactically valid, which should + only happen pre-v0.0.1 or on a non-main branch. -1. Even if you are planning to deal with only positive integers, use - `int`/`int64` types instead of `uint`/`uint64` types. This is for - consistency and compatibility with the standard library; it's better than - casting all the time. +3. Even if you are planning to deal with only positive integers, use + `int`/`int64` types instead of `uint`/`uint64` types. This is for + consistency and compatibility with the standard library; it's better + than casting all the time. -1. Try to use zerolog for logging. It's fast and has a nice API. For - smaller/quick projects, the standard library's `log` package (and specifically - `log/slog`) is fine. In that case, log structured logs whenever - possible, and import `sneak.berlin/go/simplelog` to configure it - appropriately. Example: +4. Try to use zerolog for logging. It's fast and has a nice API. For + smaller/quick projects, the standard library's `log` package (and + specifically `log/slog`) is fine. In that case, log structured logs + whenever possible, and import `sneak.berlin/go/simplelog` to configure + it appropriately. Example: ```go package main @@ -46,79 +46,79 @@ } ``` -1. Write at least a single test to check compilation. The test file can be - empty, but it should exist. This is to ensure that `go test ./...` will - always function as a syntax check at a minimum. +5. Write at least a single test to check compilation. The test file can be + empty, but it should exist. This is to ensure that `go test ./...` will + always function as a syntax check at a minimum. -1. For anything beyond a simple script or tool, or anything that is going to - run in any sort of "production" anywhere, make sure it passes - `golangci-lint`. +6. For anything beyond a simple script or tool, or anything that is going to + run in any sort of "production" anywhere, make sure it passes + `golangci-lint`. -1. Write a `Dockerfile` for every repo, even if it only runs the tests and - linting. `docker build .` should always make sure that the code is in an - able-to-be-compiled state, linted, and any tests run. The Docker build - should fail if linting doesn't pass. +7. Write a `Dockerfile` for every repo, even if it only runs the tests and + linting. `docker build .` should always make sure that the code is in an + able-to-be-compiled state, linted, and any tests run. The Docker build + should fail if linting doesn't pass. -1. Include a `Makefile` with targets for at least `clean` and `test`. If - there are multiple binaries, include a target for each binary. If there - are multiple binaries, include a target for `all` that builds all - binaries. +8. Include a `Makefile` with targets for at least `clean` and `test`. If + there are multiple binaries, include a target for each binary. If there + are multiple binaries, include a target for `all` that builds all + binaries. -1. If you are writing a single-module library, `.go` files are okay in the - repo root. +9. If you are writing a single-module library, `.go` files are okay in the + repo root. -1. If you are writing a multi-module project, put all `.go` files in a +10. If you are writing a multi-module project, put all `.go` files in a `pkg/` or `internal/` directory. This is to keep the root clean and to make it easier to see what is a library and what is a binary. -1. Binaries go in `cmd/` directories. Each binary should have its own +11. Binaries go in `cmd/` directories. Each binary should have its own directory. This is to keep the root clean and to make it easier to see what is a library and what is a binary. Only package `main` files should be in `cmd/*` directories. -1. Keep the `main()` function as small as possible. +12. Keep the `main()` function as small as possible. -1. Keep the `main` package as small as possible. Move as much code as is +13. Keep the `main` package as small as possible. Move as much code as is feasible to a library package. -1. HTTP HandleFuncs should be returned from methods or functions that need +14. HTTP HandleFuncs should be returned from methods or functions that need to handle HTTP requests. Don't use methods or our top level functions as handlers. -1. Provide a .gitignore file that ignores at least `*.log`, `*.out`, and +15. Provide a .gitignore file that ignores at least `*.log`, `*.out`, and `*.test` files, as well as any binaries. -1. Constructors should be called `New()` whenever possible. +16. Constructors should be called `New()` whenever possible. `modulename.New()` works great if you name the packages properly. -1. Don't make packages too big. Break them up. +17. Don't make packages too big. Break them up. -1. Don't make functions or methods too big. Break them up. +18. Don't make functions or methods too big. Break them up. -1. Use descriptive names for functions and methods. Don't be afraid to make - them a bit long. +19. Use descriptive names for functions and methods. Don't be afraid to + make them a bit long. -1. Use descriptive names for modules and filenames. Avoid generic names +20. Use descriptive names for modules and filenames. Avoid generic names like `server`. `util` is banned. -1. Constructors should take a Params struct if they need more than 1-2 +21. Constructors should take a Params struct if they need more than 1-2 arguments. Positional arguments are an endless source of bugs and should be avoided whenever possible. -1. Use `context.Context` for all functions that need it. If you don't need +22. Use `context.Context` for all functions that need it. If you don't need it, you can pass `context.Background()`. Anything long-running should get and abide by a Context. A context does not count against your number of function or method arguments for purposes of calculating whether or not you need a Params struct, because the `ctx` is always first. -1. Contexts are always named `ctx`. +23. Contexts are always named `ctx`. -1. Use `context.WithTimeout` or `context.WithDeadline` for any function +24. Use `context.WithTimeout` or `context.WithDeadline` for any function that could potentially run for a long time. This is especially true for any function that makes a network call. Sane timeouts are essential. -1. Avoid global state, especially global variables. If you need to store +25. Avoid global state, especially global variables. If you need to store state that is global to your launch or application instance, use a package `globals` or `appstate` with a struct and a constructor and require it as a dependency in your constructors. This will allow @@ -127,10 +127,10 @@ graph allows for it, put it in the main struct/object of your application, but remember that this harms testability. -1. Package-global "variables" are ok if they are constants, such as static +26. Package-global "variables" are ok if they are constants, such as static strings or integers or errors. -1. Whenever possible, avoid hardcoding numbers or values in your code. Use +27. Whenever possible, avoid hardcoding numbers or values in your code. Use descriptively-named constants instead. Recall the famous SICP quote: "Programs must be written for people to read, and only incidentally for machines to execute." Rather than comments, a descriptive constant name @@ -149,143 +149,143 @@ } ``` -1. Define your struct types near their constructors. +28. Define your struct types near their constructors. -1. Define your interface types near the functions that use them, or if you +29. Define your interface types near the functions that use them, or if you have multiple conformant types, put the interface(s) in their own file. -1. Define errors as package-level variables. Use a descriptive name for the +30. Define errors as package-level variables. Use a descriptive name for the error. Use `errors.New` to create the error. If you need to include additional information in the error, use a struct that implements the `error` interface. -1. Use lowerCamelCase for local function/variable names. Use UpperCamelCase for type - names, and exported function/variable names. Use snake_case for json keys. Use - lowercase for filenames. +31. Use lowerCamelCase for local function/variable names. Use UpperCamelCase + for type names, and exported function/variable names. Use snake_case for + JSON keys. Use lowercase for filenames. -1. Explicitly specify UTC for datetimes unless you have a very good reason +32. Explicitly specify UTC for datetimes unless you have a very good reason not to. Use `time.Now().UTC()` to get the current time in UTC. -1. String dates should always be ISO8601 formatted. Use `time.Time.Format` +33. String dates should always be ISO8601 formatted. Use `time.Time.Format` with `time.RFC3339` to get the correct format. -1. Use `time.Time` for all date and time values. Do not use `int64` or +34. Use `time.Time` for all date and time values. Do not use `int64` or `string` for dates or times internally. -1. When using `time.Time` in a struct, use a pointer to `time.Time` so that +35. When using `time.Time` in a struct, use a pointer to `time.Time` so that you can differentiate between a zero value and a null value. -1. Use `time.Duration` for all time durations. Do not use `int64` or +36. Use `time.Duration` for all time durations. Do not use `int64` or `string` for durations internally. -1. When using `time.Duration` in a struct, use a pointer to `time.Duration` +37. When using `time.Duration` in a struct, use a pointer to `time.Duration` so that you can differentiate between a zero value and a null value. -1. Whenever possible, in argument types and return types, try to use +38. Whenever possible, in argument types and return types, try to use standard library interfaces instead of concrete types. For example, use `io.Reader` instead of `*os.File`. Tailor these to the needs of the specific function or method. Examples: - 1. **`io.Reader`** instead of `*os.File`: + 1. **`io.Reader`** instead of `*os.File`: - - `io.Reader` is a common interface for reading data, which can be - implemented by many types, including `*os.File`, `bytes.Buffer`, - `strings.Reader`, and network connections like `net.Conn`. + - `io.Reader` is a common interface for reading data, which can be + implemented by many types, including `*os.File`, `bytes.Buffer`, + `strings.Reader`, and network connections like `net.Conn`. - 2. **`io.Writer`** instead of `*os.File` or `*bytes.Buffer`: + 2. **`io.Writer`** instead of `*os.File` or `*bytes.Buffer`: - - `io.Writer` is used for writing data. It can be implemented by - `*os.File`, `bytes.Buffer`, `net.Conn`, and more. + - `io.Writer` is used for writing data. It can be implemented by + `*os.File`, `bytes.Buffer`, `net.Conn`, and more. - 3. **`io.ReadWriter`** instead of `*os.File`: + 3. **`io.ReadWriter`** instead of `*os.File`: - - `io.ReadWriter` combines `io.Reader` and `io.Writer`. It is often - used for types that can both read and write, such as `*os.File` - and `net.Conn`. + - `io.ReadWriter` combines `io.Reader` and `io.Writer`. It is often + used for types that can both read and write, such as `*os.File` + and `net.Conn`. - 4. **`io.Closer`** instead of `*os.File` or `*net.Conn`: + 4. **`io.Closer`** instead of `*os.File` or `*net.Conn`: - - `io.Closer` is used for types that need to be closed, including - `*os.File`, `net.Conn`, and other resources that require cleanup. + - `io.Closer` is used for types that need to be closed, including + `*os.File`, `net.Conn`, and other resources that require cleanup. - 5. **`io.ReadCloser`** instead of `*os.File` or `http.Response.Body`: + 5. **`io.ReadCloser`** instead of `*os.File` or `http.Response.Body`: - - `io.ReadCloser` combines `io.Reader` and `io.Closer`, and is - commonly used for types like `*os.File` and `http.Response.Body`. + - `io.ReadCloser` combines `io.Reader` and `io.Closer`, and is + commonly used for types like `*os.File` and `http.Response.Body`. - 6. **`io.WriteCloser`** instead of `*os.File` or `*gzip.Writer`: + 6. **`io.WriteCloser`** instead of `*os.File` or `*gzip.Writer`: - - `io.WriteCloser` combines `io.Writer` and `io.Closer`. It is used - for types like `*os.File` and `gzip.Writer`. + - `io.WriteCloser` combines `io.Writer` and `io.Closer`. It is used + for types like `*os.File` and `gzip.Writer`. - 7. **`io.ReadWriteCloser`** instead of `*os.File` or `*net.TCPConn`: + 7. **`io.ReadWriteCloser`** instead of `*os.File` or `*net.TCPConn`: - - `io.ReadWriteCloser` combines `io.Reader`, `io.Writer`, and - `io.Closer`. Examples include `*os.File` and `net.TCPConn`. + - `io.ReadWriteCloser` combines `io.Reader`, `io.Writer`, and + `io.Closer`. Examples include `*os.File` and `net.TCPConn`. - 8. **`fmt.Stringer`** instead of implementing a custom `String` method: + 8. **`fmt.Stringer`** instead of implementing a custom `String` method: - - `fmt.Stringer` is an interface for types that can convert - themselves to a string. Any type that implements the `String() - string` method satisfies this interface. + - `fmt.Stringer` is an interface for types that can convert + themselves to a string. Any type that implements the `String() +string` method satisfies this interface. - 9. **`error`** instead of custom error types: + 9. **`error`** instead of custom error types: - - The `error` interface is used for representing errors. Instead of - defining custom error types, you can use the `errors.New` - function or the `fmt.Errorf` function to create errors. + - The `error` interface is used for representing errors. Instead of + defining custom error types, you can use the `errors.New` + function or the `fmt.Errorf` function to create errors. - 10. **`net.Conn`** instead of `*net.TCPConn` or `*net.UDPConn`: + 10. **`net.Conn`** instead of `*net.TCPConn` or `*net.UDPConn`: - - `net.Conn` is a generic network connection interface that can be - implemented by TCP, UDP, and other types of network connections. + - `net.Conn` is a generic network connection interface that can be + implemented by TCP, UDP, and other types of network connections. - 11. **`http.Handler`** instead of custom HTTP handlers: + 11. **`http.Handler`** instead of custom HTTP handlers: - - `http.Handler` is an interface for handling HTTP requests. - Instead of creating custom handler types, you can use types that - implement the `ServeHTTP(http.ResponseWriter, *http.Request)` - method. + - `http.Handler` is an interface for handling HTTP requests. + Instead of creating custom handler types, you can use types that + implement the `ServeHTTP(http.ResponseWriter, *http.Request)` + method. - 12. **`http.HandlerFunc`** instead of creating a new type: + 12. **`http.HandlerFunc`** instead of creating a new type: - - `http.HandlerFunc` is a type that allows you to use functions as - HTTP handlers by implementing the `http.Handler` interface. + - `http.HandlerFunc` is a type that allows you to use functions as + HTTP handlers by implementing the `http.Handler` interface. - 13. **`encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler`** - instead of custom marshal/unmarshal methods: + 13. **`encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler`** + instead of custom marshal/unmarshal methods: - - These interfaces are used for binary serialization and - deserialization. Implementing these interfaces allows types to - be encoded and decoded in a standard way. + - These interfaces are used for binary serialization and + deserialization. Implementing these interfaces allows types to + be encoded and decoded in a standard way. - 14. **`encoding.TextMarshaler` and `encoding.TextUnmarshaler`** instead - of custom text marshal/unmarshal methods: + 14. **`encoding.TextMarshaler` and `encoding.TextUnmarshaler`** instead + of custom text marshal/unmarshal methods: - - These interfaces are used for text-based serialization and - deserialization. They are useful for types that need to be - represented as text. + - These interfaces are used for text-based serialization and + deserialization. They are useful for types that need to be + represented as text. - 15. **`sort.Interface`** instead of custom sorting logic: + 15. **`sort.Interface`** instead of custom sorting logic: - - `sort.Interface` is an interface for sorting collections. By - implementing the `Len`, `Less`, and `Swap` methods, you can sort - any collection using the `sort.Sort` function. + - `sort.Interface` is an interface for sorting collections. By + implementing the `Len`, `Less`, and `Swap` methods, you can sort + any collection using the `sort.Sort` function. - 16. **`flag.Value`** instead of custom flag parsing: - - `flag.Value` is an interface for defining custom command-line - flags. Implementing the `String` and `Set` methods allows you to - use custom types with the `flag` package. + 16. **`flag.Value`** instead of custom flag parsing: + - `flag.Value` is an interface for defining custom command-line + flags. Implementing the `String` and `Set` methods allows you to + use custom types with the `flag` package. -1. Avoid using `panic` in library code. Instead, return errors to allow +39. Avoid using `panic` in library code. Instead, return errors to allow the caller to handle them. Reserve `panic` for truly exceptional conditions. -1. Use `defer` to ensure resources are properly cleaned up, such as closing - files or network connections. Place `defer` statements immediately after - resource acquisition. +40. Use `defer` to ensure resources are properly cleaned up, such as + closing files or network connections. Place `defer` statements + immediately after resource acquisition. -1. When calling a function with `go`, wrap the function call in an +41. When calling a function with `go`, wrap the function call in an anonymous function to ensure it runs in the new goroutine context: Right: @@ -302,21 +302,21 @@ go someFunction(arg1, arg2) ``` -1. Use `iota` to define enumerations in a type-safe way. This ensures that - the constants are properly grouped and reduces the risk of errors. +42. Use `iota` to define enumerations in a type-safe way. This ensures that + the constants are properly grouped and reduces the risk of errors. -1. Don't hardcode big lists in your code. Either isolate lists in their own - module/package, or use a third party library. For example, if you need a - list of country codes, you can use - [https://github.com/emvi/iso-639-1](https://github.com/emvi/iso-639-1). +43. Don't hardcode big lists in your code. Either isolate lists in their own + module/package, or use a third party library. For example, if you need a + list of country codes, you can use + [https://github.com/emvi/iso-639-1](https://github.com/emvi/iso-639-1). -1. When storing numeric values that represent a number of units, either - include the unit in the name, or use a type alias, or use a 3p library - such as - [github.com/alecthomas/units)(https://github.com/alecthomas/units) for - SI/IEC byte units, or - [github.com/bcicen/go-units](https://github.com/bcicen/go-units) for - temperatures (and others). The type system is your friend, use it. +44. When storing numeric values that represent a number of units, either + include the unit in the name, or use a type alias, or use a 3p library + such as + [github.com/alecthomas/units](https://github.com/alecthomas/units) for + SI/IEC byte units, or + [github.com/bcicen/go-units](https://github.com/bcicen/go-units) for + temperatures (and others). The type system is your friend, use it. ## Other Golang Tips and Best Practices (Optional) @@ -324,14 +324,14 @@ write-only (`chan<-`) channels to communicate the direction of data flow clearly. -1. Use `io.MultiReader` to concatenate multiple readers and +2. Use `io.MultiReader` to concatenate multiple readers and `io.MultiWriter` to duplicate writes to multiple writers. This can simplify the handling of multiple data sources or destinations. -1. For simple counters and flags, use the `sync/atomic` package to avoid the - overhead of mutexes. +3. For simple counters and flags, use the `sync/atomic` package to avoid + the overhead of mutexes. -1. When using mutexes, minimize the scope of locking to reduce contention +4. When using mutexes, minimize the scope of locking to reduce contention and potential deadlocks. Prefer to lock only the critical sections of code. Try to encapsulate the critical section in its own function or method. Acquire the lock as the first line of the function, defer @@ -340,60 +340,72 @@ using mutexes in the middle of a function. In short, build atomic functions. -1. Design types to be immutable where possible. This can help avoid issues +5. Design types to be immutable where possible. This can help avoid issues with concurrent access and make the code easier to reason about. -1. Global state can lead to unpredictable behavior and makes the code +6. Global state can lead to unpredictable behavior and makes the code harder to test. Use dependency injection to manage state. -1. Avoid using `init` functions unless absolutely necessary as they can +7. Avoid using `init` functions unless absolutely necessary as they can lead to unpredictable initialization order and make the code harder to understand. -1. Provide comments for all public interfaces explaining what they do and +8. Provide comments for all public interfaces explaining what they do and how they should be used. This helps other developers understand the intended use. -1. Be mindful of resource leaks when using `time.Timer` and `time.Ticker`. +9. Be mindful of resource leaks when using `time.Timer` and `time.Ticker`. Always stop them when they are no longer needed. -1. Use `sync.Pool` to manage a pool of reusable objects, which can help - reduce GC overhead and improve performance in high-throughput scenarios. +10. Use `sync.Pool` to manage a pool of reusable objects, which can help + reduce GC overhead and improve performance in high-throughput scenarios. -1. Avoid using large buffer sizes for channels. Unbounded channels can lead - to memory leaks. Use appropriate buffer sizes based on the application's - needs. +11. Avoid using large buffer sizes for channels. Unbounded channels can + lead to memory leaks. Use appropriate buffer sizes based on the + application's needs. -1. Always handle the case where a channel might be closed. This prevents - panic and ensures graceful shutdowns. +12. Always handle the case where a channel might be closed. This prevents + panic and ensures graceful shutdowns. -1. For small structs, use value receivers to avoid unnecessary heap - allocations. Use pointer receivers for large structs or when mutating - the receiver. +13. For small structs, use value receivers to avoid unnecessary heap + allocations. Use pointer receivers for large structs or when mutating + the receiver. -1. Only use goroutines when necessary. Excessive goroutines can lead to high - memory consumption and increased complexity. +14. Only use goroutines when necessary. Excessive goroutines can lead to + high memory consumption and increased complexity. -1. Use `sync.Cond` for more complex synchronization needs that cannot be - met with simple mutexes and channels. +15. Use `sync.Cond` for more complex synchronization needs that cannot be + met with simple mutexes and channels. -1. Reflection is powerful but should be used sparingly as it can lead to - code that is hard to understand and maintain. Prefer type-safe - solutions. +16. Reflection is powerful but should be used sparingly as it can lead to + code that is hard to understand and maintain. Prefer type-safe + solutions. -1. Avoid storing large or complex data in context. Context should be used - for request-scoped values like deadlines, cancellation signals, and - authentication tokens. +17. Avoid storing large or complex data in context. Context should be used + for request-scoped values like deadlines, cancellation signals, and + authentication tokens. -1. Use `runtime.Callers` and `runtime.CallersFrames` to capture stack traces - for debugging and logging purposes. +18. Use `runtime.Callers` and `runtime.CallersFrames` to capture stack + traces for debugging and logging purposes. -1. Use the `testing.TB` interface to write helper functions that can be - used with both `*testing.T` and `*testing.B`. +19. Use the `testing.TB` interface to write helper functions that can be + used with both `*testing.T` and `*testing.B`. -1. Use struct embedding to reuse code across multiple structs. This is a - form of composition that can simplify code reuse. +20. Use struct embedding to reuse code across multiple structs. This is a + form of composition that can simplify code reuse. -1. Prefer defining explicit interfaces in your packages rather than relying - on implicit interfaces. This makes the intended use of interfaces clearer - and the code more maintainable. +21. Prefer defining explicit interfaces in your packages rather than + relying on implicit interfaces. This makes the intended use of + interfaces clearer and the code more maintainable. + +# Author + +[@sneak](https://sneak.berlin) <[sneak@sneak.berlin](mailto:sneak@sneak.berlin)> + +# License + +[WTFPL](./LICENSE) + +Do with it what you will. There is no warranty, express or implied, +including but not limited to merchantability or fitness for a particular +purpose. Use at your own risk.