[Date Prev][Date Next][Thread Prev][Thread Next][Author Index][Date Index][Thread Index]

easy to miss bug (even in structured debug)



Abstract: I haven't had a problem with NULLs because I religiously use
some simple informal conventions.  These conventions should be
sufficient for structured debugging purposes but not for static type
checking. 


NULLs are common in C code mostly as a way of signalling failure to
find/create/etc the expected object.  I.e., they are a convention for
indicating an exception to the caller.  Were this convention to be
used universally, one could rarely use functional compostion in
writing code (our original attempt to write X++ code was in this
style).  Typically C programs use this convention sporadically, so one
can frequently use functional composition, but it is hard to remember
where its safe.

However, because we signal exceptions with BLASTs, the normal return
path corresponds to success, and therefore NULLs are much rarer in our
code.  This results in functional composition being safe far more
often.  As Dean points out, we still need a way of knowing precisely
where it's safe.

I find that the use of a few simple conventions is perfectly
sufficient for my needs here.  The conventions correspond to an
informal version of distinguishing "Foo *" and "Foo * | NULL".  I
agree with Ravi that "BOTTOM" is broken because it loses too much
useful type checking information.  Most or all of these conventions
are used already by many of us.

The conventions:

1) An expression of type "Foo *" may NOT have the value NULL unless
this possibility is explicitly notated, either with an explicit
comment or by one of the following conventions.  Note that this
convention is the opposite of Michael's suggestion that a "Foo *" may
be NULL unless the contract guarantees that it cannot.  Were we
programming in the C style of NULL use (i.e., without BLAST),
Michael's suggestion would be right.  However, in our style the
NULL-allowed case is quite rare, and therefore the other default
assumption makes sense.

2) Any function or member function that may return NULL must begin
with the prefix "fetch".  Should there be a corresponding function
which BLASTs rather than returning NULL, but otherwise operates
identically, it should have the same name but begin with "get" instead
of "fetch".

3) Any function parameter that may be passed a NULL be declared with
NULL as its default value.  This is intuitively sensible as NULL can
then be read as indicating the lack of an argument in that position.

3a) (A general convention that supports the NULL issue, but isn't
specific to it.)  When defining a function whose declaration specifies
that a given parameter has a default value, repeat the default value
specification in a comment (since the compiler won't let you repeat it
plain).  For example:

zipx.hxx
--------

extern void zip (Foo * f = NULL);

zipx.cxx
--------

void zip (Foo * f /*=NULL*/)
{
    ...
}

Unfortunately, by putting the latter in a comment we lose any checking
that it agrees with the declaration.  Oh well.

4) Explicitly say /* may be NULL */ immediately to the right of the
declarations of instance variables that may be NULL.  Unfortunately,
as opposed to the other conventions, this caveat is not apparent where
the possibly NULL value is being *used*.  This problem is mitigated by
#4a below.

4a) (A general convention that supports the NULL issue, but isn't
specific to it.)  Never never never put an instance variable (data
member) in a non-private section.  One of the *wonderful* things about
C++ is that one can provide (via inlines) procedural interfaces to
data with ZERO cost in efficiency, and still protect the client from
knowledge of the server's representation.  (The client source code is
protected.  The client object code is nicely knowledgeable.)

Therefore, the comment provided by #4 applies only across the
implementation of the one class where this comment appears.  If
there's a member function which provides the value of this instance
variable, by #2 it must begin with "fetch".

5) Some convention needs to be determined for fluids, but this issue
hasn't come up there yet.


We should take these conventions to be part of the meaning of a class
declaration as a contract.  When we do a structures debug of client
code, we need not check the NULL case unless our conventions say it's
possible.  When we structure-debug server code (the contract
implementation), we should check that we can't generate a NULL when
these conventions don't explicitly permit it.

I have followed the above conventions rather strictly, and cannot
remember having had *any* of the NULL problems that so plagued me in
earlier days.  In short, the above conventions have worked for me.  

To any of you other X++ programmers who are using these conventions:
Have you also experienced a pleasing absence of NULL bugs?  If the
answer is yes, are we trying to solve a problem we don't have?