Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

Just thinking ...

by toadi (Chaplain)
on Oct 12, 2000 at 18:37 UTC ( [id://36400]=perlmeditation: print w/replies, xml ) Need Help??

I was reading some quotations and I read a lot of them that reflect my 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)

When programming perl I have the same way of working. I try to solve the problem. Only afterwards I examine if it's ok code.
Analyzing is not really my cup of tea, I just get in there and get the problem solved(I have to say that with every new experience my code gets better). Afterwards I see if the code is good and most of the times I think there are hings that could be imporved.
It's vicious cirkel: starting to solving a new problem I use the knowledge of my previous project to write better code, but afterwards I see other things that could be improved.

What are other opinions on this matter.

I would also bring on as a second issue that analyzing and programming is best not done by one guy. Because or your a good analyzer or you are a good programmer, but being good at both is almost impossible.
This last statement is being said by some guys I work with. I haven't formed a opinion on that issue yet.


My opinions may have changed,
but not the fact that I am right

Replies are listed 'Best First'.
On elegant coding...
by merlyn (Sage) on Oct 12, 2000 at 18:52 UTC
    "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

      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!!!

        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!!!
      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

      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 :-)
      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

      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

      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

RE: Just thinking ...
by mirod (Canon) on Oct 12, 2000 at 18:50 UTC

    analyzing and programming is best not done by one guy. Because or your a good analyzer or you are a good programmer, but being good at both is almost impossible.

    <rant mode="on">
    With all due respect with the poeple you work with this is a load of BS.

    It is very easy to write a perfect design... as long as you don't have to implement it. Only going through the actual writing of the code will reveal the flaws in the design. Programming is the messy part, the part when clean abstractions hit reality.

    I agree that separating the analysis and the programming is a good thing but saying that you can't be good at both is non-sense. Analysis must take implementation into account. And analysis needs to be refined as programming progresses. It is too easy for the author of a neat but un-implementable design to put the blame on the darn programmers who can't write what he told them too.

    Programming is not a "detail" as I heard it once!
    <rant mode="off">

    Ranting aside it is generally a good idea for the designers to be good coders, and most of all to have to be part of the coding team. Makes them more responsible about what they design.

RE: Just thinking ...
by extremely (Priest) on Oct 13, 2000 at 03:08 UTC

    In reading the comments already posted, I think we need a new word other than beauty. Half of these posts stumbled off-topic a bit to coding-style rather algorithm beauty. Personally I find merlyn's coding style a bit ugly but the algorithm is purty. =)

    I like what princepawn is doing with his logic stuff and the constants stuff especially. But I find it even uglier.

    mirod's rant saved me from writing the same thing =)

    My dad once said, "There is all kinds of good looking, but there is only one kind of ugly." Now he was talking about dogs or cars or something but it is kind of true here. I see lots of great ways to express the same task, clearly and in a mostly self-documenting way. And a lot of them just moved the ugly out of sight. =) But for all those cases, the algorithm is generally non-ugly. Most of what I see is just different ways to decorate the dog.

    Oddly, I was just working on a Cache module for a nasty little web problem I have. I have a hidden webserver that only my public webserver can speak to, generating graphs on that second machine. In an effort to not beat the hidden server to death (it has to do real work too) I want to cache the images locally. Rather than run a cache on the hidden machine (eek, memory) or a reverse cache on the public machine (eek only helps with the http and not the other protocols I have to get working next) I did a the next best thing. I threw Perl at it. =)

    What I got was this:

    use DiskCache; my $xdc = new DiskCache "image", 300; if ( $xdc->exists( "filename.ext" ) ) { print $xdc->get( "filename.ext" ); } else { $xdc->del( "filename.ext" ); my $newdata = GetDataFromSomeWhere(); print $newdata; $xdc->put( "filename.ext", $newdata ); }

    Now that I've seen merlyn's stuff I suppose I'll have to do it right... =)

    --
    $you = new YOU;
    honk() if $you->love(perl)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://36400]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (5)
As of 2024-04-19 16:41 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found