www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Q: Exception design questions

reply Myron Alexander <someone somewhere.com> writes:
Hello.

I am struggling with creating an exception design for my library. I have 
written libraries and exception mechanisms before but those libraries 
were focused on a core problem for which all use-cases are known 
upfront. With this library, because it is distributed, I am not in 
control of how it is used so I am struggling to envision all possibilities.

What I have decided is to restrict the number of exceptions to just 
those that either define a domain or declare a specific recoverable 
exception. For my library, there will be very few recoverables, mostly 
to do with data conflicts and database locks.

I am of the type that, as a library/framework architect, tries to 
provide the most information possible to the developer such that the 
developer is able to quickly pinpoint the problem and resolve it.

With that in mind, I needed a flexible exception class that could handle 
all the information known about the state at the time of the exception. 
I decided on a property bag that will hold all the information as 
properties.

This is what I came up with:

 public class SqlException : Exception {
 
    this (char[] msg) {
       super (msg);
    }
 
    typeof(this) setSql (char[] sql) {
       m_propertyBag[K_SQL] = box (sql);
       return this;
    }
 
    typeof(this) setProperty(T) (char[] property, T value) {
       m_propertyBag [property] = box (value);
       return this;
    }
 
    void raise () {
       throw this;
    }
 
    char[] toString () {
 
       /* Format properties for output. The properties are listed one to a line
        * of format 'name: value'.
        */
       char[] pstr;
 
       foreach (p; m_propertyBag.keys.sort) {
          pstr ~= "\n" ~ p ~ ": " ~ m_propertyBag[p].toString ();
       }
 
       if (pstr.length > 0) {
          return msg ~ "\n" ~ pstr;
 
       } else {
          return msg;
       }
    }
 
    char[] sql () {
       if (K_SQL in m_propertyBag) {
          return unbox!(char[])(m_propertyBag[K_SQL]);
 
       } else {
          return null;
       }
    }
 
    Box[char[]] propertyBag () {
       return m_propertyBag;
    }
 
    protected Box[char[]] m_propertyBag;
    
    private static const final K_SQL = "SQL";
 }
And this is how I use it:
 (new SqlProgrammingException (
    "Invalid bind type. Mixing single and multiple value rows. "
    "The first argument type is other than Box[], thus it is "
    "assumed that the rest of the arguments are single value "
    "rows."))
 .setSql (operation)
 .setProperty ("ValueRow", i)
 .setProperty ("ValueType", t)
 .raise ();
which outputs:
 Error: Invalid bind type. Mixing single and multiple value rows. The first
 argument type is other than Box[], thus it is assumed that the rest of the
 arguments are single value rows.
 
 SQL: insert into atable values (?,?,?)
 ValueRow: 1
 ValueType: std.boxer.Box[]
My questions are: 1. Is there a better way to design the exceptions? 2. If you have experience with this style what shortcomings did you notice. 3. Is there anything I have missed (conceptually or practically)? 4. Is there anything I should remove, or add? Thanks ahead, Myron.
Jun 15 2007
next sibling parent reply eao197 <eao197 intervale.ru> writes:
On Sat, 16 Jun 2007 06:32:02 +0400, Myron Alexander  
<someone somewhere.com> wrote:

 And this is how I use it:

 (new SqlProgrammingException (
    "Invalid bind type. Mixing single and multiple value rows. "
    "The first argument type is other than Box[], thus it is "
    "assumed that the rest of the arguments are single value "
    "rows."))
 .setSql (operation)
 .setProperty ("ValueRow", i)
 .setProperty ("ValueType", t)
 .raise ();
IMHO, the following variant is more preferable for me, because I can easily find all places where an exception is thrown simply by `grep throw` (and `throw` keywords is highlighted in editors): throw ((new SqlProgrammingException ( "Invalid bind type. Mixing single and multiple value rows. " "The first argument type is other than Box[], thus it is " "assumed that the rest of the arguments are single value " "rows.")) .setSql (operation) .setProperty ("ValueRow", i) .setProperty ("ValueType", t)); -- Regards, Yauheni Akhotnikau
Jun 16 2007
parent reply Myron Alexander <someone somewhere.com> writes:
eao197 wrote:
 IMHO, the following variant is more preferable for me, because I can 
 easily find all places where an exception is thrown simply by `grep 
 throw` (and `throw` keywords is highlighted in editors):
 
 throw ((new SqlProgrammingException (
     "Invalid bind type. Mixing single and multiple value rows. "
     "The first argument type is other than Box[], thus it is "
     "assumed that the rest of the arguments are single value "
     "rows."))
     .setSql (operation)
     .setProperty ("ValueRow", i)
     .setProperty ("ValueType", t));
 
When I first came across that style, there was a reason for a .raise method. I cannot recall the reason, just that it was used. I do prefer using throw so I have decided to drop raise. I forgot to show an example of why I chose a property bag mechanism. Here is an example:
    try {
       try {
          // Within position bind method. The name of the bind parameter is not
          // known, only the position.
          throw (new SqlProgrammingException ("Unable to bind parameter
value."))
             .setVendorMsg ("Data type mismatch")
             .setVendorCode (20)
             .setSqlState ("2200G")
             .setSql ("SELECT * FROM TABLE WHERE A = :somevalue")
             .setProperty ("BindPosition", 1)
             ;
 
       } catch (SqlException e) {
          // Within name lookup method. The name lookup method finds the
position
          // of a parameter based on the name. It then calls the position
          // bind method.
          e.setProperty ("BindName", "somevalue");
          throw e;
       }
 
    } catch (SqlProgrammingException e) {
       writefln ("%s: \n---\n%s\n----", typeid(typeof(e)), e);
 
    } catch (SqlDatabaseException e) {
       writefln ("%s: \n---\n%s\n----", typeid(typeof(e)), e);
 
    } catch (Exception e) {
       writefln ("%s: \n---\n%s\n----", typeid(typeof(e)), e);
    }
In this example, when the exception is raised, I do not know the name of the parameter, just it's position. The name is known higher up the call hierarchy. With the property mechanism, I can then add the name and rethrow. Without the property mechanism, I would either have to create a new exception class, or recreate the exception with the information appended to the message string. Another way to do the above would be to have the type mismatch error in a SqlDataException, wrapped in a SqlBindException, which is a SqlProgrammingException. The data exception would contain specific information about the type expected and the type received, the sql state and vendor code/message, and the sql statement. The bind exception would have the position and name. So the print out would be like:
 Error: Unable to bind parameter value
 
 BindName: somevalue
 BindPosition: 1
 
 caused by: Data type mismatch
 
 DataTypeExpected: numeric
 DataTypeReceived: string
 Sql: SELECT * FROM TABLE WHERE A = :somevalue
 SqlState: 2200G
 VendorCode: 20
 VendorMsg: Data type mismatch
Can anyone point me to Walter's design explanation for how Error and Exception are supposed to be structured. I'm guessing that Walter intends Errors to be more generic (problem domain rather than individual problem) and that Exception to be very specific to the exact problem. I'm also considering moving away from the PEP249 exception hierarchy and going towards the JDBC 4 design based on sql states. I'm experimenting here, I honestly have no clue what I am doing. Like a rat in a maze, I can smell the cheese but can't see it. Any help and thoughts would help alot. Thanks ahead, Myron. dprogramming...myron...alexander...com replace the first ... with , remove the second, and replace the third with ".".
Jun 16 2007
parent Daniel Keep <daniel.keep.lists gmail.com> writes:
 Can anyone point me to Walter's design explanation for how Error and
 Exception are supposed to be structured. I'm guessing that Walter
 intends Errors to be more generic (problem domain rather than individual
 problem) and that Exception to be very specific to the exact problem.
http://www.digitalmars.com/d/phobos/object.html Error's are for unrecoverable errors (like running out of memory) and Exceptions are for recoverable ones. The sad thing is that Error derives from Exception, which means it's impossible to write a catch statement just for recoverable errors. You'd have to write one for Error that re-throws the exception, then a later one for Exceptions. -- Daniel
Jun 16 2007
prev sibling next sibling parent reply Henning Hasemann <hhasemann web.de> writes:
Am Sat, 16 Jun 2007 04:32:02 +0200
schrieb Myron Alexander <someone somewhere.com>:

 (new SqlProgrammingException (
    "Invalid bind type. Mixing single and multiple value rows. "
    "The first argument type is other than Box[], thus it is "
    "assumed that the rest of the arguments are single value "
    "rows."))
 .setSql (operation)
 .setProperty ("ValueRow", i)
 .setProperty ("ValueType", t)
 .raise ();
why exactly do you use this pattern? Why not auto ex = new SqlProgrammingException("text"); with(ex) { setSql(operation); setProperty("ValueRow", i); setProperty("ValueType", t); } throw ex; Ok that are 2 additional lines and a temporary variable but that shouldnt be a problem, or? Henning -- GPG Public Key: http://keyserver.ganneff.de:11371/pks/lookup?op=get&search=0xDDD6D36D41911851 Fingerprint: 344F 4072 F038 BB9E B35D E6AB DDD6 D36D 4191 1851
Jun 16 2007
parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Henning Hasemann wrote:
 why exactly do you use this pattern? Why not
 
 auto ex = new SqlProgrammingException("text");
 with(ex) {
   setSql(operation);
   setProperty("ValueRow", i);
   setProperty("ValueType", t);
 }
 throw ex;
 
 Ok that are 2 additional lines and a temporary variable but that
 shouldnt be a problem, or?
 
 Henning
I suspect he uses it *because* it's shorter and avoids the temporary variable. :P If I didn't hate member call chaining so much, I'd probably be tempted to do the same; using with makes the code a bit unwieldy. Not terribly constructive, but wouldn't it be nice if D had statements-as-expressions? Then it would simplify to: throw with(new SqlProgrammingException("text")) { setSql(operation); setProperty("ValueRow", i); setProperty("ValueType", t); } One line longer for the closing brace, but removes the need for both the temporary and the chained function call. It's not going to happen, but we can dream, right? :P -- Daniel
Jun 16 2007
prev sibling parent janderson <askme me.com> writes:
Myron Alexander wrote:
 Hello.
 
 I am struggling with creating an exception design for my library. I have 
 written libraries and exception mechanisms before but those libraries 
 were focused on a core problem for which all use-cases are known 
 upfront. With this library, because it is distributed, I am not in 
 control of how it is used so I am struggling to envision all possibilities.
 
 What I have decided is to restrict the number of exceptions to just 
 those that either define a domain or declare a specific recoverable 
 exception. For my library, there will be very few recoverables, mostly 
 to do with data conflicts and database locks.
 
 I am of the type that, as a library/framework architect, tries to 
 provide the most information possible to the developer such that the 
 developer is able to quickly pinpoint the problem and resolve it.
 
 With that in mind, I needed a flexible exception class that could handle 
 all the information known about the state at the time of the exception. 
 I decided on a property bag that will hold all the information as 
 properties.
 
 This is what I came up with:
 
 public class SqlException : Exception {

    this (char[] msg) {
       super (msg);
    }

    typeof(this) setSql (char[] sql) {
       m_propertyBag[K_SQL] = box (sql);
       return this;
    }

    typeof(this) setProperty(T) (char[] property, T value) {
       m_propertyBag [property] = box (value);
       return this;
    }

    void raise () {
       throw this;
    }

    char[] toString () {

       /* Format properties for output. The properties are listed one 
 to a line
        * of format 'name: value'.
        */
       char[] pstr;

       foreach (p; m_propertyBag.keys.sort) {
          pstr ~= "\n" ~ p ~ ": " ~ m_propertyBag[p].toString ();
       }

       if (pstr.length > 0) {
          return msg ~ "\n" ~ pstr;

       } else {
          return msg;
       }
    }

    char[] sql () {
       if (K_SQL in m_propertyBag) {
          return unbox!(char[])(m_propertyBag[K_SQL]);

       } else {
          return null;
       }
    }

    Box[char[]] propertyBag () {
       return m_propertyBag;
    }

    protected Box[char[]] m_propertyBag;
       private static const final K_SQL = "SQL";
 }
And this is how I use it:
 (new SqlProgrammingException (
    "Invalid bind type. Mixing single and multiple value rows. "
    "The first argument type is other than Box[], thus it is "
    "assumed that the rest of the arguments are single value "
    "rows."))
 .setSql (operation)
 .setProperty ("ValueRow", i)
 .setProperty ("ValueType", t)
 .raise ();
which outputs:
 Error: Invalid bind type. Mixing single and multiple value rows. The 
 first
 argument type is other than Box[], thus it is assumed that the rest of 
 the
 arguments are single value rows.

 SQL: insert into atable values (?,?,?)
 ValueRow: 1
 ValueType: std.boxer.Box[]
My questions are: 1. Is there a better way to design the exceptions? 2. If you have experience with this style what shortcomings did you notice. 3. Is there anything I have missed (conceptually or practically)? 4. Is there anything I should remove, or add? Thanks ahead, Myron.
Personally I'd try to put all those sets in the constructor, then u don't need to expose these to the catcher of the exception. -Joel
Jun 16 2007