Specifically, there are three principles we use:
- Use a consistent layout, with patterns the reader can get used to.
- Make similar code look similar.
- Group related lines of code into blocks.
class StatsKeeper {
public:
// A class for keeping track of a series of doubles
void add(double d); // and methods for quick statistics about them
private:
int count; /* how many so far */
public:
double average();
private:
double minimum;
list<double>past_items;
double maximum;
};It would take you a lot longer to understand it than if you had this cleaner version instead:
// A class for keeping track of a series of doubles
// and methods for quick statistics about them.
class StatsKeeper {
public:
void add(double d);
double average();
private:
list<double> past_items;
int count; // how many so far
double minimum;
double maximum;
};Suppose you were writing Java code to evaluate how your program behaves under various network connection speeds. You have a TcpConnectionSimulator that takes four parameters in the constructor:
- The speed of the connection (Kbps)
- The average latency (ms)
- The “jitter” of the latency (ms)
- The packet loss (percentage)
Your code needed three different TcpConnectionSimulator instances:
public class PerformanceTester {
public static final tcpConnectionSimulator wifi = new TcpConnectionSimulator(
500, /* Kbps */
80, /* millisecs latency */
200, /* jitter */
1 /* packet loss % */);
public static final TcpConnectionSimulator t3_fiber = new TcpConnectionSimulator(
45000, /* Kbps */
10, /* millisecs latency */
0, /* jitter */
0 /* packet loss % */);
public static final TcpConnectionSimulator cell = new TcpConnectionSimulator(
100, /* Kbps */
400, /* millisecs latency */
250, /* jitter */
5 /* packet loss % */);
}To make the code look more consistent, we could introduce extra line breaks:
public class PerformanceTester {
public static final TcpConnectionSimulator wifi =
new TcpConnectionSimulator(
500, /* Kbps */
80, /* millisecs latency */
200, /* jitter */
1 /* packet loss % */);
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(
45000, /* Kbps */
10, /* millisecs latency */
0, /* jitter */
0 /* packet loss % */);
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(
100, /* Kbps */
400, /* millisecs latency */
250, /* jitter */
5 /* packet loss % */);
}Here’s a more compact way to write the class:
public class PerformanceTester {
// TcpConnectionSimulator(throughput, latency, jitter, packet_loss)
// [Kbps] [ms] [ms] [percent]
public static final TcpConnectionSimulator wifi =
new TcpConnectionSimulator(500, 80, 200, 1);
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(45000, 10, 0, 0);
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(100, 400, 250, 5);
}Suppose you had a personnel database that provided the following function:
// Turn a partial_name like "Doug Adams" into "Mr. Douglas Adams".
// If not possible, 'error' is filled with an explanation.
expandFullName(DatabaseConnection dc, string partial_name, string* error);and this function was tested with a series of examples:
DatabaseConnection database_connection;
string error;
assert(expandFullName(database_connection, "Doug Adams", &error)
== "Mr. Douglas Adams");
assert(error == "");
assert(expandFullName(database_connection, " Jake Brown ", &error)
== "Mr. Jacob Brown III");
assert(error == "");
assert(expandFullName(database_connection, "No Such Guy", &error) == "");
assert(error == "no match found");
assert(expandFullName(database_connection, "John", &error) == "");
assert(error == "more than one result");To really improve this code, we need a helper method so the code can look like this:
checkFullName("Doug Adams", "Mr. Douglas Adams", "");
checkFullName(" Jake Brown ", "Mr. Jake Brown III", "");
checkFullName("No Such Guy", "", "no match found");
checkFullName("John", "", "more than one result");Now it’s more clear that four tests are happening, each with different parameters. Even though all the “dirty work” is inside checkFullName(), that function isn’t so bad, either:
#include <string>
void CheckFullName(
string partial_name,
string expected_full_name,
string expected_error
) {
string error;
string full_name = ExpandFullName(database_connection, partial_name, &error);
assert(error == expected_error);
assert(full_name == expected_full_name);
}The moral of the story is that making code “look pretty” often results in more than just surface improvements—it might help you structure your code better.
Sometimes you can introduce “column alignment” to make the code easier to read. For example, in the previous section, you could space out and line up the arguments to checkFullName():
checkFullName("Doug Adams" , "Mr. Douglas Adams" , "");
checkFullName(" Jake Brown ", "Mr. Jake Brown III", "");
checkFullName("No Such Guy" , "" , "no match found");
checkFullName("John" , "" , "more than one result");In this code, it’s easier to distinguish the second and third arguments to checkFullName().
There are many cases where the order of code doesn’t affect the correctness. For instance, these five variable definitions could be written in any order
details = request.POST.get('details')
location = request.POST.get('location')
phone = request.POST.get('phone')
email = request.POST.get('email')
url = request.POST.get('url')In situations like this, it’s helpful to put them in some meaningful order, not just random. Here are some ideas:
- Match the order of the variables to the order of the
<input>fields on the corresponding HTML form. - Order them from “most important” to “least important.”
- Order them alphabetically.
Whatever the order, you should use the same order throughout your code. It would be confusing to change the order later on.
Written text is broken into paragraphs for several reasons:
- It’s a way to group similar ideas together and set them apart from other ideas.
- It provides a visual “stepping stone”—without it, it’s easy to lose your place on the page.
- It facilitates navigation from one paragraph to another.
Code should be broken into “paragraphs” for the same reasons. For example, no one likes to read a giant lump of code like this:
# Import the user's email contacts, and match them to users in our system.
# Then display a list of those users that he/she isn't already friends with.
def suggest_new_friends(user, email_password):
friends = user.friends()
friend_emails = set(f.email for f in friends)
contacts = import_contacts(user.email, email_password)
contact_emails = set(c.email for c in contacts)
non_friend_emails = contact_emails - friend_emails
suggested_friends = User.objects.select(email__in=non_friend_emails)
display['user'] = user
display['friends'] = friends
display['suggested_friends'] = suggested_friends
return render("suggested_friends.html", display)It may not be obvious, but this function goes through several distinct steps. So it would be especially useful to break up those lines of code into paragraphs:
def suggest_new_friends(user, email_password):
# Get the user's friends' email addresses.
friends = user.friends()
friend_emails = set(friend.email for friend in friends)
# Import all email addresses from this user's email account.
contacts = import_contacts(user.email, email_password)
contact_emails = set(contact.email for contact in contacts)
# Find matching users that they aren't already friends with.
non_friend_emails = contact_emails - friend_emails
suggested_friends = User.objects.select(email__in=non_friend_emails)
# Display these lists on the page.
display['user'] = user
display['friends'] = friends
display['suggested_friends'] = suggested_friends
return render("suggested_friends.html", display)Consistent style is more important than the
rightstyle.
Everyone prefers to read code that’s aesthetically pleasing. By “formatting” your code in a consistent, meaningful way, you make it easier and faster to read.
Here are the specific techniques we discussed:
- If multiple blocks of code are doing similar things, try to give them the same silhouette.
- Aligning parts of the code into “columns” can make code easy to skim through.
- If code mentions A, B, and C in one place, don’t say B, C, and A in another. Pick a meaningful order and stick with it.
- Use empty lines to break apart large blocks into logical “paragraphs.”