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.
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.
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.
nil
interfacesUsing 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.
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")
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
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.
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.
nil
pointerNot 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.
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
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.