http://www.perlmonks.org?node_id=485945

In reading through M. Conway's Best Practices, I must say that there are a few areas where I'm disappointed to discover that he's not enlightened enough for my one true way ™. For the most part, though, I'm quite delighted to find eloquently stated reasons backing up many things I do. I was particularly happy to find him recommending programmers not use tied variables or lvalue subs. They've always struck me as hacks. And what do those hacks buy us? Well, nothing, really. OK, some tied hashes are useful, but I think said usefulness is overblown.

I know how to tie variables, but I don't like to. In fact, I can't recall a single time I've tied a variable in production code. If one really needs to alter the semantics of built-in datatypes, switch to a damned object, will ya? Tied variables are slow, they're mysterious if you don't realize they're tied (this has caused me a lot of pain) and it's easy to implement them incorrectly. One of their biggest problems is that you can't just look at a single line of code with them and know that something is amiss.

And this brings me to lvalue subs (not just lvalues, as He Who Must Not Be Named pointed out below). These little beasties, in case you're not familiar with them, are what's known as a "big bucket of suck". They let you do this:

sub name :lvalue { shift->name } # and later $object->name = [qw/ random unvalidated value which breaks encapsulation /];

This brings up an interesting question. Why would I, the conscientous Perl programmer, want to throw away my hard-won encapsulation by switching to lvalues? Yeah, I was lazy and saved a line of code in my name method, but that's a bad lazy. I can no longer validate my data. Of course, some lvalue fans argue that I can use tie to get my validation back. Great! Now I've lost much of the conciseness I've hoped to gain and I've probably moved the data validation code away from the method in which the maintenance programmer expects to see it but at least I've switched to a slow, confusing and fragile interface! And hey, if you disagree with that, go argue with the Damianator who already took a Gatling gun to tie. (Wouldn't that have made an interesting movie? "I'll be back, mate." However, I'm not sure switching from an Austrian to an Australian accent would inspire the right amount of dread).

And while we're at it, I can't say I'm a huge fan of overloading accessors to also be mutators. I've done it, but I don't like it. It turns out that the Damianator recommends against this, too. It's easy to write buggy code:

sub name { my $self = shift; return $self->{name} unless @_; $self->{name} = shift; return $self; }

Answer quickly! Does the following set the name or not?

$object->name(@name);

The short answer: maybe. You see, it's impossible to tell by visual inspection if that array has any elements. You can trace through the code and guess, but now you've lost data validation (throwing away information is rarely a good idea) and have a silent failure. Compare that with this:

sub set_name { my $self = shift; croak "set_name() requires a name, silly!" unless @_; $self->{name} = shift; # insert other validation here }

Of course, once you start overloading the method name, what happens if you have a method which returns a value you can't set? Such as the database row id?

$object->name($name); $object->age($age); $object->id($id); # huh?

Does that silently fail? Did that accidentally change the ID you weren't supposed to change? Did you remember to throw an exception with a reasonable error message? Did the programmer using your code curse you out for not making it obvious what you can and cannot do with the methods? With proper getters and setters, you would not have these issues:

$object->set_name($name); $object->set_age($age); $object->set_id($id); # huh?

And the error:

Can't locate object method "set_id" via package ...

Now, we get a verbose failure and a reasonable error message without writing any code to do this. That's good Laziness.

tie, lvalue subs, and overloaded getter/setters: do leave home without 'em.

What features of Perl do you deliberately avoid and why?

Update: (sigh) fixed a couple of typos hv, herveus and AReed pointed out in /msgs. Thanks!

Cheers,
Ovid

New address of my CGI Course.

Side note: lvalues got me to thinking about method call syntax. Geoff asks why some coding standards might require parentheses for method calls. One respondent speculated this might be a sop for those coming from other languages. Some languages (*cough* Java *cough*) alternatively require/forbid parentheses depending upon whether you're calling a method or accessing a property. Frankly, I don't like it when languages such as Java which force me to put the parentheses on a method call. In an OO point of view, why should I care that the value 42 was a property or computed on the fly? What if I hard-coded it at first and didn't encapsulate it in a method and later decided to calculate it? Yes, creating a property without encapsulation is a sign of bad design, but can anyone tell me why on earth it would be a good idea to allow that in a language? I'm stumped.

Replies are listed 'Best First'.
Re: Things I Don't Use in Perl
by itub (Priest) on Aug 23, 2005 at 17:08 UTC
    Call me old-fashioned, but I almost never use the following (although sometimes I use modules that do).
    • source filters
    • prototypes
    • formats (well, that would be old-fashioned)
    • tie (although it has been a helful hack for debugging in some occasions)
    • attributes (including lvalue)
    • barewords (not counting those that are allowed under "use strict")
    • "experimental" regex features. They look cool, but I've been bitten by their bugs too many times. I was surprised to see that Damian recomends some of them as best practices!
    • AUTOLOAD
    • CHECK, INIT
    • CORE::GLOBAL (sometimes useful, such as for testing)
    • Most of the variables on perlvar, but that's too long to mention here. Many of the functions in perlfunc as well.
    • <> to glob
    • foreach (heh, I'm to lazy to type those extra characters. But of course I use for!)
    • pseudohashes (they have been deprecated anyway)
    • version strings (also deprecated)
    • until
Re: Things I Don't Use in Perl
by hv (Prior) on Aug 23, 2005 at 16:20 UTC

    For what it's worth, I am a fan of polymorphic getter/setters, but I autogenerate them rather than writing them individually:

    • if the field is immutable, the method knows it and generates an appropriate error message (but you can set key fields on newly created objects that don't yet have them set);
    • they detect if the setting actually changes the value, and record the fact;
    • when setting, derived values are invalidated;
    • when getting, a not-yet derived value is calculated and cached;
    • they are generated into an intermediate class, so the concrete class can override them simply (though overrides must call the SUPER:: method appropriately);
    • they don't yet auto-generate documentation, but they could do so.
    Nothing here that couldn't also be done with separate getters and setters; the two main benefits are the avoidance of code duplication, and the ease of iterating over the fields.

    I agree with you about tie and lvalue though. :)

    Update: I forgot the one time I have profitably used tie:

    package MyApp::Log; my $log; sub enable { ... $log = MyApp::Log->new($context); tie *STDERR, 'MyApp::Log::STDERR'; ... } { package MyApp::Log::STDERR; sub TIEHANDLE { my $class = shift; bless {}, $class; } sub PRINT { my $self = shift; $log->die(join '', @_); } }
    for those things not caught by $SIG{__(WARN|DIE)___} handlers.

    Hugo

Re: Things I Don't Use in Perl
by friedo (Prior) on Aug 23, 2005 at 17:52 UTC
    My father is an excellent amateur woodworker and has observed the wisdom that "You should never buy a tool until you need it." Similarly I find that I don't bother with features until I must have them.

    I've never used prototypes, except for one occasion which hasn't proven all that useful (to me, anyway.)

    One time I experimented with operator overloading, but then merlyn pointed out a far better (and more obvious) way to do what I needed and I haven't had a need for operator overloading since then.

    I've never felt the need to use tie, prefering instead to use objects when I want a variable that has behaviour.

    I never use subroutine attributes, they smell bad to me, which is part of the reason Catalyst makes me recoil in horror. (CGI::App forever! :) ) I do, however, use Test::Class, which is so cool that I can get over my objection to attributes for that one case.

    I never use the debugger.

    I suppose the moral of the story is:

    1. Keep it simple, stupid
    2. You don't have to know/have everything to be productive
    3. Don't let your prejudices separate you from a good solution (e.g. Test::Class).
        I've seen that, actually. :) I found an approach to the problem that works just as well for me.
Re: Things I Don't Use in Perl
by perrin (Chancellor) on Aug 23, 2005 at 16:24 UTC
    I have a pretty large list of things I avoid in Perl. Frankly, only a fool with a very high opinion of his own elite skilz would use ALL the features in Perl. It would be totally unmanageable.

    Most of the things I try to avoid are ones that cause debugging confusion, like AUTOLOAD, most of the built-in global settings, importing or other symbol table manipulation, etc.

Re: Things I Don't Use in Perl
by chester (Hermit) on Aug 23, 2005 at 16:49 UTC
    Similar to the original post, another thing I never use is overloaded operators. (In any language.) More generally I don't like anything which changes the syntax, or "basic meaning" of a language. (I'm not sure which term to use here.) Tied variables fall into this category, as do lvalues.

    Basically I want a Perl hash to act like a Perl hash; if it's not exactly like a Perl hash, then it should be a SomeOtherHash object. I want an equal sign to mean the same thing anywhere I see it; if I need something else, use an is_equal_in_some_way() sub. I want a sub to be a sub, not something I can assign to.

    It's not that there's anything inherently wrong with those things, it's that they aren't Perl, or that they change Perl in a fundamental way. It's easier to read code when we're all speaking the same language.

    Better to combine existing words into meaningful phrases than to make up new words. Better to make up new words than to change the meaning of words I already understand to mean something else. "From now on, 'green' means 'tired'. I sure am green today!" That's very confusing to me.

      I find overloaded operators very useful in the limited context of mathematical objects such as vectors and matrices. They can make these kinds of formulas more legible IMHO: I'd rather have
      $z = $x x $y / ($x . $y)
      than
      $z = $x->cross($y)->divide_by($x->dot($y));
      (See the Math::VectorReal module, for example).
        It definitely makes the equation easier to read, but I'm not sure if it makes the code easier to read. You can't easily tell that "." may be doing tons of matrix operations (or who knows what else), not concatenating some strings. It looks like standard Perl, but it's not really Perl, it's mathematics. But on the other hand, people may already have a strong preconception of what "." means with regards to matrix manipulation. And it's easier/faster to type. I can see your point.
        The only reason you find those overloaded operators useful in the context of mathematics is that, in general, those symbols have already been overloaded as part of the standard mathematical notational. You're just conforming with a pre-existing convention; and convention is the only reason that such overloading appears natural to you.

        For coding, I prefer to conform to the larger convention: I don't redefine symbols, because I don't need to redefine symbols, and the small gain I get from standardization with a localized mathematical convention is washed out by the big loss in standardization for general coding conventions.

        I guess I don't like code that reads:

        $x = $y + $z; # delete all files if running as user wilbur

        which is exactly what operator overloading allows. Specifically, it removes any guarantees that I once had about what is being done to what, when. Instead, every single line is suspect, and must be read in a much wider context to verify correctness, which I see as a huge loss for maintainability.

        Sooner or later, someone will make a deliberate or accidental piece of malicious code, and the easier it is to track it down, the better.

      I was actually thinking about this, recently. I've discovered that I primarily use overload to overload numify, stringify, and boolify in order to have objects that behave as expected when used in other situations. For example, a Null object.

      I tend to avoid overloading the mathematical operators because the behavior tends to not be well-thought out when it comes to two different unrelated overloaded classes that should both behave as standard numbers.


      My criteria for good software:
      1. Does it work?
      2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Things I Don't Use in Perl
by haoess (Curate) on Aug 23, 2005 at 16:36 UTC
      My understanding of "when study is needed" was: never. I thought that the results of studying were never used, these days. Is that not true?
      rjbs

        I think study does as it always did. It analyzes the string and builds a data structure from it such that repeated REs on that string are ever so slightly more efficient in execution time.

Re: Things I Don't Use in Perl
by BrowserUk (Patriarch) on Aug 23, 2005 at 18:33 UTC
  • 'Singletons' to pretend I'm not using globals.
  • $collection->set_item( 22) = $collection->get_item( 22 ) + 1; when I need $collection[22]++
  • Setters/getters as a thin veneer over C-style structs to pretend they are 'OO'.
  • Group think.

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.

      I can see the argument against using Singletons just as global variables.

      But in many circumstances they are useful. The most obvious two situations I use them are in:

      • Log objects.
      • Database handles.

      Passing a database handle to all methods, or to multiple objects gets sloppy fast - but having a single resource to fetch it from is neat.

      Similarly with debug files; although they are sometimes abused when you realise you need more than one logfile...

      Steve
      --
        I can easily think of two situations where those have driven me MAD.

        I need to connect to a different dateabase than I used to for party of an application. I have to change which variable names I'm using to use the right database.

        I wrote one app, where it had a kernel so to speak, w/ a logger attached. Creating a new kernel would create a second running copy of everything, 'cept the logger, 'cause it was a singleton. Grrr!

        Don't privatize your constructors! If I want a shared object, good, if not, it's just as good. The only thing I ever want globals are things like, numbers. 1 only has one value. 1!

        ----
        Give me strength for today.. I will not talk it away..
        Just for a moment.. It will burn through the clouds.. and shine down on me.

Re: Things I Don't Use in Perl
by demerphq (Chancellor) on Aug 24, 2005 at 08:10 UTC

    Answer quickly! Does the following set the name or not?

    $object->name(@name);

    Well, its not clear to me that this behaviour is a bad thing at all. As written its conceptually similar to

    $object->name(@name) if @name;

    You could keep the dual purpose behaviour but make it more sensitive to this type of error quite simply:

    use Carp; sub name { my $self = shift; if (@_) { $self->{name} = shift; return $self; } elsif (!defined wantarray) { croak "Error:", (caller(0))[3], " called in void context with no arguments"; } else { return $self->{name}; } }

    Your point about using id() as a setter when it isnt meant to be used that way doesnt do much for me either. If the id() sub isnt meant to set the value then it shouldn't and it should throw an error when its asked to. Which puts the dual-use solution in the same position as set_id() would be. (IE it would be a run time error, one caught by the code, one caught by perl).

    Also the two method approach has a problem that the dual use approach doesnt that you havent mentioned at all: in the two method approach you have two code stubs whose responsibility it is to operate on the same data. This means that if you change the field name or the objects construction you have to change two subroutines. With the dual use approach all of the logic to manipulate a given atttribute of an object is encapsulated in one sub. That means that things like validation rules are right next to the fetch logic, which means that when you are reviewing code you can see all of the "rules" that pertain to a given attribute in one place. You don't have to remember to review both. You don't have to independently review the setters validation logic in order to determine what the fetcher will return.

    Personally i find the encapsulation offered by the dual-use approach combined with careful construction to be better practice than separate-subs.

    Updated: to add the missing defined, as noted by radiantmatrix.

    ---
    $world=~s/war/peace/g

      As implemented, your example would fail if the called in scalar context, as in $name = $object->name;, because wantarray will return false if you've called in scalar context. To check for void context, you need to check if the result of wantarray is undefined. This illustrates how easy it is to make a mistake with single get/set methods. Also, what if it is valid to set the name property to an empty string, the string '0' or undef? How would you do that with this combined get/set? Oh, you could play with wantarray to find out if you're getting, but what if (as is the case in the original, and common) the set method is to return the value set? Wantarray will be defined, so you have no way of reliably knowing if you were really called as get or set.

      It is more readable, safer, and simpler to code:

      sub set_name { my $self = shift; ## validation /untainting here ## $self->{name} = shift; return $self->{name}; } sub name { my $self = shift; if (@_) { warn "Attempt to set value in get" } return $self->{name}; }
      <-radiant.matrix->
      Larry Wall is Yoda: there is no try{} (ok, except in Perl6; way to ruin a joke, Larry! ;P)
      The Code that can be seen is not the true Code
      "In any sufficiently large group of people, most are idiots" - Kaa's Law

        As implemented, your example would fail if the called in scalar context, as in $name = $object->name;, because wantarray will return false if you've called in scalar context.

        Good catch.

        To check for void context, you need to check if the result of wantarray is undefined. This illustrates how easy it is to make a mistake with single get/set methods.

        Personally I feel this is a better illustration of how it is unwise to post untested code when you haven't even finished your morning cofee. Of course I'm biased. :-)

        Also, what if it is valid to set the name property to an empty string, the string '0' or undef? How would you do that with this combined get/set?

        With the bug you noticed squished the code presented does all of that doesn't it? If $obj->name(0) is called then it $obj->{name} gets set to 0, likewise with undef. The case of interest is when its called with an empty list.

        Oh, you could play with wantarray to find out if you're getting, but what if (as is the case in the original, and common) the set method is to return the value set?

        Here it is again slightly modified to match your code:

        So if we call it with arguments it either returns the old value, or returns self, whichever flavour you like. In fact your code (which is below)

        has an interesting property given the example before:

        my $val = $obj->set_name(@foo);

        If @foo contains something then $val and $obj->{name} ends up equal with $foo[0]. But if @foo is empty $val and $obj->{name} go to undef. Thus $obj->set_name() is equivelent to $obj->set_name(undef), but with the combined setter, $obj->name() returns the original value of the field, unless its in void context where it is an error. To me its quite arguable which of these two behaviours is to be preferred, and i might even go so far as to suggest that the posted implementation of set_name() should have something like

        die "No arguments in set_name()" unless @_;

        and possibly use it as a way of illustrating how easy it is to make a mistake in a split get/setter. :-)

        ---
        $world=~s/war/peace/g

Re: Things I Don't Use in Perl
by herveus (Prior) on Aug 23, 2005 at 17:22 UTC
    Howdy!

    It's been ages since I used a format, and I don't think I've ever used tie except to play around. I'm sure there are more features that I don't use that don't leap to mind (in part because I don't use them).

    I do find myself agreeing with most of the recommendations in Perl Best Practices, so far, and the explanations are really helpful.

    yours,
    Michael
Re: Things I Don't Use in Perl
by Anonymous Monk on Aug 23, 2005 at 19:48 UTC
    <pedantic>

    I think you mean to say you don't like lvalue subs, not lvalues in general. (of course some pure languages are against lvalues in general). That is, $a is most certainly an lvalue in...

    $a = 42
    </pedantic>

      That's not pedantic. That's a flat out error in my post. It's fixed now. Thanks.

      Cheers,
      Ovid

      New address of my CGI Course.

Re: Things I Don't Use in Perl
by derby (Abbot) on Aug 23, 2005 at 16:25 UTC

    What the heck, I could use some downvotes - I'm not a fan of map. Hey ... even the doc says it's just a funny way to write a foreach loop.

    -derby

      Almost. Map returns an array, which a foreach doesn't AFAIK. So you can get some useful behaviour with:

      join "\n", map{$_->someFunction()} @a;

        Map returns a list
      Maybe it's just that I was a math major, or got too close to those whacky Haskell nuts, but map has always seemed a very natural metaphor for me. (It's the natural extension of a function on a space A to the space An)

      However, this (map is natural instead of foreach) is only true when the expression inside the map isn't itself accumulating side-effects; e.g. the following useage of map doesn't seem natural:

      my $i=0; map {$keywordhash{$_}=(++$i);} @keywords;
      However, this useage seems quite natural:
      %keywordhash = map {$_ => 1} @keywords;
      I guess you could also distinguish these two cases by noting that one calls map in void context whereas the other does not; while that's true, I think I'd still prefer
      do {$keywordhash{$_}=(++$i);} for @keywords;
      over:
      %keywordhash = map { $_ => ++$i } @keywords;
      Even though I like map, because I'm not completely comfortable with the idea of depending on a certain execution order for map, even though I know it has one. When I'm thinking in a linear, this-gets-executed-then-this mode, map rarely makes sense. map does however make sense as a higher-order function, and when I'm thinking like that it's quite natural.

      (Of course, all style niceties get completely discarded inside JAPHs)

      --
      @/=map{[/./g]}qw/.h_nJ Xapou cets krht ele_ r_ra/; map{y/X_/\n /;print}map{pop@$_}@/for@/
Re: Things I Don't Use in Perl
by brian_d_foy (Abbot) on Aug 25, 2005 at 20:24 UTC

    I don't get too excited about any new feature in Perl until it's survived a couple of major versions. A lot of things only sound like good ideas but disappear in the next version. Pseudo-hashes, for instance, died (thankfully), although I still have to deal with their insidiousness with the right versions of Perl.

    Some other things I don't use and haven't seen mentioned:

    • our(): I can see the reason for our(), but most people don't use it to unmask a package variable. They use it so they don't have to use vars. That sacrifices backward compatibility for no good reason.

    • overloaded operators: I like overloaded operators and I think they are a lot of fun to play with, but I just don't think people think about Perl like that. I probably think about this because I learned about that sort of thing when I did C++ where I could overload based on object types. Since Perl think differently, I don't think people are expecting that. For instance, if I want to print a reference (on purpose, to debug or whatever), I don't want magic stringification. I can see the usefulness of that, but most Perl things don't do that so I don't expect it.

    • Exceptions: I think exceptions would be tolerable if everyone used them and they were built into the language, but not everyone does and they aren't. Perl is much more like C than C++. I'd rather have consistent expectations across modules than a list of modules where I have to remember to wrap everything in eval.

    • Source filters: Actually, I don't use much of anything Damian writes, but it doesn't have to do with the code quality, really. Some people get excited about his cool hacks, but can't seperate his production-useful stuff from the cool proof-of-concept hacks. They end up using anything from TheDamian, and some of them shouldn't be used for anything that anyone else has to maintain (and that's just about everything). I pretend none of that stuff exists so I don't spread the word. It's cool stuff: I just don't want to ever maintain it.
    --
    brian d foy <brian@stonehenge.com>
    Subscribe to The Perl Review
        I know this is asking a lot, but would it be possible for you to, as you have time, modify the REAMDEs and main POD for those to indicate their production-worthiness? I have seen Switch and Class::Contract used in production and I personally have used Quantum::Superpositions in production-affecting code (to good effect).

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

        This hadn't occurred to me until I read your post, but now I think it would be very useful if submitters to CPAN gave a similar explicit rating to their submissions. For one thing, this would help distinguish those submissions that are made in the spirit of "release early, release often" from those that are, at least in the author's estimation, ready for production. I realize that the author's opinion of his/her module may be inflated (or in some cases deflated) but I think it would be a valuable piece of information nonetheless.

        the lowliest monk

        Don't is kinda interesting..

        I'm a big fan of syntax highlighting.

        If I have a chunka code that I want to ignore, I delete it (it's in some versioning system..), comment it out, pod it..

        This could be cool to sort of... encase it. Ignore it. And still be able to look at code and know it does nothing. And still see the syntax highlighting.
        Hm- I guess I could also just.. not call the code.. Hm.

        I just took a look at Coy. The fact that the documentation is in haiku makes me an even bigger fan of your work than I was already. Sorry to blather, but that's just great.
        I think Don't belongs in the blue debugging/coding aids category. It's a nicer spelling of if( 0 ) for multi-line commenting.

        --
        In Bob We Trust, All Others Bring Data.

Re: Things I Don't Use in Perl
by adrianh (Chancellor) on Aug 24, 2005 at 10:12 UTC
    Great! Now I've lost much of the conciseness I've hoped to gain and I've probably moved the data validation code away from the method in which the maintenance programmer expects to see it but at least I've switched to a slow, confusing and fragile interface!

    Don't hold back! Tell us what you really feel :-)

    (oh - and ++ BTW)