JavaScript代码整洁之道-拖泥的前端之路-SegmentFault思否
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