Saturday, May 30, 2009

Coding styles that make me twitch, part 2

I'll make today's post short, because it's all about line length.

Limiting the length of code lines is something I try to be good at, not because I think the next guy will have a VT100 terminal and needs a friendly piece of code, but because of basic readability.

When we read an ordinary text, e.g. in a book (you remember books, right?), there is usually quite few characters printed on each line. Here are a few from Douglas Coupland's JPod:

the door to see that all my new furniture was gone, and my
original furniture hadn't come back. Fuck. I phoned Greg,
but realized he was on Cathay Pacific 889, headed to Hong
Kong. I phoned Mom.


Notice how the text area is even narrower than that in this friendly blog?

This comes from a long tradition in printing, it isn't as if they couldn't have squeezed twice as many words in there, if the print was only smaller. But if the print was smaller, they'd probably make the book narrower as well.

It's far easier to read something when you don't have to move your eyes around too much from line to line. This is important to both slow and quick readers.

So, back to coders who make lines of 100+ characters:

What in friggin' Ruritania are you thinking? Not much, that's what.

*GROWL*

Friday, May 22, 2009

Querying quotas with Quota.pm

I never claimed that this blog would be an exercise in expertise. ;)

Prerequisites: Unixy OS with quotas enabled, Perl 5.8.8, Quota.pm 1.6.3.

I always like to provide some sort of progress display for my programs. As a sysadmin, there are times when it might be prudent to check users' files without inspecting them by hand, e.g. when checking for parasitical exploits in websites. The number of files and/or amount of disk space used seem like reasonable measurements for keeping track of that progress.

So you use Quota; and get coding, right?

Except that you may not know beforehand whether you're scanning a local file system, or a remote file system, and Quota.pm requires that you have a magical device identifier before asking what the quota is.

The manual says that you should do something like this:

my $r_uid; # User's real UID
my $dev = Quota::getqcarg($directory);

my @quotadata = Quota::query($dev, $r_uid);

Now we've got some nice quota data, in the following order:

Current blocks used, block soft limit, block hard limit, soft block time limit, current inodes used, inode soft limit, inode hard limit, soft inode time limit.

But no, there's a catch! If you run this on the local file server, rather than via NFS/RPC, then Quota::query() will barf, because $dev is erroneous. How did that happen?

Well, Quota::query() doesn't work if the device is local!

So we have to do this after calling Quota::getqcarg():

if ($dev =~ m{^/dev/}) {
$dev = "127.0.0.1:$directory";
}

The irony is then that there appears to be a need for an RPC listener on the loopback device, at least.

Anyway, I hope this is useful; it helped me to make tings Just Work.

Friday, May 15, 2009

Coding styles that make me twitch, part 1

We'll see how long this particular series gets.

I'll try to come up with some example of coding styles that annoy me, and post about it.

First off is the appended conditional at the end of long one-line Swiss knife code snippets:

my @var = sort { length($b) <=> length($a) } split /[-.,_+ ]/ , $input{longvariablename} if defined $input{longvariablename} && length $input{longvariablename} > 4;


(Yes, that's supposed to be one line, though it doesn't look like it.)

Please, pretty please, don't append conditionals at the end of long one-liners.

Really, just don't, m'kaaay.

Code should, unless it's a one-off one-liner in your shell prompt, be maintainable for others. "Others" includes yourself some time in the future, when you've forgotten what the (insert mst-inspired expletives here) you were thinking at the time you coded the stuff.

The above example isn't particularly complex, or difficult to understand, but it's all on one line, and hardly is easy to parse even if you've got that 170 char wide window to code in.

A few parentheses, a helper variable and a few more lines -- preferably keeping well within 80 columns -- surely won't hurt that badly.

my $lvn = $input{longvariablename};
if (defined $lvn && length($lvn) > 4) {
my @var = sort { length($b) <=> length($a) }
split (/[-.,_+ ]/, $lvn);
}

Of course, these are just my personal opinions, and I won't be knocking on your door with a baseball bat in hand if you don't do as I suggest.

Thursday, May 7, 2009

Simple print-and-log subroutine

I find that I have a use for this almost all the time. It's a silly little set of subs, but in my role as sysadm, I often need to go back and see what all those printed messages were.

So here is my not-so-elegant workhorse for when I need to stuff things into logs, and shuffling modules isn't an option. I hope it's useful for someone else, too.

Dependencies, assumptions and prerequisites:

  • Perl 5-ish
  • Pre-defined global variables:
    • $level - log level (undef = normal, 1 = warn, 2 = die)
    • $logfile - a logfile that we can append to
    • $msgprefix - a program or subroutine specific prefix
    • $verbose - whether to print to STDOUT
  • Preferably disabled output buffering

Usage:

&plog("Log this");
&plogwarn("Warn about and log this");
&plogdie("Log this and die");

The code:

sub plogwarn
{
my $msg = shift;
&plog ($msg,1);
}

sub plogdie
{
my $msg = shift;
&plog ($msg,2);
exit 1;
}

sub plog
{
my $msg = shift;
my $level = shift;
my @lt = localtime;
# Format current datetime sensibly:
my $dt = sprintf("%d-%02d-%02d %02d:%02d:%02d",
$lt[5]+1900,$lt[4]+1,
$lt[3],$lt[2],$lt[1],$lt[0]);
warn "$dt $0: sub plog: No message!\n" unless defined $msg;
unless (open(F,">>$logfile")) {
warn "$dt $0: sub plog: Failed to open logfile ($logfile) for write.\n";
} else {
print F "$dt $msgprefix$msg\n";
close F;
}
if ($verbose) {
unless (defined($level)) {
print "$dt $msgprefix$msg\n";
} elsif ($level == 1) {
warn "$dt $msgprefix$msg\n";
} elsif ($level == 2) {
die "$dt $msgprefix$msg\n";
}
}
}