digitalmars.D - Q: Exception design questions
- Myron Alexander (27/99) Jun 15 2007 Hello.
- eao197 (16/26) Jun 16 2007 IMHO, the following variant is more preferable for me, because I can
- Myron Alexander (32/87) Jun 16 2007 When I first came across that style, there was a reason for a .raise
- Daniel Keep (8/12) Jun 16 2007 http://www.digitalmars.com/d/phobos/object.html
- Henning Hasemann (17/26) Jun 16 2007 why exactly do you use this pattern? Why not
- Daniel Keep (16/30) Jun 16 2007 I suspect he uses it *because* it's shorter and avoids the temporary
- janderson (4/119) Jun 16 2007 Personally I'd try to put all those sets in the constructor, then u
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
On Sat, 16 Jun 2007 06:32:02 +0400, Myron Alexander <someone somewhere.com> wrote:And this is how I use it: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(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 ();
Jun 16 2007
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 mismatchCan 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
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
Am Sat, 16 Jun 2007 04:32:02 +0200 schrieb Myron Alexander <someone somewhere.com>: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(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 ();
Jun 16 2007
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? HenningI 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
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: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. -Joelpublic 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 16 2007