How I Write Clean Code As A Develeoper : Step By Step
Tags: webdev, beginners, cleancode, tutorial
Published: 2024-03-03 23:59:29

These are some steps for write a clean code, I have also included my perspectives and preferences.

---

## Naming

- Use descriptive name
I believe that this point is emphasized the most: “Names in software are 90 percent of what make software readable.” Let’s consider the following code examples. The variable p reveals nothing. Despite having a comment explaining its purpose, it can reduce the readability of overall codes. In Example 2, those variables clearly describe their intended functions.

```
// Example 1
var p float64 // final price

// Example 2
var basePrice float64
var totalDiscount float64
var finalPrice float64
```
- Avoid disinformation
Avoid ambiguous names; be clear about what the variable, class, or function truly does. Don’t overuse abbreviation, such as r for radius and w for width, except they are commonly used such as ctx for context in Go.
- Use pronounceable and searchable names
Coding may seem like an individual task, but in a collaborative team, it becomes a social activity. Use names that are easy to pronounce to facilitate communication.

---

## Functions
- Single Responsibility Principle
A function should have a single, clearly defined responsibility indicated by its name. For instance, getUserData() should exclusively return user data without performing other tasks. Don’t hide side effects within the name of a function. Take a look at this example. This function should only update transaction status, but it also sends email to user. Furthermore, the returned result originates from SendEmailToUser(), which it is supposed to be UpdateTransactionData() . This example violates Single Responsibility Principle.

```
func (s *service) updateTransactionStatus(ctx context.Context, 
  transaction UpdateTransactionRequest) (
  result UpdateTransactionResponse, err error) {
  result, err := s.UpdateTransactionData(ctx, transaction)
  if err != nil {
    return result, err
  }
  
  result, err := s.SendEmailToUser(ctx, transaction)
  if err != nil {
    return result, err
  }
  
  return result, err
}
```
- As small as possible
A small function simplifies the creation of unit tests and facilitates the early detection of potential bugs. This principle is closely connected to the Single Responsibility Principle.

- Number of arguments
It’s advisable to limit the number of arguments for a function. The most recommended are niladic functions (with no arguments), followed by monadic functions (with one argument) and dyadic functions (with two arguments). It’s best to avoid triadic functions (with three arguments) whenever possible. Fewer arguments simplify testing and reduce the chances of input errors due to argument swapping.
In Go, it’s advisable to include the context as an argument. We can manage function complexity and limit the number of arguments to a maximum of two whenever possible. This recommendation also applies to multiple return values in Go. While returning two values like return result, err is acceptable, for three or more values, it’s preferable to wrap them in a single struct. Regardless, always choose descriptive names that clearly convey the intention of each return value.

```
// not recommended
func createTransaction(ctx context.Context, 
  userID int,
  transactionType string,
  description string,
  amount float64) (err error, status string, endingAmount float64) {
  // ...
}

// recommended
func createTransaction(ctx context.Context, 
  transactionData CreateTransactionRequest) 
  (err error, result CreateTransactionResponse) {
  // ...
}
```

- Expected arguments
Always manage to use the expected arguments. Consider this example. The second example may seem exaggerated, but we sometimes unconsciously overuse arguments.

```
// expected argument
func isValidUser(userID int) bool {
  // process
}

// unexpected arguments
func isValidUser(userID int, numberOfFriend int) (isValid bool, friendIDs []int) {
  // process
}
```
---

## Comments
- Unused and dead code
Delete unused functions unless there is a clear comment explaining why it’s beneficial to keep them.
- Redundant comment
“Comment should say things that the code cannot say for itself.” See the following example of redundant comment.

```
i += 1 // increment of i
```
- Explanation of intent
I think it’s acceptable to use comments to provide an outline of the flow or important information for others to understand the code. However, it’s crucial to avoid unnecessary comments. According to Uncle Bob, comment can be used to provide the intent behind a decision. See the examples below. Additionally, comments can serve to alert others about the potential consequences of running specific functions.

```
// ===== NOT RECOMMENDED ======
// iterate over the list of user ID
for _, id := range userIDs {
  // do something with id
}

// the day after tomorrow
var dayAfterTomorrow string

// ===== RECOMMENDED =====
// sometimes the clients don't get the callback, so we need to resend it
result, err := resendCallback(ctx, request)
```

- Journal comments
Rather than including all changes, logs, and contributors at the start of the code, Uncle Bob prefers to omit this information, relying on version control systems for tracking these details.

- Out-of-update comment
Sometimes, the next developer may not be aware of existing comments, and when they make changes, these comments often go unmodified. It’s important to be mindful of comments that require regular updates. If the naming or logic of the code is clear enough on its own, consider deleting these comments.

---

## General
- Follow coding standard conventions
If the team follows industry conventions, adhere to them.
- Replace magic numbers with constants
However, there are cases where using magic numbers is acceptable, especially when calculations involve well-known measurements. For less common numbers, constants are a better choice.

```
// using magic numbers vs consts; magic number here is more preferable
totalSeconds = days * 24 * 60 * 60
totalSeconds = days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

// using magic numbers vs consts; const is more preferable
remainingTimeOff = 20 - takenTimeOff
remainingTimeOff = MAX_TIME_OFF - takenTimeOff
```
- Vertical separation
In Go, it’s common practice to define variables and functions close to where they are used. For instance, the declaration of latestTransaction is placed before its assignment, rather than declaring it at the very beginning of the program.

```
transactionList, err := repository.GetTransactionList(ctx, ID)
if err != nil {
  return response, err
}

var latestTransaction Transaction
latestTransaction = transactionList[len(transactionList)-1]

```
- Be consistent
Once you’ve selected a convention, maintain consistency throughout your codebase to prevent confusion. If you opt for the term Userstick with it consistently and avoid introducing Customerin the middle of your code unless they represent distinct concepts.
- Selector arguments
Instead passing true or false as argument to select the behavior, it’s preferable to create separate functions to handle each behavior.
- Use explanatory variables
In many cases, splitting code into multiple lines can improve readability, but it should be done thoughtfully. The choice between one-liners and multiline code depends on the specific context and what makes the code more understandable. The following example maybe too simple to be broken down into multiple lines, but the idea is to clarify the logic so the other will get it easily.

```
// one line
price := basePrice - (basePrice * discount) + (basePrice * tax)

// multiple lines
priceAfterDiscount := basePrice - (price * discount)
finalPrice := priceAfterDiscount - (price * tax)
```
---

## Connect with me
LinkedIn : https://www.linkedin.com/in/durgesh4993/
GitHub : https://github.com/Durgesh4993
LeetCode : https://leetcode.com/durgesh4993/
Profile : https://linktr.ee/durgesh4993

Keep Supporting :)