断舍离 ——《代码整洁之道》读书笔记

注1:只看了书的前十章
注2:原书使用的语言为 Java,我改成了 JavaScript

第一章 为什么要整洁代码


1、代码永不消失

代码就是衔接人脑理解需求的含糊性机器指令的精确性的桥梁。哪怕未来会有对现在高级编程语言的再一次抽象——但这个抽象规范自身仍旧是代码。

所以既然代码会一直存在下去,且自己都干了程序员这一行了,就好好的对待它吧。

2、读远比写多

当你录下你平时的编码的过程,回放时,你会发现读代码所花的时间远比写的多,甚至超过 10:1。所以整洁的代码会增加可读性

3、对抗拖延症——稍后等于永不

糟糕的代码会导致项目的难以维护。而当你正在写糟糕的代码的时候,心里却圣洁的想:“有朝一日再回来整理”。但现实是残酷的,正如勒布朗(LeBlanc)法则:稍后等于永不(Later equals never)

4、精益求精

写代码就跟写文章一样,先自由发挥,再细节打磨。追求完美

大师级程序员把系统当作故事来讲,而不是当作程序来写。

第二章 命名


1、名副其实

(×)图表:mapTable

(√)图表:chart

推荐一个根据中文意思帮你起英文变量名的网址:http://unbug.github.io/codelf/
2021-04-06-22-51-51

2、避免误导

(1)不要用专有名词

const var = 0;

(2)避免细微之处有不同

XYZControllerForEfficientKeepingOfStrings

XYZControllerForEfficientHoldingOfStrings

2021-04-06-22-51-54

(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"};
    } 
}
posted @ 2018-09-22 14:09  小蒋不素小蒋  阅读(1057)  评论(0编辑  收藏  举报

ICP证:沪ICP备20014317号