javascript代码整洁技巧

前言

为什么代码要整洁?

代码质量与整洁度成正比。有的团队在赶工期的时候,不注重代码的整洁,代码写的越来越糟糕,项目越来越混乱,生产力也跟着下降,那就必须找更多人来提高生产力,开发成本越来越高。

整洁的代码是怎样的?

清晰表达意图、消除重复、简单抽象、能通过测试。换句话说:具有可读性、可重用性和可重构性。

命名

  • 名副其实:不使用缩写、不使用让人误解的名称,不要让人推测。

    // bad: 啥?
    const yyyymmdstr = moment().format("YYYY/MM/DD");
    // bad: 缩写
    const cD = moment().format("YYYY/MM/DD");

    // good:
    const currentDate = moment().format("YYYY/MM/DD");
    const locations = ["Austin", "New York", "San Francisco"];

    // bad:推测l是locations的项
    locations.forEach(l => doSomeThing(l));

    // good
    locations.forEach(location => doSomeThing(location));
  • 使用方便搜索的名称:避免硬编码,对数据用常量const记录。

    // bad: 86400000指的是?
    setTimeout(goToWork, 86400000);

    // good: 86400000是一天的毫秒数
    const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000;
    setTimeout(goToWork, MILLISECONDS_PER_DAY);
  • 类名应该是名词,方法名应该是动词。

    // bad
    function visble() {}

    // good
    function getVisble() {}
  • 多个变量属于同一类型的属性,那就他们整合成一个对象。同时省略多余的上下文。

// bad:可以整合
const carMake = "Honda",
const carModel = "Accord",
const carColor = "Blue",

// bad: 多余上下文
const Car = {
  carMake: "Honda",
  carModel: "Accord",
  carColor: "Blue",
};

// good
const Car = {
  make: "Honda",
  model: "Accord",
  color: "Blue",
};

其他:

  • 不要写多余的废话,比如theMessagethe可以删除。

  • 统一术语。比如通知一词,不要一会在叫notice,一会叫announce

  • 用读得通顺的词语。比如getElementById就比 useIdToGetElement好读。

函数(方法)

  • 删除重复的代码,don't repeat yourself。很多地方可以注意dry,比如偷懒复制了某段代码、try...catch或条件语句写了重复的逻辑。

 // bad
 try {
     doSomeThing();
     clearStack();
 } catch (e) {
     handleError(e);
     clearStack();
 }
 // good
 try {
     doSomeThing();
 } catch (e) {
     handleError(e);
 } finally {
     clearStack();
 }
  
  • 形参不超过三个,对测试函数也方便。多了就使用对象参数。

    • 同时建议使用对象解构语法,有几个好处:

                1. 能清楚看到函数签名有哪些熟悉,
                2. 可以直接重新命名,
                3. 解构自带克隆,防止副作用,
                4. Linter检查到函数未使用的属性。

// bad
function createMenu(title, body, buttonText, cancellable) {}

// good
function createMenu({ title, body, buttonText, cancellable }) {}
  • 函数只做一件事,代码读起来更清晰,函数就能更好地组合、测试、重构。

// bad: 处理了输入框的change事件,并创建文件的切片,并保存相关信息到localStorage
function handleInputChange(e) {
    const file = e.target.files[0];
    // --- 切片 ---
    const chunkList = [];
    let cur = 0;
    while (cur < file.size) {
        chunkList.push({
          chunk: file.slice(cur, cur + size)
        });
        cur += size;
    }
    // --- 保存信息到localstorage ---
    localStorage.setItem("file", file.name);
    localStorage.setItem("chunkListLength", chunkList.length);
}

// good: 将三件事分开写,同时自顶而下读,很舒适
function handleInputChange(e) {
    const file = e.target.files[0];
    const chunkList = createChunk(file);
    saveFileInfoInLocalStorage(file, chunkList);
}
function createChunk(file, size = SLICE_SIZE) {
    const chunkList = [];
    let cur = 0;
    while (cur < file.size) {
        chunkList.push({
          chunk: file.slice(cur, cur + size)
        });
        cur += size;
    }
    return chunkList
}
function saveFileInfoInLocalStorage(file, chunkList) {
    localStorage.setItem("file", file.name);
    localStorage.setItem("chunkListLength", chunkList.length);
}
  • 自顶向下地书写函数,人们都是习惯自顶向下读代码,如,为了执行A,需要执行B,为了执行B,需要执行C。如果把A、B、C混在一个函数就很难读了。(看前一个的例子)。

  • 不使用布尔值来作为参数,遇到这种情况时,一定可以拆分函数。

 // bad
 function createFile(name, temp) {
   if (temp) {
     fs.create(`./temp/${name}`);
   } else {
     fs.create(name);
   }
 }

 // good
 function createFile(name) {
   fs.create(name);
 }

 function createTempFile(name) {
   createFile(`./temp/${name}`);
 }
  • 避免副作用。

  • 副作用的缺点:出现不可预期的异常,比如用户对购物车下单后,网络差而不断重试请求,这时如果添加新商品到购物车,就会导致新增的商品也会到下单的请求中。

  • 集中副作用:遇到不可避免的副作用时候,比如读写文件、上报日志,那就在一个地方集中处理副作用,不要在多个函数和类处理副作用。

    • 其它注意的地方:

    • 常见就是陷阱就是对象之间共享了状态,使用了可变的数据类型,比如对象和数组。对于可变的数据类型,使用immutable等库来高效克隆。

    • 避免用可变的全局变量。

  // bad:注意到cart是引用类型!
  const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
  };

  // good
  const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
  };
  
  • 封装复杂的判断条件,提高可读性。

 // bad
 if (!(obj => obj != null && typeof obj[Symbol.iterator] === 'function')) {
     throw new Error('params is not iterable')
 }

 // good
 const isIterable = obj => obj != null && typeof obj[Symbol.iterator] === 'function';
 if (!isIterable(promises)) {
     throw new Error('params is not iterable')
 }
  • 在方法中有多条件判断时候,为了提高函数的可扩展性,考虑下是不是可以使用能否使用多态性来解决。

// 地图接口可能来自百度,也可能来自谷歌
 const googleMap = {
     show: function (size) {
         console.log('开始渲染谷歌地图', size));
     }
 };
 const baiduMap = {
     render: function (size) {
         console.log('开始渲染百度地图', size));
     }
 };

 // bad: 出现多个条件分支。如果要加一个腾讯地图,就又要改动renderMap函数。
 function renderMap(type) {
     const size = getSize();
     if (type === 'google') {
         googleMap.show(size);
     } else if (type === 'baidu') {
         baiduMap.render(size);
     }
 };
 renderMap('google')

 // good:实现多态处理。如果要加一个腾讯地图,不需要改动renderMap函数。
 // 细节:函数作为一等对象的语言中,作为参数传递也会返回不同的执行结果,也是“多态性”的体现。
 function renderMap (renderMapFromApi) {
     const size = getSize();
     renderMapFromApi(size);
 }
 renderMap((size) => googleMap.show(size));

其他

  • 如果用了TS,没必要做多余类型判断。

注释

  • 一般代码要能清晰的表达意图,只有遇到复杂的逻辑时才注释。

 // good:由于函数名已经解释不清楚函数的用途了,所以注释里说明。
 // 在nums数组中找出 和为目标值 target 的两个整数,并返回它们的数组下标。
 const twoSum = function(nums, target) {
     let map = new Map()
     for (let i = 0; i < nums.length; i++) {
         const item = nums[i];
         const index = map.get(target - item)
         if (index !== undefined){
             return [index, i]
         }
         map.set(item, i)
     }
     return []
 };

 // bad:加了一堆废话
 const twoSum = function(nums, target) {
     // 声明map变量
     let map = new Map()
     // 遍历
     for (let i = 0; i < nums.length; i++) {
         const item = nums[i];
         const index = map.get(target - item)
         // 如果下标为空
         if (index !== undefined){
             return [index, i]
         }
         map.set(item, i)
     }
     return []
 };
  • 警示作用,解释此处不能修改的原因。

    // hack: 由于XXX历史原因,只能调度一下。
    setTimeout(doSomething, 0)
  • TODO注释,记录下应该做但还没做的工作。另一个好处,提前写好命名,可以帮助后来者统一命名风格。

    class Comment {
    // todo: 删除功能后期实现
    delete() {}
    }
    
  • 没用的代码直接删除,不要注释,反正git提交历史记录可以找回。

  // bad: 如下,重写了一遍两数之和的实现方式

    // const twoSum = function(nums, target) {
    //     for(let i = 0;i
  • 避免循规式注释,不要求每个函数都要求jsdoc,jsdoc一般是用在公共代码上。

    // bad or good?
    /**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
    const twoSum = function(nums, target) {}

对象

  • 多使用getter和setter(getXXX和setXXX)。好处:

    • 在set时方便验证。

    • 可以添加埋点,和错误处理。

    • 可以延时加载对象的属性。

// good
  function makeBankAccount() {
  let balance = 0;

  function getBalance() {
    return balance;
  }

  function setBalance(amount) {
    balance = amount;
  }

  return {
    getBalance,
    setBalance
  };
  }

  const account = makeBankAccount();
  account.setBalance(100);
  
  • 使用私有成员。对外隐藏不必要的内容。

 // bad
  const Employee = function(name) {
  this.name = name;
  };

  Employee.prototype.getName = function getName() {
  return this.name;
  };
  const employee = new Employee("John Doe");
  delete employee.name;
  console.log(employee.getName()); // undefined


  // good
  function makeEmployee(name) {
  return {
    getName() {
      return name;
    }
  };
  }

solid

  • 单一职责原则 (SRP) - 保证“每次改动只有一个修改理由”。因为如果一个类中有太多功能并且您修改了其中的一部分,则很难预期改动对其他功能的影响。

  // bad:设置操作和验证权限放在一起了
  class UserSettings {
  constructor(user) {
    this.user = user;
  }

  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }

  verifyCredentials() {
    // ...
  }
  }
  // good: 拆出验证权限的类
  class UserAuth {
  constructor(user) {
    this.user = user;
  }

  verifyCredentials() {
    // ...
  }
  }

  class UserSettings {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(user);
  }

  changeSettings(settings) {
    if (this.auth.verifyCredentials()) {
      // ...
    }
  }
  }
  • 开闭原则 (OCP) - 对扩展放开,但是对修改关闭。在不更改现有代码的情况下添加新功能。比如一个方法因为有switch的语句,每次出现新增条件时就要修改原来的方法。这时候不如换成多态的特性。

 // bad: 注意到fetch用条件语句了,不利于扩展
  class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }
  }

  class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }
  }

  class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    if (this.adapter.name === "ajaxAdapter") {
      return makeAjaxCall(url).then(response => {
        // transform response and return
      });
    } else if (this.adapter.name === "nodeAdapter") {
      return makeHttpCall(url).then(response => {
        // transform response and return
      });
    }
  }
  }

  function makeAjaxCall(url) {
  // request and return promise
  }

  function makeHttpCall(url) {
  // request and return promise
  }

  // good
  class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }

  request(url) {
    // request and return promise
  }
  }

  class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }

  request(url) {
    // request and return promise
  }
  }

  class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then(response => {
      // transform response and return
    });
  }
  }
  

错误处理

  • 不要忽略捕获的错误。而要充分对错误做出反应,比如console.error()到控制台,提交错误日志,提醒用户等操作。

  • 不要漏了catch promise中的reject。

你可能感兴趣的:(javascript,javascript,前端)