DaedTech

Stories about Software

By

Methods Are Little Stories – Abstractions Are Important 6

If Then, If Then, If Then

Yesterday’s post where I included Grady Booch’s comment that clean code “reads like well written prose” made me think of something I’ve been contemplating. The other day I was looking at some code and I saw the following (obfuscated):

I automatically started refactoring this to the following:

and then:

To me, this keeps in line with Grady’s statement and is easy to reason about at every level. “If I need an umbrella, get it” and “I need an umbrella if it’s raining, I need to leave, and I’m parked in the street” are pieces of code so simple that one need not be a programmer to understand the logic. I think it’s hard to argue that this is less conversational than “If it’s raining then if I need to leave then if I am parked in the street then grab an umbrella.”

But does this matter? Am I just being fussy and shuffling around the code to no real benefit? Are there advantages to the “ifception” approach (thanks to Dan Martin for this term)? Why would someone prefer this style? These were the things that I found myself contemplating.

The Case For Ifception?

In order to understand possible advantages or reasons for this preference, I sought to figure out the motivation. My first thought was that someone would write code this way if they missed the week in discrete math/logic where DeMorgan’s laws and the rules of inference in Boolean Algebra were covered. However, I don’t like to assume incompetence or ignorance when the only evidence present is evidence only of a different preference than mine, so let’s dismiss that as a motivation.

The second thing that occurred to me was a lack of awareness or mistrust of the compiler short-circuiting and operations. To put it another way, they believe that all three conditions will be checked even if the first one fails, so the ifception is more efficient. But, again, this requires an assumption of ignorance, so let’s assume that the author understands conditional short-circuiting.

After that a slightly more valid motivation dawned on me (and one that doesn’t assume ignorance/incompetence) – the author loves debugger! That is, perhaps the code author likes it this way because he or she prefers to be able to step through the method and see the short circuiting or success in action.

As I poked around a little more, I found code in the same class of this form as well:

Two data points now seem to point to my conclusion. I believe the motivation here is Debugger Driven Development (DDD) — a term that I’ll use to describe the approach where you write production code specifically designed to be stepped through in the debugger. This is a rather pessimistic approach since it seems to say “when you’re dealing with my code you’re going to be in the debugger… a lot… seriously, I have no idea how or even if my code works.”

I will also allow for the possibility that someone might view these approaches as inherently more readable, but I can only imagine that’s the case as a result of familiarity. In other words, this style is only more readable if it’s what you expect to see — I doubt anyone not versed in programming would gravitate toward nested conditionals and/or return statements as approachable.

If anyone can think of an additional benefit that I’m missing, please let me know. Or, in other words:

Does It Really Matter?

So having tentatively identified a motivation for ifceptions (DDD), is this style preferable? Harmless? To be avoided? I actually wrestled with this for a while before forming my opinion. The style is very different from what I prefer and am used to, but I try very hard not to conflate “I’m not used to that” with “That’s bad”. Doing so is the height of arrogance and will greatly hinder one’s ability to learn.

That said, the conclusion I came to was that this should be avoided if possible. And the reason it should be avoided boils down to method level abstractions. A method should tell a story. The method “GrabUmbrellaIfNecessary” tells a story — it tells you that it’s going to figure out whether an umbrella is needed and grab it if so. As a client of that method, you’re going to take it at face value that it does what it advertises, but if you do decide to drill into the method, you’re expecting to see a concise implementation of what’s advertised.

In the factored example, that’s exactly what you see. What better captures “GrabUmbrellaIfNecessary” than a single if condition for “DoINeedAnUmbrella” followed by a “GrabUmbrella” for a true evaluation? But what about the ifception example? Well, I see that there’s a condition to see whether it’s raining or not and then a scoped block of code with another conditional. Oh, okay, if it’s raining, we’ll get in there and then we’ll see if I need to leave in which case we’ll get… another scoped block of code. Okay, okay, so now, we need to know where I’m parked and, what were we doing again? Oh, right, we’re seeing whether we need to get into another scoped block of code. Ah, okay, if we’re parked in the street, here’s the meat of the method – grab the umbrella!

Notice that in the ifception reading, you see words like “scope” and “block”. I’m having to think of scoping rules, brackets, nested conditionals, control flow and other language constructs. Each of these things has exactly nothing to do with whether I should bring my umbrella or not and yet I’m thinking of them. If you look at the flattened early return method, a similar thing is happening:

If it’s not raining, then return. Okay, assuming we’re still in the method, if it’s not true that I need to leave, then return. Okay, now if I’m still in the method, if I’m not parked in the street then return. Okay, if I’m still in the method, then get the umbrella.

“Return”? “In the method”? What does that have to do with whether I need an umbrella and getting that umbrella? And why do I care about “not is raining” if I’m trying to figure out whether to use an umbrella? If it’s not not raining, do I need an umbrella? I think… ugh.

An easy argument to make at this point is “Erik, you’re a programmer and you should understand things like scope and early returns — don’t be lazy.” While this is true, it’s also true that I’m capable of squinting and making out tiny, bright yellow font against a white background. In neither case is that enjoyable or a good use of my time and effort when it’s possible simply to have more clarity and ease of reading.

Programmers are paid to solve problems by handling and abstracting away complexity. This applies to end-users and fellow programmers as well. It’s easy to lose sight of this and believe that knowledge of particular languages, frameworks, etc is the end goal, but that knowledge is simply a tool used in pursuit of the actual end goal: problem solving. If methods are written in such a way as to make them read like “well written prose”, I don’t have to focus on language syntax and details. I don’t have to be acutely aware that I’m in the middle of a method, figuring out scope and/or when to return. Instead, I can focus on the business logic of my problem domain and meeting user requirements.

Method writing techniques that make language syntax an afterthought are good abstractions. They hide the bare-metal, nitty-gritty details of writing C# (or any language/framework) as much as possible, exposing only enough to facilitate understanding of the problems being solved. And while it’s never going to be possible to avoid all scoping, returning and other such method housekeeping, you can certainly arrange your methods in such a way as to minimize and hide it, rather than to distract readers by calling attention to it.

By

Abstractions Are Important 5 – Type Consistency

For the fifth post in this series, I’m going to start with a mini rant.

A Digression (Rant) About Enum

Over the last couple of years, enumerations/enums have been dying a slow death in the world of code that I write. I wasn’t every really an avid user of them, but they’ve definitely been declining even for me to the point of virtual-non existence. I’m not sure exactly what it is about them or about me that’s spurring this, but I’m not sorry about it at all. I don’t miss them.

I think perhaps the motivation has been an increasing desire to use polymorphism at all levels, even when the object I’m making doesn’t seem “classworthy”. That is, why would I create a “CardSuit” enum when I can just create a first class type for Suit? I think perhaps another factor in this approach of mine is that enums tend to go hand-in-hand with switches and I consider this pairing to be an anti-pattern. Even with an enum like “Suit” that is really just representing mutual exclusion, this is inevitable somewhere:

And, why do that when I could just have something like this:

This way, I can later plop suit specific designations into inheritors to my heart’s content without hunting for switch statements littered throughout the code. (Also, for language agnostic readers, C# enums are a different beast than their Java counterparts — in C#, they’re really just glorified collections of constants).

Apparently, I’m not alone in this sentiment. Just to stir things up, I googled “C# enums are evil” and came up with this interesting link from stackoverflow guru Jon Skeet:

Since working on a Java project last year, I’ve been increasingly fed up with C#’s enums. They’re really not very object oriented: they’re not type-safe (you can cast from one enum to another via a cast to their common underlying type), they don’t allow any behaviour to be specified, etc. They’re just named constant integral values. Until I played with Java 1.5’s enum support, that wouldn’t have struck me as being a problem, but (at least in some cases) enums can give you so much more.

It’s good to see that the man who defines “10” when a recruiter or someone asks you to rate yourself 1-10 on strength in a language feels the same way as me. And, I think he really nails it with the non object-oriented comment. Enums make me feel like I’m writing kernel code in C or something when I use them.

If You’re Going to Use Them…

All that said, it’s not as if they’re going to be excised from the language tomorrow — we might as well make sure they’re used in a way that makes sense if and when they are used. In a code base that I’m in from time to time (and I’m obfuscating the problem domain a bit, but leaving the intent and meaning intact), the concept of “side” exists in the sense that anything in the domain must be left or right. Think of it as though we’re shopping for a pair of shoes. Here is how “side” is represented:

At first blush, this makes sense. We might have no shoes on, the left shoe, the right shoe, or both shoes. But, ask yourself this: “what is ‘side’ and what is it made of?” Left and right are somewhat mundane, but what about none and both? Is “none” a side? Is “both”? Do I put my “none” shoe on before the left shoe and right shoe, at which time I put on the “both” shoe? Can you infer the usage of this thing from looking at it? No, you really can’t…

And the reason you can’t infer the usage is that the enum consists of two sides and two expressions of quantity of sides. This enum is a chameleon — depending on where you’re standing and what part of it you’re looking at, it can be two different things. And, I submit that this is bad — if you’re going to use enums at all, use them to represent simple, mutually exclusive concepts (like the aforementioned “Suit” in the problem domain of playing cards).

What’s the Harm?

Let’s take a look at an example (stripped down for brevity) client of this enumeration:

What we’ve got here is kind of cute and clever. If I want the client to put a shoe on, I pass in the side. But, if I want to tell it to put both on, then I can just pass in Side.Both, and the method knows how to handle this. Sweet! The only immediate drawback is the fact that we have to handle Side.None. Clearly that should be a no-op that clients should avoid, but it’s just as valid as any of the other things from the compiler’s perspective, so we manually no-op.

But, is this cute bit of recursion actually as sweet as it seems? What if we write another method like this called “TakeShoesOff”? What if we write a class called ShoeSalesman that retrieves pairs of shoes? We almost always want him retrieving both shoes for clients, so we’re probably going to want to perpetuate this pattern into his methods as well, probably by copy and paste to save time. How about a ShoeStoreCashier ringing up pairs of shoes? We can take care of that with our good buddy copy and paste too. There’s no method about shoes that we can’t handle that way!

But, wait a second? Isn’t this starting to be kind of a code smell? If this implementation is so sweet, why does it seem all wet in the face of DRY? If you have actually done this in hundreds or thousands of places, you should be getting a sinking feeling in your stomach right about now. That feeling is the feeling that your code is suddenly and subtly incredibly brittle. This cute little encoding over the enum is actually an algorithm that is everywhere in your code.

Let’s say that I want to get in on some cuteness myself. What I’d like to do is write a method and say, “I don’t care which side you pass me, so long as a side exists — I’ll just perform an operation on the first side that I have available, if any.”

I’ve now extended the cuteness to be more “flexible”, and I’m pretty pleased with myself, so I go ahead and deliver this code. No unit tests break, no problems emerge that I can immediately see, so life is good. But then, weird problems start to crop up after a while. People file bug reports saying that they add shoes and see nothing on the screen or that they remove shoes but nothing is deleted from the database. It’s kind of a mystery at first, but I’m forced to get to the bottom of this as the trickle of bug reports starts becoming an avalanche.

What’s going on?!? Well, what’s going on is that all of the Cute 1.0 code can’t handle the new enum I’ve defined for Cute 2.0. So, what does it do? Well, sometimes it skips it altogether and no ops. Sometimes it adds a new key to the dictionary — a key for which it never checks. Sometimes it throws some kind of exception where it falls into a “default” state in a switch statement someone has defined. Sometimes it throws up an error message box informing the user, “This should never happen — email Erik!” for the same reason, which is doubly bad since I’m probably going to be featured on the Daily WTF.

Wow, bummer. But, no big deal. I’ll just roll up my sleeves and upgrade Cute 1.0 to Cute 2.0 everywhere. How many can there be, right? Uh oh. Hope I’m not doing anything this weekend. For everything copy and paste programming lacks in being a good idea, it makes up for in its ability to spread through a code base like kudzu. It’s too late to revert Cute 2.0 since I now have clients that depend on it, so I’d better roll up my sleeves and get pastin’ because it’s going to be a long Saturday with some Mountain Dew and 7,400 methods that I need to change.

The Real Problem

It should be fairly obvious that any “pattern” requiring you to add 5 or 10 lines to the beginning of a bunch of methods is actually an anti-pattern, particularly if those lines are similar or identical. And some might stop here and say that getting cute in the first place here was the problem, but I contend that this is just a symptom. To tie this back in with the series, the real problem is one of abstractions.

As I asked earlier, what is “side”? Really, can you tell me? I mean if I have a “Customer” object in my domain, you’d probably say something like “that models a customer of the enterprise for which this application was written” or else maybe you’d just smack me upside the head for asking such an obtuse question. But for “Side” in Cute 1.0? Without using the word “side” in your definition? You might say “well, it represents where we can put a shoe” or “it represents the directions left and right”, and you’d be correct for two of the enums values. You might say “well, it represents a number of places that you can put a shoe” or “its a flag that you need to use to tell your methods how to behave” and you’d be right for the other two values. Hmmm….

I know! It’s a doo-dad you can use to index a hash! That’s probably the most accurate way to describe it because that is unfortunately true of all 4 values. Unfortunate because for two of the values, it makes no absolutely sense to do that — but at least it’s true, so we’re getting somewhere. At the end of the day, though, I think all you can really say of Side is “it’s an enum and it’s up to clients to figure out what to do with it.” And that, my friends, makes it a bad abstraction.

Here are some other enums that would be poor abstractions

If you’re going to use enums, they ought to be values that are mutually exclusive, constitute a complete set, and form a clear abstraction. The first one is obviously nonsense, but the second two are enums where the person writing/extending them is about to get cute. They’re about to take an enum that has a set of mutually exclusive values and reappropriate it to use as a flag to tell their client code how to behave. Think of the code that’s about to be written — things like “if the suit is black, then do it for spades and clubs” and “if the person passed in has favorite dance property set to doesn’t like to dance…”

If you find yourself doing these kinds of cute things ask yourself what you’re really trying to accomplish. In the suit case, wouldn’t it make more sense to parameterize a method so that you could pass in multiple suits for the client code to operate on, instead of hard-coding the iteration? In the dance case, maybe it’s time for dance to be a first class object so that a “FavoriteDance” property can be set to null or a null object.

In C#, enums are already pretty much screaming “hey, there’s something called polymorphism — use that instead of me!” Once you start adding cute, one-off flag encodings to them, you’re really dropping all pretense of enumeration being a suitable abstraction and going for the gusto with fake, procedural polymorphism forced on your clients. Please, for your sake and mine if we later work together, don’t do that. The world doesn’t have a “class reserve” ala oil that may be exhausted someday — make a class. You won’t regret it because you won’t be spending your weekends upgrading The Cute.

By

Abstractions are Important 4 – Personal Interactions

An Announcement

I’ve enlisted the help of freelance editor Amanda Muledy to copy edit this blog. The content will not be altered in terms of substance; rather, she’ll be correcting grammar and spelling mistakes and the like. She’ll even know whether or not the semi-colon I just used was some kind of faux pas.

She has started on my oldest posts and will be working her way toward the newest ones and we’ll eventually probably work to a state where I send them to her prior to publication. So, if you see mild edits to content here and there, that’s the reason for it. If you’re interested in her services or more information along these lines, she has contact info on her blog.

And now back to your regularly schedule post…

Beyond the Code

In the second post in this series, I described in detail an abstraction that had nothing to do with programming: the pizza parlor with delivery service. Today I’d like to revisit abstractions in the real world, but not focus on static institutions. Instead, I’d like to talk about the ad-hoc abstractions that we form in one another’s lives as we work collaboratively.

Huh?

When we work with other people, what we generally do is offer abstractions to one another for the purpose of getting things done more efficiently. Have you ever said something like “you prep the chicken, and I’ll start the grill”? This is an ad-hoc, process-based abstraction. What does prepping the chicken entail? Maybe a dry drub, chopping it into chunks, etc. But, the interesting thing is that the person starting the grill doesn’t care at all, as long as it’s taken care of. Likewise with the starting of the grill. Is it a charcoal grill? What kind of charcoal to use? None of that matters to the person prepping the chicken.

This is an example of a good process abstraction, and these types of abstractions are the basis for the concept of effective teamwork. They allow people to work in parallel and to be more efficient than they would be working on their own, assuming that the tasks are not temporally dependent. Sure, one person could prep the chicken and also start the grill, but two people doing it simultaneously will go faster. It would be a different story if the two tasks were interdependent or mutually exclusive. “Hey, you clean the bottom of the grill while I start it” wouldn’t work very well.

So efficient teamwork requires tasks that are relatively orthogonal and participants providing abstractions to one another. (It is certainly possible to have abstraction in sequential tasks, but that’s not nearly as interesting). Without orthogonality, the team members would step on one another’s toes while completing their tasks, and without abstractions there wouldn’t be any productivity boost.

For Example

Last week, I wanted to order a server and confirm that our MSDN licensing covered the server software that I wanted to install. Management paired me with someone whose job it was to worry about these things so that I could focus on my job of being a programmer. Things started to go a bit off the rail at this point, though. I got an email about computer wholesalers and the names of salespeople at these wholesalers, distrust of Microsoft and its licensing and any number of other things that weren’t of interest to me — things that should have been abstracted away from me. My goal was simple — get a computer and install server software on that computer.

I responded to this email by saying that I appreciated all of the information, but I didn’t really want to worry about all that stuff. I just wanted the computer. I did ask if it made sense for me to call one of these wholesaler employees directly, which I would have been happy to do, since this would have provided me with an actual abstraction. The response to this email was another torrent of information that was tangentially related at best (overseas latency issues, server hard drive configuration snags, etc) , but no servers ordered and no contact information provided. This went on for several emails more emails in the same fashion (the situation is now on hold for other reasons, so there is no resolution, either).

What I had was a consistent failure to provide an abstraction, and thus I had no actual help with anything. If I have to stop working at my assignment (being a programmer) to deal with an IT assignment (ordering a server) than the person whose responsibility is the latter is providing no abstraction and thus, functionally useless to me. In this example, the time I spend reading and responding to emails could better have been spent calling Microsoft and/or the wholesaler directly. Either way, I couldn’t focus on my job, so at least something productive could have come out of it.

Why Does This Happen?

As you read through this, you’re probably both asking yourself why anyone would do this and also thinking of people who have provided a bad abstraction to you in the past. That is to say, it’s often incomprehensible to a problem solver not really mired in office politics, but it’s also a common behavior. I posit that there are three reasons someone will provide a bad ad-hoc personal abstraction:

  1. Passive-aggressive desire to sabotage an initiative
  2. Earnest over-exuberance or over-inquisitiveness
  3. Simple incompetence

The last of these is the simplest and least interesting. If someone is simply incompetent at a task, they will serve poorly as an abstraction. If you were to ask me to ask someone for the time of day in Mandarin, I would respond with either “how do you ask someone the time of day in Mandarin?” or “will you do it for me?”. I am simply ill-suited for this task since I don’t speak that language.

The second of these is what results from people who are nervous about responsibility or perhaps overly excited about it, but are willing to put in a good faith effort. Perhaps you ask me to cook you an omelette, and I respond by saying, “how much pepper would you like in it; do you care if the eggs are whisked for 20 seconds or do you want 30; these are those brown eggs – is that alright; do you want me to use PAM or oil; etc?” I mean well, but I’m defeating the purpose of what you asked of me. By bogging you down in these details, I’m forcing you to spend more time helping me than was your intent and I might well be annoying you. This sort of behavior might result from me really wanting you to enjoy the omelette or me being afraid that you’ll yell at me if I mess something up, but there’s no ill intent.

The first item in the list is probably the most interesting from a realpolitik perspective in a collaborative setting, though it isn’t because it’s a sophisticated technique. Children learn to do this at an early age and employ it without any subtlety. Ask a child to clean his room and, after some initial whining and the gears start turning:

Child: Where does this go?
Parent: In the drawer over there.
Child: But I can’t reach!
Parent: Get a stool.
Child: Where are the stools?
Parent: There’s one on the other side of your bed.
Child: It’s heavy!

The goal here is to feign simple incompetence to get out of the task, which is a rather natural thing for a child to do since parent-child relationship often doubles as teacher-student. The child banks on the parent becoming so exasperated that he or she finally exclaims “Oh, for the love of… I’ll just do it!” This gets interesting in adult interaction dynamics since it necessarily involves a sacrifice of perceived status. Whoever does this must be willing to accept a role of subordinate status and often to move down the path toward learned helplessness. If you work with me, and I respond to all of your requests by saying that I don’t know how to do things, it’s only a matter of time before you and others begin to question my competence and value in my role.

As a result, the best way to get out of work (or sabotage an initiative in general if the motivation is something other than laziness) is not to offer oneself sacrificially as a buffoon, but instead to exaggerate the difficulty of the task with which one is presented:

Oh, sure, I’ll make you an omelette, but there’s really a lot to think about here. You have to consider what kind of pan you want, what size and color of egg, the quanity of pepper involved… I mean, there are a lot of variables here. Don’t worry, though — I’ll make some inquiries as to what the best kind of ingredients are and get back to you tomorrow.

Notice that it’s very similar to the over-exuberant explanation, but with a bit more of an air of authority. It also subtly implies that the earliest possible completion of the task renders it not worth doing — who wants to wait 24 hours for their breakfast? This shift from excited questioning to authoritative is often a giveaway, but sometimes these motivations may even come off as identical. They’ll certainly be similar and it would be a shame to accuse someone of some sort of malfeasance when the explanation is benign. It’s genuinely hard to distinguish items (1) and (2) in someone else in a vacuum without track records and understanding of possible ulterior motives.

Avoiding All Of It

And, most diplomatic people will avoid this sort of accusation because it is rude and assuming. They’ll act on the surface as though you’re simply excitable. But, here’s the catch — they’ll assume that you’re passive-aggressive if it happens too often and if it’s a regular source of impediment. So really, this type of behavior, even when benign has at best a short term benefit with a long term cost. People will probably assume that you deliberately make yourself an obstacle, but even if you’re fortunate enough to avoid that, they will assume that you are incompetent and/or annoying.

Do you really want any of those labels? I don’t, myself. So, I’d stress that you make sure to be a good abstraction for others when asked. Make sure that your work makes things simpler for other people and removes obstacles from their path, rather than adding them. And, if you really are ill-suited for a task, explain why and try to think of something you can offer instead to be helpful. It really does parallel code in a lot of ways — you should hide complexity from others and present them with simple but meaningful choices.

Good abstractions in code, good abstractions in life — a good way to live.

By

Abstractions are Important 3 – “What?” Before “How?”

So, I’m back from my two weeks overseas, refreshed, enriched, and generally wiser, I suppose. We traveled to Spain and Portugal, visiting a ton of historic sites, eating good food and having fun. For my first post back, I’d like to make a third post in my series on abstractions.

Methods as Recipes

I was looking at some code yesterday. It was some long method, probably 60 or 70 lines long, and I sighed as scrolled disinterestedly through it. At the moment, I couldn’t muster the energy to try to figure out what the author thought it did or probably wanted it to do, so I started ruminating about what leads to methods like this. And, I think I understand it. It’s the idea of a method as a recipe.

By way of homage to Spain, let’s write a “CookPaella()” method. When writing methods, do you ever start by doing the following?

(recipe compliments of All Recipes).

This is a sane approach. Much like making an outline for an essay in English class, you list out the basic procedure that you want to follow, and you fill in the details:

This is great because rather than starting without any kind of gameplan, we’ve stubbed everything out that needs to happen, and now we’re in the process of filling in the template. Whether you’re cooking or assembling a piece of furniture or anything else, there is a tendency to read through (or skip) to the end so that the actual following of the instructions reveals no mysteries. We take the same approach here.

Once this is complete, you’re going to have a large method, so some refactoring is probably in order. At the very least, our numbered bullets provide some logical methods to create:

There, look at that. We’re going to have this reduced to a nice 13 line method and, we could probably even group the calls further from there, resulting in a CookPaella() method that had three instructions: Prep(), CookRice(), CookMeat(). Those methods would consist of three or four lines themselves, and things would spread on out from there like a tree. This is a series of well factored methods that are probably clean and reasonable (discounting the fact that we’re instantiating what we need with Bowl, rather than having variables passed in — that’s for example purposes and not a weakness of the approach).

So where do these long methods come from? Well, I would argue that they come from people thinking in terms of recipe, but not creating or factoring to the outline. That is, they sit down to write the method and simply start banging out code line by line until they’re done. They think in terms of a recipe — a procedure — and methods are simply containers for procedures. So, when they start out, they don’t know what all a method is going to do; rather, the method twists and winds and meanders its way toward some eventual end in ad-hoc fashion.

In a less contrived case than this one, a method will probably start out as just a jump point for a series of instructions. The instructions are coded sequentially until there are no more instructions and then the method is at an end. The “how” is defined, and then the author looks at the “how” and decides what to name the method. He describes “how” and then, based on “how”, decides “what”. Ah, I see that I’ve assembled a series of instructions that seems to cook a paella, so “CookPaella” is probably a good thing to call this.

Methods as Abstractions

So, is there another way to do this? Absolutely. You can flip the script and decide “what” without worrying about “how”. With this approach, we completely discard the procedural/sequential concept and focus instead on creating meaningful abstractions. Procedural/sequential programming is good for, say, batch scripts, but object oriented programmers need to think in abstractions. I don’t want a specific, blow-by-blow recipe for cooking paella to become the ‘architecture’ of my code. I want to write code that a cook can use to get things done. That’s an important distinction.

Let’s think of our paella cooking a little differently. Let’s just think of it as cooking. In this fashion, we can define implements like pots and pans, ingredients like paprika and meat, and actions, like sear or boil. From there, we can start thinking about an object model. What is a “bowl” and what should it do? A bowl does things other than mix ingredients for paella — it has properties and, depending on the object model, may have some actions. Let’s decide what those properties and actions are without worrying about how they work.

For example, let’s define a bowl, along with concepts called “ingredient” and “quantity”:

Notice that we have a Bowl interface, rather than any kind of implementation. How do these methods work? Who cares. We don’t need to know that to make our paella. From here, we might define a paella pan interface as well, and perhaps various inheritors of ingredient and quantity, depending on their implementations. The point is, we’ll reason about each individual object and how it should behave. After we do enough of this, we can start to create larger constructs, such as some MeatCooker class that takes an arbitrary number of meats and a pan and cooks them. A few classes like this, and before you know it, your CookPaella() method will write itself.

Notice that the “start at the beginning and sequentially do everything” scripting style approach is gone, but so too is the structured, outline approach. With this abstraction-based approach, we’re defining objects with properties and behaviors that make sense in our domain model. This is what allows easy assembly. But, the important thing to understand is we’re defining abstractions rather than procedures. These are going to be easier to reason about, and they’re also going to be more flexible. We can use our meat cooker and pan and bowl to cook lasagnas as well as paellas.

So, the overriding message here is to think sequentially and abstractly when considering how to model a problem in an object oriented language. Don’t think about “how” until the very end. Instead, think about “what”. What objects should you have? What properties should they have? What actions? What interactions do you foresee for them. As you answer these questions, the specifics will become much easier. The longer you defer those specifics, the more flexible the design. You’re using an object oriented language, so leverage that language. Don’t code like you’re batch scripting. Don’t model your application by writing recipes even when you’re modeling, well, cooking a recipe.

By

Abstractions are Important Part 2 – Good Abstractions

Last time, I talked about the importance of abstractions in relation to a piece of service code that I had seen. This time, I’d like to expand on that concept a bit. I showed some examples of good versus bad abstractions and talked about why they were good or bad, and this time, I’d like to explore the idea of defining in general good versus bad abstractions.

What are Abstractions, Anyway?

If we’re going to talk about abstractions in general rather than simply by example, it probably makes sense to define the term a bit more formally. Wikipedia has a fairly good definition for it:

In computer science, abstraction is the process by which data and programs are defined with a representation similar in form to its meaning (semantics), while hiding away the implementation details.

In other words, an abstraction is a way to say to your clients, “give me the gist of what you want to do and let me worry about the details of how.” If you’re a client of the abstraction, you trust the provider to handle the details correctly.

An Example Abstraction

Abstraction, as a concept, is not limited to the problem domain of programming. Let’s consider an abstraction that has nothing to do with programming, on its face: the pizza shop. The pizza shop abstracts the process of making a pizza from its customers, allowing customers to specify basic properties of their desired pizza while the shop handles more granular ones.

If you call a pizza shop, you tell the shop that you want a large pizza with pepperoni on it. What you typically don’t do is specify how much pepperoni (aside from perhaps in vague terms like “extra” or “light”) nor do you specify the exact dimensions of “large”. These are some of the details that are simplified for you. Others are hidden entirely such as how much basil goes in the marinara sauce or the temperature at which the ovens are set for pizza cooking.

The procedure in general is a simple one. You specify a few rudimentary details about the pizza and whether you want delivery or not, and the shop responds with a time estimate and then, later, a pizza. However, this is an excellent abstraction (as one might surmise by the popularity and ubiquity of its implementation).

So, what makes an abstraction ‘good’? How do we make sure that the ones we’re creating are good?

Exposes Details That Make It Useful

Any abstraction has to expose some level of detail to clients or it would be useless. When calling the pizza place, you are aware, obviously, that you want a pizza. This is unavoidable. On top of that, the pizza place also allows you to specify size and a lot of the ingredients of the pizza. This ensures that you will get as much or as little food as you want and that dietary restrictions and considerations are met. In addition, the pizza place (usually) allows you to specify whether you want to eat there, carry the pizza home or have it delivered. This is another angle for the abstraction as it allows the pizza shop to accommodate your location preference for where you want to eat.

Making the abstraction useful is vital or else nobody would actually use it. In the world of pizza parlors, if one opened up that served only small, mushroom pizzas for carry out, it probably wouldn’t last very long. Even assuming there were no such thing as competition, people would probably opt to make their own pizzas most of the time rather than agree to such specific restrictions. No one would make much use of this abstraction.

Hides details that it needs to control

The flip side of exposing enough detail to make it useful is hiding details that need to be controlled. Imagine the opposite of our “you only get small, mushroom pizzas for carry out” situation, where a pizza parlor allowed specification of any detail, however minute. Customers could call and say that they wanted a pizza cooked in natural cave at 193 degrees Celcius, infused with rare spices from a remote island, and delivered at 4 AM.

The impact on a shop of catering (or attempting to cater) to this level of detail would be disastrous. Imagine the logistics of having to dispatch employees to whatever location customers demanded. Or, imagine the expense incurred in obtaining certain ingredients. And, imagine the absurdity of a pizza place staying open 24/7/365. These things would be the result of too much permissiveness with the abstraction. This abstraction hides no details from its users and, by relinquishing all control over operational details, it allows its users to put it into unprofitable, preposterous modes of operation.

Is Understandable and Intuitive to Clients

If usefulness and guarding against damaging levels of control by clients are table stakes for having an abstraction that can hope to survive, understand-ability and intuitiveness are necessary to thrive. One of the reasons the pizza place is so successful is that it’s a relatively universal, common-sense and simple abstraction. Whatever slight variations may exist, you will generally have no issues ordering pizza from a place even if you’ve never ordered from there before.

“Ask for food”, “add a few ingredients”, “specify where you want the food”, and “pay for food” are all very simple concepts. This simplicity is a big part of the reason that when you’ve checked into a hotel and are tired, you fall back to ordering a pizza instead of ordering, say, tapas or hibachi or going out and buying a bunch of groceries. “How do I get there?”, “Where can I cook this?”, “Do you have a way for me to take this home?”, etc are questions you don’t need to ask. This simplicity and universality makes the abstraction a wildly successful one.

Prevents (or limits) client mistakes

It’s crucial to limit mistakes that clients can force you to make, and it’s almost as crucial to prevent clients from making their own mistakes. The former might blow up your abstraction before it gets going, where the latter, like intuitiveness, is important for gaining adoption. One of the attractions of ordering a pizza is that it’s unlikely to end in disaster. Oh, it might not be cooked to perfection or it might generally be mediocre, but it won’t set your oven on fire or come bubble over into a gigantic mess during cooking.

The reason for this is that the pizza restaurant abstraction removes a lot of the potential problems of cooking a pizza by hiding the process from the customer and leaving it safely in the hands of a specialist. Nothing about specifying the size or the toppings of the pizza gives me the ability to make a decision that somehow causes the pizza to be overcooked or the meat toppings to be dangerously undercooked.

Back to the Code (and are you even making abstractions)?

So, what does all of this mean for coding? I would argue that since a pizza place is really just a process abstraction, we can translate these lessons directly to code. Exposing things to make the abstraction useful while hiding things that would cripple it is fairly straightforward to do, provided that you think in abstractions. I might have a database access abstraction that allows users to specify connection credentials but internally prevents things like multiple connections, dropped connections, etc. In this fashion, I can allow users to connect with different levels of privilege, but I can prevent them from inadvertently getting my class into some invalid state.

Likewise, I can create intuitive operations such as “create new record” or “delete record” that hide ugly details like SQL statements and transactions. This presents an intuitive and inviting interface that’s pleasant to use. And, in addition to providing a minimum guarantee of my abstraction’s own functionality, I can at least assist in saving them from their own lack of familiarity with the abstraction. Fail early goes a long way toward this — I can throw descriptive exceptions if they try to delete nonexistent records, rather than leaving them to decipher what SQL Error 9024B means. This is the equivalent of the pizza place operator saying, “I’m sorry sir, but ordering a negative six inch pizza makes no sense — we don’t offer that.” In real life, this “fail early” approach is much better than a delivery guy showing up empty handed and leaving it to you to figure out why no pizza arrived.

To pull back a bit, I think it’s important to consider the pizza shop or a similar metaphor when writing methods/classes/modules. Don’t simply write code that is technically functional, strewing it willy-nilly about various classes and locations. Don’t write code by coincidence while the debugger is running, setting flags and whatnot to get things working for your exact scenario. Don’t go with the philosophy “ship it if it works.”

Instead, when writing code, imagine that you’re creating a metaphorical pizza place. Who are your ‘customers’? Answer that question, and it becomes easy to answer “what do they want from me?” Answer this question well before “how do I get them what they want?” The “what” is your public API — the useful thing you’re going to provide. The “how” is the detail(s) that you hide from them, for their own good and the operational good of your code. The intuitiveness of your public API is going to be determined by answering questions like “am I logically book-ending operations — if I allow them to open something, do I allow them to close it?” or “if I read this method name and its parameters aloud, is it clear what this does” or “do all parts of my public API do what they say they’re going to do?” If you’re answering yes to these questions, your pizza shop is looking good. If not — if you’re sending sandwiches or sushi when the customers order a medium pepperoni pizza — your abstraction (code) is probably doomed.

And, I hate to bring unit testing into everything, but there’s really no avoiding this — if you write unit tests and especially if you practice TDD, you’re a lot more likely to have better abstractions. Why? Well, you’re your own first customer. This is like going “undercover boss” and ordering pizza from your own shop to see how the experience goes. When you write tests, you’re using your public API, and if you’re muttering things like “what are all these parameters” or “why do I have to pass that in?!?” you’re getting early feedback on your own abstraction. I’ve rarely seen an abstraction that inspired me to react with a “wat?!?” and gone on to find a nice set of unit tests covering it. Tests seem to function as insurance against boneheaded abstractions.

A Checklist For Abstractions

I’ll close out with a set of suggestions — a checklist for evaluating whether you’re creating good, usable abstractions or not.

  1. Does your API operate at a consistent level of abstraction — do you avoid having some methods that require users to pass you SQL statements and others that encapsulate this detail, for example?
  2. Do your methods generally have two or fewer parameters (more parameters making it increasingly hard on users to intuitively understand the method)?
  3. Do your methods have succinct but communicative names like “CreateEntry(Entry entryToCreate)” as opposed to needlessly verbose (“CreateRecordThatIsGoingToGoInTheDatabase(Entry entry)”) names that are hard to type and remember or weirdly succinct names (“CE(Entry e)”)?
  4. Do your methods lie? Does “CreateEntry(Entry entryToCreate)” actually delete an entry, or perhaps less egregiously, create an entry sometimes, unless the entry has a certain flag set true, in which case it quietly fails?
  5. Do you avoid forcing weird details on your clients, such as asking them to store boolean flags?
  6. Do you avoid multiple return values (i.e. out/ref parameters?)
  7. Do you somehow communicate what exceptions your methods might throw?
  8. Do you limit the number of methods per class so that reading through the documentation or IDE assistance is not painful?
  9. Do you avoid forcing your clients to violate the Law of Demeter? That is, in order to get a “D”, do you force your clients to call getA().getB().getC().getD(); ?
  10. Do you practice command query separation in your public API?
  11. Do you limit or eliminate exposing public state, and especially flags?
  12. Do you limit temporal couplings that force your clients to call your methods in a specific order?
  13. Do you avoid deep inheritance hierarchies that make it unclear where the members of the public API actually come from?
  14. Do your publicly exposed classes have a single, obvious responsibility — do you avoid exposing swiss-army-knife classes with a mish-mash of different functionalities?