Oct. 6, 2009, 4:07 a.m.
posted by oxy
Item 25: Keep it simpleKISS (Keep It Simple, Stupid). Do the Simplest Thing That Could Possibly Work. The Rule of Simplicity. It goes by several names, but they all come back to the same idea: prefer simplicity to complexity in your code. This item really shouldn't have to be here, but it's amazing how often architects and technical leads get carried away with their own cleverness (yours truly included, I'm ashamed to admit). "All we have to do is pass the data through this complex series of processing objects, and they'll all combine to produce the results we're looking for. It's just so cool. It's the classic Blackboard pattern combined with Visitor and Observer, with some Composite and Transaction Script thrown in for good measure. . . ." Unfortunately, what they forget is that complexity kills more than anything else. A friend of mine once said, "Every project has a complexity budget: once you've spent all the complexity in your budget, that's it. If you try to go over that, the project will die. Stay within it, the project will be a success." Remember, some of that complexity will need to be applied to the domain problem itself, so if you spend it all on complex technology that doesn't address the bottom line, the project is going to fall apart. I have more than one project in my history that I now realize failed for precisely that reason, and I suspect anybody who has ever architected or led more than one project can say the same; certainly everybody I know in the same position has said as much. If you can't explain what a given "piece" of processing code does in a single sentence, at most two, it's probably too complicated. The problems with complex solutions are myriad but basically boil down to several simple tenets.
Other reasons certainly leap to mind; these are simply those most often cited in the popular literature. Problems of complexity most frequently arise in the processing, the code that we write that's most domain-centric. It's tempting to get tricky with the J2EE specifications—for example, having discovered the Request Dispatcher functionality of the 2.2 servlet container, it's tempting to consider using the forward method multiple times or calling include on other servlets multiple times, with each "subservlet" doing a small part of the processing involved in the whole. Resist. You introduce a huge swath of complexity into the system when doing so, such as failure handling and/or output generation. For example, complexity tends to rear its ugly head in the area of "special-case" logic that occurs within processing—"if our customer has more than 100 outstanding orders, make sure this gets run through a supervisor before letting the order continue," "make sure the credit card charge goes through before committing this row to the orders table, since we don't ever want to ship items that haven't been paid for," or even such simple situations as "if the customer isn't in the database, go ahead and use a 'default' customer entry since we don't require customers to be in our database in order to purchase something." It's tempting to suggest that we can handle these situations directly within code. Fowler documents the idea of using a subclass of a particular class as the Special Case pattern [Fowler, 496], but the examples there, of a Customer class and special-case subclasses MissingCustomer and UnknownCustomer, are both domain-specific and fairly simple; the subclass approach doesn't work well in the processing-centric examples mentioned previously. For our example of a customer with 100 outstanding orders, a rules-based engine can simplify your processing by taking you out of the imperative programming model and letting you work in a more declarative fashion (see Item 26 for details). In addition, the first example also implies that more than one party (human or otherwise) may need a shot at processing this particular request—in the enterprise world this is frequently known as workflow, and while both expensive and open-source workflow engines are available, often you can just as easily handle workflow by adopting a messaging-based processing model. For the example of charging a credit card before committing the order, look into using transactional processing as a way to ensure that failure scenarios get handled in an atomic and fairly transparent way; see Item 27 for more details. For the final example of using a "default" customer, a variation of Special Case [Fowler, 496] might work, where the Customer returned is an actual Customer in the database, no different from any other Customer except that the data stored is nonsensical or acts as a sort of database-driven Null Object [PLOPD3, 5]. Notice that in all of those situations, the words "roll it yourself in the code" never showed up. Implementing special-case code directly in your processing logic is the best way to create a complex situation; it may seem counterintuitive, but the best line of code is the line you don't have to write. As a rule, enterprise projects tend to favor the complex rather than the simple: it's a perverse idea that somehow a complex system is more "macho" than a simple one. Perhaps it's some kind of unconscious desire to build a little job security into the system, or maybe it's the architect's suppressed need to assert him- or herself as the Big Geek on the Block. Whatever the reason, it's worth repeating over and over again: Keep It Simple. Let the tools (in this case, J2EE and its related specifications) do as much of the work as possible. |
- Comment