commit 9a396ff8d91d2d7ca19d81719566a7007c2982e9 Author: sneak Date: Mon Jun 10 04:01:47 2024 -0700 initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..707195f --- /dev/null +++ b/README.md @@ -0,0 +1,399 @@ +# My Code Styleguide + +## Python + +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: + + ```python + if __name__ == "__main__": + main() + ``` + +## Golang + +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. + +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. + +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: + + ```go + package main + + import ( + "log/slog" + _ "sneak.berlin/go/simplelog" + ) + + func main() { + slog.Info("Starting up") + } + ``` + +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. + +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`. + +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. + +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. + +1. 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 + `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 + 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. + +1. 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 + 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 + `*.test` files, as well as any binaries. + +1. 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. + +1. 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. + +1. 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 + 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 + 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`. + +1. 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 + 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 + consumers to be more easily testable and will make it easier to reason + about the state of your application. Alternately, if your dependency + 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 + strings or integers or errors. + +1. 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 + is much cleaner. + + Example: + + ```go + + const jsonContentType = "application/json; charset=utf-8" + + func (s *Handlers) respondJSON(w http.ResponseWriter, r *http.Request, data interface{}, status int) { + w.WriteHeader(status) + w.Header().Set("Content-Type", jsonContentType) + ... + } + ``` + +1. Define your struct types near their constructors. + +1. 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 + 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. + +1. 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` + with `time.RFC3339` to get the correct format. + +1. 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 + you can differentiate between a zero value and a null value. + +1. 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` + 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 + 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`: + + - `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`: + + - `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`: + + - `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`: + + - `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`: + + - `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`: + + - `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`: + + - `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: + + - `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: + + - 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`: + + - `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: + + - `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: + + - `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: + + - 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: + + - 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: + + - `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. + +1. 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. + +1. When calling a function with `go`, wrap the function call in an + anonymous function to ensure it runs in the new goroutine context: + + Right: + + ```go + go func() { + someFunction(arg1, arg2) + }() + ``` + + Wrong: + + ```go + 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. + +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). + +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. + +## Other Golang Tips and Best Practices (Optional) + +1. When passing channels to goroutines, use read-only (`<-chan`) or + write-only (`chan<-`) channels to communicate the direction of data flow + clearly. + +1. 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. + +1. 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 + release of the lock as the second line of the function, and lines 3-5 + should perform the task. Try to keep it as short as possible. Avoid + 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 + with concurrent access and make the code easier to reason about. + +1. 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 + lead to unpredictable initialization order and make the code harder to + understand. + +1. 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`. + 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. + +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. + +1. 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. + +1. 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. + +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. + +1. 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. + +1. 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. + +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.