DCL coding standards and style guide

The following was written after wading through a bunch of daily housekeeping jobs. They were rather unreadable unmaintainable.

Introduction

DCL, or Digital Command Language, is an English-like command line interface for the OpenVMS operating system. Unlike Perl, there is usually only one or two ways to go about coding something, which makes for fairly standard script implementations. However, it still pays to follow some simple coding standards, which produce maintainable and readable code.

The following list of points reflect my personal DCL style guide. You may or may not agree with these rules, but the trick is to have a standard, and to follow it.

The Rules

§ Pick a case to write your code in, and stick to it. If you use camel case, I will personally do you an injury.

§ Learn all the lexicals.

§ Try to keep lines under 80 characters in length. Use the DCL continuation character to good effect.

§ Indentation should be one tab stop, then four spaces. For example:


        $       dcl command
        $label:
        $       if condition
        $       then
        $           another dcl command
        $       endif
        $exit:
        $       exit

§ Use spaces. Readability is key. Don't squash stuff up because you can, or because you think the interpreter may run faster. Code this:


        $       a = a + 1
        $       if a .eq. 3

Not this:


        $a=a+1
        $if a.eq.3

Use DCL_DIET if you really need it.

§ Spell verbs and qualifiers out. Don't type dir. Type directory (or at least the first four characters). Use DCL_DIET if you really need it.

§ Be consistent with labels. A lot of DCL relies on goto. If you have a label called "loop:", have a matching label called "end_loop:"

§ Have one exit in the procedure. Label it "exit:" and do exactly that. Don't code:


        $       if condition
        $       then
        $           exit
        $       endif

Code this:


        $       if condition
        $       then
        $           goto exit
        $       endif

§ Name variables something meaningful.

§ Stick labels up against the dollar. Code this:


        $label:
        $inner_label:

Not this:


        $label:
        $    inner_label:

§ Don't use single line if-then statements. Use multiline if-then-else-endif. Even if it's for one command.

§ Don't use a DCL command when there is a lexical available. For example, don't code this:


        $       on warning then goto nopriv
        $       set on
        $       set process/privilege=(sysnam,sysprv)
        $!...
        $nopriv:
        $       write sys$output "Required privs: SYSNAM, SYSPRV"
        $       status = 36
        $       goto exit
        $!...
        $exit:
        $       set process/privilege=(nosysnam,nosysprv)
        $       exit status + (0 * f$verify (old_verify))

Code this:

 
        $       required_privs = "sysnam,sysprv"
        $       old_privs = f$setprv (required_privs)
        $       if .not. f$privilege (required_privs)
        $       then
        $           write sys$output "Required privs: " + required_privs
        $           status = 36         ! No priv
        $           goto exit
        $       endif
        $!...
        $exit:
        $       junk = f$setprv (old_privs)
        $       exit status + (0 * f$verify (old_verify))

§ Use f$verify () and a logical name to control verification. Code this:


        $       verify = f$trnlnm ("facility_verify")
        $       old_verify = f$verify (verify)
        $!...
        $exit:
        $       exit status + (0 * f$verify (old_verify))

§ Use an overall status variable for the procedure.

§ f$unique () is your friend. Don't code temporary file names without it.

§ DCL is one of the few languages that really is (nearly) self-documenting. Be (reasonably) sparing with comments in the code. Place a standard "Description, Author, Date Written, Modification History" comment block at the end of the file after the exit statement. That way, the interpreter will never see it.

§ Start comments up against the dollar sign, regardless of the current indent level. Code this:


        $! This is a comment
        $       dcl_verb

Not this:


        $       !  This is a comment
        $       dcl_verb

§ Sign your code. You wrote it, you should be proud enough of it to put your name on it.

§ Avoid depreciated DCL verbs. Use define, not assign.

§ Don't use inquire. Use read/error=error_label/end_of_file=eof_label sys$command command/prompt=

§ pipe is for Un*x weenies. VMS coders use temporary files (i.e., process creation is (still) expensive (although there are exceptions)).

§ Surround data with deck and eod statements.

§ Don't code symbol substitution within quoted strings unless you really have to. Code this:


        $       temp = "good"
        $       write sys$output temp + " dog!"

Not this:


        $       temp = "bad"
        $       write sys$output "''temp' dog!"

§ Don't put naked non-printable characters in procedures (for example, escape). To code an escape, and a string containing an escape, code this:


        $       esc[0,8] = 27
        $       clear = esc + "1;1H" + esc + "[2J"

§ Be aware of the define/executive command. It's the only command I know of that silently fails if you don't have CMEXEC privilege switched on. The command creates a supervisor mode logical if you don't have CMEXEC, but gives you no indication that that is what just happened.

§ Don't use global symbol assigns unless you have to. Use local assigns "=" or ":=".

§ Don't use "=" if ":=" is more appropriate. For example, foreign command definitions are usually coded with :=. Code this:


        $       foreign_command := $some_executable.exe

Not this:


        $       foreign_command = "$some_executable.exe"

§ Want to suppress messages for a command? Don't use set message unless you really have to. Redefine sys$output and sys$error instead. Code this:


        $       define/user sys$output nl:
        $       define/user sys$error nl:
        $       some_command_to_suppress_errors_for

Not this:


        $       old_mess = f$environment ("message")
        $       set message/nofac/noident/noseverify/notext
        $       some_command_to_suppress_errors_for
        $       set message'old_mess

Note that the command you are suppressing errors for must perform image activation, as the define/user command stays in effect for exactly one image activation.

§ On that note, be aware of what command activates an image and what doesn't. set default doesn't. set password does. I'm not sure if there's a definitive list of commands that are implemented within DCL. Obviously, the lexical functions don't activate an image. That's why you should use them. Additionally, a lot of commands that have to do with modifying or showing the current settings of the DCL environment do not activate an image. Here's a list of DCL commands that actually accomplish something, and do so without activating an image (as of 8.3-1h1):

§ For symbol comparison, make sure you use the correct set of operators. If the symbol is an integer, use .eq., .gt., .lt., etc. If the symbol is a string, use .eqs., .gts., .lts., etc.

§ Use f$cvtime () to manipulate dates. Don't use f$extract () or any other string manipulation method.

§ Use f$parse () to manipulate filesspecs. VMS knows far better than you do how to separate out a directory or file spec. Again, don't use string manipulation methods such as f$extract () or f$element ().

§ Have a command procedure that's going to run every day? Every hour? And it produces a log file? Be aware of the 32767 version limit, and clean up/rename your log files. For example, code something like this:


        $       file = f$environment ("procedure")
        $       name = f$parse (file,,, "name")
        $       log = "logs:" + name + ".log"
        $       temp = f$search (log)
        $       if temp .nes. ""
        $       then
        $           version = f$integer (f$parse (temp,,, "version") - ";")
        $           if version .gt. 32000
        $           then
        $               purgex/nolog 'log'
        $               rename/nolog 'log' 'log';1
        $           endif
        $       endif

§ Check for errors. This is standard coding practice, which unfortunately gets neglected all too often. You think that an error couldn't possibly happen in some situation in your code? Guess what? Think that and don't error check, and it's nearly guaranteed to bite you. Use set noon at the beginning of error handling routines.

§ If you switch something on, switch it off again before leaving the command procedure. Obviously, the reverse applies: If you switched it off, switch it back on again. Did you set default? If so, please put me back in my starting directory.

Other resources

I've already mentioned DCL_DIET. Another useful tool is DCL_CHECK, a utility that enables you to locate DCL coding errors, among other things. Also, reading the chapters in the OpenVMS User's Guide entitled "Introduction to Command Procedures" and "Advanced Programming with DCL" is a good source of information and examples.

Summary

If you have additions to this, or you violently disagree with something I've said here, I'd be interested to hear your views. The Contact me link is just over there on the left hand side.