ArticleS
.
MichaelFeathers
.
DesigningAwayPreconditions
Edit Page:
!title Designing Away Preconditions ‘Design by Contract’ is good, right? It’s supposed to be, but you always have to deal with that one question: what do you do when a precondition fails in production? In some systems you can throw an exception, or abort processing. But, what do you do when you’re committed? When failure is unacceptable? There are many answers to this question, but the one that I don’t hear often enough is that you can, in many cases, design away preconditions. Here’s a simplified example. Suppose that we need to do work using a date range. We can define a !-DateRange-! class and give it a constructor which takes two dates: the starting and ending dates of the interval: {{{ new DateRange(start, end); }}} That works, but it lets us fumble our arguments like this: {{{ new DateRange(end, start); }}} Sure, we can have a precondition that tells us that the start must be less than or equal to the end, but it’s still a shame that our interface allows the error. Can we make a better interface? We could have a !-DateRange-! constructor that accepts a starting date and an extent in days: {{{ new DateRange(start, 4); }}} That’s better, but we still have a precondition. The number of days should be positive. Is there any way around that? Well, we could create a date range at a particular date and “inflate” it afterward: {{{ DateRange vacation = new DateRange(startingDate); vacation.addWeek(); // adds a single week vacation.addDay(); // adds a single day }}} No more preconditions. Is the interface a little funky? In this case, definitely. But the point is we don’t have to take preconditions as given. They aren’t always inevitable. In some cases, we can systematically reduce them with a bit of refactoring. Are there preconditions you can remove by refactoring your interfaces? Is the interface change worth it? !* Wed, 6 Sep 2006 17:17:09, Anthony Bailey, More obvious solution for this example? I find the general point about designing away preconditions worthwhile - thanks. But the preconditions seem a little contrived for the examples you chose - can you not just remove them and use the obvious meanings? When date2 < date1, DateRange(date2, date1) can mean the same as DateRange(date1, date2); and DateRange(date, -n) means the same as DateRange(date-n, n). ''You can do that, but it likely masks an error. It comes down to what the programmer intended. I worked on an embedded system once which had intervals (they were not dates) we dealt with this issue by saying that when end < start the interval is empty. Again, it masked a programming error, but in the domain it worked. -- MichaelFeathers'' *! !* Wed, 6 Sep 2006 17:46:39, Przemyslaw Pokrywka, Great article I'm impressed - I have worked with one library that checks its preconditions very late at runtime (if ever). Sometimes it takes very long time before a bug gets detected and when it finally manifests itself it is hard to reproduce. Designing away preconditions can help avoid this, therefore I very appreciate your post. ''Thanks. I'm wondering what the limits are. When can we design away preconditions? When can't we? -- MichaelFeathers'' *! !* Wed, 6 Sep 2006 18:42:48, Dean Wampler, Do this for "null" checks, too We don't do this sort of thing enough in our business, IMHO. I'm looking at some legacy code that has tests for null all over the place. It clutters the logic and it adds more stuff to test (i.e., the "null handler"). I'm recommending to the client that we try to eliminate the possibility of null values, as much as is reasonable. In part, this will be an effort to prevent conditions that might lead to variables that can't be properly initialized. When this won't work, we'll try using the Null Object Pattern. ''Yup. Passing and returning null makes me livid. I rant about it over here: [[Offensive Coding][http://www.artima.com/weblogs/viewpost.jsp?thread=168511]] - MichaelFeathers'' *! !* Wed, 6 Sep 2006 23:29:43, ${ottinger}, I don't know. On one hand, it eliminates the issues of checking for the constructor. But it also makes the code a little less expressive. I'm not sure about the price. You have to do more math on it this way, but that's not a big deal. It's too early to deal with that. The object has an unfinished span of several lines. "Unfinished span" is the period in which it is "not done yet" in the client code. The longer it is in progress, the more you have to read to figure out what the final state of the variable is intended to be -- what the author has in mind. There are more intermediate steps, which makes it less obvious. Also, the cost is on the client side. I'd rather see us absorb extra cost on the class side than the client side. What one client has to do, most clients may have to do. The worse it gets, the worse it gets. I don't like to push my costs that way. But it is interesting that you did manage to eliminate the need for the precondition check. I just don't know that I like the result. ''I'm not sure either, but it was just an experiment. It seems like it would be fun to try this with a bunch of preconditons just to see what happens. - MichaelFeathers'' *! !* Thu, 7 Sep 2006 07:29:15, Matt McGill, Eliminated, or moved elsewhere? As Tim said, it appears that the object is in an 'unfinished' state until after the two 'add' calls. What should be the defined behavior for the following? {{{ DateRange vacation = new DateRange(startingDate); if (vacation.contains(someOtherDate)) System.out.println("Huzzah!"); }}} If, on the one hand, the vacation needs some further initialization after the constructor, then has the precondition not simply been moved to the first execution of contains()? If, on the other hand, the decision is made that a date range of length zero is valid (for which contains() would always return false), then I suppose the precondition has indeed been eliminated. Would this still have a tendency to mask programmer error, do you think? -Matt McGill ''When I've used intervals or ranges, it's usually been a convention that when start == end, the range is empty. I guess that if everyone knows that, it's fine. I agree about the multi-stage initialization.. it could mask some errors'' -- MichaelFeathers *! !* Thu, 7 Sep 2006 10:17:21, Ravi Venkataraman, Date Range is not a good example I feel that the constructor of DateRange must take two arguments, a starting date and an ending date. As others have pointed out, the "improvement" actually leaves the object in an improperly initialized state. This can lead to more severe errors than the ones that it was intended to rectify. I see no problem in the DateRange constructor having two dates, with the explicit understanding that end_Date > start_Date. It is much better to signal an error when the constructor is given an incorrect set of dates than to split the constructor and force a multi-step approach to create a usable object. The usual approach will tend to point out programming errors as soon as they occur. By the way, I am assuming that this one-arg constructor is in addition to the two-arg constructor. Also, I assume that one more method would be written to set the end date. Would you check the pre-condition when the end date is set using this method? Does this make the code and client code simpler? It does not seem so. ''In general, I agree, but I dealt with a situation many years ago in which we did a great deal of fretting about an interface like this. It was an embedded system and if someone put the arguments out of order, we had to figure out how to continue. Aborting wasn't an option. It's not a common situation, but it is interesting from a design perspective. - MichaelFeathers'' *! !* Thu, 7 Sep 2006 19:17:23, Rup, underlying point good, example bad The names 'start' and 'end' are actually horrible in this context as they imply the value, which gets to the crux of this problem. If I were writing this class, it wouldn't matter what order the dates were passed to the constructor, it would be better described as: new DateRange(oneDate, anotherDate); A range is still a range, going any direction in time. ''What if we change things around a bit. Imagine an integer interval class and an expression creates an instance: new !-IntegerInterval-!(a + b * c, d * e + f). If the second expression is smaller than the first, it's likely because we've goofed a calculation. -- MichaelFeathers'' *! !* Fri, 8 Sep 2006 00:20:46, Rup, actually.. no It's still an IntegerInterval, regardless of the sequential value of the two parameters. Why should the position of the elements in the signature matter as to the functionality of the method/class etc. ''It's the potential for having an undetected error. Here's an example. Let's say I do a calculation and I intend to get the interval (10, 20) but I get the interval (10, 5). And let's say that I'm using these intervals to determine ranges in an array that I'm going to process sequentially. If I accept (10, 5) as (5, 10) I could end up processing some of the elements prior to 10 twice rather than detecting an error. -- ${feathers}'' *! !* Fri, 8 Sep 2006 00:41:46, Rup, but... You would ask your IntegerInterval object to give you a primitive representation of it's 'elements' in the order you required in whatver context you were using them, not nessesarily determined by the sequential order of the elements in the signature. ''How would it know what I need? -- ${feathers}'' *! !* Fri, 8 Sep 2006 09:37:42, ${ottinger}, DateRange v. TimeInterval Okay, I can see the invariant for DateRange. For TimeInterval I wouldn't mind seeing it handle negatives. So I could define: {{{ TimeInterval aWeekBefore = TimeInterval( TimeInterval.DAYS, -7); }}} the longer I think about it (or rather the more times) the less I am against an exception. I would hate to add build a vacation this way: {{{ DateRange vacation = new DateRange(startingDate); vacation.addDay(); vacation.addDay(); vacation.addDay(); vacation.addDay(); }}} But if I allow one of these, we have the same problem: {{{ vacation.addDays(4); vacation.addDays(-4); }}} In fact, I seem to remember time manipulation in .NET (C#) working this way -- you add negative days to go backward in time. Now we have another point at which we have to test for negatives. Likewise if we have .subtractDay(). Mind you, I can see the point of the blog, and it is like UI design. If you can make it impossible to ''enter'' a bad value, then you have less validation otherwise. I think it's a sound principle. I think that it just gets sidetracked in the example of date ranges, and of course no example is perfect. They're not meant to be. The bad thing about exceptions is that they also put more work on the caller, and we all want our code to be easy to use. We shouldn't have to have five lines (or more) of setup in order to call a function. MFC calls often take five or ten lines to setup and teardown, and that's what makes MFC so incredibly awful to use directly (ie use ''wrappers''). So I agree in principle with any technique that means less work for the user. ''Yes, for the example I wonder if I should've just left it at moving toward a constructor which accepts a start date and a number of days. But, I wanted to push it to explain the general point. - ${feathers}'' *! !* Fri, 8 Sep 2006 13:18:58, George Dinwiddie, It's all about context and expressiveness "Yes, for the example I wonder if I should've just left it at moving toward a constructor which accepts a start date and a number of days. But, I wanted to push it to explain the general point." Context: The best way to handle it depends a lot on the context of your code, what things are acceptable, what are not, what conditions should be flagged as possible errors, what are benign. There's no good "one size fits all" answer. Expressiveness: The constructors "new DateRange(start, end)" and "new DateRange(start, daysLong)" express different ideas. Certainly I wouldn't want to see "new DateRange(start, end.subtract(start))" and I don't think I want to see "new DateRange(start, start.add(daysLong))". Again, context suggests what is appropriate. All in all, an interesting topic. ''Yes, I think it is too. Re context, there's old joke that goes.. an OO guru was visiting programming shop and the one of the programmers tells him "We've got an OO design expert in-house; he's in another department." The guru asks, "How do you know he's a expert?" The programmer replies, "Whenever we ask him a question he says 'It depends..'" The guru replies "Sounds you've got an expert." :-) - ${feathers}'' *! !* Sun, 10 Sep 2006 13:19:50, Sebastian Kübeck, Intuision over missing precondtions I'd rather opt for... DateRange vacation = new DateRange.daysFrom(startingDate, 4); ...beacuse it seems more intuitive to me than... DateRange vacation = new DateRange(start, 4); For me, "intuision" is most important in interface design. What's the most "natural" way, people would use it? I know that's not very scientifc but it works well for me. The preconition thought seems right to some extend but I would't make it a driving force since it seems a little oversimplified. One thing I dislike with your approach is that you make DateRange mutable where most people would assume it's immutable. That could lead to unpleasent "surprises" with some developers. *! !* Fri, 15 Sep 2006 11:04:48, Sebastian Kübeck, Errata Sorry, but my last post is full of typos... :-( 1. Of course, My suggestion should be... DateRange vacation = new DateRange.daysFrom(startingDate, 4); 2. Sure it's 'intuition' not 'intuision' and 'because' not 'beacuse'. *! !* Mon, 18 Sep 2006 18:09:13, keith ray, use a more "humane" interface - factory method instead of constructor jan10 = new Date( "jan 10, 2006" ); // hopefully some nice constructors exist jan20 = new Date( "jan 20, 2006"); DateRange earlyJan = jan10.rangeTo( jan20 ); or... DateRange earlyJan = new Date( "jan 10, 2006" ).rangeTo( new Date( "jan 20, 2006") ); (too bad Java has to have all those "new"s everywhere). Also, I would permit ranges in either order jan10.rangeTo( jan20 ) == jan20.rangeTo( jan10 ) I suppose Java won't let you add a "rangeTo" method to an existing Date class, but Ruby, Smalltalk, Objective-C will. *! !* Tue, 19 Sep 2006 18:17:32, ${ottinger}, I like it, but.. I like the syntax Date.rangeTo() but aren't we back to the problem of avoiding precondition exceptions? You could make the range backward or forward or sideward. If the precondition is that range[a..b) has a <= b then we're no closer to ensuring that with the syntax improvements. We end up throwing exceptions. Likewise, with Date.rangeOfDays(4) has us back to dealing with negative numbers. The original point of the post was that we could design in such a way that we don't have to deal with those problems. The original had that problem solved, but had other issues. I guess it just goes to show-- you make tradeoffs. That's why machines can't do this for us. *! !* Thu, 28 Sep 2006 14:18:04, , Nonsense What about DateRange( Date inital, Date ending ) constructing a valid or invalid date range and then having to check if the date isValid() or not? I know the Builder pattern should fix this, but I bet the Builder pattern couldn't be used in this case to solve the ambiguity... And by the way, Date.range( initial, ending ) != Date.range( ending, initial ), because if they were the same we should take into consideration a two way ticket to the moon that ships from Earth on Monday and returns the previous day. (Each flight may be served by different ships, so one trip would have no passenger). *! !* Sun, 15 Oct 2006 14:58:44, David, Design by Contract & Interfaces let's not make excuses for the idiot who violates the interface design or attempt to modify it at the risk of screwing up all the other packages or modules that use it correctly. Poor design is not a rationale for being stupid. *! !* Tue,, , 5 *! !* Thu, 26 Oct 2006 22:58:38, , The point of DBC is that you have hard failures and force bugs out in the open so that they get fixed. The fact that you are trying to hide bugs is concerns me. Its like you're afraid of hurting someone's feelings or that you'll get in trouble for finding problems.. or that you are simply too lazy to test and fix issues. You want a hard failure right away so problems are fixed during development. If you are really, really want to do this because its production runtime, then you should use asserts() for preconditions and turn them off production, and then have these dumb workarounds. In this case, I agree that the API should be improved to be a date range and not need to handle greater-than/less-than issues. Also, to Dean Wampler. You should look into the NullObject pattern. It can come in very handy for big objects that requires null checks all the time. *! !* Fri, 27 Oct 2006 06:45:22, Daniel T., Preconditions and "Tell, Don't Ask" I agree completely when it comes to preconditions that reference the recipient of the message; they fly in the face of "tell don't ask". However, I'm not so sure it's worth extending the concept to parameters the client knows more about than the server. In this case, the client knows everything there is to know about the two dates in question, before the server even comes into existence. Let the client deal with what it knows. *! !* Tue,, cheap cialis, 26 <a href="http://boxster.bloggeneration.fr/#levitra">cheap levitra</a> [url=http://boxster.bloggeneration.fr/#levitra]cheap levitra[/url] <a href="http://cayenne.bloggeneration.fr/#viagra">buy viagra cheap</a> [url=http://cayenne.bloggeneration.fr/#viagra]buy viagra cheap[/url] <a href="http://cayman.bloggeneration.fr/#cialis">cheap cialis</a> [url=http://cayman.bloggeneration.fr/#cialis]cheap cialis[/url] <a href="http://carrera.bloggeneration.fr/#viagra">cheap viagra</a> [url=http://carrera.bloggeneration.fr/#viagra]cheap viagra[/url] *!
Hints:
Use alt+s (Windows) or control+s (Mac OS X) to save your changes. Or, tab from the text area to the "Save" button!
Grab the lower-right corner of the text area to increase its size (works with some browsers).