add some stuff

This commit is contained in:
Jeffrey Paul 2024-06-10 04:21:32 -07:00
parent d11270ff9d
commit e7548045be
1 changed files with 149 additions and 95 deletions

244
README.md
View File

@ -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