Easier to Understand
At the start of 2026, one of my goals is to read roughly one technical book per month. Continuous learning is one of the most effective ways to grow as a developer. Even if the rate of growth is only 1% per day, this consistency compounds over time.
I decided to start my reading list with the classic, The Pragmatic Programmer by David Thomas and Andrew Hunt.
The Pragmatic Programmer is one of those rare technical books that feels timeless and influences the way that you approach all software development, regardless of the language, framework, or environment you are working in.
One of the core ideas of the book is that good design is easier to change than bad design. That at the heart of all good design, the underlying goal is to make your code Easier to Change (ETC).
I’ve reflected on this idea a lot while reading through the remainder of the book. I’m taking this principle to all of my current projects and finding it incredibly effective at improving my design decisions.
A simple example: imagine you need to calculate donation totals with processing fees. You could hardcode the fee directly into the calculation, or you could make it a parameter that’s easy to adjust:
# Hard to change - business logic is tightly coupled
def calculate_donation_total(donations)
total = 0
donations.each do |donation|
total += donation.amount * 1.03 # processing fee hardcoded
end
total
end
# Easier to change - fee can be adjusted without modifying logic
def calculate_donation_total(donations, processing_fee: 0.03)
subtotal = donations.sum(&:amount)
subtotal * (1 + processing_fee)
end
As I think through what constitutes good design in software, another principle has been forming in my mind. And that principle is:
In addition to being easier to change, well-designed software should be easier to understand.
One of the tests of a well-designed application or library is how easy it is to understand later, after you’ve moved on and haven’t looked at it for weeks or even months. Even more critically, when someone else has inherited your code and needs to make sense of it quickly.
If you must spend a painful amount of time just remembering why you made the decisions you did, where to find core modules, and how to make basic changes without breaking existing functionality, I would argue that the software is not well-designed.
I think it’s worth mentioning that there are varying degrees of complexity across projects, and some software will naturally be more complex and difficult to understand. So this is a general and relative guideline.
That said, even within complex projects, we can make choices that improve understandability. Take something as straightforward as filtering a list of donors. The same logic can be written in drastically different ways:
# Harder to understand - requires mental mapping
def process(data)
result = []
data.each do |d|
if d[2] > 500 && d[4] == 'monthly'
result << { n: d[0], e: d[1] }
end
end
result
end
# Easier to understand - intent is immediately clear
def get_monthly_major_donors(donors)
major_donors = []
donors.each do |donor|
if donor.total_giving > 500 && donor.frequency == 'monthly'
major_donors << {
name: donor.name,
email: donor.email
}
end
end
major_donors
end
I think it’s also worth noting that while comments are helpful at explaining design decisions, the goal should be self-documenting code. Use comments to explain why you made the choices that you made, but when it comes to what a method or function does, make sure that the code can speak for itself.
There’s a meaningful difference between comments that explain what code does versus comments that explain why you made certain decisions:
# Over-commented - explaining WHAT the code does
def calculate_discount(amount, donor)
# Check if donor is sustainer tier
if donor.tier == 'sustainer'
# Apply 5% processing discount for sustainers
discount = amount * 0.05
else
# No discount for regular donors
discount = 0
end
# Return the discounted amount
amount - discount
end
# Self-documenting - comments explain WHY when needed
def calculate_discount(amount, donor)
# Sustainers get processing discount to encourage recurring giving
discount_rate = donor.sustainer? ? 0.05 : 0.0
amount * (1 - discount_rate)
end
Overuse of comments and Markdown files can quickly violate another principle of The Pragmatic Programmer, which is Don’t Repeat Yourself (DRY). If your project is riddled with too much documentation, you risk needing to update a lot just to make a simple change. And things can quickly become outdated.
Even at a smaller scale, redundant comments create the same maintenance burden:
# Violates DRY - method name and comment say the same thing
def send_donation_receipt(donor)
# Send a donation receipt to the donor
DonorMailer.receipt(donor).deliver_now
end
# Follows DRY - method name is self-explanatory
def send_donation_receipt(donor)
DonorMailer.receipt(donor).deliver_now
end
# Good use of comments - explains WHY, not WHAT
def send_donation_receipt(donor)
# Sending immediately instead of queuing because donors expect
# instant confirmation for tax purposes
DonorMailer.receipt(donor).deliver_now
end
Ultimately, I think that Easier to Change and Easier to Understand are principles that go hand in hand. Code that is easier to change is naturally easier to understand. And code that is easier to understand will naturally be easier to change.