Forum: Ruby Question on exceptions

Posted by Justin Gamble (justingambit)
on 2012-11-15 22:33
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!
Posted by Brian Candler (candlerb)
on 2012-11-15 22:39
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.
Posted by Justin Gamble (justingambit)
on 2012-11-15 22:45
What is the reason of doing the .new(...)in

  raise InsufficientFundsError.new (1234, 50, 100)

?
Posted by 7stud -- (7stud)
on 2012-11-16 03:23
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>
Posted by Brian Candler (candlerb)
on 2012-11-16 08:43
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)
Posted by tamouse mailing lists (Guest)
on 2012-11-16 09:27
(Received via mailing list)
On Fri, Nov 16, 2012 at 1:43 AM, Brian Candler <lists@ruby-forum.com> 
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...
Posted by Robert Klemme (robert_k78)
on 2012-11-16 13:28
(Received via mailing list)
On Fri, Nov 16, 2012 at 9:27 AM, tamouse mailing lists
<tamouse.lists@gmail.com> 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
Posted by Brian Candler (candlerb)
on 2012-11-16 19:16
Robert Klemme wrote in post #1084722:
> On Fri, Nov 16, 2012 at 9:27 AM, tamouse mailing lists
> <tamouse.lists@gmail.com> 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!
Posted by tamouse mailing lists (Guest)
on 2012-11-17 04:47
(Received via mailing list)
On Fri, Nov 16, 2012 at 6:27 AM, Robert Klemme
<shortcutter@googlemail.com> wrote:
> On Fri, Nov 16, 2012 at 9:27 AM, tamouse mailing lists
> <tamouse.lists@gmail.com> 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" :)

> 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. :)  And I am going to check out your web site
right now.
Posted by Carlo E. Prelz (Guest)
on 2012-11-17 08:41
(Received via mailing list)
Subject: Re: Question on exceptions
  Date: Sat 17 Nov 12 12:47:06PM +0900

Quoting tamouse mailing lists (tamouse.lists@gmail.com):

>    # 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
Posted by tamouse mailing lists (Guest)
on 2012-11-17 15:12
(Received via mailing list)
On Sat, Nov 17, 2012 at 1:40 AM, Carlo E. Prelz <fluido@fluido.as> 
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.
Posted by Robert Klemme (robert_k78)
on 2012-11-17 15:20
(Received via mailing list)
On Sat, Nov 17, 2012 at 4:47 AM, tamouse mailing lists
<tamouse.lists@gmail.com> wrote:
>
> I know the right answer to every question is "It depends" :)

Ah, I see you learned the most important lesson! ;-)

>> 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. :-)

> 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. :-)

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

You're welcome!

Kind regards

robert
Posted by tamouse mailing lists (Guest)
on 2012-11-17 16:00
(Received via mailing list)
On Sat, Nov 17, 2012 at 8:19 AM, Robert Klemme
<shortcutter@googlemail.com> wrote:
> Ah, I see you learned the most important lesson! ;-)
I'm quick like that. :)

>> My understanding is that one only uses exceptions for things which the
>> app finds exceptional,
>
> ... which is a kind of tautological statement. :-)

That was intended. :)

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. :-)

Oh, yes, I know;; this is something I think I have to work out for
this specific situation.
Posted by Robert Klemme (robert_k78)
on 2012-11-17 16:35
(Received via mailing list)
On Sat, Nov 17, 2012 at 8:40 AM, Carlo E. Prelz <fluido@fluido.as> 
wrote:
>         Subject: Re: Question on exceptions
>         Date: Sat 17 Nov 12 12:47:06PM +0900
>
> Quoting tamouse mailing lists (tamouse.lists@gmail.com):
>
>> 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. :-)
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. :-)

> 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
Posted by Carlo E. Prelz (Guest)
on 2012-11-17 16:49
(Received via mailing list)
Subject: Re: Question on exceptions
  Date: Sun 18 Nov 12 12:35:06AM +0900

Thanks for your many words!

Quoting Robert Klemme (shortcutter@googlemail.com):

> I beg to disagree.

You are hereby granted a universal, perennial, transferrable,
irrevocable concession to do so 8-)

Carlo
Posted by Robert Klemme (robert_k78)
on 2012-11-17 17:15
(Received via mailing list)
On Sat, Nov 17, 2012 at 4:48 PM, Carlo E. Prelz <fluido@fluido.as> 
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 Klemme (shortcutter@googlemail.com):
>
>> I beg to disagree.
>
> You are hereby granted a universal, perennial, transferrable,
> irrevocable concession to do so 8-)

Wow! And: LOL

Have a nice weekend!

robert
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.