One way to deal with bugs is to keep making little tweaks to your code until it works. I’ll admit, I do this myself from time to time — “I probably made a stupid mistake somewhere, I just want to find it as quickly as possible and get back to what I was working on”.

More often than not, you will quickly exhausted all the obvious places to look, and at that point you are just randomly flipping bits and moving code around. This strategy works a surprising amount of time — after all, most bugs are indeed fixed by applying very small patches to the code.

Once the problem ceased to exist, it is very tempting to just move on. However, even if you have arrived at a working solution, fixing a bug this way often means that you have absolutely no idea why things were broken in the first place. This indicates a potential hole in your mental model, and that should be deeply concerning to you.

When working on big codebases, we have to lean very heavily on our internal mental models to keep us from feeling disoriented. These mental objects are often very coarse — they could be anything from “the billing subsystem” to “the backend”, or even “Rails” and “the OS kernel”. Obviously, when you cast a nest so wide, it results in some very leaky abstractions, but just like scientific models, the simplification here is a feature not a bug.

However, since we are leaning so heavily on them, when they are not aligned with reality, the result could be quite problematic. All the assumptions you made along the way could have been wrong, which could jeopardize a whole day’s work, leave behind subtle bugs or even harm your ability make further progress in the codebase. Therefore, when you noticed this happening, you should make it a priority to understand the root cause and make corrections to your mental model.

Now, this is not always going to be your fault — sometimes the abstraction is just poorly designed, just like the smart trash can in our office. Still, bad designs are everywhere, and you can’t always avoid them. Having the ability to notice this and recalibrate your mental model allows you to work around these problems.

If you are lucky, fixing your mental model could be as simple as carefully re-reading the documentation. But more often than not, the only way to figure things out is to dig in, and that requires us to understand the abstraction we are sitting on:

“When you need to hire a programmer to do mostly VB programming, it’s not good enough to hire a VB programmer, because they will get completely stuck in tar every time the VB abstraction leaks.” — Joel Spolsky

Traditional CS education tries to solve this problem with a bottom up approach. In most CS curriculums, you usually start from the bottom-most layer and learn your way up the stack of abstractions.

In your first year, you will probably do a mix of programming, math and computer architecture classes. Once you have these foundations in-place, you can move up to things like data structures and algorithms. In your third year, you may learn about operating system and databases. Finally, in your forth year, you can take all of these knowledge, and apply them to things like networking and information systems.

The nice thing about this bottom-up approach is that even though you rarely have to work directly with the low-level abstractions at the bottom of the stack, should you ever encounter a problem higher up, you will always be able to dig back down into these layers and feel comfortable there.

The CS Cliff

There is a problem though. Since there are so many different topics to cover, you don’t really have time to build that high during your 3–4 years at school. As soon as you start working in the industry, you will realize that no one actually starts from the bottom layers in the real world. Instead, everyone works with very high-level abstractions like Rails that allows them to build from the 50th level up. Suddenly, what you learned in school seems like a laughably short stack.

Let’s say you are building a JavaScript app in the browser with Rails and Ember, and your CS education stopped at “Networking” and “Information Systems”. You might not realize it, but there is actually a lot going on in between that gap — HTTP, HTML, CSS, Ruby, JavaScript, the DOM, web security, JIT performance… you get the idea.

Since we are so used to building up from the bottom in baby steps, often spending a whole semester on each layer before moving up to the next, the sudden jump from the 20th level all the way up to the 50th level is just too much to handle for a lot of CS graduates. Many of them fell off the “CS cliff” here and struggled to connect the dots between their education and the industry work they do.

From here, you can draw two possible conclusions.

Option A: high-level abstractions like Rails and Ember are the problem. As you can see, they are hiding so much stuff from you, you can’t possibly understand everything — there are too much magic. Therefore, the solution is to stop pretending we can get away with that.

Instead, you should build your own stack from the bottom-up, using only micro-libraries as components, perhaps even rolling a few of your own. By going through that exercise yourself, it guarantees your ability to understand the abstractions you are building on, just like the good old days in school.

Option B: since everyone builds from the 50th level without understanding most of the things underneath — and they seem to be doing just fine, we must conclude that understanding the low-level details is not actually that important for our job.

Therefore, starting from the bottom-up is useless and a waste of everyone’s time. Instead, we should focus our effort on teaching and learning 50th level abstractions like Rails — which is the approach taken by code schools and bootcamps.

Both sides have made some good observations here, but I think neither of them got the conclusion quite right.

Indeed, building your own stack is a great exercise for understanding the layers you are building on. However, as you dig in, you will realize that these abstractions are handling a lot for you, including things that requires specialized expertise — such as performance and security. On top of all that, there are also meta-problems like documentation, composition and maintainability. Most importantly, there is always going to be another layer underneath your current set of tools, so where does this end?

On the other hand, abstractions like Rails are definitely not perfect. As Joel rightfully pointed out to us, all abstractions do eventually leak. To understand why your queries are so slow, you will probably need a basic idea of how databases and operating systems work. A good understanding of data structures and algorithms is also pretty helpful for intuiting the problems. If you treat all of these as opaque black boxes, you would end up building a tower of awkward contraptions that eventually collapses on you one day.

Where does that leave us? In my opinion, what you actually need in order to survive in our industry is the ability to pop up and down the stack of abstractions as needed.

Regardless of your education background, it is simply not realistic to expect yourself to know everything about the abstractions you are sitting on, and that’s perfectly natural. On-demand learning is totally fine — but you will need a good framework for doing that, as well as not be intimidated by the idea of digging in.