This content provides an in-depth analysis of Uber and Google's Go programming language coding standards, including naming conventions, error handling, formatting, structuring, testing, and concurrency, while highlighting the strengths and limitations of automated tools like gofmt, govet, golint, and golangci-lint.
Abstract
The given content is a comprehensive article that delves into the coding standards and practices for the Go programming language, as derived from sources such as Effective Go, Google Go Style Guide, and Uber Go Style Guide. The article provides insights into various aspects of Go programming, including naming conventions, error handling, formatting, structuring, testing, and concurrency.
The article emphasizes the importance of adhering to style guides and coding standards to make code more understandable, maintainable, and error-free. It compares the focus of the three guides, with Effective Go being more beginner-friendly, Uber Go Style Guide delving deeper into real-world Go coding practices, and Google Go Style Guide expanding further with detailed examples and best practices.
The article also discusses the use of automated tools like gofmt, govet, golint, and golangci-lint for maintaining a clean, readable, and standard codebase. It highlights the strengths and limitations of these tools, acknowledging that while they are indispensable for productivity, the nuanced understanding of Go's best practices and common pitfalls is what differentiates a proficient Go developer.
Bullet points
The article provides an in-depth analysis of Uber and Google's Go programming language coding standards.
The article covers various aspects of Go programming, including naming conventions, error handling, formatting, structuring, testing, and concurrency.
The article compares the focus of three guides: Effective Go, Uber Go Style Guide, and Google Go Style Guide.
The article emphasizes the importance of adhering to style guides and coding standards for better code quality.
The article discusses the use of automated tools like gofmt, govet, golint, and golangci-lint for maintaining a clean, readable, and standard codebase.
The article acknowledges the strengths and limitations of these tools, stressing the need for a nuanced understanding of Go's best practices and common pitfalls.
The article concludes by highlighting the importance of embracing tools for productivity while relying on judgment and knowledge for excellence.
Mastering Go: In-Depth Analysis of Uber and Google’s Coding Standards
Exploring the Essence of Go Programming Through Uber and Google’s Style Guides
In software development, adhering to style guides and coding standards is not just about keeping the code visually appealing but more importantly about making the code more understandable, maintainable, and error-free. The Go language, known for its simplicity and efficiency, is no exception. By delving into the common standards and practices derived from renowned sources like Effective Go, Google Go Style Guide, and Uber Go Style Guide, let’s reveal the essence of Go programming style guides, navigate through the tools that aid in enforcing these standards, highlight the limitations of automated linting, and pinpoint the crucial aspects developers should internalize.
Simplifying Go: Insights from Top Style Guides
All three guides are about Go coding style, format, and convention when they differ in focus.
Effective Go, which covers the basic use of all data structures, initialization, control structures, concurrency, and error handling, is more beginner-friendly. For example, it introduces what init() is.
Uber Go Style Guide delves deeper into real-world Go coding practices. For example, it suggests to avoid init() and explains why.
Google Go Style Guide, an upgraded Uber guide, expands further with detailed examples and best practices. For example, it introduces comments and gives examples in sections of format, package comment, and doc comment.
Key Takeaways on Coding Conventions
Naming Conventions
Naming is more than a cosmetic feature in your code but the first line of the documentation for anyone who reads your code. Effective naming makes your code self-explanatory.
If you are experienced with other languages, you’ll find the guides share almost the same standards, such as variables should have meaningful names, constants should all be in uppercase, and package names should be concise. However, sometimes, the “experience” works the opposite, for example, Getter/Setter methods in Go are not required to start with Get or Set while in Java they are; better not to use utility names in packages in Go.
Go takes a unique approach to error handling, encouraging developers to check errors where they occur and handle them promptly and predictably. Proper error handling also involves providing context to the errors, making debugging more straightforward.
Both the Uber guide and Google guide have similar contents about error handling, from error definition, error return and processing, and panic handling as well.
Formatting
A clear and consistent code format makes the code highly understandable and readable. The common code formatting, such as indentation, alignment of brackets, combination variable definition, maximum length of a line, etc., applies to not only Go but all languages. The Literal formatting, Function formatting, and Conditionals and loops in the Google Guide offer very good references.
Structuring and Use of Data Structures
The data structures like maps, slices, arrays, and channels , each with its intended use case, are covered in all three guides.
The zero values of different data types. Such as, slice does not require initialization but is used directly by declaring var s []int, then s := []int{} is “bad” code.
The initialization of different data type. Such as telling make from new, declaring the capacity of slice and map to improve code efficiency. In Initializing Structs and Initializing Maps in the Uber guide, you can find more practices.
The use and recycling of resources like chan and file. For example, channel Size is One or None of the Uber guide shows you how to determine whether chan needs a buffer and how much buffer to set.
Besides, we can learn more about the code style related to Go’s unique data structures in interfaces, goroutine, lifetimes, and generics in the Google guide.
Testing
The Google Guide emphasizes clear and maintainable tests.
Write tests for exported and critical functions.
Use table-driven tests for multiple scenarios.
Name test functions descriptively, starting with Test.
Keep tests simple and avoid testing Go’s standard library.
Document complex test logic for better understanding.
Concurrency is an inherent part of Go, with goroutines and channels as its primary features, which make code efficient and parallel. However, careful synchronization and communication patterns are needed to avoid common pitfalls like deadlocks and race conditions.
See the following snippet riddled with common mistakes, such as non-organized imports, poor naming, wrong channels, invalid code, etc.
How many issues have you located? Find the my answer here.
Tooling the Way to Compliance
Go Tools
gofmt, govet and golint are officially designed to facilitate Go code compliance.
gofmt is primarily for formatting, ensuring code follows standard formatting conventions, such as
Consistent Indentation and Spacing: Aligns with the recommended code structure and readability practices.
Proper Line Breaks and Brace Positioning: Adheres to conventions for control structures and composite types.
Organized Imports: Aligns with the suggested grouping and ordering of import statements.
govet examines code for potential bugs, like unreachable code or problematic type assertions, and aligns with the robustness and maintainability goals of the style guides.
Error Handling: Detects unreachable code or checks that may be bypassed.
Concurrency: Identifies common mistakes in goroutine and channel usage.
Code Correctness: Flags suspicious constructs, like Printf calls with incorrect format strings.
Variable Declaration: Warns about variables that may be unintentionally shadowed.
Type Safety: Checks for problematic type assertions and conversions.
golint focuses on style, flagging suboptimal code patterns or deviations from idiomatic Go, specifically addressing:
Naming Conventions: Ensures variables, constants, functions, and other identifiers are properly named following Go’s case sensitivity rules.
Comment Formatting: Checks for well-formed and properly placed comments on exported types, functions, and methods.
Exported Entities: Verifies that exported functions, variables, and types are properly documented.
Code Simplicity: Flags unnecessarily complex constructions that could be simplified.
golangci-lint
golangci-lint includes golint, and introduces an additional extensive range of linters with the support of the community. It addresses various issues highlighted in the Effective Go, Uber, and Google guides.
Error Handling: Ensures errors are checked and handled properly.
Code Complexity: Flags overly complex functions, promoting readability and maintainability.
Concurrency Issues: Detects race conditions and improper usage of concurrency primitives.
Performance Optimizations: Identifies inefficient code patterns that can be improved for better performance.
Coding Style: Enforces naming conventions and other style-related guidelines, aligning with idiomatic Go practices.
Practice
Now, let’s try to check the “An Erroneous Code Example” with the tools.
First with gofmt, govet, and golint.Run the following script respectively.
#!/bin/bashecho"Running gofmt..."# List & Write formatting differes and results to stdout
gofmt -l -w .
echo"Running go vet..."
go vet ./...
echo"Running golint..."# Show as many warnings as possible (default threshold min_confidence=0.8)
golint -min_confidence=0.1 ./...
gofmt has no output. The reason is that there is no additional formatting when we use IDE, for example, some gofmt functions like indentation and imports sort will be provided by default when I use VSCode and Golang extension, and the below import issue is beyond gofmt capabilities.
import (
"time"// extra empty line, gofmt cannot solve"fmt"// BadImportOrdering: "fmt" should be grouped with other standard library imports"io/ioutil"// Deprecated api: io/ioutil has been deprecated since Go 1.19"math/rand""os"
)
govet catches only two issues, JSON tag syntax and unreachable code.
golint discovers also two minor coding convention issues, both package comment and command are missing after adjusting its threshold to the lowest level.
What about golangci-lint performance?
First, let’s configure the execution of golangci-lint, which can be done in two ways: One is to enable parameters with the command line — enable-all , and execute the following command to import all warning and error information into the issues.txt file.
golangci-lint run --enable-all --out-format=json ./... | jq 'del(.Report)' > issues.txt
The other is to configure .golangci.yml file to enable all linter, and execute golangci-lint run --out-format=json ./... | jq 'del(.Report)' > issues.txt.
So excellent! golangci-lint returns 37 issues like dead code, such as globalData in the struct, global const a, b, and function uncalledFunc; errorHandling, such as unhandled errors; formatting such as, comments missing a period and the whitespace in the end; API usage, such as using math/rand over crypto/rand; unreachable code, such as the code in the unreachableCode method; naming, LinkedUrl issues, etc. If you are interested, please check the complete issue list.
Beyond Tools
Tools have Limitations
golangci-lint performs greatly in locating issues, but it is still limited, not to mention the Go tools.
For instance, they fail to detect the overly long parameter list in the above example where eight parameters are passed in the doSomething method, which is undoubtedly a violation of the code convention.
Also, In line 72, the code uses []int{} to initialize a slice, which should be avoided according to both Uber and Google guides because nil is a valid slice, and we should directly use slice after declaration.
Tools can’t do
Contextual Naming: Tools can’t tell if a name reflects the variable’s purpose. Choosing meaningful names is on you.
Proper Error Handling: While tools can catch ignored errors, providing proper context and handling errors gracefully is a developer’s responsibility.
Optimal Data Structure Usage: Understanding the complexities and choosing the right data structure is beyond the scope of automated tools.
Concurrency Patterns: Correctly implementing concurrency patterns, avoiding deadlocks, and ensuring efficient communication between goroutines require a deep understanding of Go’s concurrency model.
Design Choices: Decisions like when to use interfaces, pointers, or specific data structures are reliant on the developer’s judgment.
Wrapping Up
While gofmt, go vet, golint and golangci-lint are indispensable for maintaining a clean, readable, and standard codebase, the nuanced understanding of Go’s best practices and common pitfalls is what differentiates a proficient Go developer. Embrace the tools for productivity but rely on your judgment and knowledge for excellence.
Thanks for reading!
If you found this article helpful or enjoyed reading it, you can support my work by buying me a virtual coffee. Your support goes a long way in helping me create more quality content like this.