Refactoring — improving code structure without changing observable behaviour
Beginner5 min read·eng-16-020
interview
Concept
Refactoring — improving the internal structure of code without changing its external observable behavior. The program does exactly the same thing before and after. Only the quality of the code changes.
Coined by Martin Fowler (Refactoring: Improving the Design of Existing Code, 1999). The book defined a catalog of refactoring techniques (Extract Method, Rename Variable, Move Class, etc.) that are now standard vocabulary.
What "same observable behavior" means:
- All tests pass before and after.
- External API and interfaces unchanged.
- Same inputs → same outputs.
- Internal implementation may change completely.
Why refactor:
- Reduce complexity and increase readability.
- Make the code easier to extend or test.
- Remove duplication.
- Align the code with the current understanding of the domain.
- Pay down technical debt.
Refactoring is NOT:
- Adding new features (that changes behavior).
- Fixing bugs (that changes behavior — what was broken is now fixed).
- Rewriting (throwing away and starting over — no continuity of behavior to verify).
Refactoring techniques (common ones):
- Extract Method: Take a code block, put it in a named method.
- Rename: Give things better names (variables, methods, classes).
- Move Method/Class: Move to a more appropriate home.
- Inline Method: Replace a pointless one-line method with its body.
- Replace Conditional with Polymorphism: A long
if/elseifbecomes subclasses. - Introduce Parameter Object: Multiple parameters become a single object.
- Extract Interface: Extract an interface from a class to enable polymorphism.
Test coverage as a safety net: You can only refactor safely if you have tests. The tests verify that behavior is preserved.
Code Example
php
<?php
// BEFORE REFACTORING — hard to read, duplicated logic
class OrderController extends Controller
{
public function store(Request $request)
{
$u = $request->user();
$d = $request->input('data');
if (empty($d['items'])) return response()->json(['e' => 'no items'], 422);
if ($d['total'] <= 0) return response()->json(['e' => 'bad total'], 422);
$o = new Order();
$o->user_id = $u->id;
$o->total = $d['total'];
$o->status = 'pending';
$o->save();
foreach ($d['items'] as $i) {
$oi = new OrderItem();
$oi->order_id = $o->id;
$oi->product_id = $i['product_id'];
$oi->quantity = $i['quantity'];
$oi->save();
}
Mail::to($u->email)->send(new OrderConf($o));
return response()->json($o, 201);
}
}
// AFTER REFACTORING — same behavior, better structure
// Behavior: test suite passes both before and after
// Step 1: Extract validation to FormRequest (Extract Method)
class CreateOrderRequest extends \Illuminate\Foundation\Http\FormRequest
{
public function rules(): array
{
return [
'items' => 'required|array|min:1',
'items.*.product_id' => 'required|integer|exists:products,id',
'items.*.quantity' => 'required|integer|min:1',
'total' => 'required|numeric|min:0.01',
];
}
}
// Step 2: Extract business logic to service (Move Method)
class OrderService
{
public function place(User $user, array $data): Order
{
$order = Order::create([
'user_id' => $user->id,
'total' => $data['total'],
'status' => 'pending',
]);
$this->createItems($order, $data['items']);
Mail::to($user)->send(new OrderConfirmation($order)); // Rename: OrderConf → OrderConfirmation
return $order;
}
private function createItems(Order $order, array $items): void // Extract Method
{
$order->items()->createMany($items); // Replace explicit loop with Eloquent bulk create
}
}
// Step 3: Thin controller (Extract Method + Move)
class OrderController extends Controller
{
public function __construct(private readonly OrderService $orders) {}
public function store(CreateOrderRequest $request): \Illuminate\Http\JsonResponse
{
$order = $this->orders->place($request->user(), $request->validated());
return response()->json(new OrderResource($order), 201);
}
}
// SAME BEHAVIOR: all tests that passed before still pass
// BETTER STRUCTURE: readable names, no duplication, single responsibility