怎麼避免寫出爛代碼

命名

規則:除非在小於 5 行的函數裏,不然不要使用單字命名變量

說明:含義不清晰,不能作到「望文生義」html

BadCode前端

var l = data.length;

GoodCodevue

// 閉包只有一行代碼,可使用單字變量
data.map(d => d.length)

規則:不要使用名詞加數字的命名方法

說明:含義不清晰,不能作到「望文生義」react

BadCodeios

var obj = {};
var obj2 = {...obj, key: 1};

GoodCodegit

var obj = {};
var objWithKey = {...obj, key: 1};

規則:應該且只有方法和函數以動詞開頭

此處動詞沒有包含時態
變量名應該是名詞或者名詞短語。github

例外:redux

  • 回調函數
  • 生命週期函數
  • 框架級別函數
  • getter/setter

說明:canvas

  • 函數的命名須要體現內部工做
  • 值變量以動詞命名容易讓人誤解爲是一個匿名函數

BadCodeaxios

// 以名詞開頭,看不明白什麼有什麼功能
function option() {}

// 時態不對
function updatedTime() {}

GoodCode

function selectOption() {}
function updateTime() {}

規則:避免使用拼音或者縮寫命名。

例外:

  • 專有名詞:weixin/POI
  • 傳統約定:i/j/k 表示循環索引

說明:含義不清晰,不能作到「望文生義」

BadCode

var uo = function updateOrder(){}
var as = [].slice;
var ex = Object.extends;
var nu = number

GoodCode

// weixin/wx 是專有名詞
var weixinUser = {};
var wx = weixin;
// POI 是專有名詞
var poi = {};

規則:名稱長短應與其做用域大小相對應。

例外:專有 API,如 alert
說明:在上層做用域下的代碼,會被更多函數使用到。其名稱應該儘可能長或者通用,以保證可以搜索到。

BadCode

// 在全局變量上定義了一個 item 變量,可是很難從命名上理解其做用是什麼。
window.item = {}

GoodCode

window.primaryProductItem = {};

不要在變量/函數尾部加符號、數字

說明:變量中加符號,每每是爲了約定其優先級或者做用域。符號應該在變量名前面。

BadCode

function getDot_(){}
function privateFn$$ (){}

GoodCode

function _getDot() {}
function $$privateFn() {}

規則:實例名稱要和類名相關

說明:類做爲實例的所屬,其名稱表達的含義要一脈相承

BadCode

class Person() {}
var dog = new Person(); // dog is a Person ?

GoodCode

class Person() {}
var jack = new Person();

規則:避免直白的中英文翻譯

說明:粗暴的翻譯,更容易形成誤解,還不如寫拼音

BadCode

// 渲染「頁面頂部的執行人」
// 仍是渲染「執行砍頭的人」?
function renderHeadExecutantPeople(){}

GoodCode

function renderHeader() {}

規則:概念的命名要一以貫之

說明:避免通一個概念在不一樣的代碼用多種不一樣的單詞描述。

BadCode

// 遠程請求這個概念,前後用了 get/fetch/query
// 三個名詞去描述
function getUserInfo() {}
function fetchProductInfo() {}
function queryPayment() {}

GoodCode

// 統一用 get 描述
// 閱讀代碼的人能體會到其中的共性
function getUserInfo() {}
function getProductInfo() {}
function getPayment() {}

規則:別用雙關語

例外:專有詞
說明:雙關語容易引發歧義

BadCode

// 訂單類型,仍是排序類型?
var orderType

GoodCode

var sortType

規則:命名須要和實現一致

說明:命名每每是實現的隱喻,若是存在差別則會讓閱讀者看不懂代碼。

BadCode

// empty 的命名含義和實現截然相反
// (我真的見過這種代碼)
function getProduct(id) {
    axios.delete('/product', {id}); 
}

GoodCode

function deleteProduct(id) {
    axios.delete('/product', {id}); 
}

規則:對於布爾值的命名,須要默認其爲「真」

說明:布爾變量的名稱中,若是加上 「not」之類的否認詞,則至關於作了一次了邏輯判斷。

BadCode

const notEmpty = !!array.length;

GoodCode

const empty = !array.length;

函數

規則:長度不能超過 20 行

說明:代碼太長說明作的事情不夠專注,同時也會讓閱讀變得很困難。

規則:Don't repeat yourself

說明:一樣功能的代碼不要重複三次

規則:每一個函數只作一件事情,並作好這件事

說明:代碼裏的邏輯分支要儘可能少,只作一件事情,而且要處理好邊界和異常狀況。

規則:儘可能減小函數的參數,包括 opitons、config 等參數

說明:函數的輸入越多,每每就表明功能約複雜

規則:註釋出來項目/業務的坑

說明:對於比較奇怪的業務邏輯,或者由於系統、接口緣由而寫的比較奇怪的邏輯。要經過註釋標註出來

BadCode

framework.doSomeThing();
framework.reset(); // 閱讀者心裏 OS:這裏爲啥要作一次 reset?
framework.continueSomeThing();

GoodCode

framework.doSomeThing();
// framework 有個 bug,這裏必需要作一次 rest 附連接: http://github.com/issuse/***
framework.reset(); 
framework.continueSomeThing();

規則:函數要儘可能「純」沒有反作用

說明:純函數比較好測試,邏輯也比較清晰,能夠放心的引入和刪除。

BadCode

let status;
function method() {
  if (status) { ... } 
}

GoodCode

function method(status) {
  if (status) { ... } 
}

規則:函數最好不要修改參數內的數據

說明:修改參數會致使函數的做用變得不可預測

BadCode

function updateObj(obj, value) {
  obj.key = value;
  return obj;
}

GoodCode

function updateObj(obj, value) {
  return {...obj, key: value};
}

規則:除非是 class 的方法,不然不要訪問 this

說明:this 的指向常常不固定,會致使代碼難以理解。若是調用方不熟悉的話,很容易引發 Bug。

BadCode

function method() {
    console.log(this.value); 
}

method() // 報錯
var obj = { method, value: 1}
obj.method() // 輸出 1

GoodCode

function method(value) {
    console.log(value); 
}

規則:處理錯誤

說明:錯誤也是一種邏輯分支,若是不處理的話,代碼就不夠健壯。前端代碼處理錯誤的方式通常爲提示用戶有異常發生。若是錯誤不影響業務流程,則寫入日誌裏並上報。

BadCode

function method(data) {
    try { return JSON.parse(data) }
  catch (e) {}
}

GoodCode

function method(data) {
    try { return JSON.parse(data) }
  catch (e) {
      alert('數據處理失敗')
  }
}

數據

規則:不要有 Magic Number

說明:magic number 是指直接在代碼中硬編碼的數字,每每具備一些業務含義。

這樣會致使:

  • 數字的意義難以理解
  • 數值要改動時,要改不少地方

BadCode

if (status === 1) {
    ...
} else if (type === 4) {
  ...
}

GoodCode

enum Status {
    Closed 
}
enum Type {
    Array 
}

if (status === Status.Closed) {
    ...
} else if (type === Type.Array) {
  ...
}

規則:不論是 react state 仍是 vue data 存放的業務數據都要具有原子性。

說明:原子性意味着獨立,且不可分割。其它屬性都由原子業務屬性推導、計算而來,這樣能保證狀態的一致。

BadCode

// 當 status 爲 open 的時候展現彈窗
// 其它狀態則隱藏彈窗
{
    data() {
    return {
        showAlert: false,
      status: 'closed',
    }
  },
  onStatusChange() {
      if (status === 'open') {
        this.showAlert = true;
    } else {
        this.showAlert = false; 
    }
  }
}

GoodCode

// showAlert 爲非原子的狀態
// 其狀態能夠由 status 推導而來
{
    data() {
    return {
      status: 'closed',
    }
  },
  computed: {
      showAlert() {
        return this.status === 'open';
    }
  }
}

規則:對於 react state 和 vue data,應當區分業務狀態和 UI 狀態

說明:

  • 狀態和 UI 存儲在一塊兒,有時候傳給後端的數據裏會夾雜着沒有必要的 UI 狀態。
  • 業務代碼和 UI 代碼耦合在一塊兒,業務代碼無法複用。

BadCode

// 在一個列表中,用戶能夠對數據作多選
// 而後刪除他們
class extends React.Component {
     async componentDidMount() {
      const listData = getData();
    this.setState({ listData })
  }
  
  check = (item) => {
    const listData = this.state.listData.map(i => {
        if (i === item) {
        return {...item, checked: true}
      }
      return i;
    });
    
    this.setState({ listData });
  }
  
  delete() {
    // 返回給後端的數據結構,會多出一個 checked 字段
    deleteItems(this.state.listData.filter(i => i.checked));
  }
  
    render() {
    const list = this.state.listData.map(item => {
      const className = ['item'];
      if (item.checked) className.push('active');
        return <label
          className={className}
          onClick={() => this.check(item)}
      >{item.naem}</label>;
    });
    
    return <>
      {list}
        <button onClick={this.delete}>delete</button>
    </>
  }
}

GoodCode

// 在一個列表中,用戶能夠對數據作多選
// 而後刪除他們
class extends React.Component {
     async componentDidMount() {
      const listData = getData();
    // 使用獨立的 selected 來保存 UI 狀態
    this.setState({ listData, selected: [] })
  }
  
  check = (item) => {
    let { selected } = this.state;
    selected = selected.findOrInsert(s => s.id, item);
    this.setState({ selected });
  }
  
  delete() {
    const { selected, listData } = this.state;
    deleteItems(listData.filter(i => selected.includes(i.id))));
  }
  
    render() {
       const { selected, listData } = this.state;
    const list = listData.map(item => {
      const className = ['item'];
      if (selected.includes(item.id)) className.push('active');
        return <label
          className={className}
          onClick={() => this.check(item)}
      >{item.naem}</label>;
    });
    
    return <>
      {list}
        <button onClick={this.delete}>delete</button>
    </>
  }
}

規則:對於 react 應用,避免在 render 的時候修改狀態

說明:react 的 render 應該是純函數,在 render 裏運行 setState 會致使重複渲染,或者死循環。

BadCode

// 若是 type 爲 http 的話,則自動轉換爲 https
class extends React.Component {
  render() {
      const { type } = this.state;
    if (type === 'http') {
        this.setState({ type: 'https'}) 
    }
    return <label>{type}</label>;
  }
}

GoodCode

// 若是 type 爲 http 的話,則自動轉換爲 https
class extends React.Component {
  get type() {
    const { type } = this.state;
    if (type === 'http') return 'https';
    return type;
  }
  render() {
      const type = this.type;
    return <label>{type}</label>;
  }
}

規則:對於雙向綁定應用,避免數據循環依賴。

說明:

  • 循環依賴輕則致使頁面相應慢,重則致使出現髒數據。
  • 避免循環依賴的前提是理清業務邏輯,搞清楚數據之間的依賴關係。
  • 循環依賴也是雙向綁定技術的詬病之一。

BadCode

// foo 和 bar 互相依賴,致使了死循環
{
    data() {
      return {
        foo: 1,
    }; 
  },
  computed: {
    bar() {
        return this.foo + 1;
    }
  },
  watch() {
    bar() {
        this.foo = this.bar + 1;
    },
  }
}

規則:訪問數據時,須要考慮邊界狀況和 JS 弱類型的特性。

說明:好比用雙等號作判斷

BadCode

const foo = '0';
const bar = 0

// 作數據判斷時不能用雙等號
foo == bar // true
foo ? 1 : 2 // 1
bar ? 1 : 2 // 2

// 僅經過變量有沒有 length 來判斷是否爲數組
if(obj.length) {
    obj.forEach(...) 
}

GoodCode

const foo = '0';
const bar = 0

foo === bar // false

if (Array.isArray(obj)) {
     obj.forEach(...) 
}

規則:不要在遍歷數組的同時,改變數組數據

說明:這樣作會致使數據的異常。若是須要作這種操做,最好使用數組函數,或者操做拷貝數據。

BadCode

const array = [1,2,3,4,5,6,7,8,9,10];

// 刪除數組中的偶數
for (var i = 0; i < array.length; i++) {
    if (array[i] % 2 == 0) array.splice(i);
}
// array 變成了 [1]

GoodCode

const array = [1,2,3,4,5,6,7,8,9,10];
array.filter(a => !(a % 2))

API

規則:對 setTimeout 調用時,傳遞的時間參數必須有意義。

說明:
大多數場景下,setTimeout 後面傳遞一個時間是爲了先執行後續的 A 代碼,再延後執行代碼閉包裏的 B 代碼,如右邊示例代碼。

但若是隨着業務迭代,A 被改爲異步,或者執行時間很長的話。以前作的延遲執行的防護措施就時效了,也許反而 B 會比 A 先執行。

BadCode

// 代碼的原本意圖是讓 B 延後執行
setTimeout(() => {
  B();
}, 1000);
A();

// 代碼的意圖是讓 render 在下一幀執行
// 可是不一樣設備,一幀時間是不固定的
setTimeout(() => {
  render()
}, 16);

GoodCode

// A 函數內要想辦法使用 Promise 串接起來
await A();
b();

// 使用系統提供的 API 執行動畫幀
requestAnimationFrame(() => {
 render(); 
});

規則:不要使用陳舊的 API

說明:陳舊的 API 每每有不少問題,好比安全、性能、不易讀等。

BadCode

// 判斷是否爲數組
Object.prototype.toString.call(array) === "[object Array]" 
// 查找數組裏第一個偶數
for (var i = 0; i < array.length; i++) {
    if (array[i] % 2 === 0) return array[i]; 
}
// 遍歷對象的 key
for (var key in obj) {
    console.log(key); 
}
// 判斷字符串/數組是否包含
'some text'.indexOf('some') >= 0
// 去除首位空格
' some text '.replace(/(^\s+|\s+$)/g, '')
// 新建對象/數組
const array = new Array();
const obj = new Object();

GoodCode

Array.isArray(array)

array.find(a => a % 2 === 0);

Object.keys(obj).forEach(console.log)

'some text'.includes('some')

' some text '.trim()
const array = [];
const obj = {};

規則:對於 99.9% 的場景,你都不須要使用 React ref

說明:
React Ref 通常是用來處理和原生 DOM 交互的場景,好比 canvas。

大部分對於 React ref 的使用都是錯誤的,大多都拿來用來控制子元素。這種場景咱們更推薦用數據流(redux,mobx)或者用狀態提高去作。

React 官方有對「狀態提高」的描述 https://react.docschina.org/d...

BadCode

class List extends React.Component {
    async refresh() {
      this.setState({
      items: getItems(),
    });
  }
  
  render() {
      return this.state.items.map(i => <label>{i}</label>); 
  }
}
class extends React.Component {
  onRefresh = () => {
    // 用 ref 去調用子元素的方法
    this.list.refresh();
  }
    render() {
    return <>
      <List ref={l => this.list = l}></List>
        <button onClick={this.onRefresh}/>
    </>;
      
  }
}

GoodCode

class List extends React.Component {
  render() {
      return this.props.items.map(i => <label>{i}</label>); 
  }
}
class extends React.Component {
 // 把數據狀態提高到父組件作操做                              
  refresh = async () => {
      this.setState({
      items: getItems(),
    });
  }

    render() {
    return <>
      <List items{this.state.items}></List>
        <button onClick={this.refresh}/>
    </>;
      
  }
}

規則:不要用字符串拼接 url

說明:
字符串拼接 url 須要處理 encode 或者 decode 的狀況,還有對於 ?和 # 的判斷不對的話,很容易形成漏洞或者 Bug。

目前瀏覽器和 Node 都已經提供了標準的 URL 解析方法。

https://developer.mozilla.org...

BadCode

// 這段代碼既沒有對 key、value 作 encode
// 也沒有考慮 url 中 # 出現的狀況
const url = location.href;
if (url.indexOf('?') >= 0) {
    return  url + key + '=' + value;
} else {
  return  url + '?' + key + '=' + value;
}

GoodCode

// 使用標準的 URL 解析,風險會下降不少
const url = new URL(urlStr);
url.searchParams.set(key, value);
return url.toString();

邏輯

規則:判真不判假

說明:
咱們應該指望 if 條件內是個「真」值,而不是一個「假」值。

第二種狀況會致使代碼不易理解。

解決辦法參考 布爾邏輯

BadCode

// if 條件內指望的是一個「假」值
if (!(status !== Closed) { ... }
if (!(status !== Closed || type !== Array)) { ...}

GoodCode

if (status === Closed) { ... }
if (status === Closed && type === Array) { ... }

規則:if 條件中,不易出現超過 3 個邏輯操做符。

例外:if 條件裏能夠被 「且」(&&)邏輯拆分紅多個子條件
說明:複雜的條件判斷會讓代碼不易理解,邏輯上有漏洞的話容易引發 Bug。
解決辦法:聲明中間變量

BadCode

if (srcElem != dropElem && (srcElem.nextSibling || srcElem.nextElementSibling) != dropElem) {...}
if (selectedItem || (selectedEmployee && selectedEmployee.empId && selectedEmployee) || employee) { ... }

GoodCode

const nextSibling = srcElem.nextSibling || srcElem.nextElementSibling
if (srcElem != dropElem &&  nextSibling != dropElem ) {
  ...
}
  
// 複雜的邏輯判斷能夠經過 && 作拆分
if (
     !Array.isArray(cur)
  && cur != null
  && typeof src[key] === 'object'
  && typeof cur === 'object'
) { ... }

規則:不要用嵌套的三元表達式

說明:
人們閱讀嵌套三元表達式時,容易混淆語法的優先級,進而致使理解錯代碼的含義。

對於這種狀況,建議改爲 if else。

若是是在 react render 裏,則建議獨立成函數。

BadCode

function render(props) {
  const value = props.value;
    return <>
    {value < 10 ? value > 0 ? value : 200 - value : 100 - value}
    </>;
}

GoodCode

function getValue(value) {
  if (value < 10) {
    if (value > 0) return value;
    return 200 - value;
  }
  return 100 - value;
}

function render(props) {
  const value = props.value;
    return <>
    {getValue(value)}
    </>;
}

規則:if 條件邏輯嵌套不要超過三層

說明:過深的嵌套會致使理解困難。
解決辦法:合併判斷條件,或者獨立成函數。

BadCode

if (status = Opened) {
    if (type = 'array') {
            if (code = Success) {
            doSomething();
        }
    }
}

GoodCode

if (status = Opened && type = 'array' &&code = Success) {
    doSomething();
}
相關文章
相關標籤/搜索