Boy Scout Rule
The Rule
"Always leave the code cleaner than you found it."
Applying It in Magento 2
When Touching Code
// BEFORE: Messy code you encounter while fixing a bug
public function getOrderTotal($orderId)
{
$order = $this->_orderFactory->create();
$order->load($orderId);
$total = $order->getGrandTotal();
$tax = $order->getTaxAmount();
$shipping = $order->getShippingAmount();
$discount = $order->getDiscountAmount();
return $total + $tax + $shipping - $discount;
}
// AFTER: Clean up while you're there
public function getOrderTotal(int $orderId): float
{
$order = $this->orderRepository->get($orderId);
return (float) (
$order->getGrandTotal()
+ $order->getTaxAmount()
+ $order->getShippingAmount()
- abs($order->getDiscountAmount())
);
}
Small, Safe Changes
Boy Scout Activities (5-15 minutes each):
- Extract magic numbers to constants
- Add type hints to method signatures
- Remove unused imports
- Rename unclear variable names
- Add return type declarations
- Replace deprecated method calls
- Fix one PHPStan warning
Measuring Progress
# Before cleanup
vendor/bin/phpstan analyse --level=6 src/ | tail -5
# Found 47 errors
# After boy scout session
vendor/bin/phpstan analyse --level=6 src/ | tail -5
# Found 42 errors
# Track over time
rg -c 'TODO|FIXME' src/ | wc -l
# 23 → 19 after cleanup
Team Practice
1. Each PR includes one small cleanup unrelated to the feature
2. No dedicated cleanup sprint needed
3. Code quality improves gradually
4. No risk of breaking changes
5. Everyone participates equally
Strangler Fig Pattern
Concept
Named after the strangler fig tree that grows around a host tree, eventually replacing it. Incrementally replace legacy code by building new alongside old.
Magento 2 Application
Phase 1: Identify seams
// Legacy monolithic class
class OrderService
{
public function processOrder($data) { /* 500 lines */ }
public function validateOrder($data) { /* 200 lines */ }
public function calculateTotals($data) { /* 300 lines */ }
public function sendNotifications($data) { /* 150 lines */ }
}
Phase 2: Extract to new service
// New clean service
interface OrderValidatorInterface
{
public function validate(array $data): ValidationResult;
}
class OrderValidator implements OrderValidatorInterface
{
public function validate(array $data): ValidationResult
{
// Clean, testable validation logic
}
}
// Update old code to delegate
class OrderService
{
public function __construct(
private OrderValidatorInterface $validator
) {}
public function processOrder($data)
{
$result = $this->validator->validate($data);
// ... rest of processing
}
}
Phase 3: Route traffic
<!-- di.xml configuration -->
<config>
<type name="Vendor\NewModule\Service\OrderProcessorInterface">
<plugin name="route-to-new" type="Vendor\NewModule\Plugin\OrderRoutePlugin"/>
</type>
</config>
Phase 4: Remove old code
// Once all traffic routes to new service
class OrderService
{
/**
* @deprecated Use OrderValidatorInterface instead
*/
public function validateOrder($data)
{
@trigger_error('Use OrderValidatorInterface', E_USER_DEPRECATED);
return $this->validator->validate($data);
}
}
Benefits
- No big-bang rewrite risk
- Continuous delivery of value
- Easy rollback at each step
- Team learns incrementally
Incremental Refactoring
Strategy: Small Steps, Big Impact
Refactoring Catalog for Magento
Extract Service Class
// BEFORE: Logic mixed into model
public class Product extends AbstractModel
{
public function calculateDiscount()
{
// 50 lines of discount logic
}
public function applyPromotion()
{
// 30 lines of promotion logic
}
}
// AFTER: Separate service
class ProductDiscountService
{
public function calculateDiscount(Product $product): float
{
// Clean, testable logic
}
}
Replace Helper with Service
// BEFORE: Helper doing too much
class Data extends AbstractHelper
{
public function formatPrice($price) { /* ... */ }
public function validateEmail($email) { /* ... */ }
public function generateToken() { /* ... */ }
}
// AFTER: Focused services
interface PriceFormatterInterface { /* ... */ }
interface EmailValidatorInterface { /* ... */ }
interface TokenGeneratorInterface { /* ... */ }
Introduce Repository Pattern
// BEFORE: Direct resource model usage
$order = $this->orderFactory->create();
$order->load($id);
// AFTER: Repository abstraction
$order = $this->orderRepository->get($id);
Refactoring Checklist
Before:
- [ ] Write characterization test for current behavior
- [ ] Document current API contract
- [ ] Create backup/branch
- [ ] Identify all callers of changed code
During:
- [ ] Make one small change at a time
- [ ] Run tests after each change
- [ ] Keep backward compatibility
- [ ] Update all callers
After:
- [ ] All tests passing
- [ ] PHPStan level maintained or improved
- [ ] Documentation updated
- [ ] Deprecated old API if replacing
Backward Compatibility
Magento Backward Compatibility Rules
What Breaks Compatibility
- Removing public methods
- Changing method signatures
- Renaming classes/interfaces
- Changing database schema
- Removing configuration fields
- Changing API response format
- Modifying event names/arguments
Deprecation Strategy
// Step 1: Mark as deprecated
class LegacyService
{
/**
* @deprecated Use NewServiceInterface::process() instead
* @see NewServiceInterface
*/
public function process($data)
{
@trigger_error(
sprintf('Method %s is deprecated, use %s', __METHOD__, NewServiceInterface::class . '::process'),
E_USER_DEPRECATED
);
return $this->newService->process($data);
}
}
// Step 2: Keep for 2 minor versions
// Step 3: Remove in next major version
Plugin-Based Refactoring
<!-- Safely intercept without modifying core -->
<config>
<type name="Magento\Catalog\Model\Product">
<plugin name="improved-validation"
type="Vendor\Module\Plugin\ProductValidationPlugin"
sortOrder="10"/>
</type>
</config>
Service Contract Approach
// Define interface (stable API)
interface ProductRepositoryInterface
{
public function get($id);
public function save(ProductInterface $product);
}
// Implementation can change freely
class ProductRepository implements ProductRepositoryInterface
{
// Internal implementation details hidden
}
Migration Testing
// Test backward compatibility
public function testLegacyMethodStillWorks(): void
{
$legacy = new LegacyService($this->newService);
$result = $legacy->process($data);
$this->assertEquals($expected, $result);
}
// Test new method works
public function testNewMethodWorks(): void
{
$service = new NewService();
$result = $service->process($data);
$this->assertEquals($expected, $result);
}
Practice Problems
Apply the Boy Scout Rule to a Magento class during a feature branch. Make 5+ small improvements.
Plan a strangler fig migration for a monolithic order processing class into 3 separate services.
Quiz
1. What is the Boy Scout Rule?
2. In the strangler fig pattern, what is the first step?
3. How long should deprecated code be kept in Magento?
4. What enables safe refactoring without modifying core code?
Flashcards
Question
What is the Boy Scout Rule?
Click to reveal answer
Answer
Always leave the code cleaner than you found it through small, continuous improvements
Question
What is strangler fig pattern?
Click to reveal answer
Answer
Incrementally replace legacy code by building new alongside old until old can be removed
Question
What are the 4 phases of strangler fig?
Click to reveal answer
Answer
Identify seams, extract to new service, route traffic, remove old code
Question
What breaks backward compatibility?
Click to reveal answer
Answer
Removing methods, changing signatures, renaming classes, changing DB schema or API format
Question
How long to keep deprecated code?
Click to reveal answer
Answer
2 minor versions before removal in next major version
Revision Notes
Key Takeaways
- 1. Boy Scout Rule: small, continuous improvements with each code change
- 2. Strangler Fig: 4-phase approach - seams, extract, route, remove
- 3. Incremental refactoring: small steps, test after each change, maintain compatibility
- 4. Backward compatibility: never break public API; deprecate for 2 versions first
- 5. Plugins enable safe interception without modifying core code
- 6. Characterization tests capture current behavior before refactoring
Interview Tips
- • Describe a time you applied the Boy Scout Rule
- • How would you plan a strangler fig migration for a monolith?
- • What's your approach to maintaining backward compatibility?
- • How do you decide when to refactor vs rewrite?
- • Explain the risks of large-scale refactoring and how to mitigate them
Cheat Sheet
Refactoring Strategies Cheat Sheet
Boy Scout Rule: Leave code cleaner, 5-15 min improvements per PR
Strangler Fig:
- Identify seams
- Extract to new service
- Route traffic
- Remove old code
Incremental Refactoring:
- Write characterization test
- Make one small change
- Run tests
- Repeat
Backward Compatibility:
- Never break public API
- @deprecated for 2 minor versions
- Use plugins for safe interception
- Service contracts for stable interfaces