Characteristics of high quality routines
A routine is an individual function or procedure invocable for a single purpose.
Example includes a function in C, a function or a procedure in Pascal or Ada, a subprogram
in Basic, and a subroutine in Fortran. The name of the routine is an indication of its
quality. If the name is bad and it's accurate, the routine might be poorly designed. If
the name is bad and it's inaccurate, it's not telling you what the program does. Either
way, a bad name means that the program needs to be changed.
Reasons to create
- Reduce complexity of the code. This is the single most important reason to create a
routine in the first place. Other reasons including reducing the code size, improving
maintainablity, and inproving correctness are also good reasons for making a routine, but
are relatively minor to the major reason of reducing the complexity of a program so it
becomes easier to understand. An important indication that a routine should be pulled out
of another routine, is that the level of nesting is too deep. The general rule of thumb is
not to go beyond three levels of nesting in any routine, and it is better to pull the
nested part out and put it into its own routine.
- Avoid duplicate code. This is the most appealing method to using routines. By having the
code in one area, you will save on space that would have the duplicate code present.
Modifying the code will be easier, and the code will be more reliable because you will
only have to make sure the code is correct in one area. The modifications you make will
also be easier to do, because you will know the changes you have made, and be consistent
in how you continue to make those changes.
- Limiting the effects of changes. You should create your design with the thought in mind
that the parts which will probably change are the easiest to change. These areas include
what hardware the code will run on, the input and output considerations, complex data
structures, and the business rules for whatever application you are designing for (eg.
Accounting programs always have rules that can change over time).
- Hiding sequences of events. You should make the order in which events are dealt with
hidden to the user. Design the system so that any event could happen first if needed, and
then have a routine that hides the way the order in which following events are performed.
- Improve performance. You can make your design so that you only have to change code in
one area - not several areas. This method is called "optimization", and it
allows for changing the code to produce a more efficient algorithm, or use a more
difficult programming language (eg. machine language).
- Central control for a group of routines. Using centralized control is comparable to
information hiding, but it is a "heuristic" - a technique that helps us look for
an answer - that we should have included in ours skills as programmers. Control allows for
changes in the number of entries in a form, and control of disks, tapes, printers, etc.
Using a form of control where you use one routine to read a file, and another to write to
it is particularly useful because when the file requires changing it to a data structure
contained in memory, the resultant changes will only make an impact on the access
routines. Also, using data structures with specific routines, and reading and modifying
the contents of the data is another way to have centralized control. You should use
centralized control and its various forms to your advantage.
- Hiding data structures. Routines that hide their data structures allow for a level of
abstraction that greatly reduces a program's complexity - it becomes easier for someone
reading your code to understand it. You cna reduce considerably the probability of making
an error while working with the data, and also you can easily change the data structure
without having to heavily modify your program.
- Hide global data. Several benefits come about when you use global data in an access
routine. First, you can change the data structures in the program without having to change
the program itself. Second, you can keep track of when the data is accessed. Lastly, it
forces you to think whether this data should be global in the first place. It is better to
avoid global data when you can, and use it when you must. When you decide you have to have
global data, treat it as local to several routines in a module, or in an ADT (Abstract
Data Type).
- Hide pointer operations. Pointers are difficult to read, and are easy to make errors
with. By placing pointer operations in routines, you can think better about how they are
supposed to operate rather than the details of pointer manipulation. Also, you can be
assured the code is correct if you put these operations in only one place in the code, and
if you decide to use a different data structure rather than the pointer operations, it
will be easier to change the program with jeopardizing the routines that used those
pointers.
- Promotes code reuse. By placing your code into modular routines it can more easily be
substituted into other programs compared to having the same code in one large, complicated
routine. Place the code in its own routine and this routine can be placed into another
program with minimal changes, which saves you a great deal of time in creating code.
- Future planning for related programs. Usually programs have to be changed in the future
to better suit some new need or requirements. With this expectation in mind you should
isolate the parts that are expected to change by placing them in their own routines. This
allows for easier modification to the routine without having to change the rest of the
program.
- Making code more easily read. The best way to document how something is done, is to
place the code into a routine with a good, explanatory name. A well chosen name allows for
proper documentation of what the routine is supposed to do, and makeds the routine's code
less complex for someone else to understand.
- Improve portability. By using routines you can isolate the code that is not portable or
transferable to other programs - the code can only be used in the program it was intended
for and not easily put into another program without major modifications. Nonstandard
features of programming languages, hardware dependencies, and the OS (operating system)
dependencies are all examples of features that are not portable, and should be taken into
account when writing new programs.
- Isolate complex operations. Difficult to understand algorithms, protocols for
communication, difficult to understand boolean tests, and operations on complex data are
all areas which are easy to make errors in. When an error is present, it will be easier to
find the error if it is contained in a routine, and not in different areas of your
program. Also, if later down the road you find an algorithm or method that works better or
more efficently, it will be easier to change the previous algorithm if it had been in a
routine from the beginning. Trying several designs and using the best one is a technique
to use during the development phase of making your code.
- Isolate use of nonstandard language functions. Several programing languages have
nonstandard extensions that are applicable only to that particular language. You can run
into trouble in using such extensions if they are not available in differing versions of
the same language, or not available on different hardware from the hardware you used to
create the routine. Make sure when using extensions that you have made routines of your
own that can access the routines with the extensions. This allows you to change a
supplier's nonstandard routines with the ones you have specifically written.
- Simplify boolean tests. Placing complicated boolean tests into a routine or function can
make your code more readable in that the test details are not in view, and a good function
name can easily illustrate the purpose of the test. By supplying the test with a function
you can place emphasis on the test's significance in your program, and the pay-off is that
the code and the test are easier to understand.
- Bad for the sake of modularization. You should never create a routine for the sake of
modularization by itself. All the reasons given previously are suffient enough to create a
routine, and just for the sake of modularization is a unnecessary reason.
Good Routine Names
- Procedure name: use a strong verb with a clear noun. The name of the routine should
clearly illustrate what the procedure does, and any operation on the noun part of the name
implies the verb-plus-noun method of making good names. Remember with object oriented
languages you do not have to include the name of the noun in your procedure because that
noun (or object) will be referenced in the call to the procdure you have created.
- Function name: use a return value description. A function should always return a value,
and thus the function should be named for the type of value it returns.
- Avoid unclear verbs in the name. A routine that is well-designed and works well can
still be have a verb in the name that is not strong enough or easy to understand. Make
sure that the verb used in the name is strong and clear. This helps to eliminate routines
with a weak purpose with the weak name being the primary symptom. The best way to correct
this is to make your change the routine and related ones so that they have stronger, and
clearer names that accurately describe all of your routines.
- Fully describe all the routine does. Make sure the routine's name describes all the
outputs and side effects it can have. The best way is to write your code so that you cause
processes to happen directly, and without any side effects if you can.
- Make the routine as long as required. The best average length for a variable name is 9
to 15 characters long, and this has been found out from research in the computer science
areas.
- Make conventions for common operations. If you are programming in a project with many
people, then make sure you adopt a naming convention that everyone agrees to work with.
This helps in being able to easily tell the difference between different kinds of
operations in each person's code.
Routine Cohesion
Cohesion means how closely related the operations in a routine are. It can be referred
to as "strength", or how strongly your routines are related to each other. The
overall goal is to have each routine do one and only one thing, do that one thing well,
and not do anything else. The reward is greater reliablity of your code, and your code is
easier to modify when anticipating future changes to the requirements of the program. The
following types of cohesion are deemed acceptable types of cohesion.
- Functional Cohesion. A routine should do one thing and only one thing. This is the best
kind and strongest type of cohesion for a routine.
- Sequential Cohesion. This type of cohesion occurs when a routine that has operations
that must be done in a certain order with data operated on in each step, and all the steps
together do not make a complete function when done together. If the operations in your
routine all work to do a single function, they then make a routine with functional
cohesion. It can best be summarized as doing a sequence of processes on a common data
object that requires a specific order.
- Communicational Cohesion. This type of cohesion happens when the operations in a routine
use the same data, but the operations have no other relation to each other. The data that
is modified is "communicated" to the other operations, but that is the only
contact the different operations in the routine have with each other.
- Temporary Cohesion. This type of cohesion happens when the operations on the data have
been placed into a routine simply because they all done all at a common time. Temporal
routines should be thought of as ones that organize how events occur. This can best be
achieved by having the temporal routine not do certain activities by itself, but being
able to call other routines to do the specific activities. This makes it clearer that the
temporal routine's purpose is to organize the activities of other routines, rather than do
the activities itself.
Unacceptable Cohesion
This occurs when code is pooly organized, hard to debug, and hard to modify. If a
routine has bad cohesion, it's better to rewrite it than to try to fix the problem.
- Procedural. A sequence of unrelated operations in a certain order. The operations don't
share the same data, and usually have a vague name because they don't really do just one
operation.
- Logical. A bunch of routines that are linked in the same routine by a control flag
that's passed in. You shouldn't need to pass a flag to control another routine's
processing. It's cleaner to have separate routines that do separate operations. If the
operations share some of the same code or data, the code should be put into a lower level
routine, and the routines should be packaged in a module.
- Coincidental. A bunch of routines or operations that just happen to be together. They
have no distinguishable relationship to one another.
Intimacy
This refers to the directness of the connection between two routines.
- More intimate is better. The most intimate connection between routines is a parameter in
a parameter list.
- Less intimate implies global data. It communicates less directly. The data might go
where you want it but it might not. It is better to use the parameter list to pass data.
Visibility
Visibility refers how much the transparency of the connection between two routines. The
connection should be as obvious as possible. The visibility can be achieved through
different ways. One of them is to pass data through parameter list. The method makes the
connection obvious. One bad example is to modify global data to ensure another routines to
use it. Documenting the global-data connection makes it more obvious and is slightly
better.
- The connection should be obvious.
- Parameters verse global data.
Flexibility
Flexibility refers to how easily you can change the connections between
routines.Ideally, you want something more like the snap-in modular connector on your phone
than like bare wire and a soldering gun. Fiexibility is partly a product of the other
coupling characteristics, but it's a little different too. In short, the more esaily other
routines can call a routine, the more loosely coupled it is, and that's good because it's
more flexible and maintainable. In creating a system structure, break up the program along
the lines of minimal interconnectedness. If a program were a piece of wood, you would try
to split it with the grain.
- How easy is it to modify the connection
How Big Should It Be
- No hard and fast rules.
- Dont artificially break routines into smaller units of code.
- Tends to cause errors.
- Leads to coincidental cohesion.
- Writing very long routines tends to cause errors.
- Above 150 and 200 lines examine the structure to see if some natural sub-routines are
apparent.
- Dont force the creation of sub-routines just to reduce the size of the code to
some magic number.
Defensive Programming
- Your code should be bullet proof.
Using Assertions An assertion is a function or
macro that complains loudly if an asumption isn'true. Use assertins to document
assumptions made in the code and to flush out unexpectec coditions. An assertion function
usually takes two arguments: a boolean expression that describes the assumption that's
supposed to be true and a message to print if it's not.
Example of Using an Assertion to check an Input Paramerter
float tan
(
float OppositeLength,
float AdjacentLength
)
{
/* Computer tangent of an angle*/
Assert ( AdjacentLength !=0," AdjacentLength detected to be 0.");
return( OppositeLength / AdjacentLength);
}
- Data passed to your routines should not cause catastrophic failures.
- Your routines should be written with the idea that they will be modified or enhanced.
Examples of Bad and Good Routines
Here are examples of bad and good routines.
Reference:
Code Complete, A Practical Handbook of Software Construction.
Steve McConnell. Microsoft Press (1993).
Original Authors :Brien Beattie, Dean Varg, Yixiang Wang, Yawen Wu, Hong Zhang
Date : June 4, 2000
Modififed By :Sirigon Sukpan, Hua Ma, Cyren Aldecoa, Gerald Barrie, Curtis Ferchoff,Quanxiang Li
Date : June 8, 2000
Copyright 2000 Department of Computer Science, University of Regina.
Index
Next
[CS Dept Home Page]