Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions legacy-code/01-sprout-wrap-worksheet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
### 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


270 changes: 270 additions & 0 deletions legacy-code/01-sprout-wrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
---
title: Working Effectively with Legacy Code
subtitle: Chapter 6 - I Dont Have Much Time and I Have to Change It
...

# 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

| Activity | Time |
|-----------------------|--------|
| Greetings, Warmup | 5 min |
| Sprout Method & Class | 15 min |
| Exercise 1 | 15 min |
| Wrap Method & Class | 15 min |
| Exercise 2 | 15 min |
| Summary | 10 min |
| Closing | 5 min |

# Warmup

- What’s your current strategy when you need to change legacy code?
- 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
class VehicleLogger:
def log_trip(self, vehicle_type, distance_km, fuel_used_liters):
print(f"Trip: {distance_km} km")
if vehicle_type == "gasoline":
efficiency = distance_km / fuel_used_liters
print(f"Fuel efficiency: {efficiency:.2f} km/l")
elif vehicle_type == "electric":
efficiency = distance_km / fuel_used_liters # misuse of field
print(f"Energy efficiency: {efficiency:.2f} km/kWh")
```

# Improved Code Example

```python
class VehicleLogger:
def log_trip(self, vehicle_type, distance_km, fuel_or_energy_used):
print(f"Trip: {distance_km} km")
self._log_efficiency(vehicle_type, distance_km, fuel_or_energy_used)

def _log_efficiency(self, vehicle_type, distance_km, amount_used):
if vehicle_type == "gasoline":
efficiency = distance_km / amount_used
print(f"Fuel efficiency: {efficiency:.2f} km/l")
elif vehicle_type == "electric":
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

- Create a new class for new behavior
- Keeps old logic intact
- Ideal for complex changes needing state

# Legacy code Example

```python
class VehicleLogger:
def log_trip(self, vehicle_type, distance_km, fuel_used_liters):
print(f"Trip: {distance_km} km")
if vehicle_type == "gasoline":
efficiency = distance_km / fuel_used_liters
print(f"Fuel efficiency: {efficiency:.2f} km/l")
elif vehicle_type == "electric":
efficiency = distance_km / fuel_used_liters
print(f"Energy efficiency: {efficiency:.2f} km/kWh")
```

# Improved code Example

```python
class VehicleLogger:
def log_trip(self, vehicle_type, distance_km, fuel_or_energy_used):
print(f"Trip: {distance_km} km")
if vehicle_type == "electric":
EVLogger().log_efficiency(distance_km, fuel_or_energy_used)
elif vehicle_type == "gasoline":
efficiency = distance_km / fuel_or_energy_used
print(f"Fuel efficiency: {efficiency:.2f} km/l")

class EVLogger:
def log_efficiency(self, distance_km, energy_kwh):
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?
- Time limit: 15 minutes

# Wrap Method

- The original method is renamed
- A new method with the same name as the original wraps it
- New logic (e.g. logging, validation) is inserted safely
- No change to existing callers — they still call same method

## Legacy code Example

```python
class Engine:
def calculate_torque(self, rpm, throttle):
return (rpm * throttle) / 100.0
```
# Improved code Example

```python
class Engine:
def _calculate_torque_original(self, rpm, throttle):
return (rpm * throttle) / 100.0

def calculate_torque(self, rpm, throttle):
print(f"[LOG] Inputs: rpm={rpm}, throttle={throttle}")
torque = self._calculate_torque_original(rpm, throttle)
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

- Create a new class that wraps the legacy class
- Delegates calls while injecting new logic
- Helps isolate change when subclassing is risky

## Legacy code Example

```python
class Engine:
def calculate_torque(self, rpm, throttle):
return (rpm * throttle) / 100.0
```

# Improved code Example

```python
class LoggingEngine(Engine):
def calculate_torque(self, rpm, throttle):
print(f"[LOG] Calculating torque: rpm={rpm}, throttle={throttle}")
torque = super().calculate_torque(rpm, throttle)
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)
```

# 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?
- Time limit: 15 minutes

# Decorators and the Wrap Method

- Decorators are a special form of wrapping that allows for dynamic extension.
- In the context of the Wrap Method, decorators provide an elegant solution for layering additional behavior.

### Python Decorator Pattern Example

```python
class Vehicle:
def drive(self):
return "Driving"

class VehicleDecorator(Vehicle):
def __init__(self, vehicle):
self._vehicle = vehicle

def drive(self):
return self._vehicle.drive()

class LoggingDecorator(VehicleDecorator):
def drive(self):
action = self._vehicle.drive()
print(f"[LOG] Action: {action}")
return action

# Usage
car = LoggingDecorator(Vehicle())
car.drive()
```

\note{
This decorates a Vehicle object to add logging behavior without modifying the original class.
}


# Comparison & Benefits

| Technique | Location <br/>of Change | Scope | Purpose | Risk | Code Impact |
|---------------|------------|-----------------|-------------------------------|------|---------------------------|
| Sprout Method | Same class | One method | Isolate new logic | Low | Add method,<br/> call it |
| Sprout Class | New class | Functionality | Extract cohesive<br/>behavior | Low | New class, <br/>inject it |
| Wrap Method | Same class | One method | Insert logic around<br/>method| Low | Rename + wrap<br/>method |
| Wrap Class | Subclass | Multiple methods| Modify/extend<br/>behavior | Med | New subclass<br/>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
- **Wrap** = write code that calls into the old code
- Use *Method* for simple logic, *Class* for complex or stateful logic
- These give you *safe entry points* into legacy code

# Final Thought

> "You don't need to clean up the whole kitchen to make a cup of tea. Just clear a spot."
>
> — Inspired by Feathers’ philosophy