Skip to content

代码大全

书中自有黄金屋,书中自有颜如玉。

《代码大全》总共有944页内容,很实在,里面涉及到的知识点很多,主体代码是以Java和C++为主。代码形式其实都不重要,重要的是要表达的思想。对于前端工程师来说,我总结了以下几个方面的知识,开始吧。

条件语句switch用对了么

很多时候,我们在用switch语句的时候,对如何使用default,并没有一个明确的原则,有时候default下,什么都不执行,有时候default下,会去执行最后一种可能。还有有时候case的情况很多,我们并没有在意排序问题。等等其他,其实这在编程中都是一种不好的行为。那么来,怎样才是正确编写switch语句的姿势呢?请看如下:

tip1——让default干它该干的事情

原则上,default下,不要写最后一种可能,因为default设计出来的目的,其实是为了捕捉错误的。所以业务中出现的所有情况都应该用case去捕捉,这也体现了使用case后面的label去说明这是哪种情况的作用。而default后面无法加label说明,也就失去了对可能情况的说明。举个栗子,在前端中,如果对请求返回的状态码进行判断,可以写成如下形式:

js
switch(code) {
  case: '1':
    console.log('成功')
    break
  case: '0':
    console.log('失败')
    break
  default:
    dealError()
}

从上面代码可以看出,default不要做任何有正常case的操作,它专门用来和检测和处理错误,当然如果业务中把某些case都当成错误的话,也可以统一写到default中。

tip2-让case变的简洁、有序和高效

1: 如果case的情况很多,那就要按照出现频率去从上到下排列,这样switch语句代码的整体效率会提高。

2: 如果case的一个情况,语句太多,那就果断封装成子程序来调用。避免在一个case下写很多语句,让case变得难以理解。

如何判断代码的复杂度

大家可能会本能的想到大O方法啥的,对前端来说,大O那种模式不适用,大O是衡量算法的复杂度。全全里面提到了一个很有趣的判断复杂度的方法,叫做 Tom McCabe 方法,该方法很简单,通过计算函数中 决策点 的数量来衡量复杂度。下面是一种计算决策点(结合前端)的方法:

  1. 1开始,一直往下通过函数
  2. 一旦遇到if while for else或者带有循环的高阶函数,比如forEach map等就加1
  3. 给case语句中的每一种情况都加1

比如下面代码:

js
function fun(arr, n) {
  let len = arr.length
  for (let i = 0; i < len; i++) {
    if (arr[i] == n) {
        // todo...
    } else {
        // todo...
    }
  }
}

按照Tom McCabe方法来计算复杂度,那么这个fun函数的决策点数量是 3 。知道决策点数量后,怎么知道其度量结果呢?这里有一个数值区间判断:

数量区间度量结果
0-5这个函数可能还不错
6-10得想办法简化这个函数了
10+把这个函数的某一部分拆分成另一个函数并调用他

从上面的判断依据可以看出,我上面写的代码,数量是3,可以称为函数还不错。我个人觉得这个判断方法还是很科学和简单的,可以作为平时写代码时判断函数需不需要重构的一个考虑点,毕竟一个函数,一眼扫去,决策点数量大致就知道是多少了,很好计算。

你心中的抽象和封装以及模块是什么?

怎么说呢,其实有些时候,我们不知道,不代表我们不会,很多时候是这种情况:我真的看过这方面的知识或者我确实真的研究过这些,但是确实是忘了😂。所以此刻,你看到这里的时候,就赞赞收藏吧(嘻嘻)。

我觉得这种偏概念的东西,每个人都可以回答的不同,但是只要你真正理解了其中的本质或者说是一种答案,其实这就够了。这里我说一下我自己对抽象和封装以及模块的看法。

抽象在我眼里,其实是把混乱变成有序,把零散变成整体。我们经常说,要把业务代码抽象成组件,做成组件化。其实就是把零散变成整体,把混乱变成有序的过程。举个荔枝:

比如说我们项目中还没有做组件化,弹窗这个功能,还是各种页面里写自己的弹窗代码。现在我们需要重构,把这个零散和混乱的弹窗,抽象成一个整体有序的弹窗组件。那么,我们来看看原来的弹窗代码,发现以下问题:

  1. 代码重复,同时零散分布在各个页面中。
  2. 代码混乱,没有统一的入口,没有统一的显示逻辑。

如何解决呢,这个时候就需要对弹窗进行抽象了,如何抽象,一个办法,就是解决掉重复和混乱,如果解决掉了,就可以表示抽象已经初见成效了。

这里提一下ADT,这是面对对象的编程模式中的类的基础,叫 Abstract Data Type ,也就是抽象数据类型,但是我不想用这个,这里我借鉴DOMBOM形式,我把它叫做 ADM。 全称:Abstract Data Model 也就是抽象数据模型。不知道可不可以这样玩,但是我觉得问题不大,嘻嘻。没有意见,那下面我继续操作了。

我们需要把弹窗可能用到的数据全部收集起来,然后将这些数据归属于一个对象模型,为了形象比喻,我把它叫做弹窗人,拥有生命。这个弹窗人的实体是由这些数据组成的,OK,这样就把之前零散的数据全部集合在一个实体上了,也就是把零散变成整体。请看下面这句话:

没有封装时,抽象往往很容易打破。

如果我只是抽象了,把零散变成整体了,但是却没有把混乱变成有序,那么抽象就会被打破,就好比弹窗人拥有的数据,这里我花式比喻一下。比如弹窗人拥有100万,关键100万还是公开的,结果悲剧了。

很多人都想向他借钱,如果弹窗人事先不指定好借钱的方式。那么来就会很刺激了,朋友A微信问他借钱,朋友B支付宝向他收款,社会混子直接电话向他威胁借钱,更有牛逼的,直接强行把他绑架了,然后刀光剑影伺候。弹窗人心里苦啊,难受。

终于弹窗人大悟了,事先指定了借钱的方式,只能先通过短信,其他借钱方式一律拒绝,并且要经过他的同意,同时坚决不公开自己的财产。从此以后,没有人知道弹窗人有多少钱,还以为是个穷逼(弹窗人是程序员),就算想借钱,也只能通过短信告诉弹窗人我想借多少钱,然后同意后,才借给你。从此,弹窗人过上了幸福快乐的生活了。

上面是即兴写出来的栗子。忽然发现我还挺有想象力的。给自己 0110 0110 0110,其实你读完大概就知道我想表达什么思想了。这就是封装的目的,不仅是前端领域封装的目的,同时也是所有面对对象领域封装的目的。如果有例外,,捂嘴,,没有。。

从上面我们可以看出,抽象和封装存在密不可分的联系。仔细去体味上面的故事,你会发现我们在让混乱变成有序的的时候,其实是在让可访问性变得尽可能的低,也就是封装本身的原则:

封装的原则是让可访问性尽可能的低。

很多人虽然知道封装是为了降低可访问性,但是却不知道为什么,可能也知道为什么吧?这里中断一下,跳个番外。

在贵(程序员)圈里有个很好玩的现象,那就是我知道某一个知识的目的,但是却说不清原理,或者说我也查过,试图去了解这个原理,但是总是会有一种朦朦胧胧的不懂感围绕着自己。然后心里 ob :先就这样吧,后面再说😂。然后就不能透彻的理解。我咋知道这些的,因为我有时也有这种感觉,感觉是无法避免的。能做的就是在日后别忘了把当时朦朦胧胧的困惑解决掉。

中断结束,继续胡诌🙂,我有多少钱你不能知道,知道的话,我的钱就会变得不安全。你只能通过固定的渠道来访问我,还不能直接访问数据,需要经过我的同意,才能进行相应的操作。其实可访问性这个已经解释的很透彻了。好了,不解释了。


那么前端的模块化是什么意识呢 ?

其实完全可以理解为组件化。模块化,顾名思义想表达的就是想要整体的意识,既然整体,那就要封装,封装前就需要进行抽象,然后又回归本质。那么目前前端是怎样实现模块化的呢?

JavaScript 是以面向对象为基础的编程语言,至少有句话说的是:在JavaScript中,一切皆对象,其实可以这么说,但是JavaScript有个很尴尬的一点是,没有类,虽然现在有类,但也只是一种语法糖。这里不考虑TypeScript。那它怎么实现模块化呢?

实现编程语言中的类的抽象模型,也就是我上面说的ADM。在我看来,在不使用class语法糖的情况下,前端的模块化的实现原理就是通过使用JavaScript的私有变量特性和闭包特性来实现模块化。ES5的时候,JS是没有块级作用域的,只有全局变量和私有变量,如果连私有变量也没有的话,那JavaScrript也就GG了,所以我很好奇当初设计JS的时候,都采用了类C的语言设计风格,为什么没有实现块级作用域呢,是不是当时觉得JS并不需要这么多功能啊😂。既然有私有变量,那么就会有私有属性和私有方法,毕竟本质上都是变量。具体代码我就不贴了,写个伪代码吧:

js
function fun() {
    v1 是私有属性
    f1 是私有方法
    // 返回一个对象
    return {
      v2 是共有属性
      f2 是共有方法 {
          共有方法里面可以访问和修改 私有属性和私有方法
          处理 v1
          处理 f1
      }    
    }
}

如何编写高质量的函数

全全(指代码大全,下同)里写的是如何编写高质量的子程序,其实JS函数就是子程序(99%正确)。全全里说的高质量的一些总结有些不适用JS。 这里我融合一下自己的一些经验和总结。请往下看:

有没有想过为什么要发明函数这个东西?

作为前端工程师,对于这个问题,还是10分重视的。头偏向45度方向,在脑袋中想个10秒钟,可能还会眨几下眼睛,不要问我为什么知道你的状态,因为XX。其实大家都能说出一些自己的看法,不需要答案,但是我还是想给一个看起来逼格很高的answer。那就是:

函数是迄今为止发明出来的用于节约空间和提高性能的最重要的手段。 注意,没有之一。那么我们现在可以确定的是: 它是用来节约空间和提高性能的。 这样的话,如果我们不去优化函数,写出高质量的函数,岂不是失去了它存在的意义。现在面临的最关键的问题就是:如何去优化函数,写出高质量的函数?

js
if (str === 'hello world') {
    console.log('hello world')
} else {
    console.log('666')
}

上面是一段代码,如果在其他地方要用,就必须在用的地方把上面这段代码原封不动的重复编写一遍,但是有了函数,就可以将其放到函数内,然后通过函数print(str)来调用,在其他地方只需要写这一行就可以了,优点很明显。

如何写出高质量的函数。其实就这一点就可以写一篇文章了,这里我不详细说明阐述了,随便提几点吧,高质量的具体实现以后再说😂。

从娃娃抓起,起一个好名字很重要

不能输出起跑线上,考虑的点有以下几点:

  1. 名字要能清晰的描述此函数的目的
  2. 统一好书写形式。比如统一使用下划线、驼峰等命名形式
  3. 统一好单词组成形式,是名词+动词,还是动词+名词

优雅的传递和处理参数

  1. 参数较多,可以使用参数对象模式
  2. 参数不固定,比如动态参数,可以使用ES6的扩展符
  3. 使用ES6的参数默认值来处理参数默认取值问题

设计函数时的一些考虑

  1. 避免函数代码重复
  2. 缩小函数代码规模
  3. 增加函数鲁棒性
  4. 注释风格要统一
  5. 尽可能的保持pure特性,也就是幂等性,这样可以最大限度的防止后期的各种莫名其妙的bug

关于跨度和生存时间

变量有一个自己的属性,叫存活时间,在编程语言中,要尽可能的缩短变量的存活时间。这样的话,如果我们用跨度和生存时间的概念来看全局变量,就会发现为什么要避免使用全局变量,因为全局变量的跨度和生存时间都很长。

彩蛋:JS 为什么要避免全局变量,其实一个主要原因是 JS 的代码执行,是由里向外的,如果是全局变量的话,那么这个查找的时间就比较长,所以在 JQuery 等源码中,直接将 window 当成参数传到函数中,是有它的道理的,可以提高代码性能。

关于布尔表达式的一个鲜为人知的点

看如下代码

js
let i = true 
if (i = true) {
  // todo...
}

可以看到,如果你在业务代码中,不小心少写=号,变成上面这段代码,那么它是不会报错的,但是要是写成这种形式:

js
let i = true
if (true = i) {
  // todo...
}

就会报错了,因为如果表达式左侧不是变量的话,解释器会报错。所以在JS编程中,可以把常量放在等号左侧。这是一个很小众的 tip,而且在前端来说,给项目加个 eslint 就可以完全避免这种错误了,如果没有写 eslint,可以考虑一下这个 tip,如果用了代码检查工具,那就当下酒菜吧。

如何修改bug、代码缺陷

  1. 修改代码时一定要有恰当的理由。理由要充分,有理有据。
  2. 一次只做一个改动

备注

  1. 设计模式这个就不总结了,东西很多,需要拿出来单独说。
  2. 一些系统级的总结也不说了,和前端关系不大。
  3. 对于递归的注意事项也不说了,前端用到递归的情况不多。
  4. 对于伪代码编程,以后单独提
  5. 对于编写高质量的函数,总结的简单,因为涉及到的东西很多,这里我更想提的是最本质的一些知识,比如被创造出来的目的,有时候你写了很多函数,都没有想过这个问题,现在看到了,心中会有种豁然开朗的感觉吧。

Released under the MIT License.