­

邏輯管理:解決方案(一) – 關於前端邏輯管理的設計和實現

  • 2019 年 10 月 3 日
  • 筆記

切入思考點

組件化,解決了一組可以復用的功能,我們可使用一般的開源的公共組件,也可以針對我們特殊業務場景,沉澱出符合自己業務的業務組件;

工程化,解決了可控和規範性的功能,我們可使用開源的一些腳手架比如vue-cli、create-react-app等,或者公司內部自己沉澱的內部腳手架解決方案;

但是誰來解決散落在各個模組和工程中的邏輯?怎樣去避免硬程式碼編程,減少邏輯的後期維護和成本等等,也是一個需要考慮的點。

 

觀察程式碼

首先可以從一個客觀角度去分析這份程式碼,review這份程式碼,可以看出很多問題,比如:

    • 開頭的配置參數和類型檢查的配置,程式碼佔了很大篇幅,是否可以抽離到配置文件管理里去維護?
    • tools工具類是否可以進行重構,一個tools聚合了很多不同類型的輔助方法,後期增長是否會持續臃腫,是否可以通過分類歸納,tools管理更清晰明了
    • tools的內部工具,是否可以拆分成只做一件事和多件事共同完成一件事方式?
    • 太長的函數,是否有拆分的可能,增強可讀性要求?
    • 很多方法依賴自身對象的其他方法,整個鏈路的流轉複雜多變,牽一髮動全身。
    • 程式碼能力劃分不明確,通用和非通用沒有明確界定
    • 對外暴露能力的程式碼重複度比較高
    • ……

當時最初寫這份程式碼還做過簡單的分類,有點邏輯管理的淺顯意識。但是我們可以看看我們自己真實用於生產的公司的項目,多人維護,協同開發、業務增長等,到最後已經完全不可控,邏輯動都不敢動,只敢打修補程式,越來越臃腫。下面就是我之前針對我們內部項目一小塊做的一塊分析,這些都真實存在幾乎所有人的程式碼里,是我們存在的痛點。 

    • 單獨時間處理函數,是否可以抽離到公用邏輯中,基於原型鏈的屬性,是否會污染和覆蓋原型鏈屬性等
    • 業務交互設計功能,是否可以封裝到獨立函數中?
    • 枚舉統一抽離管理?
    • 請求抽離統一管理?
    • 數據的轉換賦值處理?
    • 複雜文案拼裝,抽象到函數中,提高可讀性?減輕複雜度?
    • 多重邏輯判斷是否可簡化表達式?分解複雜條件,合併行為一致?
    • ….

 

前端對業務做了什麼?

基於之前對程式碼的分析,堆積了很多問題,說明這塊確實是我們的痛點。那麼這些痛點歸根究底是我們做了什麼導致?前端對業務到底做了哪些方面的東西?

    1. 獲取業務數據(業務規則下的數據獲取)
    2. 數據處理(可細分轉換,格式化,校驗等等)
    3. 業務判斷(針對業務場景,每個場景下需要做什麼)
    4. 業務數據提交(業務規則產出的數據的記錄)
    5. 業務交互功能(在業務規則下,需要怎麼做,做怎樣的功能)
    6. 業務展示(在業務場景下,合理的show出業務的形態)
    7. ……(暫時只想到這些領域,如有遺漏歡迎補充)

以上,幾乎囊括了前端在業務領域,所需要做的所有事情,也是我們的所有的邏輯。

 

對邏輯的深入思考

我們需要這些邏輯的堆砌去完成我們需要的東西,其實觀察每一小塊業務程式碼,都是由一條條最簡單的邏輯規則,一步步流轉到最後我們所需要的結果的,就跟我們做的思維腦圖一樣,一個流程節點都是一個小邏輯。一個業務的開始,到一個業務的結束,都是由每個最小的邏輯點組成的。

so,我們能不能站在一個全局的角度去看整個業務,能不能把每個流程節點打碎成一個最小的原子,所有的業務邏輯,都是從最小的原子一個一個組裝起來的,這樣,我們就能更專註於最小的邏輯。我們所做的任何業務都是由原子拼起來。這樣就可以從基礎去hold住任何邏輯,不管複雜和簡單。

我們也可以參考,在Java或者其他後端語言里,設計最初是最理想。它們都希望,我的世界就和現實世界一樣,都是由最小的顆粒去組裝我想要的設計的世界。所以一個class代表了一類事情,一個function代表了一件事。無論你們上面怎麼玩,我都能支援你們去組裝你們要的世界,你們要做的任何複雜的事。所以,邏輯處理其實也是這樣的,把任何邏輯打成最小顆粒,通過拼接,組裝,去支撐上層的任何業務邏輯。

 

如此之後,設想如下場景:

    • 只關心原子邏輯,去豐富原子邏輯
    • 業務邏輯,在原子提供的邏輯上適應任何業務規則,通過組裝去產出任何業務程式碼
    • 業務規則變化下,小變化,直接替換一個邏輯節點,替換插槽。大變化,重新組裝另一條業務線。
    • 整個鏈路數據流轉清晰可追蹤

 

理想設計架構圖

image.png

 

簡單摸索設計思路

原子邏輯:對象的基類,管理所有注入原子

組合邏輯:繼承原子,組合,輸出

對外介面:解析配置,調用原子和組合類管理、拋出生產結果

 

思路圖如下:

 

基類設計程式碼

// 原子管理類,管理所有原子邏輯  class Atom {      /*    * 注入原子邏輯,以屬性的方式管理    *   objArr: 原子邏輯數組    * */    setBasics(objArr) {      objArr.forEach(x => {        this[x.name] = x.assembly      })    }      /*    * 生產組裝類所需要的原子    *   param    *     useBasics:組裝類,所需要繼承的原子    *       支援type: String - 指定一個、Array - 指定多個、無(undefined)- 所有    *    *   return    *     output:生產出的原子邏輯    * */    machiningBasics(useBasics) {      let output = {}      if (useBasics) {        if (Array.isArray(useBasics)) {          useBasics.forEach(x => {            Object.assign(output, this[x])          })        } else {          Object.assign(output, this[useBasics])        }      } else {        Object.keys(this).forEach(x => {          Object.assign(output, this[x])        })      }      return output    }  }    export default Atom

基類,作為最底層的基礎模組,管理所有原子,供上層業務邏輯繼承和調用,去組裝自己的業務邏輯。該類內部拋出2個方法如下:

setBasics

作為對原子邏輯的注入。可以持續去豐富底層的原子邏輯(後期是否支援動態注入,再考慮);

machiningBasics

提供給組裝類繼承原子的邏輯,輸出所需要的底層基礎,供上游拼裝

 

組裝類設計程式碼

// 因ES6不支援私有屬性,所以將私有屬性放到外層    /*  * 生產組裝對象,並注入指定作用域  *   param -  *  *   return  *     Temporary:組裝對象  *  * */  function makeObject() {    function Temporary(assembly) {      for (let key in assembly) {        this[key] = assembly[key].bind(this)      }    }      return Temporary  }    /*  * 組裝中是否透傳原子方法  *   param  *     Temporary:組裝對象  *     config: 組裝的配置  *  *   return  *     output:輸出最終邏輯  * */  function isThrough(Temporary, config) {    // 根據配置,實例化對象    let temp = new Temporary(config.assembly)    let output = {}    for (let key in temp) {      // 是否開啟配置      if (config.through  === false) {        // 是否是自身屬性        if (temp.hasOwnProperty(key)) {          output[key] = temp[key]        }      } else {        output[key] = temp[key]      }    }    return output  }    // 組裝類,管理組裝和輸出。  class Package {      /*    * 注入組裝配置    *   param    *     config:組裝配置    *     prototype:組裝所依賴的原子屬性    *    *   return  生產完成的對象    * */    setPackage(config, prototype) {      let temp = makeObject(config)      temp.prototype = prototype      return isThrough(temp, config)    }    }    export default Package

組裝類,通過一系列的原子邏輯組裝成一條條所需要的業務邏輯。整體步驟為:生產出組裝的對象,通過原型繼承裝配原子,對外暴露組裝結果。就跟工廠一樣,生產目標,生產原料,生產產物。組裝類對內部拋出一個方法:

setPackage

根據提供的配置文件以及所需繼承的原子,組裝出一類業務邏輯。

 

index入口設計

import Atom from './atom/index'  import Package from './package/index'    // 實例化原子和組裝類  const _atom = new Atom()  const _package = new Package()    // 生產原子快取  let _globalCache = {}    /*  * 對外暴露,注入配置依賴,生產組裝  *   param  *     param: 配置參數  * */  export const injection = function (param) {    _atom.setBasics(param.atom)      param.package.forEach(x => {      let prototype = _atom.machiningBasics(x.extends)      // 快取組裝      _globalCache[x.name] = _package.setPackage(x, prototype)    })  }    /*  * 對外暴露,獲取生產完成的組裝對象  *   param  *     param:獲取的目標  *       type:String - 指定一個、Array - 指定多個、 無(undefined) - 全部  *  *   return  *     output:生產結束的對象  * */  export const getMateriel = function (param) {    let output = {}    if (param) {      if (Array.isArray(param)) {        return param.forEach(x => {          output[x] = _globalCache[x]        })      } else {        output = _globalCache[param]      }    } else {      output = _globalCache    }    return output  }

對外的入口,主要功能為解析配置,組裝配置,輸出組裝結果供使用3大功能。

injection

標準對外入口,進行邏輯管理的初始化,該方法將所有的原子邏輯注入到原子類里,再通過組裝配置,從原子類獲取到每個組裝對象所需要繼承的原子供組裝使用,最後將組裝好的邏輯全局存到一個全局的快取里。

getMateriel

對外輸出生產完成的組裝邏輯,暴露出組裝結束的結果,可獲取所有組裝結果,也可單獨或者批量獲取結果

 

使用格式規定 

默認注入配置(injection方法)

/*  *  injection方法注入對象的格式  *   atom:     所有的原子邏輯  *   package:  組裝原子的邏輯  */  {    atom: ['原子邏輯1', '原子邏輯2'],    package: ['組裝邏輯1', '組裝邏輯2']  }

 

原子邏輯文件格式

/*  *   該格式為原子邏輯的標準格式  *     name:       原子類的名稱  *     assembly:   原子的方法存放的對象  */  export default {    name: '原子的名稱',    assembly: {      // 原子邏輯所對外提供的方法      sendRequest() {        // do something      }    }  }

 

組裝邏輯文件格式

/*  *   該格式為組裝邏輯的標準格式  *     name:       組裝類的名稱  *     extends:    組裝類需要繼承的原子  *     through:    是否透傳原子類內部的資訊  *     assembly:   原子的方法存放的對象  */  export default {    name: '組裝類名稱',    extends: '繼承原子',      // 支援字元串(單原子)、無(默認繼承所有原子)、數組(指定多個原子)    assembly: {      // 組裝邏輯對外產出的方法,可直接this.來調用繼承原子的方法      getAtom1Promise() {        // do something...      }    }  }

 

DEMO展示

目錄格式

-src

  |-atom                // 存放原子邏輯的地方

  |-package          //  存放組裝邏輯的地方

  |-index.js           //  入口文件

 

原子邏輯(atom)

export default {    name: 'atom1',    assembly: {      sendRequest() {        return new Promise((res, rej) => {          setTimeout(function () {            res([1, 2, 3])          }, 3000)        })      }    }  }
export default {    name: 'atom2',    assembly: {      judgeArray(data) {        return Array.isArray(data)      }    }  }

 

組裝邏輯(package)

export default {    name: 'package1',    extends: 'atom1',    assembly: {      getAtom1Promise() {        this.sendRequest()          .then(x => {            console.warn('使用成功', x)          })      }    }  }
export default {    name: 'package2',    through: false,    assembly: {      packageLogin() {        this.sendRequest()          .then(x => {            console.warn('判斷是否是數組:', this.judgeArray(x))          })      }    }  }

 

入口注入(index)

import {injection, getMateriel} from '@fines/factory-js'    import atom1 from './atom/atom1'  import atom2 from './atom/atom2'  import package1 from './package/package1'  import package2 from './package/package2'    injection({    atom: [atom1, atom2],    package: [package1, package2]  })    console.warn('組裝成功:', getMateriel())    // 測試package1方法  getMateriel('package1').getAtom1Promise()    // 測試package2方法  getMateriel('package2').packageLogin()

 

測試結果

 

 

npm發布

包名

@fines/factory-js

安裝

npm i @fines/factory-js

註明

fines作為一個新的註冊的組織,這裡將寫一些更美好的東西,以後所有能變得更美好的程式碼都將發布到這個包下面(更重要一些包名已經無法使用,但是組織可以無限制)

 

github託管

地址

https://github.com/GerryIsWarrior/factory-js     感覺有參考意義可以點個star,內部正在使用踩坑中

Issues

https://github.com/GerryIsWarrior/factory-js/issues

demo地址

https://github.com/GerryIsWarrior/factory-js/tree/master/demo

PS:可直接clone下來 npm run start  直接跑起來測試

 

後記

以前在邏輯管理領域做過相關的摸索和思考,如下:

    1. 思考書寫更好可控的程式碼
    2. 探索複雜前端業務的開發與設計

在之前的摸索基礎上,更深入的思考,才最終產出這個邏輯的解決方案,僅供大家參考,後面仍將持續完善該方案。

 

社區有人說,這不是你前端做的事,不是你的活,做這個幹啥?聽完這句話,總感覺有點彆扭。

在我看來,我們每個人都是一個架構師,不斷地在架構自己的程式碼。不停的去認知世界的樣子,認知自我。我們都不是最完美的,有好也有壞。去發現自身痛點,對痛點進行分析,進行思考,找出最終的根源,然後再去思考如何去解決這個痛點,嘗試,摸索,失敗,階段性勝利,再繼續。就這樣一路走來,堅信終有收穫。共勉!

 

 

下期方向

 

組裝原子如何和原子共存,共建上層輸出邏輯?

因為有些通過原子邏輯組成的通用方法,也可以作為基礎原子繼續使用的,如何注入管理作為下一期課題研究。