|
|
| Good Programming Practices |
| I start my discussion about this topic by writing a program with two
different approachs. |
| First Approach(Bad Approach) |
Second Approach(Good Approach) |
| #include <iostream.h>
|
//Programmer: Mr. Ali |
| void main (void) |
//Dateof Creation:
22/06/2007 |
| { |
//Purpose: This program
adds the prices of three items
along with the sales tax applied on total amount |
| int b[3]={100,200,300}; |
#include <iostream.h> |
| int a; |
void main (void) |
| for(int i=0;i<3;i++) |
{ |
| a+=b[i]; |
const int SALE_TAX =15; |
| a+=a*15/100; |
int saleTax=0 |
| cout << a;
|
const int SIZE =3; |
| } |
int price[SIZE]={100,200,300};
// array initialization |
|
int totalAmount=0; |
|
for(int i=0;i< SIZE ;i++) |
|
totalAmount=totalAmount+price[i]; // adding prices of items |
|
saleTax=
totalAmount*SALE_TAX/100; // calculating sale Tax |
|
totalAmount
=totalAmount+saleTax; // adding sale Tex to
amount |
|
cout <<“The Amount Payable is= “ <<
totalAmount; |
|
} // end of main |
| Comparison between above
approaches |
| In First approach if somebody want to know what is the
purpose of this code. he/she has to read whole the code. where in second
approach he/she will read the the purpose at the top of the program. so
it will save the time of the reader. so it is good practice to write few lines
that will describe the program written below. |
| In First Approach. there is no indentation. so it difficult to read
the program. In Second approach the program is properly indented. so it is very
easy to read it. so it has very good readibility. |
| In First approach. the variables name are not more meaningful. but
in second approach they are more meaningful. so it has good readibility. |
| In First approach, the array is declared as int b[3]; suppose later
on we want to change the array size. we have to change in loop also. but
in second approach, for array index there is const int SIZE =3; now if we want
to change the array size later on. we have to chnage in one place. So in this
way we easily maintain our programs. So second approach saves our time. |
| In First approach there is no inline comments. but in second
approach there are inline comments. so it is easy to understand the program. so
easily maintainable. |
| In First approach, there are complex (more difficult to understand)
statement. but in second approach statements are easy. |
| In first approach, the amount (output) will display without any
message.but in second approach it will give proper message. |
|
| Questions that come to mind |
| Why do we use these good programming approach? |
| What kind of problems can occur without using these good programming approach? |
| Let start the SDLC(Software Development Life Cycle ) |
-
Requirements Specifications (Analysis)
-
Design
-
Implementation
-
Integration
-
Testing
-
Deployment (installment of the software on the client site.)
-
Maintenance Software maintenance phase involves changes to the software in
order to correct defects and deficiencies found during field usage as well as
addition of new functionality to improve the software’s usability and
applicability
|
| Note: To easy maintain your program you have to write your code with good
programming approach. |
| Maintainability |
-
Maintainability is the most desirable quality of a software artifact.
-
Good software ought to have code that is easy to maintain.
-
It is not important to write code that works, it is important to write code
that works and is easy to understand so that it can be maintained.
-
The three basic principles that guide maintainability are:
-
simplicity
-
clarity
-
generality or flexibility
Simplicity and clarity help in making the code easier to understand,Flexibility
facilitates easy enhancement of the software.
|
| Self Documenting Code |
-
Self-documenting code is that code which explains itself without the need of
comments and extraneous documentation, like flowcharts, UML diagrams,
process-flow state diagrams, etc.
-
The question is: how can we write code that is self-documenting?
-
There are a number of attributes that contributes towards making the program
self documented.
-
These include:
-
The size of each function
-
Choice of variables and other identifier names
-
Style of writing expressions
-
Structure of programming statements Comments
-
Modularity and issues relating to performance and portability.
|
| Function Size |
-
The size of individual functions plays a significant role in making the program
easy or difficult to understand.
-
In general, as the function becomes longer in size, it becomes more difficult
to understand.
-
A function should not be larger than 20 lines of code and in any case should
not exceed one page in length.
|
| Modularity |
-
Abstraction and Encapsulation are two important tools that can help in managing
and mastering the complexity of a program.
-
Modularity is a tool that can help us in reducing the size of individual
functions, making them more readable.
-
As an example, consider the following selection sort function:
|
| Example Selection sort without Modularity |
void selectionSort(int a[], int size)
{
int i, j, temp, min;
for (i = 0; i < size-1; i++)
{
min = i;
for (j = i+1; j < size; j++)
{
if (a[j] < a[min])
min = j;
} // end of inner for loop
temp = a[i];
a[i] = a[min];
a[min] = temp;
}// end of outer for loop
}// end of selectionsort function |
| Example Selection sort with Modularity |
|
int minimum(int a[], int from, int to)
{
int i, min;
min = a[from];
for (i = from; i <= to; i++)
{
if (a[i] < a[min])
min = i;
}// end of for loop
return min; // end of minimum function
}
void swap(int &x, int &y)
{
int temp;
temp = x;
x = y;
y = temp;
}
void selectionSort(int a[], int size)
{
int min, i;
for (i = 0; i < size; i++)
{
min = minimum(a, i, size –1); // function call
swap(a[i], a[min]); // function call
}
}
|
-
Reusability is one of the prime reasons to make functions but is not the only
reason.
-
Modularity is of equal concern (if not more) and a function should be broken
into smaller pieces, even if those pieces are not reused.
|
| Identifier Names |
| Identifier names also play a significant role in enhancing the readability of a
program. |
| Example |
-
if (x==0) // this is the case when we are allocating a new number In this
particular case, the meanings of the condition in the if-statement are not
clear and we had to write a comment to explain it. This can be improved if
instead of using x, we use a more meaningful name. Our new code becomes:
-
if (AllocFlag == 0) The situation has improved a little bit but the
semantics of the condition are still not very clear, as the meaning of 0 is not
very clear. Now consider the following statement:
-
If (AllocFlag == NEW_NUMBER) We have improved the quality of the code by
replacing the number 0 with a named constant NEW_NUMBER. Now, the semantics are
clear and do not need any extra comments, hence this piece of code is
self-documenting.
|
| Coding Style Guide |
-
Consistency plays a very important role in making code self-documenting. A
consistently written code is easier to understand and follow.
-
A coding style guide is aimed at improving the coding process and to
implement the concept of standardized and relatively uniform code throughout
the application or project.
-
As a number of programmers participate in developing a large piece of code, it
is important that a consistent style is adopted and used by all. Therefore,
each organization should develop a style guide to be adopted by its entire
team.
|
| Naming Conventions |
-
Charles Simonyi of Microsoft first discussed the Hungarian Notation. It is a
variable naming convention that includes information about the variable in its
name (such as data type, whether it is a reference variable or a constant
variable, etc). int ilength, float fprice
-
Every company and programmer seems to have his or her own flavor of
Hungarian Notation.
-
The advantage of Hungarian notation is that just by looking at the
variable name, one gets all the information needed about that variable.
|
| General Naming Conventions |
-
Names representing types must be nouns and written in mixed case starting with
upper case. Circle, FilePrefix
-
Variable names must be in mixed case starting with lower case. circle,
filePrefix
-
This makes variables easy to distinguish from types, and effectively resolves
potential naming collision as in the declaration Circle circle;
-
Names representing constants must be all uppercase using underscore to separate
words. MAX_ITERATIONS, COLOR_RED In general, the use of such constants should
be minimized. In many cases implementing the value as a method is a better
choice. This form is both easier to read, and it ensures a uniform interface
towards class values.
int getMaxIterations()// NOT: MAX_ITERATIONS =
25
{
return 25;
}
-
Names representing methods and functions should be verbs and written in mixed
case starting with lower case. getName(), computeTotalWidth()
-
Names representing template types in C++ should be a single uppercase letter.
template < class T > ...
template < class C,class D>.......
-
Private class variables should have _ suffix.
class SomeClass
{
private int length_;
...
}
Apart from its name and its type, the scope of a variable is its most important
feature. Indicating class scope by using _ makes it easy to distinguish class
variables from local scratch variables
-
Abbreviations should not be uppercase when used as name. exportHtmlSource(); //
NOT: exportHTMLSource(); openDvdPlayer(); // NOT: openDVDPlayer();
-
Variables with a large scope should have long names; variables with a small
scope can have short names. Scratch variables used for temporary storage or
indices are best kept short. A programmer reading such variables should be able
to assume that its value is not used outside a few lines of code. Common
scratch variables for integers are i, j, k, m, n and for characters c and d.
-
The name of the object is implicit, and should be avoided in a method name.
line.getLength(); // NOT: line.getLineLength();
The latter seems natural in the class declaration, but proves superfluous in
use.
-
The terms get/set must be used where an attribute is accessed directly.
employee.getName();
matrix.getElement (2, 4);
employee.setName (name);
matrix.setElement (2, 4, value);
-
is prefix should be used for boolean variables and methods.
isSet, isVisible, isFinished, isFound, isOpen
Using the is prefix solves a common problem of choosing bad Boolean names like
status or flag. isStatus or isFlag simply
doesn't fit, and the programmer is forced to chose more meaningful names. There
are a few alternatives to the is prefix that fits better in some situations.
These are has, can and should prefixes:
boolean hasLicense();
boolean canEvaluate();
boolean shouldAbort = false;
-
The term compute can be used in methods where something is computed.
valueSet.computeAverage();
matrix.computeInverse()
Using this term will give the reader immediate clue that this is a potential
time consuming operation
-
The term find can be used in methods where something is looked up.
vertex.findNearestVertex();
matrix.findMinElement();
This tells the reader that this is a simple look up method with a minimum of
computations involved.
-
The term initialize can be used where an object or a concept is established.
printer.initializeFontSet();
-
List suffix can be used on names representing a list of objects.
vertex (one vertex), vertexList (a list ofvertices)
A list in this context is the compound data type that can be traversed
backwards, forwards, etc. (typically a Vector).
-
n prefix should be used for variables representing a number of objects.
nPoints, nLines
The notation is taken from mathematics where it is an established convention
for indicating a number of objects.
-
Iterator variables should be called i, j, k etc .
while(Iterator i = pointList.iterator();i.hasNext();)
{
}
for (int i = 0; i < nTables; i++)
{
}
The notation is taken from mathematics where it is an established
convention for indicating iterators. Variables named j, k etc. should be used
for nested loops only.
-
Complement names must be used for complement entities. get/set,
add/remove, create/destroy, start/stop, insert/delete, increment/decrement,
old/new, begin/end, first/last, up/down, min/max, Reduce complexity by
symmetry.
-
Abbreviations in names should be avoided.
computeAverage(); // NOT: compAvg();
There are two types of words to consider. First are the common words
listed in a language dictionary, these must never be abbreviated. Never write:
cmd instead of command
cp instead of copy
pt instead of point
comp instead of compute
init instead of initialize
etc. Then there are domain specific phrases that are more naturally known
through their acronym or abbreviations. These phrases should be kept
abbreviated. Never write:
HypertextMarkupLanguage instead of html
CentralProcessingUnit instead of cpu
PriceEarningRatio instead of pe
-
Negated Boolean variable names must be avoided.
boolean isError; // NOT: isNotError
boolean isFound; // NOT: isNotFound
The problem arise when the logical NOT
operator is used and double negative arises. It is not immediately apparent
what !isNotError means.
|
| Variables |
-
Variables should be initialized where they are declared and they should be
declared in the smallest scope possible.
-
Variables must never have dual meaning. This enhances readability by ensuring
all concepts are represented uniquely. Reduce the chance of error by side
effects.
-
Variables should be kept alive for as short a time as possible. Keeping the
operations on a variable within a small scope, it is easier to control the
effects and side effects of the variable.
-
Class variables should never be declared public. The concept of information
hiding and encapsulation is violated by public variables. Use private variables
and access functions instead. One exception to this rule is when the class is
essentially a data structure, with no behavior (equivalent to a C++ struct). In
this case it is appropriate to make the class’ instance variables public.
-
Related variables of the same type can be declared in a common statement.
Unrelated variables should not be declared in the same statement.
float x, y, z;
float revenueJanuary,revenueFebrury,revenueMarch;
The common requirement of having declarations on separate lines is not useful
in the situations like the ones above. It enhances readability to group
variables.
-
Global variables should not be used. Variables should be declared only
within the scope of their use. The same is recommended for global functions or
file scope variables. It is easier to control the effects and side effects of
the variables if used in limited scope.
-
Implicit test for 0 should not be used other than for Boolean variables and
pointers.
if (nLines != 0) // NOT: if (nLines)
if (value != 0.0) // NOT: if (value)
It is not necessarily defined by the compiler that ints and floats 0 are
implemented as binary 0. Also, by using explicit test the statement give
immediate clue of the type being tested. It is common also to suggest that
pointers shouldn't test implicit for 0 either,
i.e. if (line == 0) instead of if (line).
The latter is regarded as such a common practice in C/C++ however that it can
be used.
|
| Loop Structures |
|
Only loop control statements must be included in the for() construction. int sum
= 0;
for (i=0; i<100; i++)
sum = sum+value[i];
// NOT: // for (i=0, sum=0; i<100; i++)
// sum +=value[i];
Loop variables should be initialized immediately before the loop.
boolean done = false;
while (!done)
{
..
}
// NOT: // boolean done =false;
//....... . //more statements are initilization of the variables
while (!done)
{
//....
}
-
The use of break and continue in loops should
be avoided. These statements should only be used if they prove to give higher
readability than their structured counterparts. In general break
should only be used in case statements and continue
should be avoided altogether
|
| Conditionals |
|
Complex conditional expressions must be avoided. Introduce temporary Boolean
variables instead.
if((elementNo < 0) || (elementNo > maxElement)|| elementNo ==
lastElement)
{
:
}
The above statement should be replaced by:
boolean isFinished = (elementNo < 0) || (elementNo > maxElement);<
/STRONG >
boolean isRepeatedEntry = elementNo = = lastElement;
if (isFinished || isRepeatedEntry)
{
:
}
-
The nominal case should be put in the if-part and the exception in the else-
part of an if statement.
boolean isError = readFile (fileName);
if (!isError)
{
:
}
else
{
:
}
The conditional should be put on a separate line.
if (isDone) // NOT: if (isDone) doCleanup();
doCleanup();
-
Executable statements in conditionals must be avoided.
file = openFile (fileName, "w");
if (file != null)
{
:
}
// NOT: // if((file = openFile (fileName, "w")) != null)
{
//
:
//
}
|
| Short circuiting || and & |
|
Short-circuiting is a very useful tool. It can be used where one Boolean
expression can be placed first to “guard” a potentially unsafe operation in a
second Boolean expression. Also, time is saved in evaluation of complex
expressions using operators || and &&. However, a number of issues
arise if proper attention is not paid. Let us look at the following code
segment taken from commercially developed software for a large international
bank:
struct Node
{
int data;
Node * next;
};
Node *ptr;
...
while (ptr->data < myData && ptr != NULL)
{
// do something here
}
What is wrong with this code?
The second part of condition, ptr != NULL, is supposed to be the guard. That is,
if the value of the pointer is NULL, then the control should not enter the body
of the while loop otherwise, it should check whether ptr->data < myData
or not and then proceed accordingly. When the guard is misplaced, if the
pointer is NULL then the program will crash because it is illegal to access a
component of a non-existent object. This code is rewritten as follows. This
time the short-circuiting helps in achieving the desired objective, which would
have been a little difficult to code without such help.
while (ptr != NULL && ptr->data< myData)
{
// do something here
}
|
| Operand Evaluation Order and Side Effects |
|
A side effect of a function occurs when the function, besides returning a value,
changes either one of its parameters or a variable declared outside the
function that is accessible to it. That is, a side effect is caused by an
operation that may return an explicit result but it may also modify the values
stored in other data objects. Side effects are a major source of programming
errors and they make things difficult during maintenance or debugging
activities. Many languages do not specify the function evaluation order in a
single statement. This combined with side effects causes major problems. As an
example, consider the following statement:
c = f1(a) + f2(b);
Which function (f1 or f2) will be evaluated first, as the C/C++ language does
not specify the evaluation order and the implementer (compiler writer) is free
to choose one order or the other? The question is: does it matter?
To understand this, let’s look at the definition of f1 and f2:
int f1(int &x)
{
x = x * 2;
return x + 1;
}
int f2(int &y)
{
y = y / 2;
return x – 1;
}
In this case both f1 and f2 have side effects as they both are doing two things
- changing the value of the parameter and changing the value at the caller
side. Now if we have the following code segment:
a = 3;
b = 4;
c = f1(a) + f2(b);
Then the value of a, b, and c will be:
a = 6
b = 2
c = 8
So far there doesn’t seem to be any problem. But let us now consider the
following statement:
c = f1(a) + f2(a);
What will be the value of a and c after this statement? If f1 is evaluated
before f2, then we have the following values:
a = 3
b = 9 // 7 + 2
On the other hand, if f2 is evaluated before f2 then, we get totally different
results:
a = 2
b = 3 // 3 + 0
|
| Common Mistakes |
|
Following is the short list of common mistakes made due to side effects:
1. array[i++] = i; If i is initially 3, array[3] might be set to 3 or 4.
2. array[i++] = array[i++] = x; Due to side effects, multiple assignments
become very dangerous. In this example, a whole lot depends upon when i is
incremented.
3. “,” is very dangerous as it causes side effects. Let’s look at the following
statement:
int i, j = 0;
Because of the syntax, many people would assume that i is also being
initialized to 0, while it is not. Combination of , and = -- is fatal. Look at
the following statement:
int a = b, c = 0;
A majority of the programmers would assume that all a, b, and c are being
initialized to 0 while only c is initialized and a and b have garbage values in
them. This kind of negligence causes major programming errors that are not
caught easily and are caused only because there are side effects.
|
|
|