This post was written by Chris Oliver on May 6, 2014.
As you know, writing code goes far deeper than you probably expected when you first started. Programming is a form of expression, like writing a novel. The better you are able to express your intentions, the better your code will be.
Other people (including yourself six months from now) have to interpret what you were thinking when they go to fix a bug in your application. They have to get familiar with your thought process to figure out how to not only update your code but make sure they don't break it.
That's why clarity in programming is so important. It is a way for you to express your intentions of what you want to accomplish. We are defining a set of rules on how interactions with software should work.
The definition of clarity is this:
clearness or lucidity as to perception or understanding; freedom from indistinctness or ambiguity.
I think the last part is the impost important: Freedom from indistinctness or ambiguity.
Think about this in terms of code. When was the last time you looked at a piece of logic thinking "What in the hell is this doing?" The reason you ask yourself that is because there is too much ambiguity. There is no obvious path for the code to take.
If statements are a great example. Every time you read an "if" you must think okay, assuming this is true then this happens. If it isn't true, other things happen. What are those other things? What triggers them? What defines this behavior? Why can't I hold all these limes?
When your revisit a project like this, you have to step inside someone else's mind for a while to be able to understand what is going on. If the code is written very clearly, you will be able to jump into their mindset with ease and begin making changes. If it is unclear, it could take you days or weeks to figure out.
It provides you with immense flexibility on how it can be written with the purpose of providing understanding to the developers.
Rails is a great example of this. It is nothing more than a set of tools and helpers that allow you to write web applications with a certain level of clarity. If a programmer is generally familiar with ActiveRecord, they will have no trouble interacting with the database. Want to create a new User record? Just call User.create. The User.create method is so clear that it is hard to imagine a better way of expressing this in code.
Even the folder structure provides some clarity and insight into an application. Someone with experience in the Model-View-Controller pattern will be able to get a general idea of how your application works by just looking at the files and folders in your project. Compare this to a project without any organization. You've probably created an app in your early days as a programmer that was just a complete mess.
This even extends far down into the smaller details of Rails. When you want to make a word plural based upon a number, you can just call
pluralize. No need for if statements or keeping track of English rules on pluralization. It's already there for you. The
pluralize method works exactly how you would expect. A quick glance at the name of the method and you know what it does. Seeing this used in an application provides instant clarity to the programmer who is reading the code.
In Test Driven Development, you often start by writing the code you want to have. You define the clearest written code first and then you go implement it. This way you start with a clarity-first approach as you try to maximize the amount of understanding conveyed in the way your code is written.
You think "I'd really like to check if the user is signed in".
So what should you do? You define a method called
user_signed_in? that returns true or false. This means you can use
if user_signed_in? to check if the user is signed in your code and provide the intent of your code with the utmost clarity.
The huge benefit here is that Ruby allows you to add a question mark at the end of your method names. Everyone knows what the return value of this method is without thinking about it.
So clarity in writing new code is important because it helps simplify things in the future. But what are we going to do with that code in the future? Maintenance. Fixing bugs. Changing behaviors. Adding new features.
When you think about this, that's a huge amount of time. The code you write today is going to significantly affect the time spent in the future. Poorly written code today can mean months of development later. But clearly written code today can also mean merely minutes or hours of maintenance in the future.
This means (assuming you stick with a project) you can choose to invest your time either now or in the future. Which is the logical place to invest in? Which approach will save your company or clients more money? Which approach will make you happier in the long run?
The best programmers tend to be the ones who care about clarity.
Good article Chris, ruby has the clarity implied, programmers don't...
however "ubiquitous language" is something that could help to
programmers on this, thanks for share your thoughts!
1.What's your take on defining methods purely to clarify the actions therein? for example, to cover up extremely ugly, unintuitive instances of operations like mapping and regex
2. I envision one of the oft-asked questions regarding this subject being "how long should method names be?" So I'll pose it also:
what's a better metric or guideline for method names than their length?
1. Often times in Ruby you'll see one or two line methods. This can be useful. In fact,
user_signed_in? could be as simple as
User.find(session[:user_id]). If it finds a user, you're signed in and it should return true. If it throws an exception because it can't find the record, you're not and it should return false.
But this is just one way of handling it. You may not choose to pull the record out of the database to verify they are signed in. It seems wise to both verify the user_id in the session AND load the user.
The merits of writing a method called
user_signed_in? are such that you no longer have to care how the verification works. You just simply trust that it does its job and go about your business. In fact, I would venture to guess that a large amount of people haven't even contemplated how
user_signed_in? works if they have never tried building an authentication system from scratch.
2. Related to point #1, your method names should be as concise as possible while still conveying clarity. We could have methods named
check_if_user_is_signed_in but far too dramatic.
The way you describe the code in words out loud to a coworker often give insight into how the code should ideally be written. If you tell a programmer "okay, so we want this to happen if the user is signed in" translates almost directly to:
I think the closest thing to a metric I can give is how similar your code is to the way you speak. The closer your code reads to what you speak means that understanding can be conveyed at higher bandwidth.
There are no hard and fast rules to this as it changes between industries, environments, and even countries that you live and work in. Culture affects this strongly.
The way to learn this is to begin reading LOTS of source code for large, well-established, and well known projects like Sinatra, Jekyll, and Rails. See how they go about their naming schemes and find the style that's most appealing to you that also provides clarity.
As you see examples in other projects, you will be able to pick up on the subtle nuances that make the difference between directly naming something and naming with an added dash of clarity.
Awesome answer for #2. The "say it out loud bit" rings of truth and practicality.
For #1, let's take it a level deeper: what about refactoring out of a method for clarity? For instance, moving the really_unintuitive_ugly_logic into its own function
This is hard to talk about in the abstract, so here's an example from an app I built recently. We have a Campaign object that, depending on its type, has multiple ways to be "expired".
published? && ( (timer? && expires_at < Time.zone.now) || (quantity? && subscribers.shared.count >= quantity) )
Now in the current iteration, I've already abstracted out
published? to handle the logic for how publishing works. The other pieces of logic are dependent upon the type of Campaign.
The simplest approach is to simply dump in the logic as you see here.
The better solution is to refactor it into other methods.
published? && (timer_expired? || quantity_expired?)
timer? && expires_at < Time.zone.now
quantity? && subscribers.shared.count >= quantity
You should be able to instantly see the benefits in clarity.
Campaigns can only have their timer expired if they are a timer based campaign and the time has elapsed.
expired? encompasses a higher level definition of what being expired means. It's very clear now that an expired campaign has a few paths to their "expiration" and also allows for easy modification of individual expiration methods. You can also add new types with relative easy without adding much confusion.
Is that a better example?