I remember that March morning clearly. I'm taking over a Laravel project. The client sends me the Git repository. I open the first controller: 847 lines. The second: 1,203 lines. A UserController managing orders, emails, CSV exports, notifications, and invoicing. All in one place.
That day, I understood that writing code that works and writing good code are two very different disciplines. Here are the 8 rules I've applied systematically ever since.
Rule #1: A Name That Tells the Truth
Naming is fundamental. A good name makes code self-documenting. A bad name forces anyone (including yourself in 6 months) to read the implementation to understand the intent.
// ❌ Names that explain nothing
function d($u) { ... }
$flag = true;
// ✅ Names that tell a story
function deactivateUser(User $user): void { ... }
$isEmailVerified = true;
Rule #2: Single Responsibility Principle
The S in SOLID. One class, one method, one responsibility. A controller managing business logic, emails, AND PDFs is three classes disguised as one.
// ❌ A controller doing everything
class OrderController extends Controller
{
public function store(Request $request)
{
// Validate order
// Create order in DB
// Update stock
// Send confirmation email
// Generate PDF invoice
// ...200 lines later
}
}
// ✅ Separated responsibilities
class OrderController extends Controller
{
public function store(StoreOrderRequest $request, OrderService $orderService): JsonResponse
{
$order = $orderService->createOrder($request->validated());
return response()->json(new OrderResource($order), 201);
}
}
Rule #3: DRY — Don't Repeat Yourself
// ❌ Logic duplicated in 3 controllers
$posts = BlogPost::where('is_published', true)
->whereNotNull('published_at')
->where('published_at', '<=', now())
->get();
// ✅ A reusable scope
// In BlogPost.php
public function scopePublished(Builder $query): Builder
{
return $query->where('is_published', true)
->whereNotNull('published_at')
->where('published_at', '<=', now());
}
// In controllers — readable, consistent, maintainable
$posts = BlogPost::published()->get();
Rule #4: Short Functions That Do One Thing
If you can't describe what your method does in one sentence without using "and", it's too big.
Rule #5: Avoid Magic Numbers and Strings
// ❌ What does "2" mean here?
if ($user->role === 2) { ... }
// ✅ Constants that give meaning
if ($user->role === Role::ADMIN) { ... }
enum OrderStatus: string
{
case PENDING_PAYMENT = 'pending_payment';
case PAID = 'paid';
case SHIPPED = 'shipped';
case CANCELLED = 'cancelled';
}
Rule #6: Comments Explain WHY, Not WHAT
// ❌ Useless comment (the code already says this)
// Retrieve user by email
$user = User::where('email', $email)->first();
// ✅ Useful comment (explains the decision)
// Using firstOrFail() intentionally here:
// if the user doesn't exist at this point, it's a
// system error, not a user error.
$user = User::where('email', $email)->firstOrFail();
Rule #7: Handle Errors Explicitly
// ❌ Silently ignoring errors
try {
$result = $externalApi->fetch($id);
} catch (Exception $e) { }
// ✅ Handle and log errors
try {
$result = $externalApi->fetch($id);
} catch (ApiException $e) {
Log::error('External API failure', ['id' => $id, 'error' => $e->getMessage()]);
throw new ServiceUnavailableException('External service unavailable.', previous: $e);
}
Rule #8: Actually Test Your Code
class OrderServiceTest extends TestCase
{
use RefreshDatabase;
public function test_it_creates_order_and_decrements_stock(): void
{
$product = Product::factory()->create(['stock' => 10]);
$order = app(OrderService::class)->createOrder([
'product_id' => $product->id,
'quantity' => 3,
]);
$this->assertDatabaseHas('orders', ['id' => $order->id]);
$this->assertEquals(7, $product->fresh()->stock);
}
}
Conclusion: Clean Code is an Investment
These 8 rules don't slow down your development — they speed it up, in the long run. The technical debt you accumulate with unreadable code, you pay back with interest on every feature, every bug, every new developer who joins the project.
The best time to write clean code was 3 years ago. The second best time is now.