Six Million Dollar FLSS
Last time we met, back in my GOTO Hell articles, we all — some reluctantly, others enthusiastically — agreed that it's best to code away from using GOTO. Ideally, we want to achieve this by creating stand-alone nuggets of software with discrete argument lists that each perform a specific purpose.
It may be easiest to start with the most universal stand-alone subroutine functions, such as those covered previously in the Six Million Dollar series in this publication. Those examples are general. Sure, they're specific to mvBASIC, but not to any application. Moving one step closer to the application are stand-alone subroutines which deal with the user interface and security. These are sort of 'middleware' functions and their usefulness expands over time.
Messages: I assume you have places throughout your application where error messages are displayed. You may have coded up all those error messages with a number and combined that with a routine that fetches the wording based on the number. But you may still have the display of these error messages scattered all over your code. If you haven't already, consider replacing every single display of a message or dialog box to a 'function-like-standalone-subroutine' (hereafter referred to as FLSS, pronounce floss. The author just made that up.)
Say the display program has five arguments: the character string to display, a code for the type of display, a code for the interface, a returned value, and a flag. Based on that interface code (the 3rd variable) another program can be called to handle the actual display. Yes, you have to go through the software base and make all displays go through this subroutine. It's really worth it even if you still only have one interface (so far,) because changes can be made quickly that are universal. Consistent. And new interfaces can be added easily. Figure 1 is a simplistic example pulled (sort of) from PRC of this face-saving little nugget. You may recognize your terminal emulator / user interface here.
SUBROUTINE MULTIDISP(TYPE,DIALOG.TEXT,UI.MODE,VAR.MSG,RTN.FLAG) VARMSG="";RTN.FLAG=0 BEGIN CASE CASE UI.MODE = "WIN";* wIntegrate CALL WIN.DISP(TYPE,DIALOG.TEXT,VARMSG,RTN.FLAG) CASE UI.MODE = "AT";* Accuterm CALL AT.DISP(TYPE,DIALOG.TEXT,VARMSG,RTN.FLAG) CASE UI.MODE = "BP";* Plain text CALL ASCII.(TYPE,DIALOG.TEXT,VARMSG,RTN.FLAG) CASE UI.MODE = "UNIOBJECTS" CALL UO.DISP(TYPE,DIALOG.TEXT,VARMSG,RTN.FLAG) CASE UI.MODE = "PWS" CALL PWS.DISP(TYPE,DIALOG.TEXT,VARMSG,RTN.FLAG) CASE UI.MODE = "SB" CALL SB.DISP(TYPE,DIALOG.TEXT,VARMSG,RTN.FLAG) CASE 1 CRT @(0,23):"ERROR: Unidentified user interface."; INPUT ANS RTN.FLAG=1 END CASE 999 * RETURNFigure 1
Security: Another great middleware example is the application-level security. This is what your auditors refer to as application controls. Unlike IT controls, which are developer tools, editors and the like, application controls are rules that are baked into the application to authorize access.
One simple approach is to build a table of all your menu options or process names. Those are the keys to the table. Inside the record is information about who — groups, users — can run them, perhaps even a field for time restrictions. You can even put controls in that only allow a function to be run before or after another function. The possibilities are endless, really.
Adding a maintenance screen is nice, so you can edit the options/processes to change who, when, how things can be run. Once you've got this table, the beginning of every option calls a security check sending in its own name, the user trying to run it, and a flag that comes back allowing or disallowing that user to run that procedure [Figure 2].
SUBROUTINE APPLSECCK(PROCESSOPT,USERID,YAYORNAY) YAYORNAY=0 READ PROCESS.AUTH.REC FROM F.SECURITYFILE,PROCESSOPT ELSE PROCESS.AUTH.REC LOCATE(USERID,PROCESS.AUTH.REC,1;LX) ELSE LX=0 PROCESS.AUTH.LEVEL=PROCESS.AUTH.REC<2,LX> BEGIN CASE CASE PROCESS.AUTH.LEVEL=1 YAYORNAY=1 CASE PROCESS.AUTH.LEVEL=SOME-OTHER-THING YAYORNAY=SEND-SOME-CAVEAT-BACK CASE 1 YAYORNAY=0 END CASE RETURN
These are smart and useful. They are time-saving and efficient at that semi-global level; a middleware level. And you get to tie them specificly to your programming environment and to your application requirements. Many of us already have at least part of this in place. With our rapidly widening range of user interfaces and ever-increasing audit and security requirements, now might be a good time to finish it out.
Digging deeper into your application software, specifically, you'll find gazillions of other opportunities to pull code out of your legacy behemoths into simple, reusable, standalone nuggets. They're especially useful for code used in multiple places but are still good practice even if they are not.
Breaking up a giant monolith that is core to your application and has been working for years and was worked on by nth different programmers is challenging. But go after it like eating an elephant, one bite at a time. You'll get there and the benefits are bountiful. Compatibility with future GOTO-less environments is just the beginning.
Getting away from giganta-code removes a lot of code-contention issues, as well. It keeps people from stepping on each other's work, having to wait for one another to finish work, or having to merge their changes in to each other's work.
Separating internal subroutines, repeated chunks of code and other nonsense, into these standalone programs brings flexibility and stability. It avoids variable degradation, unclutters complex programs, eliminates level-ridiculous indentation, and provides an infrastructure for reuse.
It's a six-million-dollar win.