Sunday, May 2, 2010

She's got a sweet cooling unit under that hood, it warms things better than any heater available!

In my last post, we ended with:

public class Foo {
   public List<Bar> Bars = new List<Bar>();
   public void MagicGetBarsMethod();
}
public class Bar {
   public void DoYourThing();
}
public class MyApp1 {
   public int Main(string[] args) {
      Foo foo = new Foo();
      foo.MagicGetBarsMethod();
      foreach(Bar bar in foo.Bars) {
         bar.DoYourThing();
      }
   }
}
public class MyApp2 {
   public int Main(string[] args) {
      Foo foo = new Foo();
      foo.MagicGetBarsMethod();
      if(foo.Bars.Count == 0) {
         throw new Exception("There were no Bars.");
      }
      foreach(Bar bar in foo.Bars) {
         bar.DoYourThing();
      }
   }
}

"But wait!" the highly-engaged (let's be clear, I have yet to earn a high level of engagement) among you are saying, "Foo is unclear!", and you are right! For everyone else (sometimes, I like to bully my readers through the use of literary devices), their objection is that the definition includes the method MagicGetBarsMethod, which presumably, based on its name is responsible for getting the appropriate Bar objects for the containing Foo object. However, it fails in communicating so many aspects of the behavior it contains (although they were probably obvious to the original writer in the context in which it was written). It makes obvious that it gets Bars from another place, but... Are the Bars it can retrieve always, in every case, a static collection? If not, upon what policy are the changes in the source collection applied to the Foo object's collection? Is it an actively synchronized collection such that the collection updates are propagated as a precondition of the final commitment of membership for the item being added? Is a notification sent after the final commitment of membership that the item has been added? Does the Foo object poll it's Bar source every N seconds to determine whether updates have occurred? Does the Foo object poll it's Bar source only when the consumer is interested in asking for the latest version of the collection (imagine for example that the collection is a set of tags, a specification of tab organization, data placed in specific tabs, and a description of how the tags should be displayed - a web page and your browser's refresh/reload button)? Are consumers of the Bar list allowed to add new or existing Bars into the list? If so, how is duplication handled? None of this is implied by the way that it presents itself to the world. This is a missed engineering opportunity... Worse, if I write it this way I am requiring everyone who uses the code to do unnecessary research and testing in order for them to be certain they are using my object correctly.
I am of course asserting a belief in the principles popularized (at least in my sequence of encounter) by the simple but no less elegant "The Design [previously, Psychology] of Everyday Things" (http://mitpress.mit.edu/catalog/item/default.asp?tid=5393&ttype=2) which usefully discusses things like which side of a door the handle to open it should be on relative to the hinges. To summarize, the design of an object should make it obvious beyond a doubt (intuitive, even) exactly how it works and how it is intended to be used. If these aren't the case, the engineer has not only failed but demanded that anyone who would or has to use his/her object should go through the pain and waste of time to guess at and verify what you were thinking and what you in fact did. Obviously, sometimes they can just read the code but this is not always an option and should never be a necessity.
Obviously I failed in the case of my Bar list. There is, of course (there always is...), a valid defense to this accusation: I didn't have an answer to the cluster of questions surrounding that collection at the time that I first wrote it. While this is the case, it still does not answer the question of why, if the retrieval of Bars is such a magical activity, the retrieval of Bars doesn't just happen magically and it is a failure of specification (whoever's responsibility it was to provide it). Consider the following modification:

public class Foo {
   private List<Bar> _Bars = new List<Bar>();
   private void MagicGetBarsMethod();
   public List<Bar> Bars {
      get {
         if(_Bars.Count == 0) { MagicGetBarsMethod(); }
         return _Bars;
      }
   }
}
public class MyApp1 {
   public int Main(string[] args) {
      Foo foo = new Foo();
      foreach(Bar bar in foo.Bars) {
         bar.DoYourThing();
      }
   }
}

So what's the big deal? In the absence of information relevant to Foo's retrieval of Bars, an artifact that implies some semantics (no matter how poorly) is inappropriate. By this change, the empty implication was removed and further, the use of the Foo object has been simplified. While I'm not claiming that it has been largely simplified, I have been able to eliminate a line of code from both of the "toy" applications and that means less time spent using my object, less need to understand how to correctly use my object, and fewer opportunities for bugs as a result of using my object. That one consideration for moving that one line of code pays for itself the first time someone uses my object and pays dividends on every use thereafter. It is an immediately profitable choice and if you make the other choice, you are (whether you mean to or not) explicitly deciding that all future development should be more costly across most of the vectors of resources.
Of course, the subtleties and realities of such an argument are often loss on the individual developer because it requires a little more up front work for them. The subtleties and realities are likewise often lost on the leadership of a software organization because they are rightfully and appropriately focussed on whether it is done and does what it is supposed to. Regardless, thousands of these decisions are made every day in an organization and they are made day after day after day. Over time, these decisions add up in a substantial manner and create vast differences in the productivity and effectiveness of development organizations. When it takes an hour or two to correct an off-by-one indexing error, that is the result of poor decisions, made over time, through the collaboration of all of the many interests of the organization. While it is quite accurate and important to note that these sensibilities I support are not the revenue drivers of an organization, that nor are they relevant to the users, that the long term reality is that they can have very important impacts on those very stakeholders and that making poor decisions at this level and across this aspect of our systems is an explicit decision to shorten the longevity of your product, to make your entire team less effective and productive, and to increase the negative pressures against your employment.

1 comment:

  1. A far more important method of the bars accessed through the MagicGetBarsMethod, is the LetMeDrinkInPeace(Liquor liqType) method

    ReplyDelete