Tuesday, June 12, 2007

Object-oriented management - throw new DevelopmentException();

From what little I can remember of object-oriented design from school, there was next to no attention paid to something that, in my idle thinking, makes OO implementation a whole lot more pleasant - exceptions. There's an analogy to development management in mind here, but to draw it fully I think that I need to lay out how and why I use exceptions in my object design first.

So let's say that you're developing an object. It performs functions that may or may not succeed - how do you communicate failure? At a simple level, there's good old booleans. True/false - the method call succeeded or it didn't.

class Foo {
public int Bar;

public bool Retrieve(out MissingRequiredValueBar) {
MissingRequiredValueBar = (this.Bar == 0);

bool returnValue = false;

// retrieve stuff!

return returnValue;
}

public bool Save() {
return false;
}
}

So this does a reasonable job of telling the whole story... in a simple case. But look at that Retrieve method - we've got two booleans to juggle already - pass/fail, plus an error state that we need to return explaining why things fail.


How do we know that the calling method's checking and working with both of those booleans? Boy, it'd sure be bad if they didn't. And what about Save? They know that returning false means that the data wasn't saved, right?


Furthermore, in a more complex case, I don't think it's hard to see (I'd show you some old code I'm ashamed to have written if I could) that our Retrieve() method's going to metastasize into a hideous mass of parameters. The more parameters you have, the better the odds that you won't have or need one in a calling scenario; method overloading can only take you so far.


Maybe you're thinking of another way out of this jam. Divorce the return parameter from the method call and make it another public property, right? So very wrong.


Do that and you're depending on the person calling your class to have intimate knowledge of how things work ("Oh - first I call Retrieve(), then if it fails I check this other property to see why it failed."). That's a best-case scenario and even there, you're not impressing anyone. In a worst-case scenario, your class has been transformed into an API and the developer on the other end is reaching for a decompiler while promising to punch whatever idiot was responsible in the throat for this godawful code that's been foisted upon them. Not that I've ever made that promise. Objects love loose coupling, remember?


So let's take another stab at this awful example of a class but use exceptions this time.

class Foo {
public int Bar;

public bool Retrieve() {
if(this.Bar == 0) {
throw new MissingRequiredDataException("You need to supply a non-zero value for Bar in order to call Retrieve.");
}

bool returnValue = false;

// do stuff!

return returnValue;
}

public bool Save() {
throw new FunctionalityNotImplementedException("Save functionality isn't working yet.");
}
}

A common critique of exceptions (along with the baffling observation that "throwing them is slow") is that they shouldn't be used as flow control. And you know what? I absolutely agree.


But notice - exceptions aren't being used as flow control, they're being used as cessation control. The exception message is unambiguous as to how to resolve the problem. Whether it's being used as an API or simply as a black-box object by a co-worker, it's clear what needs to be changed in the calling class to avoid the exception.


I used to write classes and have little todo comments littered all over the place. It wouldn't be long before I'd get things bootstrapped far enough along to start testing. Something goes wrong upstream and I spend time debugging to eventually realize/remember that I hadn't implemented that piece of functionality that I was trying to use yet. With exceptions, there's no ambiguity as to the source of the problem.


See how Foo's Save method blows up obviously? Would you prefer spending time figuring out why Foo's data isn't showing up in the database?


If you're not already converted, I imagine that it sounds like we'll be awash in a sea of exceptions, right? While your objects are in development and people are discovering the rough edges, yeah.


But as time goes on, a curious thing starts to happen - you and your co-workers get tired of getting bombarded with exceptions and you start to fix them. Yesterday's exception becomes today's new use case or available error state.


Summing up exceptions, I like them because...



  • Your class knows best when something's gone wrong and should have the ability to call a complete stop to the proceedings
  • You have the ability to provide context to the object's consumer that they obviously need
  • They make it very clear what isn't working with your class as you exercise it
  • The calling class can treat your object as a black box - all it knows is that either their call worked or it blew up and why
  • The calling class has the option to let that exception bubble up to its subsuming classes or to nip it in the bud right there
  • Error states can be misdiagnosed or ignored, exceptions will stick around until the process responsible is fixed

So here's where I make the big leap from merely being a so-so developer to being a clueless, completely inexperienced, hopelessly naive manager-wannabe, but I don't think this is the stupidest assertion I've ever made.


Problems employees face should be treated like exceptions and bubbled up to their manager as soon as possible.


As a manager, you trust that your employees are doing their work, but for the most part, you want to leave them be and have trust that what they're doing is working. In that sense, you're treating them like objects - do you step through every line of code of every class that you call or do you validate the inputs and outputs and take it as a matter of faith that things are working as expected since it looks OK?


When it comes to developers, there seem to be three ways to handle problems that crop up.



  1. "What problem? Don't worry about development going long, we'll just make up for it by cutting the QA cycle a little short. It's not a big deal, because if a component took longer to implement than expected because of the complexity, you can be sure that there won't be too many problems in the finished product that I'm hurrying to make."
  2. "Holy shit I had to open up a debugger today because Dave's Foo class threw an exception when I tried to Save we are going down in a sinking ship."
  3. "I know that I promised you that Foo in two months' time and I've only been on it for a couple of weeks, but I didn't realize how crufty the surrounding portions are. If I have the time to refactor classes ancillary to the new functionality, it'll take longer but should ultimately be more stable. If not, I can shoehorn it in but it likely isn't going to be pretty or stable."

#1 is probably the most common way. People don't like to throw exceptions because at its heart, you're admitting failure when you do. They don't like to cop to being late or not having features complete because again, admitting failure. At best you'll pull teeth and get some status information to work with. But how do you know how severe the problem is or isn't? How can you trust the status they're giving you? At worst, they'll be completely silent about how things are going until the day before the gold build at which point maybe they'll dig deep into their bag of tricks and pull the 24 Hour Push Of Redemption out. It's too late for you to let anyone up the ladder know at this point. Pull teeth earlier next time.


#2 is the sky is falling way - dealing with them, you know that you're going to be awash in a sea of noise. If you listen to the roar, you'll be convinced that the sky is falling too. Then the sun will rise the next morning and over time you'll learn to take what they say with a big grain of salt - you'll implement processes to deal with this constant stream of exceptions. It's perfectly permissible to catch exceptions if you know what you're doing, but when you know what you're doing, you know that there are times when you have to let them bubble up or add context and re-throw them. A doctor's going to be annoyed dealing with a hypochondriac, but even a hypochondriac genuinely gets sick sometimes. Still preferable to being blind-sided by an error state.


#3 is about the best you can hope for (and what I hope my way is) -they don't overreact to problems but at the same time, they don't try to sugarcoat any of the big, project-threatening, hurdles in the way. Figure out what the blockades are in the process and try to resolve them.


Over time, you'll see trending - what exceptions are my employees raising to me most? Chicken Little's saying that the machines aren't beefy enough to keep up? You can probably back-burner it for a while. The Late-Warning, Silent Type's griping about the compile speed of the machines? Time to freshen up that hardware.


You'll react to the exceptions as you need to and (hopefully) resolve them as you see that you need to and can. You'll implement processes to catch the ones you can, to quash the ones that don't matter and to bubble up the ones that are nightmares.


Then again, I'm a developer who thinks of co-workers as objects and problems that a project faces in terms of exceptions. Worst. Metaphor. Ever?

No comments: