Lesson 1
Refactoring and Code Smells in Go
Topic Overview

Welcome to the world of refactoring! In this lesson, we're learning about Code Smells, which are patterns in code that hint at potential problems. Our mission is to help you spot these smells and understand how to improve them or, in programming terms, how to refactor them. We'll delve into the concept of code smells, examine different types, and apply real-world code examples to solidify your understanding. Let's get started!

Introduction to Code Smells

Code smells are signs that something could be amiss in our code. You could compare them to an unpleasant smell in a room. But instead of indicating rotten food or a dirty sock, they signal that our code may not be as readable, efficient, or manageable as it could be.

Consider this bit of code:

Go
1package main 2 3import "fmt" 4 5func Calculate(quantity int, price int) int { 6 return quantity * price 7} 8 9func main() { 10 total := Calculate(5, 3) 11 fmt.Println(total) 12}

The function name Calculate is too vague. What exactly does it calculate? For whom? This ambiguity is a sign of a bad naming code smell. Let's see how this and other code smells can be improved!

Duplicate Code

If you notice the same piece of code in more than one place, you may be looking at an example of the Duplicate Code smell. Duplicate code leaves room for errors and bugs. If you need to make a change, you might overlook one instance of duplication.

Here's an example:

Go
1totalApplesPrice := quantityApples * priceApple - 5 2totalBananasPrice := quantityBananas * priceBanana - 5

This code performs the same operation on different data. Instead of duplicating the operation, we can create a function to handle it:

Go
1package main 2 3import "fmt" 4 5func CalculatePrice(quantity, price int) int { 6 discount := 5 7 return quantity*price - discount 8} 9 10func main() { 11 totalApplesPrice := CalculatePrice(quantityApples, priceApple) 12 totalBananasPrice := CalculatePrice(quantityBananas, priceBanana) 13 fmt.Println(totalApplesPrice) 14 fmt.Println(totalBananasPrice) 15}

With this solution, if we need to change the discount or the formula, we can do so in one place: the CalculatePrice function.

Too Long Method

A function that does too many things or is too long is harder to read and understand, making it a prime candidate for the Too Long Method smell.

Consider this example:

Go
1package main 2 3import "fmt" 4 5type Order struct { 6 PaymentType string 7} 8 9func ProcessOrder(order Order) bool { 10 fmt.Println("Processing order...") 11 if order.PaymentType == "credit_card" { 12 ProcessCreditCardPayment(order) 13 SendOrderConfirmationEmail(order) 14 } else if order.PaymentType == "paypal" { 15 ProcessPaypalPayment(order) 16 SendOrderConfirmationEmail(order) 17 } else if order.PaymentType == "bank_transfer" { 18 ProcessBankTransferPayment(order) 19 SendOrderConfirmationEmail(order) 20 } else { 21 fmt.Println("Unsupported payment type") 22 return false 23 } 24 fmt.Println("Order processed successfully!") 25 return true 26} 27 28func ProcessCreditCardPayment(order Order) {} 29func ProcessPaypalPayment(order Order) {} 30func ProcessBankTransferPayment(order Order) {} 31func SendOrderConfirmationEmail(order Order) {}

This function handles too many aspects of order processing, suggesting a Too Long Method smell. Breaking down the functionality into smaller, more focused functions is a better approach.

For example, the updated code can look like this:

Go
1package main 2 3import "fmt" 4 5type Order struct { 6 PaymentType string 7} 8 9func ProcessPayment(paymentType string, order Order) bool { 10 switch paymentType { 11 case "credit_card": 12 ProcessCreditCardPayment(order) 13 case "paypal": 14 ProcessPaypalPayment(order) 15 case "bank_transfer": 16 ProcessBankTransferPayment(order) 17 default: 18 fmt.Println("Unsupported payment type") 19 return false 20 } 21 return true 22} 23 24func ProcessOrder(order Order) bool { 25 fmt.Println("Processing order...") 26 if ProcessPayment(order.PaymentType, order) { 27 SendOrderConfirmationEmail(order) 28 fmt.Println("Order processed successfully!") 29 return true 30 } 31 fmt.Println("Invalid order") 32 return false 33} 34 35func ProcessCreditCardPayment(order Order) {} 36func ProcessPaypalPayment(order Order) {} 37func ProcessBankTransferPayment(order Order) {} 38func SendOrderConfirmationEmail(order Order) {}
Comment Abuse

Comments within your code should provide useful information, but remember, too much of a good thing can be a problem. Over-commenting can distract from the code itself, and more often than not, it's a sign that the code isn't clear enough.

Consider this revised function, which calculates the area of a triangle, now with comments:

Go
1package main 2 3import "fmt" 4 5func CalculateTriangleArea(base, height float64) float64 { 6 // Calculate the area of a triangle 7 // Formula: 0.5 * base * height 8 area := 0.5 * base * height // Area calculation 9 return area // Return the result 10} 11 12func main() { 13 area := CalculateTriangleArea(5, 10) 14 fmt.Println(area) 15}

While comments explaining the formula might be helpful for some, the code itself is quite straightforward, and the comments on the calculation itself might be seen as unnecessary. If the function's name and parameters are clear, the need for additional comments can be minimized.

Here is how we could change this:

Go
1package main 2 3import "fmt" 4 5// CalculateTriangleArea calculates the area of a triangle given the base and height. 6func CalculateTriangleArea(base, height float64) float64 { 7 area := 0.5 * base * height 8 return area 9} 10 11func main() { 12 area := CalculateTriangleArea(5, 10) 13 fmt.Println(area) 14}

In this version, a single comment placed at the function level provides a high-level overview of what the function does, which is sufficient given the clarity of the function name and parameters. Comments within the function have been removed as they were unnecessary.

Bad Naming

Finally, we have Bad Naming. As the name suggests, this smell occurs when names don't adequately explain what a variable, function, or piece of data does. Good names are crucial for readable, understandable code.

Take a look at the following example:

Go
1package main 2 3func Func(a, b int) int { 4 return a*10 + b 5}

The names Func, a, and b don't tell us much about what is happening. A better version could be this:

Go
1package main 2 3// CalculateScore computes the score based on the base score and extra points. 4func CalculateScore(baseScore, extraPoints int) int { 5 return baseScore*10 + extraPoints 6}

In this version, each name describes the data or action it represents, making the code easier to read.

Lesson Summary

We've discovered Code Smells and studied common types: Duplicate Code, Too Long Method, Comment Abuse, and Bad Naming. Now you can spot code smells and understand how they can signal a problem in your code.

In the upcoming real-world example-based practice sessions, you'll enhance your debugging skills and improve your code's efficiency, readability, and maintainability. How exciting is that? Let's move ahead!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.