Code maintenance: avoid naked integers

There are many ways to improve a code base. Resilience to change, readability, maintainability, and more can all be improved, sometimes with trivial modifications. One idea: avoid the use of trivial integer data types (naked integers) as function input parameters.

Many readers at this point might think I’m going to refer you to the standardized integer types, such as int32_t. And you would be correct. When a plain integer type is called for, please use the <cstdint> or stdint.h defined types. See here for more details.

But, we are not stopping there. Let us dive straight into an example.

A CAN bus driver API presents the following API (obscured/modified from the original):

int BusSetParameters(Handle h, long freq, ...yet more naked integer parameters...);

As a user of the API I am already at a loss and asking questions. For example: What range of freq is valid? Why is it a signed data type? If freq is a negative value, what happens? Thankfully, the API included appropriate documentation. Just above the declaration we see:

/* 
*  freq - See predefined constants BITRATE_xxx
*/

Again, as a user of the API, I now have yet-another-step to determine how to use the freq input parameter. I must now search for the predefined constants noted by the documentation. Don’t get me wrong, I’m glad that at least the predefined constants are available. In this case the various #defines were a mere 200 lines removed from the function’s documentation. Lets take a look:

/* Indicate a bitrate of 1 Mbit/s. */
#define BITRATE_1M (-1)
/* Indicate a bitrate of 500 kbit/s. */
#define BITRATE_500K (-2)
/* Indicate a bitrate of 250 kbit/s. */
#define BITRATE_250K (-3)

/* ... etc ... */

Now we know what happens with negative numbers and why the API used a signed long data type. <sigh>. At this point, I shake my head and move on to accomplish the task-at-hand.

So, how would I improve this function? Assuming standard C code is required, I would change each naked integer based parameter being used as an enumerated type to an actual enumerated data type. Our example is modified to:

typedef enum eBitRate 
{
BITRATE_1M = -1,
BITRATE_500K = -2,
BITRATE_250K = -3,
    ...
} BitRate;

...

int BusSetParameters(Handle handle, BitRate bitRate, ...);

For the above, I would also reconsider the int return value. Chance are it should also be an enumerated type.

So, did these changes help the user of the API? I think so. The benefits include:

  • The new data type presents a clear purpose for the input. It communicates a bit rate value. It is self-documenting and readable.
  • With nearly all modern IDEs, a mere key-stroke will take the user to the definition of BitRate immediately enabling learning more about the type and the options available. No more manual searching for the appropriate #defines.
  • The data type communicates the exact input values expected. Of course with C code, the user may still accidentally inject an incorrect integer value. However, I would assume that most static analysis tools would issue a warning on such mistakes. Furthermore, an incorrect magic number integer will now be far more noticeable during a human code review. Tip: consider C++ and the new enum class available since C++11, which would further improve this API and enable robust compiler type checking.

While using plain integer data types may be recommended in some situations, my general guideline remains: avoid naked integers. Put some clothes on already!

Photo by Nick Hillier on Unsplash

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Cove Mountain Software

Subscribe now to keep reading and get access to the full archive.

Continue reading