Software development is an evolving field, and it's crucial to continually improve our coding practices to maintain high code quality and ensure maintainability. In this article, we'll discuss a set of practices for writing cleaner and more maintainable code. I'll use Go as the programming language for the examples, but these practices can be applied to any language.
1. Enhance Code Organization by Separation of Concerns.
When writing Go code, it's essential to create a clear separation of concerns without making the code overly complex. To achieve this, follow these steps:
Step 1: Splitting Functions
Start by breaking down a large function into smaller, more manageable steps. For example, consider a function that processes a user's transaction. Split it into separate functions for validation, processing, and error handling.
func processTransaction(user User, transaction Transaction) error {
if err := validateTransaction(transaction); err != nil {
return err
}
if err := processTransactionInternal(user, transaction); err != nil {
return err
}
return nil
}
Step 2: Separate Functions
Rewrite these individual steps as their functions in separate files. For instance, move the validateTransaction
and processTransactionInternal
functions into their respective files.
├── main.go
├── transaction
│ ├── process.go # processTransaction
│ ├── validate.go # validateTransaction
│ └── internal.go # processTransactionInternal
└── user
└── process.go
Step 3: Validation as First Concern
Always prioritize validation as the initial step in each function. Group validations based on their specific purpose, such as request validation, user validation, and VCN validation. Each validation should have its own dedicated function with well-defined request and response structures.
func validateTransaction(transaction Transaction) error {
// Validation logic here
}
func validateUser(user User) error {
// User validation logic
}
func validateVCN(vcn string) error {
// VCN validation logic
}
Step 4: Proper Error Handling
Ensure that all potential errors are handled effectively. This means checking for errors and returning them, rather than ignoring or suppressing them.
vcn, httpCode, err := x.validateVCNsPrecondition(ctx, neoPG, payloadValidateVCNsPrecondition, log, now)
if err != nil {
return response, httpCode, err
}
2. Optimize Conditional Statements by Variable Declarations.
When you encounter lengthy expressions inside if statements, consider declaring those expressions as variables and then using the variables within the if statements. This makes the code more readable, self-documented and easier to maintain.
func isEligibleToVote(age int) bool {
// Lengthy expression
if age >= 18 && age <= 120 {
return true
}
return false
}
Rewrite it with variable declarations for improved readability:
func isEligibleToVote(age int) bool {
isAgeValid := age >= 18 && age <= 120
return isAgeValid
}
3. Minimize Reliance on Comments.
Meaningful function and variable naming can significantly reduce the need for excessive comments. While comments are helpful, they can sometimes become outdated or misleading. Therefore, it's best to rely on self-explanatory naming conventions. For example:
// Avoid
func calculateTax(income float64) float64 {
// Calculate the tax of income
// ...
return tax
}
// Prefer
func calculateIncomeTax(income float64) float64 {
// ...
return tax
}
4. Effective Error Logging.
When dealing with error logging, it's crucial to classify errors correctly:
For 5XX errors, include an identifier such as the user ID or card number in the interface to simplify future investigation.
For non-5XX errors that may be valuable for analytical purposes, log them for analysis.
For other predefined errors, consider error logging as optional, as these cases are typically predetermined.
func processTransaction(user User, transaction Transaction) error {
if err := validateTransaction(transaction); err != nil {
log.Err(err).
Interface("request", request).
Interface("response", response).
Msg(LABEL + " - validateTransaction")
return err
}
if err := processTransactionInternal(user, transaction); err != nil {
log.Err(err).
Interface("request", request).
Interface("response", response).
Msg(LABEL + " - processTransactionInternal")
return err
}
return nil
}
Incorporating these practices into your code can significantly enhance its quality and maintainability. Remember that clean, well-organized code is not only easier to work with, but also benefits the entire development team by reducing bugs and making the codebase more understandable and efficient.
Top comments (0)