in reply to Just thinking ...

"When I am working on a problem I never think about beauty. I only think about how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong. " - Buckminster Fuller (1895-1983)
That quote is so true for my programming style, and it's what distinguishes the "used for 10 minute hacks" from the "publish it in a column code".

I can't tell you the number of times I've whipped out some snippet for public consumption, then sat back and stared at it for somewhere between 10 minutes and 10 hours, simply because it wasn't elegant. (So if you think the code you see is weird, you should have seen the rejects! {grin})

It makes me mad when I have to use the same expression in two places, for example. There's just something wrong about that. Or when it looks like it'll be fragile to changes, or not very reusable, or has too many global variables. It just looks wrong.

Of course, I have deadlines which I'm always up against, so I publish what I've got occasionally, but you'll find me using phrases like "sorry this looks so ugly". Often, after I see the code in print a few months later, I'll go "doh! I could have done XYZ", because I still work on it in my head. It's why I've revisited that link checking problem over and over again. I am still not satisfied at how ugly the logic is in the middle of those, or how hard it is to do things in parallel. But each time I hack out a new version, I'm more and more proud of the result.

That's probably one of my objections to objects for small programs. It seems inelegant, making me write too much code to be done in what I can do tighter. So my rule is "if you don't write 1000 lines of code in your program, and you're not subclassing an existing class, don't use objects!". And that upsets some of the "OO for everything" crowd, but Perl is already a hybrid (part-OO) language, so get over it. {grin}

And back to elegance: each part of a program should do one thing well. Very well, in fact. I chunk the subroutines adding whitespace for paragraph breaks. I try to introduce lexicals as I need them, and then don't let their scope go more than 10 or 15 lines. In a recent experiment, I wrote the entire top-level code as Perl pseudo-code, invoking clearly-named subroutines for each of the steps of the algorithm, and then implemented that, but then stared at the code for a full day, agonizing over the fact that the same thing appeared in a couple of the steps. Here... take a look:

{ ## main loop if (cache_is_good()) { show_cache_and_exit("current"); } if (cache_is_stale()) { if (i_am_the_writer()) { if (i_can_fork()) { if (i_am_the_parent()) { show_cache_and_exit("stale"); } ## child does: be_a_child(); update_cache(); exit 0; } ## cannot fork, so it's up to me update_cache(); show_cache_and_exit("current"); } ## I'm not the writer, so show old cache show_cache_and_exit("stale"); } ## cache is dead if (i_am_the_writer()) { update_cache(); show_cache_and_exit("current"); } ## we cannot do anything about a bad cache, so retry close_cache(); sleep 5; redo; }
See, you can probably see the whole algorithm, self described, right here. But I agonized over the fact that some of those steps are duplicated in a way, but we get to them because of different conditions. (This is my next Linux Magazine column, but it won't be online for a few months, as usual.) I tried rewriting it, changing the order of testing for some of the conditions, but it got only messier.

So after a day, this is the main code for my program, followed by 100 lines of code to implement the subroutines. And even after I had coded them, I rearranged them so that the subroutines that shared the same global data lived in their own block so the globals could disappear. This even helped me explain the program easier, and I know it'll be easier to maintain.

And when I complain about code posted here in the Monestary, it's these ideals that I'm holding the code against. I'm sorry if I sound annoying about them sometimes. It's just that I view programming as an art, not just a hack.

Whew. Long post. Comments?

-- Randal L. Schwartz, Perl hacker

Replies are listed 'Best First'.
RE: On elegant coding...
by footpad (Abbot) on Oct 12, 2000 at 20:09 UTC

    The novice hesitates and then cautiously responds...

    Many of your thoughts strike chords with my personal development style (paraphrasing):

    • Planning the implementation using pseudocode comments before writing implementation code
    • Minimizing globals
    • Not using classes when old-fashioned structured programming will suffice
    • Using white space to visually separate sections of code
    • Reviewing it after the fact to make sure it's right

    However, do you not hold a rather unique position in the community? You are held to a higher standard because your code is often seen as a standard to follow. As a leader, a columnist, and a mentor, you are one of the sources for "how it should be done."

    Yes, your code should be elegant as time, energy, and editors will allow.

    Yes, our code should as elegant as possible. But, does not the ability for elegance come with experience? I don't expect my daughter's watercolors to match the quality or the simplicity of master artists. So, too, the quality of my perl will not match yours for a period of years (if ever).

    As possible is the key. As a leader and mentor, please don't forget that some do not yet have skills that match yours. They have the potential, just not the experience.

    In the short time I've been slouching about, I've found perlmonks to be a community interested in helping folks improve their knowledge and skills, as opposed to other communities where knowledge and ideas are hoarded for some questionable tactical advantage. I personally appreciate that very much.

    I would ask, though, that "elegance" be gently taught. After all, there are many that need to do their work very quickly, without the luxury of extended research, review, or analysis. A regrettable portion of our positions as programmers is that we're given too little time and resources to do our work properly.

    This is, as you well know, why there are so many hacks in "finished" software. One quote that comes to mind, though I confess I cannot properly attribute it, is, "Software is never finished; it's just abandoned."

    Also, elegance, to some degree is in the eye of the beholder. For example, I recognize that the standard is to use this:

       if (cache_is_good()) {
          show_cache_and_ext("current");
       }
    

    However, you will almost always find my code expresses it as:

       if ( cacheIsGood() ) 
       {
          showCacheAndExit( "current" );
       }
    

    Does that make my code less elegant? To you, perhaps...however, I find it:

    • Clearly denotes the block.
    • Is easier to debug when I've forgotten enclosing braces or mismatched my parentheses.
    • Is easier to read when viewed or printed with proportional spaced fonts.
    • Clearly separates my function calls from built-ins.

    That's just my personal preference. It works for me and I do not claim it works for anyone else.

    I would hope that in your quest to improve the elegance of our code, you would recognize that and not vote me down because I format my tokens differently that you do.

    In my opinion, good programmers continue to look for ways to improve their skills, even though previous efforts cannot (or will not) be revisited. They also recognize that "coding standards" should be guidelines and not straight-jackets. In my opinion, that's where the artistry comes in.

      Seems to me that you're looking to heavily on the brushstrokes and not enough on the theme when referring to the blocks.

      (Disclaimer: This is not a rant on either footpad or merlyn). What I note from merlyn is that one does not simply reach a point where everything is elegant. This is either comforting or a complete disappointment. I'm not sure which, yet. I appreciate the sentiment; that much is clear.

      footpad, the mistake I notice in your post is that you give a higher credence to merlyn. Possibly more than other monks, but he is, after all, 'just another perl hacker'. I guess what I'm saying is that I agree with the overall concept being discussed here: there is a good quality about treating code as an art. (Here's the potential fire, but I've tried my hardest to put the bucket of water right in front of you) Just because someone is considered a "master" in their field of art does not mean that their work is good. I think merlyn is a good artist, but it is because what he does is important to himself, not that it is important to you.

      I don't have a cool quote from anyone famous, but I'm certain that someone probably said something like this in the past (Probably Howard Roark): You can't create art for anyone but yourself. If it happens that your work reaches others, then they can only take from it what they want to take from it. This will be dependent on their personal path that has led to this moment. It will be theirs only, and even as the artist, you will not have any claim to ownership of that which they get from your work.

      ALL HAIL BRAK!!!

        PsychoSpunk,

        Interesting. I wasn't necessarily speaking completely for myself, but through things I've observed. Merlyn is placed higher than many and for good reason. Also, his RN is extremely recognizable with respect to perl. He's written books; he's a columnist in a few different magazines, and he gives much to to community. How can people not listen when he speaks?

        I wasn't disagreeing with him either. Code should as elegant as possible. It's easier for him (and perhaps even more important) for him to write the best code possible.

        In the other community I'm involved with, I'm very careful to not judge the validity of a poster's question or the elegance of the solution they've reached. As I mentioned in an earlier post, I have found that I learn much from those who have less experience than I do.

        And please don't misunderstand my intention; I appreciate everything he's given to the community and respect and appreciate those contributions highly. And I do pay attention...

        What made me pause was the possibility that some would take his post as suggesting that elegance should be the only consideration for code. (FTR, I honestly don't think that's what he was actually trying to say.) I wanted to highlight the idea for discussion....just in case...

        I hope no one thought I was trying to slam merlyn; I honestly wasn't. I respect him too much to even consider doing so. if that's how that post came across, I apologize.

        -- f
      I was just scanning some of my code to see how I do it and I notice that if the block contains a single statement, I almost always code like this:
      showCacheAndExit("current") if cacheIsGood();
      That way it reads the same way I would say it in English. But that's just me and I am FAR from considering myself an experienced Perl dude.

      -It's snowing in Mammoth!!!
RE: On elegant coding...
by Trimbach (Curate) on Oct 12, 2000 at 19:26 UTC
    In my experience with my own modest Perl skills I have gradually moved from the "hack-hack-hack-does it work? Yeah!" style of coding to a more aesthetic appreciation for the artistic aspects programming. When I'm working on a project I try to figure out an elegant solution to the problem, that is, a solution that requires the least amount of coding, but the implementation itself isn't always very elegant. Now, I certainly know when I've written something sloppy, and although most the time I have to go with it due to time constraints, but it doesn't keep me from feeling dirty somehow. I certainly appreciate programming elegance even if I'm not capable of it.

    I aspire for the day when my programs become elegant both in form and in function, because I think Merlyn's right: the more artistic a program is the easier it is to maintain, the easier it is to read, the better it works.

    But for now I am content to color with crayons. I will leave the oil paints to the Gurus.

    Gary Blackburn
    Trained Killer

RE: On elegant coding...
by runrig (Abbot) on Oct 12, 2000 at 22:04 UTC
    I enjoy at least trying to redo these things (whether or not the result is more or less maintainable/elegant is another question). My effort (which I know is probably something like what you already tried):
    { ## main loop show_cache_and_exit("current") if cache_is_good(); unless (cache_is_stale()) { ## cache is dead update_cache(),show_cache_and_exit("current") if i_am_the_writer(); ## we cannot do anything about a bad cache, so retry close_cache(); sleep 5; redo; } ## If I'm not the writer then show old cache show_cache_and_exit("stale") unless i_am_the_writer(); ## If I cannot fork it's up to me update_cache(),show_cache_and_exit("current") unless i_can_fork(); show_cache_and_exit("stale") if i_am_the_parent(); ## child does: be_a_child(); update_cache(); exit 0; }
      You know, I actually like this one, except for the excessive backwards-ifs. Where were you three days ago when I was trying to hack out my column? {grin}

      -- Randal L. Schwartz, Perl hacker

      Excessive !?!?! There's 3 if's and 3 unless's. I'd say they're perfectly balanced :-) Really, though, I was just trying to get rid of all the nested blocks, which is sometimes easier to do (in this case anyway, I believe) only after seeing the code WITH all the nested blocks.

      Update:oops, meant to reply to merlyn, and replied to myself instead :-)
RE (tilly) 1: On elegant coding...
by tilly (Archbishop) on Oct 12, 2000 at 21:50 UTC
    First of all I agree with the various thoughts and points you make. So let me just comment on the example.

    I see what you mean. Aesthetically I don't like the exit condition being woven through the logic, what happens if some day you want to have this cache updating be part of a longer-running script? Also reading the logic through I saw a lot of combinations of actions being taken, but it was not at all obvious why some of them were. And the nesting is pretty darned deep.

    I used an indicator variable to do the actions and restructured. Comments?

    { ## main loop my $cache = 'current'; # Hope for the best if (not cache_is_good()) { $cache = 'stale'; # Changed expectations if (i_am_the_writer()) { if (cache_is_stale()) { unless (start_background_update()) { $cache = 'current'; # Gotta do it foreground } } else { $cache = 'current'; # Have nothing to show, gotta do it } } elsif(not cache_is_stale()) { # Stuck, gotta retry close_cache(); sleep 5; redo; } update_cache() if $cache eq 'current'; # Make it so } show_cache($cache); } sub start_background_update { # NOTE: Uses implicit returns. if (i_can_fork()) { unless (i_am_the_parent()) { be_a_child(); update_cache(); exit 0; } } }
    UPDATE
    Yeah, I know. I was using implicit returns without a comment. At the time it made sense, but that is the kind of thing I rethink and mention shortly after...
      I'm not a real big fan of state variables, and in fact, for this example, I had tossed around doing some status checks similar to what you've done, but rejected it after a couple of false starts.

      The problem with state variables is that they introduce some additional coupling:

      • What if the variable gets set to a value other than the legal values (or not initialized)? You could branch more often or less often than you think.
      • What if you refactor and now need a finer-grain distinction (stale vs real stale)? How do you track down which values to check, or does it now need some disjunctive tests?
      • Tracing the program flow now also requires a data simulation as well.
      Yeah, again, just reporting on where I've been burned. But I think I've got enough scars to not just be a guy in a diner on this one. You know, the guy at the end of the counter who has an opinion on everything even though he's not been there? {grin}

      -- Randal L. Schwartz, Perl hacker

        I am not the greatest fan of state variables either. But the state variable always was initialized, information on what values were legal appeared fewer times within fewer lines of code than the original, and I was only resorting to it to remove something else that I disliked even more. And I note that it is an idea that you tossed around as well. :-)

        To tell the truth, were this my problem with my stuff I would look at the logic and reconsider whether I need to be fancy and fork. Since my stuff likes to run in crons and long-running jobs, I would be able to say "no" and get drastic simplifications. Down to "until it is safe to read, become writer and be sure it is fixed, once safe to read then read".

        But there is an obvious solution that doesn't involve state, and that is to make your loop into a real function, and then return out of the function rather than call a function that exits. Combine with runrig's organizational skills... :-)

        As for the diner, yeah, that hits home. And you know it. So let me explain.

        It has to do with my learning style. I found out many years and subjects ago that I learn best by going out and finding out everything that I can, trying to integrate that into a solid understanding, and then articulating my understanding in the presence of people who know better than I do. If they correct, then analyze the corrections, integrate *that* into my understanding. Wash, rinse, and repeat until satisfied.

        Which boils down to one thing. I am a lot like that guy at the end of the counter except in one huge detail. I learn!

        Besides which, I have been more places than you might think...

RE: On elegant coding...
by princepawn (Parson) on Oct 12, 2000 at 21:10 UTC
    Well, this code has one inconsistency. In one case, you aggregate an action with an exit, while in another you dont. E.g.,
    show_cache_and_exit($STATE);
    aggregates two actions, while
    update_cache(); exit 0;
    would have been written as
    update_cache_and_exit(0)
    if it were consistent with the aggregation methodology used for show_cache()

    Alternatively, and in the favor of fine granularity,

    show_cache_and_exit($STATE);

    Could be written as

    show_cache(); exit($STATE);
      Yeah, I played around a little with factoring. You're right, I could have combined those.

      But the inelegance was that there are two different reasons we could be updating the cache in the parent... one because we couldn't fork, and the other because the cache was so old we dared not show a stale cache. And I was trying to figure out how to have both of those fall out to the same place in the program without freaking out with state variables or contorted conditions, and just didn't get there.

      -- Randal L. Schwartz, Perl hacker

RE: On elegant coding...
by princepawn (Parson) on Oct 12, 2000 at 21:21 UTC
    Being a former logic programming daemon, let's take a completely different take on writing this. Among other things, this different take shows that you should use constants instead of strings to avoid spelling errors.
    ## main loop if (cache_is_good()) { show_cache_and_exit("current"); } # INSTEAD BECOMES cache(GOOD) && show_cache && exit('current');
    if (cache_is_stale()) { if (i_am_the_writer()) { if (i_can_fork()) { if (i_am_the_parent()) { show_cache_and_exit("stale"); } ## child does: be_a_child(); update_cache(); exit 0; } ## cannot fork, so it's up to me update_cache(); show_cache_and_exit("current"); } ## I'm not the writer, so show old cache show_cache_and_exit("stale"); } #### BECOMES (cache(BAD) && sleep(5) && redo) || (cache(STALE) && (!i_am_writer && show_cache && exit(STALE)) || ( i_am_writer && (forkable() && ( (parent && show_cache && exit(STALE)) || (child && update_cache && exit(0) )) || (update_cache && exit(CURRENT) )
    Prince "--'s here I come! Where is the preview button for comments? Why is this TEXTAREA so small? I cant see anything?" PAWN
      I shun this coding style, because it either implies an additional dependency which doesn't exist, or introduces a dependency which shouldn't be there.

      If you use

      a && b && c
      where you mean
      if (a) { b; c; }
      and b returns false, you're hosed.

      Therefore, as an element of "elegance", I don't introduce or require any more dependencies than I can safely justify.

      In practical terms, I'd reject your code during a code review, demanding a rewrite on the grounds of maintainability.

      -- Randal L. Schwartz, Perl hacker

        But I think it is perfectly OK to generate expectations of functions and develop code whose logic is dependent on it.