Fog Creek Software
Discussion Board




Handling Errors

I have seen excellent books on Design Patterns, Debugging, Refactoring, Development Mythologies, User Interface Design, Team/Code Management.  But I don't know any books about handling errors.  I have done Google and Amazon search but no luck.

Any recommendation ?

RM
Friday, February 28, 2003

What language? I know that there are general guidelines on handling errors, but any detailed text is going to use a specific language since error handling varies from language to language.

Go Linux Go!
Friday, February 28, 2003

FWIW, I did a search on Amazon for "Exception Handling" and got 3 results. One is handling errors in ASP.NET, 1 in Java and one appears to be a collection of computer science notes on the topic.

Go Linux Go!
Saturday, March 01, 2003

There's a whole O'Reilly book about developing error messages:

http://www.oreilly.com/catalog/error/

Joel Spolsky
Saturday, March 01, 2003

At the risk of starting a flaming war here are my
guidelines:

1) Avoid exceptions like the plague - never throw them, only catch them (and translate to a decent error object). I know all OOP gurus will not like what I say, but I think the world would be a little better place without them :-)

2) Use a general result object and make every method return this object by default.

3) Every result object should contain the following fields: error code (used by the software to determine behavior), error string (used by humans to understand the failure), and extra data (e.g., Object in Java) that can contain additional information you want to pass with the result (like the result of a calculation).

The point on exceptions needs a little more explanation: The problem with exceptions is that you cannot always expect them in code - in such a case they make the thread of execution terminate which makes your software extremely unreliable. In C++ you cannot even KNOW if you need to use a proper try/catch clause when calling a method. In Java you sometimes know (non-Runtime exceptions) and sometimes you don't. At least when using result objects you can ALWAYS check the result and the thread does not terminate if you don't.

If you want some real examples - drop me an email and I'll send you an example.


Good luck

Liron Levy
Saturday, March 01, 2003

Bless you Levy!

Listen up to this gentleman everyone! At last someone who understands!

Dennis Atkins
Saturday, March 01, 2003

Liron: maybe it's my inexperience talking, but ... WTF?

I love how Java handles exceptions (by putting the throwable exceptions in the method signature and kicking out compiler errors if they aren't handled).  It actually forces you to consider the possible exceptional conditions.  There's nothing about checking error codes by hand that encourages the programmer to (a) recognize that the function may fail and (b) do something intelligent with the failure.

(It's true that subclasses of RuntimeException need not be handled, but everyone knows RuntimeException should be used sparingly and for unrecoverable exceptional conditions anyways).

You'll be fighting an uphill battle trying to ensure that every function is checked for errors, and even if you were to manage that, nine times out of ten your error handling will be going to be no more sophisticated than to print the error message and terminate, which is what exceptions do by default anyways. 

After all, if the coder didn't expect the exception, what else is he going to do with it?

If what you want to do is print the error message and continue on regardless of the consequences of the unforseen error, you might as well wrap your function up into one big try-catch block, than try to add error checking to each one of the fifty function calls.

And in the case that the coder actually handled the error-checking intelligently, he must have already known what kinds of exceptional conditions could crop up, in which case your objection "but you don't know what to expect" makes no sense.

So what does your solution do besides create code bloat and inviting difficult to debug "oops-I-forgot-the-error-checking" defects?

I don't mean to start a flame war or anything, but I genuinely don't see any advantage of your method.

Alyosha`
Saturday, March 01, 2003

If I had a dollar for every kiosk, billboard and website that unhelpfully tells the average customer that the computer has crashed on an unhandled Java exception, I could buy a lot of coffee. That's the deal. If this stuff is so great, how come the Big Guys can't get it right?

Dennis Atkins
Saturday, March 01, 2003

That sort of comment is unverifiable/unfalsfiable.  Do you have some quantifiable measurements of whether unhandled Java exceptions are more common than undetected errors in programs that manually check error codes?

I'm certainly no researcher, but I really haven't seen many production-quality commercial applications throw Java exceptions.  I've never seen one of these bombed kiosks or billboards you've mentioned (although I've seen lots of BSODs in airport terminals) ...

Okay, I'll admit that I've seen a lot of unhandled Java exceptions in web applets, but then again Java applets aren't the highest quality code out there, either.

Alyosha`
Sunday, March 02, 2003

Liron Levy:
When I was doing my internship, I was once told by a senior developer that Exceptions are not always the best idea, (though they did not really have any alternative), I was never convinced until now.  I thank you for your explanation.

I thought of a similar idea as you suggested.  Basically, every object knows how to handle and propagate errors.  For example, Suppose Object A instantiates Object B,C,D.  If B,C and D report errors to A, then A will translate these errors to an error in terms of its operations. (ie, if B reports DividedByZeroError, A might translate it into “OutOfBoundValueError”)  And finally report it back to its predecessor.  Recursively, objects at higher level will have better understanding of what went wrong beneath without too much detail.

The main difference between yours and mine is that mine is done by having an Error-Handling method in each object, instead of every method returning “a general result”. But I am neither convinced of my own, simply because I am inexperienced and have not seen/read much about handling errors.  One reason why I am looking for a good book about handling error in general.

Anyway, I thank you everyone for your inputs.

PS: When I say “Errors”, I meant all kind of errors; Run-Time, Application, Calculation, Fatal, UnHoly, System, Missing-XYZ, Incomplete, Math, Floating-Point etc.

RM
Sunday, March 02, 2003

Hmm, your description of "result objects" is interesting, but I've never heard of them before and google turns up nothing.  Is there a book which talks about this, or perhaps a language where this is assumed?

By the way, in Java, using a runtime exception thoughtlessly is very bad form.

n
Sunday, March 02, 2003

I have to say "WTF" too.


If your code never throws Exceptions, only catches them, what is there for the code that uses your classes (and not standard/3rd party APIs) to catch?


You suggest using a general result object and make every method return this.

Hmm. Doesn't the make your API slightly less clear? I never like the idea of setters returning results.

This result object you propose contains an error code (why not just use a specific type of Exception?), and error String (why not throw new XException( errorString );?) and an actual return value... maybe, f the method is ACTUALLY supposed to return something. This technique just seems to make the result of a method call a lot less clear - all your methods are returning ResultObject, and not the "actual" return type (eg. you can easily tell from the method signature when you want to use public Date getDate() vs. public long getTime()). And what if you're implementing some language/standards defined interface that does not use the ResultObject idiom?


Furthermore, the ResultObject framework may create hassles when integrating your code with another program (eg. using your code as a .jar to another program). Instead of using a language feature to convey error information, you're using your own solution with the associated overhead (extra lines of code, other developers needing to get up to speed, possibility of having to mix methods that return ResultObjects AND throw Exceptions).

Walter Rumsby
Sunday, March 02, 2003

I was talking out of heavy experience working with exceptions. I'm really convinced they are bad (always).

Some of you suggested that it is a "bad" idea to throw
runtime exceptions. Cool - but the JDK itself did that for
me more times thn I care to remember ...

If the guys at Sun did such a lousy job - I think it means something ...

Another point - Most commercial libraries I happened to
work with did not use exceptions at all (e.g., ILOG) just
because of the problems involved.

Someone here said that returning a result object is not a very
"Clear" way as this is not enforced by the software.
My answer to this is simple - define coding guidelines and
enforce THEM (you have to do this anyway).


Good Luck

Liron Levy
Sunday, March 02, 2003

For those who are interested I've pasted the code for
the implementation of this idea:

There are two files: NMEresult.java that contains the code
for the result object, and NMEerrcode.java that  contains the code for the error codes.

[----------------------- NMEresult -----------------------]
package nme.base;

// Needed in order to allow instances of NMEresult to travel
// process boundaries.
import java.io.Serializable;

/**
* Defines the general result of performing NME requests.
* It should be used by all NME system modules. The result
* encapsulates an error indication and description in the
* case the request performing was not successful or
* the result data if the request has succeeded.
* <b>Note that the implementation is not synchronized.</b> In most cases
* the result object will be first fully constructed by one thread and after
* that it will be used by another one. Anyway, if multiple threads access
* a result concurrently and at least one of them modifies it, the
* access must be synchronized externally.
*/
public class NMEresult
    implements Serializable
{
    // ----------------------------------------------------------
    //                  public enumerations
    // ----------------------------------------------------------

    /** Error message for the case of illegal error code. */
    public static final String ILLEGAL_CODE_ERR_MSG =
        "The error cannot be resolved, the error code is illegal";

    // ----------------------------------------------------------
    //                  public methods
    // ----------------------------------------------------------

    /**
    * Constructs a successful result object with neither associated
    * request identifier nor any result data.
    *
    * @see #NMEresult(int, Serializable)
    */
    public NMEresult()
    {
        initialize(NMEerrcode.E_SUCCESS, null, null);
    } // NMEresult

    /**
    * Constructs a successful result object with given result data.
    *
    * @param extra_data the additional result data. The given
    *                  reference is stored, no cloning is performed.
    * @see #NMEresult(int, Serializable)
    */
    public NMEresult(Serializable extra_data)
    {
        initialize(NMEerrcode.E_SUCCESS, null, extra_data);
    } // NMEresult

    /**
    * Constructs a result object with given error code and provided
    * error message. The extra data field is left unspecified.
    *
    * @param err_code the code of the error.
    * @param err_msg the problem description. If is <code>null</code>,
    *                  the standard description is used.
    */
    public NMEresult(int err_code, String err_msg)
    {
        initialize(err_code, err_msg, null);
    } // NMEresult

    /**
    * Constructs an erroneous result object with given error code
    * and standard error description. The extra data field is left
    * unspecified.
    *
    * @param error_code the code of the error.
    * @see #NMEresult(int, String, int, Serializable)
    */
    public NMEresult(int error_code)
    {
        initialize(error_code, null, null);
    } // NMEresult

    /**
    * Constructs an erroneous result object with given error code,
    * description and additional data.
    *
    * @param error_code the code of the error.
    * @param error_msg the problem description. If is <code>null</code>,
    *                  the standard description is used.
    * @param extra_data the additional result data. The given
    *                  reference is stored, no cloning is performed.
    * @see #NMEresult(int, String, int, Serializable)
    */
    public NMEresult(int error_code,
                    String error_msg,
                    Serializable extra_data)
    {
        initialize(error_code, error_msg, extra_data);
    } // NMEresult

    /**
    * Checks if the given result is successful.
    *
    * @return <code>true</code> if the result is successful;
    *        <code>false</code> otherwise.
    */
    public boolean isSuccess()
    {
        return (m_ErrorCode == NMEerrcode.E_SUCCESS);
    } // isSuccess

    /**
    * Returns the code of the error associated with the result.
    *
    * @return the result error code.
    */
    public int getErrorCode()
    {
        return m_ErrorCode;
    } // getErrorCode

    /**
    * Defines the error code and standard problem description for the result.
    * Use {@link #setErrorMsg} method later for redefining the
    * error description to non-standard.
    *
    * @param error_code the result error code.
    */
    public void setErrorCode(int error_code)
    {
        // Set the error code.
        m_ErrorCode = error_code;

        // Set the error message, using the standard description.
        m_ErrorMsg = getStandardErrorDescr(m_ErrorCode);
    } // setErrorCode

    /**
    * Returns the description of the error associated with the result.
    *
    * @return the result error description.
    */
    public String getErrorMsg()
    {
        // If the error message is set for the result, return it.
        if (m_ErrorMsg != null)
            return m_ErrorMsg;

        // The error message is not set, return the standard
        // description, according to the result error code.
        // We should not actually get here, because the error
        // message is always set according to the code. The case
        // is possible only if the message was explicitly set to null.
        return getStandardErrorDescr(m_ErrorCode);
    } // getErrorMsg

    /**
    * Defines the error message of the result. The message is set
    * exactly to the given string (even if it is <code>null</code>).
    * No changes are performed in order to make is suitable to the
    * result error code.
    *
    * @param error_msg the problem description.
    */
    public void setErrorMsg(String error_msg)
    {
        m_ErrorMsg = error_msg;
    } // setErrorMsg

    /**
    * Assigns standard error description to the error
    * message of the result.
    */
    public void setStandardErrorMsg()
    {
        m_ErrorMsg = getStandardErrorDescr(m_ErrorCode);
    } // setStandardErrorMsg

    /**
    * Appends given string to the currently defined error message of the
    * result.
    *
    * @param error_msg the string to be appended.
    */
    public void appendErrorMsg(String error_msg)
    {
        if (error_msg != null)
        {
            StringBuffer new_description = new StringBuffer();
            if (m_ErrorMsg != null)
                new_description.append(m_ErrorMsg + ", ");
            new_description.append(error_msg);
            m_ErrorMsg = new String(new_description);
        } // if
    } // appendErrorMsg

    /**
    * Returns the additional data associated with the result.
    *
    * @return the additional result data.
    */
    public Serializable getExtraData()
    {
        return m_ExtraData;
    } // getExtraData

    /**
    * Defines the additional data associated with the result.
    *
    * @param extra_data the additional result data to be stored.
    */
    public void setExtraData(Serializable extra_data)
    {
        m_ExtraData = extra_data;
    } // setExtraData

    /**
    * Generates a string representation of the result. It indicates
    * if the request has failed or succeeded and gives the error
    * description or resulting data representation respectively.
    *
    * @return the string representation of the result.
    */
    public String toString()
    {
        // String representation parts.
        String header;
        String error_msg = null;

        // The flag shows if error information is needed.
        boolean add_error_info;

        // Temporary buffer for creating the resulting string.
        StringBuffer buf = new StringBuffer();

        // Constract the header of the string and check if the
        // error info should be retrieved.
        if (m_ErrorCode == NMEerrcode.E_SUCCESS)
        {
            header = "The request succeeded";
            add_error_info = false;
        }
        else
        {
            header = "The request failed";
            add_error_info = true;
            error_msg = getErrorMsg();
        } // else

        // Actually create the string.
        buf.append(header);
        if (add_error_info)
        {
            buf.append(": error code = " + m_ErrorCode);
            buf.append(" [" + error_msg + "]");
        }
        if (m_ExtraData != null)
        {
            buf.append(", result data = " + m_ExtraData);
        }
        return (new String(buf));
    } // toString

    // ----------------------------------------------------------
    //                  private methods
    // ----------------------------------------------------------

    /**
    * Initializes the request object with given values.
    *
    * @param error_code the code of the error.
    * @param error_msg the string that describes the problem. If is
    *                    <code>null</code>, the standard description is used.
    * @param extra_data some additional result data. The given
    *                  reference is stored, no cloning is performed.
    */
    private void initialize(int error_code,
                            String error_msg,
                            Serializable extra_data)
    {
        m_ErrorCode = error_code;
        if (error_msg != null)
        {
            m_ErrorMsg = error_msg;
        }
        else
        {
            m_ErrorMsg = getStandardErrorDescr(m_ErrorCode);
        }
        m_ExtraData = extra_data;
    } // initialize

    /**
    * Returns the standard error description for the error with a given code.
    *
    * @param error_code the code of the error.
    * @return the standard description of the error.
    */
    private String getStandardErrorDescr(int error_code)
    {
        String error_descr;

        // Compute the error range to which this error code belongs
        int err_range = error_code / 100;

        // Compute the index in the error range of the error code
        int err_index = error_code % 100;

        // Make sure that the description table can address this description
        try
        {
            error_descr =
                NMEerrcode.ALL_ERR_DESCRIPTIONS[err_range][err_index];
        }
        catch (Exception e)
        {
            // The error code is illegal, return the appropriate
            // error message.
            error_descr = ILLEGAL_CODE_ERR_MSG;
        }

        return error_descr;
    } // getStandardErrorDescr

    // ----------------------------------------------------------
    //                  private variables
    // ----------------------------------------------------------

    /** The code of the error. */
    private int m_ErrorCode;

    /** The error message with exact problem description. */
    private String m_ErrorMsg;

    /** Some additional result data. */
    private Serializable m_ExtraData;
} // NMEresult

[-------------------- END OF NMEresult -----------------------]

[-------------------- START OF NMEerrcode ------------------]

package nme.base;

import com.outbackinc.services.protocol.snmp.*;


/**
* Defines global error codes and their standard descriptions for
* all NME system modules.
*/
public class NMEerrcode
{
    // ----------------------------------------------------------
    //                STANDARD ERROR CODES
    // ----------------------------------------------------------

    /** Success. */
    public static final int E_SUCCESS = 0;

    /** Indicates that the client is not permitted to invoke the request. */
    public static final int E_PERMISSION_DENIED = 1;

    /** Indicates some unexpected software error occurred. */
    public static final int E_INTERNAL_SOFTWARE_ERROR = 2;

    /** Indicates a timeout problem */
    public static final int E_REQUEST_TIMEOUT = 3;

    /** Indicates that the software was requested to change
        into an illegal state */
    public static final int E_ILLEGAL_STATE = 4;

    /** Indicates that there is security model failure. */
    public static final int E_SECURITY_MODEL_FAILURE = 5;

    // ----------------------------------------------------------
    //          SNMP ACCESS MODULE ERROR CODES
    // ----------------------------------------------------------

    /** Indicates a "No Such Name" SNMP error */
    public static final int E_SA_NO_SUCH_NAME = 100;

    /** Indicates a "Authorization Error" SNMP error */
    public static final int E_SA_AUTHORIZATION_ERROR = 101;

    /** Indicates a "Unknown IP Address" SNMP error */
    public static final int E_SA_UNKNOWN_IP_ADDRESS = 102;

    /** Indicates a "General" SNMP error */
    public static final int E_SA_GENERAL_ERROR = 103;

    /** Indicates a "Bad Value" SNMP error */
    public static final int E_SA_BAD_VALUE = 104;

    /** Indicates a "No Access" SNMP error */
    public static final int E_SA_NO_ACCESS = 105;

    /** Indicates a "Inconsistent Name" SNMP error */
    public static final int E_SA_INCONSISTENT_NAME = 106;

    /** Indicates a "Incosistent Value" SNMP error */
    public static final int E_SA_INCONSISTENT_VALUE = 107;

    /** Indicates a "Resource Unavailable" SNMP error */
    public static final int E_SA_RESOURCE_UNAVAILABLE = 108;

    /** Indicates a "Not Writable" SNMP error */
    public static final int E_SA_NOT_WRITABLE = 109;

    /** Indicates a "Wrong Type" SNMP error */
    public static final int E_SA_WRONG_TYPE = 110;

    /** Indicates a "Wrong Length" SNMP error */
    public static final int E_SA_WRONG_LENGTH = 111;

    /** Indicates a "Wrong Encoding" SNMP error */
    public static final int E_SA_WRONG_ENCODING = 112;

    /** Indicates a "Wrong Value" SNMP error */
    public static final int E_SA_WRONG_VALUE = 113;

    /** Indicates a "No Creation" SNMP error */
    public static final int E_SA_NO_CREATION = 114;

    /** Indicates a "Too Big" SNMP error */
    public static final int E_SA_TOO_BIG = 115;

    /** Indicates a "Commit Failed" SNMP error */
    public static final int E_SA_COMMIT_FAILED = 116;

    /** Indicates a "Undo Failed" SNMP error */
    public static final int E_SA_UNDO_FAILED = 117;

    /** Indicates a "Unknown request or data type" SNMP error */
    public static final int E_SA_UNSUPPORTED_TYPE = 118;

    /** Indicates that there is no data about the request. */
    public static final int E_SA_NO_DATA = 119;

    // ----------------------------------------------------------
    //            DEVICE ACCESS MODULE ERROR CODES
    // ----------------------------------------------------------

    /** Indicates that the operation has failed because the i
        in the device access data structure. */
    public static final int E_DA_NOT_FOUND = 200;

    /** Indicates that the device is already defined with a specific type and
        cannot be changed. */
    public static final int E_DA_REQ_DENIED = 201;

    /** Indicates that the operation failed to start because one of the IP
        addresses specified in the request cannot be found in the network. */
    public static final int E_DA_UNKNOWN_DEVICE = 202;

    /** Indicates that the request has failed because the device that processed
        the request does not support one or more of the attributes in the request.*/
    public static final int E_DA_NO_SUCH_ATTRIB = 203;

    /** Indicates that some general device error has occurred. Further
        information will be supplied in the result error string. */
    public static final int E_DA_GENERAL_ERROR = 204;

    /** The type or instance of the management information is not accessible due
        to access control settings. */
    public static final int E_DA_NO_ACCESS = 205;

    /** This error is returned if the request contained a value that has a
        different type then the one in the device itself. */
    public static final int E_DA_WRONG_TYPE = 206;

    /** This error is returned if the value field that was specified in the
        request is illegal and should not be used. */
    public static final int E_DA_WRONG_VALUE = 207;

    /** Indicates that the request has failed due to a failure to set one of the
        variable bindings and that the state of the agent is not affected by
        this request. */
    public static final int E_DA_COMMIT_FAILED = 208;

    /** Indicates that the request has failed due to a failure to set one of the
        variable bindings and that the state of the agent could not be restored
        to its previous settings. */
    public static final int E_DA_UNDO_FAILED = 209;

    // More error codes here ...

    /** Indicates Error code ranges */
    public static final int STANDARD_ERRORS_RANGE = 0;
    public static final int SNMP_ACCESS_ERRORS_RANGE = 1;
    public static final int DEVICE_ACCESS_RANGE = 2 ;

    // More error ranges are welcome (100 error codes per range MAX !!)

    /** Standard error descriptions for all error codes (including success). */
    public static final String STANDARD_ERR_DESCR[] =
    {
        "Request succeeded",
        "Client is not permitted to invoke the request",
        "Unexpected software error",
        "Request has timed out",
        "Illegal state error",
        "Security model failure"
    };

    /** Snmp Access Module error messages */
    public static final String SNMP_ACCESS_ERR_DESCR[] =
    {
        "No such MIB name",
        "Authorization error",
        "Unknown IP address used",
        "General error",
        "Bad value",
        "No access",
        "Inconsistent name used",
        "Inconsistent value used",
        "Resource unavailable",
        "Scalar is not writable",
        "Wrong type used in request",
        "Wrong length used in request",
        "Wrong encoding error",
        "Wrong value used in request",
        "Object cannot be created",
        "Response is too big",
        "Snmp COMMIT has failed",
        "Snmp UNDO has failed",
        "Snmp received unsupported type",
        "No data about the request"
    };

    /** Device Access Module error messages */
    public static final String DEVICE_ACCESS_ERR_DESCR[] =
    {
        "device was not found",
        "device is already defined",
        "Unknown IP address used",
        "No such attribute",
        "General error",
        "No Access error",
        "Wrong type used in request",
        "Wrong value used in request",
        "Commit failed",
        "Snmp UNDO has failed",
    };

    /**
    * This table contains description tables for all error ranges.
    * Whenever a new error code is introduced, a corresponding
    * description string should be entered in its corresponding range
    * strings array
    */
    public static final String ALL_ERR_DESCRIPTIONS[][] =
    {
        STANDARD_ERR_DESCR,
        SNMP_ACCESS_ERR_DESCR,
        DEVICE_ACCESS_ERR_DESCR
        // Add more array references here for each new module
    };

    /**
    * This method converts jSnmp error codes to local error codes.
    * @param snmp_err_code jSnmp error code
    * @return Returns local error code that match the given jSnmp error code.
    */
    public static int mapSnmpError(int snmp_err_code)
    {
        switch(snmp_err_code)
        {
            case SnmpConstants.SNMP_ERR_TIMEOUT:
                return E_REQUEST_TIMEOUT;
            case SnmpConstants.SNMP_ERR_SECURITY_EXCEPTION:
                return E_SECURITY_MODEL_FAILURE;
            case SnmpConstants.SNMP_ERR_NOSUCHNAME:
                return E_SA_NO_SUCH_NAME;
            case SnmpConstants.SNMP_ERR_AUTHORIZATIONERROR:
                return E_SA_AUTHORIZATION_ERROR;
            case SnmpConstants.SNMP_ERR_UNKNOWN_HOST:
                return E_SA_UNKNOWN_IP_ADDRESS;
            case SnmpConstants.SNMP_ERR_GENERAL:
                return E_SA_GENERAL_ERROR;
            case SnmpConstants.SNMP_ERR_BADVALUE:
                return E_SA_BAD_VALUE;
            case SnmpConstants.SNMP_ERR_NOACCESS:
                return E_SA_NO_ACCESS;
            case SnmpConstants.SNMP_ERR_INCONSISTENTNAME:
                return E_SA_INCONSISTENT_NAME;
            case SnmpConstants.SNMP_ERR_INCONSISTENTVALUE:
                return E_SA_INCONSISTENT_VALUE;
            case SnmpConstants.SNMP_ERR_RESOURCEUNAVAILABLE:
                return E_SA_RESOURCE_UNAVAILABLE;
            case SnmpConstants.SNMP_ERR_NOTWRITABLE:
                return E_SA_NOT_WRITABLE;
            case SnmpConstants.SNMP_ERR_WRONGTYPE:
                return E_SA_WRONG_TYPE;
            case SnmpConstants.SNMP_ERR_WRONGLENGTH:
                return E_SA_WRONG_LENGTH;
            case SnmpConstants.SNMP_ERR_WRONGENCODING:
                return E_SA_WRONG_ENCODING;
            case SnmpConstants.SNMP_ERR_WRONGVALUE:
                return E_SA_WRONG_VALUE;
            case SnmpConstants.SNMP_ERR_NOCREATION:
                return E_SA_NO_CREATION;
            case SnmpConstants.SNMP_ERR_TOOBIG:
                return E_SA_TOO_BIG;
            case SnmpConstants.SNMP_ERR_COMMITFAILED:
                return E_SA_COMMIT_FAILED;
            case SnmpConstants.SNMP_ERR_UNDOFAILED:
                return E_SA_UNDO_FAILED;
            case SnmpConstants.SNMP_ERR_UNSUPPORTED_TYPE:
                return E_SA_UNSUPPORTED_TYPE;
            case SnmpConstants.SNMP_ERR_NOSUCHINSTANCE:
                return E_SA_NO_SUCH_NAME;
            default:
                return E_SA_GENERAL_ERROR;
        }
    }
} // NMEerrcode

[----------------- END OF NMEerrcode ----------------------]

Liron Levy
Sunday, March 02, 2003

To answer n's question, ResultObjects (and/or result error codes) are the mess that exceptions were designed to solve.  As I mentioned before, the problem with checking for error codes after every operation is insane code bloat and impossible to track down bugs when you forget a error check.

Liron: Actually I made a bit of a mistake when I said that RuntimeExceptions should be rare/unrecoverable events (I was thinking of Errors when I said that).

RuntimeExceptions consist mainly of stack underflows, divide by zero, null pointer, class casting, and bounds checking type errors -- that is to say, programming errors which would hose a C++ program and should never occur in a properly debugged program.  Failures of this catagory always indicate a defect in programming, a blatant and forseeable violation of the pre-condition design contract, and not an exceptional but otherwise legal condition that prevents an operation from completing.

Maybe *YOUR* code is full of NPEs and array bounds exceptions, especially given strange inputs, but I wouldn't blame the language or libraries for that ...

I've honestly never had the problems you have with Java's exception model, so I'm a bit boggled as to why they're so difficult for you. 

But, maybe it's because I haven't done as much hard-core programming as you.

Regarding coding guidelines, I love practical suggestions for better code, but honestly, eliminating overlooked error checks by laying down the law in the form of coding guidelines is a lot like trying to eliminate teen pregnancy by preaching complete abstinence to schoolchildren.

No, no, that's a bad analogy.  Even as much pressure biology exerts on adolescents to have sex, it's still not anywhere near the pressure programmers have to skip error checks if they think they can get away with it.

Exceptions make life so much easier.

Alyosha`
Sunday, March 02, 2003


That's fine in theory ...

In my experience - programmers need to handle every
error condition. For this matter - using try/catch or if/else
is the same thing actually.

Good code is usually ugly in this respect (its littered with error handling clauses) ...

Again - the THEORY says that you shouldn't throw runtime
exceptions where non-runtime exceptions are enought.
Practice says that you will use internal or external libraries
that do just that.

By explicitly returning a result object - you actually FORCE
the programmer to consider the consequences of his/her
behavior (by examining the documentation of the method).

A code that throws an exception can be easily overlooked (especially true for C# and C++, less true but still a problem in Java) by the programmer because its prototype does not show anything special that need handling.

Liron Levy
Sunday, March 02, 2003


Just a comment on what Alyosha said earlier:

<quote>
Failures of this catagory always indicate a defect in programming, a blatant and forseeable violation of the pre-condition design contract, and not an exceptional but otherwise legal condition that prevents an operation from completing.
</quote>

There is nothing stopping you from throwing RuntimeExceptions yourself and there are several JDK modules that do just that (e.g., when reading a corrupted JPG file ...). This caused our software to fail in a customer site and there was very little that testing could do to actually reveal this bug (the exceptions were thrown on a very specific case of corruption) in the first place.

Liron Levy
Sunday, March 02, 2003

I am sure many of us are familiar with COM.  Looking at how COM deals with errors, it uses HRESULT which is almost the same as Levy's model.  Can this scheme be abused ? or strictly enforced?  My experience says programmers are only checking and using S_OK and E_FAIL though it can offer much more.  The problem is people are misusing and abusing the models.  And the code is a mess (which is almost always the case for handling errors)

With Regard to Exception, I agree with Levy because Exception  (as the name implies ) should be reserved for the "Exception" cases where the programmers don't have control over, such as OutOfMemoryException or NetworkConnectionException.  This is where Levy is right about catch-exception but not throwing.  Basically Exceptions should be exclusively reserved for Run-Time Errors only.

Consider this.  Suppose you have hundreds of objects, millions of lines of code, many are throwing their own exceptions, some are throwing the same.  Once it is caught somewhere, Is it easy to trace back to find out who throws it.  From my limited experience, it is almost impossible to tell why the error occurs.  All I can tell is something unexpected happend but not why. 

How does the general result or HRESULT solve the problem?  Maybe it does not but It is more predictable and easier to trace back to the origin than Exception.  Because it does not suddenly jump to the "Catch" clause, it is also easier to find the execution path where the problem occurs.

Both Exceptions and Levy's model, have advantage and disadvantage.  And yes, both can be abused and misused.  Tha'ts why I am still in search of better techniques.

Again, I am looking for the techniques that is independent of Programming Language.

RM
Sunday, March 02, 2003

Agreed that every error condition must be handled, and that good error-handling code has some ugliness to it.  That said, wrapping a try/catch around a block of exception-prone code is a whole lot less ugly than checking error codes at every method invocation.

"By explicitly returning a result object - you actually FORCE
the programmer to consider the consequences of his/her
behavior."

Not so.  At most you mildly encourage the programmer to consider consequences of exceptional conditions, but there is nothing preventing the programmer from omitting the error test.  Not like in Java, where checked exceptions Must Be Handled, Period.

(It's too bad that C++ and C# don't have this nifty feature, but regardless, I'd still use exceptions on those platforms, if only for the benefit of writing my error handling code once instead of at every method invocation).

* * *

So the JDK bombed when you tried to get it to load a corrupted JPG?  Sounds like a good old-fashioned library bug to me.  It's not Sun's coding guidelines to throw unchecked exceptions in cases like that. 

Having never seen it before, I'd bet quarters to nickels it was array index out of bounds exception from a buffer overrun.  Yeah, in theory there should never be bugs in your external libraries, but ... sometimes it happens ...

You should have been glad to see the exception though; if it happened in a C++ library, it would have overrun the buffer silently and then only ten minutes later start coughing up blood.

And while we're on the subject of theory vs. practice ...

The THEORY says that you should check error results after calling a 'dangerous' method, the PRACTICE says that invariably you or your coworkers is going to forget that error check and it's gonna bite you in the ass, as your code fails silently and you have no idea what caused it.

This cure is worse than the disease.

Alyosha`
Sunday, March 02, 2003

This is where macros would come in handy in Java.  I'd often like to say:  catch all unknown exceptions (except for threadinterrupted, rethrow it) and run some failure handler.

n
Sunday, March 02, 2003

Hmm, actually macros aren't needed, this can be done with functions.

I suspect exceptions are good for quickly building code, when you don't mind trashing the current context and going up to one that has enough information to handle the error and cleanup.  But OTOH, when I have a stable design, result objects certainly look worth considering.

n
Sunday, March 02, 2003

Liron wrote: 'In my experience - programmers need to handle every
error condition. For this matter - using try/catch or if/else
is the same thing actually.'

Not really. with exceptions you can check for errors in only a few places. With error return codes you have to check the code on almost every line.

For example

try
{
    foo1();
    foo2();
    foo3();
    ...
}
catch()
{
  // error handling
}

instead of

if(foo1() == ERROR)
{
  // error handling
}
if(foo2() == ERROR)
{
  // error handling
}
if(foo3() == ERROR)
{
  // error handling
}
...

Andrew Reid
Sunday, March 02, 2003

I worked on a huge system, written in very readble Perl (yes, that's possible).
We used the following approach - every function that was supposed to return some result, returned 2 objects: result and an error object (if error occured). Note: in Perl, functions can return arbitrary number of objects. It seems to be cleaner than combining result and error in some artificial "result object", which is not really a single object.

So, our normal code looked like this:

my ($result, $error) = foo();
if ($error) {
    # use $error->code and $error->description
...
}

In the foo() function, the results were returned as

    return (undef, $error); # if errors happened

or

    return $some_result; # if everthing is OK.

Note: there's no need to pass some fake NO_ERROR object or NULL. No error means no error, literally.

Of course, this approach is possible only for languages that support multiple return values: Perl, Python, Ruby, etc.

raindog
Sunday, March 02, 2003

I think the idea of using a generic ResultObject to facilitate is a really bad one.  One of the key features of structured exception handling mechanisms like the one supported by Java is that the exceptions have well defined type information associated with them.  This provides several benefits.  The compiler can help check things being primary among them.  If a sub-procedure is altered to generate additional exceptions, the compiler will show you that you need to modify client procedures, for instance.  Also, the type information is very useful in communicating the semantics of the exceptional condition, much moreso than some arbitrary integer constant.  It's also useful, IMHO, to have language facilities for structuring the exception handling code, rather than putting a big switch statement in there, hopefully as a catchall.  And finally, using scalar constants just begs for compatibility problems between libraries that may use the same numbers differently.  Exceptions defined in namespaces do not suffer this shortcoming.

The ResultObject scheme being discussed is similar to arguments I hear about using languages that don't force you to declare the types of your variables.  This is great for slapping code together quickly, but in taking this route you take away one of your most powerful tools for ensuring correctness:  the compiler's type checking mechanism. 

The main problem I see with exceptions comes from what appears to me to be programmer laziness.  I can't even count the times I've seen someone stick a catch(Exception e) { /*do nothing*/ } into the code, just to shut the compiler up.  It could be that the programmer is working on the mainline code and doesn't want to lose the train of thought by considering exceptional conditions, but that kind of code makes it into the shared code stream enough that I have my doubts.

There are things about currently popular exception management approaches that bug me.  The biggest of these is probably how they work in combination with inheritance.  In most cases, though, I prefer them over alternatives like the one mentioned above by a wide margin.  If one of my teammates used this approach, I'd expect a *very* good reason for it.

Exceptional Coder :P
Sunday, March 02, 2003

My comment on

"Not really. with exceptions you can check for errors in only a few places. With error return codes you have to check the code on almost every line."

It is usually not practical to handle all errors that have
occured in a block of code with ONE handler. You still
need to add proper 'catch' blocks for each error involved.
In this sense - it is no better thn using if/else clauses.

One more thing - using result objects forces a more
comprehensive treatment for errors (how they are
defined, what types of errors are there etc.). Using
exception classes encourages programmers to define
new exception classes without considering their
impact on the software system as a whole. This can
be solved of course (by using proper design) but I've
rarely seen it happen.

Liron Levy
Monday, March 03, 2003

"It is usually not practical to handle all errors that have
occured in a block of code with ONE handler."

It is poor design to handle all errors in a single handler.  Except for RuntimeExceptions, all exceptions are the result of one of two occurrences in properly functioning code based on a good design.  Either A) a calling function violated the assumptions or guarantees of a called function or B) the called function is not honoring its own assumptions or guarantees.  The place where the violation occurred is where the handler should be located.

"You still need to add proper 'catch' blocks for each error involved. In this sense - it is no better thn using if/else clauses."

This is not true.  Exceptions, in Java at least, exhibit an inheritance relationship so that one handler can, though not necessarily should, respond to several related kinds of exception.  To do this with if/then logic you'd have to probably do some complex bit arithmetic or something equally opaque for even simple cases.

Using result objects doesn't force anything on anyone.  Will the compiler refuse to generate code if you don't handle the case when the error number on the result object is equal to fifty-nine?  The code will compile clean if you ignore all errors.  With exceptions you at least have to actively ignore all exceptions with an all encompassing catch block.

"Using exception classes encourages programmers to define new exception classes without considering their impact on the software system as a whole. "

How so?

Exceptional Coder :P
Monday, March 03, 2003

Hate to be the dissenting voice in this thread, but we use exceptions extensively in our C++ code and the results are excellent. The nice thing about them is that you can choose at what level to handle an error.  If any kind of error object is returned, then you are being forced to consider it at the next level up. In our code most errors will force the termination of the operation, with an error message that can be determined at the point the error is detected. If the error is detected five levels down, thats five times I have to write

if (errorReturn.result != NOERROR) {
  return errorReturn;
}

actually more than that because I have to do it once for every method that is called. And what about methods that can't have errors? Do I have to put the check in when I call them too? Someone might modify the method to have an error later.

David Clayworth
Monday, March 03, 2003


A comment to the enthusiastic C++ programmer:

1. In C++ the problem with exceptions is even more
    serious thn in Java because C++ exceptions that
    don't get caught cause the application to terminate
    without proper stack dump.

2. When using C++ callback methods that are called
    from a C library - exceptions don't pass the C stack
    frame barrier. This excludes a lot of legace code ...

3. C++ does not warn you if you didn't handle an exception
    that MAY be thrown by some called method.

Now a general comment: it is usually not practical to
handle errors in higher levels thn the method in which
they've occured. Usually - you need to translate lower
level errors (e.g., SQL error) into higher level errors (e.g.,
"inventory data error") so this "neat" feature of
exception is (IMHO) of little use.

For those of you who consider it "ugly" to handle the
error condition of each specific method using if/else
clauses - consider this: when you use try/catch clauses
and you pack calls to many methods with the "try" clause -
you miss a very important thing - clarity. You no longer
know how each error handler ("catch" clause) relate to
each method called. This is a very big disadvantage in
my opinion.

Consider:

try
{
    func1();
    func2();
    func3();
}
catch(Exception1 e1)
{ // Do I need this handler for func1,2 or 3 ? don't know ...
}
catch (Exception2 e1)
{
}
catch(Exception3 e3)
{
}

Now can you tell me which "catch" clause relate to which
method called in the "try" clause ? no way you can ...
unless you've documented this properly in the code itself
(which is not checked by the compiler ....)

Can you handle the error conditions of func1 and func2
with the same handler ? YES (you'll probably save a few
keystrokes down the road) but your code will be very hard
to maintain later when func1 or func2 have changed.
Another thing - what if someone removes the call to
func1 later during maintenance ? will he remember to
remove the proper "catch" clause ? I don't think so.

Now compare this with:

Result res = func1();
if (res != SUCCESS)
{
}
else
{
}

res = func2();
if (res != SUCCESS)
{
}
else
{
}

res = func3();
if (res != SUCCESS)
{
}
else
{
}

In this code it is clear what code handles the error
conditions of each function called. No chance someone
will ever forget to update the proper error handler when
say func3() changes ...

As I see it - there is really only one way to handle errors
properly - right where the've occured. This is the only
place where you can embed the necessary logic that
is needed to properly manage the problem.

Liron Levy
Monday, March 03, 2003

Not to sound crass, but, are you friggin' kidding me?

This result object and the examples I've seen in this thread so far have been crontrived and almost trollish.  As far as the last example on the result object, I submit that one can be as specific or inspecific on where they place try/catch to be as local or non-localized in handling specific error conditions.  With result objects, you _always_ have to test. Period.

Obviously, as others have pointed out, you can get sloppy with Java exceptions (e.g. method declared as throwing "Exception" , throwing unchecked exceptions, etc).

However, to suggest that the whole Exception paradigm is not as effective as result codes is ludicrous.

I'd contend that most experienced programmers will agree that a hybrid of two approaches allows software to be safer, more reliable and still maintainable.  Just as return code checking is lacking when dealing with error propagation, throwing exceptions is inappropriate in cases like String.indexOf().  In the case of Java, I'm really geeked at the ability to know at _compile time_ that there is some base-level of error processing ocurring in my code.

Russ T.
Monday, March 03, 2003

My maxims on exceptions vs. error codes:

1. When in Rome, do as the Romans.  If you're extending a body of code that already uses error codes, use error codes. If you're extending code that already uses exceptions, use exceptions. Code that is half and half is almost always worse than code that uses either model exclusively.

2. If you don't have garbage collection, don't use exceptions. The major problem here is that when an exception propagates through a routine, that routine needs to release any resources it acquired. By far, the most often acquired resource is a memory allocation. Without garbage collection, your code gets littered with either try...finally statements or with smart pointers, both of which tend to hinder readability.

3. For releasing resources in the face of exceptions, syntactic sugar like C#'s "using" statement is your friend. Yes, you can do it in C++ too, but you end up creating "resource holder" classes for each resource type that might be held. These classes tend to clutter up the code and reduce readability.

4. All things being equal, prefer exceptions. You can get rid of a bunch of code clutter by not having error-code checks everywhere. This is win when you don't have to replace it with lots of resource-allocation and smart-pointer clutter.

But the most important maxim is:

5. Figure out your error-handling strategy at *design* time. Over the years, I've seen innumerable projects fail because people coded the main-line code first, and then thought they could go back and add in error handling. The result is usually lots of bugs that persist for years.

Jim Lyon
Monday, March 03, 2003

It's surpising to find that some people still prefer result codes.

Here's a couple of questions for you:

How do you report errors from constructors and operators?

What do you do about the 95%+ of client programmers that ignore your result codes?

If an exception is not handled, the application ( or at least the thread) exits, for an ignored result code the application continues in an invalid state. If that doesn't concern you, I hope you're not working on anything important :)

punter
Monday, March 03, 2003

Liron, you keep talking about how bad it is that you can ignore exception errors, but that problem is much worse with error codes such as you are suggesting.  The compiler doesn't even complain if you completely ignore the error code and proceed on your merry way.  If anything, the requirement of checking result codes after every function call actually discourages error checking.

Second, there is a problem with your solution that I don't believe anyone has mentioned yet.  It requires casting the result of every method, since your return value is simply Serializable.  You are basically throwing out all compile time typechecking of return values, which seems like an extremely bad idea, especially since the interface no longer describes what the appropriate return type *should* be, and you must rely on the documentation.

One other point.  People mentioned a few cases where a library threw an unchecked runtime exception.  Well... if you weren't able to anticipate this error, you also wouldn't anticipate any other sort of error return from it, so the chances of any program continuing to work when a library encounters some sort of undocumented error condition seems pretty slim to me.  At least with an unchecked exception you get a stack trace to exactly where the error occurred, instead of some memory corruption causing your program to crash 5 minutes down the road

Mike McNertney
Monday, March 03, 2003


My answer to Russ:

Yes I ALWAYS have to check - what's wrong with this ??
What is so trollish in my examples ? - IMHO they don't
even come close to the definition of "contrived".

My answer to punter:

"How do you report errors from constructors and operators?" - You are right - constructors and operators
are the single case where result objects are a problem. In
fact - I feel that the whole exception mechanism was
designed to solve this single problem ... My solution is
simple: don't put code that can fail in constructors. I've
always preferred to do this in dedicated initializers (the
COM way). In large software systems - you often need
to do a second pass initialization anyway (e.g., to pass
pointers to interacting objects etc).

"What do you do about the 95%+ of client programmers that ignore your result codes?" - I make sure they don't.

"If an exception is not handled, the application ( or at least the thread) exits, for an ignored result code the application continues in an invalid state. If that doesn't concern you, I hope you're not working on anything important :) "

On the contrary - when all else fails I'd rather have the
software continue on working thn failing automatically.
Consider an auto-pilot software. Would you prefer the
software to crash when an unhandled error has occured
or to continue on running (with the risk of unpredictable
behavior - but still a risk, not a must).

My answer to Mike:

"The compiler doesn't even complain if you completely ignore the error code and proceed on your merry way.  If anything, the requirement of checking result codes after every function call actually discourages error checking."

I strongly disagree with you. This is strictly a matter of
coding guidelines and conducting proper code reviews. On
my team nobody ever thought of ignoring error codes !

"Second, there is a problem with your solution that I don't believe anyone has mentioned yet.  It requires casting the result of every method, since your return value is simply Serializable." - Believe me it is not such a big problem. In
java you do this on a daily basis anyway ...

"Well... if you weren't able to anticipate this error, you also wouldn't anticipate any other sort of error return from it, so the chances of any program continuing to work when a library encounters some sort of undocumented error condition seems pretty slim to me. " - At least your software
is not GUARENTEED to crash ...

"At least with an unchecked exception you get a stack trace to exactly where the error occurred, instead of some memory corruption causing your program to crash 5 minutes down the road" - We solved this using the
logger mechanism. BTW - we've always used profiling
tools such as Purify to make sure we don't have memory
problems (I wouldn't trust the JVM for these).

Liron Levy
Tuesday, March 04, 2003

Sounds to me like maybe you moved to Java from a Visual Basic or similar background and are trying to make Java behave more like what you are familiar and comfortable with.  If you are going to ignore all of the benefits of using a language like Java, why not just switch back?  At least that way you won't have to fight with the tool so much.

Seriously, your arguments assume the worst possible behavior on the part of someone using Java as it was intended:  poor design, ignorant exception handling policy, contrived sources of errors, and so on.  At the same time, you assume model behavior for someone following your recommendations: perfect code reviews, people that check all dependencies when modifying the behavior of their functions, and the coup de grace, " 'What do you do about the 95%+ of client programmers that ignore your result codes?' - I make sure they don't."

This scheme you propose goes beyond poor design; it is just plain silly. I won't bother repeating or adding to others' arguments, it will obviously do little good.  In a few years though, when you have some experience, you'll see what all these folks are trying to tell you.  Good luck.

get real
Tuesday, March 04, 2003


Reply to "get real":

First - I have about 8 years of programming experience
and I've designed and programmed in many different
environments, so please - answer to the point instead of
blatantly attack me for my thoughts.

"This scheme you propose goes beyond poor design; it is just plain silly" - tell this to the guys that developed COM,
and Windows API, I guess they were silly too ...

BTW - visual basic has an exception mechanism very similar
to Java exceptions so I don't have to switch back. I've
recently talked with a very experienced and respected
VB programmer who told me that on his team they prefer
not to use exceptions at all - only error objects (that are
also supported in VB as an alternative).

Liron Levy
Tuesday, March 04, 2003

Just a couple points of information on exception handling in C++ as responses to a few messages back:

1) C++ exceptions, at least with VC++, DO pass through C language stack frames in my experience. In fact, I've successfully thrown exceptions from Windows message handlers back to my event loop.

2) C++ does not have garbage collection, but exceptions are still a good idea. You use the "Resource Acquisition is Initialization" idiom - never use a plain pointer or handle as your only reference to a resource. Instead, put it in an object (for memory, auto_ptr<> is your friend). This way, the resource will be automatically freed in the destructor.

There's two nice things about this idiom:

1) Your C++ code has very few try catch blocks. Any cleanup is automatically done when the stack unwinds.

2) You don't need explicit deallocation anymore, since the destructors automatically free their resource when the stack frame exits.

Exception safe code in C++ is hard, no doubt about it. But it is often quite clean and elegant once you learn the techniques. And these techniques apply even in the face of return error codes. :-)

Chris Tavares
Tuesday, March 04, 2003

I've enjoyed this thread and it has made me even more convinced that the approach Levy uses, which has long been the approach I favor, is best.

One trick to make sure the caller doesn't totally ignore the result object is to make it the first argument (by reference) to any function that can fail.

Regarding the issue of memory leaks, why on earth is this an issue for anyone writing professional software anymore? Your debug version of ANY software you write should be journalling all allocations and deallocations. Any memory leaks, or buffer overruns that occurred during the program should result in a repot being emitted when your program quits. Doing this is just basic common sense. Whenever I hear about 'memory leaks' and 'security problems caused by buffer overruns' and such I wonder where on earth the developer has been the last twenty years.

Levy, you rock!

Dennis Atkins
Tuesday, March 04, 2003

Hi Liron,

I apologize if my earlier post came across as a personal attack.  Based on your description of your approach to exception handling, I get the impression that you lack experience exploiting modern approaches to this problem.  If all of the examples where you've seen these approaches used are like the ones you have described in your posts, then this impression is correct - you simply have not seen these methods used properly. 

This kind of experience is not necessarily a strong function of time in the industry either.  I've been programming for a long time, and I would say that I lack experience in a number of areas.  For instance, I couldn't write a clean texture mapper to save my life.  Ask me to code a modern device driver?  No way.

Despite this, though, the approach you recommend is simply poor, given the available alternatives.  A number of people have tried to point this out to you, and you respond with contrived arguments as I mentioned in my earlier post.

As far as the folks that designed the Windows API and COM - I'd hardly call them experts in the art of clean design.  Practical?  maybe, but certainly not clean.  Probably everyone on this forum could rant for days about the unnecessary trials they've had to endure as a result of the many poor choices these "experts" made. I know I could.  These technologies were put into place under very different circumstances than we face now, though, so there is some mitigation for the needless complexity, the bizarre behavior, and the many annoying idiosyncracies typical of those technologies.

If their new .NET offerings followed the same course as these older technologies, I'd be happy to look them in the face and tell them how silly their work was.  Fortunately, however, they have learned and adapted.  Structured exceptions didn't find their way into this new technology as a marketing gimmick; they are a superior solution in almost all cases.

Being a respected VB programmer hardly qualifies someone as an authority on the fitness of modern exception handling to a given problem.  He/she probably lacks experience with using exceptions properly too, and is therefore falling back into comfortable patterns carried over from time using VB6 and its ancestors.  All you accomplish when you do this is to complicate your code, to diffuse responsibility for handling exceptions all over the place, and to force people to do a job that the compiler could and should be doing better.  As my earlier post mentioned, you are fighting the tool.

Finally, the post above by Jim Lyon is probably the best practical advice in this entire thread.  If appeals to "purity" and "correctness" do not sway you, at least try his recommendations.  They will make your life easier.

get real
Tuesday, March 04, 2003

"As far as the folks that designed the Windows API and COM - I'd hardly call them experts in the art of clean design."

It should also be mentioned that Win32 and COM were designed to be callable from C code, which rules out the use of exceptions.  MFC throws exceptions, for what it's worth.

Exceptions are a little bit more painful in C++, what with the problems with memory allocation (everything's more painful in that language, actually =-) ... so I could see why one would avoid the try-catch-finally features of that language, but in Java, there's no excuse ...

Alyosha`
Wednesday, March 05, 2003


"As far as the folks that designed the Windows API and COM - I'd hardly call them experts in the art of clean design." - At least their system works (most of the time ..)

Liron Levy
Thursday, March 06, 2003

"On the contrary - when all else fails I'd rather have the
Software continue on working than failing automatically.
Consider an auto-pilot software. Would you prefer the
Software to crash when an unhandled error has occurred
or to continue on running (with the risk of unpredictable
behavior - but still a risk, not a must)."

I would hope the software shuts down and the Pilots take control, because a 'bad' autopilot is still worse than 'no' autopilot.

"Consider:

Try
{
    func1();
    func2();
    func3();
}
Catch (Exception1 e1)
{ // Do I need this handler for func1,2 or 3 ? don't know ...
}
Catch (Exception2 e1)
{
}
Catch (Exception3 e3)
{
}

Now compare this with:

Result res = func1();
if (res != SUCCESS)
{
}
else
{
}

res = func2();
if (res != SUCCESS)
{
}
else
{
}

res = func3();
if (res != SUCCESS)
{
}
else
{
}
"

well at least compare apples to apples:
try
{
    func1();
}
catch(Exception1 e1)
{
}
catch (Exception2 e1)
{
}
catch(Exception3 e3)
{
}

try
{
    func2();
}
catch(Exception1 e1)
{
}
catch (Exception2 e1)
{
}
catch(Exception3 e3)
{
}

try
{
    func3();
}
catch(Exception1 e1)
{
}
catch (Exception2 e1)
{
}
catch(Exception3 e3)
{
}


The exception model is a valid one and should be utilized.
If Function1 can fail, you should wrap it in a Try...Catch..Finally block.
If you put Function2 in the same block and it could also fail, then you would have to handle these exceptions as well.
Now if Function1 causes Function2 to fail, guess what, another error to handle.
Error handling is not meant to fix a broken program, that's our job as programmers.
Error handling is not meant to compensate for bad code design. If you are using a component that doesn't use error handling correctly, or exhibits unpredictable behavior, either don't use it, or contact the developer and work with them to try and fix it (as long as development is still being supported, of course).

Error Handling IS meant to PREVENT unpredictable behavior by "Handling" errors that may be thrown. If handling it means that it cannot continue, then it shouldn't.

You have to decide whether your program can RECOVER from an error, or if it is better to exit the program.

It is an extremely BAD idea to have a program running in an UNKNOWN STATE.

Check out the newer versions of Microsoft Office as an example. If they crash, what happens? They restart.
This puts the program back into a known state.
It crashes again. Well it logs the crash and restarts. Then windows asks if you want to send MS as log of the crash. This log contains the error info. Which helps MS to 'FIX' the problem.

Sure you can try to account for all errors in the beginning, but some will slip through. That is what service releases are for, to fix the problems, as well as add functionality later on.

As far as writing a program that is not being used as a component by someone else, then handle errors any way you want.
But if I was using your component to extend my program, and it didn't handle errors the way I thought was best (as well as the creators of the language) don't expect my business.

CG
Thursday, July 29, 2004

*  Recent Topics

*  Fog Creek Home