基于《编写可维护的 JavaScript》的小组 -编程风格-讨论稿

169 阅读8分钟

文章由于要拿来讨论制定小组开发规范和用于特定的场景,会比较“详尽",没有相应团队需求的掘友,节省时间,可以到别处掘金了。

随着组内新的小伙伴的加入,大家的编码风格又有向单兵作战的方向“进化”的趋势,重读一下《编写可维护的 JavaScript》,结合大家的习惯,提出一些可以讨论的点(文中用标出),和应该遵循的规范(👌标出),供小组讨论。特别同意作者提出的编码规范如此重要的原因。

  • 软件生命周期中80%的成本消耗在了维护上。
  • 几乎所有的软件维护者都不是它的最初作者。
  • 编码规范提高了软件的可读性,它让工程师能够快速且充分的理解新的代码。

基本的格式化

缩进层级(👌Tab键配置4空格缩进)

  1. 缩进方式-建议制表符缩进。缩进方式有制表符缩进和空格缩进,可配置敲击Tab键插入对应数量空格,操作方便。
  2. 缩进数量-建议制表符配置四空格缩进。当前常用的编辑器默认4空格缩进,也可配置敲击Tab键插入四个空格。四空格缩进有相对良好的代码呈现,并且兼顾8空格缩进和2空格缩进的伙伴。

👮‍♀️坚决避免单文件混用缩进方式。👮‍♀️

语句结尾( 使用分号)

有赖于分析器自动分号插入机制(Automatic Semicolon Insertion, ASI),通常情况下可以正常工作,但ASI补全规则复杂,特定情况下容易出错,因此建议不省略分号。

以下代码会打印 undefined,不符合函数返回一个包含数据的对象的本意。

// 原始代码
const asi = () => {
    return
    {
        intent: "收银设备损坏怎么办",
        id: 1
    };
}
console.log(asi()); 

// 分析器分析后代码
const asi = () => {
    return;
    {
        intent: "收银设备损坏怎么办",
        id: 1
    };
}
console.log(asi());

行长度(👌80个字符)

由于编辑器宽度限制,单行代码过长,要么折行,要么被隐藏起来需要滚动滚动条才能看到,更不友好的是折行设置不同对代码可读性造成影响。

语言单行限制字数
Android100
Java80
Ruby80

Crokford 的JavaScript代码规范中指定一行80

适当使用空行( 结合当前业务逻辑,什么情况下使用空行)

对于空行的使用没有一个标准,只有一些建议:

  • 在方法之间
  • 在方法中的局部变量和第一条语句之间。
  • 在多行或者单行注释之前。
  • 在方法内的逻辑片段之间,提高可读性。

命名(👌驼峰式、合理使用动或名词、常量特殊命名)

"驼峰式大小写“有两种-Camel Case (首字母小写式驼峰命名) 和 Pascal Case(首字母大写式驼峰命名)。

变量和函数

变量名应当遵循因当遵循驼峰命名法,命名前缀应该是名词,用以区分函数,函数前缀应该是动词

let intentName = "收银设备损坏怎么办";
let found = true;

function getIntentName() {
    return intentName;
}

命名通常要短并抓住要点,尽量体现出值的类型,如,命名count、length和size表明值类型是数字,命名name、title和message表明值类型是字符串。

对于函数和方法命名,第一个单词应该是动词,有如下一些常见约定。

动词含义
can返回Boolean
has返回Boolean
is返回Boolean
get返回非布尔值
set保存一个值
if (hasIntent) {
    setIntentName("收银设备损坏怎么办");
}
if (getIntentName === "收银设备损坏怎么办") {
    doSomething();
}

常量

js 的常量命名约定源自于C语言,使用下划线和大写字母来命名,下划线用一份个单词。

let MAX_COUNT = 100;
let BASE_URL = "https://www.guoran.com";

构造函数

沿用语言内置的构造函数的命名方法-大写式驼峰命名,比如Object、RegExp。

直接量(👌精确使用一些类型原始值)

  • 字符串:推荐双引号。
  • 数字:十进制有小数部分,要完整写整数和小数部分,避免使用八进制。
  • null:用来初始化一个变量,这个变量可能赋值为一个对象,当函数参数期望是对象时,用作参数传入,当函数的返回值期望是对象时,用作返回值传出。
  • undefined: 表示变量声明等待被赋值。

null使用时应该将它当做对象的占位符。将变量初始值赋值为null表明这个变量的意图,它最终可能赋值为对象。typeof运算符null的类型时返回”object“,区分undefined。

let intent = null;
let skill;

typeof(intent); // object
typeof(skill); // undefined

注释

什么情况下使用注释( 当代码不够清晰时)

注释并不是越多越好,简单,对阅读者不会产生歧义的代码不需要写注释。注释应该被加在必要的地方。

  • 难于理解的代码。
  • 可能被误认为错误的代码。
  • 浏览器特性hack。
  • 特殊含义的常量。
  • 只应用于当前业务逻辑。

注释类型(👌单行、多行)

单行注释

// 单行注释

当行注释可以独占一行,用来解释下一行的代码,在注释之前加一个空行,且缩进和下一行代码保持一致,也可以在代码的行尾注释,在代码和注释之间加一个缩进。如果注释和当前行代码数量超过了80个字符,则使用第一种方式注释。

if (condition) {

    // 符合条件
    doSomething();
}

多行注释

多行注释之前应当有一个空行。

doSomething()

/*
 * 多行注释第一行
 * 多行注释第二行
 */

👮‍禁止在行内使用多行注释👮‍

语句和表达式

所有的块状语句都应该使用花括号,包括 if、for、while、do...while,特别是if语句省略花括号,时常会造成困惑,代码的优雅并不在于看起来简洁,而在于带来更好的可读性,造成更少的困惑。推荐块状语句之前加空行。

// 容易造成困惑的代码
if (condition)
    // 符合条件
    doSomething();
    doSomethingElse();

// 好的写法
if (condition) {
    doSomething();
}
doSomethingElse();

花括号的对齐方式(👌左花括号放在块语句中第一句代码末尾)

if (condition) {
    doSomething();
} else {
    doSomethingElse();
}

块语句间隔(👌采用Crockford推荐方式)

在左括号之前和右括号之后添加一个空格。

if (condition) {
    doSomething();
}

switch 语句( 关于连续执行和default)

在逻辑和结构清晰地情况下可以使用连续执行,特别是有注释标明的情况下。对于default语句,作者的建议是如果default内没有逻辑,可以省略,我倾向于default为switch语句的一部分。

switch (condition) {
    case 'first':
        // 逻辑代码
        break;

    case 'second':
        // 逻辑代码
        break;

    default: // 没有逻辑,但是没有省略,倾向于default为switch语句的一部分
        break;
}

for循环(👌关于更改循环进程的建议)

有两种方法可以更改循环执行过程(除了使用return和throw语句)。第一种方式是使用break,不管所有的循环迭代有没有执行完毕,使用break总是可以跳出循环,第二种是continue,可以立即退出本次循环,进入下一次循环, continue由于部分js检测工具会警告,建议用if替代

let array = [1, 2, 3, 4, 5, 6, 7];

// break
for (let i = 0; i < array.length; i++) {
    if (i === 2) {
        break;
    }
    console.log(array[i]); // 1 2
}

// continue
for (let i = 0; i < array.length; i++) {
    if (i === 2) {
        continue;
    }
    console.log(array[i]); // 1 2 4 5 6 7
}

// continue => if
for (let i = 0; i < array.length; i++) {
    if (i !== 2) {
        console.log(array[i]); // 1 2 4 5 6 7
    }
}

for-in 循环(👌循环中的hasOwnProperty)

for-in 循环不仅遍历对象的实例属性,同样也遍历从原型链继承来的属性,遍历从原型链继承来的属性时,往往意外终止,出于这个原因,建议使用hasOwnProperty()方法来过滤出实例属性。

let object = {
    name: 'Quasi',
    age: 18
};

for (const key in object) {
    if (Object.hasOwnProperty.call(object, key)) {
        const element = object[key];
        console.log(key, element); // name Quasi age 18
    }
}

👮‍禁止使用for-in遍历数组成员👮‍

变量、函数和运算符

变量/函数声明(👌变量声明提升)

变量声明提前意味着:在函数内部任意地方定义变量/函数和在函数顶部定义变量是完全一样的。但是为了提高可读性和兼容代码检测工具,建议总是在使用之前定义变量/函数 关于单let语句,作者建议使用单let语句,但是为了代码有可读性,我倾向于Dojo编程风格指南规定,只有在变量之间有关联时,再去使用单let语句

立即调用函数(👌圆括号包裹立即执行函数,提高可读性)

const doSomething = (() => {
    // 逻辑代码
})();

相等(👌谨慎使用相等强制类型转换)

比较相等时应当使用 ===和!==,避免使用 ==和!=发生强制类型转换。

// == 字符串被强制转换为数字
console.log(8 == "8") // true
console.log(8 === "8") // false

// == 布尔值强制转换为数字
console.log(true == 1) // true
console.log(true === 1) // false

// == 十六进制强制转换为十进制进行比较
console.log(25 == "0x19") // true
console.log(25 === "0x19") // false

// == null 和undefined
console.log(null == undefined) // true
console.log(null === undefined) // false