Software quality can be tough to quantify. Sure, there are lines of code counters and static analysis tools, however, I do not know of a single embedded engineering professional that would fully endorse the quality of software without personally reviewing the source code themselves. What are they looking for, and what are the factors that separate quality software everything else? Although each of us may have our preferences, might we suggest looking at the data structures used throughout the code as a marker of software quality.
Data structures are collections of variables assembled for a specific purpose. Take an analog-to-digital converter (ADC) for example. The code structure for an ADC may look something like this:
struct ADCExampleStruct
{
bool enabled;
float referenceVoltage;
int sampleRate;
unsigned int currentConversion;
};
The fields of this structure provide a quick overview of how the ADC is configured, its enabled status and the most recent conversion. Nice and tidy!
But looking past the syntax, this data structure shows something more, something that speaks to the quality of this software. In a word, this shows “thoughtfulness.” Now, consider the alternative of using this structure, that is, consider if these fields where just stand-alone variables:
bool enabled;
float referenceVoltage;
int sampleRate;
unsigned int currentConversion;
The two pieces of code contain the same information; however, the first example is superior. Why? Because it allows for all of the relevant ADC data to be encapsulated within a single structure and for this single structure to be passed into and operated upon other functions and modules. For example, let’s say I instantiate one of these structures, then assign values to its fields. Last of all, I pass it to a function to initialize a corresponding ADC. That would look like this:
struct ADCExampleStruct myAdc;
myAdc.sampleRate = 1000;
myAdc.referenceVoltage = 4.25;
myAdc.enabled = true;
InitAdc(&myAdc);
The function
InitAdc would have a prototype interface like this:
void InitAdc(
struct ADCExampleStruct* strPtr);
Nothing too complicated here. However, consider the complexities needed if a structure is not used. The simple
InitADC function’s prototype interface would have to look like this:
void InitAdc(
bool enabledState,
float ref,
int sampleRate);
The interface complexity has tripled!
Now instead of just passing a single pointer to the function, we have to pass the values of three parameters. But, that’s not all.
Suppose we would like to name our ADC so that its data can be distinguished from the other ADCs in our system. Well, if we’re using data structures, we just add a new field to that structure to carry the name, and we do not need to change the interfaces for the functions/methods that are used. However, without using a structure, we not only need to add a variable to hold the name, we also have to change all of the functions, which could use these variables, in order to account for change. So much work; so much opportunity for a bug to sneak in!
struct ADCExampleStruct
{
bool enabled;
float referenceVoltage;
int sampleRate;
unsigned int currentConversion;
char* adcName;
};
void InitAdc(
bool enabledState,
float ref,
int sampleRate,
char* adcName);
Structures demonstrate thoughtfulness, and thoughtfulness is the key to high-quality software. So, when you’ve inherited someone else’s code and are trying to quickly ascertain the quality, may we suggest that you look at the use of data structures. Are the fields organized into a logical manner? Does the use of the data structures seem appropriate for what each portion of the software is trying to accomplish?
In closing, if your team needs support developing high-quality software, or bringing an electronic product to market, CEPD can help. Our engineering process, available on our website, enables us to meet even the most challenging of design requirements. From hardware to software, RFID to robotics, CEPD can serve. Please, reach out today.
-NM