How to Write Good Code — An Introduction to Sandi Metz’ s Rules
Write short code with good names
If I can only use a sentence to describe how to write clean code, I would say,
“write short code with good names.”
From my experience, short code is almost always easier to understand than longer code. A good name is also necessary in order to easily understand the purpose of that line of code.
We need short code because of our bad memories
Man v. Machine
A modern computer can easily remember several gigabytes of information since they have RAM. On top of that, a PC can store even more information since it can move the data between RAM (temporary storage for open applications) and its hard drive (disk storage).
That’s why a single server can run a huge application with hundreds of thousands of lines of code without any problems.
An average human, on the other hand, has a limited capacity of short-term memory compared to computers. That’s why phone numbers are adjusted to groups of 7 or 8, with groups of 3 or 4 additional numbers for area codes. An average human brain can only process long numbers in “chunks,” and this process helps us visualize the numbers so they can be stored in our short term memories. If we have difficulty memorizing a phone number, imagine how much mental effort it would take to memorize lines of code. That’s a huge disadvantage compared to our machines.
Screen Restriction
In my text editor, one page can only show 54 lines of text, using a 14-pixel size font. That means it requires 2 pages to show just 100 lines of code. And even if there are only 2 pages of code, I’d still need to scroll back and forth to check the code. This is an avoidable inconvenience.
A shorter class, function would be much easier for us to read and understand.
Abstractions
The principle of Abstraction was developed to prevent these inconveniences. During abstraction, a dev shows only the relevant data about an object to reduce complexity. Seeing only the aspects that are useful to the function increases efficiency and allows the focus to be on just the task at hand. Because of our bad memories, we need variables, functions, and classes to help us to abstract the code.
The process of abstraction is like building a source code tree or a pyramid for a project:
- The top contains the higher level concepts, built from the blocks below, and
- The bottom contains the real working code.
Creating abstractions means that a single line of source code can represent more concepts. We won’t need to occupy our already-full minds with extraneous code.
We need better names for better abstractions
Writing short code doesn’t automatically mean it’s good code, however. Short code can be a disaster when it has a bad name.
Consider the following class:
Though this class extracted three steps from post_a_new_feed
into private methods, it’s not a well-written class. It fails to give clear names to the private methods. step_1
, step_2
, step_3
are nearly meaningless since we don’t know what the steps refer to. It’s no better than splitting post_a_new_feed
into three parts using empty lines. This class just rearranges the mess somewhere else, like sweeping dirt under a rug.
It would be better if we gave these private methods clearer names, like prepare_feed_for_publishing
, send_emails_to_subscribers
, etc., Then these abstractions will become more obvious and help us rearrange our code better.
Naming things is what we do
Abstraction is the only way for us to manage a large code repository. And the way to creating better abstractions is to give them better names.
Naming things is probably the most important thing we do as software developers:
- When we create a new project, we need to name it.
- When we create a new variable, we need to name it.
- When we create a new method, we need to name it.
- When we create a new class, we need to name it.
Despite the fact that it’s such a common task, naming is still one of the hardest things to do well in programming.
Every abstraction we make relies on a name, so if we can name things better and explain the concept behind this name clearly, we’ll have better abstractions.
Sandi Metz’s Rules
Rule #1: 100 lines per class
Limit a class to 100 lines of code.
Single Responsibility Principle
According to Robert C. Martin (a.k.a. Uncle Bob), co-author of the Agile Manifesto, a class should only have one responsibility in the development of any feature. Based on this principle, a “class should only have one reason to change” (to be rewritten).
If a class has more than 100 lines of code, there is a high probability for this class to have more than one reason to change. A class that has a single responsibility is cohesive, with a lower probability for multiple reasons to change.
Rule #2: 5 lines per method
A method should be no longer than 5 lines of code.
Here are some useful quotes that explain this rule:
Robert C. Martin from his book, Clean Code —
“The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.”
Ben Orenstein from thoughtbot —
Basically, 5 lines of code should equal to 1 “if” statement
So this rule is asking us to:
1. Avoid writing logic that is more complicated than an if
statement
2. Write only 1 line per branch
3. Never use elsif
Actually, if you look into the update action in the Rails controller scaffolding template, you will find a perfect example of this kind of 5-line-method. Every method we write can actually be split into 4 basic steps and 2 additional optional steps:
- Collecting input
- Performing work
- Delivering output
- Handling failures
- (Optional) Diagnostics
- (Optional) Cleanup
Based on this assumption, we can extract every step into a helper method and make every method tell a nice story. And every method will only contain approximately 5 lines of method calls.
Rule #3: 4 parameters per method
Pass no more than 4 parameters into a method.
Hash options are also parameters. Avoid writing a hash parameter like this:
Parameters are dependencies:
- An extra parameter is an extra dependency for our class.
- If a method requires multiple parameters, whenever one of their API is changed, the corresponding method might require change, too. That creates extra work for us, since we now need to check the methods whenever we change the API.
So, the fewer the parameters a method needs, the easier it would be for us to maintain this method.
If there are more than 4 parameters, most of the time, we can pull a new object out of some of the parameters. A typical example would be date ranges:
We can then extract this data pair (starts_at
and ends_at
) to a new class (DateRange
):
As we can see in the above example, this rule actually leads us to a new abstraction
- The new abstraction consolidates code into a single place.
- The new abstraction names this consolidated code.
- The new abstraction tells you where your code relies on an idea.
Rule #4: 1 object per view
A view template should only refer to 1 object
Facade Pattern
A facade is an object that provides a simplified interface to a larger body of code. In Rails world, it’s usually called Presenter. We can use a Facade object in view template to reduce the dependency between the view template and controller actions. This is useful because it’s:
- Easier to test — when we need to test a view, we can simply setup a mock facade object rather than a bunch of mock objects to test it; and it
- Encourages abstractions — we can use a facade object to wrap many objects for a view. Then this view dispatches objects in this facade object to different partials.
Rule #5: 2 class names per controller action
A controller action can only use 2 classes
The purpose for these 2 classes is to have:
- 1 Facade Object for presentation, and
- 1 Service Object for business logic.
As we explained in the previous section, a facade is the object that provides a simple interface for your views to your business logic.
We can build this facade object in the controller action or the service object, and then send it to our view, so that our view only refers to 1 class (facade) and our controller only uses at most 2 classes (service and facade).
You can use service object to make a controller thin:
- A Rails controller is always hard to unit test
- Rails is moving away from controller test to integration test as well
And to wrap the controller logic:
- Integration test controller action
- Unit test service object
Why do we need these rules?
These Rules are just simplified versions of code metrics.
Unfortunately, we don’t have a perfect standard to measure good and bad code. That is because:
- The abstraction level of the code is hard to quantify;
- Sometimes, as a developer, we’re not sure why one code is better than another; and
- We have limited metrics to help us measure our code.
In terms of metrics, we have the following:
- Source Line of Code (SLOC or LOC): The number of lines in text of the source code
- Cyclomatic Complexity: The number of linearly independent paths through a program’s source code
- Assignments, Branches and Conditions (ABC) Metric: count of variable assignments, branches of control (function calls or message sends), and count of conditional logic, etc.
But these metrics often require calculations and are hard to understand, especially for junior developers.
The 5 rules basically act as simplified versions of these metrics to help us write cleaner code:
- 100 lines per class: Simplified version of SLOC
- 5 lines per method: Simplified version of SLOC and Cyclomatic Complexity
- 4 parameters per method: Simplified version of ABC metric
- 1 object per view: Simplified version of ABC metric
- 2 class names per controller action: Simplified version of ABC metric
Rules help us to make better trade-offs
Writing code is often about making tradeoffs. Different solutions to a software problem require different concessions. Even DRY (aka Don’t Repeat Yourself) comes with tradeoffs. For example, since we need to introduce a new level of abstraction in order to DRY something out, the caller no longer knows the result or the process, but only the message it should send. Often, when applying DRY too early in a process, it can do more harm than good.
The best solution is to choose what benefits us the most and hurts us the least. When we first started writing software, however, most devs didn’t know how to make the best trade-off. These rules can serve as a foundational guideline for developers early in their careers and help them make better trade-offs and thus write cleaner code.
When to break the rule
When you have a good reason to break them.
These rules are just simplified metrics and guidelines for junior developers who have trouble making good trade-offs.
These rules are not absolute.
If you find a good reason to break these rules, and can explain it clearly to your pair or other devs, feel confident to break them.
You can find more discussions about these Rules in these places:
- Where Sandi Metz first talked about her 5 rules — 087 RR Book Club: Practical Object-Oriented Design in Ruby with Sandi Metz
- Where Sandi Metz talked more about the Rules more officially — Baruco 2013: Rules, by Sandi Metz
- 1. Sandi & Derek’s Rules | The Bike Shed
- 70: Make Small Things | The Bike Shed
- Sandi Metz’s Rules — Online Video Tutorial by thoughtbot
- Sandi Metz’s Rules For Developers
If you want to know more about short code, you can read the following books:
- The importance of short code — Clean Code: A Handbook of Agile Software Craftsmanship
- How to shorten your code (long classes, long methods, etc.) — Refactoring by Martin Fowler
These are 2 great books that Sandi wrote:
Sandi has presented at many conferences. We recommend going to YouTube and watching them all if you have time: