Modular Programming
Robert Probin
rob@lightsoft.co.uk

Introduction

There is a story that old programmers tell (including me) that once upon a time we believed that the only reason to write a routine was to avoid code duplication.

The sad thing is a lot of programmers still believe it.

So what are the valid reasons to create a routine? The following paragraphs describe some of the reasons you may want to produce a separate routine.

1.             complexity: Probably the most important reason to produce a routine is to reduce the complexity of the problem. By sub-dividing sections into a routine you can hide the information you don't need to think about outside the routine, and then only concentrate on the specifics of the routine inside. If this is still complex, then subroutines should be created.

      The whole point is program complexity can be reduced by splitting down the structure, and provide various levels of abstraction. Taking deep loop nesting and conditionals, and moving them into a routine simplifies the problem at hand. This applies to all concepts in a program.

2.             Duplication: Avoiding duplicate code is a reason for routines. The basic fact that the code is in one place means it is easier to maintain, saves space, and will reduce the complexity (and hence reliability) of a program.

3.             Isolating Change: By isolating areas that are likely to change means that the effects of changes are also limited. If you plan so that only one, or at most, just small selection of routines are liable to change then it means that changes in these areas are easier and more reliable. Things like hardware dependencies, operating system dependencies, input/output dependencies, laws and business rules, and complex data structures and complex logic are some of the ideal candidates.

4.             Hiding Sequentiality: Hiding sequences in which events happen to be processed can be another reason to create a routine. You can hide this information within a routine, then the outer system can used so that either can be performed first.

5.             Performance: You can also use routines to create improved performance. You can optimize or recode in a limited amount of routines (that are 'hot spots') rather than trying to improve the efficiency of the whole code.

6.             Centralization: Centralizing control is a use for a routine that is similar to information hiding. Examples of this include the control for I/O devices, number of the entries in one table, access routines for files, access routines for data structures, etc, all form a place where one controlling action is centralized.

7.             Hiding data structures: The Hiding of data structures, and their use, in routines form a good use for routines. Complex data structures tend to be very 'computer science' type things, and their physical use doesn't really translate very well to the problem domain. Therefore, the best way to avoid complexity when using these structures is to make routines that do the work, and then call these.

8.             Hiding Global Data: Global data can also be hidden in routines and means you can monitor access to the data if necessary, and change the structure of the data without changing the whole program. Quite often putting access to the data in a routine means you can often find that the data can become local or module level.

9.             Pointers: Access to data by pointers or handles (which can become quite complex) can be hidden within a routine. This means it is easier to verify, and again easier to change.

10.        Reusability: Code reuse is easier if code is separated into modular routines. Even if the code is only called from one place, this section may be usable in another routine if the program has been logically separated.

      If the program is going to be adapted to another program (of very similar or different purpose) then the sections that need to be replaced can be isolated.

11.        Readability: Just making a section of code readable is a very good reason to produce a routine. A small complex action can quite often detract from the general structure, simplicity, and readability of the original routine. A readable routine is worth a lot, simply because someone (which may be you) might need to understand it to upgrade or remove a bug! Never underestimate readability!!


12.        Portability: By isolating non-portable sections of code in routines you can make it easier to move the program over to other machines. Such sections include hardware dependencies and operating system dependencies.

     There are two levels of porting - (a) porting to a machine with the same processor and (b) porting to a machine with a different processor.

     Porting to a machine with the same processor is a matter of re-coding the hardware and OS differences.

"HANG ON A MINUTE!!! DIFFERENT PROCESSOR PORTABILITY!" I hear you say.

     Yes, although higher-level languages are more portable (since you don't necessarily have to recode every line), there is no reason not to port assembler code.

     Perhaps I will do an article on porting code one day, but for now I will say assembler code can quite often be ported in a tenth of the original time, or less!!!!! The general idea is to keep the structure the same nearly line for line. To do this you must 'map' the source processor onto the target processor.

     For example when porting from 68k to PPC, you can assign, for example D0-D7 to be r10 to r17, and A0-A7 to be r20 to r27. You have plenty of spare for other functions. There are more addressing modes on the 68K, so some instructions take more than one PPC instruction but this does not matter too much. (16 bit instructions are the worst!).

     AND seeing you have divided your 'time critical' sections into routines, you can recode specifically for the target processor AFTER you have a complete new version running.

     Many thousands of games, Database packages (!), other programs have been converted like this. Occasionally people write their own tools to do the 'leg work' then tidy up each line, and sometimes they will just hand convert the whole thing.

     As an example, 'The Creation' was last converted from 68K to PPC. It took 2 to 4 weeks to originally design and code, and just 24 hours to convert!


13.        Complex Elements: Complex operations tend to be prone to errors, hence by isolating such things as tricky code, complicated algorithms, protocols, tricky Boolean tests, and operations of complex data, you can reduce the complexity (since it is isolated) and it will mean only one routine needs to be fixed.

14.        Libraries: Isolate extensions, such as libraries, so that if you have to move to another environment where these extensions are not supported, they can be easily re-written using the original library as a model.

     Quite often a complex test can be placed in a routine, making the whole test easier to understand - especially with a good routine name.