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

best practice

by George_Sherston (Vicar)
on Aug 27, 2001 at 17:34 UTC ( [id://108116]=perlquestion: print w/replies, xml ) Need Help??

George_Sherston has asked for the wisdom of the Perl Monks concerning the following question:

Brothers and Sisters, I have seen the error of my ways.

I'm a typical bumbling arts graduate who got into perl because there was a job that needed doing and I've spent my perl life bodging things together with chewing gum and string, hardly using any CPAN modules, being promiscuous with my variables and all that bad stuff. This was the Wrong Kind Of Impatience, really, or what you might call Unreasoning Fear of the J Curve. I always thought "hell, by the time I've learned how to do it right I could already have done it wrong".

Well, the disadvantages of working this way have been piling up day by day. Because of course the older and smellier my mess grows, the harder it gets to modify it. And also, in the short while I've been hanging around here I've been inspired to aspire; and I've learnt quite a lot about what I might aspire to.

So I'm repenting. I've junked the bit of my project that I've been working on for the last three weeks (which should have taken less than a week, and which has in any event almost completely disappeared up its own bottom), and I'm starting fresh. I'm going to use strict with -w. I'm going to use CGI or die;. I'm going to indent four cols, line my closing }s up with the start of the loop and cuddle my elses. I'm going to write pseudocode before I start coding and write a plan before I write the pseudocode. I'm localising my variables.

So my question is: What else should I do?

I had a bit of search through the site and read up perlman:perlstyle and a few other relevant nodes. But I'm greedy. I want all the advice I can get. So, if it's ok with you, I'd like to solicit your views on perl Best Practice. The most general programming rules - the BIG stuff. Advice; injunctions; commandments. Thoughts and links. Do you look back and think "if I'd known then what I know now I'd always have...." ? Then I'd be very grateful to hear the what and the why.

(Indeed, for the practices I already mentioned above, I'd love to read why anyway. I mean, I know for example that I should localise my variables, but I'm not 100% clear what makes this such a good thing to do... and one does the right thing much better if one knows why it's the right thing.)

What I'm hoping for, I guess, is a pre-flight checklist of good perl resolutions. I've given this node a generic name in the hope it may in future answer similar questions for other people in my condition - though of course if there already is such a node that I couldn't find, then that's what I'm looking for and I apologise for taking up your time.



§ George Sherston

Replies are listed 'Best First'.
Re: best practice
by dragonchild (Archbishop) on Aug 27, 2001 at 17:41 UTC
    99% of what you're talking about is in the book Code Complete. It's probably the best US$50 on programming knowledge I've ever spent. If you're serious about wanting to have programming be your (current) career, read it. It's a very long book, and it assumes some knowledge of programming. But, it will explain, not only what, but why as well.

    The best non-literary way of making sure you're doing things right is to write a bunch of stuff, then not look at it for six months. (Yes, I know that's a long time!) Then, go back and look at it. If you could understand what you were doing without scratching your head too much, you did it right. If you can't, fix those issues in the code you're working on right now. (Leave the crap you wrote before ... it's worthless now.)

    A number of monks have always said that code should be self-commenting, irregardless of the other documentation. I whole-heartedly agree with this and, 3 years out of school for Computer Science, I've been improving my coding style. (Just to let you know you're not alone. *grins*)

    Good luck!

    ------
    /me wants to be the brightest bulb in the chandelier!

    Vote paco for President!

Re: best practice
by mrmick (Curate) on Aug 27, 2001 at 17:55 UTC
    Another good source would be The Pragmatic Programmer. An excellent book about good programming practices.

    Many thanks to Erudil for suggesting this book at YAPC99.

    Mick
Re: best practice
by Aighearach (Initiate) on Aug 27, 2001 at 18:10 UTC
    I also recommend The Pragmatic Programmer.

    But another thing I recommend is, don't write pseudocode for Perl. In a low level language like C that is needed, but in a high level language it is less needed. I prefer another system of prototyping: the rough draft. Or, as the saying goes, (anybody know who I'm quoting?) "plan to throw one away. You will, anyways."

    UPDATE: To clarify my position on design documents, while I don't think pseudocode is particularly valuable in Perl, I do think charts are valuable, and I do recommend using charts to determine the best data structures.
    --
    Snazzy tagline here

      I disagree with not writing Perl pseudocode. (Or, rather, I would if I ever wrote pseudocode.) Just because Perl is a higher-level language than C doesn't mean that writing in it frees you from good design practices.

      It ends up that most of the uses people put Perl to are simple enough that pseudo-code isn't necessary. However, I worked on a project that was a full-fledged system. We wrote pseudo-code on whiteboards and the like a number of times. Whenever I didn't write pseudo-code, I generally regretted it. (However, I still didn't write pseudo-code. I'm great on theory, lazy in practice. It's also why I'm great with rewrites. *grins*)

      Now, pseudo-code takes a lot of forms. For example, if you're writing a CGI script, the pseudo-code has been created for you - it's called CGI.pm and you should use it. Your design document is the mockup of the page you want to create. (You do create mockups of your website beforehand, right?)

      Another example would be the brainstorming you do with a coworker that gives you the idea in mind. You then go and write a prototype. You test that, adding onto it, and end up with your development module. You bang on that some more and end up with v0.1 - that's a type of pseudo-code we call "prototyping" and "iterative development".

      Now, I'm not saying that doing PDL, or the like, is a bad thing. In fact, in languages that require you to do more work (like C++ or Fortran), it's necessary if you're doing anything with any sort of complexity.

      To achieve a similar level of design-need in Perl, you probably have a much higher level of output-need. Which boils down to:

      To achieve 'A' in C, you need pseudo-code. To achieve 'A' in Perl, you might need pseudo-code, but it's probably a good idea.

      ------
      /me wants to be the brightest bulb in the chandelier!

      Vote paco for President!

        Just because Perl is a higher-level language than C doesn't mean that writing in it frees you from good design practices.
        Of course. I'm not suggesting abondoning good design practices. I'm suggesting a different set of design practices that is popular amoung some groups of Perl programmers.
        You do create mockups of your website beforehand, right?
        No, I use a template system so that design and code are seperate, and design can be changed on the fly. Perhaps the web designers resposible for the look and feel do mockups; I wouldn't know what good web design practices are, I'm just a programmer.
        Now, pseudo-code takes a lot of forms. For example, if you're writing a CGI script, the pseudo-code has been created for you - it's called CGI.pm and you should use it.
        No, that's called code reuse, and yes it is a wonderful thing.
        You then go and write a prototype. You test that, adding onto it, and end up with your development module. You bang on that some more and end up with v0.1 - that's a type of psuedo-code we call "prototyping" and "iterative development".
        No, that's a rough draft or prototype, not pseudocode. By definition, pseudocode is not real code and cannot be executed. Not everything that is part of the design phase is pseudocode.
        --
        Snazzy tagline here
        You wouldn't happen to have a blob of pseudocode lying about the place that you could paste in to give a flavour of how you write it? Perhaps with a corresponding finished snippet? I sort of have an idea of what pseudocode is, but I'd be grateful for pointers about how much detail others find it useful to go into, etc.

        § George Sherston
      If you've got one handy I'd be very interested to see an example of a rough draft (perhaps versus a finished script) to get an idea of how much detail you get into at that stage.

      § George Sherston
        A rough draft would contain a lot of the following type of lines:
        #GGG Add error-checking here. #GGG When in production, this line changes to $foo=2;
        'GGG' is just a character string that allows me to search for it and find all my to-do notes.

        Also, a rough draft doesn't have good comments. It doesn't have a lot of error-checking (though you should build it in as you go.) It probably also isn't factored very well.

        What a rough draft does have is good variable names. Changing variable names in mid-stream is a pain in the ass. Get your names right. If you can't name a variable neatly and succinctly, that's a clue that you don't understand what you're doing.

        (The above paragraph goes doubly for function names.)

        ------
        /me wants to be the brightest bulb in the chandelier!

        Vote paco for President!

        When I said "throw one away," I really meant it. :) I don't have any because I invariably delete them. In fact, I don't even bother putting them into cvs.

        The idea isn't that I'm cutting corners on the rough draft. The idea is, rather, that in a language like Perl where there are many ways to approach a problem, you don't know what the best approach is until you've mucked about in the problem domain a bit. That's the problem with pseudocode; your pseudocode has to contain a high level algorithm, but you often don't know yet which algorithm to use. So my first draft might be procederal. My next draft might be OO. I might switch to a recursive algorithm, or decide to use Tie::RefHash to move all my filehandles into hash keys, storing related data or objects in the value. Usually, I don't know this until I've written some code.

        I can't imagine the pseudocode for procedural and OO approaches being the same; if it's that high level, it's mostly useless anyways.

        And if your first algorithm guess is always correct... well, only mortals would need pseudocode anyway.
        --
        Snazzy tagline here

      Or, as the saying goes, (anybody know who I'm quoting?) "plan to throw one away. You will, anyways."

      You are quoting Fred Brooks, from "The Mythical Man Month".

      Christian Lemburg
      Brainbench MVP for Perl
      http://www.brainbench.com

Re: best practice
by perrin (Chancellor) on Aug 27, 2001 at 19:09 UTC
    If you're looking for more style tips, you might try running your code through perltidy. It has a good, readable style that you would do well to emulate.
Re: best practice
by hsmyers (Canon) on Aug 27, 2001 at 19:50 UTC
    The way I remember it, locals versus globals and a heap of other ideas more or less came into being along with the 'Structured' revolution. If you want a good (possibly best) introduction to some of the pre-object ideas, you can't do much better than Structured Design by by Ed Yourdon and Larry Constantine (Prentice Hall/Yourdon Press, 1978.) And for an honest and funny appraisal see Ed Yourdon's self-review.

    More current thinking (i.e. oo) would say that variable scoping is tied to the idea of information hiding. In a kind of paranoid fashion, the goal is two (at least 2) fold, first, don't let anyone see object information that they have no 'need to know' clearance for. Second if they really, really, really must muck with your stuff, at least insulate that with the usual get/set pair (publish through a controlled interface.) More to the point, globals violate the hell out of these ideas—hence the bad reputation! On a real world basis, you can obviously do whatever you want, but it is usually better practice to avoid global scope, only using them when there is a clear need for same. My usual excuse comes from code optimization, stuffing a variable on the stack when I need to call a function, takes time so if time is crucial I make the needed stuff global. I'd point out here that if speed is a concern, you most likely wouldn't be using Perl anyway, more like assembler or C!

    hsm

      To harp on something here ... 99% of the time optimizations are done, it's usually a case of premature optimization.

      The basic rule of thumb is that if you cannot back up your desire to optimize with hard data, then DON'T! You will make your code completely unmanageable, usually for only a few micro-seconds of gain - completely unnoticeable to the user.

      ------
      /me wants to be the brightest bulb in the chandelier!

      Vote paco for President!

        Clearly your mileage may vary, but the difference between inner loops written in 'C' or higher compared to assembler (and optimized assembler at that) is way significant if you are talking about number crunching. Typically I sin in such a fashion when I'm generating fractals and such like. Being greedy, I will use any trick I can in order to bitblit a little faster!

        hsm

Re: best practice
by fokat (Deacon) on Aug 27, 2001 at 21:52 UTC
    In my experience, I generally follow these steps when approaching a "programming challenge":
    • Think about the problem. Try to look at the problem carefully from distinct angles. I always try to reduce the problem or to divide it into sub-problems. Many of those can probably become modules (that you write, or that you get from CPAN).

      Look for ways to make your approach as general as possible, so that the tool that you're writing will remain useful for as long as possible.

      Do not try to implement all the bells and whistles in the first pass. Stick to the basics and get a prototype running, then add features slowly. Test at each step.

    • Document before writing code. If you have a set of nice sub-problems to work at, focus on them one at a time. Chances are you can solve them easily with one or more modules.

      If this is the path you decided to follow, think a bit about the naming of the module, so that it reflects its function. Always start with h2ph. The very first thing to do after that, is to force yourself to write documentation for the module.

      Writing the documentation first sets very clearly your design and functionality goal. (After all, if you code first and it works, you know you'll never write the docs anyway). This also gives you a chance to design what the interface of the module will look like.

      If the docs you produce are detailed enough, you can spot problems before your test cases do, and plan on them ahead of time.

    • Code the modules AND the test cases Do both at once. Test cases must cover your code and insure that it is portable (yeah, I know this thingy you're writing only needs to be used this time...).

      Your code must be clean and easy to read. Add comments where decisions are taken in your code. Always use strict and -w. They're your friends.

    I wish you luck and wisdom.
Re: best practice
by rchiav (Deacon) on Aug 27, 2001 at 21:12 UTC
    ...and cuddle my elses

    I think if you look at perlstyle you'll see that they encourage not cuddling your elses. I'd imagine that the reason is (this is the reason I don't like cuddled elses anyway) that the "if" doesn't line up with the "else". Example..

    # cuddled - I feel that it's harder to read. if ($foo) { if ($bar) { print "We have a foo and a bar.\n"; } else { print "We have a foo.\n"; } } else { print "You have no foo.\n; } #uncuddled - one way (the way I use) if ($foo) { if ($bar) { print "We have a foo and a bar.\n"; } else { print "We have a foo.\n"; } } else { print "You have no foo.\n; }
    Rich

    update: as dragonchild and MeowChow point out, this is still something that people don't agree on. Some people write their elses (and subs for that matter) as MeowChow has shown. That's why I mentioned that my example was "one way". I just wanted to point out that you might have misread perlstyle

      Careful, you fanatic heretic! You're treading very close to a HOLY WAR ... we at CuddleYourElsesOrElse.com feel that else's have been deprived of their right to feeling safe, warm, and fuzzy long enough! Your patriachal, unfeeling, psychobabble has caused irreperable damage to generations of hapless elses (and their bastard siblings, elsif) and you should be jailed for else-abuse!

      You'd best be careful, or I'm going to call the EPS (Else Protection Services) on you, you whitespacer!

      <Removes tongue from cheek>

      ------
      /me wants to be the brightest bulb in the chandelier!

      Vote paco for President!

      Just as long as ya don't do this...
        
      if ($larry) { foo(); } elsif ($mo) { pie(); if ($stooges) { nyack(); } } else { cow(); }
         MeowChow                                   
                     s aamecha.s a..a\u$&owag.print
        Howdy!

        I guess you'd best not make eye-tracks on my Perl code... :)

        Care to elaborate on your point? Personally, I prefer that my braces align vertically...

        yours,
        Michael

        I actually prefered the above style for a long time. But it turns out many people can't stand it because it turns all code into a venisian blind of alternating white and black.

        On the other hand lining up the braces seems very natural.

        I have come up with the following compromise, which sacrifices ease of editing for readability:

        if ($larry) 
        {   foo();
        }
        elsif ($tom)
        {   pie();
            if ($stooges)
            {   nyack();
        }   }
        else # Randal
        {   cow();
        }
        
        BTW: Code Complete rocks
Re: best practice
by dga (Hermit) on Aug 27, 2001 at 21:32 UTC

    My tip which seems small but is actually big is use eval blocks for error checking.

    Here is an example which happens to use DBI. DBI implements tons to error checking which uses die if you ask it to.

    #up top my $dbh=DBI->connect("DBI:Pg(RaiseError=>1,AutoCommit=>0),dbname=xyzzy +"); #later eval { my($sth); $stmt="SELECT cnam_oficial FROM evocrs WHERE evocrs_id=?"; $sth=$dbh->prepare($stmt); $sth->execute($ec); $sth->bind_columns( \( $cn ) ); $sth->fetch; # $dbh->commit; #if we were writing some records instead of readin +g }; if($@) { # $dbh->rollback; #if we were doing transactions and writing data print STDERR "Could not retreive course name: $stmt: $@\n"; print "Could not retrieve course name\n"; exit; }

    I delclare $stmt in a scope where the eval and the error block can both see it for reporting. The eval saves having to say or die "message" on every line of your code. The RaiseError tells DBI to die on any database error. I put the commit inside the eval as the last line because that will not be reached if there is any problem since a die will jump right out of the eval without doing the rest of the block. The rollback goes in the $@ block which is executed if the eval fails.

    This makes your code nice and clean and results in errors being trapped always. The DBI module is very nice about this since RaiseError causes a die for all errors.

Re: best practice - thanks y'all
by George_Sherston (Vicar) on Aug 28, 2001 at 15:13 UTC
    This is just to thank everyone who contributed to this thread, from which I have learnt a lot. Thanks, danke, grazie, merci, gracias. And also to draw attention to Ovid's excellent node which covers a lot of similar ground in a systematic way. And also to link this thread which was another source of helpful advice on planning. Thanks again.

    Update: camelman's thread on Style & subroutine organization is also pretty instructive.

    Another update: here are a couple more threads that cover similar ground to camelman's. This one deals with tensions between different over-arching principals of programming, and this one talks about when to break code into subroutines - tilly's contribution being, to nobody's surprise, a bit of hightlight.

    § George Sherston

Re: best practice
by derby (Abbot) on Aug 27, 2001 at 22:21 UTC
    As much as I do not like the publisher, the book is a classic on good software techniques - buy or borrow Code Complete

    -D

Re: best practice
by pmas (Hermit) on Aug 28, 2001 at 09:15 UTC
    ++ to you for interesting discussion, George Sherston.

    One thing what I like to do (and is seldom seen) is: Comments to mark what is ended by }.

    I made my editor (MultiEdit) to use "templates", expandable on space. So, i.e. for if statement: after typying if on empty line and hitting space, MultiEdit "template" will expand whole if statement, like:

    if (){ ; } else { ; } ### if
    My "template" is created for me complete with ### if on the end (which I read END IF). And I sometimes even copy-paste logical condition from inside of IF. Why? When you have code over many lines, it's nice to see what statement is ended by }. And because template is doing it for free, why not use it, right?

    I have templates like above for most statements (including sub) using advanced features of my editor. Sure enough I am using smart ident, but it's obvious, right?

    So select carefully your editor, and learn all time-saving tricks what you can get from it. Editor is your first tool -- it is like gun for a soldier. It can save you a lot of pain - and help you with creating code in better style without any additional effort from your side - or as in my case with less effort.

    I do not want to start holly wars about which editor is best, I just want to show an example how features of (a smart programmer's) editor can be used to help maintain better coding practices. Why to avoid Notepad. It's about lazines, not hubris.

    pmas
    To make errors is human. But to make million errors per second, you need a computer.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (4)
As of 2024-03-19 11:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found