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

Brethern,

Is having circular dependencies amongst packages and modules:

I'm cleaning up a set of projects of mine that grew like topsy (roughly 50 files, avg of 250+ lines of code each.) Nothing was put in packages or modules. In many places, file A calls functions in file B and vice versa.

E.g., one file (Manip.pl) has functions that manipulate and order the data; a second (Display.pl) has functions that display the results. But there was a quicky function that manipulated the data as part of displaying it. It started out in Display.pl. Then later it was useful for other things, and got called from a function in Manip.pl ...

At a first pass, I'd like to impose packages on the existing files. I'd have Manip.pl use Display and Display.pl would use Manip. I realize that moving the functions between the files would be a better solution, but I'm trying to keep a large problem from becoming a monster one.

By creating these circular dependencies, would I be commiting a mortal sin, or just a venal one?

Thanks
throop

p.s. Any pointers to general advice about bringing old crufty code up to snuff would be appreciated too...

  • Comment on Advisability of circular dependencies in packages

Replies are listed 'Best First'.
Re: Advisability of circular dependencies in packages
by jbert (Priest) on Nov 13, 2006 at 16:34 UTC
    On the subject of bringing old code up to snuff.

    Well structured code has good interfaces between sections, hiding detail. The responsibility of a section and its underlying assumptions is understood in terms of it's interface and accompanying documentation. (OK, yes, abstractions are partially leaky in practice, but to the extent that they aren't, you win).

    Where you lack this, you need to read and attempt to understand much more code to get the picture of what is going on.

    One way you can evolve a codebase is to try and identify candidates for such interfaces (configuration and logging are often a good place to start). If you find somewhere where you can seperate off a chunk of functionality behind a decent interface, you can write your new interface in terms of the old functionality, i.e. you can proxy back to the existing (proven) code whilst gaining the benefit of having clearer expression of intent in your newer code.

    You can document and unit test these interfaces, etc and get benefits that way.

    Over time, as you touch older code, you might migrate it over to the new interface, reducing the amount of code which depends on the old codebase directly.

    Possibly, you could re-implement the interface in terms of new, clean code (relying on your tests to see if you've broken anything). You can then either ditch the old code (if no-one else is using it) or proxy the old methods onto the new code if possible.

    So, I think a large part of design (including retro-fitted design) is coming up with good interfaces between sections of code. The above is one way of retro-fitting such design on existing code.

    Also - IMHO an interface should provide the least functionality you can get away with for the task at hand. If you have an internal function (_foo) which you think might be handy to make public, resist the temptation - the smaller the interface the easier it is to understand.

    If you do want that function in the interface in the future, it's easy enough to rename (because it's only used internally in that module). The reverse isn't true. Taking a method private (say because you want to change an internal algorithm) is much harder - you have to find and change all occurences of the code which uses it (or come up with a reasonable way to fake it in the new implementation).

      jbert: ++

      Very good post--especially the last two paragraphs! I wholeheartedly agree that smaller interfaces are better interfaces. I wish more people had this view. It's much easier to build clean structures with clean, simple interfaces (think Lego) then large interfaces (Windows API, Swing, etc.). You can *always* add new functionality, if required, but it's much harder to remove functionality from an API.

      --roboticus

Re: Advisability of circular dependencies in packages
by roboticus (Chancellor) on Nov 13, 2006 at 15:23 UTC
    throop:

    My take on it is that for the long term, it's a Very Bad Idea(tm). Circular dependencies in code usually indicate that parts of the code (as you mention) are in the wrong place.

    For the short term, however, it's not a bad thing. If you're repairing code, then I'm a fan of slowly improving the codebase step by step rather than changing everything willy-nilly. Take the most annoying problem, and fix it. Then polish up the edges a bit. Rinse-lather-repeat.

    Circular dependencies may not be your worst problem, so it's not necessary that it be the first cleanup you make. However, if it were me, I wouldn't stop the process until I got rid of them.

    ...just one robot's opinion...

    --roboticus

Re: Advisability of circular dependencies in packages
by Old_Gray_Bear (Bishop) on Nov 13, 2006 at 16:27 UTC
    Short term, OK-ish; long term, Madness.

    Get a copy of Peter Scott's "Perl Medic, Transforming Legacy Code". It directly addresses the issues of controlled change (how to refactor without breakage), developing test harness for code that wasn't written to be testable, developing documentation and strategies for keeping it current, the whole nine meters.

    ----
    I Go Back to Sleep, Now.

    OGB

      Seconded. I've just started reading Perl Medic, and I'm thoroughly enjoying it. While I don't know if there's a specific chapter on dependency refactoring, a good test harness will definitely help you with that.
Re: Advisability of circular dependencies in packages
by pileofrogs (Priest) on Nov 13, 2006 at 19:34 UTC

    I'm not really educated about good code design, but I have a notion about your situation, and I'm posting it not so much because I think I'm right, as because I'd like to hear what everyone else has to say about it:

    here goes..

    If two modules load code from eachother, merge them into one module.

    /me ducks head

      That sounds like a recipe for the Big Ball of Mud to me. Did you mean to say "merge the mutual parts into a single, separate, third module"?

        I happened to be reading Big Ball of Mud, and I think it's good enough to mention the link. Ironically, the whole book is on one big page ;^)
      If two modules load code from eachother, merge them into one module.

      In theory, yes. In practice; no, except maybe on a temporary basis; depending on how your mind works.

      On a conceptual level, you need to discover how the two modules come together to form one or more logical units.

      Then you need to take the code that comprises each one of those logical units, turn each logical unit into it's own distinct module.

      Some people might find it's easier to put all the code together into one file while trying to sort out how the code works together; other people don't mind bouncing back and forth from file to file.

      So, it's not a bad first step; but as chromatic points out, it shouldn't be your final step.

Re: Advisability of circular dependencies in packages
by badaiaqrandista (Pilgrim) on Nov 14, 2006 at 00:41 UTC

    I don't think it is possible. One module will be compiled before another and perl will throw a compile time error saying that subroutine such and such is not defined. I've burned with this one before, so don't do it.

    Make display subroutines generic enough so they can display anything, regardless of where they come from. And make manipulation subroutines do not care how it will be displayed. Then make another module that uses subroutines from both modules.

    -cheepy-