| Max Guernsey, I...'s profileMax Guernsey, IIIBlogLists | Help |
|
March 29 The Zone of Distorted ComplexityMan... that watch looks complex. This post is not, about the "intelligent design" hypothesis but it does reference arguments used in the surrounding culture war. Some people would make the following argument:
Many people accept this argument as legitimate. The argument first treats life (in the form of a forest) as though it is simple when compared to a timepiece. Then, when it is convenient, the argument recognizes how much more complex than a machine life actually is. It is a flagrantly self-contradictory argument that was mutated from something even more ridiculous and rhetorical: The assertion that you can tell something was intelligently designed merely by looking at it. In fact: a single bacterium from those hypothetical woods would be more complex than any man-made watch. The watch has flat surfaces, refined substances, and gears with tines of regular sizes which are placed at regular intervals. These are all simple things. The reality is that it is the watch's simplicity that leads one to believe an intelligent being designed it, not its complexity. So why do so many people accept such an awesomely flawed argument as cannon? One could argue that growing up in a religious household so distorted their perceptions of reality that they are emotionally incapable of recognizing how absurd such an argument is. One would be both rash and wrong in so doing: growing up religious biases one's premises, not one's ability to perform logical analyses. I hypothesize that people regularly mistake complexity for simplicity (and visa-versa) by applying the following criteria:
Were that to be true, it would create a "zone of distorted complexity" in rule #2. In said zone, the complexity of a thing is perceived relative to the observer's understanding of that thing's domain rather than relative to the complexity of other similar things. Continuing on the assumption that I am right - which is what I always do - there are two reasonable ways to handle someone who has trapped themselves in a zone of distorted complexity:
The difference between these two is specificity. Taking approach #1 addresses a specific lack of understanding. Taking approach #2 addresses flawed thinking. Fortunately for me: I work in the computer industry which is a form of engineering. Since engineering is man-made it tends to be - you guessed it - pretty simple up to the point where it bumps into a complex system such as the human body, an eco-system, or an economy. As a result: fostering domain-knowledge is relatively easy. All I have to say is "Sucks to be you, biology teachers!" March 27 I Had a Premonition: "This movie will suck.""I have become unstuck in time" - Billy Pilgrim, Slaughterhouse Five NO SPOILER ALERT: This movie spoils itself. Someone doing their laundry and dropping their kids off at school is not a basis for a movie, regardless of how the events are ordered. Forward, backward, it doesn't matter: "Premonition" is still, for the most part, just a movie about that chick from "The Net" doing chores. Rent "The Lake House" instead. March 23 Cohesion: How Much is too Much?"...and I say your three cent titanium tax doesn't go too far enough!" - Jack Johnson, "Futurama" One of the most common complaints about "complexity" pertains to the principle of Cohesion. The degree to which something is cohesive is the degree to which its parts are related. For instance, a method that checks an account balance and pets a kitty is not very cohesive unless that method happens to be part of the JamesBondVillain base class. On the other hand, a method that checks an account balance and notifies the owner if their funds are running critically low would be pretty cohesive. It seems like a pretty good thing, right? So how much is too much? Let's take it to what some would call an "extreme:" Again we revisit our old friend: the Chain of Responsibility. (Aside: the more I use this pattern the more I love it). Many who implement this pattern do so by having a base class that encapsulates the decision as to whether or not the request should be passed down the chain. Then they start adding derivatives of that class. That makes sense, right? I mean, that's how it's implemented in "the" Design Patterns book. Still... it seems like it contradicts a more fundamental tenet of the Design Patterns book: favoring delegation over inheritance. In most - though: not all - cases it seems that using a Strategy pattern to decouple the decision part from the handler part of a Chain of Responsibility could only serve you well down the line. Yet, many claim that this is more difficult to understand than the so-called "canonical" form. The main arguments levied against cohesion are "readability" or "complexity." I think this comes from a drive to understand all the pieces of a system before they can use any of it. This, in turn, is driven by decades experience with brittle systems developed using, for all intents and purposes, ancient techniques. In a system where the code-quality is high, Encapsulation should protect you from the very implementation details that people feel the need to understand. Good names will tell the consumer of a service what that service is. The public interface of a type or method is all that should be required in order to use it. Then there's the "other" kind of system... The framework built in a requirements-vacuum containing layer upon layer of needless complexity that served no purpose and was developed either "just in case" or to stave off boredom. The system with cute names like "joesclass" and "Conn2DbWXmlCfg." It will be difficult to wedge any kind of design pattern - simple, complex, or otherwise - into a system where it is that difficult to get your bearings. It appears to me that, when someone says, they think such structures are "too hard to understand," what they are really saying is one of the following:
If you often receive such complaints, address the problem empirically: write a little system and don't look at it for six months. After it's been long enough that you don't remember much, go back and see how easy it is to add a feature to the system. That should make it clear where the problem or problems lie. If you often complain about the "complexity" brought about by a system of many small classes rather than a few large ones, try to strike up a conversation about it. Maybe things really are too complex. Use concrete examples to make your argument and think carefully about the responses you get. In other words: the next time you are involved in a complaint about "too much cohesion" look for the fool. If you can't find him, you're it. March 21 One Piece at a Time"Little by little, one travels far" - J.R.R. Tolkien I see a lot of developers run into the same problem, over and over: they want to understand the "big picture" before they can get any work done. They will spin their wheels trying to understand everything; writing and rewriting the same code or (hopefully) test over and over trying to get a lock on the "complete" or "true" nature of a thing. Allow me to preface the remainder of this entry by acknowledging that this phenomenon comes from the very best of motivations. It happens to people who don't want to mess anything up. Often, they are used to an environment where an incomplete understanding of the whole is synonymous with disaster or where they were able to "silo" themselves in a specific domain which they could easily grasp. To those whom this problem frequently haunts I would pose the following question: If you were putting together a jigsaw puzzle, how would you do it? Would you randomly try to force the pieces together until you had a puzzle? I'd certainly hope not. That would take an incredibly long time. Then again, maybe I should hope you do because that means you think you are going to live long enough to put a puzzle together that way. Would you stare at the cover of the box and commit the image to memory, then put each piece exactly where it should be according to your recollection? That seems like an equally ineffective extreme, to me. Maybe you are smart enough to do it. That would be pretty cool. I'm not, though. It seems like most people assemble a puzzle incrementally. Since most people aren't here to type, right now, I'll just describe how I do it. I like to start with the border and work my way in, sometimes diverging from that process to capture "low-hanging fruit" such as highly recognizable objects or unique fields of color. It is an empirical, intuitive, and flexible process that focuses on delivering a little piece of value at a time rather than following a "master plan." This analogy translates fairly well to software development; the one major difference being that not all of the pieces of the puzzle are even visible to you. That difference can actually be an advantage, though, because it acts as a powerful force guiding your hand toward the next piece. If you have a problem to solve and a good acceptance test that proves when you're done, then you don't really need to understand all of the pieces up front. The acceptance test would be the picture. The fixtures for those tests are the border. Your classes and methods stand in for the interior pieces. ...just start with the border and work your way in. Self-Referential Chains of Responsibility"Oh, you can't help that," said the Cat: "We're all mad here. I'm mad. You're mad." The Chain of Responsibility pattern is a powerful, flexible way of selecting the right handler for a request. It has many uses but one that I'm really fond of is processing polymorphic descriptors. Let's say we have a system of classes that, together, describe a criterion. For instance: equals, greater than, and, and or. From this we can build many different criteria. As we develop our system we will - probably fairly early in the game - want to be able to convert a Criterion (the base interface for all the above criteria) into the where clause of a SQL select statement. Some might feel compelled to add a method to the Criterion interface to render it as SQL. If you did that, each instance of Criterion would do two things (describe a criterion and render SQL). Does that seem very cohesive? Not really... it seems a lot better to break the task of converting making SQL out of a Criterion into its own class. Handling criteria like equals is probably fairly easy: once we know we have an equals Criterion, we just downcast, extract the relevant pieces and put them in the query string. There may be challenges such as cross-platform parameterization but, for all intents and purposes, it's straightforward. Handling a Composite criterion such as and or or, however, is a little trickier. Why? Because this sort of composite is blind to the nature of its contents. In other words: an and can contain another and, which can contain another and... and so on. In addition to being able to contain itself, and and or can contain each other along with equals and greater than. While it is possible - though tedious - to handle such structures through brute force, as the number of types of criteria grows, a solution emerges as the clear winner: allowing elements in the Chain of Responsibility ("links") to refer back to the chain as a whole for decisions on how to handle the children of a composite. We can save ourselves a little work by revising that solution to "Allow any link having a need to issue requests to the chain as a whole to do so." Here's the "tricky" part, though: most links that have a need to issue requests back to the chain need to send those requests to the head of the chain. For instance, since both and and or could contain one another, the way their children are handled needs to be independent of their order in the chain. Pointing back to the head of the chain ends up being a little stickier than we'd like. We could make it so that every link in the chain has a mutable Head property which, after the chain is constructed, is made up to date (or kept so as the chain is being constructed). That's ugly, though. We had a nice, clean, beautiful Chain of Responsibility that was (or should have been) totally immutable and we bashed it over the head with a mutable property that every link in the chain now has to support. I don't know if you can live with that but I sure can't. What else can we do? A Singleton, maybe? No. Singletons are evil and so are the people who use them. I just can't bring myself to use any "pattern" which bears the "Marquis de Sade Seal of Approval." The problem goes away if we build a special kind of link whose only job is to be the head of the chain ("Head"). Ideally, we combine the Strategy and Factory patterns to keep the exact details of how our chain is built encapsulated from Head. To do this, we would pass a factory in to the constructor of Head. That factory has a single method that accepts a reference to a link (the head) and returns another link (the body). The constructor for Head passes a reference to itself in to the factory. The factory then creates each link of the chain, passing in a reference to the Head as necessary. Each link is given the opportunity to establish an immutable reference to the head of the chain. When the factory returns a reference to the body of the Chain of Responsibility, Head can then set an immutable reference to the result. So, we're back to having an internally-consistent, immutable and (now) self-referential Chain of Responsibility. March 19 Don't Touch My Privates!...and stay away from my internals, too. A long time ago, I posed the question "Why would anyone use 'internal?'" ...a very long time ago. At the time I was only thinking of it as something that was weaker than "private." Now, I realize that it is also stronger than "public." I know... "doy." The fact is, though, that not a lot of people think about encapsulation at the assembly-level. Considering the lean principles, we can see that "Optimize the Whole" and "Building Quality In," when combined, require a holistic view of quality. That means quality at the method-, class-, assembly-, node-, and system-level, all tied together as one. Many of us have already come to the realization that encapsulation is a very powerful way of controlling (among other things) coupling. What some may not yet have clued in on is that there are more forms of encapsulation than just making the right members of a class private. Good use of the various design patterns alone can encapsulate the consumers of a class from its existence if the consumer plays along. I like to think of this as "voluntary" or "optional encapsulation." If we go one step further and use an access modifier to hide the very existence of a type from the world, we can truly encapsulate it. Doing this ends up changing the role of the design patterns of which the encapsulated type is a part. Instead of playing the role of minimizing bad coupling, they migrate to the role of facilitating good coupling. At my current gig, we came up with a pretty decent agreement. Only factories and interfaces are exposed as public types; everything else remains encapsulated. It's been working really well. The forces it brings to bear are really constructive. It puts into place a very strong dependency on interfaces that positively impacts the following:
I doubt I would ever go back. |
|
|