QC 8: Memory Allocation & Deallocation

In any system, memory buffers are allocated and de-allocated. However, the former happens regularly, but the latter gets missed out leading to memory leaks, corruptions, crashes and more similar disasters. There are enough tools around to catch these leaks and can be employed to detect the same. Rational Purify, Valgrind are some names that come up quite often and are used extensively across the globe.

However, as a pride-filled-programmer one would like to ensure that any programs or modules written by them don’t suffer from these trivial issues. This requires that every buffer/pointer allocated is meticulously tracked and accounted for i.e. each allocated pointer is tracked and ensured that is freed at the end of the program. Hence, a few habits/programming practices are described below which describe some simple and effective programming practices that can implemented in day-t0-day development with highly effective results.

Habit 1: For every *malloc*, write a *free*

Whenever a module is being implemented, one should make a habit of coupling *malloc* with a corresponding *free*. Consider a simple test program being written to validate an underlying algorithm. In this test program, we need to allocate 3 buffers to pass to the algorithm, which are allocated using the malloc method. It is a very good practice to implement their corresponding free immediately and insert all the requisite code in between.

.............
pPtr1 = malloc();
pPtr2 = malloc();
pPtr3 = malloc();
............
....
invoke_algorithm(pPtr1, pPtr2, pPtr3);
....
............
free(pPtr3);
free(pPtr2);
free(pPtr1);

This is a simple program where the pointer is allocated, used and de-allocated at the same software abstraction layer. However, typically embedded or software applications are not this simple or straight-forward.

Habit 2: Never Free a pointer that you *don’t* own

In a multi-layered hierarchical software, pointers are allocated at one place and pass downstream to other modules. The beauty of pointers is that their content, context, intent and scope is available to every function that receives them. Hence, when a pointer is no longer useful, it can easily be *de-allocated* or *freed* from any point. An example illustration is shown below in terms of 2 functions func1 and func2 where func1 allocates a pointer and passes it to func2 which frees the same.

void func1()
{
      // Variables
      int    *pPtr1;
      // Allocate Memory for pPtr1;
      pPtr1 = (int *) malloc(sizeof(int));
      .........................
      ........
      func2(pPtr1);
      ........
      .........................
      // De-allocate the memory buffer
      free(pPtr1);
}

void func2(int *pInpPtr)
{
      .....................
      ........
      // Uses the pointer pInpPtr
      ........
      .....................
      // Frees the pointer - CREATION OF BUG !!!
      free(pInpPtr);
}

In this example, func1 assumes that func2 hasn’t modified the pointer and goes ahead and tries to free up the same. However, as the pointer is already freed, a crash is the least one can expect in this scenario. Good programming practices advocate that all pointers are allocated and correspondingly freed at the same software abstraction level. In case of an algorithm or module, it would be best if all pointers are handled at one level. However, the least one can do is that allocate and free pointers at the same software abstraction level. In short, allocate and free pointers at the same software abstraction level.

Implementing the advocated principle, the example would be rewritten as below

void func1()
{
      // Variables
      int    *pPtr1;
      // Allocate Memory for pPtr1;
      pPtr1 = (int *) malloc(sizeof(int));
      .........................
      ........
      func2(pPtr1);
      ........
      .........................
      // De-allocate the memory buffer
      free(pPtr1);
}

void func2(int *pInpPtr)
{
      .....................
      ........
      // Uses the pointer pInpPtr
      ........
      .....................
}

Habit 3: Beware of pointer copies

Pointers provide a lot of flexibility in terms of context and information traversal. Pointers can be copied and be used in the same vein as the originally allocated ones.  System doesn’t associate a specific variable to a pointer entity i.e. pointer is a memory address and any number of variables i.e. pointer variables can be pointing to the same address. When multiple copies are made, care should be exercised to ensure that spurious free are avoided.

Consider the example below where func1 passes a pointer to func2, which makes a copy of the same and then frees the same. This has the same effect as the one described in Habit 2 above.

void func1()
{
      // Variables
      int    *pPtr1;
      // Allocate Memory for pPtr1;
      pPtr1 = (int *) malloc(sizeof(int));
      .........................
      ........
      func2(pPtr1);
      ........
      .........................
      // De-allocate the memory buffer
      free(pPtr1);
}

void func2(int *pInpPtr)
{
      int    *pLocalPtr = pInpPtr;
      .....................
      ........
      // Uses the pointer pLocalPtr
      ........
      .................
      // Frees the pointer
      // Though the original pointer is not employed,
      // the effect is same - CREATION OF BUG !!!
      free(pLocalPtr);
}

Pointers are extremely powerful, but require a lot of care and caution while being effectively employed.

Habit 4: Never forget to initialize a freed pointer to NULL

Last but definitely not the least, once a pointer has been freed, the variable should be set to NULL. This is to avoid spurious accesses or free. This is a stylistic statement and a recommended practice. Neither the program’s performance improves nor does it decrease. However, it does help to improve the quality of software being developed drastically.

In general, pointers are like Super-Powers quoted in Spider-Man movie – “With greater powers come greater responsibilities…” One needs to shoulder the same responsibly

QC 7: Null Pointers

In the preceding posts, I talked about the different principles or building blocks of good programming practices. Some examples augmented the advocated principles and was intended to provide an overview. However, there are some common, well-known principles that are acknowledged, but often ignored. Some principles that are applicable in day-to-day coding are captured in this post and I intend to add more of the same in times to come.

Conditional Statement

If a variable is being compared with a Macro, typical tendency is write the program in such a manner that the variable is on the LHS of the operand and the Macro on the RHS. Consider the following NULL check statement for a pointer pTestPtr below

if(pTestPtr == NULL) /* Checking pTestPtr for NULL */

If a “=” operator is missed out, it can have disastrous consequences as the pointer is initialized to NULL. Moreover, code review may not immediately catch it, as human tendency is to assume the most common operators and hence, first sight will not reveal the issue. Continuous Debugging is the only way to localize the issue and fix it. If this scenario is encountered on an embedded platform running a rich system like Android or Linux, it takes some debugging to zero-in onto the issue.

To overcome such scenarios, a simple programming technique can be employed. Whenever a variable is being tested against a macro, it should be made a habit to place the Macro on the LHS and variable on RHS. This way, even if a “=” operator is missed out, the issue is caught immediately by the compiler. The aforementioned example can be re-written as below

if(NULL == pTestPtr) /* Checking pTestPtr for NULL */

Function Arguments

In any programming paradigm, algorithm is broken down into modules and implemented as functions. Each function has its own share of input and output arguments. It is extremely critical to ensure that the arguments to a function are valid and if not, an error is raised immediately.

One of the most issues faced in debugging is NULL Pointer problem. If a function has some arguments which are pointers, there is a potential bug/crash lurking around if they aren’t tested for NULL pointers. It is imperative to ensure that any pointer arguments to any function are tested for pointers. Consider an example function with 3 pointer arguments out of which 2 signify input array pointers and 1 the output one.

void func_02(int *pInpPtr0, int *pInpPtr1,int *pOutPtr0)

To ensure that these pointers are tested for NULL check, an individual check can included for each of the pointer arguments. However, Pointer NULL Check being a most common feature of any software program, the same can be implemented as a macro, such that the same conceptual template can be employed by any function in the entire software tree. Designing a pointer NULL Check macro, the body of the function would look like

#define ASSERT_IF_NULL(x)    assert(NULL != (x)) /* ASSERT IF ARGUMENT IS NULL */
void func_02(int *pInpPtr0, int *pInpPtr1,int *pOutPtr0)
{
    ASSERT_IF_NULL(pInpPtr0); // NULL Check for pInpPtr0
    ASSERT_IF_NULL(pInpPtr1); // NULL Check for pInpPtr1
    ASSERT_IF_NULL(pOutPtr0); // NULL Check for pOutPtr0
}

Implementing a NULL check improves the quality of software incredibly and helps to achieve better stability of the code. However, in any Embedded System, performance is of utmost importance and hence, any optimization in terms of conditionals is always welcome. Hence, in an end-product, where the system is *assumed* to be stable and more importantly consistent, it is a common practice to do away with NULL Checks. The key word in the previous statement is assumed, which typically FAILS!!. Lack of testing or corner cases throw up some nasty surprises, especially during customer productization cycles.

The challenge thus becomes to have a generic piece of code that can be used for both debug/stability testing as well as being optimized. This is usually achieved by having a compile-time switch to enable multiple implementations based on some compilation flags. Considering that *NULL_CHECK_OPTIMIZATION* is a flag enabled during productization, the aforementioned implementation could be rewritten as

#if !defined(NULL_CHECK_OPTIMIZED)
/* Case when NULL CHECK is NOT Optimized */
#define ASSERT_IF_NULL(x)    assert(NULL != (x))
#else
/* Case when NULL Check is optimized */
#define ASSERT_IF_NULL(x)
#endif

void func_02(int *pInpPtr0, int *pInpPtr1,int *pOutPtr0)
{
    ASSERT_IF_NULL(pInpPtr0); //NULL check for pInpPtr0
    ASSERT_IF_NULL(pInpPtr1); //NULL check for pInpPtr1
    ASSERT_IF_NULL(pOutPtr0); //NULL check for pOutPtr0
}

To debug the issue, a new build without the NULL_CHECK_OPTIMIZATION flag is tested and voila!! any pointer issues will be caught immediately. A little bit of thought and design can save a lot of headache in the long run.

QC 6: Interfaces, Components & API

API stands for Application Programmer Interface or a similar definition as the case may be. In pure english terms, it could be described as the gateway through which another entity (program/programmer) interacts with the entity that the API embodies/personifies. Though the above definition is a round-about description, I did intend to avoid *interface* in general due to the reasons described below.

Interface is a commonly used terminology in Software Development and typically signifies the window through which 2 entities talk. It is also commonly referred to as the common language used by 2 software modules to interact. However, interface has a very specific definition from a software design perspective. Design patterns deal extensively with the definition of terms like interface, component, framework, toolkit etc. I would definitely recommend some amount of study on these terminologies to gain a better understanding.

Borrowing the definitions from these gyan gurus, I would like to term interface as a set/collection of functions/methods in it’s most elementary form. These methods are *defined* by an interface and *implemented* by a component.  According to me, this is a very critical distinction that needs to be well understood and imbibed. Definition of an entity only indicates to the defining the template of the entity. Hence, when we say interface defines the methods, we mean that interface only provides the function definitions/templates (not to be confused with C++ templates).

The question is who will provide the definitions of these functions. This is where the concept of a component-interface-model comes in. As noted earlier, interfaces defines a set of functions. If a component say C implements an interface, then the component provides the definition for that function. Hence, it is imperative that any component can choose to implement any interface or any number of interfaces in its own unique way.

Now, in a large software system, there will be multitude of components, each of them implementing the same/similar interface.  Hence, when more than one component provides its own unique implementation of the interface, we can note that an interface has multiple implementations which is specific to the component implementing the same.

In a nutshell, one interface can be implemented by many components. A component can implement any number of interfaces.

This concept is explained through a series of code snippets below:

An example illustration of an interface called IAlgorithm interface is as below. This interface defines the standard set of functions to be supported by any component that implements an algorithm.

Class IAlgorithm
{
     virtual void Create(void **) {} = 0;
     virtual void Init(void *, void *) {} = 0;
     virtual void Run(void *, void *) {} = 0;
     virtual void Delete(void *) {} = 0;
}

Another interface called IDebugStats is defined below. This interfaces defines a set of methods that are employed to dump statistics from any component for offline analysis.

Class IDebugStats
{
      virtual void DumpStats(char *) {} = 0;
}

Consider 3 components Component1, Component2, Component 3 which are defined as below. Component 1 only implements IAlgorithm where as both Components 2 and 3 implement IAlgorithm and IDebugStats as shown.

Component1: public IAlgorithm

Class Component1 :: public IAlgorithm
{
      void Create(void **handle)
      {
            cComponent *newComp = new cComponent;
            *handle = newComp;
            return;
      }

      void Init(void *handle, void *init_stats)
      {
            cComponent *pHdl = (cComponent *)(handle);
            return pHdl->init_method(init_stats);
      }

      void Run(void *handle, void *run_params)
      {
            cComponent *pHdl = (cComponent *)(handle);
            return pHdl->run_method(run_params);
      }

      void Delete(void *handle)
      {
            cComponent *pHdl = (cComponent *)(handle);
            if(pHdl->delete_method())
            {
                  *handle = NULL;
                  return;
            }
      }
}

Component2: public IAlgorithm, public IDebugStats

Class Component2 :: public IAlgorithm, public IDebugStats
{
      void Create(void **handle)
      {
            instance_t *newComp = instance_t.createComponent();
 *handle = newComp;
            return;
      }

      void Init(void *handle, void *init_stats)
      {
            instance_t *pHdl = (instance_t *)(handle);
            return instance_t->initialize(init_stats);
      }

      void Run(void *handle, void *run_params)
      {
            instance_t *pHdl = (instance_t *)(handle);
            return instance_t->execute(run_params);
      }

      void Delete(void *handle)
      {
            instance_t *pHdl = (instance_t *)(handle);
            if(pHdl->delete_instance())
            {
                  *handle = NULL;
                  return;
            }
      }
      
 char MODULE_STRING[] = "Component2";
      void DumpStats(char *str)
      {
          printf("[%s]: %s", MODULE_STRING, str);
      }
}

Component3: public IAlgorithm, public IDebugStats

Class Component3 :: public IAlgorithm, public IDebugStats
{
      void Create(void **handle)
      {
            instance_t *newComp = instance_t.createComponent();
 *handle = newComp;
            return;
      }

      void Init(void *handle, void *init_stats)
      {
            instance_t *pHdl = (instance_t *)(handle);
            return instance_t->initialize(init_stats);
      }

      void Run(void *handle, void *run_params)
      {
            instance_t *pHdl = (instance_t *)(handle);
            return instance_t->execute(run_params);
      }

      void Delete(void *handle)
      {
            instance_t *pHdl = (instance_t *)(handle);
            if(pHdl->delete_instance())
            {
                  *handle = NULL;
                  return;
            }
      }
      
 char MODULE_STRING[] = "Component2";
      void DumpStats(char *str)
      {
          FILE *f_hdl = fopen("/tmp/debugStats.log", "a");
          fprintf(f_hdl, "[%s]: %s", MODULE_STRING, str);
          fclose(f_hdl);
      }
}

From these code snippets, it is very evident that each component chooses to implement each of the interfaces (same definition) in its unique way. Similarly, each interface (whether IAlgorithm or IDebugStats) has its own unique implementation provided by the individual components.

Application Programmer Interface

API are the gateway through which any software module/program interacts with any other software module/program. API doesn’t preclude that only Software entities can be modelled through API. If there is a HW block which is programmed through Registers and the state machine is controlled by updating the same, then these set of registers combined with the values that they can take are considered to be the API for the HW block. In general, is an interface for anyone to talk to anyone else.

In Software programming, design of a very good and stable API is of utmost importance. APIs should be designed with thorough investigation and enough thought process being put in.  APIs should *not* keep evolving throughout the development cycle i.e. APIs once frozen are adhered to throughout the lifecycle of the product. A typical checklist of Do’s and Don’ts with API is enlisted below:

  • Do Design APIs at the starting of the project; Once frozen, Don’t change the existing APIs at any cost
  • API’s should be designed in a modular fashion i.e. abstract functionality and intent
  • Don’t overdo the design i.e. if a module has 37 parameters exposed, don’t expose 37 functions for anyone to set and another 37 to read i.e. Minimal number of functions providing complete access to the module.
  • Do keep your API’s always backward compatible; Don’t break backward compatibility at any cost
  • Do communicate the new functions introduced very clearly through documentation
  • Preferably, do maintain a version number for API. This helps in maintaining backward compatibility and provides a flexibility to introduce new functions.
  • Last but definitely not the least, Keep It Simple, Silly/Stupid

In general, a little bit of diligence, applied thought process and good design helps to design a good API, which then can facilitate easier integration with no/minimal pain points. Simple and straight APIs go a long way to generate high-quality software. It won’t be understatement to say that the quality of the software can be gauged by looking at the API of the module.

QC 5: Review & Rework or Repent…

A software product is conceptualized at requirements stage, embodied in design stage and realized in development stage. During this critical stage of development, the typical work breakdown would be Coding, Unit Testing, Review and Rework. Hence, this would be a template for developing individual modules prior to their integration into the next higher level abstraction layer.

Review and Rework go hand-in-hand with Unit Testing as will be evident from the post below. Review is the process by which the developed piece of software is subjected to 360 degree scrutiny. This scrutiny is to verify if the code under investigation actually adheres to the norms decided and agreed upon during the starting phase of the project. Basically, Review provides a milestone, crossing which one can assume that the software product adheres to the group-level and module design-level objectives. However, review is not a substitute for verification and validation and both have their own mutually exclusive value-add. Review shouldn’t be employed as a substitute for any other step in Software Development.

During the review process, software code is shared along with supporting documents to a review panel. The panel is usually decided at the group level, but can ideally be left to the author and group leaders. Selection of a review panel should be intelligent and augur well for the quality of the product as well as the short and long-term goals of the project. A good review panel will consist of a judicious mix of Seniors, Peers and Juniors as compared to the author of the code. Skewing on only direction breaks the equilibrium and as with any other system, it does more damage than benefit.

There is a specific reasoning behind constituents of the review panel. Seniors provide a different perspective as compared to the authors. Having been part of the industry longer, they will have some tips and tricks of the trade up their sleeve, which is handed down during these review processes. From a knowledge perspective, peers contribute and get contributed into. The knowledge gained during one review process is immediately catered to in their own module development. This way, there is a continuous, incremental and positive change from a software quality perspective. Juniors in the team should be made part of the team due to numerous reasons. One, they feel wanted and have a good feeling that they are contributing into the project. Being part of the review discussions, a large amount of knowledge and true-world wisdom would be discussed, some of which will be absorbed by the juniors.

Review process follows a pattern which is more or less the same across projects and organizations. The typical review process is described using the flow path below:

(1) Coding has been completed; Module has been Unit Tested
   
(2) Review Panel Decided; 
    Initial Review Meeting is held where code under review is described

(3) Review Panel Reviews the code and comes back with Review comments

(4) Review Team meets and discusses the comments

(5) Once comments have been accepted, Rework and Unit-Test is performed

(6) Testing complete mandates another round of quick review and verification

(7) If Review Panel is satisfied about all the changes, process is closed

During the review process, aspects related to code documentation, coding guidelines are typically discussed. However, review also provides an excellent platform for discussion on optimization aspects of the module. Better ways to realize the software modules’ objectives while optimizing the processing/memory needs of the system is captured during this process. This provides a very valuable and priceless insight into the system for all the stakeholders involved, especially the juniors on the team.

Rework is typically based on the review comments. Some of the rework may involve only aesthetic/cosmetic comments which should be straight-forward. However, any module level optimization comments tend to involve a higher rate of change and can easily run into days. Any change warrants another round of unit-level verification and validation. This should be understood and imbibed by one and level and ideally, software developers should be very adamant on the same.

Unit-testing has immense value, some of which were discussed in earlier blog-posts. However, one critical point to be brought up in this scenario is the need for testing. I will quote one of my trainers (Mr. Ashok) from 6-7 years ago. Testing is not to prove that your stuff works. Testing is to disapprove that your stuff doesn’t work.  This is a whole different dimension as compared to the typical norm and should always be remembered as a mantra.

In today’s fast paced world, patience is a virtue that is fast dwindling and people live in the fast-forward age. Review as a step is prone to being skipped as an unnecessary evil. This is an extremely bad programming practice which developers don’t appreciate and understand. Confidence is always a nice attribute to have. However, verification by an impartial 3rd party only cements one’s own value system. The confidence should be towards having a fewer number of comments in scrutiny rather than not scrutinizing at all. This is one of the most commonly observed mis-placed practices, which should be abolished in the bud. Skipping review step should be at one’s own peril and in any quality oriented organization, this is never the case.

QC 4: Coding Guidelines

Coding Guidelines is a very touchy issue which is highly individualistic and organization specific. Every organization have their own standards and an apt of application of the same results in good quality code. Coding guidelines captures a lot of gamut, each of which can be debated at length as a separate post. However, imbibing some of the most common rules would require little effort, but go a long way in making source code, true masterpieces.

Before we look at the different coding guidelines, it is imperative to note that as with code documentation, sticking to guidelines is a habit which should be inculcated at a very young age. Habits die-hard and coding guidelines is definitely a habit which one should treasure for as long as they program. The true test of an individual is if the habit translates into guidelines being followed even when a very simple and straightforward piece of code needs to be written. If an individual can write a simple test code (like a Hello World program) adhering to the guidelines, one can be rest assured that they have arrived!!

Styles

Different companies have different styles of coding convention. Hungarian notation, Leszynski notation (variation of Hungarian), Upper Camel Case (a.k.a. Pascal Style) are some of the commonly used coding conventions. These conventions mandate the naming of variables, functions etc in a specific format. Few examples of naming a variable in Hungarian notation is as below

int              smallIntegerValue;
char             myLongNameArray[256];
struct           newStructObj;

Variable & Function Naming Conventions

Implementing a style improves the aesthetics part of the code. Augmenting the same with a little bit of extra additions like formalizing the naming convention for variables and functions enhance the code readability by leaps and bounds. Lot of organizations believe in adding the type of variable (either local or static or constant or global) into the name. Similarly, based on the function’s scope, the name of function is suitably designed to reflect the same.

Few examples of local variable naming convention is as shown below

int        lSomeLocalVariable; //Example of a local integer variable
static int sStaticCount;       //Example of a static integer
const int  cArrayOfConstants;  //Example of an array of constants
int        gGlobalVariable;    //Example of a global variable

Function naming also follows similar naming patterns as shown in the examples below

static void sSomeExampleFunction(); //Example of a static function
extern int  gGlobalScopeFunction(); //Example of a global function

From the aforementioned examples, it is apparent that the following simple rules improves the quality of software being developed immensely. However, this is just the guideline, but without following the next aspect of naming convention, it is useless

Design of Meaningful Names

Following the naming convention is one part of good programming practices. However, without proper names, it is rendered absolutely useless. When variables or functions are named, their name should reflect something about them. Generic names should be avoided at all costs as they don’t convey any useful information i.e. they are useless. Names should be designed in such a manner that they convey information about the variable like it’s scope, the necessity of the same or rather what does it represent.

Consider the following example where one needs to traverse a 2D array to calculate the sum of all elements of an array. More often than not, the following code is implemented.

for(i = 0; i < N; i++)
{
    for(j = 0; j < M; j++)
    {
         lSum += gArray[i][j];
    }
}

A simple interchange of i and j can have disastrous results from a performance perspective. Though one might be acclimatized to such coding practices, it is not a nice way of programming. An alternative implementation would be as below

for(lColCtr = 0; lColCtr < N; lColCtr++)
{
     for(lRowCtr = 0; lRowCtr < M; lRowCtr++)
     {
          lSum += gArray[lColCtr][lRowCtr];
     }
}

Just naming the variables in a more appropriate manner improves the readability and the overall quality of software code by leaps and bounds. This habit of naming the variables in a more appropriate manner should be inculcated from start as a good programming practice.

Tip: i, j and k have a very special place in programming history. More often than not, these are used as array iterators. It requires a lot of practice and hardwork to overcome them, but should be done so to become better programmers.

Alignment

Alignment though being an aesthetic part of programming is an extremely critical and value-adding feature of coding guidelines. Good aligned code is extremely easy to read and facilitates a logical separation of blocks. Alignment is again a matter of habit and practice and when perfected, can yield wonderful results.

To drive the point of alignment, consider the following non-aligned piece of code (without code documentation) too

int function(int **arr, int row, int height)
{
int i, j;
int sad=0;
for(i=0;i

If the same piece of code is aligned and documented properly, it will be a pleasure to read the masterpiece.

int function(int **pInputArr, int lRow, int lHeight)
{
    // Declare the variables
    int lRowCtr, lColCtr;
    int lSad = 0; // Initialize the SAD to Zero

    //Loop to calculate the SAD of the input arry
    for(lColCtr = 0; lColCtr < lHeight; lColCtr++)
    {
         for(lRowCtr = 0; lRowCtr < lRow; lRowCtr++)
         {
                lSad += pInputArr[lColCtr][lRowCtr];
         }
    }

    //Print out the generated SAD for debugging
    printf("[function] Generated Sad:%dn", lSad);

    // Return the SAD from the function
    return lSad;
}

Different organizations have a different policy regarding tabs. Some mandate a tab based aligned and some of them space based alignment. The definition of a tab in terms of number of spaces is also organization and OS specific. Typically, a tab is defined to be 4 spaces, but again is a policy implemented by a group.

Obfuscated Coding

Obfuscated Coding is a deliberate attempt at making machine code non-readable by humans. Obfuscate by definition means to darken or to confuse or to hide the intent. There is a specific need for the same at machine-code programming level. Interested programmers take part in the International Obfuscated C Coding Contest (IOCCC) competition.

However, some programmers implement straight forward modules in an obfuscated manner. This defeats the purpose of programming paradigm and creates more problems than any coding guideline aims to resolve. As a matter of general principle, one should always practice KISS principle i.e. Keep It Simple, Silly.  Unless one is working at machine-code programming level, any kind of obfuscated coding practices should be avoided.

Conclusion

Coding Guidelines can be enforced or imbibed. The former only forces programmers to practice the principles without any conviction. However, if the guidelines are imbibed and nurtured from a young age, programmers appreciate the value of good programming coding practices and software thus produced will typically be of superior quality. KISS principle is something that can be implemented at every programming step. Usually, the simplest form of code will be the most optimal and best code that can be generated. Or in others would typically be Quality Code.

QC 3: Code Documentation

 “A thing of beauty is joy forever

Beauty lies in the eyes of beholder

One can enlist a lot more phrases about beauty, which brings a joy to the heart. The beauty of programming is that it’s a blend of science and art and is definitely a canvas on which the programmer paints their masterpiece. Or an abstract, which in other terms is called disaster.

Code Documentation is one of the most critical aspects of programming and also one of the most often neglected one. The value of code documentation is understood only over a period of time, much like red-wine. Documentation is not only about the textual part of the code i.e. code comments, but is also about the presentation, alignment, structure and most importantly, not cluttering the code. One critical aspect is that code documentation is NOT a substitute for missing out on the Design documents, but instead augments the Design documents. In the absence of a DD like in the case of some inherited code, Code documentation plays a vital role in providing the end-user/consumer a very valuable insight into the code.

There are different aspects of code documentation, some of which have been described below. However, for a seasoned programmer there are many more interesting and critical aspects of coding, which I plan to add in future. The list is definitely not exhaustive, but indicative and outlines what the author thinks are the first level critical aspects of code documentation.

1. Headers

Headers are the MOST important part of any code documentation. Whether it’s a file header or a function header, they contain a lot of critical information and should be maintained throughout the lifecycle of the project.

File Header typically consists of copyright along with organization specific information. If header files were tool-generated, then some text about the tool and flexibility to modify sources is also inserted i.e. whether the end-user can modify the contents of the file or not. In some organizations,  file headers also contain change-lists with descriptions about the changes introduced into the file and some have strings which are then read by a script and auto-header is inserted. Some examples are enlisted below.

Simple Organization Header

/* ABCD Organization Ltd Copyright 2000-2011 */

Tool Generated Header

// ABCD Organization Ltd Copyright 2000-2011
// Generated by the XYZ tool - DON'T MODIFY

Detailed Header

/* ******************************************* *
 * ABCD Organziation Ltd Copyright 2000 - 2011 *
 * Distributed under PQRS Licensing Terms      *
 * File Name: test.h                           *
 * File Location:/src/test/inc/test.h          *
 *                                             *
 * Changelist History                          *
 * =========================================== *
 * Change Id | Description                     *
 * =========================================== *
 * AA_0001    Added the support for feature A  *
 *            as part of macro FEAT_A which    *
 *            is compile-time enabled          *
 *                                             *
 * BB_0002    Removed the support for feature  *
 *            B and included place-holders     *
 * ******************************************* *
*/

Function headers are invaluable blocks of any good software code, which is often referred to by many documentation tools or experts during source code traversal. Popular documentation tools like Doxygen standardize the format of a function header which aids easier traversal and automatic document generation, which then can be referred by developers and customers alike.

Function headers typically contain information regarding the input and output parameters of the function, expected behavior/return type and brief description about the function being encapsulated. A sample function header is as below:

Function Header

/* ************************************************************************* *
 * Function Name: myFuncUnderDocumentation                                   *
 * Description:   This is a sample function header that can potentially be   *
 *                used by Doxygen Tool and generate an end-user document     *
 * Parameters:    param1 [in]: Parameter 1 which is an input parameter       *
 *                param2 [in]: Parameter 1 which is another input parameter  *
 *                param3 [out]: Output Parameter                             *
 * Return Type:   MY_SUCCESS on successful completion of the routine         *
 *                RETURN_FAILURE_01 on failure type 1                        *
 *                RETURN_FAILURE_02 on failure type 2                        *
 * ************************************************************************* *
*/

2. Macros

Macros are extensively used in programming either to enable/disable constructs or to contain a specific value. When used in nested IF conditions, it is imperative to ensure that the corresponding ENDIFs are properly documented. This helps to isolate the corresponding IFs and makes code reading a far easier and pleasant task. Consider the sample illustration below which outlines this aspect and the value-add provided by a good documented nested IF statements

#if MACRO_01

#if MACRO_02

#if MACRO_03

#endif //MACRO_03 ====> END OF MACRO_03

#if MACRO_04

#endif //MACRO_04 ====> END OF MACRO_04

#endif //MACRO_02 ====> END OF MACRO_02

#if MACRO_05

#endif //MACRO_05 ====> END OF MACRO_05

#endif //MACRO_01 ====> END OF MACRO_01

Another aspect would be appropriate naming of macros. Though this is addressed in coding guidelines (a post(s) in future), it is an integral part of code documentation which is often overlooked. In programming, one has a tendency to use native/raw numbers as is in any mathematical equation. Good programming practices mandate that source code shouldn’t contain these magic numbers and instead should be represented through macros. These macros should be named appropriately and shouldn’t be a plain name as MACRO_01. This is useless and doesn’t aid in making the process of understanding the source code any easier.

Consider the source below which is comparing the area of a circle.

if (current_value < (3.1415 * r * r))

The magic number 3.1415 should be replaced by a macro as shown below. However, if the macro is poorly named as SOME_MACRO_01, it doesn’t help the user to know that the equation is intended to signify the calculation of area of a circle.

if (current_value < (SOME_MACRO_01 * r * r))

Instead if the macro is named as PI_VALUE, it facilitates easier understanding of the sources and better code.

if (current_value < (PI_VALUE * r * r))

A flip side of macro naming would be very long names. Choosing a macro name is an art and should be designed considering the fact the coding guidelines mandate a maximum column number beyond which one can’t continue to code. If very long and exhaustive macro names are chosen, typical IF statements get spilled over multiple lines and code legibility reduces.

3. Source Code

Code documentation is one of the most widely debated and discussed subject in programming circles. Internet is full of good and not-so-good comments, with an entire section dedicated to the bloopers and funny comments. Jokes apart, code documentation is extremely essential for good software product development, which more often than not, gets neglected. The importance of code documentation is felt in maintenance phase of the project, when new additions or error fixes have to be introduced.

In today’s software development world, it is highly unlikely that a product code is developed out of scratch (except maybe standardization bodies or true R&D groups). More often than not, code is inherited. In this scenario, one truly feels the value of the code document by sheer presence or the lack of it. Without source code documentation, the effort of understanding the code and modifying the same just multiplies as a factor.

Source code documentation should be prudently chosen. Any extreme is usually unproductive. As described in earlier paragraphs, lack of source code documentation saps the available bandwidth out. In the same vein, too much of source code documentation is also a big NO-NO. One should NOT commit the sin of putting the DD in source code. In other words, the programmer shouldn’t use Source code documentation as an alternative or excuse for writing a good Design Document (DD). Source code documentation is like salt in cooking. When present in right amounts, it augments the taste. Lack of salt or excess salt makes the food non-edible and makes the same a big waste.

A sample illustration of good documented code is as below:

//Check if the current value is less than area of circle desired
if(current_value < (PI_VALUE * radius * radius))
{
   //Since the current value is less than area, return the current value
   return current_value;
}
else
{
     area = PI_VALUE * radius * radius;
     //As current value is more, invoke the scaler to map and reevaluate
     ret_val = myScaler(radius);

    // Check if scaled value is less than area and return the same
    // Else return area
    if(ret_val <= area)
         return ret_val;
    else
         return area;
}

Conclusion

Code documentation is a product of good programming practice that is inculcated over a period of time. It is the responsibility of senior members of the team to nurture and inculcate the habit of good code documentation from the time any developer enters the industry. Good code documentation goes a long way in creating some fantastic and truly world-class software products.

QC 2: Test Design

Verification and Validation are an integral part of any Software Development process. Numerous definitions of these 2 processes are available, the more famous of which is, Validation is to check if you built the right product and Verification is to check if you built the product right. The importance of these 2 processes in any SDLC is immense and critical for the successful execution of the project. It won’t be an understatement to say that the success of any product is directly linked to the role and quality of these 2 processes in the system.

Test-cases are an integral part Verification and Validation processes and are the basic construct of the entire validation system. Typically one hears that some 1000s of test-cases have been executed and sometimes, the numbers are pure mind-boggling. However, in the same breath, it is often heard that the customer has reported a lot of issues/bugs and is generally unhappy. To understand the potential reasons for the customer dissatisfaction, one needs to take a closer look at the process and determine if there is something that is fundamentally short/flawed.

1. Quantity vs Quality

Running 1000s of test-cases is necessary but not a sufficient condition to guarantee the quality of any software product. Though this appears to be counter-intuitive i.e. larger number of testcases should make the product better, one has to note that number of testcases of the dimensions of a multi-dimensional vector which embodies the test construct.

Generally, a test-case should be designed for a specific goal. Some organizations do insist on documenting the expected result of a testcase, which helps the designer to capture the potential gaps in the test. When 10 testcases test the same piece of code i.e. same exact lines of code, it is fundamentally flawed or skewed as the left out lines of code are never tested. However, if test-cases are designed with expected results in mind, one can clearly design 5 test cases where majority of the code is traversed. Code Coverage is a typical metric that is widely employed to capture this aspect. Code coverage is a cumulative percentage figure that captures the total lines of code that were touched during testing as compared to the overall available lines of code.

Higher code coverage (as a percentage) is order of the day. Typically, for a product to be field-grade, code coverage has to be in very high nineties, say greater than 98% or so. This ensures that a large part of the code is tested and verified before customer deployment and probability of customer reported issues is less. In a nutshell, 100 test-cases with a combined code-coverage of 80% is not as good as 6 test-cases with a combined code-coverage of 98%.

2. Is Code Coverage sufficient?

Code-coverage again is a good tool, but doesn’t guarantee that the software product actually meets the necessary requirements and caters to different conditions that it would be subjected to. Knowledge of the domain or the product being built is also essential in developing a good test-bed.

If the software product under consideration is a multimedia encoder, code coverage will ensure most of the algorithm paths, it will not necessarily ensure that the built product adheres to the design considerations. An encoder is expected to handle variations across multiple dimensions of the input and generate good quality bitstreams. To achieve this goal, a good test-bed which tests all the dimensions of input vector should be designed.

3. Test Suites

In any typical embedded systems, different suite of tests would be required to validate different aspects of the system. Hence, apart from the overall functional suite, one would employ Performance, Negative and Stress testing suites. One common trap is to combine one or more suites together to optimize time and effort.

If there is a combined test-case that tests both functional and performance aspects of the system, the failure of which can’t be easily localized to either functional or performance aspect of the same. This requires additional debugging effort to understand the nature of the failure and derive conclusions of the same. Hence, the rationale of combining suites is not beneficial from an overall test strategy perspective.

In the same vein, a good test design should encompass system and integration level testing as these are traditionally known to be the pain points. When modules from different developers are integrated, assumptions come to the fore and hell breaks loose. Hence, it’s prudent to forecast the potential pitfalls and design specific tests to cater to these integrations, specifically to interfaces. If a suite of integration test pass, a huge risk would be mitigated from the project perspective.

4. Customer Tests

Customer is the king!! If the software product has been released to the customer, it is always advisable to design a test suite that tests the system in the exact manner as the customer. This is a very essential and critical aspect of the overall validation strategy, which gets missed during tight schedules. Unless the customers test setup is replicated, there always would be assumptions and potential dangers in future. Forecasting this early enough and designing a suite should be the mantra of every developer.

End Notes

Summarizing the different aspects described above, one can arrive the following thumb-rules for effective test-case design:

  1. Code coverage is an important aspect which should be factored into test-case design. Try to achieve close to 100% code coverage
  2. Domain specific knowledge is essential to augment the code coverage which not only validates the product, but also ensures that the same works in accordance with the expected design.
  3. Horses for courses should be the mantra in design. Different testcases should be designed for different scenarios and it is best to avoid combining more than 1 objective into a single testcase
  4. Customer’s test-cases should be included into the overall test scenario. It reduces a lot of effort and pressure from a long-term perspective.

1. Quantity vs Quality