the art of readable code (ch1~ch4)
DESCRIPTION
The art of readable code 팀세미나 발제자료TRANSCRIPT
The Art of Readable Code (Ch1~Ch4)
Jonghan Seo
Ch1. Code Should Be Easy to Understand● collected bunch of examples of “bad code”● analyzed what made them bad ● found principles/techniques that can be used to make them better
● Key Idea: Code should be easy to understand
What Makes Code “Better”?● sometimes it is fairly obvious
What Makes Code “Better”?● sometimes it is vague
more compact !!!
less intimidating !!!
The Fundamental Theorem of Readability
● Code should be written to minimize the time it would take for someone else to understand
● to fully understand your code:○ be able to make changes to it○ be able to spot bugs○ be able to understand how it interacts with the rest of your code
● ‘someone else’ would be you
Some Different Views● The Smaller The Better?
○ It probably takes less time to understand fewer lines BUT it is not always better!
○ the time-till-understanding is an even better goal
● What about other constraints?○ code efficiency? well architectured? easy to test? …○ making code easy to understand often leads to meet the other rules
● the rest of this book discusses how to apply ‘easy to read’ in different circumstances
● There are four parts:○ Part One: Surface-Level Improvements○ Part Two: Simplifying Loops and Logic○ Part Three: Reorganizing Your Code○ Part Four: Selected Topics
● Surface-Level Improvements○ picking good names○ writing good comments○ formatting your code neatly
Contents
Ch2. Packing Information into Names● Key Idea: Pack information into your names
● Six Specific Topics1. Choosing specific words2. Avoiding generic names (or knowing when to use them)3. Using concrete names instead of abstract names4. Attaching extra information to a name, by using a suffix or prefix5. Deciding how long a name should be6. Using name formatting to pack extra information
1. Choose Specific Words● def GetPage(url): …
○ from a local cache? from a database? from the Internet?○ FetchPage(), DownloadPage()
● class BinaryTree {int size();...
}○ height of the tree? the number of codes? memory footprint of the tree?○ Height(), NumNodes(), MemoryBytes()
1. Choose Specific Words
2. Avoid Generic Names● ‘tmp’, ‘retval’, loop iterators● retval
○ var euclidean_norm = function (v) {var retval = 0.0;for (var i = 0 ; i < v.length; i += 1)
retval += v[i] * v[i];return Math.sqrt(retval);
};○ reval → sum_squares○ sum_squares += v[i]; → spot a bug!!○ Advice: Use a name that describes the variable’s value
2. Avoid Generic Names● tmp
○ if (right < left) {tmp = right;right = left;left = tmp;
} // it is ok!○ String tmp = user.name();
tmp += “ “ + user.phone_number();…template.set(“user_info”, tmp); // tmp → user_info
● Advice: ‘tmp’ should be used in cases when (1) being short-lived and (2) temporary is the most important fact about the variable
2. Avoid Generic Names● Loop Iterators
○ for (int i = 0; i < clubs.size(); i++) for (int j = 0; j < clubs[i].members.size(); j++) for (int k = 0; k < users.size(); k++) if (clubs[i].members[k] == users[j]) cout << "user[" << j << "] is in club[" << i << "]" << endl;
○ (i, j, k) → (club_i, memeber_i, users_i) or (ci, mi, ui)
○ if (clubs[ci].members[ui] == users[mi]) // Bug! First letters don't match up.
3. Prefer Concrete Names Over Abstract Names● Example1) Google Code Guide for C++
○ #define DISALLOW_EVIL_CONSTRUCTORS(ClassName) \ClassName(const ClassName&); \void operator=(const ClassName&);
class ClassName { private: DISALLOW_EVIL_CONSTRUCTORS(ClassName);};
○ ‘evil’ sounds too strong and more important, it isn’t clear what the macro is disallowing
○ DISALLOW_COPY_AND_ASSIGN
3. Prefer Concrete Names Over Abstract Names● Example2) --run_locally
○ --run_locally■ printing debugging information ■ used when testing on a local machine
○ What if we need...■ to print debugging information while the program run remotely?■ to run a performance test locally (so we do not want the logging
slowing it down)?○ --run_locally → --extra_logging
4. Attaching Extra Information to a Name● string id; //Example: “af84ef845cd8”
→ string hex_id;
5. How Long Should a Name be?
● the implicit constraint: ‘the name shouldn’t be too long’○ the longer a name, the harder it is to remember and the more space it
consumes on the screen○ However, if we take the constraint too far it also would make someone
else difficult to understand the code
● There are some guidelines:○ Shorter names are all right for shorter scope○ Typing Long Names - Not a Problem Anymore
■ if ‘they’re harder to type’ mattered■ Make full use of ‘word completion’ built in editors
○ Acronyms and Abbreviations■ project-specific abbreviations: Bad Idea ■ common to programmers: OK!
● evaluation → eval, document → doc, string → str○ Throwing out Unneeded Words
■ ConvertToString() → ToString()■ DoServerLoop() → ServerLoop()
5. How Long Should a Name be?
6. Use Name Formatting to Convey Meaning● In C++:
static const int kMaxOpenFiles = 100;class LogReader { public: void OpenFile(string local_file); private: int offset_; DISALLOW_COPY_AND_ASSIGN(LogReader);};
● In Javascript:var x = new DatePicker(); // DatePicker() is a "constructor" functionvar y = pageHeight(); // pageHeight() is an ordinary function
● in jQuery:var $all_images = $("img"); // $all_images is a jQuery object var height = 250; // height is not
● in HTML:<div id=”middle_column” class=”main-content”>
6. Use Name Formatting to Convey Meaning
Ch3. Names That Can’t Be Misconstrued● We have focused on how to put a lot of information into our names. ● We will do on a different topic: watching out for names that can be
misunderstood
● Key Idea: Actively scrutinize your names by asking yourself, “What other meanings could someone interpret from this name?”
● example: filter()○ results = Database.all_objects.filter("year <= 2011");○ what does results now contain?
■ Objects whose year is <= 2011?■ Objects whose year is not <= 2011?
Prefer min/max for (inclusive) Limits● CART_TOO_BIG_LIMIT = 10
if shopping_cart.num_items() >= CART_TOO_BIG_LIMIT: Error("Too many items in cart.")
● MAX_ITEMS_IN_CART = 10if shopping_cart.num_items() > MAX_ITEMS_IN_CART: Error("Too many items in cart.")
Prefer first/last for inclusive Ranges
Prefer begin and end for inclusive/exclusive Ranges
Naming Booleans● bool read_password = true;
○ We need to read the password: need_password○ The password has already been read: user_is_authenticated
● adding words like is, has, can or should can make booleans more clear○ SpaceLeft() → HasSpaceLeft()
● trying to avoid negated terms in a name○ disable_ssl = false → use_ssl = true
Matching Expectations of Users
● Example: get*():○ get: “lightweight accessors”○ public double getMean() {
//Iterate through all samples and return total/num_samples}
○ getMean() → computeMean()
● Example: list::size()○ in C++, list::size() is an O(n) operation○ size() → conuntSize(), countElements()
Matching Expectations of Users
● Example: Evaluating Multiple Name Candidates○ experiment_id: 100
description: ‘increase font size to 14pt’traffic_fraction: 5%…
○ experient_id: 101description: ‘increase font size to 13pt’[other lines are identical to experiment_id 100]...
○ ‘the_other_experiement_id_I_want_to_reuse: 100’ can be renamed? If so, how?
● Candidates:○ template○ reuse○ copy○ inherit
● ‘template’○ experiment_id: 101
template: 100○ ‘i am a template’ vs ‘i am using this other template’○ ‘template’ is generally something abstract that must be filled in
● ‘reuse’○ ‘This experiment can be reused at most 100 times’
Matching Expectations of Users
● ‘copy’○ ‘copy this experiment 100 times’ or ‘this is the 100th copy of
something’● ‘inherit’
○ with inheritance■ get all features from the inheritee■ can modify inherited features■ can add more own features
○ inherit_from_experiment_id might be the best
Matching Expectations of Users
Ch4. Aesthetics● How good use of spacing, alignment, and ordering can make your code
easier to read
● 3 principles:1. Use consistent layout2. Make similar code look similar3. Group related lines of code into blocks
Rearrange Line Breaks to be Consistent and Compact
Rearrange Line Breaks to be Consistent and Compact
Use Methods to Clean up Irregularity
Use Methods to Clean up Irregularity
Use Column Alignment When Helpful
Pick a Meaningful Order, and Use It Consistently● 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')
● some meaningful orders:○ the order of the <input> field on the corresponding HTML form○ from ‘most important’ to ‘least important’○ alphabetically
Organize Declarations into Blocksclass FrontendServer { public: FrontendServer(); void ViewProfile(HttpRequest* request); void OpenDatabase(string location, string user); void SaveProfile(HttpRequest* request); string ExtractQueryParam(HttpRequest* request, string param); void ReplyOK(HttpRequest* request, string html); void FindFriends(HttpRequest* request); void ReplyNotFound(HttpRequest* request, string error); void CloseDatabase(string location); ~FrontendServer();};
class FrontendServer { public:
FrontendServer();~FrontendServer();
// Handlersvoid ViewProfile(HttpRequest* request);void SaveProfile(HttpRequest* request);void FindFriends(HttpRequest* request);
// Request/Reply Utilitiesstring ExtractQueryParam(HttpRequest* request, string
param);void ReplyOK(HttpRequest* request, string html);void ReplyNotFound(HttpRequest* request, string
error);
// Database Helpersvoid OpenDatabase(string location, string user);void CloseDatabase(string location);
};
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)
Break Code into Paragraphsdef 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)
Key Idea: Consistent style is more important than the “right” style.
Personal Style vs. Consistency
Summary● Ch2. Packing Information into your names
○ Use specific words○ Avoid generic names○ Use concrete names○ Attach important details○ Use longer names for larger scopes○ Use capitalization, underscores, and so on in a meaningful way
● Ch3. Names That Can’t be Misconstrued○ Use ‘max_/min_’ for an upper or lower limit for a value○ Use ‘first/last’ for inclusive ranges○ Use ‘begin/end’ for inclusive/exclusive ranges○ Use words like is and has to make boolean variables clear○ Avoid negated terms○ Be aware of users’ expectation about certain words
Summary● Ch4. Aesthetics
○ Try to give similar codes the similar silhouette○ Align parts of the code into columns○ Pick a meaningful order and stick with it○ Break apart large blocks into logical paragraphs
Q&A
Any Questions?