LongEx Mainframe Quarterly - August 2013
A couple of years ago I had to work on an existing High Level Assembler (HLASM) program that wasn't performing very well. By not performing well, I mean it ran like a total dog. But as I started to look at the code to get a feel of what it was doing, my heart fell. No comments, no subroutines, no error handling, and no real structure. The code was a complete mess. This meant that a one week job was now going to take at least three times as long. It's easy to write bad assembler code. In many cases, a module that starts life as a 'quick and dirty' fix is modified and changed, to the point that it soon gets out of control. Or a programmer hasn't coded for a few years and needed to create something in a hurry. But let's take a step back and have a think about this. When we write assembler code, we really want code that:
The good news is that writing quality assembler code doesn't have to take any more work. In fact, the chances are that it will save you work, both now when you do the coding, and later when you need to go back and work on it some more. So, here are my top eight easy ways to quickly improve the quality of your assembler code. 1. Become a WriterIn assembler comments are free. They have no effect on the running of the module, so it makes sense to use them to make your program easier to understand. You can't do this with the odd comment or two. You need to become not just a writer of code, but also a non-fiction writer. This way you can explain clearly and in detail exactly what is going on. To give you an idea of the number of comments needed, I usually have more lines of comments than lines of code. I have a standard way of putting my comments together. I separate my comments into four different types: a. HeadingsThese are blocks of comments that give an overall explanation of a module, subroutine, or section. My headings look like: * ======================================================= * Heading text * ======================================================= At the very beginning of a program, I will have a heading with a lot of information, including:
I'll have similar information for subroutines. b. SubheadingsThese divide the code into easy to analyse sections. They look like: * ------------------------------------------------------------ * Comments * ------------------------------------------------------------ I'll usually have one of these for every 20 - 30 lines or so of assembler code. c. Full Line CommentsA single line explaining something. They look like: --- Comment ----------------- The dashes make it stand out from the code. d. Inline CommentsThese explain a line of code. They are on the same line as the instruction, and start in column 40 (or as close as I can make it).For example: LA R3,1(R3) Move to next character You'll notice that my comment above doesn't say what the line does, but why it does it. So it doesn't say "increment R3" (you can see that from the code). And I like to have all of my inline comments start at the same column to make my code look more professional. 2. Don't Write in AssemblerDuring my first year at University, we had a lecturer that obsessed about structured code. By structured code, he meant:
In our first year, we wrote everything in Pascal (this shows how old I am). In our second we wrote first in Pascal, and then converted the program to assembler (it was PDP-11 assembler at the time). Doing this resulted in an assembler program that was (more or less) structured, and also much easier to read and understand. Twenty-five years later, I still use this technique for my larger assembler programs. I'll even write the high level version of the code in comments to the right. For example LA R5,STRING LA R1,10 LOOP1 DS 0H For i = 1 to 10 until string [i] = '0' do CLI 0(R5),C'0' BE ENDLP1 LA R5,1(R5) BCT R1,LOOP1 Next i ENDLP1 DS 0H 3. CheckThis sounds silly, but check for errors. Always. Any time you call a program or service check to see if it worked and handle things if it didn't. Always. I've lost count how many time I've seen programs that just call a module and assume it works. Similarly if a value is passed in from another program or a user, check it. Always. Have error handling routines that handle invalid or missing values. I even go one step further. I like to always have an ESTAE routine to trap abends, particularly if I'm hopping through control blocks. What this routine does depends on the routine, but usually it will output a summary dump (you can specify this in the SETRP macro in your recovery routine), recover with a message (saying that an abend has been detected), and exit with a return code indicating that an error has occurred. And yes, I always test my ESTAE routines. Because this single point will make such a huge improvement to your code, I'll say it again. Check everything. Always. 4. Write Re-Entrant CodeI automatically write all my assembler code as re-entrant. This has a couple of advantages:
I find writing re-entrant code is no more work than non-re-entrant code. You can find out more about re-entrant codes from the article Understanding Re-Entrant Programming. 5. Use SubroutinesMost assembler programs I see are just one program - no matter how many lines of code. I've seen assembler routines that need three or more base registers. A far better idea is to break up large programs into smaller subroutines. This gives you a lot of advantages, including:
I have a standard way of separating code into subroutines. I call a subroutine by: (processing) LA R1,PARMS R1 -> Parms L R15,=A(SUB1) R15 -> Subroutine SUB1 BASR R14,R15 Jump to subroutine (processing) MAINEND DS 0H PR End of main program … DROP , The subroutine code looks like: * ============================== * SUB1 * Perform some processing * * Input: R1 - Parms * R13 - Workarea * * Output: R1 - Updated parms * * Register Usage: * R10 - Input parms * R12 - Subroutine addressability * R13 - Workarea * * ============================== SUB1 DS 0H BAKR R14,0 Save callers' environment LR R12,R15 R12 = Base Register LR R10,R1 R10 -> Input parms USING SUB1,R15 Addressability USING WORK,R13 Address workarea (processing) SUB1END DS 0H XR R15,R15 Zero return code LR R1,R10 R1 -> Updated parms PR Return to caller * ------------------------ * Constants * ------------------------ #SUB1NAME DC C'SUB1' LTORG , DROP , You'll see that the subroutine is completely separate from the main program. It has separate addressability, and the main program passes parameters via R1. In this example I use BAKR and PR, so no savearea needed. The subroutine and main routine share a workarea (rather than call Getmain/STORAGE for every subroutine), but I try to separate the areas used by the main program and each subroutine in the workarea. I also like to use labels that make it obvious whether it's for the main or subroutine (for example, all the labels in my subroutine above start with SUB1). Finally, I also like to have independent ESTAEs for my subroutines. 6. Think About LabelsI like a standard naming convention for my labels, so I quickly know what they are, and how they can be used. This reduces the chances of errors creeping into my code. For example 7. Consider Standard MacrosMany assembler programmers use a standard set of macros to make their programs easier to understand. Find out more from the following articles:
IBMs HLASM Toolkit product includes a set of macros for structured programming. You can find some freeware versions from file 438 on the CBTTAPE website. 8. Test EverythingEveryone knows that you need to test your code. However it's easy to just test the basic program operation. Don't fall into the trap - test EVERYTHING. Here's an idea of what I test:
Often I will write another program that I use for testing. This test program will:
ConclusionThat assembler program that ran like a dog really did turn out to be a nightmare. There were absolutely no comments, the code hopped around everywhere with branch statements, and it used undocumented ways of getting IMS information. I spent an entire week just trying to find out what the program did, and how it did it. If the original programmer of that routine followed these eight hints, my job would have been far easier. And what's more, these hints are easy to implement in your programs. If you use them, you'll have fewer errors, and more robust code that is easier to understand and debug. Thanks to Richard Corak for finding an error in one of our code fragments |