自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

你知道如何寫一個(gè)框架嗎?詳細(xì)步驟放送

開發(fā) 后端 架構(gòu)
作者也沒寫過什么框架,只是分享一些自己的理解,拋磚引玉罷了。如果你寫過一些框架可能會(huì)產(chǎn)生一些共鳴歡迎討論,如果你正在寫或正打算寫一個(gè)框架可能會(huì)給你一些啟發(fā)。本文以為較長(zhǎng)可能會(huì)分多個(gè)篇博客來寫,現(xiàn)在能想到的是主要分為步驟、模式兩部分。如果你覺得好,按一個(gè)推薦舉手之勞讓更多的人可以看到。寫本文的時(shí)候作者完全是把腦子里的東西寫了出來,沒有參考任何的資料,所以對(duì)于每一項(xiàng)內(nèi)容可能都是不完整的,不能作為一個(gè)完整的參考。有一些方法學(xué)的東西每個(gè)人都有自己的喜好,沒有覺得的對(duì)和錯(cuò)。
定位

所謂定位就是回答幾個(gè)問題,我出于什么目的要寫一個(gè)框架,我的這個(gè)框架是干什么的,有什么特性適用于什么場(chǎng)景,我的這個(gè)框架的用戶對(duì)象是誰,他們會(huì)怎么使用,框架由誰維護(hù)將來怎么發(fā)展等等。

  1. 如果你打算寫框架,那么肯定心里已經(jīng)有一個(gè)初步的定位,比如它是一個(gè)緩存框架、Web MVC框架、IOC框架、ORM/數(shù)據(jù)訪問框架、RPC框架或是一個(gè)用于Web開發(fā)的全棧式框架。
  2. 是 否要重復(fù)造輪子?除非是練手項(xiàng)目,一般我們是有了解決不了問題的時(shí)候才會(huì)考慮不使用既有的成熟的框架而重復(fù)造輪子的,這個(gè)時(shí)候需要列出新框架主要希望解決 什么問題。有關(guān)是否應(yīng)該重復(fù)造輪子的話題討論了很多,我的建議是在把問題列清后進(jìn)行簡(jiǎn)單的研究看看是否可以通過擴(kuò)展現(xiàn)有的框架來解決這個(gè)問題。一般而言大 部分成熟的框架都有一定的擴(kuò)展和內(nèi)部組件的替換能力,可以解決大部分技術(shù)問題,但在如下情況下我們可能不得不自己去寫一個(gè)框架,比如即使通過擴(kuò)展也無法滿 足技術(shù)需求、安全原因、需要更高的生產(chǎn)力、需要讓框架和公司內(nèi)部的流程更好地進(jìn)行適配、開源的普適框架無法滿足性能需求、二次開發(fā)的成本高于重新開發(fā)的成 本等等。
  3. 主打輕量級(jí)?輕量級(jí)是很多人打算自己寫一個(gè)新框架的原因,但我們要明白,大部分項(xiàng)目在一開始的時(shí)候其實(shí)都是輕量級(jí)的,隨著框架 的用戶越來越多,它必定需要滿足各種奇怪的需求,在經(jīng)過了無數(shù)次迭代之后,框架的主線流程就會(huì)多很多擴(kuò)展點(diǎn)、檢測(cè)點(diǎn),這樣框架勢(shì)必變得越來越重(從框架的 入口到框架的工作結(jié)束的方法調(diào)用層次越來越多,勢(shì)必框架也就越來越慢),如果你打算把框架定位于一個(gè)輕量級(jí)的框架的話,那么在今后的迭代過程中需要進(jìn)行一 些權(quán)衡,在心中有堅(jiān)定的輕量級(jí)的理念的同時(shí)不斷做性能測(cè)試來確保框架的輕量,否則隨著時(shí)間的發(fā)展框架可能會(huì)越來越重進(jìn)而偏離了開始的定位。
  4. 特性?如果你打算寫一個(gè)框架,并且只有輕量級(jí)這一個(gè)理由的話,你或許應(yīng)該再為自己的框架想一些新特性,就像做一個(gè)產(chǎn)品一樣,如果找不出兩個(gè)以上的亮點(diǎn),那么這個(gè)產(chǎn)品不太可能成功,比如你的新框架可以是一個(gè)零配置的框架,可以是一個(gè)前端開發(fā)也能用的后端框架。
  5. 其它?一般來說框架是給程序員使用的,我們要考慮框架使用的頻度是怎么樣的,這可能決定的框架的性能需求和穩(wěn)定性需求。還有,需要考慮框架將來怎么發(fā)展,是希望走開源路線還是商業(yè)路線。當(dāng)然,這些問題也可以留到框架有一個(gè)大致的結(jié)構(gòu)后再去考慮。

我們來為本文模擬一個(gè)場(chǎng)景,假設(shè)我們覺得現(xiàn)有的Spring MVC等框架開發(fā)起來效率有點(diǎn)低,打算重復(fù)造輪子,對(duì)于新框架的定位是一個(gè)給Java程序員使用的輕量級(jí)的、零配置的、易用的、易擴(kuò)展的Web MVC框架。


調(diào)研

雖然到這里你已經(jīng)決定去寫一個(gè)框架了,但是在著手寫之前還是至少建議評(píng)估一下市面上的類似(成熟)框架。需要做的是通讀這些框架的文檔以及閱讀一些源碼,這么做有幾個(gè)目的:

  1. 通過分析現(xiàn)有框架的功能,可以制定出一個(gè)新框架要實(shí)現(xiàn)的功能列表。
  2. 通過分析現(xiàn)有框架的問題,總結(jié)出新框架需要避免的東西和改善的地方。
  3. 通過閱讀現(xiàn)有框架的源碼,幫助自己理清框架的主線流程為總體設(shè)計(jì)做鋪墊(后面總體設(shè)計(jì)部分會(huì)更多談到)。
  4. 如果能充分理解現(xiàn)有的框架,那么你就是站在巨人的肩膀上寫框架,否則很可能就是在井底造輪子。

新 開發(fā)一個(gè)框架的好處是沒有兼容歷史版本的包袱,但是責(zé)任也同樣重大,因?yàn)槿绻麑?duì)于一開始的定位或設(shè)計(jì)工作沒有做好的話,將來如果要對(duì)格局進(jìn)行改變就會(huì)有巨 大的向前兼容的包袱(除非你的框架沒有在任何正式項(xiàng)目中使用),兼容意味著框架可能會(huì)越來越重,可能會(huì)越來越難看,閱讀至少一到兩個(gè)開源實(shí)現(xiàn),做好充分的 調(diào)研工作可以使你避免犯大錯(cuò)。

假設(shè)我們?cè)u(píng)估了一些主流框架后已經(jīng)很明確,我們的MVC框架是一個(gè)Java平臺(tái)的、基于Servlet的輕量級(jí)的Web MVC框架,主要的理念是約定優(yōu)于配置,高內(nèi)聚大于低耦合,提供主流Web MVC框架的大部分功能,并且易用方面有所創(chuàng)新,新特性體包括:

  1. 起手零配置,總體上約定由于配置,即使需要擴(kuò)展配置也支持通過代碼和配置文件兩種方式進(jìn)行配置。
  2. 除了Servlet之外不依賴其它類庫,支持通過插件方式和諸如Spring等框架進(jìn)行整合。
  3. 更優(yōu)化的項(xiàng)目結(jié)構(gòu),不需要按照傳統(tǒng)的Java Web項(xiàng)目結(jié)構(gòu)那樣來分離代碼和WEB-INF,視圖可以和代碼在一起,閱讀代碼更便利。
  4. 攔截器和框架本身更緊密,提供Action、Controller和Global三個(gè)級(jí)別的"攔截器"(或者說過濾器)。
  5. 豐富的Action的返回值,返回的可以是視圖、可以是重定向、可以是文件、可以是字符串、可以是Json數(shù)據(jù),可以是Javascript代碼等等。
  6. 支持針對(duì)測(cè)試環(huán)境自動(dòng)生成測(cè)試的視圖模型數(shù)據(jù),以便前端和后端可以同時(shí)開發(fā)項(xiàng)目。
  7. 支持在開發(fā)的時(shí)候自動(dòng)生成路由信息、模型綁定、異常處理等配置的信息頁面和調(diào)試頁面,方便開發(fā)和調(diào)試。

提供一套通用的控件模版,使得,并且支持多種模版引擎,比如Jsp、Velocity、Freemarker、Mustache等等。

嗯,看上去挺誘人的,這是一個(gè)不錯(cuò)的開端,如果你要寫的框架自己都不覺得想用的話,那么別人就更不會(huì)有興趣來嘗試使用你的框架了。

#p#
解決難點(diǎn)

之 所以把解決難點(diǎn)放在開搞之前是因?yàn)?,如果?shí)現(xiàn)這個(gè)框架的某些特性,甚至說實(shí)現(xiàn)這個(gè)框架的主流程有一些核心問題難以解決,那么就要考慮對(duì)框架的特性進(jìn)行調(diào) 整,甚至取消框架的開發(fā)計(jì)劃了。有的時(shí)候我們?cè)谟肁平臺(tái)的時(shí)候發(fā)現(xiàn)一個(gè)很好用的框架,希望把這個(gè)框架移植到B平臺(tái),這個(gè)想法是好的,但之所以在這以前這么 多年沒有人這么干過是因?yàn)檫@個(gè)平臺(tái)的限制壓根不可能實(shí)現(xiàn)這樣的東西。比如我們要實(shí)現(xiàn)一個(gè)MVC框架,勢(shì)必需要依賴平臺(tái)提供的反射特性,如果你的語言平臺(tái)壓 根就沒有運(yùn)行時(shí)反射這個(gè)功能,那么這就是一個(gè)非常難以解決的難點(diǎn)。又比如我們?cè)谀硞€(gè)平臺(tái)實(shí)現(xiàn)一個(gè)類似于.NET平臺(tái)Linq2Sql的數(shù)據(jù)訪問框架,但如 果這個(gè)目標(biāo)平臺(tái)的開發(fā)語言并不像C#那樣提供了類型推斷、匿名類型、Lambda表達(dá)式、擴(kuò)展方法的話那么由于語法的限制你寫出來的框架在使用的時(shí)候是無 法像.NET平臺(tái)Linq2Sql那樣優(yōu)雅的,這就違背了實(shí)現(xiàn)框架的主要目的,實(shí)現(xiàn)新的框架也就變得意義不大了。

對(duì)于我們要實(shí)現(xiàn)的MVC框 架貌似不存在什么根本性的無法解決的問題,畢竟在Java平臺(tái)已經(jīng)有很多可以參考的例子了。如果框架的實(shí)現(xiàn)總體上沒什么問題的話,就需要逐一評(píng)估框架的這 些新特性是否可以解決。建議對(duì)于每一個(gè)難點(diǎn)特性做一個(gè)原型項(xiàng)目來證明可行,以免在框架實(shí)現(xiàn)到一半的時(shí)候發(fā)現(xiàn)有無法解決的問題就比較尷尬了。

分析一下,貌似我們要實(shí)現(xiàn)的這8大特性只有第1點(diǎn)要研究一下,看看如何免配置通過讓代碼方式讓我們的Web MVC框架可以和Servlet進(jìn)行整合,如果無法實(shí)現(xiàn)的話,我們可能就需要把第1點(diǎn)特性從零配置改為一分鐘快速配置了。


開搞

首先需要給自己框架取一個(gè)名字,取名要考慮到易讀、易寫、易記,也需要盡量避免和市面上其它產(chǎn)品的名字重復(fù),還有就是最好不要起一個(gè)侮辱其它同類框架的名字以免引起公憤。
如果將來打算把項(xiàng)目搞大的話,可以提前注冊(cè)一下項(xiàng)目的相關(guān)域名,畢竟現(xiàn)在域名也便宜,避免到時(shí)候項(xiàng)目名和域名差距很大,或項(xiàng)目的.com或.org域名對(duì)應(yīng)了一個(gè)什么不太和諧的網(wǎng)站這就尷尬了。
然后就是找一個(gè)地方來托管自己的代碼,如果一開始不希望公開代碼的話,最好除了本地源代碼倉庫還有一個(gè)異地的倉庫以免磁盤損壞導(dǎo)致抱憾終身,當(dāng)然如果不怕出丑的話也可以在起步的時(shí)候就使用Github等網(wǎng)站來托管自己的代碼。

#p#
總體設(shè)計(jì)

對(duì) 于總體設(shè)計(jì)我的建議是一開始不一定需要寫什么設(shè)計(jì)文檔畫什么類圖,因?yàn)榭赡芤婚_始的時(shí)候無法形成這么具體的概念,我們可以直接從代碼開始做第一步??蚣艿?使用者一般而言還是開發(fā)人員,拋開框架的內(nèi)在的實(shí)現(xiàn)不說,框架的API設(shè)計(jì)的好壞取決于兩個(gè)方面。對(duì)于普通開發(fā)人員而言就是使用層面的API是否易于使 用,拿我們的MVC框架舉例來說:

最基本的,搭建一個(gè)HelloWorld項(xiàng)目,聲明一個(gè)Controller和Action,配置一個(gè)路由規(guī)則讓Get方法的請(qǐng)求可以解析到這個(gè)Action,可以輸出HelloWorld文字,怎么實(shí)現(xiàn)?
如果要實(shí)現(xiàn)從Cookie以及表單中獲取相關(guān)數(shù)據(jù)綁定到Action的參數(shù)里面,怎么實(shí)現(xiàn)?
如果要配置一個(gè)Action在調(diào)用前需要判斷權(quán)限,在調(diào)用后需要記錄日志,怎么實(shí)現(xiàn)?

我們這里說的API,它不一定全都是方法調(diào)用的API,廣義上來說我們認(rèn)為框架提供的接入層的使用都可以認(rèn)為是API,所以上面的一些功能都可以認(rèn)為是MVC框架的API。

框架除了提供基本的功能,還要提供一定程度的擴(kuò)展功能,使得一些復(fù)雜的項(xiàng)目能夠在某些方面對(duì)框架進(jìn)行增強(qiáng)以適應(yīng)各種需求,比如:

  1. 我的Action是否可以返回圖片驗(yàn)證碼?
  2. 我的Action的參數(shù)綁定是否可以從Memcached中獲取數(shù)據(jù)?
  3. 如果出現(xiàn)異常,能否在開發(fā)的時(shí)候顯示具體的錯(cuò)誤信息,在正式環(huán)境顯示友好的錯(cuò)誤頁面并且記錄錯(cuò)誤信息到數(shù)據(jù)庫?

一 般而言如果要實(shí)現(xiàn)這樣的功能就需要自己實(shí)現(xiàn)框架公開的一些類或接口,然后把自己的實(shí)現(xiàn)"注冊(cè)"到框架中,讓框架可以在某個(gè)時(shí)候去使用這些新的實(shí)現(xiàn)。這就需 要框架的設(shè)計(jì)者來考慮應(yīng)該以怎么樣的友好形式公開出去哪些內(nèi)容,使得以后的擴(kuò)展實(shí)現(xiàn)在自由度以及最少實(shí)現(xiàn)上的平衡,同時(shí)要兼顧外來的實(shí)現(xiàn)不破壞框架已有的 結(jié)構(gòu)。

要想清楚這些不是一件容易的事情,所以在框架的設(shè)計(jì)階段完全可以使用從上到下的方式進(jìn)行設(shè)計(jì)。也就是不去考慮框架怎么實(shí)現(xiàn),而是以一 個(gè)使用者的身份來寫一個(gè)框架的示例網(wǎng)站,API怎么簡(jiǎn)單怎么舒服就怎么設(shè)計(jì),只從使用者的角度來考慮問題。對(duì)于相關(guān)用到的類,直接寫一個(gè)空的類(能用接口 的盡量用接口,你的目的只是通過編譯而不是能運(yùn)行起來),讓程序可以通過編譯就可以了。你可以從框架的普通使用開始寫這樣一個(gè)示例網(wǎng)站,然后再寫各種擴(kuò)展 應(yīng)用,在此期間你可能會(huì)用到框架內(nèi)部的20個(gè)類,這些類就是框架的接入類,在你的示例網(wǎng)站通過編譯的那剎那,其實(shí)你已經(jīng)實(shí)現(xiàn)了框架的接入層的設(shè)計(jì)。

這里值得一說的是API的設(shè)計(jì)蘊(yùn)含了非常多的學(xué)問以及經(jīng)驗(yàn),要在目標(biāo)平臺(tái)設(shè)計(jì)一套合理易用的API首先需要對(duì)目標(biāo)平臺(tái)足夠了解,每一個(gè)平臺(tái)都有一些約定俗成的規(guī)范,如果設(shè)計(jì)的API能符合這些規(guī)范那么開發(fā)人員會(huì)更容易接受這個(gè)框架,此外還有一些建議:

  1. 之 所以我們把API的設(shè)計(jì)先行,而不是讓框架的設(shè)計(jì)先行是因?yàn)檫@樣我們更容易設(shè)計(jì)出好用的API,作為框架的實(shí)現(xiàn)者,我們往往會(huì)進(jìn)行一些妥協(xié),我們可能會(huì)為 了在框架內(nèi)部DRY而設(shè)計(jì)出一套丑陋的API讓框架的使用者去做一些重復(fù)的工作;我們也可能會(huì)因?yàn)橄胱尶蚣茏兊酶神詈蠌?qiáng)迫框架的使用者去使用到框架的一 些內(nèi)部API去初始化框架的組件。如果框架不是易用的,那么框架的內(nèi)部設(shè)計(jì)的再合理又有什么意義?
  2. 盡量少暴露一些框架內(nèi)部的類名吧,對(duì) 于框架的使用者來說,你的框架對(duì)他一點(diǎn)都不熟悉,如果要上手你的框架需要學(xué)習(xí)一到兩個(gè)類尚可接受,如果要使用到十幾個(gè)類會(huì)頭暈?zāi)X脹的,即使你的框架有非常 多的功能以及配置,可以考慮提供一個(gè)入口類,比如創(chuàng)建一個(gè)ConfigCenter類作為入口,讓使用者可以僅僅探索這個(gè)類便可對(duì)框架進(jìn)行所有的配置。
  3. 一 個(gè)好的框架是可以讓使用者少犯錯(cuò)誤的,框架的設(shè)計(jì)者務(wù)必要考慮到,框架的使用者沒有這個(gè)業(yè)務(wù)來按照框架的最佳實(shí)踐來做,所以在設(shè)計(jì)API的時(shí)候,如果你希 望API的使用者一定要按照某個(gè)方式來做的話,可以考慮設(shè)置一個(gè)簡(jiǎn)便的重載來加載默認(rèn)的最合理的使用方式而不是要求使用者來為你的方法初始一些什么依賴, 同時(shí)也可以在API內(nèi)部做一些檢測(cè),如果發(fā)現(xiàn)開發(fā)人員可能會(huì)犯錯(cuò)進(jìn)行一些提示或拋出異常。好的框架無需過多的文檔,它可以在開發(fā)人員用的時(shí)候告知它哪里錯(cuò) 了,最佳實(shí)踐是什么,即便他們真的錯(cuò)了也能以默認(rèn)的更合理的方式來彌補(bǔ)這個(gè)錯(cuò)誤。
  4. 建議所有的API都有一套統(tǒng)一的規(guī)范,比如入口都叫XXXCenter或XXXManager,而不是叫XXXCenter、YYYManager和 ZZZService。API往往需要進(jìn)行迭代和改良的,在首個(gè)版本中把好名字用掉也不一定是一個(gè)好辦法,最好還是給自己的框架各種API的名字留一點(diǎn)余 地,這樣以后萬一需要升級(jí)換代不至于太牽強(qiáng)。

下一步工作就是把項(xiàng)目中那些空的類按照功能進(jìn)行劃分。目的很簡(jiǎn)單,就是讓你的框架 的100個(gè)類或接口能夠按照功能進(jìn)行拆分和歸類,這樣別人一打開你的框架就可以馬上知道你的框架分為哪幾個(gè)主要部分,而不是在100個(gè)類中暈眩;還有因?yàn)?一旦在你的框架有使用者后你再要為API相關(guān)的那些類調(diào)整包就比困難了,即使你在創(chuàng)建框架的時(shí)候覺得我的框架就那么十幾個(gè)類無需進(jìn)行過多的分類,但是在將 來框架變大又發(fā)現(xiàn)當(dāng)初設(shè)計(jì)的不合理,無法進(jìn)行結(jié)構(gòu)調(diào)整就會(huì)變得很痛苦。因此這個(gè)工作還是相當(dāng)重要的,對(duì)于大多數(shù)框架來說,可以有幾種切蛋糕的方式:

  1. 分 層。我覺得框架和應(yīng)用程序一樣,也需要進(jìn)行分層。傳統(tǒng)的應(yīng)用程序我們分為表現(xiàn)層、邏輯層和數(shù)據(jù)訪問層,類似的對(duì)于很多框架也可以進(jìn)行橫向的層次劃分。要分 層的原因是我們的框架要處理的問題是基于多層抽象的,就像如果沒有OSI七層模型,要讓一個(gè)HTTP應(yīng)用去直接處理網(wǎng)絡(luò)信號(hào)是不合理的也是不利于重用的。 舉一個(gè)例子,如果我們要寫一個(gè)基于Socket的RPC的框架,我們需要處理方法的代理以及序列化,以及序列化數(shù)據(jù)的傳輸,這完全是兩個(gè)層面的問題,前者 偏向于應(yīng)用層,后者偏向于網(wǎng)絡(luò)層,我們完全有理由把我們的框架分為兩個(gè)層面的項(xiàng)目(至少是兩個(gè)包),rpc.core和rpc.socket,前者不關(guān)心 網(wǎng)絡(luò)實(shí)現(xiàn)來處理所有RPC的功能,后者不關(guān)心RPC來處理所有的Socket功能,在將來即使我們要淘汰我們的RPC的協(xié)議了,我們也可以重用 rpc.socket項(xiàng)目,因?yàn)樗蚏PC的實(shí)現(xiàn)沒有任何關(guān)系,它關(guān)注的只是socket層面的東西。
  2. 橫切。剛才說的分層是橫向的分 割,橫切是縱向的分割(橫切是跨多個(gè)模塊的意思,不是橫向來切的意思)。其實(shí)橫切關(guān)注點(diǎn)就是諸如日志、配置、緩存、AOP、IOC等通用的功能,對(duì)于這部 分功能,我們不應(yīng)該把他們和真正的業(yè)務(wù)邏輯混淆在一起。對(duì)于應(yīng)用類項(xiàng)目是這樣,對(duì)于框架類項(xiàng)目也是這樣,如果某一部分的代碼量非常大,完全有理由為它分出 一個(gè)單獨(dú)的包。對(duì)于RPC項(xiàng)目,我們可能就會(huì)把客戶端和服務(wù)端通訊的消息放在common包內(nèi),把配置的處理單獨(dú)放在config包內(nèi)。
  3. 功能。也就是要實(shí)現(xiàn)一個(gè)框架主要解決的問題點(diǎn),比如對(duì)于上面提到的RPC框架的core部分,可以想到的是我們主要解決是客戶端如何找到服務(wù)端,如何把進(jìn) 行方法調(diào)用以及把方法的調(diào)用信息傳給目標(biāo)服務(wù)端,服務(wù)端如何接受到這樣的信息根據(jù)配置在本地實(shí)例化對(duì)象調(diào)用方法后把結(jié)果返回客戶端三大問題,那么我們可能 會(huì)把項(xiàng)目分為routing、client、server等幾個(gè)包。

如果是一個(gè)RPC框架,大概是這樣的結(jié)構(gòu):

你知道如何寫一個(gè)框架嗎?詳細(xì)步驟大放送

對(duì)于我們的Web MVC框架,舉例如下:

  1. 我們可以有一個(gè)mvc.core項(xiàng)目,細(xì)分如下的包:
  2. common:公共的一組件,下面的各模塊都會(huì)用到
  3. config:配置模塊,解決框架的配置問題
  4. startup:?jiǎn)?dòng)模塊,解決框架和Servlet如何進(jìn)行整合的問題
  5. plugin:插件模塊,插件機(jī)制的實(shí)現(xiàn),提供IPlugin的抽象實(shí)現(xiàn)
  6. routing:路由模塊,解決請(qǐng)求路徑的解析問題,提供了IRoute的抽象實(shí)現(xiàn)和基本實(shí)現(xiàn)
  7. controller:控制器模塊,解決的是如何產(chǎn)生控制器
  8. model:視圖模型模塊,解決的是如何綁定方法的參數(shù)
  9. action:action模塊,解決的是如何調(diào)用方法以及方法返回的結(jié)果,提供了IActionResult的抽象實(shí)現(xiàn)和基本實(shí)現(xiàn)
  10. view:視圖模塊,解決的是各種視圖引擎和框架的適配
  11. filter:過濾器模塊,解決是執(zhí)行Action,返回IActionResult前后的AOP功能,提供了IFilter的抽象實(shí)現(xiàn)以及基本實(shí)現(xiàn)
  12. 我們可以再創(chuàng)建一個(gè)mvc.extension項(xiàng)目,細(xì)分如下的包:
  13. filters:一些IFilter的實(shí)現(xiàn)
  14. results:一些IActionResult的實(shí)現(xiàn)
  15. routes:一些IRoute的實(shí)現(xiàn)
  16. plugins:一些IPlugin的實(shí)現(xiàn)

這里我們以IXXX來描述一個(gè)抽象,可以是接口也可以是抽象類,在具體實(shí)現(xiàn)的時(shí)候根據(jù)需求再來確定。

你知道如何寫一個(gè)框架嗎?詳細(xì)步驟大放送

這 種結(jié)構(gòu)的劃分方式完全吻合上面說的切蛋糕方式,可以看到除了橫切部分和分層部分,作為一個(gè)Web MVC框架,它核心的組件就是routing、model、view、controller、action(當(dāng)然,對(duì)于有些MVC框架它沒有route部 分,route部分是交由Web框架實(shí)現(xiàn)的)。

如果我們?cè)谶@個(gè)時(shí)候還無法確定框架的模塊劃分的話,問題也不大,我們可以在后續(xù)的搭建龍骨的步驟中隨著更多的類的建立,繼續(xù)理清和確定模塊的劃分。

經(jīng)過了設(shè)計(jì)的步驟,我們應(yīng)該心里對(duì)下面的問題有一個(gè)初步的規(guī)劃了:

  • 我們的框架以什么形式來提供如何優(yōu)雅的API?
  • 我們的框架包含哪些模塊,模塊大概的作用是什么?

#p#
搭建龍骨 

在 經(jīng)過了初步的設(shè)計(jì)之后,我們可以考慮為框架搭建一套龍骨,一套抽象的層次關(guān)系。也就是用抽象類、接口或空的類實(shí)現(xiàn)框架,可以通過編譯,讓框架撐起來,就像 造房子搭建房子的鋼筋混凝土結(jié)構(gòu)(添磚加瓦是后面的事情,我們先要有一個(gè)結(jié)構(gòu))。對(duì)于開發(fā)應(yīng)用程序來說,其實(shí)沒有什么撐起來一說,因?yàn)閼?yīng)用程序中很多模塊 都是并行的,它可能并沒有一個(gè)主結(jié)構(gòu),主流程,而對(duì)于框架來說,它往往是一個(gè)高度面向?qū)ο蟮?,高度抽象的一套程序,搭建龍骨也就是搭建一套抽象層。這么說 可能有點(diǎn)抽象,我們還是來想一下如果要做一個(gè)Web MVC框架,需要怎么為上面說的幾個(gè)核心模塊進(jìn)行抽象(我們也來體會(huì)一下框架中一些類的命名,這里我們?yōu)榱烁逦?,為所有接口都命名為IXXX,這點(diǎn)不太 符合Java的命名規(guī)范):

  1. routing MVC的入口是路由
  2. 每一個(gè)路由都是IRoute代表了不同的路由實(shí)現(xiàn),它也提供一個(gè)getRouteResult()方法來返回RouteResult對(duì)象
  3. 我們實(shí)現(xiàn)一個(gè)框架自帶的DefaultRoute,使得路由支持配置,支持默認(rèn)值,支持正則表達(dá)式,支持約束等等
  4. 我們需要有一個(gè)Routes類來管理所有的路由IRoute,提供一個(gè)findRoute()方法來返回RouteResult對(duì)象,自然我們這邊調(diào)用的就是IRoute的getRouteResult()方法,返回能匹配到的結(jié)果
  5. RouteResult對(duì)象就是匹配的路由信息,包含了路由解析后的所有數(shù)據(jù)
  6. controller 路由下來是控制器
  7. 我們有IControllerFactory來創(chuàng)建Controller,提供createController()方法來返回IController
  8. IController代表控制器,提供一個(gè)execute()方法來執(zhí)行控制器
  9. 我們實(shí)現(xiàn)一個(gè)框架自帶的DefaultControllerFactory來以約定由于配置的方式根據(jù)約定規(guī)則以及路由數(shù)據(jù)RouteResult來找到IController并創(chuàng)建它
  10. 我 們?yōu)镮Controller提供一個(gè)抽象實(shí)現(xiàn),AbstractController,要求所有MVC框架的使用者創(chuàng)建的控制器需要繼承 AbstractController,在這個(gè)抽象實(shí)現(xiàn)中我們可以編寫一些便捷的API以便開發(fā)人員使用,比如view()方法、file()方法、 redirect()方法、json()方法、js()方法等等
  11. action 找到了控制器后就是來找要執(zhí)行的方法了
  12. 我們有IActionResult來代表Action返回的結(jié)果,提供一個(gè)execute()方法來執(zhí)行這個(gè)結(jié)果
  13. 我們的框架需要實(shí)現(xiàn)一些自帶的IActionResult,比如ContentResult、ViewResult、FileResult、JsonResult、RedirectResult來對(duì)應(yīng)AbstractController的一些便捷方法
  14. 再來定義一個(gè)IActionInvoker來執(zhí)行Action,提供一個(gè)invokeAction()方法
  15. 我們需要實(shí)現(xiàn)一個(gè)DefaultActionInvoker以默認(rèn)的方式進(jìn)行方法的調(diào)用,也就是找到方法的一些IFilter按照一定的順序執(zhí)行他們,最后使用反射進(jìn)行方法的調(diào)用得到上面說的IActionResult并執(zhí)行它的execute()方法
  16. filter 我們的框架很重要的一點(diǎn)就是便捷的過濾器
  17. 剛才提到了IFilter,代表的是一個(gè)過濾器,我們提供IActionFilter對(duì)方法的執(zhí)行前后進(jìn)行過濾,提供IResultFilter對(duì)IActionResult執(zhí)行前后進(jìn)行過濾
  18. 我們的IActionInvoker怎么找到需要執(zhí)行的IFilter呢,我們需要定義一個(gè)IFilterProvider來提供過濾器,它提供一個(gè)getFilters()方法來提供所有的IFilter的實(shí)例
  19. 我 們的框架可以實(shí)現(xiàn)一些自帶的IFilterProvider,比如AnnotationFilterProvider通過掃描Action或 Controller上的注解來獲取需要執(zhí)行的過濾器信息;比如我們還可以實(shí)現(xiàn)GlobalFilterProvider,開發(fā)人員可以直接通過配置或代 碼方式告知框架應(yīng)用于全局的IFilter
  20. 既然我們實(shí)現(xiàn)了多個(gè)IFilterProvider,我們自然需要有一個(gè)類來管理這些IFilterProvider,我們實(shí)現(xiàn)一個(gè)FilterProviders類并提供getFilters()方法(這和我們的Routes類來管理IRoute是類似的,命名統(tǒng)一)
  21. view 各種IActionResult中最特殊最復(fù)雜的就是ViewResult,我們需要有一個(gè)單獨(dú)的包來處理ViewResult的邏輯
  22. 我們需要有IViewEngine來代表一個(gè)模版引擎,提供一個(gè)getViewEngineResult()方法返回ViewEngineResult
  23. ViewEngineResult包含視圖引擎尋找視圖的結(jié)果信息,里面包含IView和尋找的一些路徑等
  24. IView自然代表的是一個(gè)視圖,提供render()方法(或者為了統(tǒng)一也可以叫做execute)來渲染視圖
  25. 我 們的框架可以實(shí)現(xiàn)常見的一些模版引擎,比如FreemarkerViewEngine、VelocityViewEngine 等,VelocityViewEngine返回的ViewEngineResult自然包含的是一個(gè)實(shí)現(xiàn)IView的VelocityView,不會(huì)返回 其它引擎的IView
  26. 同樣的,我們是不是需要一個(gè)ViewEngines來管理所有的IViewEngine呢,同樣也是實(shí)現(xiàn)findViewEngine()方法
  27. common 這里可以放一些項(xiàng)目中各個(gè)模塊都要用到的一些東西
  28. 比 如各種context,context代表的是執(zhí)行某個(gè)任務(wù)需要的環(huán)境信息,這里我們可以定義HttpContext、 ControllerContext、ActionContext和ViewContext,后者繼承前者,隨著MVC處理流程的進(jìn)行,View執(zhí)行時(shí)的 上下文相比Action執(zhí)行時(shí)的上下文信息肯定是多了視圖的信息,其它同理,之所以把這個(gè)信息放在common里面而不是放在各個(gè)模塊自己的包內(nèi)是因?yàn)檫@ 樣更清晰,可以一目了然各種對(duì)象的執(zhí)行上下文有一個(gè)立體的概念
  29. 比如各種helper或utility

接下去就不再詳細(xì)闡述model、plugin等模塊的內(nèi)容了。

看到這里,我們來總結(jié)一下,我們的MVC框架在組織結(jié)構(gòu)上有著高度的統(tǒng)一:

  • 如果xxx本身并無選擇策略,但xxx的創(chuàng)建過程也不是一個(gè)new這么簡(jiǎn)單的,可以由xxxFactory類來提供一個(gè)xxx
  • 如果我們需要用到很多個(gè)yyy,那么我們會(huì)有各種yyyProvider(通過getyyy()方法)來提供這些yyy,并且我們需要有一個(gè)yyyProviders來管理這些yyyProvider
  • 如果zzz的選擇是有策略性的,會(huì)按照需要選擇zzz1或zzzN,那么我們可能會(huì)有一個(gè)zzzs來管理這些zzz并且(通過findzzz()方法)來提供合適的zzz

同 時(shí)我們框架的相關(guān)類的命名也是非常統(tǒng)一的,可以一眼看出這是實(shí)現(xiàn)、還是抽象類還是接口;是提供程序,是執(zhí)行結(jié)果還是上下文。當(dāng)然,在將來的代碼實(shí)現(xiàn)過程中 很可能會(huì)把很多接口變?yōu)槌橄箢愄峁┮恍┠J(rèn)的實(shí)現(xiàn),這并不會(huì)影響項(xiàng)目的主結(jié)構(gòu)。我們會(huì)在模式篇對(duì)框架常用的一些高層設(shè)計(jì)模式做更多的介紹。

到了這里,我們的項(xiàng)目里已經(jīng)有幾十個(gè)空的(抽象)類、接口了,其中也定義了各種方法可以把各個(gè)模塊串起來(各種find()方法和execute()方法),可以說整個(gè)項(xiàng)目的龍骨已經(jīng)建立起來了,這種感覺很好,因?yàn)槲覀冃睦锖苡械?,我們只需要在接下去的工作中做兩個(gè)事情:

  • 實(shí)現(xiàn)各種DefaultXXX來走通主流程
  • 實(shí)現(xiàn)各種IyyyProvider和Izzz接口來完善支線流程

#p#
走通主線流程

所謂走通主線流程,就是讓這個(gè)框架可以以一個(gè)HelloWorld形式跑起來,這就需要把幾個(gè)核心類的核心方法使用最簡(jiǎn)單的方式進(jìn)行實(shí)現(xiàn),還是拿我們的MVC框架來舉例子:

  • 從startup開始,可能需要實(shí)現(xiàn)ServletContextListener來動(dòng)態(tài)注冊(cè)我們框架的入口Servlet,暫且起名為DispatcherServlet吧,在這個(gè)類中我們需要走一下主線流程
  • 調(diào)用Routes.findRoute()獲得IRoute
  • 調(diào)用IRoute.getRouteResult()來獲得RouteResult
  • 使用拿到的RouteResult作為參數(shù)調(diào)用DefaultControllerFactory.createController()獲得IController(其實(shí)也是AbstractController)
  • 調(diào)用IController.execute()
  • 在 config中創(chuàng)建一個(gè)IConfig作為一種配置方式,我們實(shí)現(xiàn)一個(gè)DefaultConfig,把各種默認(rèn)實(shí)現(xiàn)注冊(cè)到框架中去,也就是 DefaultRoute、DefaultControllerFactory、DefaultActionInvoker,然后把各種 IViewEngine加入ViewEngines
  • 然后需要完成相關(guān)默認(rèn)類的實(shí)現(xiàn):
  • 實(shí)現(xiàn)Routes.findRoute()
  • 實(shí)現(xiàn)DefaultRoute.getRouteResult()
  • 實(shí)現(xiàn)DefaultControllerFactory.createController()
  • 實(shí)現(xiàn)AbstractController.execute()
  • 實(shí)現(xiàn)DefaultActionInvoker.invokeAction()
  • 實(shí)現(xiàn)ViewResult.execute()
  • 實(shí)現(xiàn)ViewEngines.findViewEngine()
  • 實(shí)現(xiàn)VelocityViewEngine.getViewEngineResult()
  • 實(shí)現(xiàn)VelocityView.render()

在這一步,我們并不一定要去觸碰filter和model這部分的內(nèi)容,我們的主線流程只是解析路由,獲得控制器,執(zhí)行方法,找到視圖然后渲染視圖。過濾器和視圖模型的綁定屬于增強(qiáng)型的功能,屬于支線流程,不屬于主線流程。

雖 然在這里我們說了一些MVC的實(shí)現(xiàn),但本文的目的不在于教你實(shí)現(xiàn)一個(gè)MVC框架,所以不用深究每一個(gè)類的實(shí)現(xiàn)細(xì)節(jié),這里想說的是,在前面的龍骨搭建完后, 你會(huì)發(fā)現(xiàn)按照這個(gè)龍骨為它加一點(diǎn)肉上去實(shí)現(xiàn)主要的流程是順理成章的事情,毫無痛苦。在整個(gè)實(shí)現(xiàn)的過程中,你可以不斷完善common下的一些 context,把方法的調(diào)用參數(shù)封裝到上下文對(duì)象中去,不但看起來清楚且符合開閉原則。到這里,我們應(yīng)該可以跑起來在設(shè)計(jì)階段做的那個(gè)示例網(wǎng)站的 HelloWorld功能了。

在這里還想說一點(diǎn),有些人在實(shí)現(xiàn)框架的時(shí)候并沒有搭建龍骨的一步驟,直接以非OOP的方式實(shí)現(xiàn)了主線流程,這種方式有以下幾個(gè)缺點(diǎn):

不容易做到SRP單一指責(zé)原則,你很容易把各種邏輯都集中寫在一起,比如大量的邏輯直接寫到了DispatcherServlet中,輔助一些Service或Helper,整個(gè)框架就肥瘦不勻,有些類特別龐大有些類特別小。
不容易做到OCP開閉原則,擴(kuò)展起來不方便需要修改老的代碼,我們期望的擴(kuò)展是實(shí)現(xiàn)新的類然后讓框架感知,而不是直接修改框架的某些代碼來增強(qiáng)功能。
很難實(shí)現(xiàn)DIP依賴倒置原則,即使你依賴的確實(shí)是IService但其實(shí)就沒意義,因?yàn)樗挥幸粋€(gè)實(shí)現(xiàn),只是把他當(dāng)作幫助類來用罷了。

#p#
實(shí)現(xiàn)各種支線流程

我們想一下,對(duì)于這個(gè)MVC框架有哪些沒有實(shí)現(xiàn)的支線流程?其實(shí)無需多思考,因?yàn)槲覀冊(cè)诖罱埞请A段的設(shè)計(jì)已經(jīng)給了我們明確的方向了,我們只需要把除了主線之外的那些龍骨上也填充一些實(shí)體即可,比如:

  1. 實(shí)現(xiàn)更多的IRoute,并注冊(cè)到Routes
  2. 實(shí)現(xiàn)更多的IViewEngine,并注冊(cè)到ViewEngines
  3. 實(shí)現(xiàn)必要的IFilterProvider以及FilterProviders,把IFilterProvider注冊(cè)到FilterProviders
  4. 增強(qiáng)DefaultActionInvoker.invokeAction()方法,在合適的時(shí)候調(diào)用這些IFilter
  5. 實(shí)現(xiàn)更多的IActionResult,并且為AbstractController實(shí)現(xiàn)更多的便捷方法來返回這些IActionResult
  6. ……實(shí)現(xiàn)更多model模塊的內(nèi)容和plugin模塊的內(nèi)容

實(shí)現(xiàn)了這一步后,你會(huì)發(fā)現(xiàn)整個(gè)框架飽滿起來了,每一個(gè)包中不再是僅有的那些接口和默認(rèn)實(shí)現(xiàn),而且會(huì)有一種OOP的爽快感,爽快感來源于幾個(gè)方面:

  1. 面對(duì)接口編程抽象和多態(tài)的放心安心的爽快感
  2. 為抽象類實(shí)現(xiàn)具體類享受到父類大量實(shí)現(xiàn)的滿足的爽快感
  3. 實(shí)現(xiàn)了大量的接口和抽象類后充實(shí)的爽快感

我們?cè)賮砜偨Y(jié)一下之前說的那些內(nèi)容,實(shí)現(xiàn)一個(gè)框架的第一大步就是:

  1. 設(shè)計(jì)一套合理的接口
  2. 為框架進(jìn)行模塊劃分
  3. 為框架搭建由抽象結(jié)構(gòu)構(gòu)成的骨架
  4. 在這個(gè)骨架的基礎(chǔ)上實(shí)現(xiàn)一個(gè)HelloWorld程序
  5. 為這個(gè)骨架的其它部分填充更多實(shí)現(xiàn)

經(jīng) 過這樣的一些步驟后可以發(fā)現(xiàn)這個(gè)框架是很穩(wěn)固的,很平衡的,很易于擴(kuò)展的。其實(shí)到這里很多人覺得框架已經(jīng)完成了,有血有肉,其實(shí)個(gè)人覺得只能說開發(fā)工作實(shí) 現(xiàn)了差不多30%,后文會(huì)繼續(xù)說,畢竟直接把這樣一個(gè)血肉之軀拿出去對(duì)外有點(diǎn)嚇人,我們需要為它進(jìn)行很多包裝和完善。

#p#

單元測(cè)試 

在這之前我們寫的框架只能說是一個(gè)在最基本的情況下可以使用的框架,作為一個(gè)框架我們無法預(yù)測(cè)開發(fā)人員將來會(huì)怎么使用它,所以我們需要做大量的工作來確保框架不但各種功能都是正確的,而且還是健壯的。寫應(yīng)用系統(tǒng)的代碼,大多數(shù)項(xiàng)目是不會(huì)去寫單元測(cè)試的,原因很多:

  • 項(xiàng)目趕時(shí)間,連做一些輸入驗(yàn)證都沒時(shí)間搞,哪里有時(shí)間寫測(cè)試代碼。
  • 項(xiàng)目對(duì)各項(xiàng)功能的質(zhì)量要求不高,只要能在標(biāo)準(zhǔn)的操作流程下功能可用即可。
  • 項(xiàng)目基本不會(huì)去改或是臨時(shí)項(xiàng)目,一旦測(cè)試通過之后就始終是這樣子了,沒有迭代。
  • ……

對(duì)于框架,恰恰相反,沒有配套的單元測(cè)試的框架(也就是僅僅使用人工的方式進(jìn)行測(cè)試,比如在main中調(diào)用一些方法觀察日志或輸出,或者運(yùn)行一下示例項(xiàng)目查看各種功能是否正常,是非??膳碌模┰蛉缦拢?/p>

  1. 自動(dòng)化程度高,回歸需要的時(shí)間短,甚至可以整合到構(gòu)建過程中進(jìn)行,這是人工測(cè)試無法實(shí)現(xiàn)的。
  2. 框架一定是有非常多的迭代和重構(gòu)的, 每一次修改雖然只改了A功能,但是可能會(huì)影響到B和C功能,人工測(cè)試的話你可能只會(huì)驗(yàn)證A是否正常,容易忽略B和C,使用單元測(cè)試的話只要所有功能都有覆蓋,那么幾乎不可能遺漏因?yàn)樾薷膶?dǎo)致的潛在問題,而且還能反饋出來因?yàn)樾薷膶?dǎo)致的兼容性問題。
  3. 之前說過,一旦框架開放出去,框架的使用者可能會(huì)以各種方式在各種環(huán)境來使用你的框架,環(huán)境不同會(huì)造成很多怪異的邊界輸入或非法輸入,需要使用單元測(cè)試對(duì)代碼進(jìn)行嚴(yán)格的邊界測(cè)試,以確??蚣芸梢栽趪?yán)酷的環(huán)境下生存。
  4. 單元測(cè)試還能幫助我們改善設(shè)計(jì),在寫單元測(cè)試的時(shí)候如果發(fā)現(xiàn)目標(biāo)代碼非常難以進(jìn)行模擬難以構(gòu)建有效的單元測(cè)試,那么說明目標(biāo)代碼可能有強(qiáng)依賴或職責(zé)過于復(fù)雜,一個(gè)被單元測(cè)試高度覆蓋的框架往往是設(shè)計(jì)精良的,符合高內(nèi)聚低耦合的框架。

如果框架的時(shí)間需求不是特別緊的話,單元測(cè)試的引入可以是走通主線流程的階段就引入,越早引入框架的成熟度可能就會(huì)越高,以后重構(gòu)返工的機(jī)會(huì)會(huì)越小,框架的可靠性也肯定會(huì)大幅提高。之前我有寫過一個(gè)類庫項(xiàng)目,并沒有寫單元測(cè)試,在項(xiàng)目中使用了這個(gè)類庫一段時(shí)間也沒有出現(xiàn)任何問題,后來花了一點(diǎn)時(shí)間為類庫寫了單元測(cè)試,出乎我意料之外的是,我的類庫提供的所有API中有超過一半是無法通過單元測(cè)試的(原以為這是一個(gè)成熟的類庫,其實(shí)包含了數(shù)十個(gè)BUG),甚至其中有一個(gè)API是在我的項(xiàng)目中使用的。你可能會(huì)問,為什么在使用這個(gè)API的時(shí)候沒有發(fā)生問題而在單元測(cè)試的時(shí)候發(fā)生問題了呢?原因之前提到過,我是框架的設(shè)計(jì)者,我在使用類庫提供的API的時(shí)候是知道使用的最佳實(shí)踐的,因此我在使用的時(shí)候?yàn)轭悗爝M(jìn)行了一個(gè)特別的設(shè)置,這個(gè)問題如果不是通過單元測(cè)試暴露的話,那么其它人在使用這個(gè)類庫的時(shí)候基本都會(huì)遇到一個(gè)潛在的BUG。

#p#

示范項(xiàng)目

寫一個(gè)示例項(xiàng)目不僅僅是為了給別人參考,而且還能夠幫助自己去完善框架,對(duì)于示例項(xiàng)目,最好兼顧下面幾點(diǎn):

  1. 是一個(gè)具有一定意義的網(wǎng)站或系統(tǒng),而不是純粹為了演示特性而演示。這是因?yàn)?,很多時(shí)候只有那些真正的業(yè)務(wù)邏輯才會(huì)暴露出問題,演示特性的時(shí)候我們總是有一些定勢(shì)思維會(huì)規(guī)避很多問題?;蛘呖梢蕴峁﹥蓚€(gè)項(xiàng)目,一個(gè)純粹演示特性,一個(gè)是示例項(xiàng)目。
  2. 覆蓋盡可能多的特性或使用難點(diǎn),在項(xiàng)目的代碼中提供一些注釋,很多開發(fā)人員不喜歡閱讀文檔,反而喜歡看一下示例項(xiàng)目直接上手(模仿示例項(xiàng)目,或直接拿示例項(xiàng)目中的代碼來修改)。
  3. 項(xiàng)目中的代碼,特別是涉及到框架使用的代碼一定要規(guī)范,原因上面也說了,作為框架的設(shè)計(jì)者你不會(huì)希望大家復(fù)制的代碼粘帖的代碼一團(tuán)糟吧。
  4. 如果你的項(xiàng)目針對(duì)的不僅僅是Web項(xiàng)目,那么示例項(xiàng)目最好提供Web和桌面兩個(gè)版本,一來你自己容易發(fā)現(xiàn)因?yàn)榄h(huán)境不同帶來的使用差異,二來可以給予不同類型項(xiàng)目不同的最佳實(shí)踐。


完善日志和異常

一個(gè)好的框架不但需要設(shè)計(jì)精良,日志和異常的處理是否到位也是非常重要的標(biāo)準(zhǔn),這里有一些反例:

  1. 日志的各種級(jí)別的使用沒有統(tǒng)一的標(biāo)準(zhǔn),甚至是永遠(yuǎn)只使用某個(gè)級(jí)別的日志。
  2. 幾乎沒有任何的日志,框架的運(yùn)行完全是一個(gè)黑盒。
  3. 記錄的日志多且沒有實(shí)際含義,只是調(diào)試的時(shí)候用來觀察變量的內(nèi)容。
  4. 異常類型只使用Exception,不使用更具體化的類型,沒有自定義類型。
  5. 異常的消息文本只寫"錯(cuò)誤"字樣,不寫清楚具體的問題所在。
  6. 永遠(yuǎn)只是拋出異常,讓異常上升到最外層,交給框架的使用者去處理。
  7. 用異常來控制代碼流程,或本應(yīng)該在方法未達(dá)到預(yù)期效果的時(shí)候使用異常卻使用返回值。

其實(shí)個(gè)人覺得,一個(gè)框架的主邏輯代碼并不一定是最難的,最難的是對(duì)一些細(xì)節(jié)的處理,讓框架保持一套規(guī)范的統(tǒng)一的日志和異常的使用反而對(duì)框架開發(fā)者來說是一個(gè)難點(diǎn),下面是針對(duì)記錄日志的一些建議:

1、首先要對(duì)框架使用的日志級(jí)別有一個(gè)規(guī)范,比如定義:

  1. DEBUG:用于觀察程序的運(yùn)行流程,僅在調(diào)試的時(shí)候開啟
  2. INFO:用于告知程序運(yùn)行狀態(tài)或階段的變化,可以在測(cè)試環(huán)境開啟
  3. WARNING:用于告知程序可以自己恢復(fù)的錯(cuò)誤或異常,或不影響主線流程執(zhí)行的錯(cuò)誤或問題,可以在正式環(huán)境開啟
  4. ERROR:用于告知程序無法恢復(fù),主線流程中斷,需要開發(fā)或運(yùn)維人員知曉干預(yù)的錯(cuò)誤或異常,需要在正式環(huán)境開啟

2、按照上面的級(jí)別規(guī)范,在需要記錄日志的地方記錄日志,除了DEBUG級(jí)別的日志其它日志不能記錄過多,如果框架總是在運(yùn)行的時(shí)候輸出幾十個(gè)WARNNING也容易讓使用者忽略真正的問題。
3、日志記錄的消息需要是明確的,最好包含一些上下文信息,比如"無法在xxx下找到配置文件xxx.config,框架將采用默認(rèn)的配置",而不是"加載配置失?。?quot;

下面是一些針對(duì)使用異常的建議:

  1. 框架由于配置錯(cuò)誤或使用錯(cuò)誤或運(yùn)行錯(cuò)誤,不能完成API名字所表示的功能,考慮拋出轉(zhuǎn)化后的異常,讓調(diào)用者知道發(fā)什么了什么情況,同時(shí)框架可以建立自己的錯(cuò)誤處理機(jī)制
  2. 對(duì)于可以預(yù)料的錯(cuò)誤,并且錯(cuò)誤類型可以枚舉,考慮以返回值的形式告知調(diào)用者可以根據(jù)不同的結(jié)果來處理后續(xù)的邏輯
  3. 對(duì)于框架內(nèi)部功能實(shí)現(xiàn)上遇到的調(diào)用者無能力解決的錯(cuò)誤,如果錯(cuò)誤可以重試或不影響返回,可以記錄警告或錯(cuò)誤日志
  4. 可以為每一個(gè)模塊都陪伴自定義的異常類型,包含相關(guān)的上下文信息(比如ViewException可以包含ViewContext),這樣出現(xiàn)異??梢院芊奖阒獣允悄膫€(gè)模塊出現(xiàn)問題并且可以得到出現(xiàn)異常時(shí)的環(huán)境信息
  5. 如果異常跨了實(shí)現(xiàn)層次(比如從框架到應(yīng)用),那么最好進(jìn)行一下包裝轉(zhuǎn)換(比如把文件讀取失敗的提示改為加載配置文件失敗的提示),否則上層人員是不知道怎么處理這些內(nèi)部問題的,內(nèi)部問題需要由框架自己來處理
  6. 異常的日志中可以記錄和當(dāng)前操作密切相關(guān)的參數(shù)信息,比如搜索的路徑,視圖名等等,有關(guān)方法的信息不用過多記錄,異常一般都帶有調(diào)用棧信息
  7. 如果可能的話,出現(xiàn)異常的時(shí)候可以分析一下為什么會(huì)出現(xiàn)這樣的問題,在異常信息中給一些解決問題的建議或幫助鏈接方便使用者排查問題
  8. 異常處理從壞到好的層次是,出現(xiàn)了嚴(yán)重問題的時(shí)候:
  9. 使用者什么都不知道,程序的完整性和邏輯得到破壞
  10. 使用者既不知道出現(xiàn)了什么問題也不知道怎么去解決
  11. 使用者能明確知道出現(xiàn)了什么問題,但無法去解決
  12. 使用者不但知道發(fā)生了什么,還能通過異常消息的引導(dǎo)快速解決問題

#p#
完善配置

配置的部分可以留到框架寫的差不多了再去寫,因?yàn)檫@個(gè)時(shí)候已經(jīng)可以想清楚哪些配置是:

  1. 需要公開出去給使用者配置的,并且配置會(huì)根據(jù)環(huán)境不同而不同
  2. 需要公開出去給使用者來配置的,配置和部署環(huán)境無關(guān)
  3. 僅僅需要在框架內(nèi)供框架開發(fā)人員來配置的
  4. 無需是一個(gè)配置,只要在代碼中集中存儲(chǔ)這個(gè)設(shè)定即可

一般來說配置有幾種方式:

  1. 通過配置文件來配置,比如XML文件、JSON文件或property文件
  2. 通過注解或特性(Annotation/Attribute)方式(對(duì)類、方法、參數(shù))進(jìn)行配置
  3. 通過代碼方式進(jìn)行配置(比如單獨(dú)的配置類,或?qū)崿F(xiàn)配置類或調(diào)用框架的配置API)

很多框架提供了多種配置方式,比如Spring MVC同時(shí)支持上面三種方式的配置,個(gè)人覺得對(duì)配置,我們還是應(yīng)該區(qū)別對(duì)待,而不是無腦把所有的配置項(xiàng)都同時(shí)以上面三種方式提供配置,我們要考慮高內(nèi)聚和低耦合原則,對(duì)于Web框架來說,高內(nèi)聚需要考慮的比低耦合更多,我的建議是對(duì)不同的配置項(xiàng)提供不同的配置方式:

  1. 如果配置項(xiàng)目是需要讓使用者來配置的,特別是和環(huán)境相關(guān)的,那么最好使用配置方式來配置,比如開放的端口、內(nèi)存、線程數(shù)配置,不過要注意:
  2. 所有配置項(xiàng)目需要有默認(rèn)值,如果找不到配置使用默認(rèn)值,如果配置不合理使用默認(rèn)值(你不會(huì)希望使用你框架的人把框架內(nèi)部的線程池的min設(shè)置為999999,或定時(shí)器的間隔設(shè)置為0毫秒吧?)
  3. 框架啟動(dòng)的時(shí)候檢測(cè)所有配置,如果不合理給予提示,大多人只會(huì)在啟動(dòng)的時(shí)候看一下日志,使用的時(shí)候根本就不管
  4. 不知道大家對(duì)于配置文件的格式傾向于XML呢還是JSON呢還是鍵值對(duì)呢?
  5. 對(duì)于所有僅在開發(fā)時(shí)進(jìn)行的配置,都盡量不要去使用配置文件,并且讓配置盡量和它所配置的對(duì)象靠在一起:
  6. 如果是對(duì)框架整體性進(jìn)行的設(shè)置擴(kuò)展類型的配置,那就可以提供代碼方式進(jìn)行配置,比如我們要實(shí)現(xiàn)的MVC框架的各種IRoute、IViewEngine等,最好可以提供IConfig接口讓開發(fā)人員可以去實(shí)現(xiàn)接口,這樣他們可以知道有哪些東西可以配置,代碼就是文檔
  7. 如果是那種對(duì)模型、Action進(jìn)行的配置,比如模型的驗(yàn)證規(guī)則、Filter等一律采用注解的方式進(jìn)行配置
  8. 有的人說使用配置文件進(jìn)行配置非常靈活,使用代碼方式和注解方式來配置不靈活而且可能有侵入性。我覺得還是要權(quán)衡對(duì)待,我的建議是不要把太多框架內(nèi)在的東西放在配置文件中,增加使用者的難度(而且很多時(shí)候,大多數(shù)人只是復(fù)制配置為了完成配置而配置,并不是為了真正的靈活性而去使用配置文件來配置你的框架,看看網(wǎng)上這么所SSH配置文件的抄來抄去就知道了)。
  9. 最后,我建議很多太內(nèi)部的東西對(duì)于輕量級(jí)的應(yīng)用型框架可以不去提供任何配置選項(xiàng),只需要在某個(gè)常量文件中定義即可,讓真正有需求進(jìn)行二次開發(fā)的開發(fā)人員去修改,對(duì)于一個(gè)框架如果一下子暴露上百個(gè)"高級(jí)"配置項(xiàng)給使用者,他們會(huì)暈眩的。


提供狀態(tài)服務(wù)

所謂狀態(tài)服務(wù)就是反映框架內(nèi)部運(yùn)作狀態(tài)的服務(wù),很多開源服務(wù)或系統(tǒng)(Nginx、Mongodb等)都提供了類似的模塊和功能,作為框架的話我覺得也有必要提供一些內(nèi)部信息(主要是配置、數(shù)據(jù)統(tǒng)計(jì)以及內(nèi)部資源狀態(tài))出來,這樣使用你框架的人可以在開發(fā)的時(shí)候或線上運(yùn)作的時(shí)候了解框架的運(yùn)作狀態(tài),我們舉兩個(gè)例子,對(duì)于一個(gè)我們之前提到的Web MVC框架來說,可以提供這些信息:

  1. 路由配置
  2. 視圖引擎配置
  3. 過濾器配置

對(duì)于一個(gè)Socket框架來說,有一些不同,Socket框架是有狀態(tài)的,其狀態(tài)服務(wù)提供的信息除了當(dāng)前生效的配置信息之外,更多的是反映當(dāng)前框架內(nèi)部一些資源的狀態(tài)以及統(tǒng)計(jì)數(shù)據(jù):

  1. 各種配置(池配置、隊(duì)列配置、集群配置)
  2. Socket相關(guān)的統(tǒng)計(jì)數(shù)據(jù)(總打開、總關(guān)閉、每秒收發(fā)數(shù)據(jù)、總收發(fā)數(shù)據(jù)、當(dāng)前打開等等)
  3. 各種池的當(dāng)前狀態(tài)
  4. 各種隊(duì)列的當(dāng)前狀態(tài)

狀態(tài)服務(wù)可以以下面幾種形式來提供:

  1. 代碼方式,比如如果開發(fā)人員實(shí)現(xiàn)了IXXXStateAware接口的話,就可以為它的實(shí)現(xiàn)類來推送一些信息,也可以直接在框架中設(shè)立一個(gè)StateCenter來公開框架所有的狀態(tài)信息
  2. 自動(dòng)日志方式,比如如果在配置中開啟了stateLoggingInterval=60s的選項(xiàng),我們的框架就會(huì)自動(dòng)一分鐘一次輸出日志,顯示框架內(nèi)部的狀態(tài)
  3. 接口方式,比如開放一個(gè)Restful的接口或額外監(jiān)聽一個(gè)端口來提供狀態(tài)服務(wù),方便使用者可以拿原始的數(shù)據(jù)和其它監(jiān)控平臺(tái)進(jìn)行整合
  4. 內(nèi)部外部工具方式
  5. 比如我們可以直接為框架提供一個(gè)專門的頁面(/_route)來呈現(xiàn)路由的配置(甚至我們可以在這個(gè)頁面上讓開發(fā)人員可以直接輸入地址來測(cè)試路由的匹配情況,狀態(tài)服務(wù)不一定只能看),這樣在開發(fā)和測(cè)試的時(shí)候可以更方便調(diào)試
  6. 我們也可以為框架提供一個(gè)專有工具來查看框架的狀態(tài)信息(當(dāng)然,這個(gè)工具其實(shí)可能就是連接框架的某個(gè)網(wǎng)絡(luò)服務(wù)來獲取數(shù)據(jù)),這樣即使框架在多個(gè)機(jī)器中使用,我們可能也只有一個(gè)監(jiān)控工具即可
  7. 如果沒有狀態(tài)服務(wù),那么在運(yùn)行的時(shí)候框架就是一個(gè)黑盒,反之如果狀態(tài)服務(wù)足夠詳細(xì)的話,可以方便我們排查一些功能或性能問題。不過要注意的一點(diǎn)是,狀體服務(wù)可能會(huì)降低框架的性能,我們可能需要對(duì)狀態(tài)服務(wù)也進(jìn)行一次壓測(cè),排除狀態(tài)服務(wù)中損耗性能的地方(有些數(shù)據(jù)的收集會(huì)意想不到得損耗性能)。

#p#
檢查線程安全

框架對(duì)多線程環(huán)境支持的是否好,是框架質(zhì)量的一個(gè)重要的評(píng)估標(biāo)準(zhǔn),往往可以看到甚至有一些成熟的框架也會(huì)有多線程問題。這里涉及幾個(gè)方面:

1,你無法預(yù)料框架的使用者會(huì)怎么樣去實(shí)例化和保存你的API的入口類,如果你的入口類被用成為了一個(gè)單例,在并發(fā)調(diào)用的情況下會(huì)不會(huì)有單線程問題?

這是一個(gè)老話題,之前已經(jīng)說過很多次,你在設(shè)計(jì)框架的時(shí)候心里如果把一個(gè)類定位成了單例的類但卻沒有提供單例模式,你是無法要求使用者來幫你實(shí)現(xiàn)單例的。這其中涉及的不僅僅是多線程問題,可能還有性能問題。比如見過某分布式緩存的客戶端的CacheClient在文檔中要求使用者針對(duì)一個(gè)緩存集群保持一個(gè)CacheClient的單例(因?yàn)槠渲杏辛诉B接池),但是用的人還是每一次都實(shí)例化了一個(gè)CacheClient出來,幾小時(shí)后就會(huì)產(chǎn)生幾萬個(gè)半死的Socket導(dǎo)致網(wǎng)絡(luò)奔潰。又見過某類庫的入口工廠的代碼注釋中寫了要求使用的人把XXXFactory作為單例來使用(因?yàn)槠渲芯彺媪舜罅繑?shù)據(jù)),但是用的人就沒有注意到這個(gè)注釋,每一次都實(shí)例化了一個(gè)XXXFactory,造成GC的崩潰。所以我覺得作為框架的設(shè)計(jì)者開發(fā)人員,最好還是把框架的最佳實(shí)踐直接做到API中,使得使用者不可能出錯(cuò)(之前說過一句話,再重復(fù)一次,好的框架不會(huì)讓使用的人犯錯(cuò))。你可能會(huì)說對(duì)于CacheClient的例子,不可能做成單例的,因?yàn)槲业某绦蚩赡苄枰玫蕉鄠€(gè)緩存的集群,換個(gè)思路,我們完全可以在封裝一層,通過一個(gè)CacheClientCreator之類的類來管理多個(gè)單例的CacheClient。即使在某些極端的情況下,你不能只提供一條路給使用者去走,也需要在框架內(nèi)做一些檢測(cè)機(jī)制,及時(shí)提醒使用者 "我們發(fā)現(xiàn)您這樣使用了框架,這可能會(huì)產(chǎn)生問題,你本意是否打算那樣做呢?"

2,如果你的入口類本來就是單例的,那么你是類中是否持有共享資源,你的API在并發(fā)的情況下被調(diào)用是否可以確保這些資源的線程安全?在解決多線程問題的時(shí)候往往有幾個(gè)難點(diǎn):

百密難有一疏,你很難想到這段代碼會(huì)有人這樣去并發(fā)調(diào)用。比如某init()方法,某config()方法,你總是假設(shè)使用者會(huì)調(diào)用并且僅調(diào)用一次,但事實(shí)不一定這樣,有的時(shí)候調(diào)用者自己也不清楚我的容器會(huì)調(diào)用我這段代碼多少次。
好吧,解決多線程問題各種煩躁,那就對(duì)各種涉及到共享資源的方法全部加鎖。對(duì)方法進(jìn)行粗獷(粒度)的鎖可能會(huì)導(dǎo)致性能急劇下降甚至是死鎖問題。
自以為使用了優(yōu)雅的無鎖代碼或并發(fā)容器但卻達(dá)不到目的。我們往往在大量使用了并發(fā)集合心中暗自竊喜解決了多線程問題的同時(shí)又達(dá)到了極佳的性能,但你以為這樣是解決了線程安全問題但其實(shí)根本就沒有,我們不能假設(shè)A和B都方法是線程安全的,但對(duì)A和B方法調(diào)用的整個(gè)代碼段是線程安全的。

對(duì)于多線程問題,我沒有好的解決辦法,不過下面的幾條我覺得可以嘗試:

需要非常仔細(xì)的過一遍代碼,把涉及到共享資源的地方,以及相關(guān)的方法和類列出來,不要去假設(shè)什么,只要API暴露出去了則假設(shè)它可能被并發(fā)調(diào)用。共享資源不一定是靜態(tài)資源,哪怕資源是非靜態(tài)的,在并發(fā)環(huán)境下對(duì)相同對(duì)象的資源進(jìn)行操作也可能產(chǎn)生問題。
一般而言對(duì)于公開的API,作為框架的設(shè)計(jì)者我們需要確保所有的靜態(tài)方法(或但單例類的實(shí)例方法)是線程安全的,對(duì)于實(shí)例方法我們可以不這么做(因?yàn)樾阅茉颍?,但是需要在注釋中明確提示使用者方法的非線程安全,如果需要并發(fā)調(diào)用請(qǐng)自行處理線程安全問題。
可以看看是否有可能讓這些資源(字段)變?yōu)榉椒▋?nèi)的局部變量,有的時(shí)候我們并不是真正的需要類持有一個(gè)字段,只是因?yàn)槎鄠€(gè)方法要使用相同的東西,隨手一寫罷了。
對(duì)于使用頻率低的一些方法相關(guān)的一些資源沒有必要使用并發(fā)容器,直接采用粗狂的方式進(jìn)行資源加鎖甚至是方法級(jí)別加鎖,先確保沒有線程安全,如果以后做壓測(cè)出現(xiàn)性能問題再來解決。
對(duì)于使用頻率高的一些方法相關(guān)的一些資源可以使用并發(fā)容器,但需要仔細(xì)思考一下代碼是否會(huì)存在線程安全問題,必要的話為代碼設(shè)計(jì)一些多線程環(huán)境的單元測(cè)試去驗(yàn)證。


性能測(cè)試和優(yōu)化

之前也提到過,你不會(huì)預(yù)測(cè)到你的項(xiàng)目會(huì)在怎么樣的訪問量下使用,我們不希望框架和同類的框架相比有明顯的性能差距(如果你做的是一個(gè)ORM框架或RPC框架,這個(gè)工作就是必不可少的),所以在框架基本完成后我們需要做Benchmark:

  1. 確定幾個(gè)測(cè)試用例,盡量覆蓋主流程和一些重要擴(kuò)展
  2. 找?guī)讉€(gè)主流的同類型框架,實(shí)現(xiàn)相同的測(cè)試用例,實(shí)現(xiàn)到時(shí)候要單純一點(diǎn),盡量不要再依賴其它外部框架
  3. 為這些框架和自己的框架,使用壓力測(cè)試工具在相同的環(huán)境和平臺(tái)來跑這些測(cè)試用例,使用圖表繪制在不同的壓力下的執(zhí)行時(shí)間(以及內(nèi)存和CPU等主要資源的消耗情況)
  4. 如果出現(xiàn)明顯的差距則用性能分析工具進(jìn)行排查和優(yōu)化,比如:
  5. 優(yōu)化框架內(nèi)的線程安全的實(shí)現(xiàn)方式
  6. 為框架內(nèi)的代碼做一些緩存(緩存反射得到的元數(shù)據(jù)等等)
  7. 減少調(diào)用層次
  8. 這些調(diào)整可能會(huì)打破原來的主線流程或讓代碼變得難以理解,需要留下相關(guān)注釋
  9. 不斷重壓力測(cè)試和優(yōu)化的過程,每次嘗試優(yōu)化5%~20%的性能,雖然越到后來可能會(huì)越難,如果發(fā)現(xiàn)實(shí)在無法優(yōu)化的話(性能分析工具顯示性能的分布已經(jīng)很均勻了),可以看一下其它框架對(duì)于這部分工作實(shí)現(xiàn)的代碼邏輯

封裝和擴(kuò)展

個(gè)人覺得一個(gè)框架如果只是能用那是第一個(gè)層次,能很方便的進(jìn)行擴(kuò)展或二次開發(fā)那是另外一個(gè)層次,如果我們龍骨階段的工作做的足夠好,框架是一個(gè)立體飽滿的框架,那么這部分的工作量就會(huì)小很多,否則我們需要對(duì)框架進(jìn)行不少的重構(gòu)以便可以達(dá)到這個(gè)層次。

  1. 我們需要縱覽一下框架的所有類型,看看有哪些類型我們是打算提供開發(fā)人員進(jìn)行增強(qiáng)、擴(kuò)展或替換的,對(duì)這些類型進(jìn)行響應(yīng)的結(jié)構(gòu)調(diào)整。
  2. 比如希望被增強(qiáng),則需要從繼承的角度來考慮
  3. 比如希望被擴(kuò)展,則需要從Provider的角度來考慮
  4. 比如希望被替換,則需要在配置中提供組件的替換
  5. 我們需要再為這些類型進(jìn)行精細(xì)化的調(diào)整:
  6. 檢查是否該封閉的封閉了,該開放的開放了
  7. 增強(qiáng)擴(kuò)展或替換是否會(huì)帶來副作用
  8. 對(duì)于新來的外來類型,接收和使用的時(shí)候做足夠的檢查
  9. 相關(guān)日志的完善

#p#
重構(gòu)還是重構(gòu)

光是重構(gòu)這個(gè)事情其實(shí)就可以說一本書了,其實(shí)我有一點(diǎn)代碼的潔癖,這里列一些我自己寫代碼的時(shí)候注重的地方:

  1. 格式:每次提交代碼的時(shí)候使用IDE來格式化你的代碼和引用(當(dāng)然,實(shí)現(xiàn)可能需要配置IDE為你喜歡的代碼風(fēng)格)
  2. 命名:保持整個(gè)類和接口命名統(tǒng)一,各種er,Provider、Creator、Initializer、Invoker、Selector代表的是一件事情,不要使用漢語拼音命名,如果英文不夠好的話多查一下字典,有的時(shí)候我甚至?xí)驗(yàn)橐粋€(gè)命名去閱讀一些源代碼看看老外是怎么命名這個(gè)對(duì)象或這個(gè)方法的
  3. 訪問控制修飾符:這是一個(gè)非常難做好的細(xì)節(jié),因?yàn)橛刑嗟牡胤接性L問控制修飾符,究竟是給予什么級(jí)別的修飾符往往又取決于框架的擴(kuò)展。可以在一開始的時(shí)候給盡量小的權(quán)限,在必要的時(shí)候慢慢提升,比如對(duì)于方法除了一定要給public的地方(比如公共API或?qū)崿F(xiàn)接口),盡量都給private,在有繼承層次關(guān)系的時(shí)候去給到protected,對(duì)于類可以都給默認(rèn)包/程序集權(quán)限,產(chǎn)生編譯錯(cuò)誤的時(shí)候再去給到public
  4. 屬性/getter、setter:對(duì)于非POJO類字段的公開也要仔細(xì)想一下, 是否有必要有setter,因?yàn)橐坏┩獠靠梢詠碓O(shè)置類的某個(gè)內(nèi)部字段,那么不僅僅可能改變了類的內(nèi)部狀態(tài),你還要考慮的是怎么處理這種改變,是不是有線程安全問題等等,甚至要考慮是否有必要開放getter,是否應(yīng)該把類內(nèi)部的信息公開給外部
  5. 方法:思考每一個(gè)方法在當(dāng)前的類中存在是否合理,這是否屬于當(dāng)前類應(yīng)該做的事情,方法是否做了太多事情太少事情
  6. 參數(shù):需要思考,對(duì)于調(diào)用每一個(gè)方法的參數(shù),應(yīng)該是傳給方法,還是讓方法自己去獲?。粦?yīng)該傳多個(gè)參數(shù),還是封裝一個(gè)上下文給到方法
  7. 常量:盡量用枚舉或靜態(tài)字符串來代替框架使用到的一些常量或幻數(shù),需要為常量進(jìn)行一個(gè)分類不能一股腦堆在一個(gè)常量類Consts中

除了上面說的一些問題,我覺得對(duì)于重構(gòu),最重要的一句話就是:不要讓同一段代碼出現(xiàn)兩遍,主要圍繞這個(gè)原則進(jìn)行重構(gòu)往往就會(huì)解決很多設(shè)計(jì)問題,要實(shí)現(xiàn)這個(gè)目標(biāo)可能需要:

  1. 干差不多活的類使用繼承來避免代碼重復(fù)(提煉超類),使用模版方法來把差異留給子類實(shí)現(xiàn)
  2. 構(gòu)造方法可以層次化調(diào)用,主構(gòu)造方法只要一個(gè)就可以了,不要在構(gòu)造方法中實(shí)現(xiàn)太多邏輯
  3. 如果方法的代碼有重復(fù)可以考慮對(duì)方法提取出更小的公共方法來調(diào)用(提煉方法),也可以考慮使用Lambda表達(dá)式進(jìn)行更小粒度重復(fù)代碼的提?。ㄌ釤掃壿嫞?/li>
  4. 可以使用IDE或一些代碼分析工具來分析重復(fù)代碼,如果你能想盡一切辦法來避免這些重復(fù)的話,代碼質(zhì)量可以提高一個(gè)層次

其實(shí)也不一定是在重構(gòu)的時(shí)候再去處理上面所有的問題,如果在寫代碼的時(shí)候都帶著這些意識(shí)來寫的話那么重構(gòu)的負(fù)擔(dān)就會(huì)小一點(diǎn)(不過寫代碼思想的負(fù)擔(dān)比較大,需要同時(shí)考慮封裝問題、優(yōu)雅問題、日志異常問題、多線程問題等等,所以寫一套能用的代碼和寫一套好的代碼其實(shí)不是一回事情)。


項(xiàng)目文檔

如果要?jiǎng)e人來使用你的框架,除了示例項(xiàng)目來說提供和維護(hù)一份項(xiàng)目文檔是很有必要的,我建議文檔分為這幾個(gè)部分:

  1. 特性 Features:
  2. 相當(dāng)于項(xiàng)目的一個(gè)宣傳手冊(cè),讓別人能被你項(xiàng)目的亮點(diǎn)所吸引
  3. 每一個(gè)特性可以是一句話來介紹
  4. 新手入門 Get started:
  5. 介紹框架的基本定位和作用
  6. 從下載開始,通過一步一步的方式讓用戶了解怎么把框架用起來
  7. 整個(gè)文檔的閱讀時(shí)間在10分鐘以內(nèi)
  8. 新手教程 Tutorials:
  9. 提供5~10篇文章站在使用者的角度來介紹項(xiàng)目的主要功能點(diǎn)
  10. 還是通過一步一步的方式,教大家使用框架完成一個(gè)小項(xiàng)目(比如CRUD)
  11. 介紹框架使用的最佳實(shí)踐
  12. 整個(gè)文檔的閱讀時(shí)間在8小時(shí)內(nèi)
  13. 手冊(cè) Manual:
  14. 介紹項(xiàng)目的定位和理念
  15. 詳細(xì)介紹項(xiàng)目的每一個(gè)功能點(diǎn),可以站在框架設(shè)計(jì)者的角度多介紹一些理念
  16. 詳細(xì)介紹項(xiàng)目的每一個(gè)配置,以及默認(rèn)配置和典型配置
  17. 詳細(xì)介紹項(xiàng)目的每一個(gè)擴(kuò)展點(diǎn)和替換點(diǎn)
  18. 文檔最好不是帶格式的,方便以后適配各種文檔生成器和開源網(wǎng)站


開源

開源的好處是有很多人可以看到你的代碼幫助你改進(jìn),你的框架也可能會(huì)在更多的復(fù)雜環(huán)境下使用,框架的發(fā)展會(huì)較快框架的代碼質(zhì)量也會(huì)有很大的提升。

要把框架進(jìn)行開源,除了上面的各種工作之外可能還有一些額外的工作需要做:

  1. 選擇一個(gè)合適的License,并且檢測(cè)自己選擇的License與使用到的類庫的License是否兼容,在代碼頭的地方標(biāo)記上License。
  2. 要確保每一個(gè)人都可以在自己的環(huán)境中可以構(gòu)建你的代碼,盡量使用Maven等大家熟悉的構(gòu)建工具來管理依賴和構(gòu)建。
  3. 選擇諸如Github等平臺(tái)來管理源代碼,并以良好的格式上傳你的文檔,有條件的話對(duì)示例子網(wǎng)站進(jìn)行部署。
  4. 如果你希望你的代碼讓更多的人一起來參與開發(fā),那么需要制定和公開一些規(guī)范,比如風(fēng)格、命名、提交流程、測(cè)試規(guī)范、質(zhì)量要求等等。
  5. 開源后時(shí)刻對(duì)項(xiàng)目進(jìn)行關(guān)注,對(duì)各種反饋和整合請(qǐng)求進(jìn)行及時(shí)的反饋,畢竟開源是讓別人來幫你一起改進(jìn)代碼,不是單純讓別人來學(xué)習(xí)你的代碼也不是讓別人來幫你寫代碼。 

看到這里你可能相信我一開始的話了吧,框架可以使用到完善可以商用差距還是很大的,而且還要確保在迭代的過程中框架不能偏離開始的初衷不能有很大的性能問題出現(xiàn),任重道遠(yuǎn)。

原文鏈接:http://www.cnblogs.com/lovecindywang/p/4444915.html

原文鏈接:http://www.cnblogs.com/lovecindywang/p/4447739.html

責(zé)任編輯:王雪燕 來源: 博客園
相關(guān)推薦

2022-10-08 00:06:00

JS運(yùn)行V8

2019-08-01 12:59:21

Bug代碼程序

2022-05-09 10:47:08

登錄SpringSecurity

2020-10-16 15:06:59

開發(fā)技術(shù)方案

2015-10-12 16:45:26

NodeWeb應(yīng)用框架

2010-11-19 09:16:38

2020-04-08 08:35:20

JavaScript模塊函數(shù)

2024-01-26 11:08:57

C++函數(shù)返回不同類型

2022-03-24 14:49:57

HTTP前端

2017-08-21 16:36:12

語法樹AST解析器HTML5

2013-01-14 09:44:58

JavaScriptJSJS框架

2012-01-04 13:55:23

Canvas

2017-06-08 15:53:38

PythonWeb框架

2021-05-07 06:08:03

分布式框架NIO

2022-04-11 08:20:36

編程輔助工具GitHubCopilot

2017-09-01 14:18:50

前端React組件

2021-11-02 22:50:10

鼠標(biāo)計(jì)算機(jī)傳感器

2020-08-29 19:15:09

python數(shù)據(jù)庫SQLite

2025-02-14 10:13:55

2019-11-22 09:30:59

設(shè)計(jì)Java程序員
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)