flame: _Writing Solid Code_ 
Author Message
 flame: _Writing Solid Code_

I've been reading this book, and I must say that it isn't particularly
useful, except to the dilettante. Most of the good advice is something
that an experienced programmer would find self-evident.

I take great exception to the author's suggestion that a function
which allocates memory should have an interface like:

        int getmemory(void **ptr, size_t);

so that the indication of failure is passed in a different channel
from the pointer value.

This is valid in the case of proposing an alternate design for getchar(), but
in the case of allocator, there is nothing wrong with taking advantage of the
special null pointer value which is reserved by the type system to serve as a
sentinel value!

I anticipated that the author would mention something about the incorrectness
of using such an allocator to do, for instance:

        double *x;
        if (!getmemory(x, sizeof *x)) {
                /*...*/
        }

but he makes no mention of it. I'm led to suspect that he is unaware
of the requirement that his proposed interface requires the programmer
to use a temporary ``void *'' object to retrieve the pointer and then
convert that object's value to the target pointer type in an extra
step.

Also, in Chapter 4, ``Step Through Your code'' I was able to immediately spot
a bug in an example of incorrect code which the author claims he was only able
to find by stepping through it with his de{*filter*} and examining the contents of
variables. The error sticks out like a sore thumb to anyone who knows the C
language. Of course, it would stick out more if he didn't use the silly
typedef name ``byte'' when he means ``unsigned char'', without even providing
the type definition as part of the example.

The author goes on to say that b << 24 is legal ANSI C, which it most
certainly is not! It is undefined to request a shift by more than the width
of the type being shifted, less one.

The compiler the author was using had 16 bit ints, and 8 bit chars, which
means that the unsigned char b in the shift expression is promoted to type
int.  Such a value can only be shifted by up to 15 in a maximally portable
program.

I refuse to take inane advice from this book, such as using a de{*filter*} as a
verification tool. This sort of approach is well suited for a mentality which
equates the compiler with the language. I don't disbelieve that defects can be
uncovered this way, but many kinds of portability problems will not be,
including undefined behaviors!  Stepping through the statement i = a[i++]; may
only assure the clueless programmer that everything is fine, because
i contains the expected result afterwards. Some development systems don't let
you step through optimized code, and who knows how undefined behavior
is transformed under optimization?

I believe that most of the suggestions are simply the authors knee-jerk
reactions to genuine problems that he has encountered while in the trenches;
the methods may work for him personally, and many of them are laudable, but
that hardly makes them into general principles for everyone. There is a
complete lack of metnion of formal methods. It's hard to separate the general
truths from observations that are specific to the C language; perhaps the book
should be called ``Writing Solid C''.

An important element that is missing is analysis; there are no presented
case studies of a group of programmers using some method versus control
who do not use that method. Why should I believe that stepping through
code using a de{*filter*} is more effective than an equal amount of effort spent
doing something else?   Or why should I believe it to be false? I don't
know, and neither does the author.  All the reader gets is anecdotes about the
author's various experiences at a particular software company which are
supposed to generalize to any software development activity.

My personal recommendation is that this book is not so bad that it requires
combustion---it's far better than anything from H. Schildt---but if you don't
already have it, don't bother getting it.

If you want interesting anecdotes, read _The Psychology Of Computer
Programming_ by Gerald M. Weinberg instead.



Wed, 23 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_

: I've been reading this book, and I must say that it isn't particularly
: useful, except to the dilettante. Most of the good advice is something
: that an experienced programmer would find self-evident.

I don't think it's meant for the experienced programmer (or for the
dilettante, who wouldn't buy it).  It's meant for the inexperienced.

[...]
: I refuse to take inane advice from this book, such as using a de{*filter*} as a
: verification tool. This sort of approach is well suited for a mentality which
: equates the compiler with the language. I don't disbelieve that defects can be
: uncovered this way, but many kinds of portability problems will not be,
: including undefined behaviors!  Stepping through the statement i = a[i++]; may
: only assure the clueless programmer that everything is fine, because
: i contains the expected result afterwards. Some development systems don't let
: you step through optimized code, and who knows how undefined behavior
: is transformed under optimization?

A de{*filter*} is of no use in checking the compiler; it _is_ of use in
checking algorithms, and especially APIs.  Running though code watching
every return from an API call, even those you _know_ to be correct, is
quick, simple and effective.  Once you are very familiar with the API
you can ignore this step, but again, for the inexperienced it's useful.

Will



Wed, 23 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_



Quote:
>I take great exception to the author's suggestion that a function
>which allocates memory should have an interface like:
>    int getmemory(void **ptr, size_t);
>so that the indication of failure is passed in a different channel
>from the pointer value.

The author's general point is correct: in-band error signaling
encourages usage errors.  Consider while((c=getc(fp))==EOF)...
which doesn't work right in some codeset environments.

In the case of memory allocation, unfortunately C forces the
interface to take into account the object type:
        _bool AllocArray( n_elts, elt_type, elt_type ** );
(Implementation of this has to be done as a macro due to
limitations of C.)

In an environment where one is using a general exception mechanism,
memory allocation clearly should throw an exception when the request
cannot be satisfied.

Books like this one are good to read and mull over, forming one's
own opinions on the topics discussed.



Wed, 23 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_



Quote:


>>I take great exception to the author's suggestion that a function
>>which allocates memory should have an interface like:
>>        int getmemory(void **ptr, size_t);
>>so that the indication of failure is passed in a different channel
>>from the pointer value.

>The author's general point is correct: in-band error signaling
>encourages usage errors.  Consider while((c=getc(fp))==EOF)...
>which doesn't work right in some codeset environments.

Yes, I couldn't agree more. However, a little consideration makes it apparent
that this observation does not extend to pointer-returning memory allocators
allocation routines. There is a difference, because each pointer type has a
reserved value that is guaranteed to not be a pointer to any valid object.
Whereas the integer types have no reserved value for indicating an error.

Returning NULL to indicate allocation failure is perfectly legitimate,
whereas returning an EOF sentinel value in place of a character code
is a bit of a hack.

The general point is valid, but general principles should be supported by good
examples. Details matter.

Why should I trust an author who seems to think that void ** is a generic
pointer to pointer?

Quote:
>Books like this one are good to read and mull over, forming one's
>own opinions on the topics discussed.

Exactly. It's not that you can't get anything out of it at all, but it's just
too irritating in places. I find it vaguely insulting that anyone would throw
such trash on my desk, even if I'm new. :) :)


Wed, 23 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_

Quote:

>Exactly. It's not that you can't get anything out of it at all, but it's just
>too irritating in places. I find it vaguely insulting that anyone would throw
>such trash on my desk, even if I'm new. :) :)

You're insulted that someone tries to help you?  Boy, I'm glad we
don't work anywhere near each other!

Buy a book on teamwork.

gg



Fri, 25 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_

Quote:


> >Exactly. It's not that you can't get anything out of it at all, but it's just
> >too irritating in places. I find it vaguely insulting that anyone would throw
> >such trash on my desk, even if I'm new. :) :)

> You're insulted that someone tries to help you?  Boy, I'm glad we
> don't work anywhere near each other!

If someone tried to help you bake a cake by giving you the recipe for quiche,
within a book entitled 'Cake Baking for Dummies' would you be terrible
thrilled?

--
(initiator of the campaign for grumpiness where grumpiness is due in c.l.c)

Attempting to write in a hybrid which can be compiled by either a C compiler
or a C++ compiler produces a compromise language which combines the drawbacks
of both with the advantages of neither.



Fri, 25 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_

Quote:

> In my experience, limited though it is, de{*filter*}s are basically useless;
> I hardly use them at all anymore.  If I have concerns about code, I add
> a debugging output stream, and, when I have trouble, I run the program
> with debugging output on.  At any given time, I can easily see whether or
> not my results meet my expectations, and if they don't, I know where they
> went wrong.

I've never written any production code, so extended debugging issues
have never really come up with me.  What precisely do you mean by a
debugging output stream?

Could you provide a small example of what this would look like?  Are we
talking about macro wrappers around expected values, or something more
sophisticated?

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

Toronto, Canada     http://www.*-*-*.com/
----------------------------------------------------



Fri, 25 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_


Quote:

>An important element that is missing is analysis; there are no presented
>case studies of a group of programmers using some method versus control
>who do not use that method. Why should I believe that stepping through
>code using a de{*filter*} is more effective than an equal amount of effort spent
>doing something else?   Or why should I believe it to be false?

I can respond to that one.

In my experience, limited though it is, de{*filter*}s are basically useless;
I hardly use them at all anymore.  If I have concerns about code, I add
a debugging output stream, and, when I have trouble, I run the program
with debugging output on.  At any given time, I can easily see whether or
not my results meet my expectations, and if they don't, I know where they
went wrong.

I once spent nearly a week trying to debug a memory leak.  I took the
source with me to my girlfriend's house, and worked on the program on
a slow computer with too little memory to run a de{*filter*}.

I found it in fif{*filter*} minutes; it was just a question of sitting down and
*reading* the damn thing.

That was a major blow to my belief in de{*filter*}s.

-s
--

C/Unix wizard, Pro-commerce radical, Spam fighter.  Boycott Spamazon!
Not speaking for my employer.  Questions on C/Unix?  Send mail for help.
Visit my new ISP <URL: http://www.*-*-*.com/ ; --- More Net, Less Spam!



Sat, 26 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_

Quote:

>A de{*filter*} is of no use in checking the compiler; it _is_ of use in
>checking algorithms, and especially APIs.  Running though code watching
>every return from an API call, even those you _know_ to be correct, is
>quick, simple and effective.  Once you are very familiar with the API
>you can ignore this step, but again, for the inexperienced it's useful.

If you want that, put in debugging statements.

        {
                int i;
                debug("printing foo with %%x: ");
                i = printf("%x\n", foo);
                debug("printf returned %d.\n", i);
        }

-s
--

C/Unix wizard, Pro-commerce radical, Spam fighter.  Boycott Spamazon!
Not speaking for my employer.  Questions on C/Unix?  Send mail for help.
Visit my new ISP <URL: http://www.*-*-*.com/ ; --- More Net, Less Spam!



Sat, 26 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_



Quote:
>The author's general point is correct: in-band error signaling
>encourages usage errors.  Consider while((c=getc(fp))==EOF)...
>which doesn't work right in some codeset environments.

Theoretically - although I don't believe there's a real case of a system
where UCHAR_MAX is as large as INT_MAX *and in which any such character
can be returned by getc*.

Certainly, we never found one.

Quote:
>Books like this one are good to read and mull over, forming one's
>own opinions on the topics discussed.

This, I'll agree with.  Better to read a piece of bad advice and argue
with it than never to think about the issue at all.

(For this reason, I try to encourage everyone to read Plato's _Republic_,
even though it ends up creating a near-total dystopia.)

-s
--

C/Unix wizard, Pro-commerce radical, Spam fighter.  Boycott Spamazon!
Not speaking for my employer.  Questions on C/Unix?  Send mail for help.
Visit my new ISP <URL:http://www.plethora.net/> --- More Net, Less Spam!



Sat, 26 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_



Quote:
>I've never written any production code, so extended debugging issues
>have never really come up with me.  What precisely do you mean by a
>debugging output stream?

Basically, provide a function similar to printf, which directs its output
elsewhere, and then send all the information you want out to that stream.

Quote:
>Could you provide a small example of what this would look like?  Are we
>talking about macro wrappers around expected values, or something more
>sophisticated?

        /* debug.c minimalist */
        #include <stdio.h>
        #include <stdarg.h>

        static int debuglevel;
        static FILE *debugfp;

        int
        debug(int level, char *fmt, ...) {
                va_list ap;
                int r;

                if (level > debuglevel)
                        return 0;

                if (!init_debugfp())
                        return 0;

                va_start(ap, fmt);
                r = vfprintf(debugfp, fmt, ap);
                va_end(ap);

                return r;
        }

        int
        init_debugfp(void) {
                static int tried = 0;
                if (debugfp)
                        return 1;
                if (tried)
                        return 0;
                debugfp = fopen("debug.log", "w");
                tried = 1;
                if (debugfp)
                        return 1;
                return 0;
        }

        int
        setdebug(int level) {
                debuglevel = level;
        }

Easy, huh?  Then you just do things like
        setdebug(5); /* lots of messages */
or
        setdebug(1); /* only warn about momentous events */

and you litter your code with things like
        debug(1, "fatal problem: couldn't open log file '%s'\n", logname);
        debug(2, "errno is now %d\n", errno);
        if (logfile_already_open) {
                debug(4, "I think it's because you're an idiot, and the log file was already open.\n");
        }

It's wonderful!  In a more complete implemntation, you might provide a
set of bits in debuglevel, and allow setting them individually, so you'd
have things like
        debug(DEBF_PARSE, "%*sParsed a new %s.\n", parsedepth, "", foo->type);
        debug(DEBF_MEM, "allocated %d bytes", nbytes);
        debug(DEBF_FILE, "log file will be %s.\n", logname);
etc etc.

Then, you could also do things like
        memdebug = debugquery(DEBF_MEM);
        debugoff(DEBF_MEM);
        /* I know this linked list code is solid, but it does a lot of
         * memory allocation
         */
        ...
        if (memdebug)
                debugon(DEBF_MEM);

-s
--

C/Unix wizard, Pro-commerce radical, Spam fighter.  Boycott Spamazon!
Not speaking for my employer.  Questions on C/Unix?  Send mail for help.
Visit my new ISP <URL:http://www.plethora.net/> --- More Net, Less Spam!



Sat, 26 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_



Quote:
>I once spent nearly a week trying to debug a memory leak.  ...
>I found it in fif{*filter*} minutes; it was just a question of sitting down and
>*reading* the damn thing.

A de{*filter*} can help find a clue when you really are stuck with
a run-time problem.  The best de{*filter*}s don't disturb the envrionment
in which the debugged process runs, unlike inserting tracing code or
recompiling with NDEBUG undefined.

That said, I agree that most C programming errors can be better
detected by code review, if conducted with the proper attitude
(where one continually challenges all assumptions).  The errors
that don't tend to be caught that way are often problems with the
algorithm, and sometimes the clues from a de{*filter*} intelligently
applied can help discover what is going wrong.

There is one class of problem that practically *requires* use of
a de{*filter*}, and that is when the implementation is faulty (e.g.
bad code generation).  For example, I once encountered a system
that underallocated the stack frame by one word, which happened
not to matter unless an interrupt (signal) occurred..  Very hard
to figure out without coding up a small test case and examining
in detail every step of its operation (again, challenging every
assumption, including that the compiler knew what it was doing).



Sat, 26 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_



Quote:


>>The author's general point is correct: in-band error signaling
>>encourages usage errors.  Consider while((c=getc(fp))==EOF)...
>>which doesn't work right in some codeset environments.
>Theoretically - although I don't believe there's a real case of a system
>where UCHAR_MAX is as large as INT_MAX *and in which any such character
>can be returned by getc*.
>Certainly, we never found one.

But that could be, because the implementor realized the amount
of code that would break, and made choices (such as increasing the
width of int) to prevent them.  It's still an error in the API design.


Sat, 26 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_


Quote:



>>I've never written any production code, so extended debugging issues
>>have never really come up with me.  What precisely do you mean by a
>>debugging output stream?

>Basically, provide a function similar to printf, which directs its output
>elsewhere, and then send all the information you want out to that stream.

For C++ coding under Linux, I have hacked up a fairly sophisticated system
which will send diagnostics to multiple streams, one for each thread. In each
function (or statement block, if need be), I declare a tracer object which can
be used much like an output stream. The constructor of the object prints the
name of the function and increases the indentation level in the debugging
output; the destructor indents backward.   There is a shared global mutex in
which a counter is incremented, and each line of debugged output is printed
with this counter value. as well as a timestamp.  So afterward, I can examine
the logs and also tell in what order the diagnostics were triggered by the
various threads.

I have two versions of the tracer object; one which does the tracing, and one
which does nothing. To turn off tracing in a particular translation unit, I
simply switch a typedef at the top of the file over to the ``dead'' version of
the tracer class.



Sat, 26 Aug 2000 03:00:00 GMT  
 flame: _Writing Solid Code_



Quote:

> >Exactly. It's not that you can't get anything out of it at all, but
it's just
> >too irritating in places. I find it vaguely insulting that anyone
would throw
> >such trash on my desk, even if I'm new. :) :)

> You're insulted that someone tries to help you?  Boy, I'm glad we
> don't work anywhere near each other!

> Buy a book on teamwork.

I get the impression (I could be wrong) that Mr. Kylheku is a
relatively experienced C developer.  I would assume his new employer
knew this when he hired him.  Dropping a book as elementary as
_Writing_Solid_Code_ (and yes, I have read it, and came to many of the
same conclusions as he did) on his desk, as if to say "you may think
you're good, but you won't truly be XYZCorp. material until you've read
this", might be considered a bit insulting.  It's not like his boss
said "here's a copy of K&R2 for your bookshelf - I'm sure you already
know the C programming language, but it's useful to have around as a
reference".  (More useful than _W_S_C_.  ;-)

--Mike Smith



Sun, 27 Aug 2000 03:00:00 GMT  
 
 [ 22 post ]  Go to page: [1] [2]

 Relevant Pages 

1. flame: _Writing Solid Code_

2. Writing Solid Code & Code Complete

3. "Writing Solid Code" - compiler warnings

4. _Writing Solid Code_

5. Short review of _Writing Solid Code_

6. Querry: A good solid text on writing Macro Assemblers

7. Solid C Code

8. code/classes for Solid Modelling

9. 12 days of Christmas C code, top secret style of code writing

10. Writing optimized C code

11. standard for writing the code

12. writing portable code for structures

 

 
Powered by phpBB® Forum Software