Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Re^8: Legacy Code: "Dominoes = Bad" (predicting++)

by tye (Sage)
on Apr 30, 2011 at 18:43 UTC ( [id://902232]=note: print w/replies, xml ) Need Help??


in reply to Re^7: Legacy Code: "Dominoes = Bad"
in thread Legacy Code: "Dominoes = Bad"

the expenditure of significant extra effort in an attempt to ease the accommodation of possible future requirements is nothing more than gambling.

That's why several people have noted that they aren't talking about "significant extra effort".

I've found that my predictions for possible future requirements have been significantly accurate (that's part of my job, ya know?). But, even if I'm almost certain that feature X will be required in the near future, if supporting feature X introduces even moderate extra effort or extra complexity, then I (usually) put aside supporting that feature, mentioning "you're not going to need it".

Just because you've been seriously burned by somebody who was bad at predicting future directions and even worse at recognizing costs doesn't mean we all have to be pathologically opposed to venturing even a little bit toward that side of the scale.

I am pretty quick to resort to "you're not going to need it", when the predicted need seems other than very likely, when the support for that need is starting to add even moderate additional complexity or looks to require even moderate additional effort, when the understanding of what the predicted future need likely really means seems even a little fuzzy, or even when the discussion of the need or how to support it has gone on too long or even just doesn't appear to be progressing particularly fast.

But I don't immediately shut down considerations of potential future changes with Draconian proclamations to strictly only consider present business requirements.

And it isn't just predicting specific future needs. It is giving the design the benefit of becoming clearer and cleaner so that it is more resilient to be modified even when how it might be modified has not been predicted in any specific way.

Bouncing potential future needs off an emerging design in order to gauge how easily it might accommodate them can be quite useful in discovering more natural boundaries between components, producing more modular pieces with more logically cohesive functionality such that each piece is less likely to require changes in future.

When solving a complex programming problem, my most common first step is to identify some subset of the problem that makes sense to separate out as an isolated module. When that process produces something with an obvious and clear name and logically tightly cohesive functionality where the subroutine names are obvious and clear, then I'm unlikely to spend much time futzing with that design and I can just factor that part out of the larger problem and continue working on what is left.

More often, the first choice of what to separate out isn't such an obvious "win". So I'll bat around different names trying to find the clear logical entity. But I'll also throw potential alternate uses other than the current requirements and this usually quickly leads to selecting different subsets of features and aids in figuring out a subset that isn't overly driven by the current specific mandated approach to the larger problem.

If given a business requirement to bill customers for "minutes used", specifically rounding up to the next nearest whole minute for each call, it can certainly be far from a waste of time to consider the possibility that the currently mandated rounding method might change. If your initial approach to meeting the given requirement can't handle that, then you might have come up with a rather fragile implementation. For example, you might opt to just store the pre-rounded number of minutes and nothing more. It might be worth considering an alternative like storing the call start time and the call end time.

If you start dreaming up the business wanting to bill in milliseconds, realize the database only stores date-times accurate to one second, and start designing how you can use proprietary high-resolution timer features to get more accurate start/end times and then debating exactly how you should store these high-res times in the DB... then somebody needs to smack you with "you're not going to need it".

There are tons of not-very-specific uses that are easy to predict as "likely to be wanted in future" of having fairly accurate start and end times of phone calls recorded. Since you demand examples: You might want to provide Operations with a graph of how many calls came in over the last 5 minutes so that they can monitor system activity, even though that wasn't handed to you as a specific business feature requirement for this work. I have actually previously predicted that need and seen it come true. I have almost never been given a business requirement to store accurate call start/end times. Stand in utter amazement at my astounding psychic abilities. ;)

It might also be worth considering that the business might want to charge per call (not just per minute), per phone number allocation, and/or per phone number assignment per month, even though the business has not even mentioned those possibilities to you. You certainly shouldn't start building a separate system for handling billing per call just in case the business will eventually want that. But you might decide to build the system for billing per minute in a different way such that it will likely be significantly easier to add support for billing per call to it. The mere effort of considering these alternate billing scenarios (that are easy to predict the business wanting in future because they are all quite common requirements for other businesses in the industry!) might lead to insights that actually simplify the design of the billing system, breaking it into more modular pieces that are easy to test and actually reduces the time required to build the initial version.

A recent project required the addition of a "maximum pool size" option to each customer's account. Most similar options were just database columns directly in the "customer account" table in the database. Some other options were columns in a "customer options" table in the database. We considered those two approaches to solving this new business requirement.

We also had the foolhardiness to consider predicting future business needs. We predicted that this would not be the last time that the business required a new option be added to customers' accounts! This led us to consider creating a table of option names and another table with a primary key of customer account_number + option_name and an additional text field to store the option value. Creating the two extra tables certainly added some minor extra complexity / effort. But, and I know this is just very hard to believe, we actually ended up needing three new account options before we had even finished that specific, small project! Our prediction came true again!

On another project, we were working on cleaning up some designs around how we make changes to data in our database. We have a lot of custom bulk database manipulations that we end up writing for customers and these custom modifications need to honor the existing business rules. The existing business rules were mostly implemented rather simply and quite directly in the specific places where the rules mattered for the current implemented features. In predicting future business needs, we figured that new features would be required that still needed to obey existing business rules and so it might be a good idea to try to factor out the data-related business rules into modules that could then be used by the ad-hoc customer bulk operations that get implemented and also by the existing standard interface features.

We ended up with several classes whose objects represent things like "a campaign" each of which is mostly a record from the database along with zero or more closely related other records from the database (similar to part of what an ORM provides but not using anything that would qualify as a general-purpose ORM).

We agreed that we wanted the handle to the database not stored in such an object. That way only the few methods that actually interacted with the database would be clear because those methods would require that a handle to the database be passed in to them.

Then we considered the possible future use case of copying something like "a campaign" from one database instance to another database instance. This presented an interesting challenge to the design as each object had knowledge of what parts of the in-memory object state were already in the database so that the changes could be "flushed" to the database either as an "insert" or an "update" or neither (because the item already matched what was in the DB) yet you could, in theory, pass a different DB handle in and the save would then be done "all wrong".

This led one team member to propose a minor feature that attempted to address this theoretical problem in a small way. I ended up strongly rejecting that feature idea because 1) we had never yet written code to copy such a logical entity from one DB instance to another, 2) wanting to do such in the near future seemed only moderately likely, 3) the feature added a moderate amount of complexity and effort to be implemented, 4) the feature didn't seem to really solve the proposed problem fully, 5) figuring out how to address the problem well seemed like it would require significant contemplation and complexity.

I am actually capable of realizing when a prediction of the future is problematic in lots of ways and in teaching others how to do so. I don't need any dogmatic rules about never trying to predict the future.

So I rejected that particular feature idea. But I am still allowing at least my subconscious to contemplate whether the fragility of our class design in the face of such a theoretical need represents a real problem in the abstraction chosen. The abstraction seemed quite clear and simple. But in just writing this up and thus indirectly contemplating that problem, I feel a better abstraction already half-way formed that I suspect will actually simplify the design even further while also making it much more robust in the face of multiple database instances.

It doesn't appear that that (hoped for) design improvement would ever have occurred to any of us had we not contemplated predicting future feature needs that had never been asked for and that even seemed rather far-fetched. We weren't even able to come up with a likely use-case for the feature until many days after it was proposed.

Now that the design is starting to come together in the back of my mind, I'm also already seeing a lot of potential uses for things that it seems likely to enable that had not been asked for before but that will have real benefits in the face of likely future scenarios similar to past scenarios we've run into many times and had to solve in much more difficult ways.

Trying to predict the future has been very beneficial to me over the years. Yes, I've seen people who really sucked at it and tended to get sucked into it and wasted lots of time as a result. I avoid wasting much time on it but not by refusing to ever do it. On the contrary, I consider it an important step when designing and almost always spend small amounts of time on it and have repeatedly found significant benefits from it.

- tye        

  • Comment on Re^8: Legacy Code: "Dominoes = Bad" (predicting++)

Replies are listed 'Best First'.
Re^9: Legacy Code: "Dominoes = Bad" (Arrogance--)
by BrowserUk (Patriarch) on May 01, 2011 at 04:13 UTC
    I am actually capable of realizing when a prediction of the future is problematic in lots of ways and in teaching others how to do so.

    And all it takes is one. One ASOB who believes he can "beat the odds", and dodge the bullets of historical evidence. That he is better than all the rest.

    Now check out the evidence of history.

    In 2004, The Standish Group produced its 10th annual CHAOS report. An analysis of why IT projects fail. They concluded that in the period covered by the 2004 report, 34% of all projects had succeeded. And that this represented an increase of 100% in the success rate over the 10 years and 40,000 projects they had analysed.

    In other words:

    • In 1994, 85% of IT projects failed to meet their objectives.
    • In 2004, 66% of IT projects failed to meet their objectives.

    When asked to summarise why that (dismal) success rate had improved, the answer was: ""The primary reason is the projects have gotten a lot smaller. Doing projects with iterative processing as opposed to the waterfall method, which called for all project requirements to be defined up front, is a major step forward."

    In other words. Don't try to plan (predict) all possible future outcomes. Write what you know is needed, and get it out there. You will quickly find how what of what you have can be improved; and what more is actually required.

    Now a word about dogma:(that [which] is proclaimed as true without proof). Example: Expenditure of time on the "maintainability of source code" saves money in the long run.

    I offer not dogma, but demonstrably good advice.

    Read, digest, and recognise that a common theme emerges.

    The longer designs and code spend:

    • in the splendid isolation of architects and programmers minds;
    • in their cutsey, designed-to-test-what-was-expected-to-happen test suites;
    • running against sanatised test data in convenient test scenarios;

    then the longer it will be before you find out the truth of reality. And

    • the harder it will be to revert the wrong guesses and implement the realities.
    • the more budget will have been expended, and the more will be required to correct your mis-predictions.
    • the less likely you are to be given the opportunity to correct your mistakes.

    Think of it this way. Two men are taken to points in a field 100 yards from a flag that is their destination. They are blindfolded and spun on their heels a few times. One is given one long look to orient himself before setting off for the flag blindfolded. The other is allowed a single brief look around every 10 paces. Who makes it to the flag first?

    And if you cannot see the truth in that, then the blinkers of your own legend and ego are doing you and your employers a distinct disservice.

    Gambling may give you personal highs when you get it right, but when the overwhelming weight of evidence is that you will guess wrong, and when the effects of those wrong guesses on you and those around you are so dire, seeking those highs is pure selfishness.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Gambling may give you personal highs when you get it right, but when the overwhelming weight of evidence is that you will guess wrong.

      Of course! I guess wrong all of the time. I have no illusions about never guessing wrong. And I don't try to predict the future for some "personal high". I do it because it has a proven track record of working well (because I don't let it "run away").

      And the effects of those wrong guesses on you and those around you are so dire

      And that's where you completely missed the point at least as far as my approach and experience is concerned. The effects of my wrong guesses are usually... none at all.

      If I guess wrong (this time) about Ops (likely) wanting to be told how many calls were received in the last 5 minutes (which happens often enough, because Ops has some other way to get that information so they don't need my project to provide it for them). I lose nothing. My team loses nothing.

      We didn't implement "provide the count of calls over the last 5 minutes". We didn't spend a single minute implementing even part of that feature. We implemented exactly what we needed, just in a less fragile way that didn't even add any time to our schedule (but allowed for easily implementing a lot of other things that are likely to be useful).

      You present a lot of evidence that there are serious problems with spending too much time on design (and on designing too much at once). No kidding. But that doesn't prove that you can't also spend too little time on design. It takes very little experience to realize that zero design time is not a recipe for great success in programming.

      I never said a single thing about spending long periods of time designing. Quite the contrary. And, by being careful to limit time spent in several ways, the risk of guessing wrong is minimal while there are often time benefits even for wrong guesses. So, on average, even if I never guess right, we win.

      But I very frequently guess right. It isn't particularly hard. I know the fields I work in well. Even when I guess wrong, I'm usually still pretty close and have made good decisions about what aspects to emphasize or de-emphasize.

      If simply marching across a flat field to an obvious destination is the best analogy for the programming challenges you are given, then I don't want your job. My programming challenges are much more interesting than that. That isn't a good analogy for programming. It is a great analogy for a situation where planning does absolutely no good. And if stumbling while blindfolded is the best analogy for how you implement, then I don't want to work with you. ;)

      The biggest time sink in software implementation, in my experience, is time wasted going down what turn out to be dead ends. That is a big reason why "too little design" can be a serious problem when writing software. It takes planning to avoid dead ends.

      And, by far, the most common reason for projects coming close to failing, in my experience, is poor initial planning leading to developers feeling "behind schedule" such that they feel they don't have time to "waste" on improving planning and organization. In an attempt to catch up or just not fall further behind schedule, they concentrate on working harder/faster, staying more "focused" on the implementation work. Their tunnel vision becomes more pronounced and they enter what can end up being a very, very long phase of constantly thinking (and reporting) that they are "almost done". Which makes them look to their bosses (and maybe to themselves) like they suck at writing software.

      So I invest a fairly small amount of time up-front to accurately determine the complexity of the features being asked for and to fairly accurately nail down the landscape between the parts so that the risk of going down a dead-end is significantly reduced. And during implementation I don't just throw code at the revision control system, I track and refine a design for the component with particular emphasis to the interfaces being created and used in order to increase modularity.

      You can certainly have too much design or too much organization. I've seen plenty of that. You can end up with red tape and bureaucracy. So don't do that. In almost all of the places I have worked, there is plenty of pressure to not spend lots of time on design. And at least most of my coworkers seem keenly aware of the trap of over-design and of too much "process". So it isn't hard to avoid other than the smallest excursions into over-thinking at work. It is a continuous balancing act where we avoid having way too little design and avoid more than trivial amounts of over-design.

      Maybe you've worked with huge corporations or governments (or huge corporations working on government contracts)? A few times I've worked where we got absorbed by a huge corporation and I found I was increasingly impacted by an impressive build-up of bureaucracy and my efforts, even with the help of my managers, could not reduce red tape as fast as it leaked into my work area. I was quick to find new work.

      I don't recall ever having been involved in a massive project, much less one that failed to be finished because the initial design was just too much. I'm sure that happened a lot in 1994. It doesn't have anything to do with what I was talking about, though.

      Actually, the projects that end up being the most complicated and thus are the ones that most often get pushed off repeatedly as too risky are the projects where we are trying to rework an existing system so that it can handle new requirements. And my experience is that those situations are most common and most difficult when the developers of the system disregarded likely future concerns, the systems where they just threw code together and got it working.

      Even systems where the guesses about the future were wrong, they look far enough outside their current narrow view of the problem space such that they abstract things and the abstractions at least provide places to leverage changes even if those changes are part of completely replacing the system.

      - tye        

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://902232]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (4)
As of 2024-04-24 20:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found