best_practices
Last updated: 2024-11-07 21:05:54.556789 File source: link on GitLab
Standards and Best Practices
Introduction
This document is a store of best practices to be followed for the development of Nunet components. The current file is more focused on Golang best practices for building Device Management Service (DMS), which is the core component of the Nunet platform.
Table of Contents
Naming Conventions
File Names
All file names should be in lowercase. And it is recommended to use underscore between two words. Example:
capability_comparator.go
The files containing tests should have
_test.go
suffix in the name Example:capability_comparator_test.go
Variable Names
The name of any error variable must be err
or prefixed with err
. Consistent naming for error variables makes the code more readable and easier to follow. See below for an illustrative example.
Structs
Named fields improve code readability and reduce errors caused by misordered fields. For example,
Units
Including the unit(e.g. time) in the name. Append the interval type to the consts, for example dialupTimeoutSecond
or dialupTimeoutMillisecond
Including the unit in the name clarifies its meaning and prevents unit-related bugs.
Formatting and Style
Line Size
It is recommended to limit the line size to 100 characters per line
. Keeping line lengths manageable (e.g., under 80-100 characters) helps readability, especially on smaller screens.
Function Size
Aim for functions that fit within a single screen of code without scrolling
This usually means keeping functions under 20-30 lines, making them easier to read and understand.
Code Structure
Avoid Global Variables
As a general principle, it is recommended to avoid using global variables as much as possible, because it can lead to tight coupling between components of the system. It also simplifies testing.
As an alternative, it is suggested to use dependency injection instead.
Group API Handlers
It is recommend to use objects (structs) to group related handlers. See below for an illustrative example.
Type Definition
Type definitions should be done on top of the page. This provides a clear overview of the structures and types used in the file.
The recommended order of type definitions is
consts
interfaces
structs
A logical order (e.g., constants, interfaces, structs, functions) helps readers understand the code structure quickly. It is recommeded that the Constructor should be defined first immediately after the struct
section.
Functions and Methods
Accept interfaces and return structs
This follows Go’s idiomatic way to make code more flexible and easier to test. Special cases: When needed to return the interface, we have another constructor returning the interface
Return pointers to structs in constructors
This has several benefits:
Efficiency: Reduced Copying: When you return a pointer, you avoid copying the entire struct. This is especially important for large structs, as copying can be expensive in terms of performance. Memory Allocation: Using pointers ensures that only one instance of the struct is created and manipulated, reducing memory overhead.
Mutability: Modification: With a pointer, the receiver can modify the original struct's data. This is useful when you want to update fields of the struct after it's been constructed. Shared State: If multiple parts of the program need to share and modify the same instance, pointers facilitate this by allowing all references to point to the same underlying data.
Consistency: Interface Implementation: Methods that implement interfaces often require pointer receivers to modify the struct. Returning pointers ensures that these methods can be used as intended. Idiomatic Go: Returning pointers is a common and idiomatic practice in Go, aligning with community conventions and expectations.
Avoid very similar methods doing almost the same thing without adding any business logic
For example, in teh below interface, the second and third method are simply returning a filtered value of GetGPUs. This kind of practice decreases the abstraction of an interface.
Below is another example where the second method probably does not have any additional business logic but is added to execute a for loop. It is best to avoid such definitions, and keep only the first method.
Error Handling
Always wrap errors and return them
Wrapping errors with context provides more informative error messages.
Don’t log errors in functions that return errors
Logging should be done at the top level where errors are handled, not within lower-level functions.
Non error logs levels can be used as needed.
Consistent Error Handling
Handle errors consistently. Avoid ignoring errors, and handle them as soon as possible.
Avoid Panics
Use panics
only for unrecoverable errors. For recoverable errors, use error returns
Testing
Use table-driven tests
Table-driven tests are a common Go
pattern that make it easy to add new test cases and improve test readability.
Use assert library for testing
Using an assert library makes tests cleaner and provides better error messages when tests fail.
Only use mocks if not otherwise possible
Avoiding excessive mocking leads to more reliable tests that don't break with internal changes.
General
Elements in maps are not ordered and randomly retrieved
Never rely on ordering in a map. Using maps in tests can help ensure your code doesn’t rely on specific orderings, improving robustness.
Do not ignore "contexts"
Contexts are critical for handling deadlines, cancellation signals, and request-scoped values across API boundaries and goroutines.
Note: Telemetry uses contexts heavily, see how this is affected
Avoid Unnecessary Slices Allocations
Preallocate slices when the size is known to avoid unnecessary allocations.
Avoid Exporting Unnecessary Types and Functions
Keep the API surface small by only exporting types and functions that are intended for public use.
Check for nil Before Dereferencing Pointers
Always check for nil pointers before dereferencing to avoid panics.
Use filepath Package for File Path Manipulation
Use path/filepath for manipulating file paths to ensure cross-platform compatibility. Don’t create path by concatenation “/” or “\”
Code Quality
It is recommended to use
golangci-lint
Linter.
Linters enforce coding standards and catch common mistakes early, improving code quality.
Avoid magic numbers and use consts
Constants give meaningful names to otherwise obscure numbers, improving code readability.
Go Idioms
Avoid new keyword if possible
Using composite literals (e.g., &Person{}) is more idiomatic and clear in Go.
Last updated