Clean Code
Naming, function design, reducing complexity, the comment policy, and common code smells — the habits that separate readable code from clever code.
Name Things Right
The name is the documentation. A reader should understand intent from the identifier alone — without reading the implementation.
| Bad name | Why it fails | Good name |
|---|---|---|
$d |
Single letter; zero meaning | $daysUntilExpiry |
$data |
Everything is data | $paymentPayload |
handleIt() |
What is "it"? | processRefundRequest() |
doStuff() |
No specificity | dispatchWebhookJob() |
$flag |
Boolean with no context | $isIdempotencyKeyUsed |
$temp |
Scope, not intent | $filteredEvents |
getInfo() |
Which info? | getPaymentStatusById() |
$manager |
Too broad; implies omnipotence | $webhookEventLogger |
Function Design
A function should do one thing, do it well, and do it only. If you need "and" to describe it, split it.
function processWebhook(array $payload): void
{
// validate
if (!isset($payload['event_type'])) {
throw new \InvalidArgumentException('Missing event_type');
}
// deduplicate
$key = $payload['idempotency_key'] ?? null;
if ($key && WebhookEvent::where('idempotency_key', $key)->exists()) {
return;
}
// persist
$event = WebhookEvent::create($payload);
// notify
Mail::to('ops@example.com')->send(new WebhookReceived($event));
// log
Log::info('Webhook processed', ['id' => $event->id]);
}
// WebhookController
public function store(WebhookRequest $request): JsonResponse
{
$event = $this->webhookService->handle($request->validated());
return response()->json(['id' => $event->id], 201);
}
// WebhookService
public function handle(array $payload): WebhookEvent
{
$this->guardDuplicate($payload['idempotency_key'] ?? null);
$event = $this->repo->create($payload);
WebhookReceived::dispatch($event);
return $event;
}
private function guardDuplicate(?string $key): void
{
if ($key && $this->repo->existsByKey($key)) {
throw new DuplicateWebhookException($key);
}
}
Reducing Complexity
Deep nesting hides logic. Guard clauses return early for invalid cases, leaving the happy path flat and readable.
function applyDiscount(Order $order): float
{
if ($order->isActive()) {
if ($order->customer->isPremium()) {
if ($order->total > 10000) {
return $order->total * 0.85;
} else {
return $order->total * 0.90;
}
} else {
return $order->total;
}
} else {
throw new \RuntimeException('Inactive order');
}
}
function applyDiscount(Order $order): float
{
if (!$order->isActive()) {
throw new \RuntimeException('Inactive order');
}
if (!$order->customer->isPremium()) {
return $order->total;
}
return $order->total > 10000
? $order->total * 0.85
: $order->total * 0.90;
}
if, elseif, foreach, and catch adds 1.
The Comment Policy
Code tells you what and how. Comments should only tell you why — and only when the why is non-obvious.
// Get the user
$user = User::find($id);
// Check if user is active
if ($user->is_active) {
// Send welcome email
Mail::to($user->email)->send(new WelcomeMail($user));
}
// Return the user
return $user;
// Stripe requires amount in minor units (cents).
// 25000 = $250.00 USD.
$charge = $stripe->charge(['amount' => $cents]);
// bcrypt is intentionally slow here — this is the
// password hashing path, not a general hash utility.
$hash = bcrypt($plaintext);
// Soft-delete instead of hard-delete: compliance requires
// a 7-year audit trail (SOC 2 requirement).
$payment->delete();
- Write the comment before the code — it clarifies your intent before you implement
- If you write a comment explaining what, rename the identifier instead
- Delete commented-out code — git history preserves it
- Avoid "Added by Juan on 2026-04-21" — git blame already knows
Code Smells
A code smell is a surface symptom of a deeper problem. They don't always mean the code is broken — they mean it deserves a second look.
A method over ~20 lines is probably doing too many things. Extract smaller methods with intention-revealing names.
A class with many fields and methods likely has multiple responsibilities. Apply SRP — split into focused collaborators.
A method that accesses another object's data more than its own. It should probably live in that other class.
The same group of parameters always travel together ($street, $city, $zip). Extract them into a value object.
if ($retries > 3) — where did 3 come from? Extract to a named constant: MAX_RETRY_ATTEMPTS = 3.
Commented-out code, unreachable branches, unused variables. Delete them — git history is your undo.