CSC152, Class 17: Exceptions (or "When Things Go Wrong") Admin: * Sorry about Friday. It was a *bad* day. * Today is discussion/lecture. Tomorrow is lab. Overview: * A sample problem and how to handle it * Generalizing: Kinds of problems in programs * Java's primary solution: Exceptions * Indicating your methods may fail and when * Dealing with methods that may fail * Writing your own exceptions Sample method: /** * Compute one root of a quadratic equation. */ double quadroot(int a, int b, int c) { return ... / (2.0 * a); ... } // quadroot(int, int, int) * How do we write this? We look up the formula and turn it into Java. "Approximately: negative b plus or minus the square root of b squared minus 4 times a times c all over 2 times a" * What can go wrong? * The thing in the square root can be negative, and Java doesn't like to take the square root of negative numbers. * The programmer might provide us with values other than integers. * a can be zero. * As implementors of quadroot, what is our response to "something may go wrong"? * Put preconditions: "a is not 0, root cannot be negative and it's your own damn fault if the computer ends up being a smoldering heap of rubble because of an impossible calculation" ("smoldering heap of rubble" is a phrase often attributed to Rolf) * Do testing within our method "if (a == 0) ..." * Suppose the called method finds an error, what should the method do? * return "error" to the calling method * Communicate the error to the user * Forget that it found an error and go on * Returning "error" is harder in some langauges, like Java ("error" is the wrong type for quadroot) * SOmetimes we return a special value (e.g., 0 or -1). * But it's hard to find special values for some functions * Since the method typically does not interact with the user, it may be strange to report the method to the user * Please don't be microsoft Only two good solutions: * Rely on preconditions * Less coding for implementer of method * Lots of effort to write the preconditions * More efficient * Evidence shows that many programmers say "Assume the preconditions are met while I'm developing. I'll insert the error-checking code later." * Report errors in some way to calling method * Less coding for implementer of method that calls our potentially erroneous method * Coding in caller repetitious * Gives better "feedback" * Evidence shows that many programmers say "I'll assume that the return value shows "success" and will insert the error-checking code later." * Example from c: printf(...) prints, returns a value that indicates "I succeeded" or "I failed". The designers of Java looked askance at this situation and implemented Exceptions (designed earlier by Barbara Liskov at ... MIT, I think) Idea of exceptions: * Two ways to return from the function * A normal value, through the normal "channel" of information * An exception, which goes through a different channel * When someone calls an exceptional function (one that may fail), the caller *must* supply not only what to do with the normal result, but also what to do with the exceptional result * If the caller doesn't do both, Java refuses to compile Three key issues in exceptions: * How to indicate that a method may fail * How to indicate that a method has failed * How to call a method that may fail * And a hack that gets us back to square one * How to design our own kinds of failure * How to indicate that a method may fail * Add "throws Exception" to the method head double quadroot(int a, int b, int c) throws Exception { ... } * You can also indicate particular ways something may fail by using a different kind of exception of throws double quadroot(int a, int b, int c) throws DivideByZeroException,ComplexResultException { ... } * You can indicate that a method has failed with the "throw" command. * Parameter: An object in class Exception (or in some related class, like DivideByZeroException) if (a == 0) { throw new DivideByZeroExeption(); } or if (a == 0) { throw new Exception("Cannot divide by zero, you bozo"); } * How to call a method that may fail public void myMethod() { ... try { double f = helper.quadroot(alpha, beta, gamma); } catch (Exception e) { // What to do if it fails } ... } public void myMethod() { ... try { double f = helper.quadroot(alpha, beta, gamma); } catch (DivideByZeroException dbze) { // What to do if it fails with divide by zero } catch (ComplexResultException cre) { // What to do if it fails with a complex result } ... } Note: The constructor for FileReader can fail. ALternative hack solution to calling a method that can fail: If your method can throw an exception, you don't need to put exceptional methods in try/catch blocks. public void myStupidMetthod() throws Exception { double f = helper.quadroot(alpha, beta, gamma); } If you have a new "kind" of error that you want to throw, you can define it in a .java file public class ComplexResultException extends Exception { public ComplexResultException() { super(); } public ComplexResultException(String str) { super(str); } // ComplexResultException(String) } // extends Exception Lab tomorrow!