Greg Smacks Down Procedures
In this piece, Greg replies to the Doctor Scripto's Script Shop column on scripting with procedures, Bring in da Subs, Bring in da Funcs.
As anyone who’s ever read the Hey, Scripting Guy! column knows, I spend far more time watching and coaching baseball than I do writing scripts. (Interestingly enough, that’s what they said on my last performance review.) In my defense, however, I will say that my coaching experience has taught me some important lessons about scripting. For example, a few years ago we had a coach in the league who went everywhere dressed in a blindingly-white Michael Jordan jumpsuit, complete with gold chains, jangly gold bracelets, and a baseball hat worn sideways. He believed that by dressing like this the kids would come to see him as being “one of them” and thus accept him into their “inner circle.” (Those are exact quotes.)
As you might expect, the kids didn’t exactly see him as being “one of them.” I won’t bother telling you what they did see him as; you can probably figure that out on your own. Regardless, the players didn’t take him seriously as a coach; in turn, the team played poorly, they only won 2 or 3 games, and no one had very much fun.
Is there a moral to this story? Hey, there’s a moral to all my stories; it’s just that those morals are sometimes hard to find. In this case, though, the moral is easy to pick out: don’t try to be someone you aren’t. If you’re a 40-year-old man, you shouldn’t try to dress like a 16-year-old boy. (Not that many 16-year-old boys actually dress that way, mind you.) And if you’re a script writer, you shouldn’t try to write code like a developer. What’s that supposed to mean? Well, among other things, that means you should keep your use of functions, subroutines, and other “fancy” stuff to a minimum.
Note Unlike 40-year-old men trying to be 16-year-old boys, a script writer can become a developer. But just like you shouldn’t try to dress 16 when you’re 40, you shouldn’t try to be a developer while you’re writing scripts.
On This Page
Functions Do Have a Place
Before I get too far up on my soapbox I need to point out that Peter is mistaken about one thing: the Earth is not flat. (Sorry, Peter.) On top of that, I should add that I have no blanket opposition to all functions and all subroutines; my prejudices typically don’t extend beyond the Los Angeles Dodgers, the Oakland Raiders, and the Dallas Cowboys. In fact, I just wrote an article for TechNet Magazine that uses a recursive function to retrieve a list of all the groups (including nested groups) that a user belongs to.
So does that make me a hypocrite of sorts? After all, I’m supposed to be presenting the case against using functions and yet I just wrote an article that relies on functions. In that case, however, I’d argue that the function made complete sense; in fact, calling a recursive function is about the only way to carry out that task. What I’m opposed to are functions and subroutines that are there simply for the sake of being there, simply so scripting code can look more like something a developer would write. And my personal opinion is that many of the functions and subroutines used by script writers fall squarely into that category. Peter believes that you should encapsulate code blocks into functions whenever possible; I believe that you should never encapsulate code blocks into functions, at least not if such functions can be avoided.
Because It’s There?
To explain what I mean when I say “functions and subroutines that are there simply for the sake of being there” let me tell you about a script I recently reviewed. This is, admittedly, an extreme example, but, sadly, is not an aberration: I’ve seen this sort of thing many times before (and, yes, sometimes in the things the Scripting Guys have done). This particular script uses WMI to gather a bunch of information, then has a series of lines that look like this:
Print(strUser) Print(strDomain) Print(strComputer)
I’m sure most people can glance at these three lines of code and know exactly what they are intended to do; I couldn’t. In fact, for a minute or two I wasn’t even sure if this was VBScript code I was looking at: I know that the Print method exists in some languages, but I knew there was no such thing in VBScript. It was only after I puzzled over this for a couple minutes that it dawned on me: the writer was calling a function named Print. I began thumbing through the code (27 pages worth!) and finally found what I was looking for:
Function Print(strValue) Wscript.Echo strValue End Function
And no, we didn’t edit the function in the interest of saving space: that’s the entire thing right there. Instead of using in-line code that looked like this:
The script writer created a function that, well, ended up doing this:
If you’re looking for a difference, don’t bother: there isn’t one.
Granted, this is an extreme example, but it’s a telling one: there’s absolutely no reason to use this function other than to say that you used a function. This is the kind of thing that drives me crazy, but it’s also the kind of thing I see more often than not. Admittedly, I’m far lazier than most people. But I don’t see a need to write single-line functions that do nothing more than call Wscript.Echo, just like I see no reason to set object references to Nothing (see this Sesame Script column for details) or to include a line like Wscript.Quit(1) at the end of the script. (I haven’t seen many people do anything with these specialized exit codes. They’re there because … well … just because.)
A Linear Process
I can’t say for sure why the script writer chose to create and call a custom-built Print function rather than simply use Wscript.Echo. I don’t doubt, however, that he was influenced at least in part by other programming languages, particularly development languages. (Like I said, several of them actually have a Print method). But, hey, in a world filled with tsunamis, hurricanes and Donald Trump, is a single-line function really such a bad thing? Probably not. But remember our story about the coach in the white Michael Jordan jumpsuit: don’t try to be something that you aren’t. Yes, developers often use functions and subroutines, but there are at least two reasons for that, beginning with the fact that many languages require you to do so: for example, you often have to have at least one subroutine, a subroutine called Main().
More important, however, is the fact that applications and scripts are very different. Applications are often “event driven.” What does that mean? Take Microsoft Word, for example. When you start Microsoft Word, the application loads up, but it doesn’t automatically start opening files and typing documents and doing all the other things the program is capable of doing. Instead Word just sits there waiting for an event – a key press, a button click, whatever – to occur. Suppose you start Word and then leave for lunch. When you get back from lunch, Word will just be sitting there, waiting.
Note. OK, sure: sometimes Word just sits there even if you do press a key or click a button. But it’s not supposed to.
When you have an event-driven application you have to use functions and subroutines, and you have to divide your code into discrete modules. After all, when someone clicks the Bold button the only thing you want your application to do is make the selected text boldface; you don’t want it to also make the text italic, print the document, and save the file under a new file name. Italics, printing, Save As: those need to be separate functions. Applications – especially those with a graphical user interface – tend to be event-driven and this imposes a particular structure on the code, a framework that leaves you no choice but to wrap that code up in little functions and subroutines.
Now, what about scripting? Generally speaking, scripts are not event-driven. There are always exceptions, but the typical system administration script (written as a .vbs file) does not load up and then just sit there waiting for you to press a key or click a button. (Which is actually a goodthing considering the fact that neither Windows Script Host nor VBScript are designed to wait for and then respond to key presses and button clicks.)
Instead, the typical script starts off by executing the very first line; in rapid-fire succession the script then executes lines 2, 3, 4, 5, and so on. This continues until the very last line of code has been triggered; at that point the script automatically terminates.
In other words, system administration scripts tend to be very linear: you start at A and run straight through to Z. To me, that’s a strong argument for writing your code in equally linear fashion. (As we all know, the shortest distance between two points is a straight line.) Oddly enough, Peter disagrees with that: he says that breaking a script up into functions makes it easier to read, something akin to saying that the shortest distance between two points is a really wavy line that doubles back on itself several times. For example, suppose you have a script that looks like this:
Wscript.Echo "1" Wscript.Echo "2" Wscript.Echo "3" Wscript.Echo "4" Wscript.Echo "5"
Not very fancy, but straightforward and easy to read. Why, then, would you ever want to do something like this:
Wscript.Echo "1" EchoItem2 Wscript.Echo "3" Wscript.Echo "4" Wscript.Echo "5" Sub EchoItem2 Wscript.Echo "2" End Sub
Yes, I know: no one would ever actually do something like that. But the point is still a valid one: why interrupt the flow of the script? Peter argues that, with longer scripts, this style is easier to read than a straight linear style; on the other hand, Peter didn’t have to dig through 27 pages of scripting code trying to find a Print function. If the execution of a script follows a linear path, then it only makes sense that it will be easier to read and follow if the code follows a similar path. That way, you can start reading at line 1 and just keep going until you’ve read each and every line of code. No jumping around, no having to mark your place while you try to locate the Print function.
Note. Peter also says that functions and procedures become useful when “… inline code gets too lengthy and makes the script harder to read.” Hmmm. I’m not very good at math, but say we have a block of code with 25 lines. We decide that this is too lengthy and too hard to read. Therefore, we move those 25 lines of code into a function. Don’t we still have 25 lines of code? Wouldn’t this still be lengthy and hard to read? If train A leaves the station at 8:00 AM and travels at a speed of 35 miles per hour then who is buried in Grant’s Tomb?
Reusability – Or Lack Thereof
Of course, Peter would be quick to note that improved readability is just one of the many advantages functions have to offer; for example, he also lauds the re-usability of functions and subroutines. This is another alleged advantage that I have trouble seeing. Remember, neither WSH nor VBScript have an “include files” capability; that is, you can’t simply write a line of code similar to this and automatically have access to all the functions and subroutines found in the file MyLibrary.vbs:
objWSH.IncludeFiles = "C:\Scripts\MyLibrary.vbs"
Can you place all your functions and subroutines in a single file? Sure. Can you then use these functions and subroutines in every script you write? You bet you can … as long as you copy and paste these functions and subroutines into each new script. Peter will tell you that structuring your code in this manner makes it easy to copy and paste:
Sub ShowDate Wscript.Echo Date End Sub
In fact, for reasons that escape me, this is apparently easier than copying and pasting this code:
I don’t see it, but, then again, I don’t see why people eat sushi, use drive-through windows, or watch ice hockey.
Note. I’m not trying to offend everyone in the world. I just have a knack for that sort of thing, even without trying!
If there was a way to use include files in your scripts this all might make more sense. Because that capability does not exist (not without some goofy and less-than-ideal workarounds) you still end up copying and pasting code anyway. I don’t see where the advantage lies.
Power at a Price
Here’s another thing to consider, something which gets back to my point about scripts and applications being different. Programming languages are far more powerful and far more versatile than scripting languages: Visual Basic .NET absolutely blows VBScript out of the water. On the other hand, Visual Basic .NET (in practical terms) requires Visual Studio, and the code has to be compiled before it can be run, and you have to worry about things like data types and the way the code is structured and – well, you get the idea. There’s power in a programming language, but that power comes at a price.
Scripting languages can’t match programming languages on a feature-by-feature basis. But scripting languages do have an ace up their sleeves: if you need to carry out a task all you have to do is fire up Notepad, type in a few lines of code, save the file, and then run your script. Just that easy, just that quick. And that’s the beauty of scripting: it’s fast, it’s easy, and it doesn’t require any overhead or infrastructure outside of Notepad. Can you explicitly declare variables in VBScript? Sure. Can you take your code and parcel it out into functions and subroutines? Sure. However, the more you begin to structure your script like an application the more you need to ask yourself this question: if I’m going to go to all the trouble to write this thing as though it were an application, then shouldn’t I take the next logical step and make it an application? Why follow all the rules and regulations of Visual Basic .NET if you’re aren’t going to get all the power of Visual Basic .NET? That makes no sense. Don’t wear your baseball hat sideways (and, trust me, I know baseball hats!) and don’t write huge, complicated scripts. Scripts are supposed to be quick and easy; if yours aren’t, then you might want to think about whether they should even be scripts.
Keep It Short
Of course, right now Peter’s rolling over in his grave. (Or at least he would be if he wasn’t still alive.) One of the reasons Peter is such an advocate of functions and subroutines is because he feels they really help you when you are writing scripts that are hundreds of lines long. My take on that: don’t write scripts that are hundreds of lines long. There are exceptions, of course, but most people can get by with 10 simple little scripts that each perform a single task rather than one big script that carries out all 10 of those tasks. Big scripts are hard to write, hard to debug, and hard to maintain: would you rather deal with 25 lines of code or 250 lines of code?
Be Yourself – Especially if You’re Lazy
The truth is, I don’t really care whether or not anyone uses functions or subroutines; one of the other big advantages scripting offers is that you have at least some flexibility to use your own coding style. If you like to use functions and subroutines, then use functions and subroutines. My point is that you shouldn’t feel pressured to use functions and subroutines. Peter is quick to note that a lot of script writers use functions and subroutines. Well, good for them. When we first started the Script Center a lot of script writers (and so-called scripting authorities) thought that WMI was too hard for system administrators; in fact, the official TechNet e-seminar on scripting said that very thing.
Today, of course, we all know better. What does that mean? That means that you shouldn’t worry what other people do. Write scripts the way you want to write them. The cold, hard facts of the matter are this: the only way to determine whether a script is good or bad is by determining whether or not it carries out the desired task. If it does, then it’s a good script, and nobody should care how “aesthetically-pleasing” it might be. By the same token you might have written the most elegant script ever composed, a script that scrupulously adheres to all the best programming practices. But unless that script does what it’s supposed to do, well, so what?
Of course, people will say, “Whoa, wait a second there, Greg. What about –“ and then they’ll proceed to make a case for why they need to use a function or subroutine. My response to that: then use a function or subroutine. If you need to do something, then do it. But I firmly believe that script writers should be as lazy as possible: if you don’t need to do it, then don’t. If you want to spend your time handcrafting scripts that belong in a museum or art gallery, well, that’s up to you; if you get lonely at 2:00 in the morning Peter will still be up working on his scripts. To me, scripting is a means to an end: the quicker I can write my scripts the better. With that in mind, if you need me, I’ll be at the baseball field.
Um, right: learning more important lessons that I can apply to my job ….