# SOLID and Functional Programming Principles in Go

> **Source:** [http://localhost:1313/solid-and-functional-programming-principles-in-go/](http://localhost:1313/solid-and-functional-programming-principles-in-go/)
> **Author:** [Vikash Patel](https://vikashpatel.net)
> **Published:** September 22, 2024
> **Reading Time:** 5 min
> 
> *This is the raw Markdown source of the article from the [Lorbic Technical Journal](http://localhost:1313/).*

---


Let's go through the **SOLID principles** and **functional programming principles** that can apply to your Go codebase. I'll provide simple Go examples along with brief explanations to show how each principle can improve code quality.

---

### **SOLID Principles**

1. **S**ingle Responsibility Principle (SRP)
   - **Description:** A class or function should have only one reason to change, meaning it should only have one job or responsibility.

   **Example:**

   ```go
   // Good: Separate responsibilities into two functions
   type EmailSender struct{}


   func (e *EmailSender) SendEmail(to, subject, body string) {
       // logic for sending email
   }


   func formatEmailBody(template string, data map[string]string) string {
       // logic for formatting the email body
       return ""
   }


   // Avoid: Single function doing multiple jobs
   func sendFormattedEmail(to, subject, template string, data map[string]string) {
       body := formatEmailBody(template, data)
       // email sending logic mixed with formatting
   }
   ```

---

2. **O**pen/Closed Principle (OCP)
   - **Description:** Software entities (classes, modules, functions) should be open for extension but closed for modification.

   **Example:**

   ```go
   // Good: Extend functionality via interfaces
   type PaymentProcessor interface {
       ProcessPayment(amount float64) error
   }


   type PayPalProcessor struct{}


   func (p PayPalProcessor) ProcessPayment(amount float64) error {
       // PayPal payment processing logic
       return nil
   }


   type CreditCardProcessor struct{}


   func (c CreditCardProcessor) ProcessPayment(amount float64) error {
       // Credit card processing logic
       return nil
   }


   // Avoid: Modifying existing functions to add new payment methods
   func processPayment(method string, amount float64) error {
       if method == "paypal" {
           // PayPal logic
       } else if method == "creditcard" {
           // Credit card logic
       }
       return nil
   }
   ```

---

3. **L**iskov Substitution Principle (LSP)
   - **Description:** Objects should be replaceable by their subtypes without altering the correctness of the program.

   **Example:**

   ```go
   // Good: Subtypes maintain expected behavior
   type Animal interface {
       Speak() string
   }


   type Dog struct{}


   func (d Dog) Speak() string {
       return "Woof!"
   }


   type Cat struct{}


   func (c Cat) Speak() string {
       return "Meow!"
   }


   func makeAnimalSpeak(a Animal) {
       fmt.Println(a.Speak())
   }


   // Avoid: Subtypes behaving unpredictably
   type SilentDog struct{}


   func (s SilentDog) Speak() string {
       return "" // Violates expectations of the Speak method
   }
   ```

---

4. **I**nterface Segregation Principle (ISP)
   - **Description:** No client should be forced to depend on methods it doesn't use. Create small, specific interfaces instead of one large interface.

   **Example:**

   ```go
   // Good: Segregated interfaces
   type Reader interface {
       Read() string
   }


   type Writer interface {
       Write(data string) error
   }


   // Avoid: Fat interface forcing implementations to use unused methods
   type ReadWriteCloser interface {
       Read() string
       Write(data string) error
       Close() error
   }


   // If a class only needs reading, it should not be forced to implement Write and Close.
   ```

---

5. **D**ependency Inversion Principle (DIP)
   - **Description:** High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces).

   **Example:**

   ```go
   // Good: High-level module depends on abstraction
   type NotificationService struct {
       sender EmailSender
   }


   func NewNotificationService(sender EmailSender) *NotificationService {
       return &NotificationService{sender: sender}
   }


   func (n *NotificationService) Notify(message string) {
       n.sender.SendEmail("user@example.com", "Notification", message)
   }


   // Avoid: High-level module directly depending on a low-level module
   func sendNotification() {
       sender := &EmailSender{} // tight coupling with EmailSender
       sender.SendEmail("user@example.com", "Notification", "Hello!")
   }
   ```

---

### **Functional Programming Principles**

1. **First-Class and Higher-Order Functions**
   - **Description:** Functions are treated as first-class citizens and can be passed around as arguments, returned from other functions, or assigned to variables.

   **Example:**

   ```go
   // Higher-order function that accepts another function as a parameter
   func applyDiscount(price float64, discountFunc func(float64) float64) float64 {
       return discountFunc(price)
   }


   func tenPercentDiscount(price float64) float64 {
       return price * 0.9
   }


   func main() {
       price := 100.0
       discountedPrice := applyDiscount(price, tenPercentDiscount)
       fmt.Println(discountedPrice) // 90.0
   }
   ```

---

2. **Pure Functions**
   - **Description:** A pure function's output is determined only by its input values, without observable side effects.

   **Example:**

   ```go
   // Good: Pure function (no side effects)
   func add(a, b int) int {
       return a + b
   }


   // Avoid: Impure function (with side effects)
   var counter int


   func addWithSideEffect(a, b int) int {
       counter++ // changes external state
       return a + b
   }
   ```

---

3. **Immutability**
   - **Description:** Data should not be modified after it is created. Instead, create a new copy of the data when changes are needed.

   **Example:**

   ```go
   // Good: Immutability by creating a new slice
   func addToSlice(slice []int, value int) []int {
       newSlice := append(slice, value)
       return newSlice
   }


   // Avoid: Modifying the original slice
   func modifySlice(slice []int, value int) {
       slice = append(slice, value)
   }
   ```

---

4. **Recursion Over Loops**
   - **Description:** Functional programming often favors recursion over iterative loops, especially when solving problems like list processing.

   **Example:**

   ```go
   // Good: Recursion
   func factorial(n int) int {
       if n == 0 {
           return 1
       }
       return n * factorial(n-1)
   }


   // Avoid: Iterative approach (loops)
   func iterativeFactorial(n int) int {
       result := 1
       for i := n; i > 0; i-- {
           result *= i
       }
       return result
   }
   ```

---

5. **Function Composition**
   - **Description:** Combine simple functions to build more complex ones, allowing for clean and reusable logic.

   **Example:**

   ```go
   func double(x int) int {
       return x * 2
   }


   func square(x int) int {
       return x * x
   }


   func compose(f, g func(int) int) func(int) int {
       return func(x int) int {
           return f(g(x)) // Apply g, then f
       }
   }


   func main() {
       doubleSquare := compose(double, square)
       fmt.Println(doubleSquare(3)) // Output: 18 (3^2 * 2)
   }
   ```

---

These principles can help keep your code clean, modular, and testable while using both object-oriented and functional paradigms.

