From e7548045be6c1fb334093b52b780db540ea09b19 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 10 Jun 2024 04:21:32 -0700 Subject: [PATCH] add some stuff --- README.md | 244 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 149 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 4a3ea40..c630dcc 100644 --- a/README.md +++ b/README.md @@ -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. -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. +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. -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. +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. -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: +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,26 +46,26 @@ } ``` -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. +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. -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`. +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`. -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. +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. -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. +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. -9. 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. 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 @@ -186,96 +186,97 @@ `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() - 9. **`error`** instead of custom error types: + string` method satisfies this interface. - - 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. + 9. **`error`** instead of custom error types: - 10. **`net.Conn`** instead of `*net.TCPConn` or `*net.UDPConn`: + - 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. - - `net.Conn` is a generic network connection interface that can be - implemented by TCP, UDP, and other types of network connections. + 10. **`net.Conn`** instead of `*net.TCPConn` or `*net.UDPConn`: - 11. **`http.Handler`** instead of custom HTTP handlers: + - `net.Conn` is a generic network connection interface that can be + implemented by TCP, UDP, and other types of network connections. - - `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. + 11. **`http.Handler`** instead of custom HTTP handlers: - 12. **`http.HandlerFunc`** instead of creating a new type: + - `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.HandlerFunc` is a type that allows you to use functions as - HTTP handlers by implementing the `http.Handler` interface. + 12. **`http.HandlerFunc`** instead of creating a new type: - 13. **`encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler`** - instead of custom marshal/unmarshal methods: + - `http.HandlerFunc` is a type that allows you to use functions as + HTTP handlers by implementing the `http.Handler` interface. - - These interfaces are used for binary serialization and - deserialization. Implementing these interfaces allows types to - be encoded and decoded in a standard way. + 13. **`encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler`** + instead of custom marshal/unmarshal methods: - 14. **`encoding.TextMarshaler` and `encoding.TextUnmarshaler`** instead - of custom text 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 text-based serialization and - deserialization. They are useful for types that need to be - represented as text. + 14. **`encoding.TextMarshaler` and `encoding.TextUnmarshaler`** instead + of custom text marshal/unmarshal methods: - 15. **`sort.Interface`** instead of custom sorting logic: + - These interfaces are used for text-based serialization and + deserialization. They are useful for types that need to be + represented as text. - - `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. + 15. **`sort.Interface`** instead of custom sorting logic: - 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. + - `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. 39. Avoid using `panic` in library code. Instead, return errors to allow the caller to handle them. Reserve `panic` for truly exceptional @@ -305,19 +306,72 @@ string` method satisfies this interface. 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. -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 + Example: + + ```go + + type HandScore int + + const ( + ScoreHighCard = HandScore(iota * 100_000_000_000) + ScorePair + ScoreTwoPair + ScoreThreeOfAKind + ScoreStraight + ScoreFlush + ScoreFullHouse + ScoreFourOfAKind + ScoreStraightFlush + ScoreRoyalFlush + ) + ``` + + Example 2: + + ```go + type ByteSize float64 + + const ( + _ = iota // ignore first value by assigning to blank identifier + KB ByteSize = 1 << (10 * iota) + MB + GB + TB + PB + EB + ZB + YB + ) + ``` + +43. Don't hardcode big lists of things in your normal code. Either isolate + lists in their own module/package and write some getters, 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). + It's okay to embed a data file (use `go embed`) in your binary if you + need to, but make sure you parse it once as a singleton and don't read + it from disk every time you need it. Don't use too much memory for + this, embedding anything more than perhaps 25MiB (uncompressed) is + probably too much. Compress the file before embedding and uncompress + during the reading/parsing step for efficiency. 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 + include the unit in the variable name (e.g. `uptimeSeconds`, + `delayMsec`, `coreTemperatureCelsius`), or use a type alias (that + includes the unit name), 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. +45. Once you have a working program, run `go mod tidy` to clean up your + `go.mod` and `go.sum` files. Tag a v0.0.1 or v1.0.0. Push your `main` + branch and tag(s). Subsequent work should happen on branches so that + `main` is "always releasable". "Releasable" in this context means that + it builds and functions as expected, and that all tests and linting + passes. + ## Other Golang Tips and Best Practices (Optional) 1. When passing channels to goroutines, use read-only (`<-chan`) or