前端代码质量

115 阅读5分钟

代码质量

简介

如何提高代码的可读性,复用性,扩展性,我将从以下几个方面进行总结归纳:

  • 变量

  • 语句

  • 函数

  • 异步


变量

用有意义且常用的单词命名

// Bad:
const yyyymmdstr = moment().format('YYYY/MM/DD');

// Good:
const currentDate = moment().format('YYYY/MM/DD');

每个常量都该命名

// Bad:
// 其他人知道 86400000 的意思吗?
setTimeout( blastOff, 86400000 );

// Good:
const MILLISECOND_IN_A_DAY = 86400000;
setTimeout( blastOff, MILLISECOND_IN_A_DAY );

避免无意义的前缀

// Bad:
const car = {
  carMake: 'Honda',
  carModel: 'Accord',
  carColor: 'Blue'
};
function paintCar( car ) {
  car.carColor = 'Red';
}

// Good:
const car = {
  make: 'Honda',
  model: 'Accord',
  color: 'Blue'
};
function paintCar( car ) {
  car.color = 'Red';
}

声明变量

//Bad:
let x;
let y = 100;

//Good:
let x, y = 100;

给多个变量赋值

使用数组解构来进行赋值。

//Bad:
const a = 1;
const b = 2;
const c = 3;

//Good:
const [a,b,c] = [1,2,3];

三元运算符

//Bad:
let a = 1;
let b;
if(a>0){
  b = a;
}else{
  b = -1;
}

//Good:
let b = a > 0 ? a : -1;

给变量赋默认值

可以使用 OR(||) 短路运算来给一个变量赋默认值,如果预期值不正确的情况下。

//Bad:
Let imagePath;
let path = getImagePath();
if(path !== null && path !== undefined && path !== ''){
  imagePath = path;
} else {
 imagePath = 'dafault.jpg'
}

//Good:
let imagePath = getImagePath() || 'default.jpg'

交换变量

使用数组解构赋值来交换两个变量。

//Bad:
let x = 1;
let y = 2;
let z = x;
x = y;
y = z;

//Good:
let x = 1, y = 2;
[x,y] = [y,x]

语句

多条件检查

对于多个值匹配,可以将所有的值放到数组中,然后使用 indexOf()或 includes()方法。

//Bad:
if (value === 1 || value === 'one' || value === 2 || value === 'two') {
  // Execute some code
}

//Good:
if ([1, 'one', 2, 'two'].indexOf(value) >= 0) {
  // Execute some code
}
if ([1, 'one', 2, 'two'].includes(value)) {
  // Execute some code
}

&&运算

如果只有当某个变量为 true 时调用一个函数,那么可以使用与 (&&)短路形式书写。

//Bad:

if(isLogin){
  goHome();
}

//Good:
isLogin && goHome();

当在 React 中想要有条件地渲染某个组件时,这个与 (&&)短路写法比较有用。例如:

<div> {isLogin && <Home />} </div>

对象属性复制

如果变量名和对象的属性名相同,那么我们只需要在对象语句中声明变量名,而不是同时声明键和值。JavaScript 会自动将键作为变量的名,将值作为变量的值。

const firstName = 'zing';
const lastName = 'wu';

//Bad:
const obj = {
  firstName: firstName;
  lastName: lastName;
}

//Good:

const obj = {
  firstName;
  lastName;

字符串转数字

//Bad:
let total = parseInt('123');

//Good:
let total = +'123';

指数幂

//Bad:
const power = Math.pow(4,3);

//Good:
const power = 4**3

双非位运算符

//Bad:
const floor = math.floor(6.8) // 6

//Good:
const floor = ~~6.8 // 6

找出数组中的最大数字和最小数字

//Good:
const arr = [1,2,11,3]
const max = Math.max(...arr);
const min = Math.min(...arr);

函数

函数参数

如果参数超过两个,建议使用 ES6 的解构语法,不用考虑参数的顺序。

// Bad:
function createMenu( title, body, buttonText, cancellable ) {
  // ...
}

// Good:
function createMenu( { title, body, buttonText, cancellable } ) {
  // ...
}

createMenu({
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true
});

一个方法只做一件事情

这是一条在软件工程领域流传久远的规则。严格遵守这条规则会让你的代码可读性更好,也更容易重构。如果违反这个规则,那么代码会很难被测试或者重用。

// Bad:
function emailClients( clients ) {
  clients.forEach( client => {
    const clientRecord = database.lookup( client );
    if ( clientRecord.isActive() ) {
      email( client );
    }
  });
}

// Good:
function emailActiveClients( clients ) {
  clients
    .filter( isActiveClient )
    .forEach( email );
}

function isActiveClient( client ) {
  const clientRecord = database.lookup( client );
  return clientRecord.isActive();
}

给函数传参使用默认值

// Bad:
function createMicrobrewery( name ) {
  const breweryName = name || 'Hipster Brew Co.';
  // ...
}

// Good:
function createMicrobrewery( name = 'Hipster Brew Co.' ) {
  // ...
}

函数名上体现它的作用

// Bad:
function addToDate( date, month ) {
  // ...
}

const date = new Date();
// 很难知道是把什么加到日期中
addToDate( date, 1 );

// Good:
function addMonthToDate( month, date ) {
  // ...
}
const date = new Date();
addMonthToDate( 1, date );

删除重复代码,合并相似函数

很多时候虽然是同一个功能,但由于一两个不同点,让你不得不写两个几乎相同的函数。

  // Bad:
  function showDeveloperList(developers) {
      developers.forEach((developer) => {
          const expectedSalary = developer.calculateExpectedSalary();
          const experience = developer.getExperience();
          const githubLink = developer.getGithubLink();
          const data = {
              expectedSalary,
              experience,
              githubLink
          };
          render(data);
      });
  }
  function showManagerList(managers) {
      managers.forEach((manager) => {
          const expectedSalary = manager.calculateExpectedSalary();
          const experience = manager.getExperience();
          const portfolio = manager.getMBAProjects();
          const data = {
              expectedSalary,
              experience,
              portfolio
          };
          render(data);
      });
  }
  
  // Good:
  function showEmployeeList(employees) {
      employees.forEach(employee => {
          const expectedSalary = employee.calculateExpectedSalary();
          const experience = employee.getExperience();
          const data = {
              expectedSalary,
              experience,
          };
          switch(employee.type) {
          case 'develop':
              data.githubLink = employee.getGithubLink();
              break
          case 'manager':
              data.portfolio = employee.getMBAProjects();
              break
          }
          render(data);
      })
  }

使用 Object.assign 设置默认属性

  // Bad:
  const menuConfig = {
      title: null,
      body: 'Bar',
      buttonText: null,
      cancellable: true
  };
  function createMenu(config) {
      config.title = config.title || 'Foo';
      config.body = config.body || 'Bar';
      config.buttonText = config.buttonText || 'Baz';
      config.cancellable = config.cancellable !== undefined ? config.cancellable : true;
  }
  createMenu(menuConfig);
  
  // Good:
  const menuConfig = {
      title: 'Order',
      // 不包含 body
      buttonText: 'Send',
      cancellable: true
  };
  function createMenu(config) {
      config = Object.assign({
          title: 'Foo',
          body: 'Bar',
          buttonText: 'Baz',
          cancellable: true
      }, config);
      // config : {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
      // ...
  }
  createMenu(menuConfig);

尽量不要写全局方法

在 JavaScript 中,永远不要污染全局,会在生产环境中产生难以预料的 bug。举个例子,比如你在 Array.prototype 上新增一个 diff 方法来判断两个数组的不同。而你同事也打算做类似的事情,不过他的 diff 方法是用来判断两个数组首位元素的不同。很明显你们方法会产生冲突,遇到这类问题我们可以用 ES2015/ES6 的语法来对 Array 进行扩展。

// Bad:
Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};

// Good:
class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));
  }
}

使用ES6的class

在 ES6 之前,没有类的语法,只能用构造函数的方式模拟类,可读性非常差。

// Good:
  // 动物
  class Animal {
      constructor(age) {
          this.age = age
      };
      move() {};
  }
  // 哺乳动物
  class Mammal extends Animal{
      constructor(age, furColor) {
          super(age);
          this.furColor = furColor;
      };
      liveBirth() {};
  }
  // 人类
  class Human extends Mammal{
      constructor(age, furColor, languageSpoken) {
          super(age, furColor);
          this.languageSpoken = languageSpoken;
      };
      speak() {};
  }

使用链式调用

这种模式相当有用,可以在很多库中都有使用。它让你的代码简洁优雅。

  class Car {
      constructor(make, model, color) {
          this.make = make;
          this.model = model;
          this.color = color;
      }

      setMake(make) {
          this.make = make;
      }

      setModel(model) {
          this.model = model;
      }

      setColor(color) {
          this.color = color;
      }

      save() {
          console.log(this.make, this.model, this.color);
      }
  }
  // Bad:
  const car = new Car('Ford','F-150','red');
  car.setColor('pink');
  car.save();
  // Good:
  const car = new Car('Ford','F-150','red')
        .setColor('pink');
      .save();

异步

使用promise或者async/await 代替回调

// Bad:

get('[https://en.wikipedia.org/wiki/Robert_Cecil_Martin](https://en.wikipedia.org/wiki/Robert_Cecil_Martin)', (requestErr, response) => {
  if (requestErr) {
    console.error(requestErr);
  } else {
    writeFile('article.html', response.body, (writeErr) => {
      if (writeErr) {
        console.error(writeErr);
      } else {
        console.log('File written');
      }
    });
  }
});

// Good:
get('[https://en.wikipedia.org/wiki/Robert_Cecil_Martin](https://en.wikipedia.org/wiki/Robert_Cecil_Martin)')
  .then((response) => {
    return writeFile('article.html', response);
  })
  .then(() => {
    console.log('File written');
  })
  .catch((err) => {
    console.error(err);
  });

// perfect:
async function getCleanCodeArticle() {
  try {
    const response = await get('[https://en.wikipedia.org/wiki/Robert_Cecil_Martin](https://en.wikipedia.org/wiki/Robert_Cecil_Martin)');
    await writeFile('article.html', response);
    console.log('File written');
  } catch(err) {
    console.error(err);
  }
}