add some stuff
This commit is contained in:
parent
d11270ff9d
commit
e7548045be
244
README.md
244
README.md
@ -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.
|
||||||
|
|
||||||
2. Don't commit anything that hasn't been `go fmt`'d. The only exception is
|
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
|
when committing things that aren't yet syntactically valid, 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.
|
||||||
|
|
||||||
3. 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
|
consistency and compatibility with the standard library; it's better
|
||||||
than casting all the time.
|
than casting all the time.
|
||||||
|
|
||||||
4. 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
|
smaller/quick projects, the standard library's `log` package (and
|
||||||
specifically `log/slog`) is fine. In that case, log structured logs
|
specifically `log/slog`) is fine. In that case, log structured logs
|
||||||
whenever possible, and import `sneak.berlin/go/simplelog` to configure
|
whenever possible, and import `sneak.berlin/go/simplelog` to configure
|
||||||
it appropriately. Example:
|
it appropriately. Example:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
@ -46,26 +46,26 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
5. 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.
|
||||||
|
|
||||||
6. 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`.
|
||||||
|
|
||||||
7. 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.
|
||||||
|
|
||||||
8. 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.
|
||||||
|
|
||||||
9. 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.
|
||||||
|
|
||||||
10. 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
|
||||||
@ -186,96 +186,97 @@
|
|||||||
`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.
|
|
||||||
|
|
||||||
9. **`error`** instead of custom error types:
|
string` method satisfies this interface.
|
||||||
|
|
||||||
- The `error` interface is used for representing errors. Instead of
|
9. **`error`** instead of custom error types:
|
||||||
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`:
|
- 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
|
10. **`net.Conn`** instead of `*net.TCPConn` or `*net.UDPConn`:
|
||||||
implemented by TCP, UDP, and other types of network connections.
|
|
||||||
|
|
||||||
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.
|
11. **`http.Handler`** instead of custom HTTP handlers:
|
||||||
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.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
|
12. **`http.HandlerFunc`** instead of creating a new type:
|
||||||
HTTP handlers by implementing the `http.Handler` interface.
|
|
||||||
|
|
||||||
13. **`encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler`**
|
- `http.HandlerFunc` is a type that allows you to use functions as
|
||||||
instead of custom marshal/unmarshal methods:
|
HTTP handlers by implementing the `http.Handler` interface.
|
||||||
|
|
||||||
- These interfaces are used for binary serialization and
|
13. **`encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler`**
|
||||||
deserialization. Implementing these interfaces allows types to
|
instead of custom marshal/unmarshal methods:
|
||||||
be encoded and decoded in a standard way.
|
|
||||||
|
|
||||||
14. **`encoding.TextMarshaler` and `encoding.TextUnmarshaler`** instead
|
- These interfaces are used for binary serialization and
|
||||||
of custom text marshal/unmarshal methods:
|
deserialization. Implementing these interfaces allows types to
|
||||||
|
be encoded and decoded in a standard way.
|
||||||
|
|
||||||
- These interfaces are used for text-based serialization and
|
14. **`encoding.TextMarshaler` and `encoding.TextUnmarshaler`** instead
|
||||||
deserialization. They are useful for types that need to be
|
of custom text marshal/unmarshal methods:
|
||||||
represented as text.
|
|
||||||
|
|
||||||
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
|
15. **`sort.Interface`** instead of custom sorting logic:
|
||||||
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:
|
- `sort.Interface` is an interface for sorting collections. By
|
||||||
- `flag.Value` is an interface for defining custom command-line
|
implementing the `Len`, `Less`, and `Swap` methods, you can sort
|
||||||
flags. Implementing the `String` and `Set` methods allows you to
|
any collection using the `sort.Sort` function.
|
||||||
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.
|
||||||
|
|
||||||
39. 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
|
||||||
@ -305,19 +306,72 @@ string` method satisfies this interface.
|
|||||||
42. 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.
|
||||||
|
|
||||||
43. Don't hardcode big lists in your code. Either isolate lists in their own
|
Example:
|
||||||
module/package, or use a third party library. For example, if you need a
|
|
||||||
list of country codes, you can use
|
```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).
|
[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
|
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 variable name (e.g. `uptimeSeconds`,
|
||||||
such as
|
`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
|
[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.
|
||||||
|
|
||||||
|
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)
|
## Other Golang Tips and Best Practices (Optional)
|
||||||
|
|
||||||
1. When passing channels to goroutines, use read-only (`<-chan`) or
|
1. When passing channels to goroutines, use read-only (`<-chan`) or
|
||||||
|
Loading…
Reference in New Issue
Block a user