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

After the helpful review of this code, I would like to submit my latest assignment for collective criticism/suggestion. As with my previous post, I am not asking for help solving the problem. It has already been turned in and graded (100). So, what was the assignment?

Generate a Perl/Tk GUI which implements the basics of a "countdown to" application. You should provide a way for the user to select the Month, Day, Year, Hour, Minute, and Second to "countdown to" from the current time. Do not worry about handling the case in which the user selects a time which has occurred in the past. In addition to solving this problem, you must satisfy the following requirements:

Use at least one sub-routine
Use ternaries
Use grep
Use the configure method on one or more widgets
Use qw//
Use two of Tk's geometry managers(pack, place, grid)


So, I'd appreciate any and all suggestions/criticisms on my code. I am particularly curious about the time routines...which values are the "driving factors?" I've noticed that I can use out of range values for things such as day of the week/year and not receive any errors. Reading Time::Local indicates that timelocal performs error checking, I'm just curious about which values are the key ones.

#!/usr/bin/perl #--------------------------------------------------------------------- +------- #-- Author: Adam McManus #-- Date: 07/25/03 #-- File: countdown.pl #-- Description: A simple "countdown to" program #--------------------------------------------------------------------- +------- use strict; use Time::Local; use Tk; use Tk::LabFrame; use Tk::BrowseEntry; our %w; #-- all widgets in the GUI our %months; our %time; Initialize(); MainLoop(); #------------------------------- Sub-routines ------------------------ +------ sub CreateMainWindow { $w{main} = MainWindow->new(); $w{main}->geometry('+200+0'); $w{main}->title('Countdown'); $w{main}->appname('countdown'); $w{main}->optionAdd("countdown*font", "{Arial} 10 {normal}"); $w{main}->repeat(1000, \&Update); $w{remain_fr} = $w{main}->LabFrame(-label => 'Time Remaining', -labelside => 'acrosstop', -relief => 'groove') ->pack(-anchor => 'nw', -expand => 1, -fill => 'x'); $w{remain_lbl} = $w{remain_fr}->Label(-text => 'Computing...', -justify => 'center', -font => 'Arial 12 bold') ->pack(); $w{future_fr} = $w{main}->LabFrame(-label => 'Future Date', -labelside => 'acrosstop', -relief => 'groove') ->pack(-anchor => 'nw'); for my $time_type ( qw/month day year hour minutes seconds/ ) { $w{"${time_type}_bent"} = $w{future_fr}->BrowseEntry( -label => ucfirst($time_type) . ':', -width => 5, -variable => \$time{$time_type}, -state => 'readonly'); } =item test $w{month_bent} = $w{future_fr}->BrowseEntry(-label => 'Month:', -variable => \$time{month}, -width => 5, -state => 'readonly'); $w{day_bent} = $w{future_fr}->BrowseEntry(-label => 'Day:', -variable => \$time{day}, -width => 5, -state => 'readonly'); $w{year_bent} = $w{future_fr}->BrowseEntry(-label => 'Year:', -variable => \$time{year}, -width => 5, -state => 'readonly'); $w{hour_bent} = $w{future_fr}->BrowseEntry(-label => 'Hours:', -variable => \$time{hour}, -width => 5, -state => 'readonly'); $w{minutes_bent} = $w{future_fr}->BrowseEntry(-label => 'Minutes:', -variable => \$time{minutes}, -width => 5, -state => 'readonly') +; $w{seconds_bent} = $w{future_fr}->BrowseEntry(-label => 'Seconds:', -variable => \$time{seconds}, -width => 5, -state => 'readonly') +; =cut $w{month_bent}->grid($w{day_bent}, $w{year_bent}, -sticky => 'ne'); $w{hour_bent}->grid($w{minutes_bent}, $w{seconds_bent}, -sticky => +'ne'); } #-- END CreateMainWindow sub-routine sub Initialize { $time{month} = 'Dec'; $time{day} = 12; $time{year} = 2003; $time{hour} = 14; $time{minutes} = 0; $time{seconds} = 0; CreateMainWindow(); $. = 0; for( qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/ ) { $months{$_} = $.++; $w{month_bent}->insert('end', $_); } $w{day_bent}->insert('end', $_) for( 1 .. 31 ); $w{year_bent}->insert('end', $_) for( 2003 .. 2038 ); $w{hour_bent}->insert('end', $_) for( 0 .. 23 ); for( 0 .. 59 ) { $w{minutes_bent}->insert('end', $_); $w{seconds_bent}->insert('end', $_); } for( grep /bent/, keys %w ) { $w{$_}->configure(-command => \&CheckRanges); } } #-- END Initialize sub-routine sub Update { my $goal_time = timelocal($time{seconds}, $time{minutes}, $time{hou +r}, $time{day}, $months{$time{month}}, $time{ +year}, 9, 400, 1); my $current_time = timelocal(localtime); my $time_difference = $goal_time - $current_time; my ($days, $hours, $min, $sec) = format_time($time_difference); my $d_sfx = ($days != 1) ? 's' : ''; my $h_sfx = ($hours != 1) ? 's' : ''; my $m_sfx = ($min != 1) ? 's' : ''; my $s_sfx = ($sec != 1) ? 's' : ''; $w{remain_lbl}->configure(-text => "$days day$d_sfx, $hours hour$h_ +sfx, $min minute$m_sfx, $sec second$s_sfx"); $w{main}->update(); } #-- END Update sub-routine sub CheckRanges { my $old_day = $time{day}; my $small_months = join '|', qw/Apr Jun Sep Nov/; $w{day_bent}->delete(0, 'end'); if( $time{month} eq 'Feb' ) { my $end_day = ( ( ($time{year} % 4 == 0) && ($time{year} % 100 != 0) ) || ($time{year} % 400 == 0) ) ? 29 : 28; $w{day_bent}->insert('end', $_) for ( 1 .. $end_day ); $time{day} = $end_day if( $old_day > $end_day ); return; } elsif( $time{month} =~ /$small_months/ ) { $w{day_bent}->insert('end', $_) for ( 1 .. 30 ); $time{day} = 30 if( $old_day > 30 ); return; } else { $w{day_bent}->insert('end', $_) for ( 1 .. 31 ); } } #-- END CheckRanges sub-routine sub format_time { my $sec = shift; my $hours = int( $sec / (60 * 60) ); $sec -= ($hours * 60 * 60); my $min = int( $sec / 60 ); $sec -= ($min * 60); my $days = int( $hours / 24 ); $hours = $hours % 24; return($days, $hours, $min, $sec); } #-- END format_time sub-routine
Thanks,
Inelukii

Replies are listed 'Best First'.
Re: Style and Coding Criticism (part 2)
by halley (Prior) on Aug 05, 2003 at 14:45 UTC

    Update: Congrats on your homework score, and thanks for telling us up front. ;)

    1. Don't use POD to store test data. Instead of =item test, consider putting test data under a __DATA__ token at the end of your sources. Or if you're disabling a section of code and want to use POD, use the proper POD tags to disable compilation. =item isn't always seen as a proper beginning of a POD chunk.

    2. Completely optional, but I would put a call to exit() before you start defining subs. If I don't see one, I start looking for extra mainline code between and after all the subs.

    3. Pick a capitalization style for subroutine identifiers, and stick with it. Your format_time is Perl standard, but CheckRanges is different.

    4. Minor note about your #-- END foo sub-routine comments. If I can't see both the top of the sub and the bottom of the sub on a large screen (or with fewer than two page-ups on an average screen), it might be too long. Consider how the task can be naturally divided. These are short subs, and the indentation is mostly consistent, so I wouldn't bother with these closing comments.

    5. Dennis and Kernighan (of C fame) fought this battle too, but consider putting a space after keywords like for (...) and return (...). If it doesn't have a space, it looks like a subroutine identifier, and for is not a function.

    Check perlstyle for more general Perl style ideas.

    --
    [ e d @ h a l l e y . c c ]

      Or if you're disabling a section of code and want to use POD, use the proper POD tags to disable compilation. =item isn't always seen as a proper beginning of a POD chunk.
      While I agree with part 1, part 2 is untrue -- stick to properly formed pod, like
      #hey some code followed by a blank line =butter is the pod, and the pod is the butter =cut followed by a blank line, no more pod #and here we have some other code
      Even if use fictional pod tags, perl will parse it correctly. You should stay away from hidden pod/code, as the behaviour is not guaranteed in future versions (see Pod::Stripper, perlpod if you don't know what it is).

      MJD says "you can't just make shit up and expect the computer to know what you mean, retardo!"
      I run a Win32 PPM repository for perl 5.6.x and 5.8.x -- I take requests (README).
      ** The third rule of perl club is a statement of fact: pod is sexy.

        I agree.

        As with Perl syntax, perl will parse a lot of POD constructs which other tools just don't bother to try to read properly. This has been improving, but there are a lot of different tools, from formatters to text editor modes.

        --
        [ e d @ h a l l e y . c c ]

Re: Style and Coding Criticism (part 2)
by dragonchild (Archbishop) on Aug 05, 2003 at 14:49 UTC
    My only comment is that you're using globals instead of passing stuff in and out. That leads to potentially hard-to-debug code, because of side-effects.

    ------
    We are the carpenters and bricklayers of the Information Age.

    The idea is a little like C++ templates, except not quite so brain-meltingly complicated. -- TheDamian, Exegesis 6

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

      Just curious -- what would you recommend as an alternative to the global "%w" hash used for holding widget handles in the OP?

      I've been known to use this same approach myself for self-contained Perl/Tk apps of similar size (even larger) -- a global hash that holds all the widgets makes it easy to access any given widget from within any given callback subroutine, and this is handy when the callback for some widget or bound event has to reconfigure one or more other widgets, which might be anywhere else in the GUI.

      Given sensible hash key strings, this seems easy enough to maintain (and to "add features" or change the app's behavior later on, when needed). So, I know globals are "naughty" for various good reasons, but they can be a viable option -- they do have their place, I think.

      If there is a better way (without the global hash) in such a case, I'd be grateful to see it. (I come here to learn stuff like that.)

        There are cases where globals are fine, especially in smaller apps.

        A common way to do a "global variable" is to use a Singleon. I would recommend using Class::Singleton vs. rolling your own, but the basic idea is:

        package Dragonchild::Singleton; my $singleton; sub new { my $class = shift; $singleton ||= bless {}, $class; return $singleton; }
        No matter how many times Dragonchild::Singleton->new is called, the exact same variable is returned. It's partially a style issue, but I prefer singletons over our'ed variables. Other monks will tell you the opposite. TMTOWDI

        ------
        We are the carpenters and bricklayers of the Information Age.

        The idea is a little like C++ templates, except not quite so brain-meltingly complicated. -- TheDamian, Exegesis 6

        Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: Style and Coding Criticism (part 2)
by bobn (Chaplain) on Aug 05, 2003 at 16:47 UTC

    Congrats on "use stict;" - I'd also 'use warnings;" (or -w on the shebang line).

    Nits: instead of:

    $time{month} = 'Dec'; $time{day} = 12; $time{year} = 2003; $time{hour} = 14; $time{minutes} = 0; $time{seconds} = 0;
    write
    %time = ( month => 'Dec', day => 12, year => 2003, hour => 14, minutes => 0, seconds => 0, );

    (The => operator takes care of stringifying what's to the left of it.)

    Consolidating the globals (as in %w) is a good thing, though minimizing their use is beter, as others have said previously.

    Why are you doing $. = 0;?

    --Bob Niederman, http://bob-n.com