如何重新组织代码提高可读性? (函数层面, part 3)
1. 抽取与主要问题无关的代码
2. 重新组织代码使得一次只做一件事
3. 首先描述功能,然后再实现功能,这样更清楚明了
如何抽出问题无关的子问题? (chap 10)
0. 无关问题的思考
- 看到一个函数或一个代码块, 问自己, "这段代码的高层作用是什么(high-level gloal)"
- 对于每一行代码, 思考"它是直接解决这个目标吗",还是"解决一个子问题来达到目标的解决"
- 如果是解决子问题,并且代码的行数也足够多,那么就可以抽取出一个独立的函数
// Return which element of 'array' is closest to the given latitude/longitude.
// Models the Earth as a perfect sphere.
var findClosestLocation = function (lat, lng, array) {
var closest;
var closest_dist = Number.MAX_VALUE;
for (var i = 0; i < array.length; i += 1) {
// Convert both points to radians.
var lat_rad = radians(lat);
var lng_rad = radians(lng);
var lat2_rad = radians(array[i].latitude);
var lng2_rad = radians(array[i].longitude);
// Use the "Spherical Law of Cosines" formula.
var dist = Math.acos(Math.sin(lat_rad) * Math.sin(lat2_rad) +
Math.cos(lat_rad) * Math.cos(lat2_rad) *
Math.cos(lng2_rad - lng_rad));
if (dist < closest_dist) {
closest = array[i];
closest_dist = dist;
}
}
return closest;
};
// Good : clear, easy to test spherical_distance in isolation
var spherical_distance = function (lat1, lng1, lat2, lng2) {
var lat1_rad = radians(lat1);
var lng1_rad = radians(lng1);
var lat2_rad = radians(lat2);
var lng2_rad = radians(lng2);
// Use the "Spherical Law of Cosines" formula.
return Math.acos(Math.sin(lat1_rad) * Math.sin(lat2_rad) +
Math.cos(lat1_rad) * Math.cos(lat2_rad) *
Math.cos(lng2_rad - lng1_rad));
};
var findClosestLocation = function (lat, lng, array) {
var closest;
var closest_dist = Number.MAX_VALUE;
for (var i = 0; i < array.length; i += 1) {
var dist = spherical_distance(lat, lng, array[i].latitude, array[i].longitude);
if (dist < closest_dist) {
closest = array[i];
closest_dist = dist;
}
}
return closest;
};
* 纯粹的Utility代码 (比如操作字符串,hash表,读写文件)
这类代码最好封装到一个独立的Util类或者函数,(ReadFileToString())
* 其他通用的代码
ajax_post({
url: 'http://example.com/submit',
data: data,
on_success: function (response_data) {
var str = "{\n";
for (var key in response_data) {
str += " " + key + " = " + response_data[key] + "\n";
}
alert(str + "}");
});
}
// Continue handling 'response_data' ...
// Good : 函数名更容易定位,更容易维护
var format_pretty = function (obj) {
var str = "{\n";
for (var key in obj) {
str += " " + key + " = " + obj[key] + "\n";
}
return str + "}";
};
// Better : 功能更强大
var format_pretty = function (obj, indent) {
// Handle null, undefined, strings, and non-objects.
if (obj === null) return "null";
if (obj === undefined) return "undefined";
if (typeof obj === "string") return '"' + obj + '"';
if (typeof obj !== "object") return String(obj);
if (indent === undefined) indent = "";
// Handle (non-null) objects.
var str = "{\n";
for (var key in obj) {
str += indent + " " + key + " = ";
str += format_pretty(obj[key], indent + "
") + "\n";
}
return str + indent + "}";
};
* 创造更多通用功能的代码
像之前的ReadFileToString(),format_pretty()都可以跨工程使用。
* 工程特有的功能 (即使如此,依然可以考虑创建在工程内部使用的util类/函数)
* 简化现有的接口 (用到某个第三方的库,它的接口不好用,可以考虑对它封装一下)
- Simplifying an existing interface
- Reshaping an interface to your needs
- Don't take things too far (不要将一个函数实现细分成太多的小函数,要控制粒度)
一次只做一件事情(chap 11, one task at a time)
0. 一个代码片段只做一件事情 (空行风格的代码段)
示例1
// Bad : two things doing together (parseValue, computeScore)
var vote_changed = function (old_vote, new_vote) {
var score = get_score();
if (new_vote !== old_vote) {
if (new_vote === 'Up') {
score += (old_vote === 'Down' ? 2 : 1);
} else if (new_vote === 'Down') {
score -= (old_vote === 'Up' ? 2 : 1);
} else if (new_vote === '') {
score += (old_vote === 'Up' ? -1 : 1);
}
}
set_score(score);
};
// Good : clear to understand
var vote_value = function (vote) {
if (vote === 'Up') {
return +1;
}
if (vote === 'Down') {
return -1;
}
return 0;
};
var vote_changed = function (old_vote, new_vote) {
var score = get_score();
score -= vote_value(old_vote); // remove the old vote
score += vote_value(new_vote); // add the new vote
set_score(score);
};
示例2
// Bad : 不方便扩展
var place = location_info["LocalityName"]; // e.g. "Santa Monica"
if (!place) {
place = location_info["SubAdministrativeAreaName"]; // e.g. "Los Angeles"
}
if (!place) {
place = location_info["AdministrativeAreaName"]; // e.g. "California"
}
if (!place) {
place = "Middle-of-Nowhere";
}
if (location_info["CountryName"]) {
place += ", " + location_info["CountryName"]; // e.g. "USA"
} else {
place += ", Planet Earth";
}
return place;
// Good
var town = location_info["LocalityName"]; // e.g. "Santa Monica"
var city = location_info["SubAdministrativeAreaName"]; // e.g. "Los Angeles"
var state = location_info["AdministrativeAreaName"]; // e.g. "CA"
var country = location_info["CountryName"]; // e.g. "USA"
// Start with the default, and keep overwriting with the most specific value.
var second_half = "Planet Earth";
if (country) {
second_half = country;
}
if (state && country === "USA") {
second_half = state;
}
var first_half = "Middle-of-Nowhere";
if (state && country !== "USA") {
first_half = state;
}
if (city) {
first_half = city;
}
if (town) {
first_half = town;
}
return first_half + ", " + second_half;
// Better : 利用js语法的a||b||c的特性
var first_half, second_half;
if (country === "USA") {
first_half = town || city || "Middle-of-Nowhere";
second_half = state || "USA";
} else {
first_half = town || city || state || "Middle-of-Nowhere";
second_half = country || "Planet Earth";
}
return first_half + ", " + second_half;