The Art of Readable Code


Published on

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?

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:

1 Comment
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • \n
  • This book was released a year ago by O’Reilly - go buy it with a discount now at the stand.\nI’ve read it, and I’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 “Who was the idiot that wrote this code?”, 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’ve choosen 10 topics, 10 ideas for the book, I’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 “I can’t think of a name.” Instead of using an empty name like this, pick a name that describes the entity’s value or purpose.\n\nThe name retval doesn’t pack much information. Instead, use a name that describes the variable’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’s advocate!\n
  • What does this mean? Include only stuff before 2000? or remove stuff before 2000?.\nWhat if was called “includeOnlyIf”?\n
  • What does limit mean? Up to 10? is 10 allowed? MAX is much better.\nPlay devil’s advocate!\n
  • Booleans.\nPlay devil’s advocate!\n
  • \n
  • Not getting into where you should put your brackets. That’s asking for trouble.\n(anyway, the solution is - use Python)\n
  • Consistent style is more important than the “right” style.\n\n
  • Written text is broken into paragraphs for a number of reasons:\nIt’s a way to group similar ideas together and set them apart from other ideas. \nIt provides a visual “stepping stone”—without it, it’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’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’s unnatural to say if 18 is less or equal to your age, you say if you’re at least 18 year old\n
  • \n
  • \n
  • \n
  • \n
  • What’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—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’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 “explaining variable” because it helps explain what the subexpression means.\n\n
  • Even if an expression doesn’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’re in an interview while they’re reading your code?\n\n
  • \n
  • not breaking now a complex expression\ndoesn’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’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, “What is the high-level goal of this code?”\n2. For each line of code, ask, “Is it working directly to that goal? Or is it solving an unrelated subproblem needed to meet it?”\n3. If enough lines are solving an unrelated subproblem, extract that code into a separate function.\n\n
  • one advantage: it’s easier to improve factored out code (for instance, a format_pretty function)\n
  • General-purpose code is great because it’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’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’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’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’t do it just because it’s cool\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • The Art of Readable Code

    1. 1. The Art ofReadable Code @pedromorais morais on
    2. 2. Dustin BoswellTrevor Foucher O’Reilly Nov 2011
    3. 3. code should be easy to understand
    4. 4. 10ideas
    5. 5. 1. Pack information into your names
    6. 6. be specific X GetPage ✓ DownloadPage
    7. 7. avoid generic names X tmp X retval
    8. 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. 9. attach details X duration ✓ duration_ms X password ✓ plaintext_password
    10. 10. careful withabbreviations X BEManager ✓ str ✓ eval
    11. 11. avoid ambiguous naming
    12. 12. X.filter(“year <= 2000”)
    13. 13. CART_TOO_BIG_LIMIT = 10STOP = 10 XCART_MAX_ITEMS = 10LAST = 10 ✓
    14. 14. read_password = True X ✓needs_to_read_passwordpassword_has_been_read
    15. 15. 2. Aesthetics matter
    16. 16. be consistent
    17. 17. write code like text
    18. 18. # Import the users email contacts,# and match them to users in our system.# Then display a list of those users that X# he/she isnt already friends with.def suggest_new_friends(user, email_password): friends = user.friends() friend_emails = set( for f in friends) contacts = import_contacts(, email_password) contact_emails = set( for c in contacts) non_friend_emails = contact_emails - friend_emails suggested_friends = display[user] = user display[friends] = friends display[suggested_friends] = suggested_friends return render("suggested_friends.html", display)
    19. 19. def suggest_new_friends(user, email_password): # Get the users friends email addresses. ✓ friends = user.friends() friend_emails = set( for f in friends) # Import all email addresses from this users email account. contacts = import_contacts(, email_password) contact_emails = set( for c in contacts) # Find matching users that they arent already friends with. non_friend_emails = contact_emails - friend_emails suggested_friends = # Display these lists on the page. display[user] = user display[friends] = friends display[suggested_friends] = suggested_friends return render("suggested_friends.html", display)
    20. 20. 3. Comment wisely
    21. 21. // The class definition of Account Xclass Account {! public:! ! // Constructor! ! Account();! ! // Set the profit member! ! void SetProfit(double profit);! ! // Returns the profit! ! double GetProfit();};
    22. 22. ✓# remove everything after the second *name = *.join(line.split(*)[:2])
    23. 23. fix bad names,don’t explain them
    24. 24. 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);
    25. 25. X// Make sure reply meets the count/byte/etc.// limits from the requestvoid EnforceRequestLimits(Request request, Reply reply);
    26. 26. 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.// Thats OK; solving this 100% is hard.
    27. 27. comment code flaws TODO FIXME HACK XXX
    28. 28. make comments compact
    29. 29. 4. Make your control flow easy to follow
    30. 30. write expressions like you’d say it if (length > 10) while (bytes_received < bytes_expected) ✓ X if (10 <= length) while (bytes_expected > bytes_received)
    31. 31. if/else orderingif (a == b) { if (a != b) { // Case One ... // Case Two ...} else { } else { // Case Two ... // Case One ...} }
    32. 32. deal with positive first ✓if (debug) { // Case One ...} else { // Case Two ...}
    33. 33. deal with simpler firstif (!file) { ✓ // Log an error} else { // Actually do stuff // ... // ... // ...}
    34. 34. deal with interesting first if (url.HasQueryParameter("expand_all"))) { ✓ // Interesting code // ... // ... // ... } else { // “Easy” case }
    35. 35. avoiddo...while loops
    36. 36. return earlypublic boolean Contains(String str, String substr) { if (str == null || substr == null) return false; if (substr.equals("")) return true; ✓ ...}
    37. 37. minimize nestingif (user_result == SUCCESS) { if (permission_result != SUCCESS) { reply.WriteErrors("error reading permissions"); reply.Done(); return; } reply.WriteErrors("");} else { reply.WriteErrors(user_result); X}reply.Done();
    38. 38. 5. Break down giant expressions
    39. 39. explain with varsif line.split(:)[0].strip() == "root": ... X ✓username = line.split(:)[0].strip()if username == "root": ...
    40. 40. summarize with varsif ( == document.owner_id) { // user can edit this document... if ( != document.owner_id) { // document is read-only... }}...if ( != document.owner_id) { // document is read-only...} X
    41. 41. summarize with varsfinal boolean user_owns_document = ( == document.owner_id);if (user_owns_document) { // user can edit this document...} ✓...if (!user_owns_document) { // document is read-only...}
    42. 42. use De Morgan’s lawsif (!(file_exists && !is_protected)) Error("Sorry, could not read file."); X ✓if (!file_exists || is_protected) Error("Sorry, could not read file.");
    43. 43. 6. Tame your variables
    44. 44. X many variables broad scopechanging frequently
    45. 45. remove unnecessary varsnow = = now Xroot_message.last_view_time = ✓
    46. 46. make your variablevisible by as few lines of code as possible
    47. 47. prefer write-once variables
    48. 48. 7. Extract unrelated subproblems
    49. 49. factor out lines that are workingtowards a subproblem
    50. 50. separate thegeneric code from theproject- specific code
    51. 51. create a lot ofgeneral-purpose code
    52. 52. 8. Do one thing at a time
    53. 53. 1.figure out what tasks your code is doing
    54. 54. 2.separate those tasks into functions or sections
    55. 55. Xvoid UpdateCounts(HttpDownload hd) { // Figure out the Exit State, if available. if (!hd.has_event_log() || !hd.event_log().has_exit_state()) { counts["Exit State"]["unknown"]++; } else { string state_str = ExitStateTypeName(hd.event_log().exit_state()); counts["Exit State"][state_str]++; } // If there are no HTTP headers at all, use "unknown" for the remaining elements. if (!hd.has_http_headers()) { counts["Http Response"]["unknown"]++; counts["Content-Type"]["unknown"]++; return; } HttpHeaders headers = hd.http_headers(); // Log the HTTP response, if known, otherwise log "unknown" if (!headers.has_response_code()) { counts["Http Response"]["unknown"]++; } else { string code = StringPrintf("%d", headers.response_code()); counts["Http Response"][code]++; } // Log the Content-Type if known, otherwise log "unknown" if (!headers.has_content_type()) { counts["Content-Type"]["unknown"]++; } else { string content_type = ContentTypeMime(headers.content_type()); counts["Content-Type"][content_type]++; }}
    56. 56. void UpdateCounts(HttpDownload hd) { // Task: define default values for each of the values we want to extract string exit_state = "unknown"; string http_response = "unknown"; string content_type = "unknown"; // Task: try to extract each value from HttpDownload, one by one if (hd.has_event_log() && hd.event_log().has_exit_state()) { exit_state = ExitStateTypeName(hd.event_log().exit_state()); } if (hd.has_http_headers() && hd.http_headers().has_response_code()) { http_response = StringPrintf("%d", hd.http_headers().response_code()); } if (hd.has_http_headers() && hd.http_headers().has_content_type()) { content_type = ContentTypeMime(hd.http_headers().content_type()); } ✓ // Task: update counts[] counts["Exit State"][exit_state]++; counts["Http Response"][http_response]++; counts["Content-Type"][content_type]++;}
    57. 57. 9. Don’t write it
    58. 58. question yourrequirements
    59. 59. don’t over engineer
    60. 60. be familiar with your libraries
    61. 61. 10. Break the rules
    62. 62. 10. Break the rules when you have agood reason to do it
    63. 63. code should be easy to understand
    64. 64. Thanks! @pedromorais morais on