Initially just a list of rules to follow while writing Go code, which are not covered by already existing linters etc.
https://medium.com/@matryer/line-of-sight-in-code-186dd7cdea88
Bad:
- if err == nil {
- return foo, nil
- }
-
- return nil, fmt.Errorf("doing foo: %w", err)
-}
Good:
+ if err != nil {
+ return nil, fmt.Errorf("doing foo: %w", err)
+ }
+
+ return foo, nil
+}
Bad:
- type Foo interface {
- Print() string
- Say() string
-
- // Don't do this.
- Bar() Bar
- }
Good:
+ type Foo interface {
+ Print() string
+ Say() string
+ }
+
+ type Printer interface {
+ Print() string
+ }
+
+ func NewBar(f Printer) Bar {
+ return &bar{}
+ }
Mixing exported fields and methods in struct makes it impossible to put it behind an interface on client side
Bad:
-type Foo struct {
- ID string
-}
-
-func (f *Foo) Do() error {
- return nil
-}
Good:
+type Foo struct {
+ id string
+
+func (f *Foo) Do() error {
+ return nil
+}
+
+func (f *Foo) ID() string {
+ return f.id
+}
Note: If this problem affects library struct you use, it can be workaround using the following method:
type Foo struct {
ID string
}
func (f *Foo) Do() error {
return nil
}
type FooWrapper struct {
*Foo
}
func (fw *FooWrapper) GetID() string {
return fw.ID
}
This is a defensive programming approach to avoid misuse or potential panics of programs.
Bad:
-type Doer interface {
- Do()
-}
-
-type Foo struct {
- Doer Doer
-}
-
-func (f *foo) Execute() {
- f.Doer.Do()
-}
Good:
+type Doer interface {
+ Do()
+}
+
+type Foo interface {
+ Execute()
+}
+
+type foo struct {
+ doer Doer
+}
+
+func NewFoo(doer Doer) (Foo, error) {
+ if doer == nil {
+ return nil, fmt.Errorf("doer can't be nil")
+ }
+
+ return &foo{
+ doer: doer,
+ }, nil
+}
+
+func (f *foo) Execute() {
+ f.doer.Do()
+}
It reduces lines of code and scope of variables.
Bad:
-err := Foo()
-if err != nil {
- ...
-}
Good:
+if err := Foo(); err != nil {
+ ...
+}
As a reader, you are more likely to search for exported structs and functions in the code, as they are higher level as the unexported functions. To make it convinient for the reader, exported structs, functions etc should be placed at the top of the file.
Bad:
-func foo() {}
-
-func Bar() {}
Good:
+func Bar() {}
+
+func foo() {}
Most of the time 'else' in 'if' statement can be avoided by either shifting the logic or splitting the code into functions. This reduces the cognitive complexity while reading code, as instead of having a path X when Y happens and path Z when Y is negated, you only have path X which executes always and path Z when Y happens.
More on that: https://medium.com/@jamousgeorges/dont-use-else-66ee163deac3
Bad:
- var bar int
-
- if foo == 5 {
- bar = 2
- } else {
- bar = 3
- }
Good:
+ bar := 3
+
+ if foo != 5 {
+ bar = 2
+ }
go test
on it's output converts space characters into _
, so when using spaces in the test names,
you can not just take the segment of the test name and grep for it to find the beginning of the test.
Using underscores as separators makes source and output consistent while does not hurt the readability.
Bad:
- t.Run("when shopping cart is empty", func(t *testing.T) {
Good:
+ t.Run("when_shopping_cart_is_empty", func(t *testing.T) {})