“The code does what it is instructed to do, not what you intend it to do”.
Every
programmer would have experienced that feeling of relief which usually occurs about 2:00
in the morning as you finally find and fix that one last bug that has been bothering
you for the past few days.
A bug
usually occurs because of the unhandled, not thought of scenarios, incorrect
design, incorrect understanding of design, bad coding practices, race
conditions and few more. It is not always easy to reproduce bugs as they are
triggered by inputs to the program which may be difficult for the programmer to
recreate. One cause of the Therac-25 radiation machine deaths was a bug, specifically
a race condition that occurred only when the machine operator very rapidly
entered a treatment plan. The bug wasn’t tracked in testing or when the
manufacturer attempted to duplicate it.
So we have to minimize the bugs to an extent that the
leftover bugs can never ever occur.
Journey towards
a bug free programming
Learn the
dirty details:
The very
minor details of how the hidden parts of your language, application and
technology work are very important. By scrutinized understanding of those, you can
avoid writing complicated code or could avoid the parts that have their own
bugs.
Visit all
the scenarios that a control can:
The
unhandled scenarios would strike in later after the deployment. Handle all the
scenarios that your code can go in. This is the effective step in isolating the
bugs.
Unbreakable Design:
Design your applications carefully by
writing down the relevant events and the way your code will respond to each
one. Give each event procedure and each general procedure a specific,
well-defined purpose.
Fix the underlying cause, not the
symptom:
When you fix a bug, make sure you are
really addressing the underlying cause or problem of the bug and not just the
symptom of the bug.
Counter Memory Leak:
Memory leak is a situation, where the memory
is allocated to the program which is not freed subsequently. This kind of
situation could lead to ever increasing usage of memory and at some point of time,
the program may have to come to an exceptional halt because of the lack of free
memory. Avoid memory leaks by releasing the memory accordingly.
Sending letters to a dead person:
The variables declared in a function
represent data which is limited to that function and will be stored in the
stack associated with that function. Avoid returning these references as they
would get destroyed when the control leaves that function.
No Vacuum Dereferencing:
Incorrect initialization and missing
the initialization of objects in different paths leads to the NULL reference
error. Whenever an object is being dereferenced, make sure that it has been
initialized in all possible paths to the point of dereferencing.
Wrong assumptions on operator
precedence:
This is a common error which is not
directly visible and the results are not as expected. This is more dependent on
the programmer’s logic in coding. Learn the precedence rules of the programming
language. Do not write big and complicated expression. The best and safe
programming is to use parenthesis to avoid the confusions.
Non-Zero based indexing:
The programmer by using Non-Zero based
indexing unknowingly modifies random memory which will cause memory corruption.
Buggy Redundant Work:
This a common form of bug where the
programmer tries to free the already freed resource or releasing (winding up
references) which are already released. This may be more erroneous, if we have
some allocation statements between the two memory released statements. There is
a chance that the first freed locations are now allocated to the new variable.
And the subsequent free will deallocate the new variable.
Correct usage of Aliases:
When there is unexpected aliasing
between parameters, return values, and global variables, errors may be
inevitable. Aliasing problems sometimes lead to deallocation errors. Static
analysis of all feasible paths in the program can detect possible aliases.
Guarding Synchronization Errors:
In multi-threaded programming when
there are multiple threads which are accessing resources, there is fair chance
of causing synchronizing problems. Deadlock detection and avoidance are the
possible strategies to be followed and extreme handling of race conditions has
to be followed.
No to Buffer overflow:
Buffer Overflow is an anomalous
situation where the data is written beyond what it is assigned. This has to be
avoided by conditional writing with available buffer.
Keep an eye on the Exceptions:
Exceptions handling is the heart of
handling bugs. There may be innumerable ways in which one’s code may fail. So,
it is the programmer’s job is to see all possible data paths in the program and
write the necessary exception handlers. When writing error handlers in the
code, analyze the state of the system during exceptional handling and see that
the system will be in allowed consistent state after the exception and handling.
Logical Breaks in Macro Programming:
Use typedef to define types not macros
when defining pointers to objects. Also use constants, inline functions instead
of macros which avoid logical errors. If you desperately want to use macros the
use, then use parenthesis extensively to avoid logical bugs.
Maintainable Code
Strive to write code that is
understandable by other programmers. Good code runs. Great code runs and is
also easily maintainable.
Validate Data:
Preconditions, post conditions ensure
that provided data and the state of the program as a whole is hygienic. Check arguments
to a function or method for validity before and executing the body of the
function. Do not trust the libraries you did not write and when you use them, check
the validity of the data.
See
the below example for how a bug can be introduced.
int
div(int *dividor)
{
int result = 5;
result=result/*divisor; /* Divide
and store it in result */;
return result;
}
We have to
give spaces around the operators to avoid bugs. In the above example /*divisor
is treated as a comment in priority and hence the result is a plain assignment.