On the outside looking in

Abstraction captures only those details about an object that are relevant to the current perspective.

http://en.wikipedia.org/wiki/Abstraction_(computer_science)

Imagine we are writing code which will have the concept of a car in it somehow. Our Car abstraction isn't a real car, of course. Our Car abstraction might not need tires. Our Car abstraction might not even need to exist - maybe any Automobile will do. If we treat them all the same theres no need to make the distinction. In fact, maybe we just want to have a car image that is one of many possible images that a user can select from, so the concept of car or even automobile really isn't something we ever need to actually use in our code.

Now imagine designing this system without knowing the "current perspective" or, as I'll refer to it, the context. Which abstraction is the best one? It seems impossible to know, but, in my experience, this is how many programmers design their code.

Feel the pain

We must be aware of the context in which an abstraction exists before we design it. We won't know how to capture the essential complexity without adding accidental complexity until we understand the context.

Consider a couple of all-too-familiar experiences...

  • You try to integrate backend abstractions into a frontend UI, which was specified by a product manager at the beginning of the project, but things aren't going well. Features are missing, or there are extra features you don't need, or your abstraction's interface is really hard to work with in this particular context.
  • You tried to replace some messy code with something new and clean. You haven't really studied the messy code in depth, so you don't really know all of what it does... its just messy. You take the concept that the messy code represents (like car for example) and you just build the best one of those, as if that were possible to determine without context. Its time to integrate your code and things are going badly. The interface is hard to work with. Its methods don't answer the queries your client code has. You create mess at your integration points just so your new abstraction is usable.

There are many more scenarios like these, but the result is always the same: Your abstractions come face-to-face with your requirements for the first time and they don't get along. Your abstractions aren't useful in the context in which they are used. This mistake costs both time and money.

Inside-out, meet Outside-in

The previous examples use what is called Inside-out software development. You are starting in the middle of the code and working your way out to the edges of the code, where the integration happens. The alternative here is Outside-in software development.

If you google this phrase you get a lot of random results about BDD and Cucumber or some Agile practices. I'm not here to sell you those things. Outside-in is a useful concept even without those tools and practices attached.

The Outside-in approach is very basic. Start at your integration points and work inward from it so you can understand the context in which your abstractions will exist. You don't necessarily need to write your code in order from outermost to innermost, you just need to start by acknowledging the outermost. You can do this with or without acceptance tests. The outcome should be obvious - your code integrates cleanly because you had the integration in mind from the very start.

This approach is very beneficial if you're working with other developers who will be creating code that will use your new abstraction even before you've finished implementing it. Since you started with the outermost part of your abstraction you should have at least an interface that other developers can begin to use as a stub.

Abstractions are soft amorphous things. Contexts are made of requirements. They are hard and immovable. Allowing your context to form your abstraction early on removes the pain of integration and allows your team to easily work around your "piece of the puzzle" while you're building it. This translates into saved time and opens the door for concurrent work, all of which enhances your ability to deliver your project on time.

I'd recommend reading Growing Object-Oriented Software, Guided by Tests if you're looking to find a good set of practices using Outside-in software development.

Problems vs Solutions: Putting the cart before the horse

Design patterns are common solutions to common problems. They are a problem-solution pair. If you were to ask your programmer friends to describe an Observer Pattern solution (implementation) I bet they'd nail it in a matter of seconds. Yet, if you asked them describe the problem it aims to solve I bet even if they got it right the speed and quality of their answer would be less impressive. The language would be a little fuzzy. Maybe they'd resort to giving an example of when they used it and work backwards from there.

The solution is what we do in code. We repeat it over and over. It is deeply engrained in our memories. Yet, a solution cannot exist without its problem. How have we been managing to take full advantage of these solutions without a firm understanding of the problems they aim to solve?

Similarly, have you ever introduced another programmer to unit testing? These programmers can crank out production code like a pro, yet struggle to write tests before writing the production code to be tested. They say they don't know what to test, but tests are simply statements of the problem to be solved, so how can that be? Once again it would seem we are solution experts despite a poor understanding of the problem we're solving. How do we do it?

Answer: We don't.

I think programmers often get by with a pinch of luck and a gallon of denial.

  • Our general understanding gets us in the ballpark.
  • We spend most of our time solving the same problems in slightly different ways so repeating solutions doesn't present us with an immediate burden too large to deny.
  • We see bad code as if it were the result of a single disaster rather than a series of small mistakes that we make every day.
  • We believe the complexity of our code is essential, but it is actually accidental.
  • We judge our code in terms of beauty but we have a poor understanding of how beauty should be measured.

Eventually a problem comes along that doesn't fit neatly into our limited list of solutions and we don't notice until its too late. We ignore all the signs that something is going wrong. I call these signs tension.

Tension

My simple definition of tension is "Given the existing design, how hard is it to add a new requirement?" Releasing the tension in a design leaves you with abstractions that have the flexibility you need for the system you must build.

It may seem natural to view tension as an attribute that would be exposed long term as new requirements are added over months and years, but tension is also important in micro-iterations - the ones you might have many of in an hour - as you write code for a larger project. Sure, you might be given a full list of requirements up front but as you code you are likely adding support for one requirement, or even one piece of one requirement, at a time. You aren't getting new requirements, but your code is. You can begin to sense the tension in your design using this perspective.

If you are sitting alone at your desk quietly cursing "the code is slowing me down", "the code is in my way", or "I'm sick of fighting with this code" you likely have a tense design. You are frustrated by your code. You react to your code like it is a teammate who isn't pulling their weight. These are telltale signs of tension.

Building tension

A programmer who has invested little time in understanding design patterns but has a high desire to use them is bound to produce some really stellar tension. Lets follow just such an eager idiot named Eddy Iddy.

Eddy is working on a game where the user can choose their own character and give it a bit of personality. The characters can introduce themselves and those introductions can be modified at runtime with alternate speech styles. Internally we also need to get the character ID from any given character object so we can assign it points as it completes different challenges in our game.

Eddy starts with the introduction part.

interface Character {
    public function introduce();
}

class Bob implements Character {
    public function introduce() {
        return "Hi, I'm Bob.";
    }
}

Great! How about those alternate speech styles...

class SlangDecorator implements Character {
    // ...
    public function introduce() {
        return str_replace(
            'Hi', 'Sup',
            $this->_decorated_character->introduce()
        );
    }       
}

class FeignExcitementDecorator implements Character {
    // ...
    public function introduce() {
        return strtoupper(
            $this->_decorated_character->introduce()
        );
    }       
}

Its flexible where we need it to be flexible. OK Edddy, lets get the character's ID.

interface Character {
    public function introduce();
    public function id();
}

class Bob implements Character {
    // ...
    public function introduce() {
        return "Hi, I'm Bob.";
    }

    public function id() {
        return this->_id;
    }
}

class SlangDecorator implements Character {
    // ...
    public function introduce() { /* ... */ }

    public function id() {
        return $this->_decorated_character->id();
    }
}

class FeignExcitementDecorator implements Character {
    // ...
    public function introduce() { /* ... */ }

    public function id() {
        return $this->_decorated_character->id();
    }
}

Not so hot. Do you feel the tension? These decorators don't really care about id(). They only care about introduce(). If Eddy needs to add more speech styles the problem gets compounded because id() gets repeated even more. If the Character interface needs more methods added then all the decorators also need to have those methods added. This is tension. If I want to add something simple its harder than it needs to be with this design and this extra work provides no benefit whatsoever. More boilerplate. More waste.

The Decorator Pattern is a solution Eddy has in his tool belt and he uses it whenever he can. Initially it works out pretty well. At that point I can see Eddy patting himself on the back saying "You did it, you sly fox." Next Eddy's autopilot kicks in and he just starts tacking on requirements as if all the designing was done and all the problems were solved. It was solutions time. He would have caught the tension immediately if he stopped to think about how the new requirement (the new problem) would play out in the context of the existing design.

Really, this design started to go wrong even before he added id(). He didn't look back at those decorators and say "A Slang is not a Character". These little language cues give you a subtle hint that something has gone awry.

You can think about how to fix the design, but I won't distract you with a solution. This article is about seeing problems before solutions. There is no solution without a problem, so when writing code try to understand the problem first in order to pick the right solution.

If Programming is an Art

Some of our best and brightest programmers classify programming as an art (Donald Knuth, Guido van Rossum, and Bjarne Stroustrup to name a few). Depending on who you ask, art is skill, craftsmanship, fine art like painting or sculpture, or perhaps solving problems despite walking blindly through the realm of the unknown. Donald Knuth says in Computer Programming as an Art:

Science is knowledge which we understand so well that we can teach it to a computer; and if we don't fully understand something, it is an art to deal with it.

Interesting... intriguing... but useful? When we hear this premise, programming is an art, what argument is it supporting? Or is it simply an observation devoid of practical value?

Beautiful code?

When the common programmer says programming is an art I believe they usually mean that they strive for beauty in their code. I also believe they are talking about the result, which is the code, not the process, which is the programming. I also define common programmers as programmers who are common in their field. They won't be writing any books and probably not speaking at any conferences but they're smart folks, have significant experience, and they get a lot done. I would put myself in this group.

I wrote beautiful code years ago. My code would have been tweaked repeatedly such that all its components seemed to be where they belonged by the end of my projects. In my gut it felt beautiful, but beautiful really just meant putting like things together. I arrived there without knowing why I was doing what I was doing, how it was good, and how it was bad. I was ignorant of just about every other meaningful attribute of my code.

My ability to compose such beauty created an internal illusion of my own expertise. What I did was working, wasn't it?

Beauty has no value in code

Try putting a monetary value on the beauty of code. How would you come up with such a number? Could you measure it without defining beauty in terms of things like extensibility, testability, readability, or other objectively measurable attributes? It would seem not. If art and beauty are simply imprecise terms for a conglomeration of these objective attributes, does it not detract from our discourse to use them at all?

Beauty is not one thing to all people. It is subjective and as such it is a terrible attribute for comparison. Is the Mona Lisa more beautiful than Jackass 3D? I don't know, I'm sick. Really sick.

Since beauty is subjective, anyone could claim that their code is beautiful and they'd be right and you'd also be right even if you disagreed. Jackass 3D is more beautiful than the Mona Lisa because I said so. It levels the playing field. Differences in coding practices become a matter of personal taste with no connection to real world consequences like expenditure of time and money. That is the true harm in this kind of thinking.

Maybe you write medical software so testability is extremely important. Maybe you write embedded software that will never be changed after its released so maybe you don't care about flexibility or extensibility. Maybe you need to ship your product in 3 days, or maybe 3 years. Maybe your team is junior HTML developers or maybe its programming gods who can flip bits with their minds. Different code attributes will have different business value depending on the changes (or lack there of) that your code will go through in its lifetime and the team that will see to those changes. This business knowledge should inform your coding practices, not some misguided quest for art.

Shit, I work with artists

My advice to those out there trying to make the case for less art and more of a practical approach to coding would be to:

  • Analyze the code, coding practices, and tools you use. What makes it hard to meet your business needs? Look at specific cases, because context matters. No silver bullets. No one size fits all. You want to solve a real problem, not sell a religion.
  • Avoid arguing minor details. For example, if you think its beneficial to make a big change to your system but you spend all your time arguing whether curly braces should be on the same line or the next line no one will listen to you. Choose your battles. Don't gain the reputation of the code nazi.
  • Read about how other people solve problems.
Page 1 / 1