如何設(shè)計(jì)配置文件掉丽?

配置能簡化項(xiàng)目復(fù)雜度签孔,最經(jīng)典的例子就是webpack。
以webpack 的watch 舉例华临,一行代碼足以:

{
  watch: true芯杀,
  // 其他選項(xiàng)...
} 

如果使用Webpack Node API, 代碼臃腫很多

const webpack = require("webpack");
const compiler = webpack({});

const watching = compiler.watch({},
   (err, stats) => {
    // 回調(diào)處理   
});

配置可以隱藏細(xì)節(jié),暴露我們關(guān)心的參數(shù)雅潭,分而治之揭厚。 另一面,配置靈活性差扶供,無法定制處理棋弥,需要學(xué)習(xí)成本。如果設(shè)計(jì)配置文件诚欠?兼具簡明和靈活性顽染,降低學(xué)習(xí)成本可通過如下方法:

  1. 開箱即用
  2. 使用中間件
  3. 利用函數(shù),“計(jì)算”屬性
  4. 最小化配置
  5. 提高可讀性

1. 開箱即用

最好的配置是不用配置轰绵。 所謂開箱即用粉寞,你不用修改配置,默認(rèn)配置足以支持程序的運(yùn)行左腔。所以請給你的配置準(zhǔn)備份默認(rèn)配置吧唧垦,盡量為所有選項(xiàng)提供默認(rèn)值, 這樣有幾個(gè)好處:

  1. 對(duì)于特殊配置液样,只需關(guān)注特殊點(diǎn)振亮,隱藏共有的細(xì)節(jié)
  2. 修改默認(rèn)配置,即可影響所有配置鞭莽,維護(hù)方便
  3. 默認(rèn)配置可作為配置樣本坊秸,作為新人學(xué)習(xí)、自動(dòng)化測試的依據(jù)

1.1 實(shí)現(xiàn)默認(rèn)配置

雖然可以將默認(rèn)配置作為基類/原型鏈澎怒,按照J(rèn)S通常的繼承方式實(shí)行繼承褒搔, 但這種方式靈活性低,我不推薦喷面。

我更喜歡用混同的方式實(shí)現(xiàn)星瘾, 即提供一個(gè)合并函數(shù),通過此函數(shù)將新配置和默認(rèn)配置合并惧辈,比如webapck-merge

const merge = require('webpack-merge')
const finalConfig =           // finalConfig 最終的config
    merge(defaultConfig,   // 默認(rèn)配置
          confg                 // 新配置
          )

常見的開源合并函數(shù)有l(wèi)odash中的merge, 你也可以寫自己的合并函數(shù)琳状,有些注意事項(xiàng):

  1. 淺合并與深合并。
  2. 數(shù)組的處理盒齿∧畛眩可能的選擇有: 1)替換整個(gè)數(shù)組生宛, 2)合并兩個(gè)數(shù)組, 3) 合并兩個(gè)數(shù)組并去掉重復(fù)項(xiàng)肮柜。
  3. undefindnull倒彰、 0审洞、 ''fasle 這種假值的處理待讳。 假值可能有兩種含義: 1)不需要這個(gè)選項(xiàng)芒澜,需要在合并時(shí)刪除此選項(xiàng)。 2)這個(gè)選項(xiàng)的值有意義,需要保留
  4. 使用純函數(shù)创淡,避免影響原有數(shù)據(jù)
  5. 定制合并痴晦,如果常見的合并規(guī)則無法滿足常見,你需要傳一個(gè)函數(shù)琳彩,進(jìn)行定制化合并誊酌,比如loadash 中的mergeWith。如無特別需求露乏,最好不使用定制合并碧浊,會(huì)嚴(yán)重降低代碼可讀性。

一個(gè)項(xiàng)目最好用相同的合并函數(shù)瘟仿。如無特別需求箱锐,最好不使用定制合并,這會(huì)降低代碼可讀性劳较。

1.3 配置的再組合

對(duì)于復(fù)雜項(xiàng)目驹止,一套默認(rèn)配置可能不夠,我們可以準(zhǔn)備多套配置观蜗,利用合并函數(shù)進(jìn)行組合臊恋。比如webapck4 中提供有mode選項(xiàng), 如果是development, webpack會(huì)啟用開發(fā)階段的默認(rèn)配置墓捻。 如果是production, 啟用生產(chǎn)階段的默認(rèn)配置捞镰。

在網(wǎng)頁應(yīng)用中,默認(rèn)值可能來源于后端毙替,再根據(jù)用戶的交互組合成新的配置岸售,可能出現(xiàn)如下層級(jí)的組合:

  1. 靜態(tài)資源中規(guī)定的默認(rèn)配置。寫在js靜態(tài)文件中厂画,作為兜底配置凸丸,用于白屏或后端加載失敗時(shí)的配置。
  2. 后端加載的默認(rèn)配置袱院。 加載來自后端的配置規(guī)則屎慢。
  3. 根據(jù)用戶的交互選擇相應(yīng)配置瞭稼。 比如一個(gè)博客網(wǎng)站,針對(duì)訪客腻惠、注冊用戶使用不同的規(guī)則环肘。
  4. 最終的配置。
function async getConfig() {
    const staticConfig = { .. }   // 寫在前端JS靜態(tài)資源中的默認(rèn)值
    const remoteConfig = await getRemoteCofing() // 獲取遠(yuǎn)端配置
    const orginConfig = merge(staticConfig, remoteConfig) 
    const userConfig = await getUserConfig() // 獲取用戶交互的配置
    return merge(orginConfig, userConfig) 
}

真實(shí)場景中集灌,配置的組合可能更復(fù)雜悔雹,有更多的層級(jí)。合理的使用合并函數(shù)欣喧,可以輕松組合配置腌零,并可追溯配置。如果使用繼承的方法唆阿,則無法靈活使用益涧,且難以溯源。

合并函數(shù)使用方便驯鳖,如果需復(fù)雜判斷闲询,特別深的層級(jí),配置的可讀性將降低浅辙,你需要選用其他更合適的方式嘹裂。

2. 使用中間件

遇到下面的情況,中間件可能更適合:
1) 需要全局調(diào)整
2) 需要臨時(shí)性動(dòng)態(tài)調(diào)整

你可以在項(xiàng)目的各個(gè)文件摔握,針對(duì)不同情況修改配置寄狼,但隨著業(yè)務(wù)的擴(kuò)展,會(huì)對(duì)配置失去了控制氨淌,不清楚某個(gè)選項(xiàng)在哪個(gè)地方突然被改了泊愧。代碼中到處有充斥的if else語句:

if (user.isVister) {
  config.site.banner = '歡迎光臨'
}

如果你使用中間件思想,則可以解決問題盛正,把對(duì)配置的修改集中到一起删咱, 修改配置的函數(shù)可以放到各個(gè)模塊中

require 'config' from 'config'
require 'updateUserConfig' from 'user/updateUserConfig'
require 'updateSiteConfig' from 'site/updateSiteConfig'

config = updateUserConfig(config)
config = updateSIteConfig(config)

更進(jìn)一步,你也可以使用類似koa中的中間件風(fēng)格代碼豪筝,比如

require 'config' from 'config'
require 'createMiddleUse' from 'createMiddleUse'
require 'updateUserConfig' from 'user/updateUserConfig'
require 'updateSiteConfig' from 'site/updateSiteConfig'

const [use, getConfig] = createMiddle(config)
use(updateUserConfig)
use(updateSiteConfig)
console.log(getConfig()) // 輸出經(jīng)過中間件處理的config

由于沒有找到現(xiàn)成的中間件庫痰滋,你需要自己寫創(chuàng)建中間件createMidddle的方法,實(shí)現(xiàn)這一方便续崖。這一架構(gòu)不是很復(fù)雜敲街,花一點(diǎn)時(shí)間你一定寫的出來。

3. 利用函數(shù)严望,“計(jì)算”屬性

配置中的選項(xiàng)通常是靜態(tài)的棚点,比如webpack 中:

webpackConfig = {
  entry: 'main.js',
  watch: isDevelopment 
    ? true : false,  // 雖然不是靜態(tài)值滔韵,但加載配置后值就固定,所以也是靜態(tài)屬性值
  plugins: [
    new ProgressBarPlugin()
  ]
}

有時(shí)靜態(tài)屬性值不夠用,如需要根據(jù)網(wǎng)站的語言返回不同的價(jià)格

{
  price: 12         // 常規(guī)靜態(tài)寫法
  price (payload) { // 函數(shù)寫法
    const {language, price} = payload
    return language === 'cn' ? price * 1.2: price * 1.1
        }, 
   // ....
}

我們在讀取配置時(shí)增加判斷烂瘫,如果選項(xiàng)是function則讀取函數(shù)結(jié)果,如果是其他類型值則直接返回。此情況類似Vue的計(jì)算屬性, 如果想給函數(shù)緩存挽拂,可使用memoizee

有時(shí)你真的需要將函數(shù)寫入配置中骨饿,由于函數(shù)被占用亏栈,你需要在第一個(gè)函數(shù)中返回一個(gè)函數(shù):

{
  getPrice (payload) {
      // 配置屬性讀取時(shí),執(zhí)行
      return function(payload.price) {
          // 函數(shù)體
      }
  }
}

或者用箭頭模式簡寫:

{
  getPrice: payload =>
    payload.price => {
      // ...函數(shù)體  
    }
}

使用函數(shù)方法處理特殊情況样刷, 使用靜態(tài)值處理一般情況,配置會(huì)靈活強(qiáng)大览爵。

JSON無法解析函數(shù)置鼻,如果配置中包含函數(shù),將配置保存至服務(wù)端會(huì)存在問題蜓竹』福可以將函數(shù)轉(zhuǎn)為字符串存儲(chǔ),并通過eval函數(shù)調(diào)用俱济。我建議不要這么做嘶是, 你可以把函數(shù)寫在JS靜態(tài)文件中,在配置中保存函數(shù)名稱即可蛛碌。

4. 最小配置化

請?jiān)谂渲弥写娣疟M可能少的選項(xiàng):

  1. 設(shè)置盡可能少的選項(xiàng)聂喇。越少的選項(xiàng),學(xué)習(xí)成本越低蔚携,對(duì)新手越友好
  2. 如果默認(rèn)默認(rèn)配置滿足需求希太,不要重寫。第一酝蜒,不方便查找該特殊配置的特殊點(diǎn)誊辉。第二, 如果全局變更默認(rèn)值亡脑,對(duì)該特殊配置將無效堕澄。
  3. 及時(shí)刪除無用配置、廢棄配置霉咨。 無用配置蛙紫、廢棄配置會(huì)讓人產(chǎn)生誤解,不清楚到底是哪個(gè)選項(xiàng)是有效的途戒。
  4. 通過函數(shù)計(jì)算能得到的值不要存在配置中惊来。隨著業(yè)務(wù)的開展,函數(shù)可能改變棺滞, 如果將計(jì)算得到的值寫入配置裁蚁,函數(shù)改變后存的值可能還會(huì)保留矢渊, 需要做前后兼容。如果配置存在服務(wù)器中枉证,兼容處理會(huì)更復(fù)雜矮男。

5. 提高可讀性

使用配置,正是利用配置的高可讀性室谚。為提高可讀性毡鉴,有如下建議:

  1. 準(zhǔn)備文檔,原因不多說秒赤。
  2. 準(zhǔn)備可以正常運(yùn)行的配置樣本猪瞬。對(duì)于配置,最好有文檔入篮,而如果實(shí)在沒法創(chuàng)建陈瘦、更新文檔,至少準(zhǔn)備一個(gè)配置樣本潮售,約束配置規(guī)則痊项、方便新人學(xué)習(xí)。同時(shí)酥诽,如果沒條件給所有配置做自動(dòng)化測試鞍泉,可以僅對(duì)配置樣本做自動(dòng)化配置。如果默認(rèn)配置覆蓋面足夠肮帐,可以將默認(rèn)配置作為配置樣本咖驮。
  3. 使用TypeScript或flow.js做配置的校驗(yàn)。
  4. 將配置存于狀態(tài)管理中训枢,方便配置的查看與修改游沿。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市肮砾,隨后出現(xiàn)的幾起案子诀黍,更是在濱河造成了極大的恐慌,老刑警劉巖仗处,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眯勾,死亡現(xiàn)場離奇詭異,居然都是意外死亡婆誓,警方通過查閱死者的電腦和手機(jī)吃环,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洋幻,“玉大人郁轻,你說我怎么就攤上這事。” “怎么了好唯?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵竭沫,是天一觀的道長。 經(jīng)常有香客問我骑篙,道長蜕提,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任靶端,我火速辦了婚禮谎势,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杨名。我一直安慰自己脏榆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布台谍。 她就那樣靜靜地躺著须喂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪典唇。 梳的紋絲不亂的頭發(fā)上镊折,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天胯府,我揣著相機(jī)與錄音介衔,去河邊找鬼。 笑死骂因,一個(gè)胖子當(dāng)著我的面吹牛炎咖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寒波,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼乘盼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了俄烁?” 一聲冷哼從身側(cè)響起绸栅,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎页屠,沒想到半個(gè)月后粹胯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辰企,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年风纠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牢贸。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竹观,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情臭增,我是刑警寧澤懂酱,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站速址,受9級(jí)特大地震影響玩焰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芍锚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一昔园、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧并炮,春花似錦默刚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至伍俘,卻和暖如春邪锌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背癌瘾。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工觅丰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人妨退。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓妇萄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咬荷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冠句,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345