Safe Refactoring Principles
Refactoring Rules
1. Refactor in small steps
2. Run tests after each change
3. Don't change functionality
4. Use version control
5. Refactor before adding features
6. Don't refactor and add features simultaneously
Refactoring Workflow
1. Ensure tests exist (write if needed)
2. Run tests - they should pass
3. Make one small change
4. Run tests - they should still pass
5. Commit
6. Repeat
Preparation Checklist
## Before Refactoring
- [ ] Tests cover current behavior
- [ ] Version control is clean
- [ ] No pending changes
- [ ] CI pipeline is green
- [ ] Understand the code
- [ ] Plan the refactoring steps
Refactoring with Tests
// 1. Write test for current behavior
public function testCalculateTotal()
{
$order = new Order();
$order->addItem(new OrderItem(1000)); // $10.00
$order->addItem(new OrderItem(2000)); // $20.00
$this->assertEquals(3000, $order->getTotal());
}
// 2. Verify test passes
vendor/bin/phpunit --filter=CalculateTotal
// 3. Refactor
public function getTotal(): int
{
return array_reduce(
$this->items,
fn($carry, $item) => $carry + $item->getSubtotal(),
0
);
}
// 4. Verify test still passes
vendor/bin/phpunit --filter=CalculateTotal
Key Takeaway
Refactor in small steps with tests. Never change functionality during refactoring. Commit after each successful change.
Extract Method Pattern
Extract Method
// Before: Long method
class OrderProcessor
{
public function process($orderId)
{
// Load order (10 lines)
$connection = $this->resource->getConnection();
$select = $connection->select()->from('sales_order')->where('entity_id = ?', $orderId);
$order = $connection->fetchRow($select);
// Validate order (15 lines)
if (!$order) {
throw new \Exception('Order not found');
}
if ($order['status'] == 'cancelled') {
throw new \Exception('Cannot process cancelled order');
}
// Process payment (20 lines)
$payment = $this->paymentFactory->create();
$payment->setOrderId($orderId);
$payment->setAmount($order['total']);
$payment->process();
// Send email (10 lines)
$email = $this->emailFactory->create();
$email->setTo($order['customer_email']);
$email->send();
return $order;
}
}
// After: Extracted methods
class OrderProcessor
{
public function process($orderId)
{
$order = $this->loadOrder($orderId);
$this->validateOrder($order);
$this->processPayment($order);
$this->sendConfirmation($order);
return $order;
}
private function loadOrder($orderId)
{
$connection = $this->resource->getConnection();
$select = $connection->select()->from('sales_order')->where('entity_id = ?', $orderId);
$order = $connection->fetchRow($select);
if (!$order) {
throw new \Exception('Order not found');
}
return $order;
}
private function validateOrder($order)
{
if ($order['status'] == 'cancelled') {
throw new \Exception('Cannot process cancelled order');
}
}
private function processPayment($order)
{
$payment = $this->paymentFactory->create();
$payment->setOrderId($order['entity_id']);
$payment->setAmount($order['total']);
$payment->process();
}
private function sendConfirmation($order)
{
$email = $this->emailFactory->create();
$email->setTo($order['customer_email']);
$email->send();
}
}
Key Takeaway
Extract Method moves code blocks into named methods. Each method has a single responsibility and clear name.
Replace Temp with Query
Replace Temp with Query
// Before: Temporary variable
class OrderTotalCalculator
{
public function calculate($order)
{
$basePrice = $order->getItemsTotal();
$discount = $basePrice * 0.1;
$tax = ($basePrice - $discount) * 0.08;
return $basePrice - $discount + $tax;
}
}
// After: Extract to query method
class OrderTotalCalculator
{
public function calculate($order)
{
return $this->getBasePrice($order)
- $this->getDiscount($order)
+ $this->getTax($order);
}
private function getBasePrice($order): int
{
return $order->getItemsTotal();
}
private function getDiscount($order): int
{
return $this->getBasePrice($order) * 0.1;
}
private function getTax($order): int
{
return ($this->getBasePrice($order) - $this->getDiscount($order)) * 0.08;
}
}
Inline Temp
// Before: Unnecessary variable
$discount = $this->calculateDiscount($order);
return $order->getTotal() - $discount;
// After: Inline
return $order->getTotal() - $this->calculateDiscount($order);
Replace Magic Numbers
// Before: Magic number
if ($order->getTotal() > 1000) {
$order->setFreeShipping(true);
}
// After: Named constant
const FREE_SHIPPING_THRESHOLD = 1000;
if ($order->getTotal() > self::FREE_SHIPPING_THRESHOLD) {
$order->setFreeShipping(true);
}
Key Takeaway
Replace temporary variables with query methods. Inline unnecessary variables. Replace magic numbers with named constants.
Magento-Specific Refactoring
Refactoring to Service Contracts
// Before: Direct model usage
class ProductController
{
public function execute()
{
$product = Mage::getModel('catalog/product')->load($id);
return $product->getName();
}
}
// After: Service contract
class ProductController
{
private ProductRepositoryInterface $productRepository;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
public function execute()
{
$product = $this->productRepository->get($id);
return $product->getName();
}
}
Refactoring Observers
// Before: Heavy logic in observer
class OrderPlacedObserver implements ObserverInterface
{
public function execute(EventObserver $observer)
{
$order = $observer->getEvent()->getOrder();
// 100+ lines of processing
}
}
// After: Delegate to service
class OrderPlacedObserver implements ObserverInterface
{
private OrderPostProcessor $postProcessor;
public function execute(EventObserver $observer)
{
$order = $observer->getEvent()->getOrder();
$this->postProcessor->process($order);
}
}
Refactoring Plugins
// Before: Plugin too broad
class ProductPlugin
{
public function aroundGetPrice($subject, $proceed)
{
// Modifies ALL product prices
}
}
// After: Specific plugin with condition
class ProductPlugin
{
public function aroundGetPrice($subject, $proceed)
{
if ($this->isSpecialPromotion($subject)) {
return $this->calculateSpecialPrice($proceed());
}
return $proceed();
}
}
Refactoring to Event-Driven
// Before: Direct dependencies
class OrderService
{
public function placeOrder($order)
{
$this->inventoryService->decrement($order);
$this->emailService->send($order);
$this->analyticsService->track($order);
}
}
// After: Event-driven
class OrderService
{
private EventDispatcher $eventDispatcher;
public function placeOrder($order)
{
$this->orderRepository->save($order);
$this->eventDispatcher->dispatch('order_placed', ['order' => $order]);
}
}
Key Takeaway
Refactor Magento code to use service contracts, thin observers, specific plugins, and event-driven architecture for better maintainability.
Quiz
1. What is the first rule of refactoring?
2. What is Extract Method?
3. Why replace magic numbers?
4. How to refactor Magento observers?
5. When should you refactor?
Flashcards
Question
What is the first rule of refactoring?
Click to reveal answer
Answer
Ensure tests exist and pass before making changes
Question
What is Extract Method?
Click to reveal answer
Answer
Move code block into named method with single responsibility
Question
Why replace magic numbers?
Click to reveal answer
Answer
Named constants improve readability and maintainability
Question
How to refactor observers?
Click to reveal answer
Answer
Keep observers thin, delegate logic to service classes
Question
When to refactor?
Click to reveal answer
Answer
Before adding features, not during bug fixes or under pressure
Question
What is Replace Temp with Query?
Click to reveal answer
Answer
Replace temporary variables with query methods
Revision Notes
Key Takeaways
- 1. Refactor in small steps with tests running after each change
- 2. Extract Method: move code blocks into named methods
- 3. Replace magic numbers with named constants
- 4. Refactor Magento to use service contracts and thin observers
- 5. Never refactor and add features simultaneously
Interview Tips
- • Explain safe refactoring principles
- • Describe common refactoring patterns
- • Discuss test-driven refactoring approach
- • Explain Magento-specific refactoring techniques
Cheat Sheet
Refactoring
Principles:
- Small steps
- Tests after each change
- Don't change functionality
- Commit frequently
Patterns:
Extract Method
Replace Temp with Query
Replace Magic Numbers
Inline Temp
Magento:
Refactor to service contracts
Thin observers
Specific plugins
Event-driven architecture