代码十诫

566 阅读8分钟

写过很多代码,知道很多语法,却依然被团队嫌弃。不知道大家有没有总结过,为什么看别人源码的时候总是觉得好像很厉害,比较优雅,看自己的或者看团队里面的就哪哪都不舒服,就觉得这**就是一坨*。其实我觉得看不舒服的原因主要就是那么几个:1.思路没理解,或者思维习惯不同 2.代码块和注释不清晰 3.缺少注释……。笔者总结了写代码中应该要注意的十个点,相信只要避免掉这十个点,大家的代码可阅读性一定会提升几个档次。这十个点我称之为代码十诫。

1.诫冗长的条件表达式

相信很多人都觉得用if else很low,于是在写条件判断的时候经常是这样的:

ariable != null ? variable.getSomething() : null;

这段代码看着其实还好,还算能理解,但是如果是这样呢(瞬间心态崩了)

x >= 90 ? "A" : x >= 80 ? "B" : x >= 70 ? "C" : x >= 60 ? "D" : "E";

是不是看了很久才明白他的意思?很多人都在追求代码的简洁,但笔者觉得其实代码这种东西首先是给人看的,其次才是给机器看。所以代码首先要能让人第一眼看得明白,写成下面这样是不是就舒服多了


if (variable != null) {
   return variable.getSomething();
}
return null;

// 第二段 
if(x >= 90){
    return 'A'
}else if(x >= 80){
   return 'B'
}else if(x >= 70){
    return "C"
}else if(x >= 60){
    return "D"
}else{
    return "E"
}

简短的条件表达式我觉得是没有问题的,但是条件多了大家一定要注意避免,真的难读,改起来分分钟出事

2.诫没有大括号的if

依然是简洁惹的祸,大家请看

if ((error = doSomething()) != 0)
    doSomething1();
    doSomething2();
doSomething3();

这段代码是不是很疑惑,咋一看条件成立就执行doSomething1,但是问题来了,如果你对于空格不那么注意,你是不是得遗漏掉第二句,以为第二句是不会执行的,结果就导致了重大bug。我们知道有时候不同的编辑器会对代码进行一些格式化,我们就会不注意到那些空格,于是代码的逻辑我们就比较难理解了,但是如果我们加上大括号是不是很多了?

if((error = doSomething()) != 0){
     doSomething1();
}
doSomething2();
doSomething3();

同理,这样的代码也是应当避免的

if((error = doSomething()) != 0) doSomething1();
3.诫不加思考的无效命名

大家看一下这段代码

let name1 = 'haha';
let name2 = 'hehe'
let temp1 = name1

function query(){
    for(let i = 0;i < 10; i++){
        for(let j = 0;j < 10; j++){
        }
    }
}

这种代码我想大家一定见到过不少,也很容易犯。有时候代码很难理解往往是因为没有起到一个好名字,大家在理解这段代码的时候,需要从上下文找到这个变量的定义以及使用,然后去猜测他的思路,但是如果我们能够把名字起好,其实名字就已经能够阐述我们的意思了。

let childName = 'haha';
let fatherName = 'hehe'
let newChildName = childName

function getdNewChilNameByFather(){
    for(let fatherIndex = 0;fatherIndex < 10; fatherIndex++){
        for(let childIndex = 0;childIndex < 10; childIndex++){
            /* 省略…… */
        }
    }
}
4.诫没有规范的换行缩进

这个问题比较常见,比如这样的代码:

let childName = 'haha';let fatherName = 'hehe'

    let newChildName = childName
    
function getChildNewNameByFather(){
    for(let fatherIndex = 0;fatherIndex < 10; fatherIndex++){
    for(let childIndex = 0;childIndex < 10; childIndex++){
    }
    }
}

如此缩进不规范的代码已经很少见的,毕竟编辑器基本上已经把缩进的事情给做好了,自动格式化。但是有一点需要注意的是,编辑器并不会自动帮你划分代码块,也就是换行。一段优雅的代码我们应当遵循这样的原则:1.一行代码只干一件事 2.不同的逻辑需要通过加空行来划分代码块 3.用空格来区分嵌套代码块。 所以它应该是这样的:

// 集中声明需要使用到的变量
let childName = 'haha';
let fatherName = 'hehe'
let newChildName = childName

// 用空行来区分逻辑块,空格区分嵌套逻辑
function getChildNewNameByFather(){
    for(let fatherIndex = 0;fatherIndex < 10; fatherIndex++){
        for(let childIndex = 0;childIndex < 10; childIndex++){
        }
    }
}

function getChildNewNameByFather(){
    for(let fatherIndex = 0;fatherIndex < 10; fatherIndex++){
        for(let childIndex = 0;childIndex < 10; childIndex++){
        }
    }
}
5.诫一段超长的if

诸位请看大屏幕

if(a===b && c===d || e>f || (g===h && h>10)){
    log("yes,i am very man")
}

是不是很刺激?比这种还长的都有。其实我们完全可以把条件划分成几个字条件或者抽出来做函数

function isMan(){
    const condition1 = a===b && c===d;
    const condition2 = e>f
    const condition3 = g===h && h>10
    
    return condition1 || condition2 || condition3
}

if(isMan()){
    log("yes,i am very man")
}

是不是容易读很多了,就算判断的具体条件是业务性的东西,但是我们通过if里面的函数名字就能领会到这个条件是用来判断是不是男人的。

6.诫到处都有的值常量

有时候代码是这样的

let name1 = "haha"

function useName1(){
    log(useName1)
}

let name2 = "haha"
let name4 = "haha"

function useName2(){
    log(useName1+useName2+name4)
}


let name3 = "haha"

function useName3(){
    log(useName3+name4)
}

这段代码看起来其实没啥问题,但是如果稍微长一点也是很费劲的。我们其实可以遵循一个原则去把自己的变量给管理好:统一定义在作用域范围的顶部。name1其实被useName1和useName2使用,所以他应该注册在useName1和useName2的同级作用域的上面,name4同理。name2和name3只被他们自己的函数使用,所以我们应该定义在他们的函数内部。

let name1 = "haha"
let name4 = "haha"

function useName1(){
    log(useName1)
}

function useName2(){
    let name2 = "haha"
    log(useName1+useName2+name4)
}

function useName3(){
    let name3 = "haha"
    log(useName3+name4)
}

7.诫不规范的注释

我们知道如果别人写代码能够写注释就已经是谢天谢地了,但事实上如果注释写不好,非但不会对理解代码有帮助,反而会看着更烦更不懂了。不规范的注释通常有:

// a等于b(废话注释)
if(a == b){
}


//(密集注释)
// 思路步骤1
******
// 思路步骤2
******
// 思路步骤3
******


/**
 省略一千字(超长注释)
**/


// 大段代码注释
/* * 
This is a multiple line comment.
This is a multiple * line comment. 
if (programingLanguage.equals("Java")) {
...
} */

不规范的注释有很多种,所以我这里介绍几种规范的做法

1.用有效的命名代替注释

错误
 let name1 = "hh" // 孩子名字
正确
 let childName = "hh"

2.多个单行注释换成段注释

错误
    // This is a multiple line comment. This is a multiple
    // line comment. 
    if (!myString.isEmpty()) {
    ...
    }

正确

/**
 This is a multiple line comment. This is a multiple
 line comment. 
*/
 if (!myString.isEmpty()) {
...
}

3.不要的代码直接清除
例子就不展示了,字面意思。很多人写代码的时候不舍得把没用的代码删掉,觉得以后还能用到,事实上不用了的概率极大,而且万一以后真要找回来,不还有git么?

4.在文件头部加入标准注释,包含:文件用途、版本号、创建时间、修改时间、修改人


/**
 * A {@code Readable} is a source of characters. Characters from
 * a {@code Readable} are made available to callers of the read
 * method via a {@link java.nio.CharBuffer CharBuffer}.
 *
 * @since 1.5
 */
public interface Readable {
   ...
}

5.特殊的业务规则或者处理需要写注释

// 老板说要加个延迟,方便以后显得优化性能成果显著
setTimeout(function(){
   
},5000)
8.诫三层以上的if else

比如以下代码:

if(a){
    if(b){
        if(c){
            log("领导来了,经理来了,小弟来了,全都来了")
        }else{
             log("小弟不来了")
        }
    }else{
        log("领导来了,经理不来")
    }
}else{
     log("领导不来,其他人也不敢来")
}

大家可以看到这样的代码实际上非常得不美观,嵌套多不好理解。事实上这样的代码我们可以把条件扁平出来办法降低嵌套,比如

if(!a){
     log("领导不来,其他人也不敢来")
}
if(a && !b){
    log("领导来了,经理不来")
}
if(a && b && c){
    log("领导来了,经理来了,小弟来了,全都来了")
}
if(a && b && !c){
   log("小弟不来了")
}

这个例子也许还不能体现出他真正的变化,因为逻辑比较短且简单。但是如果逻辑比较复杂,大家一定会被这种嵌套绕得晕头转向。顺便说一句,还有一种常见的if是这样的:

if(a){
    // 此处是几十行代码
    if(b){
        // 此处又是几十行代码
    }
    return true
}else{
    return false
}

阅读者看到这样的代码会非常迷糊,因为它往往需要记住你当前在哪个嵌套中。事实上如果我们可以先处理短的逻辑直接先return掉往往会收得奇效

if(!a){
    return false
}
// 此处是几十行代码
if(b){
   // 此处又是几十行代码
}

return true

9.诫超出八十行的函数
这个也是大家比较容易出现的问题,一个函数无比的长,做了123456789件事。这样的代码看起来容易让人难以把握整理上的思路。通常一个函数的代码长度不应该超出八十行(80行不是一个统一的标准,标准是不要超出一个屏幕的高度即可)。通常来说我们可以用这样的方式避免自己写出一个超长的函数。第一步就是把函数要做的事用大纲列举出来,把核心逻辑整理出来作为主函数,第二步就是把各个步骤根据粗粒度抽取成子函数调用。比如我们要做一件事是去到美国,那首先你需要申请护照,申请护照的细节有准备复印件、去公安局办理……然后你需要申请签证,签证步骤巴拉巴拉巴拉,然后要购买合适的机票巴拉巴拉,然后就订酒店巴拉巴拉,然后飞过去巴拉巴拉。那我们其实就可以这样写主函数

function 申请护照(){
    //省略一千字
}

function 申请签证(){
    //省略一千字
}
。。。

function 去美国(){
    申请护照()
    申请签证()
    购买机票()
    订酒店()
    坐飞机()  
}

这样子逻辑上我们只需要关注去美国这个简单的组合函数就知道整理脉络了,对于异常分支影响主逻辑的也都会有清晰的处理。

function 去美国(){
    const result = 申请护照()
    if(!result){
        log(去不成了,摆烂)
    }
    申请签证()
    购买机票()
    订酒店()
    坐飞机()  
}

10.无脑复制粘贴的重复代码

在日常开发中,无脑复制的代码相信大家一定见到过不少了,图快一时爽,维护悔断肠。在这里倡议大家对于相同的逻辑一定要抽取出来,如果有变化的部分可以作为参数进行适配。抽取代码比较简单,这里就不做示范了,介绍一种可以统计分析项目重复代码的工具:jscpd。详细介绍可以参考www.npmjs.com/package/jsc…
这里简单介绍一下使用方式

Installation
$ npm install -g jscpd
Usage
$ npx jscpd /path/to/source

运行完之后可以在控制台看到重复率,也可以选择输出html的方式更加直观地查看重复代码,对于重构优化项目非常有帮助。