我是如何將業務程式碼寫優雅的

螞蟻保險體驗技術發表於2019-04-30

0x00 前言

我是一名來自螞蟻金服-保險事業群的前端工程師,在一線大廠的業務部門寫程式碼,非常辛苦但也非常充實。業務程式碼不同於框架程式碼、個人專案或者開源專案,它的特點在於邏輯複雜、前後依賴多、可複用性差、迭代週期短,今天辛辛苦苦寫的程式碼,上線執行一週可能就下線了。能熟練書寫框架程式碼、構建底層基礎設施的工程師不一定能寫好業務程式碼。

有人說,業務程式碼無非就是按部就班,優不優雅?who care?。但實際業務規則複雜得多,不是依葫蘆畫瓢就能輕鬆解決的,寫一段糟糕的程式碼,可能要用雙倍的時間去發現和解決問題,麻煩了自己、也難受了和你並肩作戰的戰友。

有時候為了減少重複開發的成本,反覆提煉和沉澱有複用價值的功能,就需要我們對業務程式碼進行合理抽象、甚至精雕細琢,要把業務程式碼寫得優雅並非易事。我一直認為,程式設計和搬磚的最大區別在於設計二字,寫程式碼也是一門藝術活。今天就藉此機會,站在前端工程師的視角,給大家分享關於書寫業務程式碼的最佳實踐。

0x01 漸進式重構

漸進式重構是不斷地對既有程式碼進行抽象、分離和組合。做程式碼重構之前需要回答兩個問題:

1、什麼樣的程式碼需要重構?
2、何時進行重構?
複製程式碼

設計不是一蹴而就的,有時候寫著寫著才發現某些程式碼可以抽離出來單獨使用,需要重構的程式碼需要滿足幾個條件:

1、程式碼後期可複用
2、程式碼無副作用
3、程式碼邏輯單一
複製程式碼

過早重構可能會因需求變化太快白白浪費許多時間;過晚重構會因為程式碼邏輯複雜、相似程式碼積壓過多導致變更風險太高,難以維護。漸進式重構如下圖所示(紅色部分為增加的程式碼):

enter image description here

首先我們在同一個原始檔中新增功能,發現部分程式碼無副作用且可分離,因此在同一個檔案中進行程式碼分割,形成許多功能單一的模組。如此往復後發現單檔案的體積越來越大,此時就可以將功能相關聯的模組抽出來放到單獨的檔案中統一管理,如 helpers、components、constants 等等。

0x02 高內聚低耦合

高內聚低耦合一直是軟體設計領域裡亙古不變的話題,重構的目標是提高程式碼的內聚性,降低各功能間的耦合程度,降低後期維護成本,特別是寫業務程式碼,這一點相當重要。

舉個例子,比如新需求希望在現有的產品頁面上增加發紅包功能,以吸引使用者開通某個功能,按照正常邏輯,我需要:

1、在當前頁面中引入相關依賴
2、初始化,查詢紅包相關資訊
3、使用者點選時,觸發紅包傳送
複製程式碼

白色部分表示上個版本的程式碼,紅色部分表示完成這個需求需要變更的程式碼:

enter image description here

這樣一來,這個發紅包功能就和以前的程式碼嚴重耦合,如果這是個只需要上線一週的臨時需求,下線程式碼的時候就是一個高風險的動作;如果上線執行期間還需要對產品頁面進行迭代,越往後就越搞不清楚誰是誰了。合理的設計應該是下面這個樣子的:

enter image description here

將和產品程式碼無關的功能性程式碼拆分出來,放到另一個檔案中內部維護好整個生命週期狀態,對外只暴露少量的介面或是方法,這樣一來對產品頁面的改造只需要:

1、引入紅包元件
2、使用者點選時,呼叫紅包元件的發獎方法
複製程式碼

這樣的變更是極小的、明確的、可控的。換句話說,整個紅包功能是高內聚的,與產品程式碼是低耦合的。這樣實踐也帶來另一個好處:我得到了一個可複用的紅包元件!

0x03 合理冗餘

業務需求是多變的,寫出來的程式碼也是如此,頻繁地抽象很可能導致過度設計,一個抽象很可能隨著迭代次數的增多變得十分複雜。在存在多個變數的分支業務場景,比如同時包含活動是否過期、是否已參加活動、是否完成一次任務這樣的情況,會存在多個巢狀 if-else 結構,這時將程式碼冗餘設計是個不錯的選擇。下面舉一個例子來說明什麼是合理冗餘:

e.g. 有這樣一個需求,一開始很簡單,需要設計兩個運營展位:

enter image description here

那麼抽象一個元件:

const Item = ({ title, content }) => (
  <div>
    <h4>{title}</h4>
    <p>{content}</p>
  </div>
);
複製程式碼

現在需求要求在第一個展位的標題上增加熱文標記:

enter image description here

也很容易:

const Item = ({ title, content }, index) => (
  <div>
    <h4>{title}{index === 0 && <span>hot</span>}</h4>
    <p>{content}</p>
  </div>
);
複製程式碼

需求又變了,要求:在第一個展位去掉內容,並且在下方加個按鈕;第二個展位的標題右邊增加一個超連結以及增加一個副標題:

enter image description here

這下有點噁心了:

const Item = ({ title, content }, index) => (
  <div>
    <h4>
      {title}
      {index === 0 && <span>hot</span>}
      {index === 1 && <a href="xxx">去看看</a>}
    </h4>
    {index === 1 && <h5>副標題</h5>}
    <p>
      {index !== 0 && content}
      {index === 0 && <button>領福利<button>}
    </p>
  </div>
);
複製程式碼

可以看到,之前抽象的好好的,現在需求一變,程式碼就面目全非了,中間混雜著兩個狀態(第一個、第二個)的判斷邏輯。實際情況很可能比這個更復雜,在多狀態交織邏輯難以通過一套程式碼表達清楚時,進行合理冗餘就是個不錯的選擇,將上面的例子用兩個 if 重寫如下:

// 第一個展位
if (index === 0) {
  return (
    <div>
      <h4>標題一<span>hot</span></h4>
      <p><button>領福利<button></p>
    </div>
  );
}
// 第二個展位
if (index === 1) {
  return (
    <div>
      <h4>標題二<a href="xxx">去看看</a></h4>
      <h5>副標題</h5>
      <p>內容</p>
    </div>
  );
}
複製程式碼

合理冗餘其實也是一種重構,根據業務邏輯和程式碼規模,做相似抽象還是程式碼冗餘,這其實也是漸進式重構的一種體現。無論採用何種方式,只要能把業務邏輯表達清楚,讓程式碼始終保持良好的可讀性和可維護性,就OK。

下面介紹一個過度抽象的例子。

0x04 拒絕過度抽象

在 JavaScript 程式碼中進行深度抽象有時並非好事,有 OOP(物件導向程式設計)背景的同學很容易先入為主設計:所有資料結構都想封裝成一個類 (Class) 。實際上 Class 在 JavaScript 中是個不好的設計,它並非真正的類。幾年前,我曾看到一位 Java 轉前端的同學寫出了類似這樣的程式碼:

class DataItem {
  constructor(id, name, value) {
    this.id = id;
    this.name = name;
    this.value = value;
  }
}

class DataCollection {
  constructor() {
    this.items = new Array();
  }
  insert(item) {
    this.items.push(item);
  }
}

const item1 = new DataItem(1, 'name1', 100);
const item2 = new DataItem(2, 'name2', 200);
const list = new DataCollection();
list.insert(item1);
list.insert(item2);
...
複製程式碼

一股濃濃的 Java 味道撲面而來。上面的程式碼並沒有發揮出 JavaScript 的語言優勢,也增加了不少理解成本,如果用物件導向程式設計的思路去寫前端程式碼,特別是業務程式碼,可真是一場噩夢。正確的寫法如下:

const list = [{
  id: 1,
  name: 'name1',
  value: 100
}, {
  id: 2,
  name: 'name2',
  value: 200
}];
複製程式碼

由於 JS 屬於弱型別語言,弱型別語言就要發揮弱型別的優勢,無需過多型別定義和 Class 抽象,用最原始的 object 和 function 足以勝任從簡單到複雜的業務場景。這裡特別想提及前端所熟知的 Redux 狀態管理器,Redux 中,state 就是普通的 object,reducer 就是普通的 function,action 也是普通的 object,不加任何型別約束。因為簡單,所以強大。

0x05 眼觀六路

用弱型別語言程式設計意味著無需編譯,無需編譯的語言天生存在一個問題是在執行前缺少必要的型別檢查,將問題暴露在執行時往往會導致非常嚴重的故障。這就要求開發者能在寫程式碼的階段嚴格保證程式碼質量,特別是寫業務程式碼。

整合開發環境(IDE)對 JavaScript 程式碼的智慧提示能力有限,很多時候不能通過 IDE 查詢某個變數或者函式的所有引用,這時就要善用 Ctrl + F 進行全域性查詢來保證自己的單點變更不會影響到其他地方。如果使用 TypeScript,在型別檢查、引用查詢上的幫助會更好。

0x06 總結

今天給大家分享了關於書寫業務程式碼的一些實踐經驗:對程式碼進行漸進式重構是提升程式碼健壯性的有力武器;設計高內聚低耦合的程式碼可以讓你在做需求的過程中沉澱出一套通用解決方案;合理冗餘可以簡化複雜的場景,讓開發變得高效、測試變得容易;拒絕過度抽象,擁抱簡單,靈活變化。保持 眼觀六路 的好習慣能讓程式碼質量提升一個臺階。

最後,希望大家能在實際開發過程中去體會和學習,不斷思考和總結,將業務程式碼寫優雅,是個很大的挑戰。


關於我們:

我們是螞蟻保險體驗技術團隊,來自螞蟻金服保險事業群。我們是一個年輕的團隊(沒有歷史技術棧包袱),目前平均年齡92年(去除一個最高分8x年-團隊leader,去除一個最低分97年-實習小老弟)。我們支援了阿里集團幾乎所有的保險業務。18年我們產出的相互寶轟動保險界,19年我們更有多個重量級專案籌備動員中。現伴隨著事業群的高速發展,團隊也在迅速擴張,歡迎各位前端高手加入我們~

我們希望你是:技術上基礎紮實、某領域深入(Node/互動營銷/資料視覺化等);學習上善於沉澱、持續學習;性格上樂觀開朗、活潑外向。

如有興趣加入我們,歡迎傳送簡歷至郵箱:yiyuan.lpy@antfin.com


本文作者:螞蟻保險-體驗技術組-禕遠

掘金地址:micooz

相關文章