概述

规范的代码,是项目质量的保障。本代码规范从typescript、 javascript、css、vue 等几个方面,对日常开发中的代码编写,给出了规范性的指导与建议,能有效避免代码级别的缺陷与错误;也可依据本规范,在代码审查中,对不符合规范的代码提出问题。

优先级说明

A:必要的

非特殊情况下,必须遵守的规范,通常这些规范会帮你避免错误。

B:强烈推荐的

被大量使用的规则,大多数都能改善代码可读性与开发体验,但即使违反,也不会导致错误。

C:谨慎使用的

使用起来有风险的规范,这些规范可能会解决一些比较复杂的问题,但同时也会带来潜在的风险,当你编写此类代码时,需要仔细思考可能存在的问题。

命名

对文件、目录的命名进行规范。

(A)项目

全部小写,多个单词以下划线分隔。

// bad
cisdiPlatform
CisdiPlatform

// good
cisdi_platform

(A)目录、文件夹

参照项目命名,有复数结构时,使用复数命名法。

(A)javascript 文件

参照项目命名。

(A)css、less、scss 文件

参照项目命名。

(A)html 文件

参照项目命名。

(A)vue 文件

使用 PascalCase 命名规则。

// bad
headerTitle.vue
header_title.vue

// good
HeaderTitle.vue

JAVASCRIPT

javascript 代码规范的详细说明。

(A)使用 let、const,不使用 var

// bad
var bar = 1;
var PI = 3.14;

// good
let bar = 1;
const PI = 3.14;

参考:

(A)变量命名

  • 变量使用 camelCase 命名;
  • 常量全大写,多个单词以下划线连接;
  • 使用英文,不使用拼音命名;
// bad
let table_data;
// good
let tableData;

// bad
const one_day = 24 * 3600 * 1000;
const oneDay = 24 * 3600 * 1000;
// good
const ONE_DAY = 24 * 3600 * 1000;

// bad
let shijian;
// good
let time;

(A)数组、对象格式

  • 对象的属性名不要加引号,必须加引号的情况除外;
  • 对象只有一个属性的,可以写在一行,超过一个属性的,每个属性换行书写并有缩进;
  • 数组、对象的最后一项不要逗号;
// bad
let foo = { "a": 1 };
let bar = { a: 1, b: 2 };
// good
let foo = {
    a: 1,
    'prop-name': 'name'
};

(A)对象、数组浅拷贝时,更推荐使用扩展运算符

// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 });
delete copy.a; 

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

// good 
const original = { a: 1, b: 2 };
// 浅拷贝
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }

// rest 解构运算符
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
// bad
const len = items.length;
const itemsCopy = [];
let i;

for (i = 0; i < len; i += 1) {
  itemsCopy[i] = items[i];
}

// good
const itemsCopy = [...items];

(A)关键字后必须有大括号

下列关键字后必须有大括号(即使代码块的内容只有一行):if, else, for, while, do, switch, try, catch, finally, with。

const condition = Math.random() > 0.5;
// bad
if (condition) return;

// good
if (condition) {
    return;
}

(A)undefined 的使用

禁止直接使用 undefined 进行变量判断;使用 typeof 和字符串’undefined’对变量进行判断。

let name;
// bad
console.log(name === undefined);

// good
console.log(typeof name === 'undefined');

(A)for-in

使用 for-in 时,一定要有 hasOwnProperty 的判断。

let foo = {
    a: 1,
    b: 2
};
foo.__proto__.c = 3;
// bad
for (let item in foo) {
    console.log(item);
}
// 1,2,3

// good
for (let item in foo) {
    if (foo.hasOwnProperty(item)) {
        console.log(item);
    }
}
// 1,2

(A)函数参数不能超过 3 个

超过 3 个的情况使用对象的方式进行传递。

// bad
const foo = (a, b, c, d) => {
    console.log(a, b, c, d);
};

// good
const foo = args => {
    const { a, b, c, d } = args;
    console.log(a, b, c, d);
};

(A)禁止全局变量、禁止污染全局变量

// bad
bar = 1;
window.bar = 1;

// good
const bar = 1;

(A)使用 class 代替 function

// bad
function User(name) {
    this.name = name;
}
User.prototype.hello = function() {
    console.log(this.name);
};

// good
class User {
    constructor(name) {
        this.name = name;
    }

    hello() {
        console.log(this.name);
    }
}

(A)使用 Promise 代替回调函数

// bad
require('request').get('api_url', function(err, response) {
    if (err) {
        console.error(err);
    } else {
        require('fs').writeFile('article.html', response.body, function(err) {
            if (err) {
                console.error(err);
            } else {
                console.log('File written');
            }
        });
    }
});

// good
const request = require('request-promise').get('api_url');
request.then(response => {
    console.log(response);
});

(A)注册事件与卸载事件的代码必须同时存在

// bad
mounted() {
    window.addEventListener('resize', resizeHandler);
}

// good
mounted() {
    window.addEventListener('resize', resizeHandler);
},
beforeDestroy() {
    window.removeEventListener('resize', resizeHandler);
}

(A)避免无意义的条件判断

优先使用 ||&& 运算符。

// bad
function createMicrobrewery(name) {
    let breweryName;
    if (name) {
        breweryName = name;
    } else {
        breweryName = 'Hipster Brew Co.';
    }
}

// good
function createMicrobrewery(name) {
    let breweryName = name || 'Hipster Brew Co.';
}

(A)使用模板字符串

避免直接拼接字符串带来的代码可读性降低、易出现 bug 等问题。

const date = '2011-01-01';
const time = '12:00';
// bad
const fullTime = date + ' ' + time;

// good
const fullTime = `${date} ${time}`;

(A)在最后的审查中,不得出现无效的、过时的代码、注释或文件

所有对代码的修改都应该可以通过 git 来追踪,因此对于无效、过时的代码、注释等没有必要保存在最新的代码库中。

(A)用 === 和 !== 而不是 == 和 !=

(B)变量命名风格

  • 避免字母+数字的形式;
  • 避免简写;
  • 能准确表达变量作用;
  • 对功能相似的变量使用统一的命名风格;
  • 使用易于检索的变量名;

(B)函数参数使用默认值

// bad
function writeForumComment(subject, body) {
    subject = subject || 'No Subject';
    body = body || 'No text';
}

// good
function writeForumComment(subject = 'No subject', body = 'No text') {
    // todo
}

(B)使用 async/await 函数

// bad
const request = require('request-promise').get('api_url');
request.then(response => {
    console.log(response);
});

// good
async function getCleanCodeArticle() {
    try {
        let request = await require('request-promise');
        let response = await request.get('api_url');
        let fileHandle = await require('fs-promise');

        await fileHandle.writeFile('article.html', response);
        console.log('File written');
    } catch (err) {
        console.log(err);
    }
}

(B)优先使用 const

对于明确不会发生变化的值,优先使用 const 进行变量声明,可避免出现对该值的意外修改的情况。

// bad
let PI = 3.14;

// good
const PI = 3.14;

(B)单行代码长度不要超过 160 个字符

过长的单行代码会造成代码的可阅读性下降,需要进行单行代码长度的限定。

(B)注释

在以下情况推荐使用注释对代码进行说明:

  • 仅靠名称仍不能完全表明其意义的常量、函数、类
  • 逻辑难以理解,需要注释辅助说明其用意的代码;
  • 可能存在错误的代码;
  • 浏览器相关的 hack 代码;
  • 业务逻辑强相关的代码;
// bad
function hashIt(data) {
    // The hash
    let hash = 0; // Length of string

    let length = data.length; // Loop through every character in data

    for (let i = 0; i < length; i++) {
        // Get character code.
        let char = data.charCodeAt(i); // Make the hash
        hash = (hash << 5) - hash + char; // Convert to 32-bit integer
        hash = hash & hash;
    }
}

// good
function hashIt(data) {
    let hash = 0;
    let length = data.length;

    for (let i = 0; i < length; i++) {
        let char = data.charCodeAt(i);
        hash = (hash << 5) - hash + char; // Convert to 32-bit integer

        hash = hash & hash;
    }
}

(B)多行注释格式

最少三行, * 后跟一个空格。

/*
 * one space after '*'
 */
const num = 1;

(B)以按需加载的方式使用第三方库

尽量减少第三方库的引用大小,保证打包文件尺寸在可控范围之内。

大多数主流的第三方库都提供了开箱即用的按需加载机制。

// bad
import _ from 'lodash';

// good
import pick from 'lodash/pick';

(B)使用第三方库来处理日期

日期处理属于极为常见的需求,且成熟工具数量众多,自己造轮子反而会产生不必要的风险。

// bad
function format(date) {
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();
    return `${year}-${month}-${day}`;
}

// good
import dayjs from 'dayjs';
dayjs().format('YYYY-MM-DD');

(B)对象的属性总是使用简写方式

updated

属性简写使得代码更简洁,当有简写属性与非简写属性同时存在时,避免简写属性与非简写属性的混合排列。

const name = 'sam';
const age = 12;
// bad
const person = {
    name: name
};

// good
const person = {
    name
};

// bad
const person = {
    name,
    sex: 'male',
    age,
    country: 'china'
};

// good
const person = {
    name,
    age,
    sex: 'male',
    country: 'china'
};

(B)封装复杂的判断条件

// bad
if (fsm.state === 'fetching' && isEmpty(listNode)) {
    // / ...
}

// good
function shouldShowSpinner(fsm, listNode) {
    return fsm.state === 'fetching' && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
    // ...
}

(B)在必要的情况才引用 this

// bad
mounted() {
    const that = this;
    setTimeout(() => {
        that.doSomething();
    }, 1000);
}

// good
mounted() {
    setTimeout(() => {
        this.doSomething();
    }, 1000);
}

(C)避免“否定情况”的判断

规则参考

// bad
function isDOMNodeNotPresent(node) {
    // ...
}

if (!isDOMNodeNotPresent(node)) {
    // ...
}

// good
function isDOMNodePresent(node) {
    // ...
}

if (isDOMNodePresent(node)) {
    // ...
}

(C)setInterval 的使用

谨慎使用 setInterval,尤其是在回调中存在网络请求的情况,网络情况不佳时,出现请求堆积的风险会大大增加,并影响后续的数据展示、处理等。

// bad
setInterval(request, 1000);

// good
function longRequest() {
    request().then(result => {
        setTimeout(longRequest, 1000);
    });
}

TypeScript

(A)命名

  • 属性、函数使用 camelCase 命名
  • 类型使用 PascalCase 命名。
  • 不要使用I做为接口名前缀。
  • 枚举值使用 PascalCase 命名。
  • 不要为私有属性名添加_前缀,使用private修辞符。
  • 尽可能使用完整的单词拼写命名。
  • 文件名使用PascalCase 命名
// Bad
const Foo = 1

// Good
const foo = 1
// Bad
const UserList:string[] = []

// Good
const userList:string[] = []
// Bad
function Foo() {}

// Good
function foo() {}
// Bad
class Foo {
  Bar: number;
  Baz(): number {}
}

// Good
class Foo {
  bar: number;
  baz(): number {}
}
接口
接口本身使用 PascalCase 方式命名,不要在接口名前加I。接口成员使用 camelCase 方式命名。
// Bad
interface IFoo {
  Bar: number;
  Baz(): number;
}

// Good
interface Foo {
  bar: number;
  baz(): number;
}
// Bad
enum status {}

// Good
enum Status {}
// Bad
enum Status {
	success = 'success'
}

// Good
enum Status {
	Success = 'success'
}

(A)类型声明

在进行类型声明时应尽量依靠 TS 的自动类型推断功能,如果能够推断出正确类型尽量不要再手动声明。

变量

基础类型变量不需要手动声明类型。

let foo = 'foo'
let bar = 2
let baz = false

引用类型变量应该保证类型正确。

// Bad
let bar = [] // any[]

// Good
let bar:number[] = [] 

(A)回调函数

不要为返回值会被忽略的回调函数设置返回值类型any,应该为返回值会被忽略的回调函数设置返回值类型void

// Bad
function fn(x: () => any) {
    x();
}
// Good
function fn(x: () => void) {
    x();
}

(A)使用联合类型

不要仅因某个特定位置上的参数类型不同而定义重载,应该尽可能地使用联合类型:

// Bad
interface Moment {
  utcOffset(): number;
  utcOffset(b: number): Moment;
  utcOffset(b: string): Moment;
}

// Good
interface Moment {
  utcOffset(): number;
  utcOffset(b: number | string): Moment;
}

(A)类成员

类成员声明时除了public成员,其余成员都应该显式加上作用域修辞符。

// Bad
class Foo {
    foo = 'foo'
    bar = 'bar'
    getFoo() {
        return this.foo
    }
}
const foo = new Foo()
foo.foo
foo.bar

// Good
class Foo {
    private foo = 'foo'
    bar = 'bar'
    getFoo() {
        return this.foo
    }
}
const foo = new Foo()
foo.getFoo()
foo.bar

(B)对象里面尽可能不用 【any】,定义成一个 BO 对象

bad
1、private knowInfo!: any;
 2、private spaceInfo!: any;
3、private parentLevelId!: any;
4、public handleDropdownCommand(node: any, data: any) {
      this.curEditNode = data;
}

CSS

css、less、scss 编写规范的详细说明。

(A)命名

  • 类名使用小写字母,以多个单词以-分隔;
  • id 使用 camelCase 命名;
  • scss、less 中的变量、函数、混合、placeholder 采用 camelCase 命名;
  • 不能使用拼音;
  • 单个单词不能使用缩写;
  • 不得出现拼写错误;
/* bad */
.HeaderTitle
#UserName
#user-name
.biaoge
.filed
.bg

/* good */
.header-title
#userName
.table
.field
.background

(A)颜色

  • 普通颜色使用 16 进制表示,字母均小写;
  • 带透明度的颜色使用 rgba 表示;
/* bad */
color: #3DFEAE;
color: #1b2b351a;

/* good */
color: #3dfeae;
color: rgba(27, 43, 53, 0.1);

(A)属性简写

以下属性不得简写:background、animation、flex、font。

参考:属性简写 - MDN

/* bad */
background: #000 url(images/bg.gif) no-repeat top right;

/* good */
background-color: #000;
background-image: url(images/bg.gif);
background-repeat: no-repeat;
background-position: top right;

/* bad */
flex: 0 1 auto;

/* good */
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;

/* bad */
font: italic bold 0.8em/1.2 Arial, sans-serif;

/* good */
font-style: italic;
font-weight: bold;
font-size: 0.8em;
line-height: 1.2;
font-family: Arial, sans-serif;

(A)属性值为 0 时,不加单位

/* bad */
width: 0px;
margin: 0px;

/* good */
width: 0;
margin: 0;

(A)不能使用!important

实际开发过程中,绝大多数情况下的样式需求不使用!important即可实现。

(A)不能使用行内样式

这里指的是固定的行内样式,行内样式代码的维护性几乎为 0,极不推荐使用。需要通过程来控制的,动态的行内样式除外。

(A)选择器层级不要超过 5 层

选择器层级过深对于样式代码的可读性是灾难,对于浏览器的解析也带来很大负担,如果有超过 5 层的情况,请重新考虑选择器层级是否设计得合理。

(B)优先使用 class 选择器,避免使用元素选择器

/* bad */
div

/* good */
.class

(B)媒体查询

尽量将媒体查询的规则靠近与他们相关的规则,不要将他们一起放到一个独立的样式文件中,或者丢在文档的最底部,这样做只会让大家以后更容易忘记他们。

VUE

vue 规范的详细说明。

(A)组件名为多个单词

组件名是指组件的 name 属性,而不一定是组件文件名。

参考:name - vuejs

// bad
export default {
    name: 'Content'
    // ...
};

// good
export default {
    name: 'MainContent'
    // ...
};

(A)属性定义必须包含类型与默认值

// bad
export default {
    props: ['status']
    // ...
};

// good
export default {
    props: {
        status: {
            type: String,
            default: ''
        }
    }
    // ...
};

(A)避免 v-for 与 v-if 同时使用

<!-- bad -->
<template>
    <ul>
        <li v-for="user in users" 
            v-if="user.isActive" 
            :key="user.id">
            {{ user.name }}
        </li>
    </ul>
</template>

<!-- good -->
<template>
    <ul>
        <li v-for="user in activeUsers" 
            :key="user.id">
            {{ user.name }}
        </li>
    </ul>
</template>

(A)为组件样式设置作用域

覆盖子组件样式优先使用深度选择器。

<!-- bad -->
<template>
    <button class="btn btn-close">X</button>
</template>

<style>
.btn-close {
    background-color: red;
}
</style>

<!-- good -->
<template>
    <button class="button button-close">X</button>
</template>

<!-- 使用 `scoped` attribute -->
<style scoped>
.button {
    border: none;
    border-radius: 2px;
}

.button-close {
    background-color: red;
}
</style>

(A)不使用自闭和组件

模板中的组件名始终为小写字母+短横线。

<template>
    <!-- bad -->
    <my-component />
    <MyComponent />

    <!-- good -->
    <my-component></my-component>
</template>

(A)组件名不得缩写

// bad
components/
|- SdSettings.vue
|- UProfOpts.vue

// good
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue

(A)属性命名

  • 定义时,使用 camelCase 命名;
  • 使用时,使用 kebab-case 命名;
<!-- bad -->
<template>
    <welcome-message greetingText="hi"></welcome-message>
</template>
<script>
export default {
    props: {
        'greeting-text': String
    }
};
</script>

<!-- good -->
<template>
    <welcome-message greeting-text="hi"></welcome-message>
</template>
<script>
export default {
    props: {
        greetingText: {
            type: String,
            default: ''
        }
    }
};
</script>

(A)单文件组件

单文件组件中的标签顺序应为:

  • template
  • script
  • style

style 标签的属性只能有 langscopedmodule3 个。

<!-- bad -->
<template>
    <div class="test">test</div>
</template>
<style ref="stylesheet" lang="less" scoped>
.test {
    width: 100px;
}
</style>
<script>
export default {
    name: 'test'
};
</script>

<!-- good -->
<template>
    <div class="test">test</div>
</template>
<script>
export default {
    name: 'test'
};
</script>
<style lang="less" scoped>
.test {
    width: 100px;
}
</style>

(A)多个属性分多行书写,每个属性一行

<template>
    <!-- bad -->
    <img src="https://vuejs.org/images/logo.png" alt="Vue Logo">

    <!-- good -->
    <img src="https://vuejs.org/images/logo.png"
         alt="Vue Logo">
</template>

(A)进行路由跳转时,只能使用 name 属性

// bad
router.push('/route-path');

// good
router.push({ name: 'routeName' });

(A)异步组件

在以下场景使用异步组件:

  • 定义路由时;
  • 引用依赖或内容很多的组件时;
// bad
import HeavyPage from '../views/HeavyPage.vue';
{
    path: '/page-with-heavy-code',
    component: HeavyPage
}

// good
{
    path: '/page-with-heavy-code',
    component: () => import('../views/HeavyPage.vue')
}


// bad
import HeavyComponent from './HeavyComponent.vue';
export default {
    // ...,
    components: {
        HeavyComponent
    }
}

// good
export default {
    //...,
    components: {
        HeavyComponent: () => import('./HeavyComponent.vue')
    }
}

(A)data 中只存放会变化的数据

不会变化的数据,并不需要实时响应其变化,应放到 computed 或其他文件里。

// bad
export default {
    data() {
        return {
            rules: {
                name: [{ required: true }]
            }
        };
    }
};

// good
export default {
    computed: {
        rules() {
            return {
                name: [{ required: true }]
            };
        }
    }
};

(A)路由命名

  • name使用 camelCase 命名;
  • path使用 kebab-case 命名;
// bad
{
    name: 'RouteName',
    path: '/routePath'
}

// good
{
    name: 'routeName',
    path: '/route-path'
}

(B)基础组件名

应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V。

// bad
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue

// good
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue

components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue

components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue

(B)单例组件名

只应该拥有单个活跃实例的组件应该以 The 前缀命名,以示其唯一性。

// bad
components/
|- Heading.vue
|- MySidebar.vue

// good
components/
|- TheHeading.vue
|- TheSidebar.vue

(B)紧密耦合的组件名

和父组件紧密耦合的子组件应该以父组件名作为前缀命名。

// bad
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue

// good
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue

(B)组件名中的单词顺序

组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。

// bad
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

// good
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue

(B)避免使用 javascript 直接操作 dom

<!-- bad -->
<template>
    <div class="editor"></div>
</template>
<script>
export default {
    mounted() {
        document.querySelector('.editor').innerHTML = '';
    }
};
</script>

<!-- good -->
<template>
    <div class="editor">{{ text }}</div>
</template>
<script>
export default {
    data() {
        return {
            text: ''
        };
    }
    mounted() {
        this.text = '';
    }
}
</script>

(B)html 标签

尽量遵循 HTML 标准和语义,但是不应该以浪费实用性作为代价;任何时候都要用尽量小的复杂度和尽量少的标签来解决问题。

(C)计算属性

把复杂的计算属性分割为尽可能多的更简单的属性。

<!-- bad -->
<template>
    <div>
        {{
            fullName
                .split(' ')
                .map(function(word) {
                    return word[0].toUpperCase() + word.slice(1);
                })
                .join(' ')
        }}
    </div>
</template>

<!-- good -->
<template>
    <div>
        {{ normalizedFullName }}
    </div>
</template>
<script>
export default {
    // 复杂表达式已经移入一个计算属性
    computed: {
        normalizedFullName: function() {
            return this.fullName
                .split(' ')
                .map(function(word) {
                    return word[0].toUpperCase() + word.slice(1);
                })
                .join(' ');
        }
    }
};
</script>

其他

(B)单个文件代码行数不超过 500 行

多于 500 行代码的单个文件在可读性、可维护性上都会给使用者带来一定的负面情绪,应充分考虑如何分割代码。

(A)网络请求的错误提示(如果是根据后端返回的提示),使用 transformError2JSON

(A)网络请求 RequestUtil.sendRequest 在 then 里面不需要通过 res.code === 0 来判断成功,then 都是网络请求成功

附录

参考文档

Logo

前往低代码交流专区

更多推荐