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

Fellow monks...

While I am not having a problem with this script specifically, I was hoping to have some comments about what I had written, perhaps a better way to do something, perhaps a hidden bug. Either way, I will go over some of the code that I had specific questions about and focus on those areas. If you want to read the entire program, its only ~500 lines.

Overview

This script is deisgned to parse a .csv file containing a part#, a description and a price, sorted by category. Once the file is parsed, the part numbers are compared to a list of part numbers already contained in a database. New parts are added to the database and marked as a "new arrival". Existing parts have their price updated and parts that are in the database but no longer in the parts list are deleted.

line 419

# bad kludge, I haven't figured out how to handle this, but # if the query doesn't return anything, this function fails; if($query =~ /SELECT/i) { $return_data = $sth->fetchall_arrayref(); }

Here I'm trying to abstract some of the database implementation by creating a function that takes in a query string and an argument list and returns the results, if there are any. Unfortunatly, when I try to call fetchall_arrayref after performing a SELECT query, it dies on me. So, I make this quick hack to see if the query contains the word "select". I would like to do this better, but I'm not quite sure how.

line 230

sub update_price { # get reference to hash and algorithm for updating parts. my ($price, $price_algo) = @_; #TODO lookup return values for eval eval $price_algo or die( __LINE__ . "Err: $! \n$@\n"); return $price; }

This is part of my design for updating the price on a sliding scale or only under certain conditions. For example, I want to update prices that are over $100 by $15 and anything under, by only $5. My solution was to eval a statement that sets the $price variable. How safe is this? I only expect people who have an account to use this, so they have access to Perl already. Also, is there any more checking I should be doing to make sure the eval statement execute properly?

line 78

my %items = parse_input($options{u}, $parts_algo);

This last one is kind of vague, but the problem is, parse_input doesn't do anything with $parts_algo, it just passes it off to the update_price function. I was trying to come up with a better way of doing this... maybe breaking up parse_input a little, but I hadn't really thought of anything.

Other then that, please take a look at the rest of the code, and if there are any things you would change, please let me know, I'm still pretty green.

Thanks
--
Ben
"Naked I came from my mother's womb, and naked I will depart."

Replies are listed 'Best First'.
(jeffa) Re: Code review and question for parts script
by jeffa (Bishop) on Oct 31, 2002 at 04:29 UTC
    First, even though you have already written code to parse a CSV file, i still recommend you use Text::CSV or Text::CSV_XS - maybe DBI::CSV would be very useful for this script.

    Second, your kludge on 419 ... trying to encapsulate your database queries into one 'catch-all' subroutine is most likely going to lead to one nasty chunk of code. If you really want to abstract your database layer, try something like Class::DBI. One serious problem you have with that subroutine (execute_mysql_query) is that you connect to the database and disconnect from it each time you call that sub. Better is to connect once at the beginning of the script and store the database handle as a global variable. That subroutine really gains you very little right now, and seems rather hackish.

    Third, i would just make $parts_algo a global variable. I like to declare vars that hold the command line arguments with use vars. For a small utility script like this, having your database handle and your options global shouldn't cause any problems ... unless you plan on using this under Apache::Registry (and i should hope not!). As for eval ... i personally don't like to eval just anything that the user can hand me. You should at least verify that the eval comforms to some kind of specification. Also, check the truth value of $@ to see if the eval fails.

    Everything else seems fine ... oh yeah! Don't forget to remove you password when you post code online! ;)

    Updated node: added a few things.

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    

      Thanks for your review, I was wondering about that kludge on 419. Unfortunately, this is on a hosted system, so I don't have access to Text::CSV or Text::CSV_XS (which is a pre-req for DBD::CSV). I will make a request to see if I can get those modules installed. I should have mentioned that in my first post...

      Same goes for Class::DBI, although I hadn't thought of using it. It looks pretty slick, so even if I don't get it installed on the hosted system, I will install it on my system and play around with it.

      When I declare a global variable is it like this?:

      use vars qw/foo, bar, baz/;

      I normally like to stay away from global variables, but in this case I think you're right. The size of the script and the basic interaction make good reasons to have a couple of global variables... although I want to keep them read-only.

      Thanks for all your help and the discussion :)
      --
      Ben
      "Naked I came from my mother's womb, and naked I will depart."

        I already /msg'ed this, but i should probably reply for the benefit of other readers. Check out A Guide to Installing Modules for info on how to install modules on servers you do not have root access for.

        As for use vars drop the commas in qw():

        use vars qw/foo bar baz/;

        And finally, global variables ... yes they are generally considered bad design, but sometimes they can provide elegance that is, IMHO, worth more than the trouble not using them causes. The important thing is to not rely upon them heavily. I tend to favor OO for larger programs, but in a sense, an attribute is a global variable for an object. An object is just a convenient way to tuck away that variable/attribute when you don't need it. Consider a framework that 'HAS-A' database connection abstracted away in a object heirarchy somewhere:
        # some base class that overrides a handler sub handle { my ($self) = @_; my $dbh = $self->{dbh}; my $ses = $self->{session}; my $sth = $dbh->selectall_arrayref(" select id from agents "); my $users = [map { {user => $_->[0]} } @$sth]; my $paramsout = {users => $users}; return $paramsout; }
        (look familiar, maverick? ;))

        Here, making the database connection accessible as an attribute from an object not only makes more sense, it is necessary to keep 'things from getting out of hand'. But for small-ish utility scripts, this kind of abstraction is really overkill that gets it the way of 'getting stuff done'. Happy coding. :)

        jeffa

        L-LL-L--L-LL-L--L-LL-L--
        -R--R-RR-R--R-RR-R--R-RR
        B--B--B--B--B--B--B--B--
        H---H---H---H---H---H---
        (the triplet paradiddle with high-hat)