|Problems? Is your data what you think it is?|
The value of declarationsby tilly (Archbishop)
|on Apr 06, 2009 at 16:57 UTC||Need Help??|
We are all familiar with the standard advice. Use strict.pm. It catches typos.
My theory is that it is an over-reaction to statically typed languages. When you come from a world where you are used to typing:
over and over again it is easy to overreact to the realization that constantly declaring types gave you no real benefit. It is more convenient to not declare the types at all, so why not go all of the way and remove the declaration entirely?
As we all know, the answer is that the answer is that the apparent convenience is misleading. Required declarations automatically catch a class of real bugs for you. Not requiring declarations forces eternal vigilance. As I noted in Re: Avoiding silly programming mistakes, eternal vigilance is a bad thing.
However it seems to me that Perl programmers have less cause to be smug than might appear. It seems to me that as a culture we have internalized the practice, but not the principle.
Let me give a simple example that many of us have experienced. Have you ever written any logfile processing? It is pretty straightforward. You parse lines into hash refs, then work with the hash refs. Not exactly hard, and you've likely done it.
Did you think of using Hash::Util's lock_keys function to catch typos in your hash access? I will confess that for years I did not. For me the moment of revelation was when I read Damian Conways PBP and he argued against using Hash::Util because it wasn't secure. And I argued back at the page, "Of course it isn't secure! It isn't supposed to be! It is a strict declaration for hashes!" Then I decided to use it the next time the opportunity came up to see if it was helpful.
When that opportunity arose I found that there is a serious performance penalty, but while developing the log processing I caught 2 bugs that would have otherwise taken me much longer to catch. And found that once developed I could avoid the speed penalty by commenting out one line. I've been using the technique ever since.
Let's move on to argument processing. Most of us know that using named arguments to functions is a good thing. It is common to see people pass hashes into functions, and then process them for exactly that reason. You've probably done it. (If not, then consider it.) But how many of you have a check to find when named arguments are passed in that are not on the list of allowed arguments? I do that, and frequently catch errors where I pass in baz and should have passed in bar. Those errors would otherwise be much harder to catch because to see it you have to carefully compare the function call and definition which are in different pieces of code.
Now let me give a somewhat more bothersome example, which is what prompted this meditation.
Not long ago I had to prepare a small Catalyst site as a code sample. In the process of doing that I was quite surprised to find that if a template variable called a method that wasn't there, you had missing data but no error message to help you track it down. Luckily Template::Stash::Context doesn't suffer from that bug.
But that stash does not catch the use of template variables that were not passed in. But I already had a piece of code for that. Which took more work to produce when I needed it than it really should have. (Sorry, highly non-reusable code for reasons that are mostly out of my control. For Catalyst I also added a few variables that were OK to not pass in.)
And there we have it. The most popular web framework in Perl, using the most popular templating solution, defaults to silently swallowing the most egregious possible signs of error. And unless you really know what you're doing you're not going to find the option of making it helpfully give you a hint of what you did wrong.
So the next time you sit down to write some Perl, pause and give thought to this issue. Think through in different contexts what a "declaration" looks like, and how you might automatically catch things that were not properly declared. The first bug that you automatically catch will likely be your own.