断舍离 ——《代码整洁之道》读书笔记
注1:只看了书的前十章
注2:原书使用的语言为 Java,我改成了 JavaScript
第一章 为什么要整洁代码
1、代码永不消失
代码就是衔接人脑理解需求的含糊性和机器指令的精确性的桥梁。哪怕未来会有对现在高级编程语言的再一次抽象——但这个抽象规范自身仍旧是代码。
所以既然代码会一直存在下去,且自己都干了程序员这一行了,就好好的对待它吧。
2、读远比写多
当你录下你平时的编码的过程,回放时,你会发现读代码所花的时间远比写的多,甚至超过 10:1。所以整洁的代码会增加可读性。
3、对抗拖延症——稍后等于永不
糟糕的代码会导致项目的难以维护。而当你正在写糟糕的代码的时候,心里却圣洁的想:“有朝一日再回来整理”。但现实是残酷的,正如勒布朗(LeBlanc)法则
:稍后等于永不(Later equals never)
4、精益求精
写代码就跟写文章一样,先自由发挥,再细节打磨。追求完美。
大师级程序员把系统当作故事来讲,而不是当作程序来写。
第二章 命名
1、名副其实
(×)图表:mapTable
(√)图表:chart
推荐一个根据中文意思帮你起英文变量名的网址:http://unbug.github.io/codelf/
2、避免误导
(1)不要用专有名词
const var = 0;
(2)避免细微之处有不同
XYZControllerForEfficientKeepingOfStrings
XYZControllerForEfficientHoldingOfStrings
(3)避免废话
如果是一个数组类型,就没必要叫 ProductArray
如果返回值就是数据,就没必要叫 ProductData
(4)便于搜索
(×)
if ( team.length === 3 )
(√)
const MAX_NUM_OF_TEAM = 3 ;
……
if ( team.length === MAX_NUM_OF_TEAM )
MAX_NUM_OF_TEAM 比 3 好检索
早期流行一种
匈牙利语标记法
:如 arru8NumberList 的前缀 "arru8" 表示变量是一个无符号8位整型数组;
3、避免思维映射
类名、参数名用名词:
member
leader
方法名用动词:
getTeam
根据 Javabean 的标准,方法名可以加上 get set is 前缀
4、每个概念用一个词
get fetch 选一个,不要混着用
5、添加有意义的语境
如果你要记录 member 的详细住址,会设置了如下几个变量:
(×)
addrStreet addrHouseNumber addrCity addrState
(√)
new Address {
street,
houseNumber,
city,
state
}
第三章 函数
尽量短小
函数的缩进层级尽量控制在 1-3 层
例如要依次读取 A、B、C 三个文件:
(×)
function test() {
readFileA(function (err, data) {
// todo
readFileB(function (err, data) {
// todo
readFileC(function (err, data) {
// todo
//……
});
});
});
}
(√)
function test() {
try {
let re_a = await readFileA();
let re_b = await readFileB();
let re_c = await readFileC();
} catch (err) {
}
}
只做一件事
判断标准:是否可以再拆出一个函数
(×)
function test() {
//......
Session.save();
//......
}
(√)
function test() {
//......
saveSession();
//......
}
function saveSession(){
Session.save();
}
每个函数即是一个抽象层,如上面的例子,Session 跟 test 函数不是一个抽象层,所以抽离出来。
函数名代替注释
长且具有描述性的名字比描述性的长注释好
(×)
//取出所有满员的团
getSpecialGroup()
(√)
getGroupOfFullMember()
参数顺序太多记不住怎么办?
方法一:体现在函数名上
(×)assertEqual(expected, actual)
(√)assertExpectedEqualsActual(expected, actual)
现代 IDE 已经具有鼠标移在调用函数名上可以浮窗显示形参列表了。
方法二:让参数可以打乱传
function getMember({isNew = false,isActivate = false}){
console.log("isNew:"+isNew,", isActivate:"+isActivate)
}
getMember({isNew:true,isActivate:false}) //isNew:true , isActivate:false
//不会因为传参的顺序记错而出错
getMember({isActivate:false,isNew:true}) //isNew:true , isActivate:false
//也支持默认参数
getMember({}) //isNew:false , isActivate:false
这里用到了 ES6 的新特性:
解构赋值
let obj = {a:1,b:2}; let {a,b} = obj; // a = 1, b = 2
方法三:减少参数
最理想的参数数量 < 3,最好不用输入参数。
A、布尔值参数 一拆二
(×)getMember(isNew)
(√) getNewMember() getOldMember()
这里的前提是获取新、老会员的方法体代码不一样,不然还是共用在一个方法通过布尔值比较好。
B、二元函数变一元
function addSuffix(origin,suffix){
return origin+"+"+suffix;
}
console.log(addSuffix("hello","world"));
console.log("hello".addSuffix("world"));
异常代替返回错误码
见我另一篇 《 如何做好错误处理?(PHP篇)》 里面有体现
抽离 try / catch 块
见我另一篇 《 如何做好错误处理?(PHP篇)》 里面有体现
避免重复
否则得修改多处地方
结构化编程
(1)每个函数都应该有一个入口和一个出口
关于(只能有一个 return 语句,在结尾处
/ 尽快 return,即有多个 return 语句
)的争论:
《结构化编程》的建议:
function test(is) {
let result;
if(is){
result = true;
}else{
result = false;
}
return result;
}
《重构》的建议:
function test(is) {
if(is){
return true;
}else{
return false;
}
}
这两者写法现在仍有争议,详细的讨论可以点击:https://stackoverflow.com/questions/36707/should-a-function-have-only-one-return-statement
(2)循环中尽量避免有 break 或 continue ,而且决不能出现 goto 语句。
第四章 注释
尽量用代码表达,而不是用注释
就像上文提到的用详尽的函数名代替注释,或者:
//代码过于追求简洁而导致这里要加详细的注释
if( smodule.getDependSubsystems().contains(subSysMod.getSubSytem()) )
//还不如这里做拆分,取易懂的变量名方便理解,就可以不用加注释或者少加
ArrayList moduleDependees = smodule.getDependSubsystems();
String ourSubSystem = subSysMod.getSubSystem();
if( moduleDependees.contains(ourSubSystem) )
原因是:注释存在的越久,随着代码的不断迭代,会离代码的距离越来越远,这个时候好的做法是同时维护代码 + 注释,而注释越多越复杂,维护的成本自然就上升了。
注释不能美化糟糕的代码,尽量去优化代码
好注释
(1)法律信息、许可证、版权、著作权、外链文档的 url
(2)对意图的解释
(3)警示
(4)TODO 注释
坏注释
(1)只有自己看得懂
(2)多余的注释
a、不能提供比代码更多的信息
b、读的时间比直接看代码还久
(3)歧义性
(4)循规蹈矩的注释
例如用第三方工具生成的注释,很多都是累赘的废话
(5)日志式、署名式注释
(×)
// write by colin
// fix #201 bug
(√)
交给 git 记录
(6)慎用位置标记
// **********************
及时清理不需要的注释
(1)越堆越多
(2)导致以后因看不懂而不敢删
第五章 格式
向报纸学习
(1)从上往下读,从左往右读
(2)源文件在最顶端给出高层次的概念和算法,细节往下逐次展开,直到最底层的函数和细节。
垂直格式
(1)善用空白行:人会更容易将目光聚焦到空白行之后的那一行
(2)函数:调用者放在被调用者上面
横向格式
(1)缩进
(2)IDE 中不会出现横向滚动条
第六章 对象和数据结构
数据结构
和对象
的区别
数据结构暴露其数据,没有提供有意义的函数。
对象把数据隐藏在抽象之后,暴露操作数据的函数。
//数据结构
function Point(x,y){
this.x = x;
this.y = y;
}
//对象
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
getX() {
return this.x;
}
setX(x) {
this.x = x;
}
}
ES6暂不提供私有变量(虽然有提案),但可以通过其他办法变相实现:https://juejin.im/entry/572c0b2d2e958a00667a081d
第七章 错误处理
使用异常而非返回码
(上文有述)
定义异常类
如引用了一个第三方库,它会 throw 自己的几种异常值,但我们可以定义一个异常类,封装好它的几种异常值为一种专门异常,然后再二次 throw 交给上层捕获。
别返回 null 值
null 值会造成很多不必要的 if 判断
function getCurrentMember(){
let a = DB.getCurrentMember();
if (a){
return a;
}else{
return null;
}
}
方法一:抛异常
function getCurrentMember(){
let a = DB.getCurrentMember();
if (a){
return a;
}else{
throw Error("no member")
}
}
方法二:返回特例值
function getCurrentMember(){
let a = DB.getCurrentMember();
if (a){
return a;
}else{
return {};
//return {name : "default"};
}
}