So earlier this year I came across "The Art of Readable Code", an O'Reilly book about writing better, more maintainable code.
I found myself nodding in agreement at mostly everything in the book.
At about 200 pages, it's a quick read, so you should just go and read it. Honestly. Go.
You'll probably even be able to find a PDF somewhere.
Still here?
Uhm.
Ok, well then.
What about if I gave a talk about a few of main points of the book? And used a lot of expletives for entertainment purposes?
Some topics we would cover:
- the importance of names
- when to comment your code
- making control flow understandable
- separating unrelated code
- code aesthetics
The book: http://shop.oreilly.com/product/9780596802301.do
8. var euclidean_norm = function (v) {
var retval = 0.0;
for (var i = 0; i < v.length; i += 1)
retval += v[i] * v[i];
X
return Math.sqrt(retval);
};
var euclidean_norm = function (v) {
var sum_squares = 0.0;
for (var i = 0; i < v.length; i += 1)
sum_squares += v[i] * v[i];
return Math.sqrt(sum_squares);
✓
};
9. attach details
X duration
✓ duration_ms
X password
✓ plaintext_password
19. # Import the user's email contacts,
# and match them to users in our system.
# Then display a list of those users that
X
# 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)
20. def suggest_new_friends(user, email_password):
# Get the user's friends' email addresses.
✓
friends = user.friends()
friend_emails = set(f.email for f in friends)
# Import all email addresses from this user's email account.
contacts = import_contacts(user.email, email_password)
contact_emails = set(c.email for c 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)
22. // The class definition of Account
X
class Account {
! public:
! ! // Constructor
! ! Account();
! ! // Set the profit member
! ! void SetProfit(double profit);
! ! // Returns the profit
! ! double GetProfit();
};
25. X
// Enforce limits on the Reply as stated in the Request,
// such as the number of items returned, or total byte
// size, etc.
void CleanReply(Request request, Reply reply);
26. X
// Make sure 'reply' meets the count/byte/etc.
// limits from the 'request'
void EnforceRequestLimits(Request request, Reply reply);
27. include “director’s
commentary”
// Surprisingly, a binary tree was 40% faster
// than a hash table for this data.
// The cost of computing a hash was more
// than the left/right comparisons.
// This heuristic might miss a few words.
// That's OK; solving this 100% is hard.
31. write expressions like
you’d say it
if (length > 10)
while (bytes_received < bytes_expected)
✓
X
if (10 <= length)
while (bytes_expected > bytes_received)
32. if/else ordering
if (a == b) { if (a != b) {
// Case One ... // Case Two ...
} else { } else {
// Case Two ... // Case One ...
} }
33. deal with positive first
✓
if (debug) {
// Case One ...
} else {
// Case Two ...
}
34. deal with simpler first
if (!file) {
✓
// Log an error
} else {
// Actually do stuff
// ...
// ...
// ...
}
35. deal with interesting first
if (url.HasQueryParameter("expand_all"))) {
✓
// Interesting code
// ...
// ...
// ...
} else {
// “Easy” case
}
40. explain with vars
if line.split(':')[0].strip() == "root":
... X
✓
username = line.split(':')[0].strip()
if username == "root":
...
41. summarize with vars
if (request.user.id == document.owner_id) {
// user can edit this document...
if (request.user.id != document.owner_id) { // document is read-only...
}
}
...
if (request.user.id != document.owner_id) {
// document is read-only...
}
X
42. summarize with vars
final boolean user_owns_document =
(request.user.id == document.owner_id);
if (user_owns_document) {
// user can edit this document...
}
✓
...
if (!user_owns_document) {
// document is read-only...
}
43. use De Morgan’s laws
if (!(file_exists && !is_protected))
Error("Sorry, could not read file.");
X
✓
if (!file_exists || is_protected)
Error("Sorry, could not read file.");
66. Thanks!
pedro@morais.it
@pedromorais
morais on app.net
Editor's Notes
\n
This book was released a year ago by O&#x2019;Reilly - go buy it with a discount now at the stand.\nI&#x2019;ve read it, and I&#x2019;ve mostly agreed with the guys.\nIf you enjoy this talk, the book is far better, so read it.\n
The central concept of the book.\nhave you asked &#x201C;Who was the idiot that wrote this code?&#x201D;, and found out it was yourself, 3 months ago\n\nCode should be written to minimize the time it would take for someone else to understand it.\n\n
Today, I&#x2019;ve choosen 10 topics, 10 ideas for the book, I&#x2019;d like to share with you\n
Starting with the basics: naming.\n
\n
Names like tmp, retval, and foo are usually cop-outs that mean &#x201C;I can&#x2019;t think of a name.&#x201D; Instead of using an empty name like this, pick a name that describes the entity&#x2019;s value or purpose.\n\nThe name retval doesn&#x2019;t pack much information. Instead, use a name that describes the variable&#x2019;s value.\n\n
\n
\n
Would a new team member understand it?\n
You need to ask yourself: what other meanings could someone interpret from this name?\nPlay devil&#x2019;s advocate!\n
What does this mean? Include only stuff before 2000? or remove stuff before 2000?.\nWhat if was called &#x201C;includeOnlyIf&#x201D;?\n
What does limit mean? Up to 10? is 10 allowed? MAX is much better.\nPlay devil&#x2019;s advocate!\n
Booleans.\nPlay devil&#x2019;s advocate!\n
\n
Not getting into where you should put your brackets. That&#x2019;s asking for trouble.\n(anyway, the solution is - use Python)\n
Consistent style is more important than the &#x201C;right&#x201D; style.\n\n
Written text is broken into paragraphs for a number of reasons:\nIt&#x2019;s a way to group similar ideas together and set them apart from other ideas. \nIt provides a visual &#x201C;stepping stone&#x201D;&#x2014;without it, it&#x2019;s easy to lose your place on the page. \nIt facilitates navigation from one paragraph to another. \n\n
\n
\n
Comments are meant to help the reader know as much as the writer did.\n
Don&#x2019;t comment on facts that can be quickly derived from the code itself\n
*quickly* derived from the code\n
CleanReply vs. EnforceRequestLimits\n
\n
\n
why is it this way?\nantecipate likely questions\nput yourself in the readers shoes\nbig picture comments\nadvertise pitfalls\n
\n
\n
\n
it&#x2019;s unnatural to say if 18 is less or equal to your age, you say if you&#x2019;re at least 18 year old\n
\n
\n
\n
\n
What&#x2019;s weird about a do/while loop is that a block of code may or may not be reexecuted based on a condition underneath it. Typically, logical conditions are above the code they guard&#x2014;this is the way it works with if, while, and for statements. Because you typically read code from top to bottom, this makes do/while a bit unnatural. Many readers end up reading the code twice.\n\n
One of the motivations for wanting a single exit point is so that all the cleanup code at the bottom of the function is guaranteed to be called. But modern languages offer more sophisticated ways to achieve this guarantee (java&#x2019;s finally, for instance).\n\n
When you see that first closing brace, you have to think to yourself, Oh, permission_result != SUCCESS has just ended, so now permission_result == SUCCESS, and this is still inside the block where user_result == SUCCESS.\n\n
\n
The simplest way to break down an expression is to introduce an extra variable that captures a smaller subexpression. This extra variable is sometimes called an &#x201C;explaining variable&#x201D; because it helps explain what the subexpression means.\n\n
Even if an expression doesn&#x2019;t need explaining (because you can figure out what it means),it can still be useful to capture that expression in a new variable. We call this a summary variable if its purpose is simply to replace a larger chunk of code with a smaller name that can be managed and thought about more easily.\n\n\n
\n
\n
Some guy from Microsoft has talked about how a great interview question should involve at least three variables.\nBut do you want your coworkers to feel like they&#x2019;re in an interview while they&#x2019;re reading your code?\n\n
\n
not breaking now a complex expression\ndoesn&#x2019;t add clarification\nno redundant code removed\n
you know globals? you know you should avoid them. well, apply that to all scopes. if it can be just inside a method, don&#x2019;t make it a class variable\nyou can mark methods as static to let the reader know their scope is reduced\n
a constant is easier to reason with; the closer to a constant your variable is, the easier it is to understand it\n
\n
1. Look at a given function or block of code, and ask yourself, &#x201C;What is the high-level goal of this code?&#x201D;\n2. For each line of code, ask, &#x201C;Is it working directly to that goal? Or is it solving an unrelated subproblem needed to meet it?&#x201D;\n3. If enough lines are solving an unrelated subproblem, extract that code into a separate function.\n\n
one advantage: it&#x2019;s easier to improve factored out code (for instance, a format_pretty function)\n
General-purpose code is great because it&#x2019;s completely decoupled from the rest of your project. Code like this is easier to develop, easier to test, and easier to understand. If only all of your code could be like this!\n\n
\n
General-purpose code is great because it&#x2019;s completely decoupled from the rest of your project. Code like this is easier to develop, easier to test, and easier to understand. If only all of your code could be like this!\n\n
General-purpose code is great because it&#x2019;s completely decoupled from the rest of your project. Code like this is easier to develop, easier to test, and easier to understand. If only all of your code could be like this!\n\n
You should defrag your code.\n
1. Using "unknown" as the default value for each key2. Detecting whether members of HttpDownload are missing \n3. Extracting the value and converting it to a string4. Updating counts[]\n\n
not breaking now a complex expression\ndoesn&#x2019;t add clarification\nno redundant code removed\n
\n
store locator; does it need to handle curvature or near north/south pole?\n
just the essential stuff; lots of features go unused and just complicate the code. don&#x2019;t do it just because it&#x2019;s cool\n