Question on exceptions

Hello! I have a simple bank program where I have to have an exception
prompt on screen when someone tries to make a negative withdrawal and
also when there are insufficient funds in the account to be withdrawn.
This is what I have so far. Its in 2 separate files on my comp. One is
the “driver” for the program whereas the other contains all the classes.

DRIVER PROGRAM CODE

$:.unshift File.dirname( FILE )
require( “BankAccount” )

accounts = []

choice = -1

until choice == 5
puts “1. Open Account”
puts “2. View Accounts”
puts “3. Make Deposit”
puts “4. Make Withdraw”
puts “5. Exit”

print "Choose: "
choice = gets.to_i

case choice
  when 1 ;

if accounts.size < 6
print "Under what name? "
name = gets.chomp
accounts.push( BankAccount.new name )
puts “Account created”
else
puts “You already have the maximum number of accounts”
end
when 2 ;
accounts.each_with_index {|a, i| puts “#{i}. #{a}”}
when 3 ;
begin
print "How much to deposit? "
amount = gets.to_i
accounts.each_with_index {|a, i| puts “#{i}. #{a}”}
print "Deposit to which account? "
accounts[ gets.to_i ].deposit( amount )
rescue NegativeAmountError => e
puts e
end
when 4 ;
begin
print "How much to withdraw? "
amount = gets.to_i
accounts.each_with_index {|a, i| puts “#{i}. #{a}”}
print "Withdraw from which account? "
accounts[ gets.to_i ].withdraw( amount )
rescue InsufficientFundsError => e
puts e
end
end
end

CLASSES PROGRAM CODE

class BankAccount
@@id = 0

def initialize( name )
@id = @@id = (@@id + 1)
@name = name
@balance = 0
end

def deposit( amount )
@balance += amount if amount > 0
end

def withdraw( amount )
@balance -= amount if amount > 0 and amount <= @balance
end

def to_s
“Account #@id - Balance: #@balance (#@name)”
end
end

class InsufficientFundsError
def initialize( id, balance, amount )

end

def to_s
“Account #{@id} has insufficient funds ($#{@balance}) to allow the
withdrawal of $#{amount}”
end
end

class NegativeAmountError
def initialize( action, id )

end

def to_s
“You cannot [action] a negative amount for account [id]”
end
end

Like I said above, the two operations that I want this program to do
are:

  1. When making a withdrawal, it cant withdraw if there are insufficient
    funds thus prompting the InsufficientFundsError

  2. You cant make a negative withdrawal thus prompting the
    NegativeAmountError

Any words of wisdom would be greatly appreciated!

Justin Gamble wrote in post #1084633:

Like I said above, the two operations that I want this program to do
are:

  1. When making a withdrawal, it cant withdraw if there are insufficient
    funds thus prompting the InsufficientFundsError

To raise an exception when the condition is met:

raise InsufficientFundsError.new(1234,50,100)

However you will need to change your class so that it is a subclass of
an existing exception, e.g.

class InsufficientFundsError < RuntimeError

end

and you will need to make the other corrections to these classes which
should become obvious when you run and debug the program.

What is the reason of doing the .new(…)in

raise InsufficientFundsError.new (1234, 50, 100)

?

Justin Gamble wrote in post #1084635:

What is the reason of doing the .new(…)in

raise InsufficientFundsError.new (1234, 50, 100)

?

class MyException < Exception
def exception
return ZeroDivisionError.new
end
end

begin
raise MyException
rescue MyException => e
p e
end

begin
raise MyException.new
rescue ZeroDivisionError => e
p e
end

–output:–
#<MyException: MyException>
#<ZeroDivisionError: ZeroDivisionError>

Justin Gamble wrote in post #1084635:

What is the reason of doing the .new(…)in

raise InsufficientFundsError.new (1234, 50, 100)

?

If the exception doesn’t take any arguments, you can just do

raise InsufficientFundsError

If the exception takes only one argument you can do

raise InsufficientFundsError, 123

But if the initialize method takes multiple arguments, as far as I know
you must create an instance of the exception class explicitly. (Tested
with ruby 1.8)

On Fri, Nov 16, 2012 at 9:27 AM, tamouse mailing lists
[email protected] wrote:

Is this a good way to use exceptions? Having an insufficent balance
might be something the user finds exceptional, but I’d think it would
be a standard sort of handling for a bank applicaion…

Depends on how you implement it: if you have the option to query the
current balance and define that a withraw operation is only allowed
with sufficient funds then you throw an exception. If OTOH you define
that withdraw will return success then you have a boolean return
value. Both approaches are legal and feasible.

Kind regards

robert

On Fri, Nov 16, 2012 at 1:43 AM, Brian C. [email protected]
wrote:

Is this a good way to use exceptions? Having an insufficent balance
might be something the user finds exceptional, but I’d think it would
be a standard sort of handling for a bank applicaion…

Robert K. wrote in post #1084722:

On Fri, Nov 16, 2012 at 9:27 AM, tamouse mailing lists
[email protected] wrote:

Is this a good way to use exceptions? Having an insufficent balance
might be something the user finds exceptional, but I’d think it would
be a standard sort of handling for a bank applicaion…

Depends on how you implement it: if you have the option to query the
current balance and define that a withraw operation is only allowed
with sufficient funds then you throw an exception. If OTOH you define
that withdraw will return success then you have a boolean return
value. Both approaches are legal and feasible.

But I’d suggest not the current implementation:

def withdraw( amount )
@balance -= amount if amount > 0 and amount <= @balance
end

There is no indication whether the withdrawal took place or not!

On Fri, Nov 16, 2012 at 6:27 AM, Robert K.
[email protected] wrote:

On Fri, Nov 16, 2012 at 9:27 AM, tamouse mailing lists
[email protected] wrote:

Is this a good way to use exceptions? Having an insufficent balance
might be something the user finds exceptional, but I’d think it would
be a standard sort of handling for a bank applicaion…

Depends on how you implement it:

I know the right answer to every question is “It depends” :slight_smile:

if you have the option to query the
current balance and define that a withraw operation is only allowed
with sufficient funds then you throw an exception. If OTOH you define
that withdraw will return success then you have a boolean return
value. Both approaches are legal and feasible.

I guess I’m looking for some kind of guidance to generalize this.

My understanding is that one only uses exceptions for things which the
app finds exceptional, meaning things which it cannot or should not be
dealing with, which in some cases may depend on the business practices
and policies one is implementing.

Recognizing this was a class assignment for the OP, the situation is
highly contrived, and the lesson might be how to use and handle
exceptions, so great.

Do you (or anyone else who’d like to chime in!) have a … set of
heuristics, maybe? … that help you know when it’s appropriate and
when it’s not?

The reason I ask is not merely academic. I see in code many places
where it is raising and exception, and then the rescue code does
something like this:

rescue Exception

blah blah

end

In other words, they know an exception might be raised, but they trap
evey possible exception, rather than a specific one. I’m worried (?)
(thinking) that use of raising exceptions might be a little too easily
decided upon as the answer, and if I should maybe push back on such
implementations…

Kind regards

robert


remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Thanks, so much, Robert. :slight_smile: And I am going to check out your web site
right now.

Subject: Re: Question on exceptions
Date: Sat 17 Nov 12 12:47:06PM +0900

Quoting tamouse mailing lists ([email protected]):

blah blah

end

In other words, they know an exception might be raised, but they trap
evey possible exception, rather than a specific one. I’m worried (?)
(thinking) that use of raising exceptions might be a little too easily
decided upon as the answer, and if I should maybe push back on such
implementations…

I will try to describe in words the fuzzy logic I follow.

I do not often use exceptions. In more than seven years of using Ruby,
I have probably only subclassed Exception once or twice. I generally
capture an exception (thrown by my code or by other libraries) like
this:

rescue => err
print_a_message_on_the_log(err)
maybe_print_backtrace(err.backtrace)
maybe_correct_this_or_that_value
maybe_break_out_of_a_loop_or_return
end

The Ruby interpreter’s usage of exceptions is mostly limited to grave
events. I try to mimic this usage in my raising practices. Basically,
exceptions should only indicate situations that have to be debugged
out: an exception should never indicate a special, but acceptable,
case.

I come from C (I still use C). Practice in C tends to be that your
procedures return non-zero in case of errors - the non-zero value
indicates the type of error. This can be cascaded: you check the
return value, and return the same if negative, and so on. But it can
certainly escalate to awkwardness, especially if at some level your
function has to return other data.

For me, the advantage of the exception mechanism is that it has its
own, separate distribution channel. If you do not catch an exception,
the program terminates, with VERY useful debugging data: the message
of the exception, and the backtrace.

When I encounter an exception, the catching option is not the
preferred one. If at all possible, I try to modify the code so that
the special case either does not happen, or is specifically handled,
in a programmatic way. If I resort to a rescue clause like the one
above, it means that I come to the (maybe temporary) conclusion that
the special case can happen, but it is something to be monitored. My
programs tend to have meaningful log files: disk space is
cheap. Patterns of repetition of warning messages may give precious
hints to solving complex bugs or refactoring non-optimized
algorithms.

If I catch an exception, I can do so at any of the steps of the
backtrace. Often it is useful to make use of the exception-raising
logic to get out of some of the levels of the call tree.

This iterative process is part and parcel of the testing phase that
all new code has to undergo. Every situation is different. If you
really care about the healthy operation of your code, every choice
should be tuned to the specific case. The process cannot be made
automatic. Automatic coding/debugging results in mediocre programs.

In all cases, I would not use the exception mechanism in the example
that the original poster makes. Rather, the BankAccount#withdraw
method would return true (or possibly the new balance of the account)
if the withdraw method was successful, and false otherwise. Ruby has a
neat syntax for catching false/nil values:

unless(account.withdraw(sum))

The condition of insufficient funds is maybe undesirable from the
point of view of the account holder, but it is a perfectly legitimate
situation; all mechanisms that handle withdrawals should foresee and
properly manage it. Withdrawals can fail. It should not be an
exception-al event from the point of view of the code.

This is just an effort to put into words my craft. Every other
craftsman/woman will most probably follow different ways.

Carlo

On Sat, Nov 17, 2012 at 1:40 AM, Carlo E. Prelz [email protected]
wrote:

In all cases, I would not use the exception mechanism in the example
that the original poster makes. Rather, the BankAccount#withdraw
method would return true (or possibly the new balance of the account)
if the withdraw method was successful, and false otherwise. Ruby has a
neat syntax for catching false/nil values:

One of the things I really like about Ruby is that false is not
the same as nil or zero. Thus returning false from a method is
something the calling code can actually get a handle on, unlike in
other languages (such as C, perl, etc) where false is overloading
zero, which might be a legitimate return value.

This is just an effort to put into words my craft. Every other
craftsman/woman will most probably follow different ways.

Carlo

Thanks, Carlo, that was an excellent desciription, and mirrors my
thoughts as well.

On Sat, Nov 17, 2012 at 4:47 AM, tamouse mailing lists
[email protected] wrote:

I know the right answer to every question is “It depends” :slight_smile:

Ah, I see you learned the most important lesson! :wink:

if you have the option to query the
current balance and define that a withraw operation is only allowed
with sufficient funds then you throw an exception. If OTOH you define
that withdraw will return success then you have a boolean return
value. Both approaches are legal and feasible.

I guess I’m looking for some kind of guidance to generalize this.

My understanding is that one only uses exceptions for things which the
app finds exceptional,

… which is a kind of tautological statement. :slight_smile:

meaning things which it cannot or should not be
dealing with, which in some cases may depend on the business practices
and policies one is implementing.

Yes. And this also depends on context and design philosophy. For
example, in Java you will get an exception when accessing an array or
an ArrayList with an illegal index. In Ruby you just get nil from
Array if the index was out of range. This makes perfectly sense given
that the normal iteration method in Ruby is via #each which ensures
you only get to see elements in the Array and in Java you have to
externally iterate with an index or Iterator instance.

OTOH you will get an exception in Ruby if the type is not an integral
type or the numeric value is too large.

Do you (or anyone else who’d like to chime in!) have a … set of
heuristics, maybe? … that help you know when it’s appropriate and
when it’s not?

I can see these categories of errors where an exception seems
appropriate to me:

  1. programmer errors

    • invoking methods which are not there
    • invalid arguments passed to methods (type errors, range errors)
    • invalid arguments passed which would invalidate the class invariant
    • calling methods at the wrong time i.e. when the instance is not
      in the proper state for the method call (e.g. you must invoke #open on
      something before you can invoke #write, you need to obtain some form
      of lock before you are allowed to work with an instance)
  2. exceptional conditions outside the program

    • technical IO errors (disk and network)
    • memory exhausted

There are probably more but this is what comes to mind off the top of my
head.

In other words, they know an exception might be raised, but they trap
evey possible exception, rather than a specific one. I’m worried (?)
(thinking) that use of raising exceptions might be a little too easily
decided upon as the answer, and if I should maybe push back on such
implementations…

That’s really hard to tell without more context. :slight_smile:

Thanks, so much, Robert. :slight_smile: And I am going to check out your web site
right now.

You’re welcome!

Kind regards

robert

On Sat, Nov 17, 2012 at 8:40 AM, Carlo E. Prelz [email protected]
wrote:

    Subject: Re: Question on exceptions
    Date: Sat 17 Nov 12 12:47:06PM +0900

Quoting tamouse mailing lists ([email protected]):

Do you (or anyone else who’d like to chime in!) have a … set of
heuristics, maybe? … that help you know when it’s appropriate and
when it’s not?

I will try to describe in words the fuzzy logic I follow.

I do not often use exceptions. In more than seven years of using Ruby,
I have probably only subclassed Exception once or twice.

Well, there are other uses of exceptions than defining your own. :slight_smile:
One could even go as far as to say that you use exceptions if you use
code that throws exceptions - even if there is no trace of catching
exceptions in your code. That makes sense from a certain perspective
because you rely on the fact that the code you use will raise
exceptions if errors surface.

The Ruby interpreter’s usage of exceptions is mostly limited to grave
events.

What exactly is a “grave event”?

I try to mimic this usage in my raising practices. Basically,
exceptions should only indicate situations that have to be debugged
out: an exception should never indicate a special, but acceptable,
case.

I beg to disagree. An IO error which surfaces is usually not
something you will be tackling by debugging. You might be the invoker
of a script who learns that way the the file name you passed on the
command line was wrong. There may also be an network error because
your WLAN lost connection spuriously etc.

I come from C (I still use C). Practice in C tends to be that your
procedures return non-zero in case of errors - the non-zero value
indicates the type of error. This can be cascaded: you check the
return value, and return the same if negative, and so on. But it can
certainly escalate to awkwardness, especially if at some level your
function has to return other data.

What you describe neatly shows the major disadvantages of error
handling via return type checking:

  • It is cumbersome (you need to check a lot return values).
  • It has to be done manually (i.e. you can forget it).
  • Propagation to invokers is tedious.
  • The error handling code is intermixed with regular code which makes
    the code harder to read.

Of course, in C you do not have much choices since there are no
exceptions in the language.

For me, the advantage of the exception mechanism is that it has its
own, separate distribution channel. If you do not catch an exception,
the program terminates, with VERY useful debugging data: the message
of the exception, and the backtrace.

Absolutely.

When I encounter an exception, the catching option is not the
preferred one. If at all possible, I try to modify the code so that
the special case either does not happen, or is specifically handled,
in a programmatic way.

Often you cannot do that. For example in the IO error case mentioned
above the only thing you could do would be check the file for
existence and readability before opening it. But since other
processes have access to the file system as well that still does not
give you 100% safety that the file is still there when you are going
to open it. So just opening it and handling the exception (or just
propagating it up the call stack) is the simpler option because the
exception may come anyway. You end up with less code and a clearer
structure. And in a way you avoid redundancy (duplicate checks for
existence and accessibility).

If I catch an exception, I can do so at any of the steps of the
backtrace. Often it is useful to make use of the exception-raising
logic to get out of some of the levels of the call tree.

Or put it differently: often it makes sense to handle an exception
remote from to where it originated.

This iterative process is part and parcel of the testing phase that
all new code has to undergo. Every situation is different. If you
really care about the healthy operation of your code, every choice
should be tuned to the specific case. The process cannot be made
automatic. Automatic coding/debugging results in mediocre programs.

Right, and if it was possible at all, we would be without jobs. :slight_smile:

In all cases, I would not use the exception mechanism in the example
that the original poster makes. Rather, the BankAccount#withdraw
method would return true (or possibly the new balance of the account)
if the withdraw method was successful, and false otherwise. Ruby has a
neat syntax for catching false/nil values:

unless(account.withdraw(sum))

alternative

account.withdraw(sum) or do_something_else

The condition of insufficient funds is maybe undesirable from the
point of view of the account holder, but it is a perfectly legitimate
situation; all mechanisms that handle withdrawals should foresee and
properly manage it. Withdrawals can fail. It should not be an
exception-al event from the point of view of the code.

Unless for example the program cannot verify itself that funds are
sufficient. It is not a too uncommon scenario to rely on database
functionality (constraints) to ensure a negative balance is not
reached. In this case the RDBMS driver’s exception is necessary to
learn of the violation because even if you do the check beforehand
there can still be a transaction modifying the balance between the
check and the withdraw (depending on concurrency model of the RDBMS of
course, in Oracle and Postgres, where reads are not blocked by writes,
you would either need explicit locking or work with the exception).

This is just an effort to put into words my craft. Every other
craftsman/woman will most probably follow different ways.

I think your stress of context is very important. The downside is
that it takes some time and experience to feel comfortable in all
situations to decide on the proper way.

Kind regards

robert

Subject: Re: Question on exceptions
Date: Sun 18 Nov 12 12:35:06AM +0900

Thanks for your many words!

Quoting Robert K. ([email protected]):

I beg to disagree.

You are hereby granted a universal, perennial, transferrable,
irrevocable concession to do so :sunglasses:

Carlo

On Sat, Nov 17, 2012 at 4:48 PM, Carlo E. Prelz [email protected]
wrote:

    Subject: Re: Question on exceptions
    Date: Sun 18 Nov 12 12:35:06AM +0900

Thanks for your many words!

You’re welcome!

Quoting Robert K. ([email protected]):

I beg to disagree.

You are hereby granted a universal, perennial, transferrable,
irrevocable concession to do so :sunglasses:

Wow! And: LOL

Have a nice weekend!

robert

On Sat, Nov 17, 2012 at 8:19 AM, Robert K.
[email protected] wrote:

Ah, I see you learned the most important lesson! :wink:
I’m quick like that. :slight_smile:

My understanding is that one only uses exceptions for things which the
app finds exceptional,

… which is a kind of tautological statement. :slight_smile:

That was intended. :slight_smile:

But, seriously, it does bring up the question “why did (this
developer) think (this situation) was exceptional to (this
application)?” I don’t think there can ever be a general answer to
that question, which is why I asked for heuristics.

I can see these categories of errors where an exception seems appropriate to me:

Great list. Thanks.

That’s really hard to tell without more context. :slight_smile:

Oh, yes, I know;; this is something I think I have to work out for
this specific situation.