Friday, January 16, 2026

Clean Code Chapter 5: Formatting

 Clean Code Chapter 5: Formatting

Code formatting might seem like a trivial concern compared to architecture or algorithms, but Robert C. Martin dedicates an entire chapter to it in *Clean Code* for good reason. As he puts it: "Code formatting is about communication, and communication is the professional developer's first order of business."

When you format code, you're not just making it pretty. You're making it readable, and readable code is maintainable code. The choices you make about whitespace, line length, and organization affect how quickly another developer (or future you) can understand what's happening.

The Newspaper Metaphor

Martin introduces a powerful metaphor: think of your source file like a newspaper article. The headline tells you what the story is about and helps you decide whether to read further. The first paragraph gives you a synopsis. As you continue down, you get increasing levels of detail.

Your code should work the same way. The name of the file or class should tell readers what they're looking at. The topmost functions should provide high-level concepts. As you scroll down, details should emerge.

Vertical Formatting

Vertical formatting answers the question: how long should a source file be?

Martin's research on successful open source projects found that most files were under 500 lines, with an upper limit around 200 being typical. Smaller files are easier to understand. This doesn't mean you artificially break up cohesive code, but it does mean you should question any file that grows beyond a few hundred lines.

Vertical Openness Between Concepts

Blank lines serve as visual separators between distinct thoughts. Each blank line is a visual cue that a new concept is beginning.

The Problem:

```ruby
# Ruby - Dense, hard to scan
class InvoiceGenerator
attr_reader :order, :customer
def initialize(order)
@order = order
@customer = order.customer
end
def generate
validate_order
invoice = build_invoice
calculate_totals(invoice)
apply_discounts(invoice)
invoice.save!
send_notification(invoice)
invoice
end
private
def validate_order
raise InvalidOrderError unless order.finalized?
raise MissingCustomerError unless customer.present?
end
def build_invoice
Invoice.new(
order: order,
customer: customer,
issued_at: Time.current
)
end
def calculate_totals(invoice)
invoice.subtotal = order.line_items.sum(&:total)
invoice.tax = TaxCalculator.calculate(invoice.subtotal, customer.tax_region)
invoice.total = invoice.subtotal + invoice.tax
end
end
```

The Solution:

```ruby
# Ruby - Breathing room between concepts
class InvoiceGenerator
attr_reader :order, :customer

def initialize(order)
@order = order
@customer = order.customer
end

def generate
validate_order

invoice = build_invoice
calculate_totals(invoice)
apply_discounts(invoice)

invoice.save!
send_notification(invoice)

invoice
end

private

def validate_order
raise InvalidOrderError unless order.finalized?
raise MissingCustomerError unless customer.present?
end

def build_invoice
Invoice.new(
order: order,
customer: customer,
issued_at: Time.current
)
end

def calculate_totals(invoice)
invoice.subtotal = order.line_items.sum(&:total)
invoice.tax = TaxCalculator.calculate(invoice.subtotal, customer.tax_region)
invoice.total = invoice.subtotal + invoice.tax
end
end
```

The second version uses blank lines to create visual paragraphs. You can scan it quickly and understand the structure without reading every line.

Vertical Density

The flip side of openness is density. Lines of code that are tightly related should appear vertically close to each other. When you separate related concepts with blank lines, you force readers to hunt for connections.

```ruby
# Ruby - Related things should stay together
class ReportConfig
attr_reader :title,
:date_range,
:include_charts

def initialize(title:, date_range:, include_charts: true)
@title = title
@date_range = date_range
@include_charts = include_charts
end
end
```

Notice how the attribute declarations stay together without blank lines between them. They're all part of the same concept: "what data this class holds."

Vertical Distance

Related concepts should not only be close together, they should also be ordered in a way that helps readers. Martin outlines several principles.

Variable declarations should appear as close to their usage as possible. In short functions, that usually means at the top.

Instance variables should be declared at the top of the class in a consistent location. Ruby's convention of `attr_reader` declarations at the top serves this well.

Dependent functions should be vertically close. If one function calls another, the caller should be above the callee where possible, and they should be nearby. This creates a natural downward flow through the source code.

```ruby
# Ruby - Caller above callee, close together
class OrderFulfillment
def fulfill(order)
return unless fulfillable?(order)

reserve_inventory(order)
schedule_shipment(order)
notify_customer(order)
end

private

def fulfillable?(order)
order.paid? && inventory_available?(order)
end

def inventory_available?(order)
order.line_items.all? do |item|
Inventory.available?(item.sku, item.quantity)
end
end

def reserve_inventory(order)
order.line_items.each do |item|
Inventory.reserve(item.sku, item.quantity)
end
end

def schedule_shipment(order)
ShippingService.schedule(order)
end

def notify_customer(order)
OrderMailer.fulfillment_started(order).deliver_later
end
end
```

The public `fulfill` method sits at the top. The private methods it calls are ordered roughly in the sequence they're invoked. A reader can follow the flow without jumping around.

Horizontal Formatting

How long should a line be? Martin argues for keeping lines short enough that you never need to scroll horizontally. His suggestion: aim for 120 characters or fewer.

Modern monitors are wide, but that doesn't mean you should fill them. Shorter lines are easier to scan, and they work better with side-by-side diffs during code review.

Horizontal Openness and Density

Just as blank lines separate concepts vertically, spaces separate things horizontally. Use them to show association and disassociation.

```ruby
# Ruby - Spaces showing relationships
def calculate_score(base, multiplier, bonus)
adjusted = base*multiplier + bonus # Multiplication binds tighter
normalized = adjusted / MAX_SCORE
[normalized, 1.0].min
end
```

In practice, Ruby style guides (and most linters) prefer spaces around all operators for consistency. The point is that spacing conveys meaning. Assignment operators get space on both sides because they separate two distinct things. Method arguments don't get space after the method name because they're closely associated.

Indentation

Indentation makes the hierarchy of scopes visible. Without it, code becomes nearly impossible to read.

The Problem:

```jsx
// React - Collapsed indentation destroys readability
function UserDashboard({user}) {
return (<div className="dashboard">
<header><h1>Welcome, {user.name}</h1>
<nav>{user.isAdmin && <AdminLink />}</nav></header>
<main><UserStats stats={user.stats} />
<RecentActivity activities={user.recentActivities} /></main>
</div>);}
```

The Solution:

```jsx
// React - Proper indentation reveals structure
function UserDashboard({ user }) {
return (
<div className="dashboard">
<header>
<h1>Welcome, {user.name}</h1>
<nav>
{user.isAdmin && <AdminLink />}
</nav>
</header>

<main>
<UserStats stats={user.stats} />
<RecentActivity activities={user.recentActivities} />
</main>
</div>
);
}
```

The indentation immediately shows you the DOM hierarchy. You can scan the left edge and understand the structure without reading the content.

Breaking Indentation

Sometimes developers are tempted to collapse short statements onto single lines. Resist this urge.

```ruby
# Ruby - Don't collapse control structures
class User
# Tempting but harder to scan
def active?; status == 'active'; end
def admin?; role == 'admin'; end

# Better: consistent structure
def active?
status == 'active'
end

def admin?
role == 'admin'
end
end
```

The multi-line version takes more vertical space, but it maintains visual consistency. Your eye can scan down the left margin and know exactly what it will find.

Team Rules

Martin emphasizes that a team of developers should agree on a single formatting style. Individual preference matters less than consistency across the codebase.

In Ruby, this is largely solved by tools like RuboCop. In JavaScript and TypeScript, Prettier handles it automatically. The specific rules matter less than having rules everyone follows.

```ruby
# .rubocop.yml - Enforce team consistency
Layout/LineLength:
Max: 120

Layout/EmptyLinesAroundClassBody:
EnforcedStyle: empty_lines

Layout/MultilineMethodCallIndentation:
EnforcedStyle: indented
```

```javascript
// .prettierrc - Remove formatting debates
{
"printWidth": 100,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "es5"
}
```

Once you've configured these tools, formatting debates end. The computer enforces consistency, and developers can focus on what the code does rather than how it looks.

Putting It Together: A React Example

Let's look at a complete React component demonstrating these principles.

The Problem:

```jsx
// React - Poor formatting obscures intent
import {useState,useEffect,useCallback} from 'react';
import {fetchUserData,updateUserPreferences} from '../api/users';
export function UserPreferences({userId,onSave}) {
const [preferences,setPreferences] = useState(null);
const [loading,setLoading] = useState(true);
const [error,setError] = useState(null);
useEffect(() => {fetchUserData(userId).then(data => {setPreferences(data.preferences);setLoading(false);}).catch(err => {setError(err.message);setLoading(false);});},[userId]);
const handleToggle = useCallback((key) => {setPreferences(prev => ({...prev,[key]: !prev[key]}));},[]);
const handleSave = useCallback(async () => {try {await updateUserPreferences(userId,preferences);onSave(preferences);} catch (err) {setError(err.message);}},[userId,preferences,onSave]);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={error} />;
return (<div className="preferences"><h2>Your Preferences</h2><PreferenceToggle label="Email notifications" checked={preferences.emailNotifications} onChange={() => handleToggle('emailNotifications')} /><PreferenceToggle label="Dark mode" checked={preferences.darkMode} onChange={() => handleToggle('darkMode')} /><button onClick={handleSave}>Save Changes</button></div>);}
```

The Solution:

```jsx
// React - Clean formatting aids comprehension
import { useState, useEffect, useCallback } from 'react';

import { fetchUserData, updateUserPreferences } from '../api/users';

export function UserPreferences({ userId, onSave }) {
const [preferences, setPreferences] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
fetchUserData(userId)
.then(data => {
setPreferences(data.preferences);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [userId]);

const handleToggle = useCallback((key) => {
setPreferences(prev => ({
...prev,
[key]: !prev[key]
}));
}, []);

const handleSave = useCallback(async () => {
try {
await updateUserPreferences(userId, preferences);
onSave(preferences);
} catch (err) {
setError(err.message);
}
}, [userId, preferences, onSave]);

if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={error} />;

return (
<div className="preferences">
<h2>Your Preferences</h2>

<PreferenceToggle
label="Email notifications"
checked={preferences.emailNotifications}
onChange={() => handleToggle('emailNotifications')}
/>

<PreferenceToggle
label="Dark mode"
checked={preferences.darkMode}
onChange={() => handleToggle('darkMode')}
/>

<button onClick={handleSave}>Save Changes</button>
</div>
);
}
```

The formatted version:

1. Groups imports with blank lines separating external and internal modules
2. Keeps related state declarations together
3. Separates logical sections (state, effects, handlers, render) with blank lines
4. Breaks JSX across lines so the structure is visible
5. Uses consistent indentation throughout

The Bottom Line

Formatting is about respect. Respect for the readers of your code, for your teammates, and for your future self. Martin writes: "Perhaps you thought that 'getting it working' was the first order of business for a professional developer. I hope by now this book has disabused you of that idea."

Code is read far more often than it is written. Every formatting choice either helps or hinders that reading. Choose wisely, automate what you can, and remember that the goal is communication.

---

What formatting conventions does your team follow? What tools have helped standardize your codebase? I'd love to hear what's worked for you.

Wednesday, January 7, 2026

Clean Code Chapter 4: Comments, or The Art of Saying Nothing

 

Clean Code Chapter 4: Comments, or The Art of Saying Nothing

One of the most counterintuitive lessons in Robert Martin's Clean Code appears in Chapter 4: the best comment is often no comment at all. As Martin puts it, "the proper use of comments is to compensate for our failure to express ourselves in code."

This doesn't mean comments are evil. It means they're a last resort. When you feel the urge to write a comment, your first instinct should be to ask: Can I refactor this code so the comment becomes unnecessary?

Why Comments Lie

Comments have a fundamental problem: they rot. Code changes, deadlines loom, and comments get left behind. Unlike code, comments don't throw errors when they become inaccurate. A misleading comment is worse than no comment at all because it actively deceives the reader.

Martin argues that we should spend our energy making code self-documenting rather than explaining unclear code with comments.

Good Comments vs. Bad Comments

Not all comments are bad. Martin identifies several legitimate uses:

Acceptable comments:

  • Legal notices (copyright, licensing)
  • Explanation of intent when the why isn't obvious from code
  • Clarification of obscure arguments or return values from external libraries
  • Warnings of consequences
  • TODO markers (used sparingly)

Comments to avoid:

  • Redundant comments that repeat what the code says
  • Commented-out code (that's what version control is for)
  • Position markers and banners
  • Attributions (again, version control)
  • Closing brace comments

Rails Example: Let the Code Speak

Here's a common pattern I see in Rails applications with over-commenting:

# Bad: Comments that add noise
class OrderProcessor
  # Process the order
  def process(order)
    # Check if order is valid
    return unless order.valid?

    # Calculate the total price of all items
    total = order.line_items.sum do |item|
      # Multiply quantity by unit price
      item.quantity * item.unit_price
    end

    # Apply discount if customer has one
    if order.customer.discount_percentage.present?
      # Calculate discount amount
      discount = total * (order.customer.discount_percentage / 100.0)
      # Subtract discount from total
      total = total - discount
    end

    # Save the total to the order
    order.update(total_amount: total)

    # Send confirmation email to customer
    OrderMailer.confirmation(order).deliver_later
  end
end

Every comment here restates what the code already says. The method name process is vague, forcing us to read comments to understand what's happening. Let's refactor:

# Good: Self-documenting code
class OrderProcessor
  def finalize_and_confirm(order)
    return unless order.valid?

    order.update(total_amount: calculate_discounted_total(order))
    OrderMailer.confirmation(order).deliver_later
  end

  private

  def calculate_discounted_total(order)
    subtotal = order.line_items.sum(&:total_price)
    apply_customer_discount(subtotal, order.customer)
  end

  def apply_customer_discount(amount, customer)
    return amount unless customer.discount_percentage.present?

    amount * (1 - customer.discount_percentage / 100.0)
  end
end

The refactored version uses method names that explain intent. finalize_and_confirm tells you exactly what the public method does. calculate_discounted_total and apply_customer_discount make the business logic readable without a single comment.

Notice how extracting total_price to the LineItem model (implied by sum(&:total_price)) moves that logic where it belongs. The code reads like a sentence describing the business process.

React Example: When Comments Help and When They Hurt

Frontend code often accumulates comments explaining complex UI logic. Here's a React component drowning in unnecessary comments:

// Bad: Comment noise obscuring the code
interface CartProps {
  items: CartItem[];
  onCheckout: () => void;
}

function ShoppingCart({ items, onCheckout }: CartProps) {
  // State for showing the discount input
  const [showDiscount, setShowDiscount] = useState(false);
  // State for the discount code value
  const [discountCode, setDiscountCode] = useState('');
  // State for discount validation error
  const [error, setError] = useState<string | null>(null);

  // Calculate the total price
  const total = items.reduce((sum, item) => {
    // Add item price times quantity to sum
    return sum + item.price * item.quantity;
  }, 0); // Start with 0

  // Handle applying the discount code
  const handleApplyDiscount = () => {
    // Check if code is valid
    if (discountCode.length < 5) {
      // Set error message
      setError('Invalid discount code');
      return;
    }
    // Clear error and apply
    setError(null);
    // TODO: Actually apply discount
  };

  // Render the component
  return (
    <div className="cart">
      {/* Map through items and display them */}
      {items.map((item) => (
        // Use item id as key
        <CartItemRow key={item.id} item={item} />
      ))}
      {/* Show total */}
      <div className="total">Total: ${total.toFixed(2)}</div>
      {/* Checkout button */}
      <button onClick={onCheckout}>Checkout</button>
    </div>
  );
}

The comments here are pure noise. // Calculate the total price before a variable called total adds nothing. // Render the component before a return statement is almost comical. Let's clean this up:

// Good: Clear code with one meaningful comment
interface CartProps {
  items: CartItem[];
  onCheckout: () => void;
}

function ShoppingCart({ items, onCheckout }: CartProps) {
  const [discountForm, setDiscountForm] = useState({
    isVisible: false,
    code: '',
    error: null as string | null,
  });

  const cartTotal = items.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0
  );

  const applyDiscountCode = () => {
    const MIN_CODE_LENGTH = 5;

    if (discountForm.code.length < MIN_CODE_LENGTH) {
      setDiscountForm((prev) => ({ ...prev, error: 'Invalid discount code' }));
      return;
    }

    setDiscountForm((prev) => ({ ...prev, error: null }));
    // TODO: Integrate with discount API once pricing service is deployed
  };

  return (
    <div className="cart">
      {items.map((item) => (
        <CartItemRow key={item.id} item={item} />
      ))}

      <div className="total">Total: ${cartTotal.toFixed(2)}</div>
      <button onClick={onCheckout}>Checkout</button>
    </div>
  );
}

The refactored version groups related state into a single object with descriptive property names. cartTotal is more specific than total. The magic number 5 becomes a named constant MIN_CODE_LENGTH.

One comment remains: the TODO. This is a legitimate use because it explains why the code is incomplete (waiting on an external dependency) rather than restating what the code does.

The Litmus Test

Before writing a comment, ask yourself these questions:

  1. Can I rename this variable or function to eliminate the need for this comment?
  2. Can I extract this logic into a well-named method?
  3. Am I explaining what the code does (bad) or why it does something non-obvious (potentially acceptable)?
  4. Will this comment stay accurate as the code evolves?

If you can refactor instead of comment, do it. Your future self (and your teammates) will thank you.

The Takeaway

Comments aren't inherently bad, but they should make you pause. Each comment is an admission that your code couldn't speak for itself. Sometimes that's unavoidable. More often, it's an invitation to write clearer code.

As Martin writes: "The only truly good comment is the comment you found a way not to write."

Clean Code Chapter 5: Formatting

  Clean Code Chapter 5: Formatting Code formatting might seem like a trivial concern compared to architecture or algorithms, but Robert C. M...