views:

89

answers:

4

I was wondering what the best practice in Perl is regarding getting - or, more importantly, setting - a global variable of some module by directly accessing $Module::varName in case the module didn't provide getter/setter method for it.

The reason it smells bad to me is the fact that it sort of circumvents encapsulation. Just because I can do it in Perl, I'm not entirely certain I should (assuming there actually is an alternative such as adding a getter/setter to the module).

+8  A: 

It isn't violating encapsulation if the variable is part of the public API. (If it isn't that's another matter.)

I think direct access is preferable as it allows you to take advantage of dynamic scoping:

local $Module::varName = 42;

This makes conflicts with other code using Module less likely.

Michael Carman
Agreed, I do things like `local $Data::Dumper::Maxlevels = 1;` all the time.
Ether
+1 amplifying the "if part of the public API"; if it is not, other users of this coyly named module might get unexpected behavior and turn on you with nasty teeth
msw
@Michael - what exactly would you consider "public API"? Whatever is in POD? If so, then no, the variable is NOT in POD.
DVK
@DVK If its documented you can use it. If its not, you can use it but there's no guarantee next version it'll still be there or do what it did before. Worse, there's no easy way to tell. There's no error message for setting a global variable that didn't exist. If you *must* use an undocumented global, if there's no other way to make your code work, add a test of the 3rd party module in your own test suite. Ensure that undocumented global is there and does its job.
Schwern
@Schwern - Thanks. That's pretty much what I did for now.
DVK
+2  A: 

Global module variables were in vogue in the past, but considered "bad form" as an interface in Modern Perl. It's important to recognize that Perl is 22-23 years old now, and styles and practices have changed. :) Do note though that there are times when it's still appropriate, because there's some very nice features that come along with package variables. It's as usual a matter of experience and practice to see what a good solution might be.

To understand the best use for package variables, you really need to understand how local works. Check out local's perldoc help. Local lets you take a package variable like (as an example) $My::Variable in the package My, and create a dynamically scoped version of it. Normally if you change $My::Variable in place, it will affect your entire program, and will persist. For small programs, that might not be a big deal. For large ones, this can have disastrous side effects. local lets you make a temporary change to that variable, that's limited to your current scope.

Here's how it works:

use 5.012;
use warnings;

package My;

our $Variable = 5;

package main;

say $My::Variable; # prints 5
$My::Variable = 7;
say $My::Variable; # prints 7
{  # create a new lexical scope
    local $My::Variable = 10; # create a new dynamic scope for $My::Variable
                              # that will persist to the end of the lexical scope
    say $My::Variable;  # prints 10
}
say $My::Variable;  # end of the lexical scope for the localized
                    # $My::Variable, so prints 7 again

Effectively, it lets you use package variables in a safe way. Unfortunately, not everyone knows about local, so they often clobber the global variable. Documenting good use (eg, local) always helps.

A getter/setter with proper object encapsulation prevents a lot of this, but not always. To make it work the way a local variable does, you'd have to do a lot of extra work. The nicest thing about being able to localize a package variable is that you can do temporary changes very easily, say, for a debug variable. Normally, you have to do a pattern like:

{
    my $current_variable  My::get_variable();
    $My::set_variable($new_value);

    # Do code work

    $My::set_variable($current_variable);
}

With local, this becomes:

{
    local $My::Variable = $new_value;

    # do code work
}

(Incidentally, I wish you could do this to lexical variables too, for the same reason...but you can't.) So, for some things, package variables can make sense. It depends on how you want to use it. Things like

  • Debugging Variables
  • Global configuration that doesn't/shouldn't be changed often

However, if it's something that does need to be changed on a regular basis, like

  • Regularly used variables (see the horrible interface for File::Find)
  • Temporary configuration
  • "Object" variables

Basically, anything that needs to be modified more than once or in rare situations, or should otherwise be encapsulated into a generated object, then I'd avoid the package variable.

Robert P
Saying Perl is 22 years old now makes *me* feel ancient. :(
Ether
Will be 23 in about 6 months! :D
Robert P
`local` provides *dynamic* scoping, not *lexical* scoping. If you called a subroutine from inside the block it would see `$My::Variable` as 10, not 7. In retrospect, `local` should have been named `temp`, as it provides a temporary value for a global variable.
Michael Carman
Indeed it is. Correcting.
Robert P
A: 

Isn't input-sanitation/ error-handling what encapsulation is all about?

If there is no need for such, then one could argue against implementing getters and setters, which would then look something silly like sub set_variable { $variable = shift; }.

Mind you, it's easier on the eye to use set_variable(42) than what Michael suggests!

Zaid
Encapsulation is hiding the details of the implementation so it can change without the caller knowing. It is an acknowledgment that we suck at predicting the future. For example, if input sanitation and error handling isn't important *now* it may be later. Use a global and there's no easy way to shim it in (you have to use tie, now your code is slow AND complicated). Or perhaps now the data in question is global but later you want it per object. Or maybe you want to perform an action on every use? Exposing a global is laying a bet on your prediction how your code will evolve in the future.
Schwern
@Schwern: There are whole Java shops that have something like 'No behavior in Value objects' as a "standard". This demonstrates how commonplace it is for getters and setters to add absolutely nothing to "encapsulation".
Axeman
@Axeman Whether or not they're a good idea in general, there is value in accessors on a pure VO. Sometimes it makes sense to change the internal structure of a VO; perhaps its expensive or wasteful to calculate all the data at construction time and you only want to do it on demand? Maybe you want a VO to become a proxy or flyweight object? Maybe a piece of data has become deprecated and you want to warn the user or record its access? These are not possible with public data members. A good language avoids this issue by making no syntax distinction between a public data member and an accessor.
Schwern
@Schwern: Oh, I agree with encapsulation, I'm just saying that getters and setters are a poor substitute for encapsulation, and by themselves aren't even that. In fact, Perl gives you more ability by *tie-ing* package variables to change their behavior. I think Zaid's answer is understandable due to "encapsulation" as it is practiced, and totally suitable to the Perl world, that has more weapons for encapsulation.
Axeman
I'd be scared of any package which used `tie` instead of a get / set routine. That's fooling the user, which isn't a good idea in my opinion.
Snake Plissken
Unfortunately, tied variables are, at minimum, 10 times slower than a normal variable and 3 times slower than a method call. And they're packed with caveats. Exposing a global to reduce overhead winds up adding even more in the long run. They should be used not by design, but as a last resort.
Schwern
@Schwern : Thanks for the input. I guess my answer didn't convey what I wanted to say. *I am not against encapsulation*. I was merely trying to provide DVK with some food for thought, reasons why encapsulation should be implemented, which you fledged-out in your comments. I admit that the answer was written in a hurry... Great discussion though!
Zaid
+1  A: 

If the module doesn't provide an accessor, create one, use it, and send in the patch.

brian d foy