276 Views
January 15, 25
スライド概要
THE ART OF READABLE CODE by Dustin Boswell, Trevor Foucher Publisher: O’Reilly Media (2011) 2022/02/13
1. Code Should Be Easy to Understand ✦ The main question What makes code “better”? ✦ The fundamental theorem of readability Code should be written to minimize the time it would take for someone else to understand it
PART 1 Surface-Level Improvements
2. Packing Information into Names ✦ More concrete/precise word GetPage → FetchPage Size → Height, NumNodes, MemoryBytes Stop → Kill, Pause ✦ Examples find → search, extract, locate retval → sum_squared start → launch, create, begin, open tmp → user_info, tmp_file make → create, build, generate (i, j, k) → (club_i, member_j, user_k)
3. Names That Can’t Be Misconstrued (Mis-understood / Mis-interpreted) ✦ Rephrasing words that can be interpreted in two ways filter → select (to pick out), exclude (to get rid of) length → max_length, max_chars limit → max_*, min_* ✦ Appropriate pair of words inclusive range start last inclusive/exclusive range begin end
4. Aesthetics ✦ Aligning columns 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); terms units values
5. Knowing what to comment ✦ Principle: good code > bad code + comment ✦ Commenting on constants - calculation NUM_THREADS = 8 # as long as it’s >= 2 * num_processors, that’s enough. - reasonable values // Impose a reasonable limit - no human can read that much anyway. const int MAX_RSS_SUBSCRIPTIONS = 1000; - highly tuned values image_quality = 0.72; // users thought 0.72 gave the best size/quality tradeoff
6. Making Comments Precise and Compact
✦ Comments with high information-to-space ratio
- to use mathematical expression
// The int is the CategoryType.
// The first float in the inner pair is the 'score',
// the second is the 'weight'.
typedef hash_map<int, pair<float, float> > ScoreMap;
// CategoryType -> (score, weight)
typedef hash_map<int, pair<float, float> > ScoreMap;
- to polish sentences
// Depending on whether we’ve already crawled this URL before, give it a different priority.
// Give higher priority to URLs we’ve never crawled before.
PART 2 Simplifying Loops and Logic
7. Making Control Flow Easy to Read ✦ Removing nesting by returning early if (user_result == SUCCESS) { if (permission_result != SUCCESS) { reply.WriteErrors("error reading permissions”); reply.Done(); return; } reply.WriteErrors(""); } else { reply.WriteErrors(user_result); } reply.Done(); if (user_result != SUCCESS) { reply.WriteErrors(user_result); reply.Done(); return; } if (permission_result != SUCCESS) { reply.WriteErrors(permission_result); reply.Done(); return; } reply.WriteErrors(""); reply.Done();
8. Breaking Down Giant Expressions ✦ Using summary variables if (request.user.id == document.owner_id) { // user can edit this document... } if (request.user.id != document.owner_id) { // document is read-only... } 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... }
9. Variables and Readability ✦ Eliminating variables ✦ Shrinking the scope of variables ✦ Using for loop instead of while loop var setFirstEmptyInput = function (new_value) { var found = false; var i = 1; var elem = document.getElementById('input' + i); while (elem !== null) { if (elem.value === '') { found = true; break; } i++; elem = document.getElementById('input' + i); } if (found) elem.value = new_value; return elem; }; var setFirstEmptyInput = function (new_value) { for (var i = 1; true; i++) { var elem = document.getElementById('input' + i); if (elem === null) return null; // Search Failed. No empty input found. if (elem.value === '') { elem.value = new_value; return elem; } } };
PART 3 Reorganizing Your Code
10. Extracting Unrelated Subproblems ✦ Separating the generic code from the project-specific code CHARS_TO_REMOVE = re.compile(r"['\.]+") CHARS_TO_DASH = re.compile(r”[^a-z0-9]+”) business = Business() business.name = request.POST["name"] url_path_name = business.name.lower() url_path_name = re.sub(r"['\.]", "", url_path_name) url_path_name = re.sub(r"[^a-z0-9]+", “-", url_path_name) url_path_name = url_path_name.strip(“-") business.url = "/biz/" + url_path_name business.date_created = datetime.datetime.utcnow() business.save_to_database() def make_url_friendly(text): text = text.lower() text = CHARS_TO_REMOVE.sub('', text) text = CHARS_TO_DASH.sub('-', text) return text.strip("-") business = Business() business.name = request.POST["name"] business.url = "/biz/" + make_url_friendly(business.name) business.date_created = datetime.datetime.utcnow() business.save_to_database()
11. One Task at a Time
✦ Organizing tasks
void 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]++;
}
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”;
// 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;
}
// 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());
}
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]++;
}
}
// Task: update counts[]
counts["Exit State"][exit_state]++;
counts["Http Response"][http_response]++;
counts[“Content-Type"][content_type]++;
}
12. Turning Thoughts into Code
✦ Describing logic clearly
$is_admin = is_admin_request();
if ($document) {
if (is_admin_request()) {
// authorized
if (!$is_admin && ($document[‘username'] != $_SESSION[‘username'])) {
} elseif ($document && ($document['username'] == $_SESSION['username'])){
return not_authorized();
// authorized
}
} else {
} else {
if (!$is_admin) {
return not_authorized();
return not_authorized();
}
}
}
In short, there are two ways you can be authorized:
1) you are an admin
2) you own the current document (if there is one)
Otherwise, you are not authorized.
✦ Explaining what a program is doing in plain English
- to your colleagues
- to rubber duck (cf. rubber duck debugging)
13. Writing Less Code ✦ Overengineering - we tend to overestimate how many features are truly essential - we tend to underestimate how much effort it takes - rethink requirements to solve the easiest version of the problem ✦ Keeping your codebase small - create as much generic “utility” code as possible - remove unused code or useless features ✦ Being familiar with the libraries - get familiar with standard libraries by periodically reading through their entire APIs - use UNIX tools instead of coding
Slide Recipes ✦ Theme: Silver chars / lead background (with lower contrast than W/B) ✦ Font: Cica (monospaced) ✦ Table GetPage → FetchPage Size → Stop → no border GetPage → FetchPage Height, NumNodes, MemoryBytes Size → Height, NumNodes, MemoryBytes Kill, Pause Stop → Kill, Pause ✦ Code Blocks NUM_THREADS = 8 overlay NUM_THREADS = 8