Lesson 1
Clean Code Practices with Structs in Go
Introduction

Welcome to the very first lesson of the "Clean Code with Multiple Structs in Go" course! 🎉 This course aims to guide you in writing code that's easy to understand, maintain, and enhance. Within the broader scope of clean coding, effective struct collaboration is crucial for building well-structured applications. In this lesson, we will delve into the intricacies of struct collaboration and coupling—key factors that can make or break the maintainability of your software. Specifically, we'll address some common "code smells" that indicate problems in struct interactions and explore ways to resolve them.

Overview of Struct Collaboration Challenges

Let's dive into the challenges of struct collaboration by focusing on four common code smells:

  • Feature Envy: Occurs when a method in one struct is overly interested in methods or data in another struct.
  • Inappropriate Intimacy: Describes a situation where two structs are too closely interconnected, sharing private details.
  • Message Chains: Refer to sequences of method calls across several structs, indicating a lack of clear abstraction.
  • Middle Man: Exists when a struct mainly delegates its behavior to another struct without adding functionality.

Understanding these code smells will enable you to improve your struct designs, resulting in cleaner and more maintainable code.

Problems Arising During Struct Collaboration

These code smells can significantly impact system design and maintainability. Let's consider their implications:

  • They can lead to tightly coupled structs, making them difficult to modify or extend. 🔧
  • Code readability decreases, as it becomes unclear which struct is responsible for which functionality.

Addressing these issues often results in code that's not only easier to read but also more flexible and scalable. Tackling these problems can markedly enhance software architecture, making it more robust and adaptable.

Feature Envy

Feature Envy occurs when a method in one struct is more interested in the fields or methods of another struct than its own. Here's an example in Go:

Go
1package main 2 3import "fmt" 4 5type Item struct { 6 Price float64 7 Quantity int 8} 9 10type ShoppingCart struct { 11 Items []Item 12} 13 14func (sc ShoppingCart) CalculateTotalPrice() float64 { 15 total := 0.0 16 for _, item := range sc.Items { 17 // Feature Envy: The ShoppingCart method is overly interested in the internal fields of Item 18 total += item.Price * float64(item.Quantity) 19 } 20 return total 21} 22 23func main() { 24 cart := ShoppingCart{ 25 Items: []Item{ 26 {Price: 5.0, Quantity: 2}, 27 {Price: 10.0, Quantity: 1}, 28 }, 29 } 30 fmt.Printf("Total Price: %.2f\n", cart.CalculateTotalPrice()) 31}

In this scenario, CalculateTotalPrice in ShoppingCart overly accesses data from Item, indicating feature envy.

To refactor, consider moving the logic to the Item struct:

Go
1package main 2 3import "fmt" 4 5type Item struct { 6 Price float64 7 Quantity int 8} 9 10func (item Item) CalculateTotal() float64 { 11 return item.Price * float64(item.Quantity) 12} 13 14type ShoppingCart struct { 15 Items []Item 16} 17 18func (sc ShoppingCart) CalculateTotalPrice() float64 { 19 total := 0.0 20 for _, item := range sc.Items { 21 total += item.CalculateTotal() 22 } 23 return total 24} 25 26func main() { 27 cart := ShoppingCart{ 28 Items: []Item{ 29 {Price: 5.0, Quantity: 2}, 30 {Price: 10.0, Quantity: 1}, 31 }, 32 } 33 fmt.Printf("Total Price: %.2f\n", cart.CalculateTotalPrice()) 34}

Now, each Item calculates its own total, reducing dependency and distributing responsibility appropriately. ✔️

Inappropriate Intimacy

Inappropriate Intimacy occurs when a struct is overly dependent on the internal details of another struct. In Go, package-level access is used to control visibility. Here's an example:

Go
1package main 2 3import "fmt" 4 5type Book struct { 6 Title string 7 Author string 8} 9 10type Library struct { 11 Book Book 12} 13 14func (l Library) PrintBookDetails() { 15 // Inappropriate Intimacy: The Library struct relies too heavily on directly accessing 16 // the internal details of the Book struct, leading to a high degree of coupling. 17 fmt.Printf("Title: %s\nAuthor: %s\n", l.Book.Title, l.Book.Author) 18} 19 20func main() { 21 lib := Library{ 22 Book: Book{Title: "Go Programming", Author: "John Doe"}, 23 } 24 lib.PrintBookDetails() 25}

The Library struct relies too heavily on the details of the Book struct, demonstrating inappropriate intimacy.

To refactor, allow the Book struct to handle its own representation:

Go
1package main 2 3import "fmt" 4 5type Book struct { 6 Title string 7 Author string 8} 9 10func (b Book) GetDetails() string { 11 return fmt.Sprintf("Title: %s\nAuthor: %s\n", b.Title, b.Author) 12} 13 14type Library struct { 15 Book Book 16} 17 18func (l Library) PrintBookDetails() { 19 fmt.Print(l.Book.GetDetails()) 20} 21 22func main() { 23 lib := Library{ 24 Book: Book{Title: "Go Programming", Author: "John Doe"}, 25 } 26 lib.PrintBookDetails() 27}

This adjustment enables Book to encapsulate its own details, encouraging better encapsulation and separation of concerns. 🛡️

Message Chains

Message Chains occur when structs need to traverse multiple objects to access the methods they require. Here's a demonstration in Go:

Go
1package main 2 3import "fmt" 4 5type Address struct { 6 ZipCode ZipCode 7} 8 9type ZipCode struct { 10 Code string 11} 12 13type User struct { 14 Address Address 15} 16 17func main() { 18 user := User{ 19 Address: Address{ 20 ZipCode: ZipCode{Code: "90210"}, 21 }, 22 } 23 // Message Chains: Accessing user.Address.ZipCode.Code creates a chain of method calls, 24 // indicating a lack of clear abstraction and making the code harder to maintain. 25 fmt.Println(user.Address.ZipCode.Code) 26}

The chain user.Address.ZipCode.Code illustrates this problem.

To simplify, encapsulate the access within methods:

Go
1package main 2 3import "fmt" 4 5type ZipCode struct { 6 Code string 7} 8 9type Address struct { 10 ZipCode ZipCode 11} 12 13func (a Address) GetPostalCode() string { 14 return a.ZipCode.Code 15} 16 17type User struct { 18 Address Address 19} 20 21func (u User) GetUserPostalCode() string { 22 return u.Address.GetPostalCode() 23} 24 25func main() { 26 user := User{ 27 Address: Address{ 28 ZipCode: ZipCode{Code: "90210"}, 29 }, 30 } 31 fmt.Println(user.GetUserPostalCode()) 32}

This adjustment makes the User struct responsible for retrieving its postal code, creating a clearer and more direct interface. 📬

Middle Man

A Middle Man problem occurs when a struct primarily exists to delegate its functionalities. Here's an example in Go:

Go
1package main 2 3import "fmt" 4 5type Service struct{} 6 7func (s Service) PerformAction() { 8 fmt.Println("Action performed") 9} 10 11type Controller struct { 12 Service Service 13} 14 15func (c Controller) Execute() { 16 // Middle Man: The Controller struct primarily delegates its behavior to the Service struct without adding functionality, 17 // making it an unnecessary intermediary that can be removed to simplify the code design. 18 c.Service.PerformAction() 19} 20 21func main() { 22 controller := Controller{ 23 Service: Service{}, 24 } 25 controller.Execute() 26}

The Controller doesn’t do much beyond delegating to Service.

To refactor, simplify delegation or reassign responsibilities:

Go
1package main 2 3import "fmt" 4 5type Service struct{} 6 7func (s Service) PerformAction() { 8 fmt.Println("Action performed") 9} 10 11func main() { 12 service := Service{} 13 service.PerformAction() 14}

By removing the unnecessary middle man, the design becomes more streamlined and efficient. 🔥

Summary and Practice Heads-Up

In this lesson, you've explored several code smells associated with suboptimal struct collaboration and coupling, including Feature Envy, Inappropriate Intimacy, Message Chains, and Middle Man. By identifying and refactoring these smells, you can elevate your code's clarity and maintainability.

Get ready to put these concepts into practice with upcoming exercises, where you'll identify and refactor code smells, strengthening your skills. Keep striving for cleaner, more effective code! 🌟

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