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.

Homegrown programmers

When programmers say another programmer is homegrown I believe they mean that programmer is self-taught and does not have a traditional computer science background. Though I don't perfectly fit this criteria, I consider myself homegrown.

I got an associate degree of science at, basically, an art college. During my college time I never took courses on data structures or algorithms and never programmed in assembly language. All seemed fine though. I thought I was going to be a web designer, not a boring programmer for the rest of my life. I was into HTML and Macromedia Flash and those things were waaaaay cooler.

In high school I was lucky enough to be living somewhere that had three years worth of computer science classes (pretty good for the mid-late 90s). The classes, as I remember them, were more about writing increasingly difficult programs but not really about theory or formal algorithm education.

Before high school I spent time on some old IBM machine. I can't name drop the specific type of computer and even if I did I don't think it would give me that OG programmer street cred I yearn for. I wrote programs in QBasic. I occasionally tweaked simple DOS game code to make them run in some random way I desired. Programming was just for fun.

Time and effort

Despite a high school and college experience focusing generally on programming, the formality most people experience in a computer science curriculum wasn't there. That said, I did leave out one important detail of my story so far - I programmed a lot.

I commuted to college only three days a week and the rest of the time I worked a job as a sort of junior MIS guy at a small company that built circuit boards. I was only 18 and I had my own office and computer. During my time at work I was sure to sneak plenty of programming in.

I was at home coding even more while every other college kid was out partying. I didn't drink or really care for crowds and I had a girlfriend so I didn't need to go chasin' gals, as the kids say. If I had a single vice that was becoming a problem... it was programming. Every night I stayed up late watching Conan and writing code in my parents basement.

In college I entered a design contests and did well, which I believe got me preferential placement in my college's internship program. My internship turned into a full time job. I was a Flash and PHP programmer.

Keep in mind this was after only spending two years on my associate degree. All my friends were still in college earning their bachelor degrees. This meant that on top of already working professionally, I was still tapping into an abundance of time to myself where I could write code. More late nights coding with Conan.

My girlfriend at the time, now wife, teaches children to play piano. This means she works later in the evening, so each day I get home and have a few more hours to myself. I also commute into the city each day. I use much of this time to read or write about programming.

I've had the opportunity to invest a lot of time in my programming career. I've taken advantage and continue to take advantage of that opportunity. This isn't the same as a college degree but possibly as valuable.

Are we good enough?

Every so often I worry about the gaps in my knowledge that certainly would have been filled by a traditional computer science curriculum. I think my worries are mostly due to fear of embarrassment. Most of my colleagues do have that traditional training, so that leaves me as a bit of a black sheep. The truth is everyone has gaps in their knowledge. No one knows everything. I try to focus on learning as much as I can about the problems I face rather than worrying too much about the problems I should know about but have never come up. I also try to be aware of my knowledge gaps so I know when I need to ask for help and invest time in filling those gaps.

There are some vast knowledge gaps that both homegrown and traditionally-trained programmers seem to suffer from. I haven't looked at all available computer science courses at all colleges, but the ones I did see all seem to focus on teaching students how to tell the computer what they want it to do. "Isn't that what programming it is?" you might ask. Nope. Its only one piece of the puzzle.

For all our code mess, bandaid fixes, and technical debt we still produce stuff that works most of the time. We're generally telling computers to do the right thing. But really computers only care about 1s and 0s, everything else is for humans. We should be good at writing code for humans; Code that humans can read and easily understand, reuse, extend, test, and so on. From what I gather these things are hardly covered in computer science programs and that to me seems like a major failure.

If you're a programmer you know that learning needs to be an ongoing process in order to survive. This means you'll probably do most of that learning outside of college. College for most lasts four years, but careers may last forty to fifty.

As for the learning that does happen in college, I wonder how long that knowledge stays sharp when those skills go unused. I certainly have lost many skills over time. Calculus, for example. Holy shit, I hope I don't have to do any calculus.

I would guess that, like my calculus, the unused skills of a traditionally-trained programmer would quickly atrophy. And why do they go unused? Lots of jobs do not use much of what would be learned in college. I wish I had numbers on this but I doubt they exist, so I'm basing this off my personal observations. I simply haven't had that "oh shit" panic moment where I've felt I was out of the loop at work because of that missing computer science background, so I assume that the special training they have and I missed, they aren't using very much.

One faction of the homegrown programmer population does worry me - The homegrown programmer who has spent most of his career working in total isolation. By "isolation" I mean having no programmers at work, at home, online, or anywhere, to learn from. If you aren't use to working with other people or writing code for humans (other than yourself) you are likely at a disadvantage.

Homegrown and traditionally-trained programmers are a mixed bag. The best and worst programmers I've worked with had traditional training. I've interviewed people who asked for six-figure salaries and had computer science degrees, but couldn't code FizzBuzz. Lots of people fail at FizzBuzz.

Having a computer science degree does not prove your competence as a programmer. Employers need to look beyond these degrees and dig into the actual skills their applicants have. I believe they'll find that a homegrown programmer who has invested time and effort in their self-training has an equal chance of being a productive member of their team.

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.

« Page 3 / 9 »