From bfb917de70ec91394171f19f232b833dfc4aa6bc Mon Sep 17 00:00:00 2001 From: Yury Bayda Date: Sun, 29 Jun 2025 21:49:46 -0700 Subject: [PATCH] Add first draft for legacy code curriculum --- README.md | 15 ++ legacy-code/01-sprout-wrap-worksheet.md | 85 ---------- legacy-code/01-understanding-legacy.md | 102 ++++++++++++ legacy-code/02-kata-identifying.md | 151 +++++++++++++++++ legacy-code/03-safety-net.md | 152 ++++++++++++++++++ .../04-kata-characterization-testing.md | 82 ++++++++++ .../{01-sprout-wrap.md => 05-safe-changes.md} | 85 ++-------- legacy-code/06-kata-sprouting-wrapping.md | 72 +++++++++ legacy-code/07-dependency-breaking.md | 113 +++++++++++++ legacy-code/08-kata-dependency-breaking.md | 78 +++++++++ legacy-code/09-advanced-topics.md | 105 ++++++++++++ .../10-kata-large-scale-refactoring.md | 98 +++++++++++ 12 files changed, 985 insertions(+), 153 deletions(-) delete mode 100644 legacy-code/01-sprout-wrap-worksheet.md create mode 100644 legacy-code/01-understanding-legacy.md create mode 100644 legacy-code/02-kata-identifying.md create mode 100644 legacy-code/03-safety-net.md create mode 100644 legacy-code/04-kata-characterization-testing.md rename legacy-code/{01-sprout-wrap.md => 05-safe-changes.md} (74%) create mode 100644 legacy-code/06-kata-sprouting-wrapping.md create mode 100644 legacy-code/07-dependency-breaking.md create mode 100644 legacy-code/08-kata-dependency-breaking.md create mode 100644 legacy-code/09-advanced-topics.md create mode 100644 legacy-code/10-kata-large-scale-refactoring.md diff --git a/README.md b/README.md index 8d0af6e..65834f4 100644 --- a/README.md +++ b/README.md @@ -91,3 +91,18 @@ Here you can find slides for Clean Code conversations or classes. [tire-pressure-kata-cpp]: https://github.com/Coding-Cuddles/tire-pressure-monitoring-cpp-kata [99-bottles-kata-python]: https://github.com/Coding-Cuddles/99-bottles-of-beer-python-kata [99-bottles-kata-cpp]: https://github.com/Coding-Cuddles/99-bottles-of-beer-cpp-kata + +### Legacy Code (Blue Belt) + +| # | Session Type | Name | +| --: | ------------ | ------------------------------------------------------------------------------------------------------ | +| 1 | Discussion | [Understanding Legacy Code](legacy-code/01-understanding-legacy.md) | +| 2 | Kata | [Identifying Legacy Code Kata](legacy-code/02-kata-identifying.md) | +| 3 | Discussion | [Building a Safety Net](legacy-code/03-safety-net.md) | +| 4 | Kata | [Characterization Testing Kata](legacy-code/04-kata-characterization-testing.md) | +| 5 | Discussion | [Safe, Non-Invasive Changes](legacy-code/05-safe-changes.md) | +| 6 | Kata | [Sprouting & Wrapping Kata](legacy-code/06-kata-sprouting-wrapping.md) | +| 7 | Discussion | [Core Dependency Breaking Techniques](legacy-code/07-dependency-breaking.md) | +| 8 | Kata | [Dependency Breaking Kata](legacy-code/08-kata-dependency-breaking.md) | +| 9 | Discussion | [Advanced Topics & Large-Scale Strategy](legacy-code/09-advanced-topics.md) | +| 10 | Kata | [Large-Scale Refactoring Kata](legacy-code/10-kata-large-scale-refactoring.md) | diff --git a/legacy-code/01-sprout-wrap-worksheet.md b/legacy-code/01-sprout-wrap-worksheet.md deleted file mode 100644 index 3c5cc2a..0000000 --- a/legacy-code/01-sprout-wrap-worksheet.md +++ /dev/null @@ -1,85 +0,0 @@ -### Exercise 1: Sprout a Simple Class - -Topic 1: When refactoring legacy code, how do you decide whether to extract a Sprout Method versus -creating a Sprout Class? What are the trade-offs in terms of testability, cohesion, -and future maintenance? - -Topic 2: How do you recognize when legacy behavior should remain within the current class versus -when it’s time to sprout a new class for better separation of concerns? - -Topic 3: In a large legacy system with high coupling, how can Sprout Method and Sprout Class be -part of a broader strategy to gradually modernize the codebase? - -- **Objective:** Implement the Sprout Method on a simple class. -- **Task:** - - Create a `Car` class with a `drive()` method that prints `"The car is driving!"`. - - Use the **Sprout Method** to add new functionality (e.g., adding a `fuel()` method) to the `Car` - class, **without modifying** the original `drive()` method. - - Add a `fuel()` method that prints `"Refueling the car!"`. - - Extend the `Car` class to call both `drive()` and `fuel()` in the new method - `drive_with_fuel()`. - -- **Hint:** - - Focus on isolating the changes to the `drive_with_fuel()` method, while keeping the original - `drive()` method intact. - -- **Time Limit:** 5-10 minutes - ---- - -### Exercise 2: Implement a Wrap Method - -- **Objective:** Apply the **Wrap Method** to enhance functionality. -- **Task:** - - Create a `Coffee` class with a `cost()` method that prints `"Basic coffee: $5"`. - - Implement a **Wrap Method** where a new class, `MilkDecorator`, wraps the `Coffee` class to - modify its behavior by adding a cost for milk. - - The `MilkDecorator` class should call the original `cost()` method of `Coffee` and then add an - additional cost for milk. - - After that, create a `MilkDecorator` instance and call the `cost()` method to see the new - behavior. - -- **Time Limit:** 5-10 minutes - ---- - -### Exercise 3: Wrapping Multiple Behaviors - -- **Objective:** Apply the **Wrap Method** to chain multiple decorators. -- **Task:** - - Building on the previous exercise, extend the `MilkDecorator` to create a `SugarDecorator`. - - The `SugarDecorator` should add an additional cost for sugar, while still keeping the - `MilkDecorator` functionality. - - Create a `Coffee` object and wrap it with both `MilkDecorator` and `SugarDecorator`. - - Use the `cost()` method to see the total cost after both decorators are applied. - -- **Time Limit:** 5-10 minutes - ---- - -### Exercise 4: Python Decorator for Wrap Method - -- **Objective:** Implement a **decorator function** in Python to wrap a method. -- **Task:** - - Create a `Car` class with a `drive()` method that prints `"The car is driving!"`. - - Write a **Python decorator function** that adds functionality to the `drive()` method. - For instance, before calling the `drive()` method, the decorator could print - `"Starting the car..."`. - - Apply the decorator function to the `drive()` method and call it. - -- **Time Limit:** 5-10 minutes - ---- - -### Exercise 5: Refactor with Decorators - -- **Objective:** Refactor an existing method to use a decorator for extra functionality. -- **Task:** - - Given a simple `User` class with a `login()` method that prints `"User logged in"`, write - a decorator that logs an additional message such as `"Tracking login time..."` whenever the - `login()` method is called. - - Apply the decorator to the `login()` method and test it. - -- **Time Limit:** 5-10 minutes - - diff --git a/legacy-code/01-understanding-legacy.md b/legacy-code/01-understanding-legacy.md new file mode 100644 index 0000000..f2daa47 --- /dev/null +++ b/legacy-code/01-understanding-legacy.md @@ -0,0 +1,102 @@ +% Understanding Legacy Code +% Week 1: Discussion Session +% Advanced Software Engineering + +# Learning Objectives + +- Define legacy code and its characteristics +- Understand the challenges and mindset needed +- Learn to approach legacy code systematically + +# What is Legacy Code? + +> "Code without tests" - Michael Feathers + +## Characteristics + +- Difficult to change +- High coupling +- Low cohesion +- Insufficient documentation +- Missing or outdated tests + +# Discussion Questions + +1. How does Feathers' definition of legacy code differ from the common industry perception? What implications does this have for how we treat our codebase? + +2. In your experience, what are the most challenging aspects of working with legacy code? How do these challenges affect team productivity and morale? + +3. What strategies have you used or seen used to deal with legacy code in your projects? How effective were they? + +# Code Examples + +## C++ Example: Tightly Coupled Legacy Code + +```cpp +class DataProcessor { + Database db; + Logger logger; + +public: + void process(const std::string& data) { + db.connect("hardcoded:connection:string"); + logger.log("Processing started"); + + // Complex business logic intertwined with + // database calls and logging + if (data.length() > 0) { + db.insert(data); + logger.log("Data inserted"); + } + + db.disconnect(); + } +}; +``` + +## Python Example: Missing Abstraction + +```python +class ReportGenerator: + def generate_report(self, data): + # Direct database access + conn = mysql.connector.connect( + host="localhost", + database="reports_db", + user="admin" + ) + + # Business logic mixed with data access + cursor = conn.cursor() + cursor.execute("SELECT * FROM sales") + results = cursor.fetchall() + + # Direct file system access + with open('report.pdf', 'wb') as f: + pdf = PDF() + for row in results: + pdf.add_line(row) + f.write(pdf.output()) +``` + +# Key Takeaways + +1. Legacy code is any code without tests, regardless of age +2. Working with legacy code requires a systematic approach +3. Understanding code dependencies is crucial +4. Small, safe changes are preferable to large refactoring + +# Required Reading + +- Chapters 1-3 of "Working Effectively with Legacy Code" +- Focus on: + - Understanding the legacy code dilemma + - The role of testing + - Basic strategies for working with legacy code + +# Next Week + +Prepare for hands-on kata: +- Identifying legacy code characteristics +- Initial assessment techniques +- Setting up a testing strategy diff --git a/legacy-code/02-kata-identifying.md b/legacy-code/02-kata-identifying.md new file mode 100644 index 0000000..7685e22 --- /dev/null +++ b/legacy-code/02-kata-identifying.md @@ -0,0 +1,151 @@ +% Identifying Legacy Code Kata +% Week 2: Practical Session +% Advanced Software Engineering + +# Learning Objectives + +- Identify characteristics of legacy code +- Assess code for testability +- Plan initial refactoring steps + +# Kata Instructions + +Work in pairs to analyze and improve the following code examples. + +## C++ Starting Point + +```cpp +class OrderProcessor { +private: + static OrderProcessor* instance; + Database* db; + EmailService* emailService; + + OrderProcessor() { + db = new Database("production_db"); + emailService = new EmailService(); + } + +public: + static OrderProcessor* getInstance() { + if (instance == null) { + instance = new OrderProcessor(); + } + return instance; + } + + bool processOrder(Order order) { + if (!db->isConnected()) { + db->connect(); + } + + bool success = false; + if (order.getTotal() > 0) { + success = db->executeQuery( + "INSERT INTO orders VALUES (" + + order.getId() + "," + + order.getCustomerId() + "," + + order.getTotal() + ")" + ); + + if (success) { + emailService->sendEmail( + order.getCustomerEmail(), + "Order Confirmation", + "Your order #" + order.getId() + " has been processed." + ); + } + } + return success; + } +}; +``` + +## Python Starting Point + +```python +class ReportGenerator: + def __init__(self): + self.db = Database() + self.template_engine = TemplateEngine() + + def generate_monthly_report(self, month, year): + # Connect to database + self.db.connect() + + # Get data + sales_data = self.db.execute_query( + f"SELECT * FROM sales WHERE MONTH(date) = {month} " + f"AND YEAR(date) = {year}" + ) + + # Process data + total = sum(sale['amount'] for sale in sales_data) + avg = total / len(sales_data) if sales_data else 0 + + # Generate report + report = self.template_engine.load_template('monthly_report') + report.set_data({ + 'month': month, + 'year': year, + 'sales': sales_data, + 'total': total, + 'average': avg + }) + + # Save to file system + filename = f"report_{month}_{year}.pdf" + report.save_as_pdf(filename) + + # Send email + email = EmailService() + email.send( + to="management@company.com", + subject=f"Monthly Report {month}/{year}", + body="Please find attached the monthly report.", + attachment=filename + ) +``` + +# Tasks + +1. Code Analysis (30 minutes) + - Identify all dependencies in the code + - List specific legacy code characteristics + - Document potential risks in making changes + +2. Testability Assessment (30 minutes) + - Identify what makes the code hard to test + - List specific changes needed to enable testing + - Prioritize which parts need testing first + +3. Refactoring Plan (30 minutes) + - Create a step-by-step plan for adding tests + - Identify seams that could be used + - Document potential breaking points + +4. Discussion (30 minutes) + - Present findings to another pair + - Compare different approaches + - Agree on best practices for similar situations + +# Success Criteria + +- Comprehensive list of dependencies identified +- Clear testing strategy documented +- Practical refactoring plan created +- Risks and mitigation strategies outlined + +# Notes for Facilitators + +- Ensure pairs rotate roles during the exercise +- Encourage discussion of real-world examples +- Focus on identifying patterns that can be applied to actual work situations +- Emphasize the importance of small, safe steps + +# Follow-up + +Prepare for next week's session on: +- Building a safety net +- Writing characterization tests +- Understanding test coverage strategies diff --git a/legacy-code/03-safety-net.md b/legacy-code/03-safety-net.md new file mode 100644 index 0000000..a9eb977 --- /dev/null +++ b/legacy-code/03-safety-net.md @@ -0,0 +1,152 @@ +% Building a Safety Net +% Week 3: Discussion Session +% Advanced Software Engineering + +# Learning Objectives + +- Understand the importance of characterization tests +- Learn techniques for safely adding tests to legacy code +- Master the art of identifying and creating seams + +# Understanding Characterization Tests + +## What are they? +- Tests that describe current behavior +- Document "what is" rather than "what should be" +- First line of defense when working with legacy code + +## Why do we need them? +- Capture existing behavior +- Provide safety for refactoring +- Document system behavior +- Enable incremental improvement + +# Discussion Questions + +1. How do characterization tests differ from traditional unit tests? When would you prefer one over the other? + +2. What strategies can be employed when the legacy code seems impossible to test? How do you balance pragmatism with best practices? + +3. How do you determine the appropriate level of characterization testing before beginning refactoring? What factors influence this decision? + +# Code Examples + +## C++ Example: Adding Characterization Tests + +```cpp +// Original Legacy Code +class InvoiceCalculator { +public: + double calculateTotal(const std::vector& items) { + double total = 0; + for (const auto& item : items) { + total += item.quantity * item.price; + if (item.quantity > 10) { + total *= 0.95; // Bulk discount + } + if (total > 1000) { + total *= 0.90; // Large order discount + } + } + return total; + } +}; + +// Characterization Test +TEST_CASE("InvoiceCalculator preserves existing behavior") { + InvoiceCalculator calc; + std::vector items = { + {5, 100.0}, // Regular case + {11, 100.0}, // Bulk discount case + {1, 1001.0} // Large order case + }; + + // Capture current behavior + double result = calc.calculateTotal(items); + + // Document the exact current behavior + REQUIRE(result == Approx(1850.95)); + // Note: This might not be correct behavior, + // but it's what the system currently does +} +``` + +## Python Example: Characterization Testing Technique + +```python +# Original Legacy Code +class TaxCalculator: + def calculate_tax(self, income, state): + if state == "NY": + base_rate = 0.04 + if income > 50000: + base_rate += 0.02 + if income > 100000: + base_rate += 0.03 + elif state == "CA": + base_rate = 0.03 + if income > 75000: + base_rate += 0.04 + else: + base_rate = 0.01 + + return income * base_rate + +# Characterization Tests +def test_capture_current_tax_behavior(): + calc = TaxCalculator() + + # Test cases to capture current behavior + test_cases = [ + (30000, "NY"), + (60000, "NY"), + (120000, "NY"), + (50000, "CA"), + (80000, "CA"), + (45000, "TX") + ] + + # Store current behavior + results = { + case: calc.calculate_tax(*case) + for case in test_cases + } + + # Verify behavior remains unchanged + for case in test_cases: + assert calc.calculate_tax(*case) == results[case], \ + f"Behavior changed for {case}" +``` + +# Key Techniques + +1. Capturing Current Behavior + - Use automated tools where possible + - Document exact current outputs + - Include edge cases + +2. Creating Seams + - Extract Method + - Extract Interface + - Parameterize Constructor + - Break Dependencies + +3. Testing Strategies + - Start with broad integration tests + - Gradually add more specific tests + - Use approval testing for complex output + +# Required Reading + +- Chapters 8-10 of "Working Effectively with Legacy Code" +- Focus on: + - Breaking Dependencies + - Sensing and Separation + - Test Organization + +# Next Week + +Prepare for hands-on kata: +- Writing effective characterization tests +- Creating seams in legacy code +- Organizing test suites effectively diff --git a/legacy-code/04-kata-characterization-testing.md b/legacy-code/04-kata-characterization-testing.md new file mode 100644 index 0000000..534cf68 --- /dev/null +++ b/legacy-code/04-kata-characterization-testing.md @@ -0,0 +1,82 @@ +% Characterization Testing Kata +% Week 4: Practical Session +% Advanced Software Engineering + +# Learning Objectives + +- Practice writing characterization tests for legacy code +- Identify and create seams for testing +- Safely document and preserve existing behavior + +# Kata Instructions + +Work in pairs to add tests to the following legacy code examples. + +## C++ Starting Point + +```cpp +class DiscountCalculator { +public: + double calculate(double price, int quantity) { + double total = price * quantity; + if (quantity > 10) { + total *= 0.95; // Bulk discount + } + if (total > 1000) { + total *= 0.90; // Large order discount + } + return total; + } +}; +``` + +## Python Starting Point + +```python +class ShippingCostCalculator: + def calculate(self, weight, destination): + cost = 5.0 + if destination == "international": + cost += 15.0 + if weight > 10: + cost += (weight - 10) * 2.0 + return cost +``` + +# Tasks + +1. **Write Characterization Tests (30 min)** + - Write tests that capture the current behavior for a variety of inputs + - Document any surprising or unclear behavior + +2. **Identify Seams (20 min)** + - Find places where you can introduce tests without changing logic + - Note any dependencies that make testing difficult + +3. **Refactor for Testability (30 min)** + - Make minimal changes to enable more testing (e.g., extract methods, parameterize dependencies) + - Add additional tests as needed + +4. **Review and Discuss (20 min)** + - Share your tests and findings with another pair + - Discuss strategies for handling untestable code + +# Success Criteria + +- Comprehensive set of characterization tests +- Identification of seams and testability issues +- Minimal, safe refactoring to enable testing +- Clear documentation of current behavior + +# Notes for Facilitators + +- Encourage participants to avoid changing logic +- Focus on capturing, not correcting, behavior +- Discuss the value of approval testing for complex outputs + +# Follow-up + +Prepare for next week's session on: +- Safe, non-invasive changes +- Sprouting and wrapping techniques +- Planning incremental improvements diff --git a/legacy-code/01-sprout-wrap.md b/legacy-code/05-safe-changes.md similarity index 74% rename from legacy-code/01-sprout-wrap.md rename to legacy-code/05-safe-changes.md index 792a78d..a0fa8d8 100644 --- a/legacy-code/01-sprout-wrap.md +++ b/legacy-code/05-safe-changes.md @@ -1,14 +1,8 @@ ---- -title: Working Effectively with Legacy Code -subtitle: Chapter 6 - I Dont Have Much Time and I Have to Change It -... +% Safe, Non-Invasive Changes +% Week 5: Discussion Session +% Advanced Software Engineering -# Pre-work - -- Book: *Working Effectively with Legacy Code* by Michael Feathers -- Focus: Chapter 6: I Dont Have Much Time and I Have to Change It - -# Timetable +# Session Timetable | Activity | Time | |-----------------------|--------| @@ -26,17 +20,13 @@ subtitle: Chapter 6 - I Dont Have Much Time and I Have to Change It - Ever added a wrapper instead of modifying directly? - Share in chat: "Sprout" or "Wrap" — which sounds safer to you? -\note{ -Use this to establish mental models and prepare learners to explore change-based techniques. -} - # Sprout Method - Add new logic in a new method - Call the new method from existing one - No edits to original logic -# Legacy Code Example +## Python Example ```python class VehicleLogger: @@ -50,7 +40,7 @@ class VehicleLogger: print(f"Energy efficiency: {efficiency:.2f} km/kWh") ``` -# Improved Code Example +### Improved Code Example ```python class VehicleLogger: @@ -66,12 +56,6 @@ class VehicleLogger: efficiency = distance_km / amount_used print(f"Energy efficiency: {efficiency:.2f} km/kWh") ``` -\note{ -Benefits: - Isolated new behavior (_log_efficiency) → easier to test - Minimal change to existing method → reduced risk - Future changes (e.g., hybrid logic) can be added safely to the new method -} # Sprout Class @@ -79,7 +63,7 @@ Benefits: - Keeps old logic intact - Ideal for complex changes needing state -# Legacy code Example +## Python Example ```python class VehicleLogger: @@ -93,7 +77,7 @@ class VehicleLogger: print(f"Energy efficiency: {efficiency:.2f} km/kWh") ``` -# Improved code Example +### Improved Code Example ```python class VehicleLogger: @@ -110,18 +94,10 @@ class EVLogger: efficiency = distance_km / energy_kwh print(f"Energy efficiency: {efficiency:.2f} km/kWh") ``` -\note{ -Benefits: - New behavior is isolated in EVLogger - We avoid changing or bloating the legacy method with EV-specific logic - EVLogger is easy to test independently -} + # Exercise 1 -- Prompt - - When refactoring legacy code, how do you decide whether to extract a Sprout Method versus -creating a Sprout Class? What are the trade-offs in terms of testability, cohesion, -and future maintenance? +- When refactoring legacy code, how do you decide whether to extract a Sprout Method versus creating a Sprout Class? What are the trade-offs in terms of testability, cohesion, and future maintenance? - Time limit: 15 minutes # Wrap Method @@ -131,14 +107,15 @@ and future maintenance? - New logic (e.g. logging, validation) is inserted safely - No change to existing callers — they still call same method -## Legacy code Example +## Python Example ```python class Engine: def calculate_torque(self, rpm, throttle): return (rpm * throttle) / 100.0 ``` -# Improved code Example + +### Improved Code Example ```python class Engine: @@ -151,14 +128,6 @@ class Engine: print(f"[LOG] Output: torque={torque}") return torque ``` -\note{ -Benefits: - Add behavior without modifying original logic - Keep method name unchanged for existing callers - Enable logging, validation, or instrumentation - Safe and reversible change - Supports incremental refactoring -} # Wrap Class @@ -166,7 +135,7 @@ Benefits: - Delegates calls while injecting new logic - Helps isolate change when subclassing is risky -## Legacy code Example +## Python Example ```python class Engine: @@ -174,7 +143,7 @@ class Engine: return (rpm * throttle) / 100.0 ``` -# Improved code Example +### Improved Code Example ```python class LoggingEngine(Engine): @@ -184,16 +153,9 @@ class LoggingEngine(Engine): print(f"[LOG] Torque result: {torque}") return torque ``` -\note{ -Benefits: - Extend behavior via subclassing - Override methods to add new logic - Avoid modifying the original class - Useful for testing or temporary changes - Keeps legacy code stable while injecting behavior -} # Usage + ```python engine = LoggingEngine() engine.calculate_torque(3000, 70) @@ -201,9 +163,7 @@ engine.calculate_torque(3000, 70) # Exercise 2 -- Prompt - - How do you recognize when legacy behavior should remain within the current class versus -when it’s time to sprout a new class for better separation of concerns? +- How do you recognize when legacy behavior should remain within the current class versus when it’s time to sprout a new class for better separation of concerns? - Time limit: 15 minutes # Decorators and the Wrap Method @@ -236,11 +196,6 @@ car = LoggingDecorator(Vehicle()) car.drive() ``` -\note{ -This decorates a Vehicle object to add logging behavior without modifying the original class. -} - - # Comparison & Benefits | Technique | Location
of Change | Scope | Purpose | Risk | Code Impact | @@ -250,12 +205,6 @@ This decorates a Vehicle object to add logging behavior without modifying the or | Wrap Method | Same class | One method | Insert logic around
method| Low | Rename + wrap
method | | Wrap Class | Subclass | Multiple methods| Modify/extend
behavior | Med | New subclass
created | - -\note{ -Summarize the differences in the implementation of decorators in C++ and Python, emphasizing the trade-offs between the two languages in terms of flexibility and performance. -} - - # Summary - **Sprout** = add new code that old code calls diff --git a/legacy-code/06-kata-sprouting-wrapping.md b/legacy-code/06-kata-sprouting-wrapping.md new file mode 100644 index 0000000..c9d8cde --- /dev/null +++ b/legacy-code/06-kata-sprouting-wrapping.md @@ -0,0 +1,72 @@ +% Sprouting & Wrapping Kata +% Week 6: Practical Session +% Advanced Software Engineering + +# Learning Objectives + +- Practice sprouting and wrapping techniques +- Make safe, incremental changes to legacy code +- Validate changes with tests + +# Kata Instructions + +Work in pairs to apply sprouting and wrapping to the following code. + +## C++ Starting Point + +```cpp +class UserManager { +public: + bool authenticate(const std::string& user, const std::string& pass) { + // Hardcoded credentials (legacy) + return user == "admin" && pass == "password"; + } +}; +``` + +## Python Starting Point + +```python +class UserManager: + def authenticate(self, user, password): + # Hardcoded credentials (legacy) + return user == "admin" and password == "password" +``` + +# Tasks + +1. **Sprouting (20 min)** + - Add a new authentication method that uses a user database or config file + - Do not modify the original method + +2. **Wrapping (20 min)** + - Create a wrapper that logs authentication attempts + - Ensure the wrapper can be tested independently + +3. **Testing (30 min)** + - Write tests for both the new and legacy authentication methods + - Validate that wrapping does not change legacy behavior + +4. **Review (20 min)** + - Share your approach with another pair + - Discuss trade-offs and risks + +# Success Criteria + +- New functionality added via sprouting +- Legacy code safely wrapped +- Comprehensive tests for both old and new code +- Risks and trade-offs discussed + +# Notes for Facilitators + +- Encourage minimal changes to legacy code +- Focus on testability and safety +- Discuss real-world applications of these techniques + +# Follow-up + +Prepare for next week's session on: +- Breaking dependencies +- Using adapters and seams +- Refactoring for flexibility diff --git a/legacy-code/07-dependency-breaking.md b/legacy-code/07-dependency-breaking.md new file mode 100644 index 0000000..68d4a04 --- /dev/null +++ b/legacy-code/07-dependency-breaking.md @@ -0,0 +1,113 @@ +% Core Dependency Breaking Techniques +% Week 7: Discussion Session +% Advanced Software Engineering + +# Learning Objectives + +- Understand dependency breaking techniques +- Learn to use adapters, seams, and interfaces +- Enable safe refactoring of tightly coupled code + +# Dependency Breaking Techniques + +## Adapters +- Introduce new interfaces to decouple code +- Allow substitution of dependencies for testing + +## Seams +- Places where you can alter behavior without editing code +- Types: Preprocessor, Link, Object, and Parameter seams + +# Discussion Questions + +1. What are the most common sources of hard-to-break dependencies in C++ and Python codebases? +2. How do adapters and seams differ in their application? When would you use each? +3. What are the risks of introducing new interfaces or seams? How can you mitigate them? +4. Share an example where breaking a dependency enabled a major refactoring or improvement. + +# Code Examples + +## C++ Example: Adapter Pattern + +```cpp +class LegacyLogger { +public: + void writeLog(const std::string& msg) { + // ... legacy logging ... + } +}; + +class ILogger { +public: + virtual void log(const std::string& msg) = 0; +}; + +class LoggerAdapter : public ILogger { + LegacyLogger legacy; +public: + void log(const std::string& msg) override { + legacy.writeLog(msg); + } +}; +``` + +## C++ Example: Object Seam + +```cpp +class PaymentProcessor { + ILogger* logger; +public: + PaymentProcessor(ILogger* l) : logger(l) {} + void process(double amount) { + logger->log("Processing payment"); + // ... + } +}; +``` + +## Python Example: Adapter Pattern + +```python +class LegacyLogger: + def write_log(self, msg): + # ... legacy logging ... + pass + +class LoggerAdapter: + def __init__(self, legacy_logger): + self.legacy = legacy_logger + def log(self, msg): + self.legacy.write_log(msg) +``` + +## Python Example: Object Seam + +```python +class PaymentProcessor: + def __init__(self, logger): + self.logger = logger + def process(self, amount): + self.logger.log("Processing payment") + # ... +``` + +# Key Takeaways + +- Adapters and seams enable testability and flexibility +- Breaking dependencies is essential for safe refactoring +- Use interfaces and parameterization to inject dependencies + +# Required Reading + +- Chapters 11, 14-15 of "Working Effectively with Legacy Code" +- Focus on: + - Dependency breaking patterns + - Seams and their types + - Refactoring strategies + +# Next Week + +Prepare for hands-on kata: +- Implementing adapters and seams +- Refactoring for testability +- Managing risk during dependency breaking diff --git a/legacy-code/08-kata-dependency-breaking.md b/legacy-code/08-kata-dependency-breaking.md new file mode 100644 index 0000000..7245c54 --- /dev/null +++ b/legacy-code/08-kata-dependency-breaking.md @@ -0,0 +1,78 @@ +% Dependency Breaking Kata +% Week 8: Practical Session +% Advanced Software Engineering + +# Learning Objectives + +- Practice breaking dependencies using adapters and seams +- Refactor legacy code for testability +- Safely introduce new interfaces + +# Kata Instructions + +Work in pairs to break dependencies in the following code. + +## C++ Starting Point + +```cpp +class EmailSender { +public: + void send(const std::string& to, const std::string& body) { + // Directly uses SMTP library + SmtpClient smtp; + smtp.connect("smtp.company.com"); + smtp.sendMail(to, body); + smtp.disconnect(); + } +}; +``` + +## Python Starting Point + +```python +class EmailSender: + def send(self, to, body): + # Directly uses SMTP library + smtp = SmtpClient() + smtp.connect("smtp.company.com") + smtp.send_mail(to, body) + smtp.disconnect() +``` + +# Tasks + +1. **Identify Hard Dependencies (15 min)** + - List all direct dependencies in the code + - Discuss why they make testing difficult + +2. **Introduce Adapters (25 min)** + - Refactor to use an interface or protocol for the SMTP client + - Allow injection of a mock or test double + +3. **Create Seams (30 min)** + - Identify and implement seams for testing + - Write tests using the new seams/adapters + +4. **Review and Discuss (20 min)** + - Share your refactoring and tests with another pair + - Discuss trade-offs and risks + +# Success Criteria + +- Dependencies broken via adapters or seams +- Code is testable with mocks or stubs +- Minimal changes to legacy logic +- Risks and trade-offs discussed + +# Notes for Facilitators + +- Encourage use of interfaces/protocols +- Focus on testability and minimal change +- Discuss real-world applications of these techniques + +# Follow-up + +Prepare for next week's session on: +- Large-scale refactoring +- Managing technical debt +- Strategic improvement planning diff --git a/legacy-code/09-advanced-topics.md b/legacy-code/09-advanced-topics.md new file mode 100644 index 0000000..e4387a3 --- /dev/null +++ b/legacy-code/09-advanced-topics.md @@ -0,0 +1,105 @@ +% Advanced Topics & Large-Scale Strategy +% Week 9: Discussion Session +% Advanced Software Engineering + +# Learning Objectives + +- Understand strategies for large-scale refactoring +- Learn to manage technical debt +- Plan and execute long-term improvements + +# Large-Scale Refactoring + +## Approaches +- Incremental vs. Big Bang +- Strangler Fig Pattern +- Parallel Change (Expand and Contract) + +## Managing Technical Debt +- Identifying and prioritizing debt +- Communicating debt to stakeholders +- Balancing business needs and code quality + +# Discussion Questions + +1. What are the risks and benefits of incremental vs. big bang refactoring? +2. How do you prioritize technical debt in a large codebase? What frameworks or tools do you use? +3. How can you ensure business continuity during large-scale refactoring? +4. Share a real-world example of a successful (or failed) large-scale refactoring. What were the key lessons? + +# Code Examples + +## C++ Example: Strangler Fig Pattern + +```cpp +// Legacy class +class LegacyOrderSystem { +public: + void processOrder(const Order& order) { + // ... legacy logic ... + } +}; + +// New class +class NewOrderSystem { +public: + void processOrder(const Order& order) { + // ... improved logic ... + } +}; + +// Facade +class OrderFacade { + LegacyOrderSystem legacy; + NewOrderSystem modern; +public: + void processOrder(const Order& order, bool useNew) { + if (useNew) { + modern.processOrder(order); + } else { + legacy.processOrder(order); + } + } +}; +``` + +## Python Example: Parallel Change + +```python +class LegacyBilling: + def calculate(self, invoice): + # ... legacy logic ... + pass + +class NewBilling: + def calculate(self, invoice): + # ... improved logic ... + pass + +def calculate_billing(invoice, use_new=False): + if use_new: + return NewBilling().calculate(invoice) + else: + return LegacyBilling().calculate(invoice) +``` + +# Key Takeaways + +- Large-scale refactoring requires planning and communication +- Use patterns like Strangler Fig and Parallel Change +- Manage technical debt proactively + +# Required Reading + +- Chapters 21-24 of "Working Effectively with Legacy Code" +- Focus on: + - Large-scale refactoring strategies + - Technical debt management + - Communication and planning + +# Next Week + +Prepare for hands-on kata: +- Applying large-scale refactoring +- Managing risk and business continuity +- Measuring success diff --git a/legacy-code/10-kata-large-scale-refactoring.md b/legacy-code/10-kata-large-scale-refactoring.md new file mode 100644 index 0000000..113905b --- /dev/null +++ b/legacy-code/10-kata-large-scale-refactoring.md @@ -0,0 +1,98 @@ +% Large-Scale Refactoring Kata +% Week 10: Practical Session +% Advanced Software Engineering + +# Learning Objectives + +- Apply large-scale refactoring strategies +- Manage technical debt in practice +- Ensure business continuity during change + +# Kata Instructions + +Work in pairs to refactor the following codebase excerpt using large-scale strategies. + +## C++ Starting Point (Excerpt) + +```cpp +class CustomerManager { +public: + double calculateDiscount(const Customer& customer) { + if (customer.getType() == "VIP") { + return 0.2; + } else if (customer.getType() == "Regular") { + return 0.1; + } else { + return 0.0; + } + } + // ... many more methods ... +}; + +class OrderManager { +public: + double calculateOrderTotal(const Order& order) { + CustomerManager cm; + double discount = cm.calculateDiscount(order.getCustomer()); + return order.getSubtotal() * (1.0 - discount); + } + // ... many more methods ... +}; +``` + +## Python Starting Point (Excerpt) + +```python +class CustomerManager: + def calculate_discount(self, customer): + if customer.type == "VIP": + return 0.2 + elif customer.type == "Regular": + return 0.1 + else: + return 0.0 + # ... many more methods ... + +class OrderManager: + def calculate_order_total(self, order): + cm = CustomerManager() + discount = cm.calculate_discount(order.customer) + return order.subtotal * (1.0 - discount) + # ... many more methods ... +``` + +# Tasks + +1. **Identify Refactoring Targets (20 min)** + - List code smells and technical debt + - Prioritize areas for improvement + +2. **Apply Large-Scale Patterns (30 min)** + - Use Strangler Fig or Parallel Change to introduce new logic + - Ensure old and new code can coexist + +3. **Manage Risk (20 min)** + - Plan for business continuity + - Write tests to validate both old and new behavior + +4. **Review and Discuss (20 min)** + - Present your approach to another pair + - Discuss lessons learned and remaining risks + +# Success Criteria + +- Large-scale refactoring applied safely +- Technical debt identified and addressed +- Business continuity maintained +- Lessons and risks documented + +# Notes for Facilitators + +- Encourage incremental, reversible changes +- Focus on communication and planning +- Discuss real-world applications and outcomes + +# Follow-up + +- Review the full curriculum +- Plan for ongoing improvement and learning