go

Go Clean Code Guidelines

Posted by Vikash Patel on Sunday, Sep 22, 2024 (Updated Friday, Sep 27, 2024) Reading time: 5 Min

Go Clean Code Guidelines for Code Quality and Maintainability.

When it comes to writing clean, maintainable code, there are a few fundamental rules that can help improve the overall structure and quality of your codebase. As engineers, our goal should be to keep things simple, clear, and scalable. With this in mind, here are some guidelines which prioritize code readability, functional clarity, and the overall maintainability of a project. These guidelines are based on principles from clean code, SOLID, and functional programming while emphasizing simplicity over unnecessary complexity.


Rule 1: Function Naming Clarity

Your function names must clearly indicate what they do. Avoid vague names or overly complex abstractions. Each function should serve a single purpose, and its name should reflect this responsibility.

Example:

Instead of a vague name like ProcessData:

// Bad
func ProcessData() {
    // Complex logic for processing some data
}

Use something more descriptive like ConvertOrderToInvoice:

// Good
func ConvertOrderToInvoice(order Order) Invoice {
    // Convert an Order object into an Invoice
}

Here, the function name precisely conveys what the function does, making it easier for developers to follow the code.

Rule 2: No Magic Numbers or Strings

Avoid using magic numbers or strings directly in your code. Instead, define constants with meaningful names to improve readability and reduce errors.

Example:

// Bad
if user.Role == "admin" {
    // Grant access
}

Replace magic strings with constants:

// Good
const AdminRole = "admin"
if user.Role == AdminRole {
    // Grant access
}

This makes the code more readable and easier to maintain. If the role name changes in the future, you only need to update it in one place.

Rule 3: Use Clear and Consistent Variable Names

Variable names should be descriptive enough to indicate their purpose. Do not abbreviate in a way that makes it hard for others to understand the meaning of the variable.

Example:

// Bad
v := 10 // What is 'v'?

Instead, use meaningful names:

// Good
maxAllowedItems := 10

With clear variable names, other developers can quickly understand what the variable represents.

Rule 4: Keep Functions Small

Each function should perform one task and do it well. If a function is doing multiple things, break it down into smaller functions. This improves code readability and makes testing easier.

Example:

// Bad
func HandleOrder(order Order) Invoice {
    // Generating invoice, many lines of code

    // Save invoice to database, many lines of code

    // Send notification to customer, many lines of code
    return invoice
}

The function is doing too many things. Instead, split it into smaller functions:

// Good
func HandleOrder(order Order) Invoice {
    invoice := generateInvoice(order)
    persistInvoice(invoice)
    notifyCustomer(invoice)
    return invoice
}
func persistInvoice(invoice Invoice) {
    // Save invoice to the database
}
func notifyCustomer(invoice Invoice) {
    // Send a notification to the customer
}

This makes each function’s responsibility clearer, and it is easier to test and maintain.

Rule 5: Error Handling Should Be Explicit

Always check for errors and handle them explicitly. Do not assume the absence of an error. The code should either handle the error gracefully or propagate it back to the caller.

Example:

// Bad
result, err := getUserDetails(userID)
process(result) // No error handling!

Instead, always handle or propagate errors:

// Good
result, err := getUserDetails(userID)
if err != nil {
    logError(err)
    return nil, err
}
process(result)

Proper error handling ensures that bugs and edge cases are caught early and makes the code more reliable.

Rule 6: Use Structs for Complex Data

If a function accepts multiple parameters of related data, bundle them into a struct. This makes the function signature cleaner and more flexible.

Example:

// Bad
func CreateUser(name string, age int, address string, email string) User {
    // Create a new user
}

Instead, use a struct for the user’s details:

// Good
type UserDetails struct {
    Name    string
    Age     int
    Address string
    Email   string
}
func CreateUser(details UserDetails) User {
    // Create a new user
}

This approach makes the code easier to read and modify in the future, especially if more fields are added to the user details.

Rule 7: Use Consistent Struct Field Order

Maintain consistency in the order of struct fields. Group similar fields together to enhance readability.

Example:

// Bad
type User struct {
    Address   string
    Age       int
    Email     string
    Name      string
}

Instead, group fields logically:

// Good
type User struct {
    Name      string
    Age       int
    Address   string
    Email     string
}

This way, when you or others revisit the code, it’s easier to understand the structure of the object.

Rule 8: Inline Commenting for Complex Logic

Where necessary, provide inline comments explaining the purpose of complex logic or algorithms. Avoid obvious comments, but make sure to clarify non-obvious code.

Example:

// Bad
// Add two numbers
total := a + b

Instead, explain complex logic:

// Good
// Apply discount if the customer is a premium user and their purchase exceeds $100
if customer.IsPremium && order.Total > 100 {
    applyDiscount(order)
}

The inline comment explains the logic behind applying the discount, which may not be immediately apparent from the code itself.

Separate blocks of code that serve different purposes with blank lines. This improves readability and helps group related logic together.

Example:

// Bad
func ProcessOrder(order Order) {
    validateOrder(order)
    applyDiscount(order)
    finalizePayment(order)
    sendReceipt(order)
}

Instead, separate the code into logical sections:

// Good
func ProcessOrder(order Order) {
    validateOrder(order)
    applyDiscount(order)

    finalizePayment(order)
    sendReceipt(order)
}

This makes the code visually easier to follow and highlights the different stages of processing an order.

Rule 10: Function Arguments vs. Return Types

Make sure function names clearly reflect their return types and arguments. This prevents confusion and makes the function’s purpose obvious to the caller.

Example:

// Bad
func FetchData(input string) bool {
    // Returning a boolean from a 'Fetch' function is misleading
}

Instead, update the name to reflect the return type:

// Good
func IsDataAvailable(input string) bool {
    // Now it's clear that the function returns a boolean
}

This helps maintain consistency in your codebase and makes the function’s role immediately clear.

By following these guidelines, you can ensure that your codebase remains clean, efficient, and easier to navigate for you and your team. Keep function names clear, avoid magic numbers, handle errors explicitly, and group your code logically for the best results. Happy coding!



comments powered by Disqus