Typing is not a programming bottleneck by Mark Seemann
Some developers seem to think that typing is a major bottleneck while programming. It's not.
I sometimes give programming advice to people. They approach me with a software design problem, and, to the best of my ability, I suggest a remedy. Despite my best intentions, my suggestions sometimes meet resistance. One common reaction is that my suggestion isn't idiomatic, but recently, another type of criticism seems to be on the rise.
The code that I suggest is too verbose. It involves too much typing.
I'll use this article to reflect on that criticism.
The purpose of code #
Before we get into the details of the issue, I'd like to start with the big picture. What's the purpose of code?
I've discussed this extensively in my Clean Coders video on Humane Code. In short, the purpose of source code is to communicate the workings of a piece of software to the next programmer who comes along. This could easily include your future self.
Perhaps you disagree with that proposition. Perhaps you think that the purpose of source code is to produce working software. It's that, too, but that's not its only purpose. If that was the only purpose, you might as well write the software in machine code.
Why do we have high-level programming languages like C#, Java, JavaScript, Ruby, Python, F#, Visual Basic, Haskell, heck - even C++?
As far as I can tell, it's because programmers are all human, and humans have limited cognitive capacity. We can't keep track of hundreds, or thousands, of global variables, or the states of dozens of complex resources. We need tools that help us structure and understand complex systems so that they're broken down into (more) manageable chunks. High-level languages help us do that.
The purpose of source code is to be understood. You read source code much more that you write. I'm not aware of any scientific studies, but I think most programmers will agree that over the lifetime of a code base, any line of code will be read orders of magnitude more often that it's edited.
Typing speed #
How many lines of code do you produce during a productive working day?
To be honest, I can't even answer that question myself. I've never measured it, since I consider it to be irrelevant. The Mythical Man-Month gives the number as 10, but let's be generous and pretend it's ten times that. This clearly depends on lots of factors, such as the language in which you're writing, the state of the code base, and so on. You'd tend to write more lines in a pristine greenfield code base, whereas you'll write fewer lines of code in a complicated legacy code base.
How many characters is a line of code? Let's say it's 80 characters, because that's the maximum width code ought to have. I realise that many people write wider lines, but on the other hand, most developers (fortunately) use indentation, so as a counter-weight, code often has some blank space to the left as well. This is all back-of-the-envelope calculations anyway.
When I worked in a Microsoft product group, we typically planned that a productive, 'full' day of coding was five hours. Even on productive days, the rest was used in meetings, administration, breaks, and so on.
If you write code for five hours, and produce 100 lines of code, at 80 characters per line, that's 8,000 characters. Your IDE is likely to help you with statement completion and such, but for the sake of argument, let's pretend that you have to type it all in.
8,000 characters in five hours is 1,600 characters per hour, or 27 characters per minute.
I'm not a particularly fast typist, but I can type ten times faster than that.
Typing isn't a bottleneck.
Is more code worse? #
I tend to get drawn into these discussions from time to time, but good programming has little to do with how fast you can produce lines of code.
To be clear, I entirely accept that statement completion, refactoring support, and such are, in general, benign features. While I spend most of my programming time thinking and reading, I also tend to write code in bursts. The average count of lines per hour may not be great, but that's because averages smooth out the hills and the valleys of my activity.
Still, increasingly frequent objection to some of my suggestions is that what I suggest implies 'too much' code. Recently, for example, I had to defend the merits of the Fluent Builder pattern that I suggest in my DI-Friendly Library article.
As another example, consider two alternatives for modelling a restaurant reservation. First, here's a terse option:
public class Reservation { public DateTimeOffset Date { get; set; } public string Email { get; set; } public string Name { get; set; } public int Quantity { get; set; } public bool IsAccepted { get; set; } }
Here's a longer alternative:
public sealed class Reservation { public Reservation( DateTimeOffset date, string name, string email, int quantity) : this(date, name, email, quantity, false) { } private Reservation( DateTimeOffset date, string name, string email, int quantity, bool isAccepted) { Date = date; Name = name; Email = email; Quantity = quantity; IsAccepted = isAccepted; } public DateTimeOffset Date { get; } public string Name { get; } public string Email { get; } public int Quantity { get; } public bool IsAccepted { get; } public Reservation WithDate(DateTimeOffset newDate) { return new Reservation(newDate, Name, Email, Quantity, IsAccepted); } public Reservation WithName(string newName) { return new Reservation(Date, newName, Email, Quantity, IsAccepted); } public Reservation WithEmail(string newEmail) { return new Reservation(Date, Name, newEmail, Quantity, IsAccepted); } public Reservation WithQuantity(int newQuantity) { return new Reservation(Date, Name, Email, newQuantity, IsAccepted); } public Reservation Accept() { return new Reservation(Date, Name, Email, Quantity, true); } public override bool Equals(object obj) { if (!(obj is Reservation other)) return false; return Equals(Date, other.Date) && Equals(Name, other.Name) && Equals(Email, other.Email) && Equals(Quantity, other.Quantity) && Equals(IsAccepted, other.IsAccepted); } public override int GetHashCode() { return Date.GetHashCode() ^ Name.GetHashCode() ^ Email.GetHashCode() ^ Quantity.GetHashCode() ^ IsAccepted.GetHashCode(); } }
Which alternative is better? The short version is eight lines of code, including the curly brackets. The longer version is 78 lines of code. That's ten times as much.
I prefer the longer version. While it takes longer to type, it comes with several benefits. The main benefit is that because it's immutable, it can have structural equality. This makes it trivial to compare objects, which, for example, is something you do all the time in unit test assertions. Another benefit is that such Value Objects make better domain models. The above Reservation
Value Object only shows the slightest sign of emerging domain logic in the Accept
method, but once you start modelling like this, such objects seem to attract more domain behaviour.
Maintenance burden #
Perhaps you're now convinced that typing speed may not be the bottleneck, but you still feel that you don't like the verbose Reservation
alternative. More code could be an increased maintenance burden.
Consider those With[...]
methods, such as WithName
, WithQuantity
, and so on. Once you make objects immutable, such copy-and-update methods become indispensable. They enable you to change a single property of an object, while keeping all other values intact:
> var r = new Reservation(DateTimeOffset.Now, "Foo", "foo@example.com", 3); > r.WithQuantity(4) Reservation { Date=[11.09.2018 19:19:29 +02:00], Email="foo@example.com", IsAccepted=false, Name="Foo", Quantity=4 }
While convenient, such methods can increase the maintenance burden. If you realise that you need to change the name of one of the properties, you'll have to remember to also change the name of the copy-and-update method. For example, if you change Quantity
to NumberOfGuests
, you'll have to also remember to rename WithQuantity
to WithNumberOfGuests
.
I'm not sure that I'm ready to concede that this is a prohibitive strain on the sustainability of a code base, but I do grant that it's a nuisance. This is one of the many reasons that I prefer to use programming languages better equipped for such domain modelling. In F#, for example, a record type similar to the above immutable Reservation
class would be a one-liner:
type Reservation =
{ Date : DateTimeOffset; Name : string; Email : string; Quantity : int; IsAccepted : bool }
Such a declarative approach to types produces an immutable record with the same capabilities as the 78 lines of C# code.
That's a different story, though. There's little correlation between the size of code, and how 'good' it is. Sometimes, less code is better; sometimes, more code is better.
Summary #
I'll dispense with the usual Edsger Dijkstra and Bill Gates quotes on lines of code. The point that lines of code is a useless metric in software development has been made already. My point is a corollary. Apparently, it has to be explicitly stated: Programmer productivity has nothing to do with typing speed.
Unless you're disabled in some way, you can type fast enough to be a productive programmer. Typing isn't a programming bottleneck.
Comments
Watch a bad typist code. (It's easier with coding-heavy tutorial videos). It usually go as: type four or five characters, backspace, make a correction, type another six characters, backspace, make another fix, type a few more character, etc.
They rarely get more than 10 keystrokes out before have to stop and go back. This reeks havoc with your cognitive flow, and makes it much harder to get into the "groove" of coding. Once you can type fluidly, you can concetrate on the code itself, rather than the mechanics of entering it.
Moreover, in many cases, too much verbosity might only lead to more defects (unless you're a strong advocate of TDD et similia)
That said, one thing that I really liked about your article is the version of the Builder pattern you've implemented in the Reservation class. I love immutability in my classes and I often use the Builder pattern implemented as a inner class, just to be able to access the private cTor.
One thing that I don't like with your approach is that in case you have a bit parameter list in your cTor (as you do in the example), the consumers are forced to create a "dummy" instance and then call the WithXXX() methods on it when possible. I guess it could be solved adding a static readonly Empty property on the Reservation class that can be used as a starting point.
Anyways thank you for sharing this!
James, thank you for writing. I agree that being able to type out your thoughts as fast as possible lowers the barrier between your brain and whatever medium you're working in. I had something similar in mind when I wrote
I do, however, have mounting concerns about what you call the groove of coding. Flow state is generally characterised by a lack of reflection that I have often found to be counter-productive. These days, when programming, I deliberately use the Pomodoro technique - not to motivate me to code, but to force me to take breaks. Uncountable are the times I've had an important realisation about my work as soon as I'm away from the keyboard. These insights never seem to come to me when I'm in flow.You could create a more generic
With
method that would enable modifying any property. Here is an alternative design of theReservation
class including such a method. It is untested, so it might not work properly in every case.Of course this design is not type-safe and we are throwing exceptions whenever the types of property and value don't match. Property names can be passed using
nameof
which will provide compile-time feedback. I believe it would be possible to write a code analyzer that would help calling theWith
method by raising compilation errors whenever the types don't match. TheWith
method could be even designed as an extension method onobject
and distributed in a Nuget package with the anayler alongside it.