invidian / go-style-guide

Personal Go style guide

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Invidian Go language style guide

Initially just a list of rules to follow while writing Go code, which are not covered by already existing linters etc.

The happy path is left-aligned

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
+}

Chained interfaces are not replaceable

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
}

Struct fields are validated if user may put bad values into them or not initialize them

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()
+}

Short if notation should be preferred

It reduces lines of code and scope of variables.

Bad:

-err := Foo()
-if err != nil {
-  ...
-}

Good:

+if err := Foo(); err != nil {
+  ...
+}

Define exported objects before unexported ones

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() {}

Don't use 'else'

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
+ }

Don't use spaces in subtest names

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) {})

About

Personal Go style guide