If we wanted to, we could instead pass our block using Proc.new. Since my_method takes a block and not a Proc object, we would have to prefix Proc.new with an ampersand to convert the Proc object into a block.
(If you didn’t know, prefixing an expression with & will convert a proc to a block and a block to a proc. See this post for more details on how that works.)
def my_method(&block)
block.call("hello")
end
puts my_method(&Proc.new { |value| value.upcase })
There would never really be a practical reason to express the syntax this way, but I wanted to show that it’s possible. This “second way” example will also connect the first way and the third way.
Third way: using to_proc
All symbols respond to a method called to_proc which returns a Proc object. If we do :upcase.to_proc, it gives us a Proc object that’s equivalent to what we would have gotten by doing Proc.new { |value| value.upcase }.
def my_method(&block)
block.call("hello")
end
puts my_method(&:upcase.to_proc)
Fourth way: passing a symbol
I’ll show one final way. When Ruby sees an argument that’s prefixed with an ampersand, it attempts to call to_proc on the argument. So our to_proc on &:upcase.to_proc is actually superfluous. We can just pass &:upcase all by itself.
def my_method(&block)
block.call("hello")
end
puts my_method(&:upcase)
What ultimately gets passed is the Proc object that results from calling :upcase.to_proc. Actually, more precisely, what gets passed is the block that results from calling &:upcase.to_proc, since the & converts the Proc object to a block.
Passing symbols to map
With the understanding of the above, you now know that this:
[1, 2, 3].map(&:to_s)
Is equivalent to this:
[1, 2, 3].map(&:to_s.to_proc)
Which is equivalent to this:
[1, 2, 3].map(&Proc.new { |number| number.to_s })
Which, finally, is equivalent to this:
[1, 2, 3].map { |number| number.to_s }
So, contrary to the way it may seem, there aren’t two different “versions” of the map method. The shorthand syntax is owing to the way that Ruby passes Proc objects.
Takeaways
When an argument is prefixed with &, Ruby attempts to call to_proc on it.
All symbols respond to the to_proc method.
There aren’t two different versions of the map method. The shorthand syntax is possible due to the two points above.
Ruby blocks are one of the areas of the language that’s simultaneously one of the most fundamental parts of the language but perhaps one of the hardest to understand.
Ruby functions like map and each operate using blocks. You’ll also find heavy use of blocks in popular Ruby libraries including Ruby on Rails itself.
If you start to dig into Ruby blocks, you’ll discover that, in order to understand blocks, you have to understand something else called Proc objects.
And as if that weren’t enough, you’ll then discover that if you want to deeply understand Proc objects, you’ll have to understand closures.
The concept of a closure is one that suffers from 1) an arguably misleading name (more about this soon) and 2) unhelpful, jargony explanations online.
My goal with this post is to provide an explanation of closures in plain language that can be understood by someone without a Computer Science background. And in fact, a Computer Science background is not needed, it’s only the poor explanations of closures that make it seem so.
Let’s dig deeper into what a closure actually is.
What a closure is
A closure is a record which stores a function plus (potentially) some variables.
I’m going to break this definition into parts to reduce the chances that any part of it is misunderstood.
A closure is a record
which stores a function
plus (potentially) some variables
I’m going to discuss each part of this definition individually.
First, a reminder: the whole reason we’re interested in Ruby closures is because of the Ruby concept called a Proc object, which is heavily involved in blocks. A Proc object is a closure. Therefore, all the examples of closures in this post will take the form of Proc objects.
If you’re not yet familiar with Proc objects, I would suggest taking a look at my other post, Understanding Ruby Proc objects, before continuing. It will help you understand the ideas in this post better.
First point: “A closure is a record”
A closure is a value that can be assigned to a variable or some other kind of “record”. The term “record” doesn’t have a special technical meaning here, we just use the word “record” because it’s broader than “variable”. Shortly we’ll see an example of a closure being assigned to something other than a variable.
Here’s a Proc object that’s assigned to a variable.
my_proc = Proc.new { puts "I'm in a closure!" }
my_proc.call
Remember that every Proc object is a closure. When we do Proc.new we’re creating a Proc object and thus a closure.
Here’s another closure example. Here, instead of assigning the closure to a variable, we’re assigning the closure to a key in a hash. The point here is that the thing a closure gets assigned to isn’t always a variable. That’s why we say “record” and not “variable”.
my_stuff = { my_proc: Proc.new { puts "I'm in a closure too!" } }
my_stuff[:my_proc].call
Second point: “which stores a function”
As you may have deduced, or as you may have already known, the code between the braces (puts "I'm in a closure!") is the function we’re talking about when we say “a closure is a record which stores a function”.
my_proc = Proc.new { puts "I'm in a closure!" }
my_proc.call
A closure can be thought of as a function “packed up” into a variable. (Or, more precisely, a variable or some other kind of record.)
Third point: “plus (potentially) some variables”
Here’s a Proc object (which, remember, is a closure) that involves an outside variable.
The variable number_of_exclamation_points gets included in the “environment” of the closure. Each time we call the closure that we’ve named amplifier, the number_of_exclamation_points variable gets incremented and one additional exclamation point gets added to the string that gets outputted.
number_of_exclamation_points = 0
amplifier = Proc.new do
number_of_exclamation_points += 1
"louder" + ("!" * number_of_exclamation_points)
end
puts amplifier.call # louder!
puts amplifier.call # louder!!
puts amplifier.call # louder!!!
puts amplifier.call # louder!!!!
puts number_of_exclamation_points # 4 - the original variable was mutated
As a side note, I find the name “closure” to be misleading. The fact that the above closure can mutate number_of_exclamation_points, a variable outside the function’s scope, seems to me like a decidedly un-closed idea. In fact, it seems like there’s a tunnel, an opening, between the closure and the outside scope, through which changes can leak.
I personally started having an easy time understanding closures once I stopped trying to connect the idea of “a closed thing” with the mechanics of how closures actually work.
Takeaways
Ruby blocks heavily involve Proc objects.
Every Proc object is a closure.
A closure is a record which stores a function plus (potentially) some variables.
Ruby blocks can be difficult to understand. One of the details which presents an obstacle to fully understanding blocks is the fact that there is more than one way to call a block.
In this post we’ll go over the two most common ways of calling a Ruby block: block.call and yield.
There are also other ways to call a block, e.g. instance_exec. But that’s an “advanced” topic which I’ll leave out of the scope of this post.
Here are the two common ways of calling a Ruby block and why they exist.
The first way: block.call
Below is a method that accepts a block, then calls that block.
def hello(&block)
block.call
end
hello { puts "hey!" }
If you run this code, you’ll see the output hey!.
You may wonder what the & in front of &block is all about. As I explained in a different post, the & converts the block into a Proc object. The block can’t be called directly using .call. The block has to be converted into a Proc object first and then .call is called on the Proc object.
The example below is very similar to the first example, except instead of using block.call we’re using yield.
def hello(&block)
yield
end
hello { puts "hey!" }
You may wonder: if we already have block.call, why does Ruby provide a second, slightly different way of calling a block?
One reason is that yield gives us a capability that block.call doesn’t have. In the below example, we define a method and then pass a block to it, but we never have to explicitly specify that the method takes a block.
def hello
yield
end
hello { puts "hey!" }
As you can see, yield gives us the ability to call a block even if our method doesn’t explicitly take a block. (Side note: any Ruby method can be passed a block, even if the method doesn’t explicitly take one.)
The fact that yield exists raises the question: why not just use yield all the time?
The answer is that when you use block.call, you have the ability to pass the block to another method if you so choose, which is something you can’t do with yield.
When we put &block in a method’s signature, we can do more with the block than just call it using block.call. We could also, for example, choose not to call the block but rather pass the block to a different method which then calls the block.
Takeaways
There are two common ways to call a Ruby block: block.call and yield.
Unlike block.call, yield gives us the ability to call a block even if our method doesn’t explicitly take a block.
Unlike using an implicit block and yield, using and explicit block allows us to pass a block to another method.
Here’s a code sample that I’ve grabbed more or less at random from the Rails codebase.
def form_for(record, options = {}, &block)
The first two arguments, record and options = {}, are straightforward to someone who’s familiar with Ruby. But the third argument, &block, is a little more mysterious. Why the leading ampersand?
This post will be the answer to that question. In order to begin to understand what the leading ampersand is all about, let’s talk about how blocks relate to Proc objects.
Blocks and Proc objects
Let’s talk about blocks and Proc objects a little bit, starting with Proc objects.
Here’s a method which takes an argument. The method doesn’t care of what type the argument is. All the method does is output the argument’s class.
After we define the method, we call the method and pass it a Proc object. (If you’re not too familiar with Proc objects, you may want to check out my other post, Understanding Ruby Proc objects.)
def proc_me(my_proc)
puts my_proc.class
end
proc_me(Proc.new { puts "hi" })
If you run this code, the output will be:
Proc
Not too surprising. We’re passing a Proc object as an argument to the proc_me method. Naturally, it thinks that my_proc is a Proc object.
Now let’s add another method, block_me, which accepts a block.
def proc_me(my_proc)
puts my_proc.class
end
def block_me(&my_block)
puts my_block.class
end
proc_me(Proc.new { puts "hi" })
block_me { puts "hi" }
If we run this code the output will be:
Proc
Proc
Even though we’re passing a Proc object the first time and a block the second time, we see Proc for both lines.
The reason that the result of my_block.class is Proc is because a leading ampersand converts a block to a Proc object.
Before moving on I encourage you to try out the above code in a console. Poke around at the code and change some things to see if it enhances your understanding of what’s happening.
Converting the Proc object to a block before passing the Proc object
Here’s a slightly altered version of the above example. Notice how my_proc has changed to &my_proc. The other change is that Proc.new has changed to &Proc.new.
def proc_me(&my_proc) # an & was added here
puts my_proc.class
end
def block_me(&my_block)
puts my_block.class
end
proc_me(&Proc.new { puts "hi" }) # an & was added here
block_me { puts "hi" }
If we run this code the output is the exact same.
Proc
Proc
This is because not only does a leading ampersand convert a block to a Proc object, but a leading ampersand also converts a Proc object to a block.
When we do &Proc.new, the leading ampersand converts the Proc object to a block. Then the leading ampersand in def proc_me(&my_proc) converts the block back to a Proc object.
I again encourage you to run this code example for yourself in order to more clearly understand what’s happening.
The differences between blocks and Proc objects
Ruby has a class called Proc but no class called Block. Because there’s no class called Block, nothing can be an instance of a Block. The material that Ruby blocks are made out of is Proc objects.
That’s because the syntax { puts "hi" } doesn’t make any syntactical sense on its own. If we want to say { puts "hi" }, there are only two ways we can do it.
First way: put it inside a Proc object
That would look like this:
Proc.new { puts "hi" }
In this way the { puts "hi" } behavior is “packaged up” into an entity that we can then do whatever we want with. (Again, see my other post on Ruby proc objects for more details.)
Second way: use it to call a method that takes a block
That would look like this:
some_method { puts "hi" }
Why converting a block to a Proc object is necessary
Let’s take another look at our code sample from the beginning of the post.
def form_for(record, options = {}, &block)
In methods that take a block, the syntax is pretty much always &block, never just block. And as we’ve discussed, the leading ampersand converts the block into a Proc object. But why does the block get converted to a Proc object?
Since everything in Ruby is an instance of some object, and since there’s no such thing as a Ruby Block class, there can never be an object that’s a block. In order to be able to have an instance of something that represents the behavior of a block, that thing has to take the form of a Proc object, i.e. an instance of the class Proc, the stuff that Ruby blocks are made out of. That’s why methods that explicitly deal with blocks convert those blocks to Proc objects first.
Takeaways
A leading ampersand converts a block to a Proc object and a Proc object to a block.
There’s no such thing as a Ruby Block class. Therefore no object can be an instance of a block. The material that Ruby blocks are made out of is Proc objects.
The previous points taken together are why Ruby block arguments always appear as e.g. &block. The block can’t be captured in a variable unless it’s first converted to a Proc object.
If you’re a Ruby programmer, you almost certainly use Proc objects all the time, although you might not always be consciously aware of it. Blocks, which are ubiquitous in Ruby, and lambdas, which are used for things like Rails scopes, both involve Proc objects.
In this post we’re going to take a close look at Proc objects. First we’ll do a Proc object “hello world” to see what we’re dealing with. Then we’ll unpack the definition of Proc objects that the official Ruby docs give us. Lastly we’ll see how Proc objects relate to other concepts like blocks and lambdas.
A Proc object “hello world”
Before we talk about what Proc objects are and how they’re used, let’s take a look at a Proc object and mess around with it a little bit, just to see what one looks like.
The official Ruby docs provide a pretty good Proc object “hello world” example:
square = Proc.new { |x| x**2 }
We can see how this Proc object works by opening up an irb console and defining the Proc object there.
We can kind of intuitively understand how this works. A Proc object behaves somewhat like a method: you define some behavior and then you can use that behavior repeatedly wherever you want.
Now that we have a loose intuitive understanding, let’s get a firmer grasp on what Proc objects are all about.
Understanding Proc objects more deeply
The official Ruby docs’ definition of Proc objects
According to the official Ruby docs on Procs objects, “a Proc object is an encapsulation of a block of code, which can be stored in a local variable, passed to a method or another Proc, and can be called.”
This definition is a bit of a mouthful. When I encounter wordy definitions like this, I like to separate them into chunks to make them easier to understand.
The Ruby Proc object definition, broken into chunks
A Proc object is:
an encapsulation of a block of code
which can be stored in a local variable
or passed to a method or another Proc
and can be called
Let’s take these things one-by-one.
A Proc object is an encapsulation of a block of code
What could it mean for something to be an encapsulation of a block of code? In general, when you “encapsulate” something, you metaphorically put it in a capsule. Things that are in capsules are isolated from whatever’s on the outside of the capsule. Encapsulating something also implies that it’s “packaged up”.
So when the docs say that a Proc object is “an encapsulation of a block of code”, they must mean that the code in a Proc object is packaged up and isolated from the code outside it.
A Proc object can be stored in a local variable
For this one let’s look at an example, straight from the docs:
square = Proc.new { |x| x**2 }
As we can see, this piece of code creates a Proc object and stores it in a local variable called square. So this part of the definition, that a Proc object can be stored in a local variable, seems easy enough to understand.
A Proc object can be passed to a method or another Proc
This one’s a two-parter so let’s take each part individually. First let’s focus on “A Proc object can be passed to another method”.
Here’s a method which can accept a Proc object. The method is followed by the definition of two Proc objects: square, which squares whatever number you give it, and double, which doubles whatever number you give it.
If you were to run this code you would get the following output:
25
10
So that’s what it means to pass a Proc object into a method. Instead of passing data as a method argument like normal, you can pass behavior. Or, to put it another way, you can pass an encapsulation of a block of code. It’s then up to that method to execute that encapsulated block of code whenever and however it sees fit.
If we want to pass a Proc object into another Proc object, the code looks pretty similar to our other example above.
perform_operation_on = Proc.new do |number, operation|
operation.call(number)
end
square = Proc.new { |x| x**2 }
double = Proc.new { |x| x * 2 }
puts perform_operation_on.call(5, square)
puts perform_operation_on.call(5, double)
The only difference between this example and the one above it is that, in this example, perform_operation_on is defined as a Proc object rather than a method. The ultimate behavior is exactly the same though.
A Proc object can be called
This last part of the definition of a Proc object, “a Proc object can be called”, is perhaps obvious at this point but let’s address it anyway for completeness’ sake.
A Proc object can be called using the #call method. Here’s an example.
There are other ways to call a Proc object but they’re not important for understanding Proc objects conceptually.
Closures
In order to fully understand Proc objects, we need to understand something called closures. The concept of a closure is a broader concept that’s not unique to Ruby.
Closures are too nuanced a concept to be included in the scope of this article, unfortunately. If you’d like to understand closures, I’d suggest checking out my other post, Understanding Ruby closures.
But the TL;DR version is that a closure is a record which stores a function plus (potentially) some variables.
Proc objects and blocks
Every block in Ruby is a Proc object, loosely speaking. Here’s a custom method that accepts a block as an argument.
def my_method(&block)
puts block.class
end
my_method { "hello" }
If you were to run the above code, the output would be:
Proc
That’s because the block we passed when calling my_method is a Proc object.
Below is an example that’s functionally equivalent to the above. The & in front of my_proc converts the Proc object into a block.
The difference between lambdas and Proc objects is that the two have certain subtle differences in behavior. For example, in lambdas, return means “exit from this lambda”. In regular Proc objects, return means “exit from embracing method”. I won’t go into detail on the differences between lambdas and Proc objects because it’s outside the scope of what I’m trying to convey in this post. A have a different post that describes the differences between procs and lambdas.
Takeaways
A Proc object is an encapsulation of a block of code, which can be stored in a local variable, passed to a method or another Proc, and can be called.
A closure is a record which stores a function plus some variables. Proc objects are closures.
Blocks are Proc objects.
Lambdas are Proc objects too, although a special kind of Proc object with subtly different behavior.
One of the most common questions about testing, including what to write tests for, is what NOT to write tests for.
When people ask me what to write tests for, my honest but maybe not very helpful answer is “basically everything”. But I don’t test literally absolutely everything. There are some cases when I choose to skip tests.
My criteria for skipping a test
My habit of writing tests for everything leads me to write tests for my code by default. For most of the code I write, I actually finder it harder to write the code without tests than with tests.
But sometimes I get lazy or my instincts tell me that a test would be a waste of time. When I’m feeling like this, I ask myself three questions:
If this code were to misbehave, would it fail silently?
If this code were to misbehave, how bad would the consequences be?
If this code were to misbehave, how frequently would it fail?
How costly would it be to write a test?
If the code I’m working on would fail in a very obvious way, and the consequences are minor, and the failure would only happen once a year, and the test would be very costly to write, then that’s a case where I would probably skip the test.
On the other hand, if the code would fail silently, OR if the consequences would be bad, OR if the failure would be frequent, OR if it’s super easy to write the test, then I would just write the test.
You could think of these questions as a boolean expression where all the items get OR’d together:
“Should I write a test?” formula (boolean OR)
Might fail silently?
Consequences might be bad?
Might fail frequently?
Test is easy to write?
If any one of the items is true, then the whole boolean expression is true and I go ahead and write the test. Otherwise I skip the test without guilt or worry.
Behavior vs. implementation
There are also entire types of tests I avoid.
I don’t test for things like the presence of associations, the presence of methods, etc. Such tests are pointless. Rather, I test the behavior that these things enable. Testing the behavior is the only way you can really be sure anything works.
Takeaways
I’ll skip a test if and only if the feature won’t fail silently, the consequences won’t be bad, the failure won’t occur frequently, and the test is expensive to write.
I don’t test implementation details, but rather I test the behaviors that the implementations enable.
Good code quite frequently comes under fire. Managers explicitly or implicitly pressure developers to cut corners in order to “move fast”.
And sadly, even programmers sometimes argue against writing good code. They say things like “it doesn’t always need to be perfect, because after all we need do to ship”.
These arguments sound reasonable on the surface but, as we’ll see, they contain subtle lies.
The biggest lie in many arguments against good code is that programmers spend too much time gold-polishing their code. To me, cautioning programmers against gold-polishing is kind of like, for example, cautioning Americans not to starve themselves and become too skinny. Sure, it’s a theoretical danger, but our in reality our problem is overwhelmingly the opposite one. Similarly, the problem in the software industry is not that we spend too much time writing good code, but that we spend too much time wrestling with bad code.
If you ever find yourself pressured to write sloppy code, my goal with this post is to arm you with some arguments you can use to push back.
Here’s what I’ll go over:
What “good code” means
Weak arguments for writing good code
Weak arguments for writing bad code
My argument for writing good code
Let’s start with what “good code” means to me.
What “good code” means
Good code is code that’s fast to work with. Good code is easy to understand and change. To me it’s nothing more than that.
If the code can be changed quickly and easily, then it’s good, by definition. If the code is slow and difficult to change then it’s bad. Any specific coding practices like short methods, clear names or anything else are just incidental. The only thing that matters is whether the code can be changed quickly and easily.
One reason I like this definition of good code is that it also ought to be appealing to everyone. Developers like code that’s quick and easy to change. Non-technical stakeholders also ought to like the idea of code that’s quick and easy to change.
People might not understand exactly what it means for code to be “high quality” or “good”, but they can certainly understand what it means to be able to work quickly.
Anytime you have to make a defense for writing good code, it seems smart to remind your “opponent” (who will hopefully become your ally) that your goal is to move fast.
Before we address some of the bad arguments for writing bad code in order to refute them, let’s first talk about some bad arguments for writing good code. It’s good to be aware of the bad arguments for your case so you can avoid trying to use them.
Weak arguments for writing good code
I think if we’re going to write good code, we should have a clear understanding of why we’re doing it. We should also be able to articulate to others exactly why we’re doing it.
The bad arguments I’ve heard for writing good code include things like “craftsmanship”, “professionalism” and “integrity”.
Saying something like “I write good code because it’s more professional to write good code” is a little bit of a copout. It doesn’t explain why it’s more professional to write good code.
Same with craftsmanship. You can say “I write good code because I believe in craftsmanship”. But that doesn’t explain what the benefits of craftsmanship supposedly are.
Such appeals are also selfish. They speak to what makes the programmer feel good, not to what benefits the business. These types of arguments are unlikely to be persuasive except perhaps to other programmers.
So when people pressure me to cut corners to get a job done quickly, I don’t ever push back with talk about craftsmanship or professionalism.
Weak arguments for writing bad code
Finally, here are some bad arguments for doing sloppy work and why I think each one is flawed.
“Perfect is the enemy of the good”
This is a good and useful saying for the cases to which it actually applies. For example, when you’re talking about project scope, “perfect is the enemy of the good” is a good saying to keep in mind. A bent toward perfectionism can eat up all your time and keep you from shipping something that’s good.
But with respect to code quality, “perfect is the enemy of the good” is almost always a false premise. Comically so, in fact.
The typical spectrum of possibilities for a coding change usually doesn’t range from “perfect” to merely “good”. Usually it ranges from “acceptable” to “nightmarish”. A more honest version of this saying would be “acceptable is the enemy of the nightmarish”.
Refutation: If someone tries to pull “perfect is the enemy of the good” on you, you can say, “Oh, don’t worry, I’m not trying to make it perfect, I’m just trying to make the code understandable enough so I can work with it.” This statement is hard to refute because it appears as though you’re agreeing with the other person. Plus no reasonable person would argue against making the code understandable enough to work with. What you’re saying is also true: you’re not trying to make the code perfect. You’re just trying to make it not nightmarish.
“Users don’t care about code”
This idea reflects a shallow, elementary level of thinking. Yes, obviously users don’t directly care about code. But bad code has negative consequences that eventually become obvious to users.
Bad code (again, by definition) is slower to work with than good code. When bad code is piled on top of other bad code, the slowdowns become exponential. Changes that should take a day take a week. Changes that should take a week take a month. Users definitely notice and care about this.
Bad code is also harder to keep bugs out of than good code. Code that’s hard to understand gives bugs safe places to hide. Users are obviously going to notice and care about bugs.
Refutation: If someone uses “users don’t care about code” on you, you can point out that users don’t care directly about bad code, but users do care about the effects of bad code, like slow delivery and buggy software.
“Your company might go out of business”
Multiple times I’ve heard something along the lines of this: “If your company goes out of business, it doesn’t matter if the code was perfect.” This might sound on the surface like a slam-dunk argument against geekishly polishing code rather than maturely considering the larger business realities. But it’s not.
All that’s needed to destroy this argument is a reminder that good code is called good because it’s faster to work with. That’s why we call it “good”.
Refutation: Good code is called good because it’s faster to work with. Cutting corners only saves time in the very very short term.
“There’s no time” or “my manager made me do it” or “they did the best they could with the time they had”
These aren’t arguments for writing bad code but rather excuses for writing bad code.
No one is holding a gun to your head and making you write shitty code. You’re the steward of your codebase. It’s your responsibility, and no one else’s, to protect the quality of the codebase so that the codebase can continue to be fast to work with.
If you consciously choose to take on technical debt, you’ll almost certainly never be granted time to pay back that technical debt. Instead you’ll have to pay interest on that technical debt for the rest of your time with that codebase.
It’s easy for your boss to tell you to cut corners. Your boss doesn’t have to (directly) live with the consequences of poor coding choices. But eventually when the poor coding choices accumulate and bring development to a crawl, your boss will blame you, not himself.
Obviously it’s not always easy to fight back against pressure to cut corners. But I think developers could stand to fight back a little more than they do (even if it means being quietly insubordinate and writing good code anyway), and I think developers would benefit greatly from doing so. And so would their bosses and the organizations they work for.
My argument for writing good code
My argument for writing good code is very simple: code that’s easy to understand and change is faster to work with. Obviously that’s better.
I’ll also point out something that might not be obvious. Coding choices are multiplicative. The coding choices you make today have an influence over how easy the code will be to work with tomorrow, the next day, and every day after that. Same with the coding choices you make tomorrow. Each day’s choices multiply against every previous day’s choices.
The result is exponential. Poor coding choices every day lead to an exponential slowdown in productivity. Good coding choices unfortunately don’t lead to an exponential speedup, but they do at least avoid the exponential slowdown.
You can think of each day’s code additions as having a score. If you add code that has an “easy-to-change score” of 90%, and you do that three days in a row, then your cumulative score is 0.9^3 = 72.9%. If you add code that has an “easy-to-change score” of 40% three days in a row, then your cumulative score is 0.4^3 = 6.4% (!). This is why programmer productivity doesn’t vary by a factor of just 10X but more like infinityX. Bad code can eventually drive productivity down to something close to 0%.
Takeaways
Our industry has a much bigger sloppy-code problem than gold-plating problem.
Good code is code that’s fast to work with.
The popular arguments for writing poor-quality code, although they sound mature and reasonable on the surface, are a result of sloppy and confused thinking.
Whether you choose to write good or bad code is your responsibility, and you’re the one who will have to live with the consequences of your decisions.
Why it’s bad to mix refactorings with behavior changes
It adds risk
Probably the biggest reason not to mix refactorings with behavior changes is that it makes it too easy to make a mistake.
When you look at the diff between the before and after versions of a piece of code, it’s not always obvious what the implications of that change are going to be. The less obvious the implications are, the more opportunity there is for a bug to slip through.
When you mix behavior changes with refactorings, the behavior change and the refactoring obscure each other, often making the change substantially harder to understand and allowing for a much greater opportunity for bugs to slip through.
Mixing refactorings with behavior changes also requires you to make your deployment deltas (i.e. the amount of change being deployed) bigger. The bigger the delta, the greater the risk.
It makes bug attribution harder
If I deploy a behavior change that was mixed with a refactoring, and then I discover that the deployment introduced a bug, I won’t know whether it was my refactoring or my behavior change that was responsible because the two were mixed together.
And then potentially I’m forced to do something painful in order to remove the bug, which is to roll back both my behavior change and my refactoring, even though only one of those two things was the culprit and the other one was innocent. If I had committed and deployed these changes separately, there’s a higher chance that I would be able to attribute the bug to either the refactoring or the behavior change and not have to roll back both.
It makes code review harder
When you mix refactoring with behavior changes, it’s hard or impossible for a reviewer to tell which is which. It makes a discussion about a code change harder because now the conversation is about two things, not just one thing. This makes for a potentially slow and painful PR review process.
How to approach refactorings instead
When I’m working on a behavior change and I discover that my work would also benefit from some refactoring, here’s what I do:
Set aside my current feature branch
Create a new branch off of master on which to perform my refactoring
Merge my refactoring branch to master (and preferably deploy master to production as well)
Merge or rebase master into my feature branch
Resume work on my feature branch
This allows me to work in a way that reduces risk, allows for easier bug attribution, makes code review easier, and generally saves a lot of time and headache.
You can find discussions online regarding the idea of a “10X programmer”. Much of what you’ll find is ridicule of the idea that 10X programmers exist.
I’ve always thought it’s fairly obvious that 10X programmers exist. In fact, I think programmers vary by a factor of way more than 10X.
Months and years vs. days and hours
When I think of a 10X programmer, I don’t think of something who can finish a job in one hour what would have taken an “average” programmer ten hours. Rather, I think of someone who can accomplish ten times as much in a year than an average programmer. I think it’s a very reasonable proposition that one programmer could accomplish 10X as much in a year than another programmer.
When programmers vary beyond 10X
To me it seems clear that programmers can vary not just by 10X or 100X but by infinityX. This is because some programmers are so good that they can solve problems that weaker programmers would never be able to solve. In this case the better programmer hasn’t produced 10X as much value as the worse programmer, but infinitely more value. (I know that you can’t divide by zero, but humor me.)
My infinite variance claim doesn’t require the weaker programmer to be very bad or even below average. Some programming projects are really hard and require a really good programmer for the project not to fail.
Cumulative work vs. non-cumulative work
It’s not possible to be a 10X worker in every type of work. For example, the best dishwasher in the world is probably not 10X more productive than the average dishwasher. The world’s fastest ditch digger probably can’t dig 10X as much as the average ditch digger. That’s because a dishwasher or ditch digger starts each day with a clean slate. They can move their body parts a little faster but that’s all and then they hit a ceiling.
With programming, each change you make to a codebase influences how easily you’ll be able to make future changes to the codebase. The work you do today is helped or burdened by the choices you made yesterday, the day before that, and so on, all the way back to the first day of the project. Each coding decision in the codebase multiplies with some number of other decisions in the codebase, producing an exponential effect. Good coding choices can’t make your work get much faster, but bad coding choices can make your work slow to a crawl or possibly even a halt. When hundreds or thousands of changes interact with each other multiplicatively, it’s not hard for codebases to vary by a factor of much more than 10X.
How to become a 10X programmer
I think being a 10X programmer is mainly a result of four skills: communication, critical thinking, process, and writing good code.
Being skilled at communication helps reduce the chances of building the wrong thing, which wastes time. It also reduces the chances of experiencing interpersonal problems with colleagues which can slow down work.
Being skilled at critical thinking helps you arrive at right answers and helps you, for example, to avoid spending time barking up the wrong tree when debugging.
Following efficient development processes (small tasks, frequent deployment, automated tests, version control, etc.) helps you avoid wasting time on the overhead of programming.
Finally, writing good code (that is, code that’s easy to understand and change) can help make future changes to the codebase faster.
All four of those areas are very broad and many books have been written on each. It’s obviously not realistic for me to go very deep into those areas here. But if you want to become a 10X programmer, I think those are the areas in which to build your skills.
The Single Responsibility Principle (SRP) is an object-oriented programming principle that says (more or less) that each object should only have one responsibility.
The main difficulty that I’ve seen others have with the SRP, which I’ve also had myself, is: what exactly constitutes a single responsibility?
The answer I’ve arrived at is that it’s a subjective judgment what constitutes a single responsibility. If I write a class that I claim has just one responsibility, someone else could conceivably look at my class and credibly argue that it has eight responsibilities. There’s no way to look at a class and objectively count the number of responsibilities it has.
Here’s how I would characterize the gist of the Single Responsibility Principle: things that are small and that are focused on one idea are easier to understand than things that are big and contain a large number of things. Understanding this principle is much more helpful than understanding exactly what a “single” responsibility is.