The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

When is an exception an exception

Got into a discussion with my brother in law (who is also a programmer) last night about proper use of exception handling. I regard exceptions as situations that we do not expect to happen, not something that we expect could happen on regular basis. The example he gave me was a CokeBuyer class which have many clients calling its BuyCoke function. His argument is there are 3 possibilities we know:
1. u give the class enough money or more  to buy coke
2. u dont give the class money to buy coke
3. u give the class the money, but not enough

And he wants to capture case 2 and 3 by catching exceptions in the buycoke function and throwing it back at the caller. My argument is, those are not exceptions, those are freaking business logics. Exceptions to me in this case will be if the coke buyer got hit by a bus on his way to buy coke and does not return (network goes down, never hear from backend, timeout), or if the supplier that sells coke is burned down (database server down).

For something like 2 or 3 I will write a function in the cokebuyer class to check if the money is there and is enough and expose it to the callers/buyers, and insist they call that function before they even think of calling the buycoke class. Heck I'll even call that check function myself another time before I go just in case if they decide to skip it. Why should my coke buyer class waste all the time to go all the way to buy coke without checking those in the first place? Not to mention the fact that throwing exception can be quite expensive. He seems to think by catching and throwing the exceptions back to the caller he doesn't have to write codes to verify 2 and 3 anymore. Any opinions?
Wills
Wednesday, October 18, 2006
 
 
Perhaps you're both right:

* The back end should (or must) throw an exception if there's insufficient money. For example, if it's coded to return a coke instance by value, throwing an xception is its only way to return no coke.

* The front end should (or may) check for sufficient money before invoking the back end method (to avoid the need for the back-end to throw an exception).
Christopher Wells Send private email
Wednesday, October 18, 2006
 
 
If you where to design by contract then What Christopher said  is quite right.


A common example in similar vein is the bank teller, and transfering funds.

Do you withdraw the money before you work out if you can deposit the money in the other account?  If you did and you failed, what happens if you fail to re deposit the money in the original account?


What you need in your example is the CanBuyCoke method, and a way of finding out why not.

Bertrand Myer and Object-Oriented Software construction covers this nicely I think.


An exception in your example might be, failure to deliver the coke because the mechanics have become jammed.
Or trying to  buy coke even when you have been told and ignored the response from CanBuyCoke.
Gornin Send private email
Thursday, October 19, 2006
 
 
In Java, the best practice is to use a checked exception for cases when the calling code could be expected to recover, and to use a runtime exception for cases when recovery is impossible (usually these are programming errors).

For example:

* FileNotFoundException: (checked) prompt the user to give you a valid filename.

* ArrayIndexOutOfBoundsException: (runtime) the user doesn't have a prayer of recovering. Bork the process and log a bug.
BenjiSmith Send private email
Thursday, October 19, 2006
 
 
So best practice, in the above example:
1. Provide methods to check for the commonly occuring situations where they will get no coke (exceptions) and expose them to the callers. Encourage them to use it.
2. If they choose to ignore that and call buycoke regardless and dont have enough money, throw an exception back at the caller to let them know why they didn't get the coke.

In other words, we should really do both, but encourage the use the exception as last resort for checking rather than main. Correct?
Wills
Thursday, October 19, 2006
 
 
I would follow the design by contract example outlined above. 

You put a contract onto your "BuyCoke" command that specifies that calling "BuyCoke" is only valid when the "SufficientFunds" property is true.  Calling it at any other time is a violation of the contract and is signalled with an exception.  If pre-condition checking is disabled, then the behavior of the BuyCoke command is undefined.


This places responsibility for checking the "SufficientFunds" property onto the client of your coke machine model.  A well constructed client, being aware of its responsibilities, will either test the SufficientFunds property before all calls to BuyCoke, or otherwise guarantee that any call to BuyCoke only happens when the contract is valid.  Poorly constructed clients will exhibit failures due to the exception.
Another DbC fan Send private email
Thursday, October 19, 2006
 
 
I am not familiar with design by contract. Reading the entry on wikipedia.

"Using the DBC methodology, the program code itself must never try to verify the contract conditions; the whole idea is that code should "fail hard", with the contract verification being the safety net. (This stands in stark contrast to the defensive programming methodology.) DBC's "fail hard" property makes debugging for-contract behavior much easier because the intended behaviour of each routine is clearly specified.

The contract conditions should never be violated in program execution: thus, they can be either left in as debugging code, or removed from the code altogether for performance reasons."

I am bit confused, are they saying the whole class must never try to verify it? or simply not within that function itself it must not try to verify the contract for that function? If the whole class does not provide way to verify it, how then can the caller check if they have met the contract for a function at all?
Wills
Thursday, October 19, 2006
 
 
I mean.. with design by contract, it's entirely up to the caller to check everything is in order. But they can only check so much with the information they have. Eg with the above example they wouldn't be able to check if they have enough money without consulting the cokebuyer as they wouldn't know the price.

By the way, thanks for the input so far.
Wills
Thursday, October 19, 2006
 
 
Using Exceptions for normal predictable control of flow is a Really Bad Idea - all the cases you describe can be reasonably be expected to occur in normal usage so, almost by definition, they aren't Exceptions.

Of course, this rather dogmatic approach has to be combined with the fact that sometimes the "simplest" thing to do is to use an Exception to jump out of the depths of some particular bit of processing. However, this use of exceptions is pretty exceptional in itself ;-)
Arethuza Send private email
Thursday, October 19, 2006
 
 
In my long experience there are only two kinds of error - recoverable and irrecoverable. If it is recoverable then display an error message to the user and ask him what to do about it. If it is irrecoverable then abort and provide some sort of dump or trace so that the error can be investigated.

I don't use exceptions for recoverable errors (because they are *NOT* exceptions, goddammit!) and I can deal with irrecoverable errors without using exceptions at all.

Exceptions? I don't need no steenking exceptions.
Tony Marston Send private email
Thursday, October 19, 2006
 
 
Arethuza, that's what I normally think as well, but my brother-in-law and what I read on DBC are convincing me it's not that bad after all. While the exceptions are quite predictable and not really uncommon, it is an exception to the rule (that the function will return him a coke) and in the above example the caller will need to know why their coke is not there. Personally I prefer to use both a checking function and an exception to fall back on as I mentioned above. After my lengthy discussion with me I am starting to think of exception not only as a way to deal with errors like I used to, but to handle exceptions to the business rules too.

Tony, bear in mind that you are not the only user of your classes. What if a front end that is built by someone else (and is physically separated) is calling your function. Without exception how do you let them know what went wrong though, recoverable or not? So that they know what to do with it and tell their users?
Wills
Thursday, October 19, 2006
 
 
lengthy discussion with him ;)
Wills
Thursday, October 19, 2006
 
 
Wills, I think you're going down entirely the right route.
Mark Pearce Send private email
Thursday, October 19, 2006
 
 
Of course, there never are clear write/wrong answers about something like this but the only warning that I would raise is that overuse of *planned* Exception throwing and catching can lead to the same kinds of problems that led to goto statements being pretty well deprecated in most development environments.
Arethuza Send private email
Thursday, October 19, 2006
 
 
This except from Wikipedia seems to support what others have said, which is "do both"

---

Separating mechanism from policy

Condition handling moreover provides a separation of mechanism from policy. Restarts provide various possible mechanisms for recovering from error, but do not select which mechanism is appropriate in a given situation. That is the province of the condition handler, which (since it is located in higher-level code) has access to a broader view.

An example: Suppose there is a library function whose purpose is to parse a single syslog file entry. What should this function do if the entry is malformed? There is no one right answer, because the same library could be deployed in programs for many different purposes. In an interactive log-file browser, the right thing to do might be to return the entry unparsed, so the user can see it -- but in an automated log-summarizing program, the right thing to do might be to supply null values for the unreadable fields, but abort with an error if too many entries have been malformed.

That is to say, the question can only be answered in terms of the broader goals of the program, which are not known to the general-purpose library function. Nonetheless, exiting with an error message is only rarely the right answer. So instead of simply exiting with an error, the function may establish restarts offering various ways to continue -- for instance, to skip the log entry, to supply default or null values for the unreadable fields, to ask the user for the missing values, or to unwind the stack and abort processing with an error message. The restarts offered constitute the mechanisms available for recovering from error; the selection of restart by the condition handler supplies the policy.

---
http://en.wikipedia.org/wiki/Exceptions
Shane Harter Send private email
Thursday, October 19, 2006
 
 
Another rule of thumb:

Don't use exceptions to trap or indicate any event that occurs frequently.  What is "frequently"?  Well:

In most languages, actually taking an exception (e.g. throw and its consequences) is very time-consuming, perhaps taking a run time equivalent to hundreds of statements.  In contrast, simply trying (e.g. entering a "try" block) is cheap, often free.

So, if you figure the "exceptional" condition to happen maybe one time in ten, then your runtime performance will be dominated by that edge case if you use exceptions to indicate it.  On the other hand, events such as "network down" or "database file not found" are truly exceptional; they should happen with sufficiently low probability, and have such dire consequences, that exceptions are appropriate.
David Jones Send private email
Thursday, October 19, 2006
 
 
What do people think about mandatory error codes (http://www.ddj.com/dept/cpp/191601612) as a solution to this type of problem? I ran across this idea a couple weeks ago and have mixed feelings about it. On one hand it seems kind of cool, but on the other, it seems like it could be confusing and hard to debug what's happening; functionality seems to be somewhat hidden.

Basically it delays raising the exception until the caller is done with the result. If they don't do something with it, then an exception is raised. If they do, then an exception is not raised. In this example, it'd be the return result from BuyCoke which indicates success or failure and the reason for the failure. Normally the caller checks for failure and takes remedial action resulting in no exception. If they fail to check for failure an exception is raised.

So, can you have your cake and eat it too?
Harley Pebley Send private email
Thursday, October 19, 2006
 
 
By the way, sometimes I run the debugger with enabled "break into the debugger when any exception is thrown (no matter whether or not the exception will be caught)": when I'm doing this it's annoying if the code routinely breaks into the debugger because some method in there is routinely throwing an exception.
Christopher Wells Send private email
Thursday, October 19, 2006
 
 
"What if a front end that is built by someone else (and is physically separated) is calling your function."

This would be the key selling point for me. If I'm responsible for the front end, I would assume that the function call is valid (there are sufficient funds). In other words, I would not bother doing defensive programming and I would only throw exceptions on irrecoverable errors since the cost of doing otherwise is quite expensive, both in terms of compile time, run time and programmer productivity.

If I'm creating what is essentially a black box, then it is to my long term benefit to return meaningful, consistently formatted error messages to whomever uses the product, and if they expect exceptions, then I'll throw exceptions.
TheDavid
Thursday, October 19, 2006
 
 
"I mean.. with design by contract, it's entirely up to the caller to check everything is in order. But they can only check so much with the information they have. Eg with the above example they wouldn't be able to check if they have enough money without consulting the cokebuyer as they wouldn't know the price."

The idea with design by contract is that the contract states the conditions that must be necessary in order for it to be legal to call a routine.  To do this, the "supplier" exposes queries that allow clients to test the contract prior to making the call.  So, for example, if the contract said that in order to call BuyCoke(), the query SufficientFundsDeposited must be true, then the supplier would have to make the SufficientFundsDeposited query available to the client.

Some well formed client code might then look like this:

if(cokeMachine.SufficientFundsDeposited) {
  cokeMachine.BuyCoke()
}
else {
  // Inform the user that he hasn't deposited enough money.
}

The usual mechanism with DbC is to enable contract checking (i.e. force each supplier to check the contract of each of its routines) while you are developing. This way, an exception will be generated during testing any time a contract is violated, and you can go and fix the code so that it honors the contract.

Once the code moves to production, it is customary to disable some or all of the contract checking so that we don't have to eat the associated overhead.  The idea is that as a result of rigorous testing of the code, we've worked out any potential circumstances where a contract may be violated, so we assume that contracts are always honored by the time the code makes it to production.

Usually, what happens is that pre-condition monitoring is left in the enabled state in production, but other kinds of checks are turned off.  Pre-conditions are usually relatively inexpensive to check and this offers a reasonable compromise between performance and correctness.  In this case, exceptions will be thrown any time a precondition is violated, which should be very rare.
Another DbC fan
Thursday, October 19, 2006
 
 
Re: exceptions are time-consuming

As with all performance-related advice, make sure this actually applies to your situation.  I just did a little test with .NET 1.1 and found I could throw and catch an exception in about 10 microseconds.  If I'm doing that a few million times, it's a big issue.  If I'm throwing one exception to get an error message to my user interface, it's nothing.
Mike S. Send private email
Thursday, October 19, 2006
 
 
The big problem with this discussion is that you are talking about a complex process requiring multiple calls as if it were a single thing. You can't just "buy a coke". You need to look up a price, verify the amount tendered, dispense the product, and so on. Each of these steps should or shouldn't throw exceptions themselves based on certain criteria.

You can't take a complex, orchestrated process and treat it like a single function call solely for the purposes of a discussion on exceptions. It simply isn't valid.
anon
Thursday, October 19, 2006
 
 
Re:

if(cokeMachine.SufficientFundsDeposited) {
  cokeMachine.BuyCoke()
}
else {
  // Inform the user that he hasn't deposited enough money.
}

The problem in the real world is that there may be many conditions to be validated and therefore the validation method needs to return both a boolean and a message to display to the user.

Method 1:

string message = cokemachine.ValidateFunds();
if (message.Length == 0) { // yuck?!?!
  cokeMachine.BuyCoke();
}
else {
  // display message to user
}

Method 2:

try {
  cokeMachine.ValidateFunds();
  cokeMachine.BuyCoke();
}
catch (Exception e) {
  // display e.Message to user
}

Method 3:

Result r = cokeMachine.ValidateFunds();
if (r.IsOK) {
  cokeMachine.BuyCoke();
}
else {
  // display r.Message to user
}

Personally, I don't like method 1, using an empty message to signal success.  I've often used method 2 - although many people discourage exceptions for invalid user input, I've never really seen the harm in it.  Probably something like method 3 is best alothough I'm just playing it now and don't know how it might work out in the long run.
Mike S. Send private email
Thursday, October 19, 2006
 
 
A variation on Method 3 might be:

Result r = cokeMachine.BuyCoke();
if (!r.IsOK) {
  // display r.Message to user
}
Harley Pebley Send private email
Thursday, October 19, 2006
 
 
I generally like Mike's method three, but there is a potential problem. You're assuming that subsequent method calls do not alter the "in spirit" status of the validation check.

As anon said, it's fairly rare that you can check one conditional, execute one method call, and pass an exception variable all the way back up the stack. "Real World" code is often far messier and complex than that.

Ergo, I think an exception should be limited to problems that the current scope of the code honestly doesn't know how to deal with, or cannot deal with. An example would be an input stream that is aborted midway through - it's not practical to poll it every few milliseconds to see if it's still valid.
TheDavid
Thursday, October 19, 2006
 
 
>> I just did a little test with .NET 1.1 and found I could throw and catch an exception in about 10 microseconds. <<

Mike, you're correct in saying that you need to measure first. But have you read these 2 blog entries?
http://blogs.msdn.com/ricom/archive/2006/09/14/754661.aspx
http://blogs.msdn.com/ricom/archive/2006/09/25/771142.aspx
Mark Pearce Send private email
Thursday, October 19, 2006
 
 
Yes, I have read Rico.  He's spot on that you have to measure, and to understand what you've measured.  Too many people don't even take the first step.
Mike S. Send private email
Thursday, October 19, 2006
 
 
"The problem in the real world is that there may be many conditions to be validated and therefore the validation method needs to return both a boolean and a message to display to the user."

Couldn't you get a better design by allowing the coke machine to maintain a separate query describing this situation?  Like this:

if(cokeMachine.CanDispense) {
    cokeMachine.Dispense();
}
else {
    updateUser(cokeMachine.StatusMessage);
}

The CanDispense property is maintained by the coke machine as it cycles through its states - true when enough money has been deposited and the user has chosen a selection that's in stock, for example, or false otherwise.

The machine also maintains a status message as it goes through its state machine so that the current message gives some kind of human readable detail about what's going on.
Franklin Send private email
Thursday, October 19, 2006
 
 
Yes, that works but I prefer to avoid those kind of state dependencies, e.g. machine.Message is only valid after you've called machine.CanDispense
Mike S. Send private email
Thursday, October 19, 2006
 
 
OK, I read your post a little more closely and I think I like it.

So machine.Message could be valid always - initially it could return "no money deposited", after machine.deposit(25) it could return "please deposit 75c more", etc.
Mike S. Send private email
Thursday, October 19, 2006
 
 
That's right.
Franklin Send private email
Thursday, October 19, 2006
 
 
Franklin, I thought about that as well I have implemented that kind of design and use components that act this way.

The only arguments my brother in law came back with when I brought that up were:
1. That's so 1990s (not really an argument ;).
2. Standard, look at the objects in big frameworks like java or .net. They don't give you a status (or similar) property on their objects when things don't go according to plan, they throw you an exception. Programmers who use your objects expect that kind of behaviour. While it's true, it's up to preference really, I personally am happy to use both classes that throw exceptions or provide a status property as long as they are properly documented and I know what to expect. Then I threw back the argument that those common objects often provide a way for user to check first for common exceptions (ie file.exists(), I would always use that before reading a file rather than wait for an exception to catch that).

The other drawback I can think of status property is that unless it records the last updated status, which may not necessarily be the cause. Again this is open to argument as this depends on the competency of the programmer who wrote the function.
Wills
Thursday, October 19, 2006
 
 
Anon, you are quite right.
I guess there are a few ways of looking at it:
1. Cokebuyer hides the detail of all those from caller, those could be internal function within cokebuyer himself. You walk into cokebuyer store, give him $2 and say hey give me a coke. He does all those things and either give you a coke (and some changes) or come back and tell you why you can't get a coke.
2. Cokebuyer exposes them to you. You walk into the store, ask him do you have a coke, he says yes then you ask him how much is a coke? he says $1.50 you check your pocket if you have enough money before you say hey give me a coke. If a customer decides to act like in case 1 and skip the questions, this cokebuyer can respond like case 1 too.
3. Or Franklin suggestion, you can check, but rather than cokebuyer getting back directly with you what went wrong. He remembers what happen when you last ask him a question or ask him to do something and then you have to ask him 'what exactly happened last?' and he tells you.

I personally would prefer to buy coke from cokebuyer #2.

btw, above post should read "The other drawback I can think of status property is that it records the last updated status".
Wills
Thursday, October 19, 2006
 
 
Mike, Why would you call validatefunds on method2 if you are going to call buycoke regardless of the outcome of that?

I like Method 3 best too. But if the return of checking is complicated enough to warrant its own set of flags and messages, I am leaning more on breaking it up as individual check that return boolean and leave it up to the user to do what they have to do rather than them having to understand my return message.

Harley, buycoke returns a coke, not a check result, and I wouldn't want to attach flag like isOk and an error message on coke itself, doesn't sound right.
Wills
Thursday, October 19, 2006
 
 
Hi Wills,

You said: "They don't give you a status (or similar) property on their objects when things don't go according to plan, they throw you an exception."

Perhaps I wasn't clear.  The "DispenseCoke" method would indeed throw an exception if it was called in a state where "CanDispense" was false because (as mentioned in an earlier message) the contract on DispenseCoke specifies that it's only valid to call it when CanDispense is true.

What I'm saying it that in order to allow the client to abide by the contract, you have to expose the CanDispense property.  This way, the client can decide whether he wants to check the property proactively, and respond, or just make the call and handle the exception. 

Kind of like what you were saying about File.exists business above.  Most often you want to test the exists property, but if you design things so that the file always does exist, then a missing file is, indeed, an exceptional situation.  Your supplier class is designed to handle both situations. 

Imagine how aggravating it would be if the File class didn't have that exists property.  You'd have to protect every File access with an exception handler.  Now, imagine instead that the exists method is there, but that the File access methods didn't throw any exceptions. Instead it just fails silently.  Also not too cool.

As an example of poor design, consider the Collection class provided in Visual Basic 6.  The class provides a Remove method that raises an error if the element being removed isn't actually in the collection.  The problem is that the class doesn't provide any way to test whether the element is there before calling Remove.  So, client code either uses the error processing for flow control or else implements its own test by looping through the elements.  A mess either way.
Franklin Send private email
Thursday, October 19, 2006
 
 
Wills: "The other drawback I can think of status property is that unless it records the last updated status, which may not necessarily be the cause. Again this is open to argument as this depends on the competency of the programmer who wrote the function."

It might help to shift the thinking away from thinking about the stuff as individual functions and instead look at it as a coherent object.  One approach to OO Design treats each object like a little machine.  There are commands that you can use to manipulate the machine causing it to change state, and there are queries which allow you to observe indicators of the object's state. Each property or method is one or the other, not both.

So, the coke machine might have commands like:

DepositMoney(amountToDeposit)
DispenseSelection
SelectItem(ItemNumber)
RefundDeposit

and queries like

CanDispense
StatusMessage
TotalDeposit

and maybe

Cost
AmountNeededForVend

if the machine supported a single cost for all inventory items.

Thinking about it this way, you can see that the status messages are a function of the sequence of commands issued to the machine.  As commands are received at the machine, the individual queries are updated to reflect the current state.  As long as the state doesn't change, the values of the queries are durable.

See http://en.wikipedia.org/wiki/Command-Query_Separation

Note: this approach does have its downside, but it's a useful model to help think about the question you posed.
Franklin Send private email
Thursday, October 19, 2006
 
 
> buycoke returns a coke, not a check result,

In the original "Method 3" I modified, BuyCoke didn't return anything. I just made it return a generic result object.

> and I wouldn't want to attach flag like isOk and an error message on coke itself, doesn't sound right.

Completely agree!

I tend to like the following pattern:

  if cokeMachine.CanBuy(WhyNot) then
    coke = cokeMachine.BuyCoke
  else
    // Display WhyNot to user

Where the WhyNot parameter in CanBuy is an output parameter (something I tend not to like) indicating the reason the user can't call BuyCoke. It eliminates the concurrency problems with putting the status message in a separate property but is more readable (IMO) than something like:

  status = cokeMachine.CanBuy;
  if status.IsOK then
    coke = cokeMachine.BuyCoke
  else
    // Display status.FailureReason to user

Of course, as stated by others, BuyCoke would also raise an exception if it was called when CanBuy returns false.
Harley Pebley Send private email
Thursday, October 19, 2006
 
 
Now I am with you. Good point and it is certainly applicable in cases where the question is complex enough that a simple yes or no from the function won't do and/or you want to have a standard status message for your classes at different state.

I would still prefer to split complex checking up (when possible) so the users have access to a number of small simple methods to negate the needs to return both a boolean and a message to display to the user.

like
if (cokeMachine.HasCokeLeft()) {
  if (myMoney >= cokeMachine.CokePrice) {
      cokeMachine.AddMoney(myMoney);
      cokeMachine.Dispense();
  }
  else {
      //borrow money from my mates
  }
}

rather than
if(cokeMachine.CanDispense) {
    cokeMachine.Dispense();
}
else {
    switch (cokeMachine.StatusMessage)
    {
          case cokeMachine.Messages.NoCokeLeft:
          //get other drink
          case cokeMachine.Messages.NotEnoughMoney:
      //borrow money from my mates

          .....
    }
}

Or you could do both and break up the checking and still has status or similar property (to tell things like how much more they need to put before the machine can dispense their chosen drink). At this stage it looks more like it's up to personal preferences than any major advantages/disadvantages.
Wills
Thursday, October 19, 2006
 
 
Franklin, posted last one before I saw your other post regarding Command-Query_Separation. It looks like we are talking about the same thing after all.

"methods should return a value only if they are referentially transparent and hence possess no side effects."

I was trying to solve the same problem by making the checks small and straightforward thus they are clearly queries, not commands.

Queries
HasCokeLeft() or HasStockLeft(Coke)
CokePrice or GetPrice(Coke)

Commands
AddMoney(myMoney)
Dispense()

Plus a lot of other that I obviously didn't include in my simplified example.
Wills
Thursday, October 19, 2006
 
 
Sorry misunderstanding again. The splitting up checkings have nothing to do with the Command-Query_Separation, you could have a gigantic complex query that return both boolean and message, and I still wouldn't like that.

And my initial buycoke (rather than dispense as in the machine example) will return a coke while potentially changing other aspects of the object. With this command-query_separation and machine example, you would do
myDrink = cokeMachine.DispensedDrink after you call cokeMachine.Dispense()
Interesting approach...
Wills
Thursday, October 19, 2006
 
 
The general problem with an approach like

if (cokeMachine.HasCokeLeft()) {
  if (myMoney >= cokeMachine.CokePrice) {
      cokeMachine.AddMoney(myMoney);
      cokeMachine.Dispense();
  }
}

is that all of that logic (basically the contract) is externalized, not encapsulated in the object responsible for enforcing the contract.

So, if you have many clients of this object, you may need to capture identical logic in multiple places, and you need to maintain those as the code evolves (for example, if we decided to support tiered pricing on the products dispensed by the machine).
Franklin Send private email
Friday, October 20, 2006
 
 
That's true (logic in multiple places/clients, though different clients may react differently and have different logics). But even if you use status like that, wouldn't it be up to the caller to look at the status and then determine the course of action when machine can't dispense yet? This particular piece of logic belongs to the caller, not the machine. All the machine does is display the status, it's still up the caller to decide.

Correct me if I am wrong, I thought with DBC it's the obligation of the client to guarantee the precondition?

The example was obviously simplified, if the machine supports tiered pricing then the code will be a bit more like

if (cokeMachine.HasCokeLeft()) {
  cokeMachine.SelectedDrink = coke;
  cokeMachine.SelectedQuantity = 3; //or whatever
  if (myMoney >= cokeMachine.AmountNeededForVend()) {
      cokeMachine.AddMoney(myMoney);
      cokeMachine.Dispense();
  }
}

The logic for the tiered price will still be encapsulated in cokeMachine.AmountNeededForVend() or similar function since that is part of the machine responsibility.

Apology for my many lengthy posts, I am new to this concept and keen to hear what other seasoned pros think.
Wills
Friday, October 20, 2006
 
 
A lot of this stuff is subjective and/or situation dependent, so concrete designs depend on what it is you're trying to accomplish.  If you expect your clients will need a lot of flexibility then, of course, it's appropriate to provide it.  As the designer, you have to figure out where behavior belongs.

"But even if you use status like that, wouldn't it be up to the caller to look at the status and then determine the course of action when machine can't dispense yet?"

The interpretation of the various queries is definitely application dependent and belongs in the client.

"Correct me if I am wrong, I thought with DBC it's the obligation of the client to guarantee the precondition?"

This is correct.

if (cokeMachine.HasCokeLeft()) {
  cokeMachine.SelectedDrink = coke;
  cokeMachine.SelectedQuantity = 3; //or whatever
  if (myMoney >= cokeMachine.AmountNeededForVend()) {
      cokeMachine.AddMoney(myMoney);
      cokeMachine.Dispense();
  }
}

There's nothing wrong with this style of code.  The point is, is it all necessary?  If you know that Dispense can never be legally called by any client unless HasCokeLeft() is true, SelectedDrink has been set, SelectedQuantity has been set, etc, then why force every client to check each condition independently?  Why not just provide the IsReadyToVend property and encapsulate all of that in one place? (And consider what happens when you have multiple subclasses with different IsReadyToVend policies, or when you need to modify the policy, or when the client wants to update the status after every operation on the machine)

If you need to know the specific reason(s) that the machine is not ready to vend, why not let the machine itself maintain and supply a status code or message or whatever is necessary that can be queried and interpretted by the client?

if(machine.IsReadyToVend) {
  machine.Vend();
}
else {
  switch(machine.StatusCode) {
    ...
  }  // or any number of other approaches to decoding.

}

I guess I don't see what the motive is for wanting the individual tests in the client.  What's your thinking on it?
Franklin Send private email
Friday, October 20, 2006
 
 
I prefer a vending machine that tells me whether it has coke when before I press the coke button (we do this either visually or some vending machine has light on on the available items). If it's one where I can order multiple, I prefer one that tells me how many there are left before I enter the quantity. And one that shows me how much money I need to put in before I put in the money and get the drinks. And obviously give me a way or knowing something is not right if I insist on selecting an out-of-stock drink or too much quantity.

In your case, a lazy user can go

cokeMachine.SelectedDrink = coke;
cokeMachine.SelectedQuantity = 3;
cokeMachine.AddMoney(myMoney);
if(machine.IsReadyToVend) {
  machine.Vend();
else {
  switch(machine.StatusCode) {
    ...
  }  // bugger I should have looked at the status to see there is no coke left before I waste all those time putting the coin in!
}

or a more careful client (my kind of client) may take this approach and keep his eye on the status (ignoring the alternate sequence of them actually selecting something else or less quantity).

cokeMachine.SelectedDrink = coke;
if (!machine.IsReadyToVend) {
  if (cokeMachine.StatusCode != RUNOUT) {
    cokeMachine.SelectedQuantity = 3;
    if(!machine.IsReadyToVend) {
      if (cokeMachine.StatusCode != NOTENOUGHSTOCK) {
        cokeMachine.AddMoney(myMoney);
        if (machine.IsReadyToVend) {
          cokeMachine.Dispense();
        }
      }
    } 
  }
}

Well I suppose with your way the machine can cater for both users, make life easier for the lazy users they only have to check for one thing at the end. But they can be careful if they really want to (or need the flexibility), they just need to understand your status clearly. Whereas mine they have to do a few checks or take an exception, no blanket check and status message for them. In term of DBC my precondition will be the stockexist before they select and the quantity is enough before they set whereas you only place a precondition contract on Dispense.
Wills
Friday, October 20, 2006
 
 
I think I understand what you're after now.  I probably didn't make things clear because I wanted to keep the "sample code" simple.

Remember that the machine can offer many queries and that the values of the queries are always up to date with the state of the machine.  So, we could certainly offer queries like

StockLevel(ItemID)

to always return the number of items in stock of type ItemID.  No matter what sequence of operations the client performs, this query will always allow the client to display to the user the number of items available. 

We could offer similar queries about other elements of the machine's state like "How much money has been deposited so far" or "How much money needs to be deposited before the machine can dispense a product" or anything else we need to advertise to the user.

The little snippet of code I put above is independent of all of that stuff - the client just pulls that information out whenever it needs to - potentially after every operation.  The code I have above is basically just the client's event handler when the user presses the "Vend" button.  It checks that the machine is capable of performing the vending operation (IsReadyToVend) and then either asks the machine to dispense a product, or reports back to the user that a vend is not possible and potentially why based on interrogating the machine's status.

If you think about building a control panel to act as this machine's client you would probably have indicators next to each product selection to show the inventory remaining for that product.  The indicator would just be set up as an observer [1] on one of the machine's StockLevel queries.  Similarly, you might have a display to show the user how much money has so far been deposited.  That display would be set up as an Observer on the machine's AmountDeposited query.  Maybe the vend button next to each product lights up when enough money has been deposited.  Each of these would Observe a query like CanVend(ItemID).  And so on.  None of that stuff has to happen when the user actually presses the Vend button. 

[1] http://en.wikipedia.org/wiki/Observer_pattern
Franklin
Friday, October 20, 2006
 
 
I think that what the OP has addressed in this post is a PERFECT example of how the flexibility of programming can accommodate multiple ways of thinking.

I think that either approach is appropriate depending on what makes SENSE to the developer crafting the code. One way to determine which approach to use is to opt for what is the MOST READABLE. However, having said that, I would also qualify the remark by suggesting that the most appropriate way to code the CANBUYCOKE code would be to determine the total number of lines of code necessary to code the CANBUYCOKE in all of its coding manifestations such as appropriate methods within a class, as independent functions in a module, and as error handling protocols and see which approach takes the least number of lines of code. The result will provide the REFACTORED way of crafting this.

For me, without knowing which way supports the least LOC, I would be inclined to use error handling to address the extenuating circumstances involved with the BUYCOKE example. If I were error handling multiple instances of the same code, I would then push to develop a FUNCTION that would be used within the error handler.
Brice Richard Send private email
Monday, October 23, 2006
 
 
I would agree with some of the earlier posters here.

I would throw a checked exception if the payment is incorrect (eg PaymentException), and a runtime exception if there is a network error or something else not related to business logic (eg MachineBrokenException).

I would also have a method, as suggested previously, to check if the payment is enough. This way we can check payment amount at anytime before trying to pay, so for instance we could keep checking the current coinage to see if it's up to the required amount and display this to the user.

The whole discussion on CPU cycles and efficiency with regard to exception handling is a load of crock. Machines are so fast these days that it makes no noticeable difference to the speed of execution. If you are going to make 10k calls to this method it would be another matter, but then you could use the 'isPaymentEnough' method before making the actual payment call and have a reasonable expectation that it wouldn't throw an exception, thereby avoiding the performance issue.

Checking return codes is stupid, it takes far more effort to code, is not as flexible, and one can't add extra diagnostic information to the cause of the error (including stacktraces for debugging). Using exceptions you can far more easily add more descriptive/different errors over time without having to modify the interface (eg instead of a 'MachineBrokenException' you could be more specific and throw 'CoinJammedException', 'CoinTrayFullException' which subclass MachineBrokenException (or better yet just have error codes on the MachineBrokenException so you don't have a gizillion subclasses for every error)).

One needs a good exception handling system to handle i18n and issue tracking (every exception has a unique id, and a key to use to lookup a langauge file to display the error in a particular language) when using exception though, but this is reasonably straightforward. Return Error codes? Yuck!
Bert van Brakel Send private email
Thursday, October 26, 2006
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz