Clean Code, SOLID Principles & Architecture Masterclass
Welcome to Phase 15 (Chapter 42): Clean Code, SOLID Principles, Design Patterns & Refactoring Masterclass! Writing code that works is the baseline. Writing code that is readable, maintainable, testable, and extensible is what separates junior from senior engineers. In this chapter, we master meaningful naming conventions, small focused methods, the DRY principle, all five SOLID design principles, common design patterns (Repository, Service, Factory), and practical refactoring techniques.
| Principle | Full Name | Rule | Violation Example |
|---|---|---|---|
| S | Single Responsibility | Every class should have ONE reason to change โ one job. | UserService that handles registration + email sending + PDF generation. |
| O | Open/Closed | Open for extension, Closed for modification. Add features via new classes, not editing existing. | Adding discount type by adding if/else to existing OrderService. |
| L | Liskov Substitution | Subtypes must be usable wherever their base type is expected without breaking behavior. | Square extends Rectangle but breaks area calculation assumptions. |
| I | Interface Segregation | Clients should not depend on interfaces they don't use. Split large interfaces into focused ones. | IWorker with Work() + Eat() + Sleep() forced on Robot class. |
| D | Dependency Inversion | High-level modules should depend on abstractions (interfaces), not on concrete implementations. | OrderService directly creates new EmailService() instead of IEmailService injection. |
// โ SRP VIOLATION โ OrderService does too many things
public class OrderService
{
public void ProcessOrder(Order order)
{
// Responsibility 1: Validate order
if (order.Items.Count == 0) throw new Exception("Empty order!");
// Responsibility 2: Calculate totals
order.Total = order.Items.Sum(i => i.Price * i.Quantity);
// Responsibility 3: Save to database
_db.Orders.Add(order); _db.SaveChanges();
// Responsibility 4: Send email confirmation
var smtp = new SmtpClient("smtp.example.com");
smtp.Send("noreply@shop.com", order.Customer.Email, "Order Confirmed", "...");
// Responsibility 5: Generate PDF invoice
var pdf = new PdfDocument();
pdf.AddPage().AddText("Invoice #" + order.Id);
pdf.Save("invoice_" + order.Id + ".pdf");
}
}
// โ
SRP COMPLIANT โ Separate single-responsibility classes
public class OrderValidator { public void Validate(Order o) { /* validate */ } }
public class OrderCalculator { public void Calculate(Order o) { /* calc total */ } }
public class OrderRepository { public void Save(Order o) { /* save to DB */ } }
public class OrderEmailService { public void SendConfirmation(Order o) { /* send email */ } }
public class InvoiceService { public void GeneratePdf(Order o) { /* generate PDF */ } }
public class OrderService // Orchestrator only โ delegates to specialists
{
public void ProcessOrder(Order order)
{
_validator.Validate(order);
_calculator.Calculate(order);
_repository.Save(order);
_emailService.SendConfirmation(order);
_invoiceService.GeneratePdf(order);
}
}
โ Clean Code Rules โ Quick Reference:
โข DRY (Don't Repeat Yourself): Extract duplicated logic into shared methods or services. Every piece of knowledge should have a single, unambiguous representation.
โข Meaningful Names: Use intention-revealing names. GetActiveOrdersByCustomerId(int customerId) beats getData(int id).
โข Small Methods: Each method should do ONE thing and fit on one screen (10-20 lines max). If you need to write a comment to explain what a block of code does, extract it into a named method.
โข Avoid Magic Numbers: Use named constants (const int MAX_RETRY = 3) instead of literal numbers scattered in code.
Q1: What is Clean Architecture?
Clean Architecture organizes code into concentric layers: Domain (entities, business rules) at the center, Application (use cases, service interfaces) next, Infrastructure (database, external APIs) in the outer ring, and Presentation (controllers, UI) at the outermost. Dependencies point inward โ infrastructure depends on application, never the reverse.
Q2: What is the Open/Closed Principle in practice?
Use interfaces and polymorphism to add new behavior. For example, add a new payment method (CryptoPayment) by creating a new class implementing IPaymentProcessor โ without modifying existing payment classes or checkout service code.