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

Howdy.

Executive Summary
=================

What are the arguments in favor of using dynamic code (e.g. Class::Accessor and Class::DBI), versus writing procedural code? I am particularly interested in discussions of code quality, testability, and productivity.

Background
==========

I was extremely fortunate to land a job at a very large company hacking Perl. At the end of the day, my bottom line is that jobs like this (good management and lifetime best compensation) for guys in their mid-40's don't grow on trees, and given the long term compensation practices around here, it's worth my while to stay here until retirement.

I was hired to support a business line that we bought from another company that processes credit transactions worth a lot of money. After porting our stuff onto the company platforms, and stabilizing it to live in a much more secure (thus restrictive) environment without setting off on call pagers dozens of times a night, we are making a push for code quality.

Part of this is going to involve settling on standard ways of doing things. I'm all for that. It's no skin off my nose to use a standard set of libraries, like picking Class::Accessor over Class::MakeMethods, Class::Contract or Class::Generate. When you waltz into a piece of code you're never seen before, it's nice to have a standard code layout, standard way of doing setup and so on.

My issue is that the point guy for the Perl practices study group is looking at Perl like it is Java, and wanting to treat it the same way. His POV is to use static code as much as he can. He writes all his accessor/mutators out manually using vim code snippets or an IDE. He claims that is safer and more maintainable that using dynamically generated closures and abstracting things like that into the background.

On my side, I've been using Perl professionally for 10 years now. And I use abstraction whenever possible, and in my own coding I will grab stuff from CPAN as needed, especially anything high quality with a simple interface.

Ack.

On the other hand, I think this guy is coming from the right place, and he won't just jam a set of standards down my throat. If I can marshal good arguments for using the dynamic aspect of Perl, I think he will at least give an honest listen, and in any case he's not making the final decisions.

Please help.

  • Comment on Help me avoid writing C Programs in Perl for a living

Replies are listed 'Best First'.
Re: Help me avoid writing C Programs in Perl for a living
by BrowserUk (Patriarch) on Mar 25, 2010 at 19:39 UTC

    You are mixing several issues together here.

    1. OO versus procedural (not C versus Perl).

      Your title is:

      Help me avoid writing C Programs in Perl

      But then you say:

      What are the arguments in favor of ... versus writing procedural code?

      You can write very effective procedural code in Perl, without resorting to writing C. You can use functional techniques; 1st class functions; closures; et al. None of which are available to the C programmer. Who conversely can write OO code with the use of simple, but very powerful and efficient libraries like the C Object System.

    2. dynamic code (e.g. Class::Accessor and Class::DBI), versus writing procedural code?

      This is another conflation of several ideas.

      There is are considerable distinctions between OO code & code generation. And between compile-time code generation & runtime code-generation.

      The modules you list, (and that your point guy seems to prefer not to use), are OO, but the distinctive part is that they are compile-time generators.

      The best argument for compile-time code generation, is that the less code you write, the less bugs you can introduce.

      With the second best argument for being the ease of maintenance over cut&paste or hand-coded, if across-the-board changes or bug-fixes are required.

      But there are also caveats. Here are a few:

      • The first requirement is that the code-generator modules you used need to be not just well-tested, but well exercised. Especially in the area(s) of your application.

        Using a well tried & tested module for a type of application for which it was not specifically designed or exercised can mean you're the path-finder as far as a new class of bugs is concerned.

      • It can increase the maintenance task when changes required are specific to individual subclasses of a generated superclass.

        Where one subclass of a generated superclass needs a particular change, but other subclasses do not, you may need to create a new superclass, or layer of intermediate subclasses to accommodate the disparate requirements.

      • Poor performance can be a penalty of generators.

        Eg. Go overboard with something like Params::Validate, and the costs of all the checking can significantly impact the performance of your code.

        An alternative like Class::Contract that allows you to disable runtime checks for production may be a better choice if performance is any sort of an issue.

      • Mix and match several different generator type modules, may cause you to paint yourself into a corner because they don't play well together when really exercised.

        Something like Moose may be a better choice if you have complex requirements--but be aware of the performance issue.

    3. He writes all his accessor/mutators out manually using vim code snippets or an IDE. He claims that is safer and more maintainable that using dynamically generated closures and abstracting things like that into the background.

      Consider briefly that he may have a point.

      Developing a class using a well designed IDE can be just as quick as using a generator. You may end up with more code, but if most of it comes from fill-in-the-blanks templates, it doesn't take long to produce.

      And when bugs arise--and they will--code that seems so convenient because you didn't write yourself, can soon become a burden. And doubly or triply so for generators.

      1. You have to start fixing code you have no familiarity at all.

        Not just code you can't remember, but code that may use techniques and coding standards that are totally alien to you.

      2. Debugging code is hard enough.

        Debugging the code that produced the code that contains the bug can orders of magnitude harder.

      3. Any changes you make have to be done such that they will be accepted back into the module by its authors.

        Otherwise the next refresh and you'll find yourself having to fix them again.

      4. That can be compounded again if the bugs live in a dependency of the module you used.

        Now there are two sets of foreign authors and all of their constraints and goals to take into consideration in addition to your own.

    To summarise:

    • Clarify what you are arguing for and against.

      Mixed up arguments allow one weak point to be shot down, and the rest of your (even good) arguments to be summarily dismissed.

    • Emphasis the difference between compile-time & runtime dynamic code.

      Perl tends to blur the lines between the two, but the former is far less costly in terms of performance, and far easier to debug, than the latter.

    • Pick your dependencies carefully. Just like kids, dependencies have their costs as well as benefits.

      Too new born and they'll keep you awake all night--but you'll be the one screaming.

      Too mature, and the experienced helpers will have moved on to pastures new.

    • Look for short dependency chains.

      Bug identification, acceptance, and fix times will go up to the power of the depth of the dependency chain.

    • Seek out mature and well-tried (in your type of application), modules.

      Real-time modules may be wonderful in their domain, but are naff all good to you if you have long running, indivisible processing to do.

    • Not everything lends itself to being OO.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      OK, I see where you are going with this.

      I guess part of my problem is that I've been discussing this inside the wrong frame. And like you said, conflating OO, code generation, and so on isn't going to help.

      I'd be fine with writing procedural code using functional techniques, 1st class functions, closures, etc.

      That may end up off limits. :(

      FWIW, there was a Java guy on our team who honestly thought that the map function resulted in unmaintainable code. In any case, Point Guy thinks that once Perl finishes building a parse tree, it's done compiling. That hasn't been my understanding for years and years.

Re: Help me avoid writing C Programs in Perl for a living
by Your Mother (Archbishop) on Mar 25, 2010 at 17:25 UTC
    Class::Accessor and Class::DBI

    Don't use those. They were fine for their era but vastly superior tools exist: s/Class::Accessor/Moose/ and s/Class::DBI/DBIx::Class/; # or Rose::DB.

    The sort of encapsulation and extensibility you get with these tools is enormous. Encapsulating code in situations like monetary apps is tremendously valuable. You can write code that simply won't work if it's misused. Code with pluggable log points and business logic that becomes easy to test and manipulate with configuration instead of code hacks. It's still Perl so it's internally fungible but Moose makes it easy to design solid APIs around this idea.

    If you're serious about code quality and not being paged at 3am (or getting your company sued out of existence since money is involved) I recommend test driven design/development. It's not a fail-safe but it will save you huge amounts of time on the initial investment and allow you to write code boldly and quickly because you'll have the baseline correct behavior known.

    About your co-worker: the proof is in the pudding. Get some Moose under your belt, whip up the schema code for your DB with DBIx::Class::Schema::Loader::make_schema_at, and show him what it can look like with a little test suite for your demo.

    When you do this, you're preparing many tools that you don't even know you need yet too; db versioning, object abstractions and serialization, fat model logic, extensible classes with roles and constraints... Jump into the Perl renaissance with both feet!

      These are the sorts of things I'm looking for:

      * You can write code that simply won't work if it's misused.
      * You can write Code with pluggable log points and business logic that becomes easy to test and manipulate with configuration instead of code hacks.
      * ... allow you to write code boldly and quickly because you'll have the baseline correct behavior known.

      As for test driven development, we are moving towards that. In fact I am co-pointing that study group. One of my basic precepts is going to be: If there's not a test for it, that code shouldn't exist. So if you write your own accessors, you need to write test for all your accessors. OTOH, if something like Moose is setting up your accessors in declarative style, then it becomes a question of: is Moose correct? Unfortunately, getting to the place of test-driven development, is going to be a very long road--we have 7 different systems, and hardly any shared code between them. On top of which, time for developing tests is allocated along with the time needed for bugfixes and adding new functionality.

        OTOH, if something like Moose is setting up your accessors in declarative style, then it becomes a question of: is Moose correct?

        FWIW, Moose is really well tested and the Moose development team is dedicated to test driven development. Class::MOP (the underlying meta object protocol that Moose is built on) has 2242 unit tests and Moose itself has 4632 unit tests. We also consider the test suites of the over 700 modules that depend on Moose to be an "external" test suite, so much so that we have a smoke testing setup which downloads and tests a set of approx. 100 of those CPAN modules and runs them before we release (and you can actually make it download all 700 or so if you want, but that tends to take a while). Additionally the Catalyst framework now uses Moose and since that is still a very active project Moose ends up getting exercised there as well.

        One of the key selling points of Moose is that you no longer need to write tedious and repetitive accessors and constructors all the time. Moose instead provides a sane, consistent and well tested object system which does these things for you. We test it, so that you don't have too.

        Now regarding "correct"-ness. No, we do not have a proof available to show that Moose is correct, but unless you're working in aerospace or academic research then I doubt that is what you mean. But you should know that Moose was built after about a year of steady R&D via the Pugs project and is based very heavily on tried and proven object models like CLOS and Smalltalk. It is and has been from the very start built as a solid production ready framework which very specifically does not make use of Perl black magic or insane hacks because the inherit fragility they bring to the code. In short, it is not a toy it is serious business.

        -stvn