How to Write Clean Arduino Code

If you recently started to code for Arduino you probably have already run into what every good coder eventually faces- Your own mess.

Writing clean code would make your code more robust and reliable, help you find bugs easily, allow you to add new features simply and to be so satisfied with the code that you will happily want to share it with others.

The idea of writing ”clean code” is very much similar to telling a story, the story of your code. My goal is to make you tell your code's story as simple as possible, so as if you opened at a random page you could understand right away and get into the story, without tracing the whole way back to the first page.

Here are some basic, important, great practices to achieve cleaner code. Take a few minutes to read and your future self will thank you later ? .

*Disclaimer: The examples are here only to express concepts. I trust you to understand the concept, the idea, and apply it no matter the language you code in.

  1. Indentation, indentation, indentation

Writing code without indentation is like telling a story in one breath:

public void ifYouWantToSingOutSingOut(Writer catStevens) {
...
if (youWantToLiveHigh) {
liveHigh();
if (youWantToLiveLow) {          
liveLow();
}
}
//'Cause there's a million ways to go
//You know that there are
//You can do what you want
//The opportunity's on
if (youFindANewWay) {
doItToday(youCan);
}
makeItAll = TRUE; //And you can make it undo
}
No indentation

Sometimes we copy snippets of code from forums or examples around the web, and when we paste the code the format is corrupted and the indentation can turn out to be random. The equivalent “story” metaphor would be an abstract modern-poetry, more than a story, really, where the readers have no chance of figuring out the writer’s intentions:

public void ifYouWantToSingOutSingOut(Writer catStevens) {
...
    if (youWantToLiveHigh) {
   	 liveHigh();
   	 if (youWantToLiveLow)
 	 		 {          
   		 liveLow();
}
  	 	        }    
//'Cause there's a million ways to go
 	//You know that there are
//You can do what you want
//The opportunity's on
 	 if (youFindANewWay)
    			{
 
   	 doItToday(youCan);
}
 
makeItAll = TRUE; //And you can make it undo
}
Random indentation

Even while writing these examples it took me a minute to validate myself and make sure I closed all parenthesis correctly O_O .

If you don’t indent correctly you can not understand the flow of the story at a glance. Probably not even after a while...

If you don’t indent correctly you might accidentally forget to open/close your parentheses (compilation error), or worst- not the way you meant (bug!! The compiler won’t protect you from this one!).

If you don’t indent correctly… good luck.

public void ifYouWantToSingOutSingOut(Writer catStevens) {
...
	if (youWantToLiveHigh) {
   		liveHigh();
   		if (youWantToLiveLow) {          
			liveLow();
   	 	}
    }
    
    //'Cause there's a million ways to go
    //You know that there are
    //You can do what you want
    //The opportunity's on
    if (youFindANewWay) {
		doItToday(youCan);
    }
 
    makeItAll = TRUE; //And you can make it undo
}

Useful Tip: Correct indentation is SO important that Arduino IDE provides you with a keyboard shortcut! Simply use Ctrl+T (or Cmd+T) to automatically indent the selected code ;) .

Note: the parenthesis are sometimes written differently:

if (youFindANewWay) {
   	 doItToday(youCan);
}
In the Arduino convention, or in Java, for example
if (youFindANewWay)
{
  	 doItToday(youCan);
}
In some C environments

Make sure to use the parenthesis format according to the coding standards of the language you code in, and that you stick with it.


2. Be consistent, NEVER mix coding standards

Each coding language has Coding Standards, which are the unified, conventional way to write code in that language. The subjects vary from naming to even spaces. Many times different companies will also have their own standards.

Writing according to the standards helps to achieve a cleaner look, and to work your way through the code, since you know what you look for and how it would probably look like.

Thinking about it in terms of our “story” and looking at the sentences below, without even reading them, you see the quote marks, your eyes jump line-by-line in a ping-pong motion and you can tell right away that these few sentences are a dialog- only by how it looks:

“Red is the best flavor for popsicles”

“But red is not even a flavor, it is a color”

“I don’t care, it is delicious”

“I tend to disagree, yellow is better”.

One of the main standards is the format of variables and function names. In C++, which is mostly used for Arduino programming, a common standard is Camel Case: the first word starts with a lowercase letter, the following words start with a capital letter, no space between words.

public string singOut(int howLoud);
public void beFree();
camelCaseStyle

In C, the standard is to write in Snake Case: all letters are lowercase, underscore between words.

public string sing_out(int how_loud);
public void be_free();
snake_style

As you can read around the web, there is no “one-truth”, but it is very important to make sure you stick with the style you decided on, and never mix different styles, even if it means modifying pieces of code you copied!

public string sing_out(int howLoud);
public void BeFree();
Mixing styles

Good coding standards can also add information when you read. For example, values that cannot change during runtime would be usually written in capital letters.

#define WAYS_TO_GO	1,000,000

So when I see the following line I know right away that WAYS_TO_GO is not a variable, but a constant:

int num_of_ways = WAYS_TO_GO;

3. Meaningful names

If you're familiar with variables called n, num, a, b, list, var, arr, etc., then this one's for you.

When we copy code snippets from examples or forums we sometimes just use those as they are. Because it is so obvious in the 4-lines-snippet what do you mean by "n". So you take it, you use it, it works great, you come back to your code a few months later - and you have no idea what you meant there.

Now you need to go all over the code's flow, and do the worst thing you can do when it comes to programming- Try to remember.

When you write code, "remembering" is a potential bug waiting to happen and precious time consuming. If your code's story talks by itself, you won't need to remember anything.

"Simple" doesn't mean "short".

Use simple naming, meaning: easily understood, easy to search and find, describes the role of the variable in your story. A good, meaningful name will spare you most comments since it will be very much clear what is happening (but more about that later...).

setOp(1); //set opportunity ON
string str = getCatSong();
You just turned a musician into a house animal!

My golden guideline is to "write like I speak" or like how I’d explain it to a coworker. To make it more clear, if I have a function that returns the percentage value of the battery I’d name the function exactly as I'd tell a coworker about it: getRemainingBatteryPercentage() in contrary to getBatPercentageValue() or getBatPerVal() since this is NOT how I speak.

If I a have a function that returns Pulse Width Modulation duty cycle percentage I can call it getPWMCycleDutyPercentage() since PWM is a well known abbreviation for Pulse Width Modulation, and I’d probably say “PWM” even if I needed to explain it out loud to someone.

#define ON 1
 
...
setOpportunity(ON);
string lyrics = getCatStevensSongLyrics();

4. Separate actions into functions

When looking at a piece of code, you don't always have to know the specific details of every action, but just what it does in general. Especially when you just want to understand the story. If you need to get deeper into the story, you can just refer to the relevant place, which, in code, is the implementation of the function.

In other words, keep your function small!

Usually, if you can’t read the whole function content without scrolling, or the function is more than 25-30 lines of code, that means you should probably split it into smaller functions.

Keep separation of concerns: ideally, each function does one thing, and is the only place in the code that is in charge of that thing.

When I code, I usually like to think Top-down, where I first write the main logic of what I want to do, and which functions I believe I’ll need, and only then implement those functions. I start from the “big picture” and dig down.

...{

	int emotionalLevel = 0;
	boolean wantToSing = true;
	for (int i = 0; i < FEW_SECONDS; i++) {
		emotionalLevel = checkEmotionalStatus(ALL_EMOTIONS);
		if (emotionalLevel > EMOTIONAL_TRIGGER) {
			wantToSing = false;
			break;
		}
	}
 
	if (wantToSing) {
 		int endorphins = mBrain.startCommand(CMD_SING);
 		char songBuffer[SONG_MAX_LENGTH];
		int charIndex = 0;
 		
 		while (mMouth.isSinging() && charIndex < SONG_MAX_LENGTH) {
			songBuffer[charIndex++] = mMouth.getChar();
 		}
 
		Serial.println(songBuffer);
 		setMood(endorphins);
	}
    
...
}
...{

	if (youWantToSingOut()) {
 		singOut();
	}
    
...
}
 
public void singOut() {
 	int endorphins = mBrain.startCommand(CMD_SING);
 	char songBuffer[SONG_MAX_LENGTH];
	int charIndex = 0;
 
 	while (mMouth.isSinging() && charIndex < SONG_MAX_LENGTH) {
		songBuffer[charIndex++] = mMouth.getChar();
 	}
 
	Serial.println(songBuffer);
	setMood(endorphins);
}
 
private boolean youWantToSingOut() {
	int emotionalLevel = 0;
	for (int i = 0; i < FEW_SECONDS; i++) {
		emotionalLevel = checkEmotionalStatus(ALL_EMOTIONS);
		if (emotionalLevel > EMOTIONAL_TRIGGER) {
			return true;
		}
	}
 
	return false;
}

Once your code is organized, less likely for a bug to occur. But we're all human (yet) and bugs happen. By splitting your code into shorter functions you get code that is much easier to debug! In many cases, just by looking at the functions names, you'll have a good idea where to look for the bug. Once you find a suspect, you'll be able to run it in isolation (call it with some parameters, and see if the result is what you expect), add debug prints, or even replace the body of the function so it always returns a specific value, allowing you to further narrow down the scope of the bug.

5. Reusable

Whenever you want to perform a sequence of actions more than once, or if you have two similar classes/functions with only slight differences, consider to wrap the common part in a different class/function and then call it.

public void liveHigh() {
	for (int days = 0; day < 365; day++) {
		for (int year = 0; year < 120; year++) {
			wakeup(HIGH);
			eat(HIGH);
			work();
			eat();
			doStuff(HIGH);
			sleep();
		}
	}
}
 
public void liveLow() {
	for (int days = 0; day < 365; day++) {
		for (int year = 0; year < 120; year++) {
			wakeup(LOW);
			eat(LOW);
			work();
			eat();
			doStuff(LOW);
			sleep();
		}
	}
}
This example turned out to be a little too morbid than I intended...
public void liveHigh() {
	live(HIGH);
}
 
public void liveLow() {
	live(LOW);
}
 
public void live(int highOrLow) {
	for (int days = 0; day < 365; day++) {
		for (int year = 0; year < 120; year++) {
			wakeup(highOrLow);
			eat(highOrLow);
			work();
			eat();
			doStuff(highOrLow);
			sleep();
		}
	}
}

In addition to saving your time and writing less code, reusable functions also give you these benefits:

1. Maintainability - The code is easier to maintain.

If tomorrow you'll “live different”, and the new life routine will include dancing every day all you will have to change is adding one line to the live() function. If you just copied the code every time you wanted to perform this routine (wakeup, eat, work, etc’...), you’d have to look for all the places it appears in the code and add a call to dance(), not to mention the potential bug if you missed one.

2. Memory - there's less code to store on the chip. No need to say more.

6. Validity? Check!

If the story is not relevant to me, don't tell it, because it would just be confusing, not understood and worst of all- a waste of time.

public void ifYouWantToSingOutSingOut(Writer catStevens) {
...

	if (youWantToLiveHigh) {          
 		return;
   	} 
    else {
		liveLow();
		if (youFindANewWay) {
			doItToday(youCan);
		} 
        else {
			return;
		}
		
		//'Cause there's a million ways to go
		//You know that there are
 		//You can do what you want
 		//The opportunity's on
		makeItAll = TRUE; //And you can make it undo
	}
   
 ...
 }

Some of the executed code in the example above is eventually not needed. In addition, when I determine in the example whether I should continue the flow of the function or handle a special case (like returning), I split into two cases: "if" section and the "else" section.

A better practice is handling that special case, usually it is the "un-successful scenario", and returning. Continue the function flow only if passed the validity check. Think of it as a bodyguard for your function.

It is obvious at a glance to the reader that there are some validity terms and conditions check at the beginning of the function and it assures that the rest of the flow is a clean success-oriented logic flow.

For debugging, it is very easy to realize by using simple logs if the actions of the function were executed, or it returned due to validation term (and which one).

public void ifYouWantToSingOutSingOut(Writer catStevens) {
...
   	if (youWantToLiveHigh) {          
 		return;
   	} 
 	
 	if (! youFindANewWay) {
 		return;
 	}
 
	liveLow();
 	doItToday(youCan);
 
	//'Cause there's a million ways to go
	//You know that there are
 	//You can do what you want
 	//The opportunity's on
	makeItAll = TRUE; //And you can make it undo
    
...
}

7. Pick up the trash

I know it is sometimes tempting to keep every piece of code for “just-in-case”, but you have to let it go and get into a minimalist mindset when it comes to coding.

When you read a story, you only read the final “one source of truth”. You want to read only the relevant parts that are really part of the story! Not the 5 different drafts of the same paragraph the author tried out before the final outcome. It will definitely ruin the fun of reading it, but also confuse you with details and characters that are not part of the final plot. This unnecessary additional information can mislead and prevent you from focusing on telling your story right.

There are few common types of “unnecessary code”: comments, (what I call) archive code and debug code. Every time you see a commented-out piece of code stop and consider whether this line should be removed or handled "the clean way" described below.

Proper comments

Comments should add information, not repeat information in a different way. If you write a comment to explain something, it often makes sense to figure out if you can include this information somewhere else, like a variable name (ahem, meaningful names, ahem) or a function name, e.g.:

//check how many “new” ways are in the list
int sum = checkHowMany(list<Way> optionalWays);
Change names to make your code more clear
//check how many new ways in the list
int numOfNewWays = checkHowManyNewWays(list<Way> optionalWays);
The comment is just repeating information
int numOfNewWays = checkHowManyNewWays(list<Way> optionalWays);
No comment is needed, everything is clear.

Keep comments only for the parts that can't be explained by the code (e.g. explaining WHY you chose a specific way to do something, what else you tried and didn't work, etc’).

//Note! According to the song, “there's a million ways to go”
#define WAYS_TO_GO	1,000,000
 
if (! youKnowThatThereAreWays(WAYS_TO_GO)) {
	//According to the song, you know that there are! 
	//If you don’t, it is not valid case, so return
	return;
}

How to properly archive code

Use Git- If you haven't yet, I kindly advise you to start. Git is a source control that helps developers to manage their code and the code's versions.

Since it keeps all of the history you "save", you can delete unnecessary code, and you won't feel the need to keep it "just in case".

Working with Git is a main, important and very common method in developers life, and you can easily find guides on the subject.

...

//original line I was inspired by:
// makeMeACake(icecream);
//The line after I modified it a little to fit it to my code:
//makeMeACake(vegan);
//the only line I actually use:
makeMeADesert(cake, vegan);

...
...

makeMeADesert(cake, vegan);

...
...and the rest is history, on your Git history log.

How to properly keep debug code

Usually, you want to keep your story clean of debug code. If you MUST keep some debug code (such as debug prints) which debugs the final form of your code (meaning, not redundant “archive code”) I recommend conditional compilation to compile debug code only when you run your code on Debug Mode.

For conditional compilation, use preprocessor directives to determine whether a piece of code will compile or not. In addition, if your debug code contains multiple lines, I advise to move it into a separate function. For example:

void loop()  { 
	doSomething();
	/* 
	debugStuff1();
	debugStuff2();
	Serial.println(“If you see this line- you’re on DEBUG MODE!”); 
	*/
}
#define DEBUG_MODE //Comment out in order to turn off debug mode.
 
void doDebugThing() {
	#ifdef DEBUG_MODE
	debugStuff1();
	debugStuff2();
	Serial.println(“If you see this line- you’re on DEBUG MODE!”);
	#endif //DEBUG_MODE
}
 
void loop() { 
	doSomething();
	doDebugThing();
}

You might see different indentation styles for preprocessors conditions. As said, you can choose the style which makes most sense to you, just make sure you’re being consistent!

Note: It is a good practice to mention in the function name that it is a debug function.

Conclusion

Taking out the trash is always a big step towards cleaner life. Sometimes "wasting time" on cleaning up and organizing your code can save you a significant amount of time later!

These are all basic first steps for better coding. Keeping them in mind and practicing them will make you a better programmer,  yield easier coding-life for the long term for yourself and even more important - for other coders you share your code with!

In the words of (of course) Cat Stevens:

You can make it all true, and you can make it undo

? Huge thanks to the amazing Uri Shaked for helping with this post