add license, gpt copyedit/format

This commit is contained in:
Jeffrey Paul 2024-06-10 04:06:57 -07:00
parent 9a396ff8d9
commit d11270ff9d
2 changed files with 207 additions and 181 deletions

14
LICENSE Normal file
View File

@ -0,0 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
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.

374
README.md
View File

@ -4,9 +4,9 @@
1. Format all code with `black`. 1. Format all code with `black`.
1. Put all code in functions. If you are writing a script, put the script in a 2. Put all code in functions. If you are writing a script, put the script
function called `main` and call `main()` at the end of the script using in a function called `main` and call `main()` at the end of the script
the standard invocation: using the standard invocation:
```python ```python
if __name__ == "__main__": if __name__ == "__main__":
@ -15,23 +15,23 @@
## Golang ## Golang
1. Any project that has more than 2 or 3 modules should use the `uber/fx` DI 1. Any project that has more than 2 or 3 modules should use the `uber/fx` DI
framework to keep things tidy. framework to keep things tidy.
1. Obviously, don't commit anything that hasn't been `go fmt`'d. The only 2. Don't commit anything that hasn't been `go fmt`'d. The only exception is
exception is when committing things that aren't yet syntactically valid, when committing things that aren't yet syntactically valid, which should
which should only happen pre-v0.0.1 or on a non-main branch. 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 3. Even if you are planning to deal with only positive integers, use
`int`/`int64` types instead of `uint`/`uint64` types. This is for `int`/`int64` types instead of `uint`/`uint64` types. This is for
consistency and compatibility with the standard library; it's better than consistency and compatibility with the standard library; it's better
casting all the time. than casting all the time.
1. Try to use zerolog for logging. It's fast and has a nice API. For 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 smaller/quick projects, the standard library's `log` package (and
`log/slog`) is fine. In that case, log structured logs whenever specifically `log/slog`) is fine. In that case, log structured logs
possible, and import `sneak.berlin/go/simplelog` to configure it whenever possible, and import `sneak.berlin/go/simplelog` to configure
appropriately. Example: it appropriately. Example:
```go ```go
package main package main
@ -46,79 +46,79 @@
} }
``` ```
1. Write at least a single test to check compilation. The test file can be 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 empty, but it should exist. This is to ensure that `go test ./...` will
always function as a syntax check at a minimum. always function as a syntax check at a minimum.
1. For anything beyond a simple script or tool, or anything that is going to 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 run in any sort of "production" anywhere, make sure it passes
`golangci-lint`. `golangci-lint`.
1. Write a `Dockerfile` for every repo, even if it only runs the tests and 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 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 able-to-be-compiled state, linted, and any tests run. The Docker build
should fail if linting doesn't pass. should fail if linting doesn't pass.
1. Include a `Makefile` with targets for at least `clean` and `test`. If 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 there are multiple binaries, include a target for each binary. If there
are multiple binaries, include a target for `all` that builds all are multiple binaries, include a target for `all` that builds all
binaries. binaries.
1. If you are writing a single-module library, `.go` files are okay in the 9. If you are writing a single-module library, `.go` files are okay in the
repo root. 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 `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. 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 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 what is a library and what is a binary. Only package `main` files
should be in `cmd/*` directories. 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. 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 to handle HTTP requests. Don't use methods or our top level functions
as handlers. 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. `*.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. `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 19. Use descriptive names for functions and methods. Don't be afraid to
them a bit long. 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. 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 arguments. Positional arguments are an endless source of bugs and
should be avoided whenever possible. 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 it, you can pass `context.Background()`. Anything long-running should
get and abide by a Context. A context does not count against your get and abide by a Context. A context does not count against your
number of function or method arguments for purposes of calculating number of function or method arguments for purposes of calculating
whether or not you need a Params struct, because the `ctx` is always whether or not you need a Params struct, because the `ctx` is always
first. 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 that could potentially run for a long time. This is especially true for
any function that makes a network call. Sane timeouts are essential. 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 state that is global to your launch or application instance, use a
package `globals` or `appstate` with a struct and a constructor and package `globals` or `appstate` with a struct and a constructor and
require it as a dependency in your constructors. This will allow 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 graph allows for it, put it in the main struct/object of your
application, but remember that this harms testability. 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. 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: descriptively-named constants instead. Recall the famous SICP quote:
"Programs must be written for people to read, and only incidentally for "Programs must be written for people to read, and only incidentally for
machines to execute." Rather than comments, a descriptive constant name 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. 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 error. Use `errors.New` to create the error. If you need to include
additional information in the error, use a struct that implements the additional information in the error, use a struct that implements the
`error` interface. `error` interface.
1. Use lowerCamelCase for local function/variable names. Use UpperCamelCase for type 31. Use lowerCamelCase for local function/variable names. Use UpperCamelCase
names, and exported function/variable names. Use snake_case for json keys. Use for type names, and exported function/variable names. Use snake_case for
lowercase for filenames. 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. 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. 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. `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. 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. `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. 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 standard library interfaces instead of concrete types. For example, use
`io.Reader` instead of `*os.File`. Tailor these to the needs of the `io.Reader` instead of `*os.File`. Tailor these to the needs of the
specific function or method. Examples: 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 - `io.Reader` is a common interface for reading data, which can be
implemented by many types, including `*os.File`, `bytes.Buffer`, implemented by many types, including `*os.File`, `bytes.Buffer`,
`strings.Reader`, and network connections like `net.Conn`. `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 - `io.Writer` is used for writing data. It can be implemented by
`*os.File`, `bytes.Buffer`, `net.Conn`, and more. `*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 - `io.ReadWriter` combines `io.Reader` and `io.Writer`. It is often
used for types that can both read and write, such as `*os.File` used for types that can both read and write, such as `*os.File`
and `net.Conn`. 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 - `io.Closer` is used for types that need to be closed, including
`*os.File`, `net.Conn`, and other resources that require cleanup. `*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 - `io.ReadCloser` combines `io.Reader` and `io.Closer`, and is
commonly used for types like `*os.File` and `http.Response.Body`. 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 - `io.WriteCloser` combines `io.Writer` and `io.Closer`. It is used
for types like `*os.File` and `gzip.Writer`. 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.ReadWriteCloser` combines `io.Reader`, `io.Writer`, and
`io.Closer`. Examples include `*os.File` and `net.TCPConn`. `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 - `fmt.Stringer` is an interface for types that can convert
themselves to a string. Any type that implements the `String() themselves to a string. Any type that implements the `String()
string` method satisfies this interface. 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 - The `error` interface is used for representing errors. Instead of
defining custom error types, you can use the `errors.New` defining custom error types, you can use the `errors.New`
function or the `fmt.Errorf` function to create errors. 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 - `net.Conn` is a generic network connection interface that can be
implemented by TCP, UDP, and other types of network connections. 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. - `http.Handler` is an interface for handling HTTP requests.
Instead of creating custom handler types, you can use types that Instead of creating custom handler types, you can use types that
implement the `ServeHTTP(http.ResponseWriter, *http.Request)` implement the `ServeHTTP(http.ResponseWriter, *http.Request)`
method. 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.HandlerFunc` is a type that allows you to use functions as
HTTP handlers by implementing the `http.Handler` interface. HTTP handlers by implementing the `http.Handler` interface.
13. **`encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler`** 13. **`encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler`**
instead of custom marshal/unmarshal methods: instead of custom marshal/unmarshal methods:
- These interfaces are used for binary serialization and - These interfaces are used for binary serialization and
deserialization. Implementing these interfaces allows types to deserialization. Implementing these interfaces allows types to
be encoded and decoded in a standard way. be encoded and decoded in a standard way.
14. **`encoding.TextMarshaler` and `encoding.TextUnmarshaler`** instead 14. **`encoding.TextMarshaler` and `encoding.TextUnmarshaler`** instead
of custom text marshal/unmarshal methods: of custom text marshal/unmarshal methods:
- These interfaces are used for text-based serialization and - These interfaces are used for text-based serialization and
deserialization. They are useful for types that need to be deserialization. They are useful for types that need to be
represented as text. 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 - `sort.Interface` is an interface for sorting collections. By
implementing the `Len`, `Less`, and `Swap` methods, you can sort implementing the `Len`, `Less`, and `Swap` methods, you can sort
any collection using the `sort.Sort` function. any collection using the `sort.Sort` function.
16. **`flag.Value`** instead of custom flag parsing: 16. **`flag.Value`** instead of custom flag parsing:
- `flag.Value` is an interface for defining custom command-line - `flag.Value` is an interface for defining custom command-line
flags. Implementing the `String` and `Set` methods allows you to flags. Implementing the `String` and `Set` methods allows you to
use custom types with the `flag` package. 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 the caller to handle them. Reserve `panic` for truly exceptional
conditions. conditions.
1. Use `defer` to ensure resources are properly cleaned up, such as closing 40. Use `defer` to ensure resources are properly cleaned up, such as
files or network connections. Place `defer` statements immediately after closing files or network connections. Place `defer` statements
resource acquisition. 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: anonymous function to ensure it runs in the new goroutine context:
Right: Right:
@ -302,21 +302,21 @@
go someFunction(arg1, arg2) go someFunction(arg1, arg2)
``` ```
1. Use `iota` to define enumerations in a type-safe way. This ensures that 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. 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 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 module/package, or use a third party library. For example, if you need a
list of country codes, you can use list of country codes, you can use
[https://github.com/emvi/iso-639-1](https://github.com/emvi/iso-639-1). [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 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 include the unit in the name, or use a type alias, or use a 3p library
such as such as
[github.com/alecthomas/units)(https://github.com/alecthomas/units) for [github.com/alecthomas/units](https://github.com/alecthomas/units) for
SI/IEC byte units, or SI/IEC byte units, or
[github.com/bcicen/go-units](https://github.com/bcicen/go-units) for [github.com/bcicen/go-units](https://github.com/bcicen/go-units) for
temperatures (and others). The type system is your friend, use it. temperatures (and others). The type system is your friend, use it.
## Other Golang Tips and Best Practices (Optional) ## Other Golang Tips and Best Practices (Optional)
@ -324,14 +324,14 @@
write-only (`chan<-`) channels to communicate the direction of data flow write-only (`chan<-`) channels to communicate the direction of data flow
clearly. 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 `io.MultiWriter` to duplicate writes to multiple writers. This can
simplify the handling of multiple data sources or destinations. simplify the handling of multiple data sources or destinations.
1. For simple counters and flags, use the `sync/atomic` package to avoid the 3. For simple counters and flags, use the `sync/atomic` package to avoid
overhead of mutexes. 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 and potential deadlocks. Prefer to lock only the critical sections of
code. Try to encapsulate the critical section in its own function or code. Try to encapsulate the critical section in its own function or
method. Acquire the lock as the first line of the function, defer 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 using mutexes in the middle of a function. In short, build atomic
functions. 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. 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. 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 lead to unpredictable initialization order and make the code harder to
understand. 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 how they should be used. This helps other developers understand the
intended use. 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. Always stop them when they are no longer needed.
1. Use `sync.Pool` to manage a pool of reusable objects, which can help 10. Use `sync.Pool` to manage a pool of reusable objects, which can help
reduce GC overhead and improve performance in high-throughput scenarios. reduce GC overhead and improve performance in high-throughput scenarios.
1. Avoid using large buffer sizes for channels. Unbounded channels can lead 11. Avoid using large buffer sizes for channels. Unbounded channels can
to memory leaks. Use appropriate buffer sizes based on the application's lead to memory leaks. Use appropriate buffer sizes based on the
needs. application's needs.
1. Always handle the case where a channel might be closed. This prevents 12. Always handle the case where a channel might be closed. This prevents
panic and ensures graceful shutdowns. panic and ensures graceful shutdowns.
1. For small structs, use value receivers to avoid unnecessary heap 13. For small structs, use value receivers to avoid unnecessary heap
allocations. Use pointer receivers for large structs or when mutating allocations. Use pointer receivers for large structs or when mutating
the receiver. the receiver.
1. Only use goroutines when necessary. Excessive goroutines can lead to high 14. Only use goroutines when necessary. Excessive goroutines can lead to
memory consumption and increased complexity. high memory consumption and increased complexity.
1. Use `sync.Cond` for more complex synchronization needs that cannot be 15. Use `sync.Cond` for more complex synchronization needs that cannot be
met with simple mutexes and channels. met with simple mutexes and channels.
1. Reflection is powerful but should be used sparingly as it can lead to 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 code that is hard to understand and maintain. Prefer type-safe
solutions. solutions.
1. Avoid storing large or complex data in context. Context should be used 17. Avoid storing large or complex data in context. Context should be used
for request-scoped values like deadlines, cancellation signals, and for request-scoped values like deadlines, cancellation signals, and
authentication tokens. authentication tokens.
1. Use `runtime.Callers` and `runtime.CallersFrames` to capture stack traces 18. Use `runtime.Callers` and `runtime.CallersFrames` to capture stack
for debugging and logging purposes. traces for debugging and logging purposes.
1. Use the `testing.TB` interface to write helper functions that can be 19. Use the `testing.TB` interface to write helper functions that can be
used with both `*testing.T` and `*testing.B`. used with both `*testing.T` and `*testing.B`.
1. Use struct embedding to reuse code across multiple structs. This is a 20. Use struct embedding to reuse code across multiple structs. This is a
form of composition that can simplify code reuse. form of composition that can simplify code reuse.
1. Prefer defining explicit interfaces in your packages rather than relying 21. Prefer defining explicit interfaces in your packages rather than
on implicit interfaces. This makes the intended use of interfaces clearer relying on implicit interfaces. This makes the intended use of
and the code more maintainable. interfaces clearer and the code more maintainable.
# Author
[@sneak](https://sneak.berlin) &lt;[sneak@sneak.berlin](mailto:sneak@sneak.berlin)&gt;
# 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.