in reply to Structural Elegance

Oddly enough, this is no longer an issue that concerns me. With proper test suites in place, I have, on a number of occasions, made large, sweeping changes in my code. Knowing you'll catch any bugs you make with a test suite substantially reduces "fear based programming."

As a side note, I noticed this:

I woke up in the middle of last night worrying about whether I'd trashed a data variable I was merrily carrying along through a control sequence just because it was ...

That sounds suspiciously like "tramp data." Tramp data is data that is passed, unused, through a call chain so that the later methods/subroutines can get access to it. This is a Bad Thing. Your code becomes more difficult to refactor because subs now take extraneous arguments. Also, if you accidently munge the data, it can be difficult to track down where this happened.

Instead, use access subroutines to handle this:

my $TRAMP; sub my_data { $TRAMP = shift if @_; return $TRAMP; }

You can then take the variable out of the call chain. Later, if you find something is altering this data in an unexpected manner, you can put data validation here, dump stack traces, etc.

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re^2: Structural Elegance
by zby (Vicar) on Apr 20, 2005 at 20:09 UTC
    That is you propose to make the tramp data a member of the object? This is a bit similar to making it a global variable - that is you make it here global in the object (dynamic scope, instead of global in the module - lexical scope).

      But if you give it a fancy name, "Singleton", then the global is acceptable...to some.


      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?
        And if you call it a "monad", a different group of people gets excited. But wait, monads are threaded through, which makes them tramp data instead, I guess... :-)

      Well, in this particular case, I showed a non-OO way of handling this, so it's not really a data member of an object, but it can be (in this case, it would be class data.)

      The particular point, though, is that extraneous arguments should not be passed through a call chain. Providing an access method is merely one step in a refactoring. Perhaps it's all that's needed. Perhaps a greater refactoring is needed. Who knows? It's much cleaner than tramp data, though.

      Cheers,
      Ovid

      New address of my CGI Course.

Re^2: Structural Elegance
by samizdat (Vicar) on Apr 20, 2005 at 20:25 UTC
    I don't use OO Perl much at all, but OO's data access principles are definitely a part of my design philosophy. I spent far too many years writing assembler code to fear designing access strategies.

    That said, I'm not above getting lazy. It's rarely a call chain pass that I use, but more usually the 'globalization' of a data item that is plucked by one sub after another without controls on what happens in the middle. In OO, the data item is intenal to an object, and the object is supposed to protect itself. It's all too easy to neglect the sanity checks as you add code.

    In this particular case, I was modifying a working string in several steps, and added a modification in the middle that broke that chain such that the data was not moving from a known state to another known and stable state. My subprocedures were too finely grained, in other words, and I inserted another grain in the middle that broke it.
Re^2: Structural Elegance
by Anonymous Monk on Apr 21, 2005 at 19:15 UTC
    That sounds suspiciously like "tramp data." Tramp data is data that is passed, unused, through a call chain so that the later methods/subroutines can get access to it. This is a Bad Thing. Your code becomes more difficult to refactor because subs now take extraneous arguments. Also, if you accidently munge the data, it can be difficult to track down where this happened.

    Instead, use access subroutines to handle this:

    my $TRAMP; sub my_data { $TRAMP = shift if @_; return $TRAMP; } You can then take the variable out of the call chain. Later, if you find something is altering this data in an unexpected manner, you can put data validation here, dump stack traces, etc.

    This is just using a function call to re-implement a global variable. Perl has real built in globals. Use them.

    I've seen sooo many people try to dodge using global variables, then ending up with an obfuscated version of the same thing.

    "No, it's not a global: it's a single hash that contains all our data, passed down to every function".

    "It's not a global, it's stateful accessor function"

    "It's not a global, it's a class method" (but used maintain state information about unrelated objects)

    "It's not a global, it's a tied hash that references a accessor function" (my favourite)

    And so on, ad naseum.

    The K.I.S.S. principle says that if you need to store global data, just put it in a global variable, unless you have a good reason not to. If you're shoving too many things down your call stack, perhaps you need to re-design your call stack. I'm very much a fan of using the scope you need, rather than jumping through hoops to avoid it because of some "rule" that says globals are always bad.

    Unnecessary use of globals is very bad: avoiding globals when they're useful or necesseary is just as bad.

    End of rant.
    --
    AC