Readable Code
Why
I wrote it. I can read it.
It's easy to read short snippets of that you just wrote. As soon as time passes or complexity increases, the value of readable code starts to shine.
Readable code is...
- Easier to tell what is happening
- Less likely to be buggy
- Easier to debug
- Easier to get feedback on
- A good habit to foster
Developing The Habit
- Familiarize yourself with the principles of readable code.
- It's okay to just focus on one at a time.
- While you're learning, make second pass or third pass to refactor the code.
- Note: If you're going to change working code, always make a copy to allow you to get back to a working state if something goes wrong during refactoring.
It might take a little longer to focus on readability while you're writing the initial code, but the benefit comes quickly thereafter. If you develop the habit of writing readable code, it doesn't cost any additional time.
Problem 1
This is a rather extreme implementation of our balanceArrays problem. Since the problem requires a single function, you might be tempted to skimp on variable names and spacing. You'd have to admit that it's not very readable.
Let's look at how this can be improved. Ideally you could take these steps on the first pass. If not, clean it up as you go.
The steps below are not in any particular order. Each one improves readability, and each expands on the others.
int[ ][ ] balanceArrays(int[ ][ ] a) {
int al = a.length, te = 0;
for(int i = 0; i < al; i++) te += a[i].length;
int epa = te/al; if (te % epa != 0) epr += 1;
int[ ][ ] rv = new int[al][epa];
int r = 0, c = 0;
for(int i = 0; i < al; i++) {
for(int j = 0; j < a[i].length; j++) {
rv[r][c] = a[i][j];
c += 1;
if (c == epa) {r += 1;c = 0;}
}
}
return rv;
}
Add Space
Once you create more than three or four lines of code, that code probably contains more than one idea or section. Adding lines breaks up those sections, much like paragraphs break up written text.
Don't put an extra line between every line of code, but look for changes in the problem being tackled.
After
int[ ][ ] balanceArrays(int[ ][ ] a) {
int al = a.length, te = 0;
for(int i = 0; i < al; i++) te += a[i].length;
int epa = te/al; if (te % epa != 0) epr += 1;
int[ ][ ] rv = new int[al][epa];
int r = 0, c = 0;
for(int i = 0; i < al; i++) {
for(int j = 0; j < a[i].length; j++) {
rv[r][c] = a[i][j];
c += 1;
if (c == epa) {r += 1;c = 0;}
}
}
return rv;
}
Avoid Compound Lines
Most programming language allow you to do multiple things on the same line of code. Sure, occasionally compound line can be helpful, but make it the exception and not the norm.
For Example:
- Defining multiple integer variables together one the same line. (see Before line 2)
- Including the body of an if or for statement on the same line, when the body is only a single statement. (see Before lines 4, 6, & 14)
- Putting multiple short statements together, separated by a semicolon. (see Before lines 6 & 14)
Note:
Opinions vary on the proper placement of the opening bracket of a block of code. I tend to put it at the end of the statement line. Some people prefer to put it on its own line.
for(int i = 0; i < al; i++)
{
te += a[i].length;
}
After
int[ ][ ] balanceArrays(int[ ][ ] a) {
int al = a.length;
int te = 0;
for(int i = 0; i < al; i++) {
te += a[i].length;
}
int epa = te/al;
if (te % epa != 0) {
epr += 1;
}
int[ ][ ] rv = new int[al][epa];
int r = 0;
int c = 0;
for(int i = 0; i < al; i++) {
for(int j = 0; j < a[i].length; j++) {
rv[r][c] = a[i][j];
c += 1;
if (c == epa) {
r += 1;c = 0;
}
}
}
return rv;
}
Use Descriptive Variable Names
One letter variable names are expedient, but quickly become obscure. It's better to pick a variable name that is descriptive.
Good rules to follow:
- One or two words (maybe more if required).
- Enough to describe the contents of the variable.
- Too short or too long are both awkward.
- Follow the standard format for the language you're using (camelCase for Java or JavaScript; snake_case for Python or Ruby).
- Aim for readability!
Note:
It not uncommon to use the simple letter variable "i" as an index when iterating through an array. It's become so common that it is well understood.
Feel free to use it or avoid it. We're going to revisit our for statements soon anyway.
After
int[ ][ ] balanceArrays(int[ ][ ] intArrays) {
int arraysCount = intArrays.length;
int totalEntries = 0;
for(int i = 0; i < arraysCount; i++) {
totalEntries += intArrays[i].length;
}
int entriesPerArray = totalEntries/arraysCount;
if (totalEntries % entriesPerArray != 0) {
entriesPerArray += 1;
}
int[ ][ ] balancedArrays = new int[arraysCount][entriesPerArray];
int row = 0;
int column = 0;
for(int i = 0; i < arraysCount; i++) {
for(int j = 0; j < intArrays[i].length; j++) {
balancedArrays[row][column] = intArrays[i][j];
column += 1;
if (column == entriesPerArray) {
row += 1;
column = 0;
}
}
}
return balancedArrays;
}
Add Key Comments
We've already added lines to separate sections of the code. Consider adding a comment to describe the purpose of each section. Comments do not have to be long. Usually no more than a sentence. Just enough to remind yourself of the purpose of the section that follows.
Note:
Another approach is to write the comments first, before writing any lines of code. This approach allows you to describe what you need to do before determining the lines of code to make it happen.
After
int[ ][ ] balanceArrays(int[ ][ ] intArrays) {
int arraysCount = intArrays.length;
// Determine the total number of elements across all arrays
int totalEntries = 0;
for(int i = 0; i < arraysCount; i++) {
totalEntries += intArrays[i].length;
}
// Determine how many entries will be in each balanced array
int entriesPerArray = totalEntries/arraysCount;
if (totalEntries % entriesPerArray != 0) {
entriesPerArray += 1;
}
int[ ][ ] balancedArrays = new int[arraysCount][entriesPerArray];
int row = 0;
int column = 0;
// Walk through the arrays using row and column to track
// the insert position in balancedArrays.
for(int i = 0; i < arraysCount; i++) {
for(int j = 0; j < intArrays[i].length; j++) {
balancedArrays[row][column] = intArrays[i][j];
column += 1;
// Shift to the next row at the end of each row.
if (column == entriesPerArray) {
row += 1;
column = 0;
}
}
}
return balancedArrays;
}
Revisit Hard-To-Read Sections
Extra, if there is time!
Look for statements that are still confusing due to complex logic or syntax. Here are a few examples:
for(init, exit, next)
Most languages offer several methods for iterating through the items in the array. The classic three-component for statement is powerful, but usually requires a separate index variable. This involves more code, and more code offerers more opportunities for bugs. Look for cleaner ways to handle loop iteration. (see Before lines 6, 22, & 23)
if...else if Conditions
If statements can get complex quickly because it's not always clear what you're testing for, and frequently you need to test for multiple conditions. A great way to clarify is to add an extra line or two with a boolean variable that clarifies the test condition. (see Before line 28)
Confusing Math
Computers make different assumptions than humans. In code this can be things like integer vs. float math (e.g., 8/5 will give you a different number than 8.0/5.0) or operator precedence (e.g., 1 + 2 * 3 will multiply before adding). Knowing the computer assumptions is great, but it's worth making the code more explicit. Our original code required an extra step to determine the needed array size (see Before lines 11-14). The new code isn't necessarily more readable, but its use of float math and a Math.ceil (rounding up fractional numbers) is more expressive of what is required. There is no good example or operator precedence issues in this code example.
After
int[ ][ ] balanceArrays(int[ ][ ] intArrays) {
// Determine the total number of elements across all arrays
int totalEntries = 0;
for(int[ ] array : intArrays) {
totalEntries += array.length;
}
// Determine how many entries will be in each balanced array
float averageEntries = (float)totalEntries/(float)intArrays.length;
// Round up because we can't have a fractional number of entries
int entriesPerArray = (int)Math.ceil(averageEntries);
int[ ][ ] balancedArrays = new int[intArrays.length][entriesPerArray];
int row = 0;
int column = 0;
// Walk through the arrays using row and column to track
// the insert position in balancedArrays.
for(int[ ] array : intArrays) {
for(int value : array) {
balancedArrays[row][column] = value;
column += 1;
// Shift to the next row at the end of each row.
boolean atEndOfRow = (column == entriesPerArray);
if (atEndOfRow) {
row += 1;
column = 0;
}
}
}
return balancedArrays;
}
Other Tips
Indentation
- Indent your code cleanly.
- Every new block of code (e.g., functions, ifs, loops, etc.) should get 2 to 4 spaces.
- Make sure you can visually distinguish each section of code.
- It's better to use spaces instead of tabs because editors settings may vary.
- Editors can usually convert all tabs to a set number of spaces.
Line Length
- Avoid really long lines of code or comments.
- Keep the whole line visible.
- If the editor automatically wraps a line of code, insert your own line breaks to improve readability.
Function Length
- Try to keep the length of functions down.
- A good rule of thumb is be able to see the entire function without scrolling.
- If you can, it is much easier to keep track of what it is doing.