Routines
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. 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. 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. 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 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. You can also use routines to create improved performance. You can optimise
or recode in a limited amount of routines (that are 'hot spots') rather
than trying to improve the efficiency of the whole code.
6. Centralising 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 centralised.
7. 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. 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. 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. 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.
11. 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.
12. 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!!
13. 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!
14. Complex operation 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.
15. 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.
16. Quite often a complex test can be placed in a routine, making the whole
test easier to understand - especially with a good routine name.
BACK TO ASSEMBLY CODING STYLES