Larry Osterman's WebLog

Confessions of an Old Fogey
Blog - Title

What does style look like, part 8 (the end)

What does style look like, part 8 (the end)

Rate This
  • Comments 20

So over the course of the past week, I've spent some time talking about various things that make up a program's "style". 

There's one final aspect of programming style that I want to touch upon, and that's "Literate Programming".  Literate programming got its start with Donald Knuth's seminal book "Literate Programming".  The entire TeX project was written (or rather re-written) using literate programming.  Literate programming is in many ways the antithesis of many of the things I've talked about here - with a more traditional programming style, the code is the documentation, in literate programming, the documentation is the code.  The literate programming paradigm is a totally document-centric paradigm, in fact the code is almost irrelevant.  In the literate programming paradigm, your source code is a document, and the actually resulting program is built up by stitching together fragments of code.

When I wrote my compiler back in my senior year in college, I wanted to explore the idea, so I wrote my compiler using literate programming.  Here's a simple example:

58. Determine if the fields of a record are legit, and return a pointer to the type field of the rightmost field on the list Variable^.LHSOperand.

<Check and return the type of a record field access 58> ==
  with  Variable^ do
      begin
      TSTE := Type_of_Var(LHSOperand);
     
if LHSOperand^.NodeT in [Terminal, ProCall] then
         
RValue := Type_of_Record(IDList, TSTE)
      else
          begin
         
SemError( 'Huh? Record fields can only be identifiers or arrays', Error);
          UnParser(TTY, Variable);
          end;
    
return;
     end

This code is used in section 57.

Section 57 had:

  if  Variable^.NodeT == LValue then
      <
Check and return the type of a record field access 58>;
  if ....

Literate programming isn't a style for everyone (I'm sure that the literate programming people are going to be mad at that statement), going back over my compiler, in retrospect, it's clear that I didn't do a great job of living up to the paradigm, but it IS a viable form of programming style.

 

As I've pointed out, there are literally dozens and dozens of ways of writing a particular piece of code, and all of them are "correct".  The differences between those representations is what brings "style" to a piece of code.

And just like fashion, coding styles have had trends - various coding constructs come in and out of style - for instance, currently exception handling is in, and error codes are out.  This may (and likely will) change as time goes on - time changes peoples perceptions of the value of various constructs (there was a time when structured error codes were the rage - that particular fad climaxed in x.500 error codes, which were highly structured entities that encapsulated not only the error, but the reason, recommended action, etc.

Over time, every developer comes up with their own particular style - it may be the style that their mentor (or team) used, it may be the style that was in a piece of code they admired, it may be in a construct that they ran across in a textbook, it might just be from hard experience.

Ultimately, the purpose of having a coding style is to result in more maintainable code.  Even if your code will only ever be used by you, having a consistent style is STILL critical - you're still going to have to maintain your code, and if your code is internally inconsistent, then you're going to struggle when you go back to that old code.

I've got to say that I was reasonably pleased when I went back and looked at my old code for this series - there were some seriously cringe-worthy bits, and the quantity of comments was scarcer than it is today, but overall, it wasn't that hideous.

If you're working on a team, it's even more important that you have a single coding style (or rather coding conventions).  As I pointed in this article last week, if you don't have a single coherent coding style, all you get is a mishmash, and mishmashes are rarely maintainable (and yeah, the spelling checker had mishmash in it).

Coding style brings together individual and team discipline, personal esthetics, and a little bit of magic to make a coherent whole.

Tomorrow, what does my personal coding style look like?

  • Interesting series -- just stumbled onto this today.

    There's one more aspect to code style that I'd think about, however, which seems to have been overlooked in this series: how do you reduce or break up what would otherwise be long lines. Sometimes its useful to allow a longer expression, if it concisely expresses the logic. For example, to test for two overlapping boxes:

    if ( obj1.maxx < obj2.minx ||
    obj1.minx > obj2.maxx ||
    obj1.maxy < obj2.miny ||
    obj1.miny > obj2.maxy )
    {
    return no_collision;
    }
    else
    {
    return collides;
    }

    Do you even allow something like that or do you assign the conditionals to flags and then test? Where do you break the expression? Before or after the operator? Do you line up the subexpressions?

    Of course, maybe I've just spent too much time studying functional languages. :-)
  • Actually I'd thought about it, and planned on putting it in, but kept on forgetting it - it belonged in the "effective whitespace" section :(
  • Larry,

    >> Ultimately, the purpose of having a coding style is to result in more maintainable code.

    exactly. thank you _very_ much for mentioning this. unfortunately, not a lot of people understand this. now i have a 'reference' to point to. :)

    WM_THX
    thomas woelfer
  • As much as I dislike exceptions (I'm in the Joel camp on that one), MS has pretty much assured that exceptions will be The Way for however long the CLR lasts.
  • Thank you for including Literate Programming in your series!

    Regarding your statement that LP is not for everyone, I shall have to grudgingly agree, although I wish it were otherwise. Very simply, LP demands a very high level of skill in writing (both prose and code) on the part of the developer. Not everybody can express themselves are clearly and succintly as Don Knuth. Perhaps they should teach better writing skills in school, specifically targetted towards programmers -- even if that results in clearly comments in code, it would be well worthwhile the effort.

    I have experimented with LP a couple of times for toy programs, and the biggest benefit I noticed was something that I least expected -- the process of writing the program was immensely enjoyable! Maybe it was the freedom from the strict structure imposed by the programming language (LP lets you rearrange code and prose in arbitrary ways), or perhaps it was just being able to describe the problem and the solution in such detail -- I don't know what it was, but I loved it!

    -K
  • Mike,
    I'm not so sure. The "exception vs. error code" debate's still raging internally, it's actually fascinating to watch.

    I've grudgingly started to realize that if you're very careful and use RAAI exclusively, it MAY be possible to write code that correctly handles exceptions, but...

    It'll be interesting to see how it plays out. One thing that's become utterly clear is that it's almost impossible to handle cross-domain exceptions - so taking SEH and handling it with C++ exceptions is totally fraught with danger, and taking COM errors and turning them into managed exceptions is only problematic.
  • I am both a fan of RAII and not using exceptions. I have yet to find any evidence that exceptions help you produce more bug free code. Like many things all you are doing is trading in one set of problems for another.

    Larry, I would love to hear your thoughts on whitespace. I for one am always telling my guys to "PLEASE FIND THE SPACEBAR". :)

    if(bob.frank->tom()<42||flag==true)
    {
    ...
    }

    And don't get me started on why they decided that doing "flag==true" is a good thing especially in the case of testing for TRUE with BOOLs. I don't know if it was you or someone else, but I loved the article on not mixing your integer and logical operators.
  • "If exceptions are the answer, umm... what was the question again?"

    There are a few critical problems with exceptions.

    First, C# doesn't have anything like RAII. Look very closely at the spec for "using" and you'll see that the resource is acquired before the try block is entered. Maybe this can be fixed but can you really see people declaring IDisposable things to fit the RAII paradigm? People think GC == "you don't have to manage anything determinstically ever again". bzzzt.

    Second, nobody still understands what to do about exceptions when you cross "major component boundaries". Java's design says that you had to declare the exceptions you throw which led to everyone catching and dropping exceptions. CLR says you don't have to declare the exceptions you throw... which leads to everyone catching and dropping exceptions. :-) COM takes a stance here with HRESULTs but you should ponder how careful you are not to propagate FACILITY_ITF through you. (e.g. "if (FAILED(hr)) return hr;" has a fatal bug in it.)

    Third, even most of the C++ code out there (where arguably they could have made RAII work) doesn't correctly handle cleanup during exception propgatation because it's impossible to inspect local source code and understand the explicit and implicit operations which may cause exceptions to be thrown. Thus, in reality, even the most exception safe code that's been written (that is, C++ that most people would say is in the RAII style) can't be seen to be correct over time without whole program analysis.

    The problem with exceptions in total is that evaluating whether a line or phrase of source code is correct requires whole program analysis. This is the mark of a bad design. Some questions clearly require whole program analysis to correctly answer, but when a simple question like "do I need to use RAII when I'm incrementing a variable?" requires whole program analysis, it's a terrific indicator that the pattern is void. (In general patterns which do require whole program analysis are to be avoided like the plague since you'll never find the bugs before you ship.)

    My religion: RAII + status codes so that control flow is always explicit and visible and so that cleanup doesn't rely on people remembering which set of values are live at any given point in the source. Just last week some coworkers complained about how my team's source code uses RAII... "it hides too much".

    I don't know what to do about status codes that have to cross major component boundaries; so far trying to have a uniform set of status codes has gotten us a long way... I hope I can help figure out the right long-term answer in the next decade...
  • 11/21/2004 1:47 PM Michael Grier

    > "If exceptions are the answer, umm... what
    > was the question again?"

    "How do you bring peace to the Mid-East?"

    (This is just a not-really-parallel variation on an old joke.)
  • "I am both a fan of RAII and not using exceptions. I have yet to find any evidence that exceptions help you produce more bug free code. Like many things all you are doing is trading in one set of problems for another."

    And as long as you continue to look for why exceptions should reduce bug count, you'll continue to fail to find it, because that is not the point.

    The point is the amount of information about failures, and removing the need to have redundant code in order to handle failure cases.

    It is not, nor will it ever be, about reducing bugs, and so as long as you keep it about that, you'll always find an excuse not to use exceptions.

    With a status code, after each line of code that could potentially fail, you need near duplicate code to check if there was a failure, and if there was a failure, to log information so that you can diagnose it.

    Note that failures are not bugs -- one can have a failure without a bug, and one can have a bug without a failure. The two things are totally separate issues.

    when you throw an exception, you get a general class of error (the exception's inheritence chain), a user-displayable error information, the line of code where the error was thrown, the line of code where the error was caught/logged and most of the time, the complete call stack with parameters at the time of the failure.

    A status code very rarely gives you that information, and requires a lot more effort (having implemented systems that provide that level of details from status codes) to implement.
  • > What does style look like, part 8 (the end)

    OK ... the end, except for feeback ^_^

    > Tomorrow, what does my personal coding style
    > look like?

    Hmm ... looks like null, because the series ended yesterday? `u`

    Now checking ... yup, today's posting is null, at least so far ^o^

    By the way a cat owner's smiley looks like >^.^<
  • Actually it didn't. But I'm on thanksgiving vacation (thus the blog's dark), and that post is in the office.
  • "The point is the amount of information about failures, and removing the need to have redundant code in order to handle failure cases."

    This is part of the problem. When it comes to exceptions, everyone has their own opinion about what problems they solve. There are many people who feel what you describe is exactly what exceptions are NOT intended to do. They are intended to handle "exceptional" conditions, not to handle failure cases (and there is a HUGE difference).

    As far as getting all that "fancy" exception information from an exception, that is implementation specific. You don't get that information using SEH or C++ exceptions as defined by their design or the standards.
  • (apologies to Larry for using his blog as a soapbox but the topic was here and it's one of my hot buttons)

    Dave Bacher:

    It sounds like you just need some discipline in your status propagation so that it's easy to capture this kind of contextual information. In my dev team we do this easily by differentiating between when an "exceptional condition" is detected (I call this "originating" the condition) vs. propagated.

    Neither case is performance critical so we always call a central function. Just set a breakpoint there and you have all the context you could hope for. We endeavor to not hit the origination function for any normal case so 90% of our malfunction debugging comes down to setting a breakpoint on the origination function and rerunning the scenario.

    Ada's original guideline on exception usage was probably the best I've heard (something to the effect of "no correct program may rely on exception handling for correct execution") but stuck its head in the sand on the issue of "uh, then what do you use them for?"

    If you're arguing that we need techniques for statuses with context, I'm with you and it's trivially solvable. If you're arguing that exceptions are good because your code isn't littered with all those little bits of control flow, you're wrong because your code /is/ still littered with all those little bits of control flow, you just can't see them any more so you can't tell if the source is right or wrong any more.

    My usual example is this: consider the following COM-ish code fragment:

    HRESULT hr;

    hr = Coerce(FileNameIn, &FileNameICanOpen);
    if (SUCCEEDED(hr))
    hr = TryToOpen(FileNameICanOpen, &FileObject);
    if (SUCCEEDED(hr))
    hr = Coerce(FileObject, &FileObjectICanUse);

    if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { /* I guess the file's not there, eh? */ }

    That's clearly a bug. The check for the specific error clearly should have been scoped to the call to open the file. Now look at the same source code using exceptions:

    try {
    FileObjectICanUse = OpenFile(FileNameIn);
    } catch (const CFileNotFound &Exception) {
    /* I guess the file's not there, eh? */
    }

    Hmm... I don't see the bug. How am I supposed to guess which expressions there call code which actually can throw an exception? Can an analysis tool find this? If the coersion before or after throws a CFileNotFound exception, is that a bug or not? (Usually internally someone chimes in saying that it's rediculous for such coersions to throw a file not found exception but that's moot since I probably didn't even know a coersion was going on in the first place.)

    I think we've been sold a bill of goods around how good exceptions are:

    1. Code looks clearer
    2. You get to do groovy things like assignment operators and copy constructors
    3. Encourages RAII
    4. They're new and sexy! Everyone from the research community wants them! C++ and Java have them!

    I agree with point 3 but that has nothing to do with exceptions.

    The first two points are the seductive badness here. Hiding complexity that you don't have to deal with is good. Hiding complexity that you did have to deal with all along is bad.

    The last point is mostly that fads are fads, no matter how popular they are. There is a class of software which does not require high quality and those kinds of folks can do whatever the heck they want.

    (I need to come clean here: The C++ exception usage design is self-consistent and can be used reliably. The problem is that it does rely on deep understanding of the entire source base including the template libraries you may use. So for any individual monolithic application it's usable with high quality results. We really do not understand how to apply the techniques to "programming the large" type of scopes. IMHO.)
  • 11/23/2004 12:30 AM Michael Grier

    > Ada's original guideline on exception usage
    > was probably the best I've heard (something
    > to the effect of "no correct program may
    > rely on exception handling for correct
    > execution") but stuck its head in the sand
    > on the issue of "uh, then what do you use
    > them for?"

    Well, not that I was involved in it or anything, nor would I have been able to answer instantaneously if I'd been there, but I think I could answer that now. Exception usage is to minimize the damage from incorrect execution. Exception handling includes notifying the user, trying to avoid destroying existing data, trying to avoid crashing, etc. In an airplane, exception handling can include things like aborting a landing. In a car, it might include applying the brakes (and subsequently recovering from locked brakes if the ground is icy etc.). Indeed these should be considered as not correct executions.
Page 1 of 2 (20 items) 12