Writing Readable Code: The Art of Arranging for Clarity

Writing Readable Code: The Art of Arranging for Clarity

Writing Readable Code: The Art of Arranging for Clarity

Your no-BS weekly brief on software engineering.
Join 100,000+ developers

We hear it all the time: readability matters. But why? What is it about well-structured code that makes such a difference? The reality is that future-you—six months from now, staring at a function you barely remember writing—will either thank past-you or curse your name, depending on how readable that code is.

The Problem with Memory

Humans are unreliable narrators of their own past decisions. When writing code, we assume we'll remember the mental model we had at the time, but we don't. We're fallible, just like an AI system hallucinating answers when it lacks context. You've probably seen AI generate plausible but incorrect code—it looks right, but when you run it, something is off.

The same thing happens when we write unclear code: it might work today, but future-you will struggle to understand why. Without clear structure and explicit intent in our code, we end up debugging our own thoughts rather than our software, wasting time unraveling logic that should have been obvious.

In other words, think of yourself as being a forgetful developer.

Arrangement is Everything

When we structure our code effectively, we make it easier to trace, reason about, and modify. What does that mean in practice?

  • Highlights the most important details first.
  • Establishes a logical order of execution.
  • Groups related concepts together.
  • Uses naming and formatting to reinforce clarity.

The Hidden ROI of Readable Code

Readable code is an investment that pays off in ways you may not immediately notice:

  1. Debugging is Easier – When code is well-structured, tracking down issues becomes a breeze. You're not left wondering, "Where does this variable even come from?"

  2. Adding Unexpected Functionality is Easier – Requirements change, and code needs to adapt. Readable code makes these transitions smoother.

  3. Removing Code is Easier – Some of the most satisfying pull requests remove more lines than they add. When code is arranged well, identifying redundant or obsolete logic is simpler.

A Case Study: When Simple Code Starts to Break

The best way to understand this is to look at some actual code! Imagine you're a consultant working with a client in the online dating space. One day, the CEO decides annual plans need a discount. Sounds simple enough. Let's start that way with our solution.

class DiscountCalculator
  def initialize(plan)
    @plan = plan
  end

  def calculate
    return 10 if @plan == "annual"
    0
  end
end

This is hardcoded of course. Both the percentage discount and also the type of plan required is a hardcoded string. Ick — but hey, it works. In fact, the CFO realized how well it worked and said we can't run the discount all the time. It needs to be able to be turned on and off.

class DiscountCalculator
  def initialize(plan, promo_active)
    @plan = plan
    @promo_active = promo_active
  end

  def calculate
    return 10 if @plan == "annual" && @promo_active
    0
  end
end

Still hardcoded but you can read it and work with it. Well, that is until marketing came along.

Let's run a special sale in the first two weeks of February before Valentine's Day! If they do, let's give them a 15% discount!

class DiscountCalculator
  def initialize(plan, promo_active, signup_date)
    @plan = plan
    @promo_active = promo_active
    @signup_date = signup_date
  end

  def calculate
    return 15 if @plan == "annual" && @promo_active && @signup_date.month == 2 && @signup_date.day <= 14
    return 10 if @plan == "annual" && @promo_active
    0
  end
end

Now our logic is starting to get messy. Every new request forces us to edit this method directly. Let's refactor.

Refactored: A Simpler, Configuration-Driven Approach

Instead of overengineering with abstract rules, we can use a configuration-driven approach. This keeps the logic declarative and easy to extend without introducing unnecessary complexity.

class DiscountCalculator
  DISCOUNT_CONFIG = [
    { name: "Valentine's Day", condition: ->(signup_date, plan, promo_active) {
      plan == "annual" && signup_date.month == 2 && signup_date.day <= 14
    }, value: 15 },
    { name: "Annual Plan Promo", condition: ->(_signup_date, plan, promo_active) {
      plan == "annual" && promo_active
    }, value: 10 }
  ]

  def initialize(plan, signup_date, promo_active)
    @plan = plan
    @signup_date = signup_date
    @promo_active = promo_active
  end

  def calculate
    applicable_discounts.map { |rule| rule[:value] }.max || 0
  end

  private

  def applicable_discounts
    DISCOUNT_CONFIG.select do |rule|
      rule[:condition].call(@signup_date, @plan, @promo_active)
    end
  end
end

Why This Approach Works

Unlike the earlier hardcoded approaches, this configuration-driven design reduces repetition, centralizes logic, and makes adding new rules effortless.

  1. Declarative Logic: The discount rules are stored as data, making them easy to read, understand, and modify.
  2. Minimal Complexity: No need to define multiple classes or methods—conditions are defined inline.
  3. Extensibility: Adding new rules is as simple as appending a new entry to the DISCOUNT_CONFIG array.
  4. Debugging Context: Rules can include descriptive names, helping identify which discount was applied.

What could make this even better? Storing the discount codes in a database table! But hey, we're not there yet. For all we know, this may be the extent of our discount codes. For now, let's leverage a principle we wrote about on technical debt: solve today’s problems cleanly and extend solutions only when necessary.

Remember that as developers, planning for scalability isn't just about database tables—it's also about ensuring future maintainability.

The Future You

So next time you’re writing a function, think about future-you. Write as if you’re leaving breadcrumbs for an AI with limited recall, hallucinating solutions unless given clear instructions. Arrange your code to tell a story—one that’s easy to extend, adapt, and understand long after you’ve moved on. Because in the end, that’s what good code is: a story worth rereading.