Consider the following conversation:
* at le Starbucks * Friend: “Hey Hudson! What are you up to today?” Me: “Not much, it’s an easy day today. Just working on a simple web app. I’ll probably grab lunch with some friends this afternoon and read a bit this evening. What about you?” Friend: “Sounds fun. A few friends and I are going to see <recently released movie> tonight; do you want to come?” Me: “Sure, I’ve been wanting to go see it.” Friend: “Great! I’ll text you the details later.”
Ignoring the fact that I dislike staring at screens outside of writing / programming, that’s a conceivable conversation. Now consider this one:
* at le Starbucks * Friend: “Hey Hudson! What are you up to today?” Me: “Quite a lot. I got out of bed at 4:23am this morning, cooked breakfast and showered, realized I needed gas, drove to the gas station, swiped my debit card and entered my pin, filled up the gas tank, and went to work. Now I’m taking a break from editing line 105 of search-form.component.ts on my current project. I’ll probably grab lunch with some friends this afternoon, then go home and read with a cup of decaf coffee made in my French press. Friend: “Sounds fun. A few friends and I are driving to the theater to purchase tickets for <recently released movie>, find seats in the theater in which it’s playing, then fixate our eyes on the screen therein until it ends; do you want to come?” Me: “Sure, I’ve been wanting to go to the theater and watch it.” Friend: “Great! I’ll send you the details via SMS later.”
Imagine if every conversation were like that (the horror…)
The first interaction easy to follow because it stays at the same level of abstraction. Work, eat lunch, read, see a movie – these are high-level, simple statements about what my friend and I are doing. The second interaction is hard to follow because it uses several different levels of abstraction. 4:23am and using SMS are very low-level, specific details about how I did something; getting lunch and mentioning a few friends are descriptive and don’t involve details.
Our code should look like the first conversation and stay at a consistent level of abstraction. Instead, it often looks like the second; it forces the reader to context switch constantly and makes understanding the code needlessly difficult.
To help you refactor the “conversation 2” kind of code, here are 3 levels of abstraction that I’ve been using recently.
1. The primitive level
Ints, strings, booleans, pointers, etc. Functions and methods that operate on primitive types, properties, or class members are very low-level. Put another way, these functions should do only the “grunt work” of the application. Working at this level can get a little dull because it’s usually easy – that’s probably a sign that you’re doing it right.
2. The problem space level
This depends on what you’re building. In general, the problem space level operates on objects in the realm of your client. For instance, if you’re building a library application, the problem space level might contain
Computers. If you’re building a course registration system, the problem space level might contain
Classrooms. This is probably the lowest level of abstraction you’ll use when talking to your client.
The problem space level should consist of classes that simply group related values and functions. Classes shouldn’t need to talk to each other very much; that’s what the component level is for.
3. The component level
Components operate on items from the problem space level. To use the library example again, a
CheckoutComponent might create a
Checkout record from a
Patron and a
Book. To use the course registration example, a
SignUpForm might allow a
Student to sign up for a
Course. In general, the component level does the interesting work that makes you money.
I realize this will be an almost childish review for some people, but thinking in these three concrete levels has helped me tremendously lately.
Are there additional levels of abstraction you tend to use? Different abstraction approaches altogether? Let me know.