欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JavaScript代码整洁之道-拖泥的前端之路-SegmentFault思否

程序员文章站 2022-06-07 19:22:12
javascript代码整洁之道 整洁的代码不仅仅是让人看起来舒服,更重要的是遵循一些规范能够让你的代码更容易维护,同时降低bug几率。 原文clean-code-javascr...

javascript代码整洁之道

整洁的代码不仅仅是让人看起来舒服,更重要的是遵循一些规范能够让你的代码更容易维护,同时降低bug几率。

原文clean-code-javascript,这里总结摘录出个人觉得有帮助的地方,也加入了一些自己的理解(有些文字我感觉保留原文更好,所以直接copy了),其他还有很多点没有列出来,感兴趣可以去看原文。另外这不是强制的代码规范,就像原文中说的,these are guidelines and nothing more。

1. 用命名的变量代替数组下标

// bad
const address = "one infinite loop, cupertino 95014";
const cityzipcoderegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
savecityzipcode(
  // 下标1,2不易于理解
  address.match(cityzipcoderegex)[1],
  address.match(cityzipcoderegex)[2]
);
// good
const address = "one infinite loop, cupertino 95014";
const cityzipcoderegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
// 使用数组解构更好的命名变量
const [, city, zipcode] = address.match(cityzipcoderegex) || [];
savecityzipcode(city, zipcode);

2. 函数的参数最好<=2个,尽量避免3个。

如果有很多参数就利用object传递,并使用解构。

注意object array这些引用类型的值是mutable的。

3. 一个函数只做一件事。

好处在于compose, test, and reason about。

4. 不要自行扩展原型

如果想扩展原型,可以先继承再添加方法,防止污染。

// 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));
  }
}

5. 用多态来代替条件语句

// bad
if (type === 'text') {
    // do something
} else if (type === 'select') {
    // do something else
}

我个人写这种代码的一种常用方式是:

const control = {
    text: {
        mapper() {},
        restore(){},
        name: 'this is a text field',
    },
    select: {
        mapper() {},
        restore(){},
        name: 'this is a select field',
    }
}

control[type].mapper();

实际上就是多态(polymorphism),也可以考虑用class的方式,大概这样:

class field {
    ...
}

class textfield extends field {
    mapper(){}
    restore(){}
    name = 'this is a text field';
}

class selectfield extends field {
    mapper(){}
    restore(){}
    name = 'this is a select field';
}

6. 使用getter和setter函数。

// bad
function makebankaccount() {
  // ...

  return {
    balance: 0
    // ...
  };
}

const account = makebankaccount();
account.balance = 100;
// good
function makebankaccount() {
  // this one is private
  let balance = 0;

  // a "getter", made public via the returned object below
  function getbalance() {
    return balance;
  }

  // a "setter", made public via the returned object below
  function setbalance(amount) {
    // ... validate before updating the balance
    balance = amount;
  }

  return {
    // ...
    getbalance,
    setbalance
  };
}

const account = makebankaccount();
account.setbalance(100);

你可以在getter和setter里面做很多事情而不需要修改每一个.balance的地方。

7. prefer composition over inheritance

尽量用组合来代替继承,什么情况下用继承:

your inheritance represents an "is-a" relationship and not a "has-a" relationship (human->animal vs. user->userdetails). you can reuse code from the base classes (humans can move like all animals). you want to make global changes to derived classes by changing a base class. (change the caloric expenditure of all animals when they move).

8. solid

single responsibility principle 单一职责原则

there should never be more than one reason for a class to change,一个类被改变的原因数量应该尽可能降低。如果一个类中功能太多,当你修改其中一点时会无法估量任何引用该类的模块所受到的影响。

open/closed principle 开放封闭原则

用户可以在不修改内部实现的前提下自行扩展功能。例如有一个http模块,内部会根据环境判断用哪个adaptor。如果用户要添加adaptor就必须修改http模块。

// bad
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
    });
  }
}

liskov substitution principle 里式替换原则

父类和子类应该可以被交换使用而不会出错。

// bad
class rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setcolor(color) {
    // ...
  }

  render(area) {
    // ...
  }

  setwidth(width) {
    this.width = width;
  }

  setheight(height) {
    this.height = height;
  }

  getarea() {
    return this.width * this.height;
  }
}

class square extends rectangle {
  setwidth(width) {
    this.width = width;
    this.height = width;
  }

  setheight(height) {
    this.width = height;
    this.height = height;
  }
}

function renderlargerectangles(rectangles) {
  rectangles.foreach(rectangle => {
    rectangle.setwidth(4);
    rectangle.setheight(5);
    const area = rectangle.getarea(); // bad: returns 25 for square. should be 20.
    rectangle.render(area);
  });
}

const rectangles = [new rectangle(), new rectangle(), new square()];
renderlargerectangles(rectangles);

上面的rectangle不能直接替换square,因为会导致计算面积错误,考虑将计算面积的方法抽象出来:

class shape {
  setcolor(color) {
    // ...
  }

  render(area) {
    // ...
  }
}

class rectangle extends shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getarea() {
    return this.width * this.height;
  }
}

class square extends shape {
  constructor(length) {
    super();
    this.length = length;
  }

  getarea() {
    return this.length * this.length;
  }
}

function renderlargeshapes(shapes) {
  shapes.foreach(shape => {
    const area = shape.getarea();
    shape.render(area);
  });
}

const shapes = [new rectangle(4, 5), new rectangle(4, 5), new square(5)];
renderlargeshapes(shapes);

interface segregation principle 接口隔离原则

clients should not be forced to depend upon interfaces that they do not use。举例来说,一个功能模块需要设计必须传的参数和可选参数,不应该强迫用户使用可选参数。

dependency inversion principle 依赖注入原则

原文:

high-level modules should not depend on low-level modules. both should depend on abstractions. abstractions should not depend upon details. details should depend on abstractions.
// bad
class inventoryrequester {
  constructor() {
    this.req_methods = ["http"];
  }

  requestitem(item) {
    // ...
  }
}

class inventorytracker {
  constructor(items) {
    this.items = items;

    // bad: we have created a dependency on a specific request implementation.
    // we should just have requestitems depend on a request method: `request`
    this.requester = new inventoryrequester();
  }

  requestitems() {
    this.items.foreach(item => {
      this.requester.requestitem(item);
    });
  }
}

const inventorytracker = new inventorytracker(["apples", "bananas"]);
inventorytracker.requestitems();

上面例子在于,inventorytracker内部实例化了inventoryrequester,也就意味着high-level的模块需要知道low-level模块的细节(比如实例化inventoryrequester需要知道它的构造参数等,或者说需要import该模块,造成耦合)。

// good
class inventorytracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

  requestitems() {
    this.items.foreach(item => {
      this.requester.requestitem(item);
    });
  }
}

class inventoryrequesterv1 {
  constructor() {
    this.req_methods = ["http"];
  }

  requestitem(item) {
    // ...
  }
}

class inventoryrequesterv2 {
  constructor() {
    this.req_methods = ["ws"];
  }

  requestitem(item) {
    // ...
  }
}

// by constructing our dependencies externally and injecting them, we can easily
// substitute our request module for a fancy new one that uses websockets.
const inventorytracker = new inventorytracker(
  ["apples", "bananas"],
  new inventoryrequesterv2()
);
inventorytracker.requestitems();

直接传入low-level的实例而不需要考虑它是如何被实例化的,high-level只需要依赖抽象的接口就可以完成对子模块的调用。

9. 注释

comments are an apology, not a requirement. good code mostly documents itself. 好的代码是自解释的。

参考:clean-code-javascript