Golang by Example

Logo

Anti Patterns

Anti-patterns are common responses to recurring problems in software development. They are often ineffective and can lead to more problems down the line. In Go, there are several anti-patterns that developers should be aware of to avoid writing inefficient or error-prone code.

Overuse of interface{}

It might ease our life when dealing with different types, but using interface{} as a catch-all type can lead to code that is difficult to understand and maintain. It can also introduce runtime errors if the underlying type is not what you expect. Instead, use specific types or define your own interfaces to provide better type safety and clarity.

Overengineering the interface

Overengineering an interface can lead to unnecessary complexity and make the code harder to understand. Instead, keep interfaces simple and focused on a specific behavior or set of behaviors. This will make it easier for other developers to understand and use your code.


  type Fooer interface {
      Foo() string
  }

  func DoSomething(b Fooer) {
      fmt.Println(b.Foo())
  }

Never overengineer an interface. Keep it simple and focused on a specific behavior or set of behaviors. This will make it easier for other developers to understand and use your code.

Using nil interfaces

Using nil interfaces can lead to confusion and unexpected behavior. When you use a nil interface, it can be difficult to determine whether the interface is nil or if it contains a value of type nil. Instead, use concrete types or define your own interfaces to avoid this confusion.

Using panic instead of error

When an error occurs, using panic to handle it can lead to unexpected behavior and make it difficult to recover from errors. Instead, use the built-in error type to handle errors gracefully. This allows you to return an error value and let the caller decide how to handle it.


    panic("something went wrong")

Not using defer for properly



    f := openFile("file.txt")
    ... // some statement and forgot to close the file after using it

Forgotting to close the file after using it can lead to resource leaks and other issues. Instead, use the defer statement to ensure that the file is closed properly, even if an error occurs.


    f := openFile("file.txt")
    defer f.Close()
    ... // use the file

Other case is to manually close or cleanup resources. This can lead to resource leaks and make your code harder to maintain. Instead, use the defer statement to ensure that resources are cleaned up properly, even if an error occurs.


    conn := openConnection()
    ... // use the connection

    conn.Close()

If some error occurs, the connection might not be closed properly. Instead, use the defer statement to ensure that the connection is closed properly, even if an error occurs.


    conn := openConnection()
    defer conn.Close()
    ... // use the connection

Global variables

Using global variables can lead to code that is difficult to understand and maintain. Global variables can be modified from anywhere in the code, making it difficult to track their state and behavior. Instead, use local variables or pass values as function arguments to avoid this issue.

This sample might look simple, but in a large codebase, global variables can lead to confusion and bugs. Instead, use local variables or pass values as function arguments to avoid this issue.

Not using context

Using context in Go is essential for managing timeouts, cancellations, and deadlines in concurrent programs. Not using context can lead to resource leaks and make it difficult to manage the lifecycle of goroutines. Instead, use the context package to pass context information between functions and goroutines.

Not checking nil pointer

Not checking for nil pointers can lead to runtime panics and crashes in your program. Always check for nil pointers before dereferencing them to avoid these issues. This is especially important when dealing with pointers to structs or interfaces.

Magic numbers

Using magic numbers in your code can make it difficult to understand and maintain. Instead, use named constants or variables to give meaning to the numbers in your code. This will make it easier for other developers to understand the purpose of the numbers and how they relate to the rest of the code.


    if status == 9 { ... } // What the heck is 9?

    const StatusOK = 9
    if status == StatusOK { ... } // This is much better

Too many return values

Using too many return values can make your code difficult to read and understand.


    func GetUserInfo() (string, int, string) {
        return "John Doe", 30, "San Francisco"
    }

Instead, use a struct or a custom type to group related values together. This will make it easier for other developers to understand the purpose of the values and how they relate to each other.


Table of contents

Hello World - Begin with the classic Hello World program
Primitives - Learn about the basic data types in Go
Flow Control - Controlling the flow of your program
Struct - All about struct
Functions - Define and call functions
Methods and Interfaces - Methods and interfaces in Go
Error Handling - Handling errors idiomatically
Concurrency - Goroutines and channels
Anti Patterns - Anti patterns in Go
Libraries - Standard library and third-party libraries
Testing - Writing unit tests with `go test`
Benchmarking - Performance benchmarks
Containerization - Dockerize your Go app
What's Next? - Explore the next steps in your Golang journey


Share to: