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

Re: What are the core points of good procedural software design? (functions, code structuring)

by GrandFather (Saint)
on Jun 23, 2008 at 04:08 UTC ( [id://693435]=note: print w/replies, xml ) Need Help??


in reply to What are the core points of good procedural software design? (functions, code structuring)

I find really light weight OO cleans up globals something wonderful. Consider:

use strict; use warnings; my $obj = bless {global1 => 0, global2 => 3}; $obj->setThree (6); $obj->doSomething (); print $obj->{four}; sub setThree { my ($self, $three) = @_; $self->{three} = $three; } sub doSomething { my ($self) = @_; $self->{four} = $self->{three} + $self->{global2}; }

Prints:

9

So the cost is a bless to create $obj then calling "methods" on $obj rather than directly calling the subs and having to access stuff through $self inside the subs.

The real question is, does it buy you anything? Well, for the first cut when there were only a couple of variables and the whole thing was only a few dozen lines long, nope, it buys you nothing at all.

For the second cut where you are adding a few more globals, you don't have to scroll to the top of the file to add globals - not really a win yet. But at least it's likely the subs dealing with the new "globals" are close together and the documentation describing them can be close by too, so there is some real benefit.

But with the third round where you could really do with refactoring the code and generate a real class, guess what - you've already done most of the work!

For anything that is likely to grow I find using light weight OO from the start makes it easier to evolve the code over time. The payback isn't on day one, but by a modest chunk into the project the scale tips and the up front work becomes worth while.


Perl is environmentally friendly - it saves trees
  • Comment on Re: What are the core points of good procedural software design? (functions, code structuring)
  • Select or Download Code

Replies are listed 'Best First'.
Re^2: What are the core points of good procedural software design? (functions, code structuring)
by Anonymous Monk on Jun 23, 2008 at 04:50 UTC

    Grandfather, I like it. And I'm glad to use classes where I can. However, it seems to me that compartmentalizing off a part of the program into a class doesn't actually help me understand how to better write the necessary functions -- the only difference now is they're "instance methods" and every time they access one of the object's variables they need a "$self->{}" to do it.

    That is to say, the following two things look quite similar to me:

    1. A class, with instance variables, and instance methods
    2. A file, with file-scope lexicals, and functions

    So, getting back to the original posted question, perhaps I wasn't specific enough. Here's two specific questions:

    1. Should my functions be returning something instead of setting values of globals? (er.. FSL: file-scope lexicals, that is)
    2. Should my functions be taking arguments (specifying necessary data) instead of just looking at FSLs for that data?

    (1) If you say "yes" to the first one, then what does that buy me? Instead of setting an FSL somewhere, I'm returning values, and so need to create an FSL to hold the value anyway! Ex:

    foobar(); # vs. my $value = foobar();

    ...and now I'm back to my file containing a bunch of FSLs, only now they're scattered around the file (in front of function calls) instead of all listed at the top of the file.

    (2) If you say yes to the 2nd one: my functions need to know lots of things sometimes. I might have to pass in a bunch of arguments to tell it everything it needs to know. It seems ugly to have a function take more than a few arguments. How do you deal with this situation?

      What the light weight OO buys you up front is a "legal" way of using FSLs. Or actually, rather than using a FSL, you are using an OSP (Object Scope Property). In other words, it doesn't really buy you anything at all on day one. However, it does make it a whole lot easier to refactor your code as it grows. There are a few minor things that may be considered advantages of the OO approach:

      You might gain a little advantage by using setters on the object rather than setting random FSLs in various places - at least you can centralize sanity checking of values.

      You might gain a little advantage by grouping manipulation code for particular related properties together and describe how the properties are related in a POD block above the manipulation members. Using OSPs rather than FSLs removes the need to provide all the FSLs at the top of your script so grouping related stuff in sensible way becomes easier.

      None of these is a big win. For small chunks of code, unless someone has already solved the problem for you, there are seldom any big wins anyway. The best you can do is prepare the ground for later grand development, and that is where light weight OO does have an advantage.

      In fact, if it is possible you may migrate to an OO solution, it's easier to not pass stuff around for your first iteration. When you OO things just delete all the FSLs and change any $FSL to $self->{FSL}. So you could argue that you ought avoid both 1. and 2. for the first cut.


      Perl is environmentally friendly - it saves trees
      1. Should my functions be returning something instead of setting values of globals? (er.. FSL: file-scope lexicals, that is)

      ...

      (1) If you say "yes" to the first one, then what does that buy me? Instead of setting an FSL somewhere, I'm returning values, and so need to create an FSL to hold the value anyway!

      At least you don't have the globals in the body of functions definitions. You can now analyze the functions in separation from the rest of the code - this is a clear win. The next step is to move more code into the functions - and you'll get mostly global free code.

        Once upon a time, it was realized that human beings couldn't reliably hold an entire program inside their heads at once, so it was necessary to subdivide tasks into subtasks small enough to grasp.

        The trouble with globals to maintain state is that it "breaks encapsulation", you get "action-at-a-distance" between your subtasks, i.e. you don't have something small enough to hold in your head.

        So from there, you get to the idea that we should write "pure functions" where all arguments are explicitly passed in to the routine, and all returns are explicity passed out of it, and there are no "side-effects".

        The trouble is that if you actually try to write large software projects that way it often gets pretty awkward: if you end up passing in a few dozen pieces of information and returning a dozen, that straight-jacket starts feeling pretty tight.

        So, is there some sort of compromise solution? OOP design, with multiple classes is one of them -- but another, very similar one, is proceedural design with multiple modules. These days I have a slight preference for OOP code, but as far as scoping concerns go, the two are close to identical. The main advantage of OOP (in my opinion) is that the namespaces of the things you're using are always labeled by a short alias, i.e. the names of the objects: consider $Some::Hairy::Name::Space::important_variable, vs the Exporter style of important_variable, vs the OOP style of $shns->get_important_variable.

        As to what do do to refactor your hairy code, I'd suggest:

        • If it's all in a script, you need to move it's guts to a module, any kind of module...
        • Now write tests that verify the behavior of the module (it's better to do this on a module rather than a script, especially if you use the perl debugger).
        • Now do a re-write of the module, making the argument passing explicit. I have an idea on how you might do that (see below).
        • As you isolate each sub from accessing globals directly, you should then write additional tests ("unit tests") that verify the behavior of each of them.

        Okay, now here's a possible strategy for getting the globals under control:

        • Move them all into a hash named %global
        • Rewrite all subs so they accept a hashref to %global as a single argument: some_sub( \%global );.
        • Each sub will then need to unpack each value it needs explicitly: my $global = shift; my $some_value = $global->{ some value };. Don't forget the my.
        • Any changes you need to make to %global need to be organized somehow -- to start with maybe just gather them to the bottom of the routines.
        • Now, look at which values each sub needs to unpack. Do you see any patterns? Can you organize the subs into groups that use (and effect) a particular subset of values?
        • Start moving values out of %global into new hashes oriented toward particular purposes.
        • Re-write the effected routines so that they take the new hashes as arguments, rather than %global.
        • A goal here is to minimize the number of hashes you need, but it's okay for a routine to need more than one as an argument.
        After all that, with any luck you'll be in a position to subdivide this module into multiple modules, one centered around each of the hashes. You could even, you know, bless those hashes, and then you've got perl object classes.

        How do you deal with routines that need to work on more than one of the hashes? In the OOP style, you can do things like pass objects into a method as arguments. That just changes the way you unpack the values a little (if you're being good and using accessors).

        All of this work might not be worth it to you of course, but the early stages of what I'm talking about here (moving the code to a module and writing tests) is bound to be helpful even if you don't complete the program.

Re^2: What are the core points of good procedural software design? (functions, code structuring)
by Erez (Priest) on Jun 23, 2008 at 11:55 UTC

    Although I was expecting someone to say that the core points of good procedural software design are to use object oriented (i.e. "not procedural") design, but I didn't expect that to be the first reply :)

    Stop saying 'script'. Stop saying 'line-noise'.
    We have nothing to lose but our metaphors.

      I'm glad that answer came.

      I've looked into some larger C libraries (GTK and Imlib2 lately), and I found that their interfaces look very object oriented.

      It seems to be a general trend to write OO code even in non-OO languages.

        Agree. But OO code without an OO structure behind is like icing without the cake.

        Taking a "spaghetti code" using only global vars and forcing it into an object seems to me a little like sweeping the dust under the carpet: it's a little better, but very little, and perhaps it doesn't worth the effort.

        In general the problem is far behind: if you want a good OO code you need a good OO structure that comes from an OO thinking before the project is started (or when it is fully rewritten because you can't understand any more how it works).

        Disclaimer: I don't blame the OP for this, for several of my customers have plenty of similar code, much of which written by myself.

        I'm certainly not proud of this, but there are situations in which you are requested to do something quick and dirty (quick in my experience meaning "before yesterday") and then to add features and features to these abominations never having the time to refactor or rebuild them.

        Careful with that hash Eugene.

Log In?
Username:
Password:

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

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

    No recent polls found