| Max Guernsey, I...'s profileMax Guernsey, IIIBlogLists | Help |
|
|
November 15 Lean Thinking in Software Design Part 1 - ValueAs I was working on the course book for my course on how to achieve Database Agility, I noticed something: a lot of good design practices map directly to Lean principles. Not just that they are consistent with them; certain practices are actually instantiations of Lean principles in the small. I'm not claiming to be the first one to have discovered this. It is just the first time that I have ever thought of or heard this. So let's look at the first principle: value. What does "value" mean? It means a lot of things to a lot of people but, to Lean thinkers, it has a very specific meaning: value is something a customer wants or needs. In other words: we start by figuring out how we can fulfill a need, then work backwards from there to build a product. How does this map into software development? One obvious practice that comes to mind is programming by intention, a.k.a. "top-down programming." Programming by intention runs deep in Agile software development. It is part of test-driven development - in fact it is most commonly done as part of that process - it is part of refactoring. It is part of a lot of things. We come up with all of these terms for why it's a good thing to do. For instance, people say "programming by intention is good because we are defining classes from the outside in." Another common way of putting it is "programming by intention is good because a class or method is designed in the context of the need that caused it to be made." If you consider clients to a class or method to be that artifact's "customers," then what you are really saying is "programming by intention is good because we are defining classes from the outside [where their customers are] in [where the service provider is]" or "programming by intention is good because a class or method is designed in the context of the value that it creates." Both of which are just fancier ways of saying what Lean consultants have been saying for a long, long time... Principle #1: start by identifying value. Up next, we'll see how Principle #2 (define the value-stream) maps to software design. September 20 Bubble Blaster - an Analogy for Planning Ahead, ValueI have downtime. It's uncommon but it happens. Most people who know me know that I like to play casual games in my scant spare time.
I found a neat new one called Bubble Blaster. Why do you care? Well, there's a lesson in this game. This is the first bubble popping game I've found that has a built-in opponent and that changes all of the foces.
In this game, you are working toward a goal (highest score) and the opponent is working toward that same goal. You and your opponent have the option to do a number of things:
There are two lessons to be learned from this game:
Both of these lessons also depend on the notion of position. Your position is constantly changing as a result of another actor working it its own self interest. This can be thought of a micro simulation of market forces. On Value This game has some very interesting principles that make it necessary to work on value rather than taking a large number of essentially blind actions some of which yield value. Every ball you fire has a cost. Like programming, there is a small cost to firing each ball and, also like software development, each action carries with it the risk of changing your position for the positive or the negative. The amount of impact that each shot has on your position and your score can vary greatly. Consequently, I've found that option number 3 (hold your fire) is more valuable than originally would have seemed to be the case. By firing selectively, I am biasing the impact that each shot has on my score and/or position toward the positive. This is just like product development. Only developing those features you need yields far greater profitability than blindly developing everything you can think of. On Prescience Many of you probably also know that I have a product called GuideWire. I and many others use this product to help me plan my shots when playing this kind of game. One of the features of this tool is that it gives you the ability to predict how a ball will bounce off of a wall, as shown in the picture below... ![]() In slow-paced games, such as Bubble Spinner, it is advantageous to try and plan long shots that ricochet off of several surfaces to capture big blocks of value with a minimum number of shots.
While it is important to conserve shots in Bubble Blaster, it is also important to act quickly. Even more important is to make sure that your shots go where you planned for them to go. ...but how to you make that happen?
The biggest risk to a shot is the time between when you shoot and when the ball strikes its target. Why? Because your opponent can take a shot while your ball is in transit, changing your position after you've committed.
In the higher levels - where the pace is fast and the opponent takes well aimed shot - this makes bouncing the ball several times all but useless. There are a few times, of course, where you might take advantage of your circumstances; for instance, if your opponent is stunned, and there is something on the opposite side of the playing surface of great value, you might take the shot.
Most of the time, you want to take the closest shots - the ones that have some value and are most likely to be completed before your opponent can change the board. If you apply this strategy: wait for low-risk shots for which the value outweighs the cost, you can quickly obtain a large lead against your opponent.
This is an analogy for product development, where the biggest risk is work in progress...
Wait for the right shot - the right feature. In your down time, control your position - keep quality high and monitor your customers' needs. Avoid longshots - don't create months-long plans and then commit to them. June 17 State of the ArcaneI've noticed that people have a tendency to want to go backwards. I'm hardly the first person to discover this. In Lean Thinking ...but nothing is ever as real as when you experience it first hand. Anyway. One thing that people try in an attempt to stave off a Lean conversion is to "teach" you "how it is really done." I can't count the number of times that I've sat, mouth hanging open, as someone "explains" to me how things should really work. As though anyone who has worked in this industry hasn't heard it all before. Specifics aside, it always amounts to one of a few arguments. "Just batch up a bunch of work and costs will go down." "Just skimp on quality and costs will go down." "Just create large queues and costs will go down." Pish posh. These arguments always remind me of an old generation of scientists who simply refuse to accept a newer, better theory in spite of all the evidence such as was the case with the transition from phlogiston theory to the oxidation theory of combustion. What is being taught in these scenarios is always the most advanced interpretation of the old theories available - the "state of the arcane," if you will. What I need is a way to validate that someone has a highly advanced understanding of an out-of-date thought-framework so that I can validate what they do know without accepting what they believe as the truth. Any ideas? March 22 Less than a month to my course!I am so excited about my upcoming course on database agility. We've got a lot of great people signed up and I think it's going to be a really good discussion. I am really looking forward to it. March 20 Do you want to reveal your intentions?I keep hearing the term "intention-revealing names." I get the idea but I don't think that's the right term. Intentions shouldn't be revealed by names - do you really care what I intended when I wrote something? I know that I don't care what I was thinking when I wrote something. No. Intentions are coupled to developers. They are too subjective to be tied to the name of a class or a method. If anything, tests should be what reveals intention. What is it we really want to convey with, say, a class name? I think we want to convey it's responsibilities. We want to tell other programmers what that class does. In short, we want to describe the value encapsulated by that class. So why not "value-revealing names," instead? March 28 Rethinking Agility in Databases, Part V: CollaborationI just finished another article in my Rethinking Agility in Databases series...
Check it out and let me know what you think. "Refactoring Data" Seminar (March 25th, 2008) ResultsWell, I would characterize my March 25th, 2008 seminar as a success in that I was not laughed out of the building.
It was tough, though. Trying to do the right amount of preparation. I made a lot of rookie mistakes. For instance, I apparently apologized way too many times (sorry about that). Worse, I cut one of the attendees off in the middle of his question (you know who you are). Hopefully, I won't be doing those things again.
I should be putting on another one in about a month (toward the end of April) and I am looking for a venue I can use in Seattle to deliver this seminar.
If you have a presentation-enabled room (one with a projector) on the left-side of Lake Washington and want to hear a seminar on enabling changing designs in data, please let me know via email. February 29 Refactoring Data Seminar: March 25th, 2008I will be giving a free seminar on bringing Agility to the database world by enabling true refactoring on March 25th, 2008. It takes place in Bellevue, WA and the venue was made available to us by Net Objectives. January 18 DataConstructorSo I've been working on a product for the last couple months. It's called DataConstructor. It makes agile database development possible. Actually, it makes agile any-kind-of-data development possible.
Check it out. There's a free thirty-day trial, so you really have nothing to lose. April 07 Throw First, Please"No. Dig up, stupid!" - Clancy Wiggum, The Simpsons One of my many, many pet peeves is when people do something like the following:
It is a lot easier, cleaner, and more maintainable to do something like:
Exceptions are about... Well... ExceptionsExceptions are more than just "an error handling mechanism." They are a way of thinking. Good code operates on a set of stipulations. Cases that fall outside of those assumptions are not errors they are simply deviant circumstances requiring attention from a higher authority. The paradigm shift described above improves both cohesion and encapsulation and, out of that, falls out a secondary benefit: the ability to discover exceptional cases earlier. With nested "if statements," assuming the problem is reasonably complex, it is usually difficult to fail as early as possible. Doing so often leads to massive networks of nested if statements which are difficult to read or maintain. A Change in ThinkingShifting your mind set from handling multiple cases (if/else) to handling one case and "passing the buck" upward otherwise (if/throw) is not easy. Most people won't just snap their fingers and shift gears. Proper use of this technique requires the same delicate touch you might apply to static structure. If you make the leap, though, your code will be a lot cleaner and more flexible. Better EncapsulationConsolidating decision making improves the Encapsulation or, at least, the potential for Encapsulation in your code. With an if/else, you can refactor predication, but not the resulting branch, into its own method, as in the following:
Whereas with an if/throw, you can encapsulate both the decision and the branch.
The Encapsulation Bone's Connected to The Open-Closed BoneAnother clear advantage is the ability to easily recognize new, and discard old, exceptional cases. As your services gain popularity within a system, the set of circumstances in which they can be applied will flex a little. Sometimes they flex inward - potentially eliminating the need for the exception. Other times they flex outward - possibly adding an exceptional case. Being able to easily encapsulate both a decision and a branch allows us to do all sorts of neat things with it. For instance: we could refactor our EnsureConditionMet method into a Strategy. Once we've done that, we can move it into a Decorator, dissolve it into a Chain of Responsibility, Adapt it from some other component that already solves our problem, vary it with a Bridge, and so on... Discover EarlyOnce you've broken out your exception raising technology, you get something else almost for free: early failure. Every line of code that executes up to some point of completion carries with it a little risk. If your exception discovery technology is broken out properly, it becomes easy to see when the earliest point at which a stipulation can be enforced: when you can provide the decider with all of the information it needs. The sooner you throw, the less risk you incur on an task that will obviously not be finished and clean up after what has already been done. ConclusionUse exceptions the Chicago way: throw early, throw often. April 05 Capacity to Deliver as a Currency"No one exceeds their potential. If they do it just means we didn't judge it accurately in the first place." - "Dr. Lamar" from "Gattaca" BasicsFor some reason people spend a lot of time cutting their own hamstrings. They feel pressure to deliver so they work "harder," degrade code quality and, effective, borrow a feature from future releases at an exorbitant rate. Quality - and, I think, any kind of capacity for delivery - is a currency. You can have more of it or less of it. You can spend it to get a feature. If properly deployed it can yield dividends. If properly managed it starts to compound. Sounds like a currency (or, maybe, an extremely liquid security), right? Let's go through a some models. Now, like any good hypothesis pulled from one's gut (I leave it to the reader to guess what route it took), this entry relies on the principle of ceteris paribus. Factors like people's ability to "learn while doing" are left out of the discussion but understood to have a real-life effect. We have two metrics: capacity to deliver and rate of delivery. Capacity to deliver is an abstract measurement of all the things that allow a team to deliver functionality; skill, code quality, methodologies, processes, et cetera. It is denoted by thick, green lines. Rate of delivery is the amount of business value that is delivered in a period of time. It will appear as a thick, red line. Good Manager/Bad ManagerThe simplest, and most prevalent, model for work is also the least likely to be realistic. It is the notion that, ceteris paribus, capacity to deliver is constant and a good manager or project manager "rides" people to deliver. In this model, it is as though capacity to deliver is a ceiling from which the team's rate of delivery is suspended by springs. A "good manager" constantly presses upward, compressing the springs and "squeezing a little more" out of his team. When a spring becomes exhausted, we call it "burn out." The manager tries not to push too hard because he doesn't want to lose a good spring - after all: they may be pushing away from true potential, but they still keep things from falling apart. To keep the spring coefficient from becoming zero, we periodically send springs on vacation or buy them lunch. In times of stress - times when the manager has just "gotta get something out the door" - he pushes harder on the rate of delivery - assuming that his springs will hold and that the green line (capacity to deliver) is constant. This doesn't seem to be a very realistic model and, fortunately, people are starting to realize it. It seems to me, and to many others that the green line (capacity to deliver) is not a constant. It can be manipulated and, actually, it is the most important thing too change. Capacity to Deliver as an Exhaustible ResourceIf we treat capacity to deliver software features as though it is mutable we get a different picture. To do so, we have to redefine capacity to deliver. Instead of being the absolute maximum a team might be able to do over some period of time, let's say it's the most a team could deliver in a sustainable way. In my experience, the red line and the green line appear to be covariant. Each simultaneously affects how the other behaves but the rules aren't anywhere near as simple as the behavior of a spring. I can't say I know all of the dynamics but I can say it's a lot more complicated than linear resistance. One thing I've noticed is that, when the red line is near the green line it pushes capacity to deliver away. So, when the rate of delivery is just below capacity, capacity starts to go up. When a team is delivering above capacity it starts to lose its ability to deliver in the future. Even this doesn't work like a spring, though. It almost works like the opposite of a spring; as though there is a "sweet spot" where the pressure remains highest. It seems like there is a focal point - at least when the rate of delivery is below capacity - where urgency provides incentive to invest surplus resources rather than squander them trolling the Internet for meaningless blog entries. Beneath the sweet spot you will probably get nothing. Sometimes it's because people just don't feel a need to work. Other times people need to work but there isn't enough context to do anything of value. A worthless framework with high-code quality is still a worthless framework. One rule that's fairly obvious: the further above capacity you try to deliver, the more long term damage you do to your future abilities. In other words, the rate of delivery presses down on capacity for delivery as though it were a weight proportional to the distance between the two. Attempt to deliver too much, too quickly and you will break your product's or your team's back... or both. Earlier, I mentioned that the two lines were covariant. Capacity to deliver effects the rate of delivery in a couple of ways. The true maximum rate of delivery is related to the capacity for delivery. Once a team's capacity is sufficiently high, it will be able to achieve a sustainable velocity that is far greater than anything it could have done earlier - even at a "breakneck" pace. Sometimes what happens is the team is given an opportunity to improve its capabilities but, then, the pressure to deliver is increased (based on the "spring-loaded development" philosophy shown above). When this happens, the results can be disastrous. If the rate of delivery is cranked up too high (specifically: high enough to exceed the team's capacity), the team's capacity to deliver will start to degrade and all of the previously invested resources will go to waste. The bottom line is this: Set realistic goals and focus on constant improvement. If you do this, you will actually meet your goals in a sustainable way. If you focus on short-term gains then all you get is a burst of apparent value with a massive, hidden liability. April 04 Completeness vs. Agility"...but I'm so comfortable... too comfortable..." - Tool, "Undertow" NOTE: I use the word "Agility," in this entry, to refer to a hypothetical force that drives us to remain agile, nimble, or adaptable. I am not referring to any specific Agile methodology. Recently, at the gig on which I am currently working, I was pairing with someone who is new to Design Patterns. He was working his way through implementing the Composite pattern and doing a good job of it. At some point it was suggested that he wouldn't need to implement all of the methods on the composite class. The reason for this was that, in the only known context for the composite, about half of the methods would be used. To an extent, I could see the argument so I said nothing. The natural instinct, when building a software system, is to make it "complete." That is: we try to add every feature that anyone might possibly need. Later we find out which features were waste and which were necessary - assuming we deliver anything at all. Agility drives us to mitigate waste by developing what is understood to be the most valuable thing first, then getting feedback on what's really important. So we can see how Agility and Completeness end up competing in a healthy way: the drive to complete things helps us implement the features to which we've already committed, and the drive to remain agile helps minimize the set of features to which we commit at any given point in time. A very nice balance. This client of mine is working in a test-driven environment. I don't just mean they write unit tests before they start to code - although they do that - I mean tests drive everything. They write acceptance tests before they even start thinking about the units of code involved in a problem. These tests are an excellent prototype for the environment in which all relevant classes will be instantiated. It is very unlikely that leaving a class only partially implemented would end up being a problem as the acceptance tests will catch the problem before we call our software done. Still, I don't think that means we should leave a class half-implemented. The same way that a feature is not "done" until it's done, it seems that a class should not be "done" until it fulfills its contract entirely. I think that classes are atoms; they should always be whole and to divide one should be a considerable event with commensurate consequences. I propose that we separate in our minds the concepts of "completeness" and of "wholeness." In so doing we can recognize that nothing intra-universal is ever really "complete" except (possibly) the universe, itself and then only in a grotesque sense. Things can still be "whole," though. For example: "I spent a whole dollar on that candy bar, yesterday." Wholeness could be a single force underneath which all the many flavors of "it's not done until it's done" could be united. If we then recognize that Wholeness is not at odds with Agility the same way Completeness is, we end up with a nice, clean line for where to stop working on a thing. Always favor Agility over Completeness when limiting scope, but never limit the scope in a way that would jeopardize Wholeness. Who knows? It is possible that a deeper analysis would find that one remains more capable of responding to change if one keeps things whole. In other words: Wholeness may be a part of Agility. If that were true then I may have just wasted an hour. 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. |
|
|