Why Coding Style Matters
Programming Style is incredibly important and usually ignored when learning to program. Instead of ignoring it, we will lay out an explicit set of rules for all programmers to follow. It is difficult to teach the “why” of these rules without the students first creating a large quagmire of code. To avoid the quagmire and ensuing refactor which results in a set of Style Rules, we will lay them out ahead of time and explain as best we can why they are needed.
In the worst case scenario, should we fail, the programmers will ultimately end up writing their own style rules anyway but only after some unknown length of time during which the code will be nearly indecipherable and only understood by some.
An entire article could be written on why code style matters. In fact, many have been written. A great one to read, Why Coding Style Matters, is written by Nicholas Zakas over at Smashing Magazine.
In RobotC, we designed our own style guide based on various other languages and the preferences of our programming mentors. With the switch to Java, we have adopted Google’s Java Style Guide which happens to be very similar to what we had chosen for RobotC.
The style rules covered here range over a few general areas.
- Programmer Level style rules describe how programmers should go about thinking about problems with some best practices.
- Code Level style rules describe how programmers should write their code including formatting, naming constraints, and best coding practices.
- Repository Level style rules describe how programmers should organize their code into files, name their files, and structure their folders.
Programmer Level Style Rules
Programming take meticulous care. Programmers should not be hasty. Following rules helps facilitate the programmer mind set.
Code Level Style Rules
Scope refers to the scope of a variable but in this case, I am talking about the indentation level that each additional if/while/for statement adds. Each time that a scope-level statement is used, you MUST indent everything within that statement by a single additional tab character – in RobotC, this equates two spaces (in more sophisticated IDEs, you can save tab-characters and each user can configure their IDE to display tab characters as a certain number of spaces).
// first level
// second level
// third level
// fourth level
// third level again
// second level again
// first level again
All curly braces should always appear on their own line.
See Scope Spacing code example.
For the purposes of clarity and conformity, different parts of the code will use different styles so that we can clearly delineate between them at a glance. In general, when you choose variable names, make them descriptive. This helps reduce the need for large comments. Note the difference between the two following code examples.
Bad Code Example:
// This function returns x divided by y
bool Q(int x, int y)
return (x / y);
Good Code Example:
bool Quotient(int dividend, int divisor)
We will use what is often referred to as camel-case for most variables. This means you use no spaces or underscores between words in the name of a variable. Instead, you capitalize each word.
Functions will be identical to variable naming except that the first word will be capitalized. With the exception of the main task, all tasks will follow this regime too.
All #defines and global constants must use all caps and use underscores between words. This helps programmers easily identify the difference in their code between a local variable and a global constant which is very important. Global constants are usually accessed at the top level and require changing from time to time.
#define DEAD_ZONE (15)
const float MINIMUM_CONTROLLER_INPUT = 15.0;
const float CONTROLLER_DEAD_ZONE = MINIMUM_CONTROLLER_INPUT;
Magic numbers are number values typed directly into the middle of your code and they should never be used. If you need a constant value, then you should predefine it as a constant or use a #define.
Magic Number Code Examples:
|const int MAX = 50;
if(i < MAX)...
if(i < 50)...
Magic Numbers should instead be defined as constants at the top of the file either through a #define or a constant and then assigned within the function. Note the DEAD_ZONE used in the following section’s Code Example for Function Encapsulation and Length.
Function Encapsulation and Length
All control of the robot should use functions. This includes but is not limited to motor control, reading sensors, and initializing the robot. All function definitions will go in files which are not the main file. This leaves the main task in a file by itself. #includes must be used to access all needed functions.
There is no hard-and-fast rule about function length but a safe bet is no longer than one page. If your function is longer than a page, you should seriously consider breaking it up into several functions.
const int DEAD_ZONE = 15;
if(abs(joystick.joy2_y1) > DEAD_ZONE)
You must comment your code. When you leave comments, you are not only explaining it to your peers but to yourself. When you see this code in two years (or even in two months), you want to easily be able to remember what you intended to do.
This code fragment does something weird but leaves no comment informing a future programmer why.
void DriveSpinnerMotor(int power)
if(power>power-1) // should be a comment here explaining this
motor[spinnerMotor] = power;
motor[spinnerMotor] = -power;
Comments about a code block should go above the code block. Comments about a single line should go at the end of the line unless this would make the line too long, in which case the comment should go above the line. If you do a good job of using descriptive variable names, you can reduce your need for detailed comments.
// ScaleControllerInput converts controller input values by
// first calculating the the slope-intercept equation based
// on constants.
int ScaleControllerInput(int controllerInput)
float slope = (MAX_MOTOR-MIN_MOTOR)/(MAX_CONTROLLER-MIN_CONTROLLER);
float intercept = (MIN_MOTOR-(slope*MIN_CONTROLLER));
// note output may be reversed
return (slope*controllerInput + sgn(controllerInput)*intercept);
Repository Level Style Rules
There is no hard-and-fast rule about where to put your code or what to name your folders. Decisions about this should be discussed with your teammates so that you can all agree on where, for example, the code should go that allows you to handle the light sensors. Over time, you will find that some files must be split. This is normal. Just make sure you discuss larger library changes with your team so that nobody gets out of sync.
All files and folders must be named using only lowercase characters. An effort should be made to use simple single-word naming. Users should be able to easily browse through folders at a glance without having to decipher the text.
motors.java would be placed in a folder named “motors”.
The #include would then be adjusted to read #include “motors/motors.java”
Other motors related files would also go in the folder named “motors”.
The primary compiled code files should be located at the top level of a directory structure. All includes should be maintained inside folders which describe what the includes handle. This means that any file which is not possible to compile should not be available at the top level of the team’s directory. At Swerve Robotics, we have three FTC teams who share a common library. This tactic enables any team member to edit the library and, through testing compiles of other teams’ code, verify that their edit does not break any other teams’ code.
Include files full of global constants belong in a folder named “includes”.
Motor interaction functions belong in files in a folder named “motors”.
Sensor interaction functions belong in files in a folder named “sensors”.
Autonomous code belongs in a folder named “autonomous”.