The Art and Science of Error Handling

  • Thread starter Michael Haufe (\TNO\)
  • Start date
M

Michael Haufe (\TNO\)

I'm developing a significantly sized library that will potentially see
widespread use and abuse. To prevent some of the abuse, I'd like to
ask for some pointers, and some of your experience in developing
scalable robust code, especially in regards to error handling, and
enforcement of proper Object usage.

For the sake of an example, what is your opinion of the following
design pattern?

----------------------------------------------------------------
function Point(x, y, z){
if(!(this instanceof Point))
throw new Error("Point is a constructor")

if(isNaN(+x))
throw new Error("The x argument '"+ x + "' is not a number.");

if(isNaN(+y))
throw new Error("The y argument '"+ y + "' is not a number.");

if(isNaN(+z))
throw new Error("The z argument '"+ z + "' is not a number.");

this.x = parseInt(x,10);
this.y = parseInt(y,10);
this.z = parseInt(z,10);
}

Point.prototype.toString = function(){
if(!(this instanceof Point))
throw new Error("toString() called in an invalid context.")

if(isNaN(+this.x))
throw new Error("The x property '"+ this.x + "' is not a
number.");

if(isNaN(+this.y))
throw new Error("The y property '"+ this.y + "' is not a
number.");

if(isNaN(+this.z))
throw new Error("The z property '"+ this.z + "' is not a
number.");

return this.x + ", " + this.y + ", " + this.z;
}

var p = new Point(1,2,3)
p.toString() // 1, 2, 3

p.x = 4;
p.y = 5;
p.z = "a";

p.toString(); //throws "The z property 'a' is not a number."

var str = p.toString;

str(); // throws "toString called in an invalid context"

var p2 = Point(1,2,3); //throws "Point is a constructor"

----------------------------------------------------------------
 
D

David Mark

I'm developing a significantly sized library that will potentially see
widespread use and abuse. To prevent some of the abuse, I'd like to
ask for some pointers, and some of your experience in developing
scalable robust code, especially  in regards to error handling, and
enforcement of proper Object usage.

For the sake of an example, what is your opinion of the following
design pattern?

----------------------------------------------------------------
function Point(x, y, z){
   if(!(this instanceof Point))
      throw new Error("Point is a constructor")

   if(isNaN(+x))
      throw new Error("The x argument '"+ x + "' is not a number.");

   if(isNaN(+y))
      throw new Error("The y argument '"+ y + "' is not a number.");

   if(isNaN(+z))
      throw new Error("The z argument '"+ z + "' is not a number.");

   this.x = parseInt(x,10);
   this.y = parseInt(y,10);
   this.z = parseInt(z,10);

}

Point.prototype.toString = function(){
   if(!(this instanceof Point))
      throw new Error("toString() called in an invalid context.")

   if(isNaN(+this.x))
      throw new Error("The x property '"+ this.x + "' is not a
number.");

   if(isNaN(+this.y))
      throw new Error("The y property '"+ this.y + "' is not a
number.");

   if(isNaN(+this.z))
      throw new Error("The z property '"+ this.z + "' is not a
number.");

   return this.x + ", " + this.y + ", " + this.z;

}

var p = new Point(1,2,3)
p.toString() // 1, 2, 3

p.x = 4;
p.y = 5;
p.z = "a";

p.toString(); //throws "The z property 'a' is not a number."

var str = p.toString;

str(); // throws "toString called in an invalid context"

var p2 = Point(1,2,3); //throws "Point is a constructor"

----------------------------------------------------------------

Over-engineered. Put that stuff in the documentation. ;)
 
O

optimistx

David Mark wrote:
....
Over-engineered. Put that stuff in the documentation. ;)
Interesting. What are the disadvantages of being over-engineered?

As a dumb and lazy user I would be happy to get clear, kind, thorough error
messages immediately when I make the error. If I have to start reading a
manual with 300 pages (either paper or electronic) and trying to find the
location of this error, I would be lazy and do something else, if possible.
May be use some other software.

Ok, other users may love to read the manuals, hours and hours, and very
happily then find interesting other details also, 17 digits precision, wov!
interesting!
 
S

Stevo

optimistx said:
David Mark wrote:
...
Interesting. What are the disadvantages of being over-engineered?

For a web application where all the code has to come down the pipe, the
bloat of over-engineering leads to a slower experience. It's an
environment where things like jquery, prototype, yui, and the like are
catering to far more features than what one particular user needs, and
being fault tolerant to far more errors than a careful programmer needs
them to be. One that reads the API carefully and knows what the I/O is
for a set of functions will find the library over-engineered if it
treats their calls with suspicion by default.
 
T

Thomas 'PointedEars' Lahn

Michael said:
I'm developing a significantly sized library that will potentially see
widespread use and abuse. To prevent some of the abuse, I'd like to
ask for some pointers, and some of your experience in developing
scalable robust code, especially in regards to error handling, and
enforcement of proper Object usage.

For the sake of an example, what is your opinion of the following
design pattern?

"is called _as_ a constructor". By definition, every Function object is a
constructor as it implements the [[Construct]] method.
if(isNaN(+x))
throw new Error("The x argument '"+ x + "' is not a number.");

if(isNaN(+y))
throw new Error("The y argument '"+ y + "' is not a number.");

if(isNaN(+z))
throw new Error("The z argument '"+ z + "' is not a number.");

While throwing an exception is in itself a more useful and extensible
approach than returning an error value, it is questionable how throwing a
general exception could be helpful to the caller.

It is not necessary to convert a value to number for isNaN(); the method
does that internally, probably more efficient.

At this point I would also not use the `throw', `try', `catch' or `finally'
keyword unguarded in a general purpose library.
this.x = parseInt(x,10);
this.y = parseInt(y,10);
this.z = parseInt(z,10);

As parseInt() parses the first argument into a number according to the
number base given by the second argument (or an interpolated number if the
second argument is missing), it does not make sense to throw exceptions if
conversion to number is not possible.
}

Point.prototype.toString = function(){
if(!(this instanceof Point))
throw new Error("toString() called in an invalid context.")

if(isNaN(+this.x))
throw new Error("The x property '"+ this.x + "' is not a
number.");

if(isNaN(+this.y))
throw new Error("The y property '"+ this.y + "' is not a
number.");

if(isNaN(+this.z))
throw new Error("The z property '"+ this.z + "' is not a
number.");

return this.x + ", " + this.y + ", " + this.z;

Since the property values are converted to String anyway, it does not
make sense to throw exceptions if they cannot be converted to Number. (An
the unary `+'s are redundant again.)

In fact, I cannot think of a situation right now where I would throw an
exception in a getter. Why would you want to prevent this method from being
reused? Other objects that do not have the intial value of Point.prototype
in their prototype chain, may also have `x', `y', and `z' properties.
}

var p = new Point(1,2,3)
p.toString() // 1, 2, 3

p.x = 4;
p.y = 5;
p.z = "a";

p.toString(); //throws "The z property 'a' is not a number."

You should have thrown the exception in the setter for the `z' property instead.
var str = p.toString;

str(); // throws "toString called in an invalid context"

See above.
var p2 = Point(1,2,3); //throws "Point is a constructor"

Apparently you determine that Point() is called as a constructor; why can't
Point be called as a factory, too, and return a reference to the newly
created object then?


PointedEars
 
A

abozhilov

On Sep 18, 9:39 pm, Thomas 'PointedEars' Lahn <[email protected]>
wrote:

Hi Thomas.
As parseInt() parses the first argument into a number according to the
number base given by the second argument (or an interpolated number if the
second argument is missing), it does not make sense to throw exceptions if
conversion to number is not possible.

For my, usefully of parseInt is, he doesn't convert ToInt32 first
argument.
See example:

var a = 9999999999999.102012012010210;
alert(parseInt(a));
alert(a & a);
Apparently you determine that Point() is called as a constructor; why can't
Point be called as a factory, too, and return a reference to the newly
created object then?

How you implement that? Something like this:

function Construct(a, b, c)
{
var callee = arguments.callee;
if (!(this instanceof callee))
{
return new callee(a, b, c);
}
}

But how to pass in normal way arguments to constructor? Do you have
any suggestion?
Thanks.
 
T

Thomas 'PointedEars' Lahn

abozhilov said:
For my, usefully of parseInt is, he doesn't convert ToInt32 first
argument.

Neither does unary `+'.
See example:

var a = 9999999999999.102012012010210;
alert(parseInt(a));
alert(a & a);
So?


How you implement that? Something like this:

function Construct(a, b, c)
{
var callee = arguments.callee;
if (!(this instanceof callee))
{
return new callee(a, b, c);
}
}
Yes.

But how to pass in normal way arguments to constructor? Do you have
any suggestion?

Yes, search for `construct'.


PointedEars
 
A

abozhilov

Yes, search for `construct'.

Yes, but how i can call directly [[Construct]] method, without
emulation and without `new' operator?

I write simple emulation on [[Construct]], but i don't like this code:

function Construct(a, b, c)
{
var callee = arguments.callee;
if (!(this instanceof callee))
{
var o = function(){};
o.prototype = callee.prototype;
return callee.apply(new o(), arguments);
}
return this;
}

I don't like this code, because will be execute [[Call]] method of
intermediate `object', who referred from `o`. Do you know any way to
invoke [[Construct]] without `new' operator and without dummy
emulation like this?

Thanks in advance, for your response.
 
T

Thomas 'PointedEars' Lahn

abozhilov said:
Thomas said:
Yes, search for `construct'.

Yes, but how i can call directly [[Construct]] method, without
emulation and without `new' operator?

I do not think that can be done.
I write simple emulation on [[Construct]], but i don't like this code:

function Construct(a, b, c)
{
var callee = arguments.callee;
if (!(this instanceof callee))
{
var o = function(){};
o.prototype = callee.prototype;
return callee.apply(new o(), arguments);
}
return this;
}

I don't like this code, because will be execute [[Call]] method of
intermediate `object', who referred from `o`.

At first sight, I liked this approach much better than what I suggested,
not least because it avoids eval().

However, there are two problems with it:

1. It requires to be placed in a user-defined constructor.

2. If the former was worked around, by contrast it did not work with
Function instances that return a different value when [[Call]]ed,
like Date(). So, e.g., `new Date()' would result in a reference
to a Date instance, while `Date.construct()', which should be
equivalent, would return a string value.
Do you know any way to invoke [[Construct]] without `new' operator
and without dummy emulation like this?
No.

Thanks in advance, for your response.

You are welcome.


Regards,

PointedEars
 
G

Garrett Smith

Thomas said:
abozhilov said:
Thomas said:
But how to pass in normal way arguments to constructor? Do you have
any suggestion?
Yes, search for `construct'.
Yes, but how i can call directly [[Construct]] method, without
emulation and without `new' operator?

I do not think that can be done.

Sure it can.

There are several places in the spec that has the text "as if by the
expression new Object()".

Create an object without new:

{}
[]
 
T

Thomas 'PointedEars' Lahn

Garrett said:
Thomas said:
abozhilov said:
Thomas 'PointedEars' Lahn wrote:
But how to pass in normal way arguments to constructor? Do you have
any suggestion?
Yes, search for `construct'.
Yes, but how i can call directly [[Construct]] method, without
emulation and without `new' operator?
I do not think that can be done.

Sure it can.

There are several places in the spec that has the text "as if by the
expression new Object()".

Create an object without new:

{}
[]

You are correct, but you miss the point.


PointedEars
 
A

abozhilov

2. If the former was worked around, by contrast it did not work with
   Function instances that return a different value when [[Call]]ed,
   like Date().  So, e.g., `new Date()' would result in a reference
   to a Date instance, while `Date.construct()', which should be
   equivalent, would return a string value.

Excellent point. Not only Date constructor behaviour is different when
use [[Call]] and [[Construct]]. String, Boolean and Number too.

Example with Boolean:

var bool_obj = new Boolean(true);
var bool_val = Boolean(true);

bool_obj will be store reference value to `object' who been create
from [[Construct]] of Boolean function.
bool_val use only [[Call]] of Boolean function. And here isn't
explicit provide `object' who internal [[Prototype]] referred to
Boolean.prototype for the this value. And bool_val store primitive
boolean value from ToBoolean(first_argument);
 
T

Thomas 'PointedEars' Lahn

abozhilov said:
Thomas said:
2. If the former was worked around, by contrast it did not work with
Function instances that return a different value when [[Call]]ed,
like Date(). So, e.g., `new Date()' would result in a reference
to a Date instance, while `Date.construct()', which should be
equivalent, would return a string value.

Excellent point. Not only Date constructor behaviour is different when
use [[Call]] and [[Construct]]. String, Boolean and Number too.
[...]

"e.g." is the abbreviation of the Latin "exempli gratia", which means
"for example" in English.
Example with Boolean:

var bool_obj = new Boolean(true);
var bool_val = Boolean(true);
[...]

Whereas it is pointless and potentially harmful to use `new Boolean' in the
first place.


PointedEars
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,821
Latest member
AleidaSchi

Latest Threads

Top