說明:做者也沒寫過什麼框架,只是分享一些本身的理解,拋磚引玉罷了。若是你寫過一些框架可能會產生一些共鳴歡迎討論,若是你正在寫或正打算寫一個框架可能會給你一些啓發。本文覺得較長可能會分多個篇博客來寫,如今能想到的是主要分爲步驟、模式兩部分。若是你以爲好,按一個推薦舉手之勞讓更多的人能夠看到。html
步驟
定位
所謂定位就是回答幾個問題,我出於什麼目的要寫一個框架,個人這個框架是幹什麼的,有什麼特性適用於什麼場景,個人這個框架的用戶對象是誰,他們會怎麼使用,框架由誰維護未來怎麼發展等等。 前端
- 若是你打算寫框架,那麼確定內心已經有一個初步的定位,好比它是一個緩存框架、Web MVC框架、IOC框架、ORM/數據訪問框架、RPC框架或是一個用於Web開發的全棧式框架。
- 是否要重複造輪子?除非是練手項目,通常咱們是有了解決不了問題的時候纔會考慮不使用既有的成熟的框架而重複造輪子的,這個時候須要列出新框架主要但願解決什麼問題。有關是否應該重複造輪子的話題討論了不少,個人建議是在把問題列清後進行簡單的研究看看是否能夠經過擴展示有的框架來解決這個問題。通常而言大部分紅熟的框架都有必定的擴展和內部組件的替換能力,能夠解決大部分技術問題,但在以下狀況下咱們可能不得不本身去寫一個框架,好比即便經過擴展也沒法知足技術需求、安全緣由、須要更高的生產力、須要讓框架和公司內部的流程更好地進行適配、開源的普適框架沒法知足性能需求、二次開發的成本高於從新開發的成本等等。
- 主打輕量級?輕量級是不少人打算本身寫一個新框架的緣由,但咱們要明白,大部分項目在一開始的時候其實都是輕量級的,隨着框架的用戶愈來愈多,它一定須要知足各類奇怪的需求,在通過了無數次迭代以後,框架的主線流程就會多不少擴展點、檢測點,這樣框架勢必變得愈來愈重(從框架的入口到框架的工做結束的方法調用層次愈來愈多,勢必框架也就愈來愈慢),若是你打算把框架定位於一個輕量級的框架的話,那麼在從此的迭代過程當中須要進行一些權衡,在心中有堅決的輕量級的理念的同時不斷作性能測試來確保框架的輕量,不然隨着時間的發展框架可能會愈來愈重進而偏離了開始的定位。
- 特性?若是你打算寫一個框架,而且只有輕量級這一個理由的話,你或許應該再爲本身的框架想一些新特性,就像作一個產品同樣,若是找不出兩個以上的亮點,那麼這個產品不太可能成功,好比你的新框架能夠是一個零配置的框架,能夠是一個前端開發也能用的後端框架。
- 其它?通常來講框架是給程序員使用的,咱們要考慮框架使用的頻度是怎麼樣的,這可能決定的框架的性能需求和穩定性需求。還有,須要考慮框架未來怎麼發展,是但願走開源路線仍是商業路線。固然,這些問題也能夠留到框架有一個大體的結構後再去考慮。
咱們來爲本文模擬一個場景,假設咱們以爲現有的Spring MVC等框架開發起來效率有點低,打算重複造輪子,對於新框架的定位是一個給Java程序員使用的輕量級的、零配置的、易用的、易擴展的Web MVC框架。 程序員
調研
雖然到這裏你已經決定去寫一個框架了,可是在着手寫以前仍是至少建議評估一下市面上的相似(成熟)框架。須要作的是通讀這些框架的文檔以及閱讀一些源碼,這麼作有幾個目的: 正則表達式
- 經過分析現有框架的功能,能夠制定出一個新框架要實現的功能列表。
- 經過分析現有框架的問題,總結出新框架須要避免的東西和改善的地方。
- 經過閱讀現有框架的源碼,幫助本身理清框架的主線流程爲整體設計作鋪墊(後面整體設計部分會更多談到)。
- 若是能充分理解現有的框架,那麼你就是站在巨人的肩膀上寫框架,不然極可能就是在井底造輪子。
新開發一個框架的好處是沒有兼容歷史版本的包袱,可是責任也一樣重大,由於若是對於一開始的定位或設計工做沒有作好的話,未來若是要對格局進行改變就會有巨大的向前兼容的包袱(除非你的框架沒有在任何正式項目中使用),兼容意味着框架可能會愈來愈重,可能會愈來愈難看,閱讀至少一到兩個開源實現,作好充分的調研工做可使你避免犯大錯。 數據庫
假設咱們評估了一些主流框架後已經很明確,咱們的MVC框架是一個Java平臺的、基於Servlet的輕量級的Web MVC框架,主要的理念是約定優於配置,高內聚大於低耦合,提供主流Web MVC框架的大部分功能,而且易用方面有所創新,新特性體包括: 編程
- 起手零配置,整體上約定因爲配置,即便須要擴展配置也支持經過代碼和配置文件兩種方式進行配置。
- 除了Servlet以外不依賴其它類庫,支持經過插件方式和諸如Spring等框架進行整合。
- 更優化的項目結構,不須要按照傳統的Java Web項目結構那樣來分離代碼和WEB-INF,視圖能夠和代碼在一塊兒,閱讀代碼更便利。
- 攔截器和框架自己更緊密,提供Action、Controller和Global三個級別的"攔截器"(或者說過濾器)。
- 豐富的Action的返回值,返回的能夠是視圖、能夠是重定向、能夠是文件、能夠是字符串、能夠是Json數據,能夠是Javascript代碼等等。
- 支持針對測試環境自動生成測試的視圖模型數據,以便前端和後端能夠同時開發項目。
- 支持在開發的時候自動生成路由信息、模型綁定、異常處理等配置的信息頁面和調試頁面,方便開發和調試。
- 提供一套通用的控件模版,使得,而且支持多種模版引擎,好比Jsp、Velocity、Freemarker、Mustache等等。
嗯,看上去挺誘人的,這是一個不錯的開端,若是你要寫的框架本身都不以爲想用的話,那麼別人就更不會有興趣來嘗試使用你的框架了。 json
解決難點
之因此把解決難點放在開搞以前是由於,若是實現這個框架的某些特性,甚至說實現這個框架的主流程有一些核心問題難以解決,那麼就要考慮對框架的特性進行調整,甚至取消框架的開發計劃了。有的時候咱們在用A平臺的時候發現一個很好用的框架,但願把這個框架移植到B平臺,這個想法是好的,但之因此在這之前這麼多年沒有人這麼幹過是由於這個平臺的限制壓根不可能實現這樣的東西。好比咱們要實現一個MVC框架,勢必須要依賴平臺提供的反射特性,若是你的語言平臺壓根就沒有運行時反射這個功能,那麼這就是一個很是難以解決的難點。又好比咱們在某個平臺實現一個相似於.NET平臺Linq2Sql的數據訪問框架,但若是這個目標平臺的開發語言並不像C#那樣提供了類型推斷、匿名類型、Lambda表達式、擴展方法的話那麼因爲語法的限制你寫出來的框架在使用的時候是沒法像.NET平臺Linq2Sql那樣優雅的,這就違背了實現框架的主要目的,實現新的框架也就變得意義不大了。 後端
對於咱們要實現的MVC框架貌似不存在什麼根本性的沒法解決的問題,畢竟在Java平臺已經有不少能夠參考的例子了。若是框架的實現整體上沒什麼問題的話,就須要逐一評估框架的這些新特性是否能夠解決。建議對於每個難點特性作一個原型項目來證實可行,以避免在框架實現到一半的時候發現有沒法解決的問題就比較尷尬了。 設計模式
分析一下,貌似咱們要實現的這8大特性只有第1點要研究一下,看看如何免配置經過讓代碼方式讓咱們的Web MVC框架能夠和Servlet進行整合,若是沒法實現的話,咱們可能就須要把第1點特性從零配置改成一分鐘快速配置了。 緩存
開搞
- 首先須要給本身框架取一個名字,取名要考慮到易讀、易寫、易記,也須要儘可能避免和市面上其它產品的名字重複,還有就是最好不要起一個侮辱其它同類框架的名字以避免引發公憤。
- 若是未來打算把項目搞大的話,能夠提早註冊一下項目的相關域名,畢竟如今域名也便宜,避免到時候項目名和域名差距很大,或項目的.com或.org域名對應了一個什麼不太和諧的網站這就尷尬了。
- 而後就是找一個地方來託管本身的代碼,若是一開始不但願公開代碼的話,最好除了本地源代碼倉庫還有一個異地的倉庫以避免磁盤損壞致使抱憾終身,固然若是不怕出醜的話也能夠在起步的時候就使用Github等網站來託管本身的代碼。
整體設計
對於整體設計個人建議是一開始不必定須要寫什麼設計文檔畫什麼類圖,由於可能一開始的時候沒法造成這麼具體的概念,咱們能夠直接從代碼開始作第一步。框架的使用者通常而言仍是開發人員,拋開框架的內在的實現不說,框架的API設計的好壞取決於兩個方面。對於普通開發人員而言就是使用層面的API是否易於使用,拿咱們的MVC框架舉例來講:
- 最基本的,搭建一個HelloWorld項目,聲明一個Controller和Action,配置一個路由規則讓Get方法的請求能夠解析到這個Action,能夠輸出HelloWorld文字,怎麼實現?
- 若是要實現從Cookie以及表單中獲取相關數據綁定到Action的參數裏面,怎麼實現?
- 若是要配置一個Action在調用前須要判斷權限,在調用後須要記錄日誌,怎麼實現?
咱們這裏說的API,它不必定全都是方法調用的API,廣義上來講咱們認爲框架提供的接入層的使用均可以認爲是API,因此上面的一些功能均可以認爲是MVC框架的API。
框架除了提供基本的功能,還要提供必定程度的擴展功能,使得一些複雜的項目可以在某些方面對框架進行加強以適應各類需求,好比:
- 個人Action是否能夠返回圖片驗證碼?
- 個人Action的參數綁定是否能夠從Memcached中獲取數據?
- 若是出現異常,可否在開發的時候顯示具體的錯誤信息,在正式環境顯示友好的錯誤頁面而且記錄錯誤信息到數據庫?
通常而言若是要實現這樣的功能就須要本身實現框架公開的一些類或接口,而後把本身的實現"註冊"到框架中,讓框架能夠在某個時候去使用這些新的實現。這就須要框架的設計者來考慮應該以怎麼樣的友好形式公開出去哪些內容,使得之後的擴展實如今自由度以及最少實現上的平衡,同時要兼顧外來的實現不破壞框架已有的結構。
要想清楚這些不是一件容易的事情,因此在框架的設計階段徹底可使用從上到下的方式進行設計。也就是不去考慮框架怎麼實現,而是以一個使用者的身份來寫一個框架的示例網站,API怎麼簡單怎麼舒服就怎麼設計,只從使用者的角度來考慮問題。對於相關用到的類,直接寫一個空的類(能用接口的儘可能用接口,你的目的只是經過編譯而不是能運行起來),讓程序能夠經過編譯就能夠了。你能夠從框架的普通使用開始寫這樣一個示例網站,而後再寫各類擴展應用,在此期間你可能會用到框架內部的20個類,這些類就是框架的接入類,在你的示例網站經過編譯的那剎那,其實你已經實現了框架的接入層的設計。
這裏值得一說的是API的設計蘊含了很是多的學問以及經驗,要在目標平臺設計一套合理易用的API首先須要對目標平臺足夠了解,每個平臺都有一些約定俗成的規範,若是設計的API能符合這些規範那麼開發人員會更容易接受這個框架,此外還有一些建議:
- 之因此咱們把API的設計先行,而不是讓框架的設計先行是由於這樣咱們更容易設計出好用的API,做爲框架的實現者,咱們每每會進行一些妥協,咱們可能會爲了在框架內部DRY而設計出一套醜陋的API讓框架的使用者去作一些重複的工做;咱們也可能會由於想讓框架變得更鬆耦合強迫框架的使用者去使用到框架的一些內部API去初始化框架的組件。若是框架不是易用的,那麼框架的內部設計的再合理又有什麼意義?
- 儘可能少暴露一些框架內部的類名吧,對於框架的使用者來講,你的框架對他一點都不熟悉,若是要上手你的框架須要學習一到兩個類尚可接受,若是要使用到十幾個類會頭暈腦脹的,即便你的框架有很是多的功能以及配置,能夠考慮提供一個入口類,好比建立一個ConfigCenter類做爲入口,讓使用者能夠僅僅探索這個類即可對框架進行全部的配置。
- 一個好的框架是可讓使用者少犯錯誤的,框架的設計者務必要考慮到,框架的使用者沒有這個業務來按照框架的最佳實踐來作,因此在設計API的時候,若是你但願API的使用者必定要按照某個方式來作的話,能夠考慮設置一個簡便的重載來加載默認的最合理的使用方式而不是要求使用者來爲你的方法初始一些什麼依賴,同時也能夠在API內部作一些檢測,若是發現開發人員可能會犯錯進行一些提示或拋出異常。好的框架無需過多的文檔,它能夠在開發人員用的時候告知它哪裏錯了,最佳實踐是什麼,即使他們真的錯了也能以默認的更合理的方式來彌補這個錯誤。
- 建議全部的API都有一套統一的規範,好比入口都叫XXXCenter或XXXManager,而不是叫XXXCenter、YYYManager和ZZZService。API每每須要進行迭代和改良的,在首個版本中把好名字用掉也不必定是一個好辦法,最好仍是給本身的框架各類API的名字留一點餘地,這樣之後萬一須要升級換代不至於太牽強。
下一步工做就是把項目中那些空的類按照功能進行劃分。目的很簡單,就是讓你的框架的100個類或接口可以按照功能進行拆分和歸類,這樣別人一打開你的框架就能夠立刻知道你的框架分爲哪幾個主要部分,而不是在100個類中暈眩;還有由於一旦在你的框架有使用者後你再要爲API相關的那些類調整包就比困難了,即便你在建立框架的時候以爲個人框架就那麼十幾個類無需進行過多的分類,可是在未來框架變大又發現當初設計的不合理,沒法進行結構調整就會變得很痛苦。所以這個工做仍是至關重要的,對於大多數框架來講,能夠有幾種切蛋糕的方式:
- 分層。我以爲框架和應用程序同樣,也須要進行分層。傳統的應用程序咱們分爲表現層、邏輯層和數據訪問層,相似的對於不少框架也能夠進行橫向的層次劃分。要分層的緣由是咱們的框架要處理的問題是基於多層抽象的,就像若是沒有OSI七層模型,要讓一個HTTP應用去直接處理網絡信號是不合理的也是不利於重用的。舉一個例子,若是咱們要寫一個基於Socket的RPC的框架,咱們須要處理方法的代理以及序列化,以及序列化數據的傳輸,這徹底是兩個層面的問題,前者偏向於應用層,後者偏向於網絡層,咱們徹底有理由把咱們的框架分爲兩個層面的項目(至少是兩個包),rpc.core和rpc.socket,前者不關心網絡實現來處理全部RPC的功能,後者不關心RPC來處理全部的Socket功能,在未來即便咱們要淘汰咱們的RPC的協議了,咱們也能夠重用rpc.socket項目,由於它和RPC的實現沒有任何關係,它關注的只是socket層面的東西。
- 橫切。剛纔說的分層是橫向的分割,橫切是縱向的分割(橫切是跨多個模塊的意思,不是橫向來切的意思)。其實橫切關注點就是諸如日誌、配置、緩存、AOP、IOC等通用的功能,對於這部分功能,咱們不該該把他們和真正的業務邏輯混淆在一塊兒。對於應用類項目是這樣,對於框架類項目也是這樣,若是某一部分的代碼量很是大,徹底有理由爲它分出一個單獨的包。對於RPC項目,咱們可能就會把客戶端和服務端通信的消息放在common包內,把配置的處理單獨放在config包內。
- 功能。也就是要實現一個框架主要解決的問題點,好比對於上面提到的RPC框架的core部分,能夠想到的是咱們主要解決是客戶端如何找到服務端,如何把進行方法調用以及把方法的調用信息傳給目標服務端,服務端如何接受到這樣的信息根據配置在本地實例化對象調用方法後把結果返回客戶端三大問題,那麼咱們可能會把項目分爲routing、client、server等幾個包。
若是是一個RPC框架,大概是這樣的結構:
對於咱們的Web MVC框架,舉例以下:
-
咱們能夠有一個mvc.core項目,細分以下的包:
- common:公共的一組件,下面的各模塊都會用到
- config:配置模塊,解決框架的配置問題
- startup:啓動模塊,解決框架和Servlet如何進行整合的問題
- plugin:插件模塊,插件機制的實現,提供IPlugin的抽象實現
- routing:路由模塊,解決請求路徑的解析問題,提供了IRoute的抽象實現和基本實現
- controller:控制器模塊,解決的是如何產生控制器
- model:視圖模型模塊,解決的是如何綁定方法的參數
- action:action模塊,解決的是如何調用方法以及方法返回的結果,提供了IActionResult的抽象實現和基本實現
- view:視圖模塊,解決的是各類視圖引擎和框架的適配
- filter:過濾器模塊,解決是執行Action,返回IActionResult先後的AOP功能,提供了IFilter的抽象實現以及基本實現
-
咱們能夠再建立一個mvc.extension項目,細分以下的包:
- filters:一些IFilter的實現
- results:一些IActionResult的實現
- routes:一些IRoute的實現
- plugins:一些IPlugin的實現
這裏咱們以IXXX來描述一個抽象,能夠是接口也能夠是抽象類,在具體實現的時候根據需求再來肯定。
這種結構的劃分方式徹底吻合上面說的切蛋糕方式,能夠看到除了橫切部分和分層部分,做爲一個Web MVC框架,它核心的組件就是routing、model、view、controller、action(固然,對於有些MVC框架它沒有route部分,route部分是交由Web框架實現的)。
若是咱們在這個時候還沒法肯定框架的模塊劃分的話,問題也不大,咱們能夠在後續的搭建龍骨的步驟中隨着更多的類的創建,繼續理清和肯定模塊的劃分。
通過了設計的步驟,咱們應該內心對下面的問題有一個初步的規劃了:
- 咱們的框架以什麼形式來提供如何優雅的API?
- 咱們的框架包含哪些模塊,模塊大概的做用是什麼?
搭建龍骨
在通過了初步的設計以後,咱們能夠考慮爲框架搭建一套龍骨,一套抽象的層次關係。也就是用抽象類、接口或空的類實現框架,能夠經過編譯,讓框架撐起來,就像造房子搭建房子的鋼筋混凝土結構(添磚加瓦是後面的事情,咱們先要有一個結構)。對於開發應用程序來講,其實沒有什麼撐起來一說,由於應用程序中不少模塊都是並行的,它可能並無一個主結構,主流程,而對於框架來講,它每每是一個高度面向對象的,高度抽象的一套程序,搭建龍骨也就是搭建一套抽象層。這麼說可能有點抽象,咱們仍是來想一下若是要作一個Web MVC框架,須要怎麼爲上面說的幾個核心模塊進行抽象(咱們也來體會一下框架中一些類的命名,這裏咱們爲了更清晰,爲全部接口都命名爲IXXX,這點不太符合Java的命名規範):
-
routing MVC的入口是路由
- 每個路由都是IRoute表明了不一樣的路由實現,它也提供一個getRouteResult()方法來返回RouteResult對象
- 咱們實現一個框架自帶的DefaultRoute,使得路由支持配置,支持默認值,支持正則表達式,支持約束等等
- 咱們須要有一個Routes類來管理全部的路由IRoute,提供一個findRoute()方法來返回RouteResult對象,天然咱們這邊調用的就是IRoute的getRouteResult()方法,返回能匹配到的結果
- RouteResult對象就是匹配的路由信息,包含了路由解析後的全部數據
-
controller 路由下來是控制器
- 咱們有IControllerFactory來建立Controller,提供createController()方法來返回IController
- IController表明控制器,提供一個execute()方法來執行控制器
- 咱們實現一個框架自帶的DefaultControllerFactory來以約定因爲配置的方式根據約定規則以及路由數據RouteResult來找到IController並建立它
- 咱們爲IController提供一個抽象實現,AbstractController,要求全部MVC框架的使用者建立的控制器須要繼承AbstractController,在這個抽象實現中咱們能夠編寫一些便捷的API以便開發人員使用,好比view()方法、file()方法、redirect()方法、json()方法、js()方法等等
-
action 找到了控制器後就是來找要執行的方法了
- 咱們有IActionResult來表明Action返回的結果,提供一個execute()方法來執行這個結果
- 咱們的框架須要實現一些自帶的IActionResult,好比ContentResult、ViewResult、FileResult、JsonResult、RedirectResult來對應AbstractController的一些便捷方法
- 再來定義一個IActionInvoker來執行Action,提供一個invokeAction()方法
- 咱們須要實現一個DefaultActionInvoker以默認的方式進行方法的調用,也就是找到方法的一些IFilter按照必定的順序執行他們,最後使用反射進行方法的調用獲得上面說的IActionResult並執行它的execute()方法
-
filter 咱們的框架很重要的一點就是便捷的過濾器
- 剛纔提到了IFilter,表明的是一個過濾器,咱們提供IActionFilter對方法的執行先後進行過濾,提供IResultFilter對IActionResult執行先後進行過濾
- 咱們的IActionInvoker怎麼找到須要執行的IFilter呢,咱們須要定義一個IFilterProvider來提供過濾器,它提供一個getFilters()方法來提供全部的IFilter的實例
- 咱們的框架能夠實現一些自帶的IFilterProvider,好比AnnotationFilterProvider經過掃描Action或Controller上的註解來獲取須要執行的過濾器信息;好比咱們還能夠實現GlobalFilterProvider,開發人員能夠直接經過配置或代碼方式告知框架應用於全局的IFilter
- 既然咱們實現了多個IFilterProvider,咱們天然須要有一個類來管理這些IFilterProvider,咱們實現一個FilterProviders類並提供getFilters()方法(這和咱們的Routes類來管理IRoute是相似的,命名統一)
-
view 各類IActionResult中最特殊最複雜的就是ViewResult,咱們須要有一個單獨的包來處理ViewResult的邏輯
- 咱們須要有IViewEngine來表明一個模版引擎,提供一個getViewEngineResult()方法返回ViewEngineResult
- ViewEngineResult包含視圖引擎尋找視圖的結果信息,裏面包含IView和尋找的一些路徑等
- IView天然表明的是一個視圖,提供render()方法(或者爲了統一也能夠叫作execute)來渲染視圖
- 咱們的框架能夠實現常見的一些模版引擎,好比FreemarkerViewEngine、VelocityViewEngine等,VelocityViewEngine返回的ViewEngineResult天然包含的是一個實現IView的VelocityView,不會返回其它引擎的IView
- 一樣的,咱們是否是須要一個ViewEngines來管理全部的IViewEngine呢,一樣也是實現findViewEngine()方法
-
common 這裏能夠放一些項目中各個模塊都要用到的一些東西
- 好比各類context,context表明的是執行某個任務須要的環境信息,這裏咱們能夠定義HttpContext、ControllerContext、ActionContext和ViewContext,後者繼承前者,隨着MVC處理流程的進行,View執行時的上下文相比Action執行時的上下文信息確定是多了視圖的信息,其它同理,之因此把這個信息放在common裏面而不是放在各個模塊本身的包內是由於這樣更清晰,能夠一目瞭然各類對象的執行上下文有一個立體的概念
- 好比各類helper或utility
接下去就再也不詳細闡述model、plugin等模塊的內容了。
看到這裏,咱們來總結一下,咱們的MVC框架在組織結構上有着高度的統一:
- 若是xxx自己並沒有選擇策略,但xxx的建立過程也不是一個new這麼簡單的,能夠由xxxFactory類來提供一個xxx
- 若是咱們須要用到不少個yyy,那麼咱們會有各類yyyProvider(經過getyyy()方法)來提供這些yyy,而且咱們須要有一個yyyProviders來管理這些yyyProvider
- 若是zzz的選擇是有策略性的,會按照須要選擇zzz1或zzzN,那麼咱們可能會有一個zzzs來管理這些zzz而且(經過findzzz()方法)來提供合適的zzz
同時咱們框架的相關類的命名也是很是統一的,能夠一眼看出這是實現、仍是抽象類仍是接口;是提供程序,是執行結果仍是上下文。固然,在未來的代碼實現過程當中極可能會把不少接口變爲抽象類提供一些默認的實現,這並不會影響項目的主結構。咱們會在模式篇對框架經常使用的一些高層設計模式作更多的介紹。
到了這裏,咱們的項目裏已經有幾十個空的(抽象)類、接口了,其中也定義了各類方法能夠把各個模塊串起來(各類find()方法和execute()方法),能夠說整個項目的龍骨已經創建起來了,這種感受很好,由於咱們內心頗有底,咱們只須要在接下去的工做中作兩個事情:
- 實現各類DefaultXXX來走通主流程
- 實現各類IyyyProvider和Izzz接口來完善支線流程
走通主線流程
所謂走通主線流程,就是讓這個框架能夠以一個HelloWorld形式跑起來,這就須要把幾個核心類的核心方法使用最簡單的方式進行實現,仍是拿咱們的MVC框架來舉例子:
-
從startup開始,可能須要實現ServletContextListener來動態註冊咱們框架的入口Servlet,暫且起名爲DispatcherServlet吧,在這個類中咱們須要走一下主線流程
- 調用Routes.findRoute()得到IRoute
- 調用IRoute.getRouteResult()來得到RouteResult
- 使用拿到的RouteResult做爲參數調用DefaultControllerFactory.createController()得到IController(其實也是AbstractController)
- 調用IController.execute()
- 在config中建立一個IConfig做爲一種配置方式,咱們實現一個DefaultConfig,把各類默認實現註冊到框架中去,也就是DefaultRoute、DefaultControllerFactory、DefaultActionInvoker,而後把各類IViewEngine加入ViewEngines
-
而後須要完成相關默認類的實現:
- 實現Routes.findRoute()
- 實現DefaultRoute.getRouteResult()
- 實現DefaultControllerFactory.createController()
- 實現AbstractController.execute()
- 實現DefaultActionInvoker.invokeAction()
- 實現ViewResult.execute()
- 實現ViewEngines.findViewEngine()
- 實現VelocityViewEngine.getViewEngineResult()
- 實現VelocityView.render()
在這一步,咱們並不必定要去觸碰filter和model這部分的內容,咱們的主線流程只是解析路由,得到控制器,執行方法,找到視圖而後渲染視圖。過濾器和視圖模型的綁定屬於加強型的功能,屬於支線流程,不屬於主線流程。
雖然在這裏咱們說了一些MVC的實現,但本文的目的不在於教你實現一個MVC框架,因此不用深究每個類的實現細節,這裏想說的是,在前面的龍骨搭建完後,你會發現按照這個龍骨爲它加一點肉上去實現主要的流程是瓜熟蒂落的事情,毫無痛苦。在整個實現的過程當中,你能夠不斷完善common下的一些context,把方法的調用參數封裝到上下文對象中去,不但看起來清楚且符合開閉原則。到這裏,咱們應該能夠跑起來在設計階段作的那個示例網站的HelloWorld功能了。
在這裏還想說一點,有些人在實現框架的時候並無搭建龍骨的一步驟,直接以非OOP的方式實現了主線流程,這種方式有如下幾個缺點:
- 不容易作到SRP單一指責原則,你很容易把各類邏輯都集中寫在一塊兒,好比大量的邏輯直接寫到了DispatcherServlet中,輔助一些Service或Helper,整個框架就肥瘦不勻,有些類特別龐大有些類特別小。
- 不容易作到OCP開閉原則,擴展起來不方便須要修改老的代碼,咱們指望的擴展是實現新的類而後讓框架感知,而不是直接修改框架的某些代碼來加強功能。
- 很難實現DIP依賴倒置原則,即便你依賴的確實是IService但其實就沒意義,由於它只有一個實現,只是把他看成幫助類來用罷了。
實現各類支線流程
咱們想一下,對於這個MVC框架有哪些沒有實現的支線流程?其實無需多思考,由於咱們在搭建龍骨階段的設計已經給了咱們明確的方向了,咱們只須要把除了主線以外的那些龍骨上也填充一些實體便可,好比:
- 實現更多的IRoute,並註冊到Routes
- 實現更多的IViewEngine,並註冊到ViewEngines
- 實現必要的IFilterProvider以及FilterProviders,把IFilterProvider註冊到FilterProviders
- 加強DefaultActionInvoker.invokeAction()方法,在合適的時候調用這些IFilter
- 實現更多的IActionResult,而且爲AbstractController實現更多的便捷方法來返回這些IActionResult
- ……實現更多model模塊的內容和plugin模塊的內容
實現了這一步後,你會發現整個框架飽滿起來了,每個包中再也不是僅有的那些接口和默認實現,並且會有一種OOP的爽快感,爽快感來源於幾個方面:
- 面對接口編程抽象和多態的放心安心的爽快感
- 爲抽象類實現具體類享受到父類大量實現的知足的爽快感
- 實現了大量的接口和抽象類後充實的爽快感
咱們再來總結一下以前說的那些內容,實現一個框架的第一大步就是:
- 設計一套合理的接口
- 爲框架進行模塊劃分
- 爲框架搭建由抽象結構構成的骨架
- 在這個骨架的基礎上實現一個HelloWorld程序
- 爲這個骨架的其它部分填充更多實現
通過這樣的一些步驟後能夠發現這個框架是很穩固的,很平衡的,很易於擴展的。其實到這裏不少人以爲框架已經完成了,有血有肉,其實我的以爲只能說開發工做實現了差很少30%,後文會繼續說,畢竟直接把這樣一個血肉之軀拿出去對外有點嚇人,咱們須要爲它進行不少包裝和完善。
http://www.cnblogs.com/lovecindywang/p/4444915.html
單元測試
在這以前咱們寫的框架只能說是一個在最基本的狀況下可使用的框架,做爲一個框架咱們沒法預測開發人員未來會怎麼使用它,因此咱們須要作大量的工做來確保框架不但各類功能都是正確的,並且仍是健壯的。寫應用系統的代碼,大多數項目是不會去寫單元測試的,緣由不少:
- 項目趕時間,連作一些輸入驗證都沒時間搞,哪裏有時間寫測試代碼。
- 項目對各項功能的質量要求不高,只要能在標準的操做流程下功能可用便可。
- 項目基本不會去改或是臨時項目,一旦測試經過以後就始終是這樣子了,沒有迭代。
- ……
對於框架,偏偏相反,沒有配套的單元測試的框架(也就是僅僅使用人工的方式進行測試,好比在main中調用一些方法觀察日誌或輸出,或者運行一下示例項目查看各類功能是否正常,是很是可怕的)緣由以下:
- 自動化程度高,迴歸須要的時間短,甚至能夠整合到構建過程當中進行,這是人工測試沒法實現的。
- 框架必定是有很是多的迭代和重構的, 每一次修改雖然只改了A功能,可是可能會影響到B和C功能,人工測試的話你可能只會驗證A是否正常,容易忽略B和C,使用單元測試的話只要全部功能都有覆蓋,那麼幾乎不可能遺漏由於修改致使的潛在問題,並且還能反饋出來由於修改致使的兼容性問題。
- 以前說過,一旦框架開放出去,框架的使用者可能會以各類方式在各類環境來使用你的框架,環境不一樣會形成不少怪異的邊界輸入或非法輸入,須要使用單元測試對代碼進行嚴格的邊界測試,以確保框架能夠在嚴酷的環境下生存。
- 單元測試還能幫助咱們改善設計,在寫單元測試的時候若是發現目標代碼很是難以進行模擬難以構建有效的單元測試,那麼說明目標代碼可能有強依賴或職責過於複雜,一個被單元測試高度覆蓋的框架每每是設計精良的,符合高內聚低耦合的框架。
若是框架的時間需求不是特別緊的話,單元測試的引入能夠是走通主線流程的階段就引入,越早引入框架的成熟度可能就會越高,之後重構返工的機會會越小,框架的可靠性也確定會大幅提升。以前我有寫過一個類庫項目,並無寫單元測試,在項目中使用了這個類庫一段時間也沒有出現任何問題,後來花了一點時間爲類庫寫了單元測試,出乎我意料以外的是,個人類庫提供的全部API中有超過一半是沒法經過單元測試的(原覺得這是一個成熟的類庫,其實包含了數十個BUG),甚至其中有一個API是在個人項目中使用的。你可能會問,爲何在使用這個API的時候沒有發生問題而在單元測試的時候發生問題了呢?緣由以前提到過,我是框架的設計者,我在使用類庫提供的API的時候是知道使用的最佳實踐的,所以我在使用的時候爲類庫進行了一個特別的設置,這個問題若是不是經過單元測試暴露的話,那麼其它人在使用這個類庫的時候基本都會遇到一個潛在的BUG。
示範項目
寫一個示例項目不只僅是爲了給別人參考,並且還可以幫助本身去完善框架,對於示例項目,最好兼顧下面幾點:
- 是一個具備必定意義的網站或系統,而不是純粹爲了演示特性而演示。這是由於,不少時候只有那些真正的業務邏輯纔會暴露出問題,演示特性的時候咱們老是有一些定勢思惟會規避不少問題。或者能夠提供兩個項目,一個純粹演示特性,一個是示例項目。
- 覆蓋儘量多的特性或使用難點,在項目的代碼中提供一些註釋,不少開發人員不喜歡閱讀文檔,反而喜歡看一下示例項目直接上手(模仿示例項目,或直接拿示例項目中的代碼來修改)。
- 項目中的代碼,特別是涉及到框架使用的代碼必定要規範,緣由上面也說了,做爲框架的設計者你不會但願你們複製的代碼粘帖的代碼一團糟吧。
- 若是你的項目針對的不只僅是Web項目,那麼示例項目最好提供Web和桌面兩個版本,一來你本身容易發現由於環境不一樣帶來的使用差別,二來能夠給予不一樣類型項目不一樣的最佳實踐。
完善日誌和異常
一個好的框架不但須要設計精良,日誌和異常的處理是否到位也是很是重要的標準,這裏有一些反例:
- 日誌的各類級別的使用沒有統一的標準,甚至是永遠只使用某個級別的日誌。
- 幾乎沒有任何的日誌,框架的運行徹底是一個黑盒。
- 記錄的日誌多且沒有實際含義,只是調試的時候用來觀察變量的內容。
- 異常類型只使用Exception,不使用更具體化的類型,沒有自定義類型。
- 異常的消息文本只寫"錯誤"字樣,不寫清楚具體的問題所在。
- 永遠只是拋出異常,讓異常上升到最外層,交給框架的使用者去處理。
- 用異常來控制代碼流程,或本應該在方法未達到預期效果的時候使用異常卻使用返回值。
其實我的以爲,一個框架的主邏輯代碼並不必定是最難的,最難的是對一些細節的處理,讓框架保持一套規範的統一的日誌和異常的使用反而對框架開發者來講是一個難點,下面是針對記錄日誌的一些建議:
-
首先要對框架使用的日誌級別有一個規範,好比定義:
- DEBUG:用於觀察程序的運行流程,僅在調試的時候開啓
- INFO:用於告知程序運行狀態或階段的變化,能夠在測試環境開啓
- WARNING:用於告知程序能夠本身恢復的錯誤或異常,或不影響主線流程執行的錯誤或問題,能夠在正式環境開啓
- ERROR:用於告知程序沒法恢復,主線流程中斷,須要開發或運維人員知曉干預的錯誤或異常,須要在正式環境開啓
- 按照上面的級別規範,在須要記錄日誌的地方記錄日誌,除了DEBUG級別的日誌其它日誌不能記錄過多,若是框架老是在運行的時候輸出幾十個WARNNING也容易讓使用者忽略真正的問題。
- 日誌記錄的消息須要是明確的,最好包含一些上下文信息,好比"沒法在xxx下找到配置文件xxx.config,框架將採用默認的配置",而不是"加載配置失敗!"
下面是一些針對使用異常的建議:
- 框架因爲配置錯誤或使用錯誤或運行錯誤,不能完成API名字所表示的功能,考慮拋出轉化後的異常,讓調用者知道發什麼了什麼狀況,同時框架能夠創建本身的錯誤處理機制
- 對於能夠預料的錯誤,而且錯誤類型能夠枚舉,考慮以返回值的形式告知調用者能夠根據不一樣的結果來處理後續的邏輯
- 對於框架內部功能實現上遇到的調用者無能力解決的錯誤,若是錯誤能夠重試或不影響返回,能夠記錄警告或錯誤日誌
- 能夠爲每個模塊都陪伴自定義的異常類型,包含相關的上下文信息(好比ViewException能夠包含ViewContext),這樣出現異常能夠很方便知曉是哪一個模塊出現問題而且能夠獲得出現異常時的環境信息
- 若是異常跨了實現層次(好比從框架到應用),那麼最好進行一下包裝轉換(好比把文件讀取失敗的提示改成加載配置文件失敗的提示),不然上層人員是不知道怎麼處理這些內部問題的,內部問題須要由框架本身來處理
- 異常的日誌中能夠記錄和當前操做密切相關的參數信息,好比搜索的路徑,視圖名等等,有關方法的信息不用過多記錄,異常通常都帶有調用棧信息
- 若是可能的話,出現異常的時候能夠分析一下爲何會出現這樣的問題,在異常信息中給一些解決問題的建議或幫助連接方便使用者排查問題
-
異常處理從壞到好的層次是,出現了嚴重問題的時候:
- 使用者什麼都不知道,程序的完整性和邏輯獲得破壞
- 使用者既不知道出現了什麼問題也不知道怎麼去解決
- 使用者能明確知道出現了什麼問題,但沒法去解決
- 使用者不但知道發生了什麼,還能經過異常消息的引導快速解決問題
完善配置
配置的部分能夠留到框架寫的差很少了再去寫,由於這個時候已經能夠想清楚哪些配置是:
- 須要公開出去給使用者配置的,而且配置會根據環境不一樣而不一樣
- 須要公開出去給使用者來配置的,配置和部署環境無關
- 僅僅須要在框架內供框架開發人員來配置的
- 無需是一個配置,只要在代碼中集中存儲這個設定便可
通常來講配置有幾種方式:
- 經過配置文件來配置,好比XML文件、JSON文件或property文件
- 經過註解或特性(Annotation/Attribute)方式(對類、方法、參數)進行配置
- 經過代碼方式進行配置(好比單獨的配置類,或實現配置類或調用框架的配置API)
不少框架提供了多種配置方式,好比Spring MVC同時支持上面三種方式的配置,我的以爲對配置,咱們仍是應該區別對待,而不是無腦把全部的配置項都同時以上面三種方式提供配置,咱們要考慮高內聚和低耦合原則,對於Web框架來講,高內聚須要考慮的比低耦合更多,個人建議是對不一樣的配置項提供不一樣的配置方式:
-
若是配置項目是須要讓使用者來配置的,特別是和環境相關的,那麼最好使用配置方式來配置,好比開放的端口、內存、線程數配置,不過要注意:
- 全部配置項目須要有默認值,若是找不到配置使用默認值,若是配置不合理使用默認值(你不會但願使用你框架的人把框架內部的線程池的min設置爲999999,或定時器的間隔設置爲0毫秒吧?)
- 框架啓動的時候檢測全部配置,若是不合理給予提示,大多人只會在啓動的時候看一下日誌,使用的時候根本就無論
- 不知道你們對於配置文件的格式傾向於XML呢仍是JSON呢仍是鍵值對呢?
-
對於全部僅在開發時進行的配置,都儘可能不要去使用配置文件,而且讓配置儘可能和它所配置的對象靠在一塊兒:
- 若是是對框架總體性進行的設置擴展類型的配置,那就能夠提供代碼方式進行配置,好比咱們要實現的MVC框架的各類IRoute、IViewEngine等,最好能夠提供IConfig接口讓開發人員能夠去實現接口,這樣他們能夠知道有哪些東西能夠配置,代碼就是文檔
- 若是是那種對模型、Action進行的配置,好比模型的驗證規則、Filter等一概採用註解的方式進行配置
- 有的人說使用配置文件進行配置很是靈活,使用代碼方式和註解方式來配置不靈活並且可能有侵入性。我以爲仍是要權衡對待,個人建議是不要把太多框架內在的東西放在配置文件中,增長使用者的難度(並且不少時候,大多數人只是複製配置爲了完成配置而配置,並非爲了真正的靈活性而去使用配置文件來配置你的框架,看看網上這麼所SSH配置文件的抄來抄去就知道了)。
- 最後,我建議不少太內部的東西對於輕量級的應用型框架能夠不去提供任何配置選項,只須要在某個常量文件中定義便可,讓真正有需求進行二次開發的開發人員去修改,對於一個框架若是一會兒暴露上百個"高級"配置項給使用者,他們會暈眩的。
提供狀態服務
所謂狀態服務就是反映框架內部運做狀態的服務,不少開源服務或系統(Nginx、Mongodb等)都提供了相似的模塊和功能,做爲框架的話我以爲也有必要提供一些內部信息(主要是配置、數據統計以及內部資源狀態)出來,這樣使用你框架的人能夠在開發的時候或線上運做的時候瞭解框架的運做狀態,咱們舉兩個例子,對於一個咱們以前提到的Web MVC框架來講,能夠提供這些信息:
- 路由配置
- 視圖引擎配置
- 過濾器配置
對於一個Socket框架來講,有一些不一樣,Socket框架是有狀態的,其狀態服務提供的信息除了當前生效的配置信息以外,更多的是反映當前框架內部一些資源的狀態以及統計數據:
- 各類配置(池配置、隊列配置、集羣配置)
- Socket相關的統計數據(總打開、總關閉、每秒收發數據、總收發數據、當前打開等等)
- 各類池的當前狀態
- 各類隊列的當前狀態
狀態服務能夠如下面幾種形式來提供:
- 代碼方式,好比若是開發人員實現了IXXXStateAware接口的話,就能夠爲它的實現類來推送一些信息,也能夠直接在框架中設立一個StateCenter來公開框架全部的狀態信息
- 自動日誌方式,好比若是在配置中開啓了stateLoggingInterval=60s的選項,咱們的框架就會自動一分鐘一次輸出日誌,顯示框架內部的狀態
- 接口方式,好比開放一個Restful的接口或額外監聽一個端口來提供狀態服務,方便使用者能夠拿原始的數據和其它監控平臺進行整合
-
內部外部工具方式
- 好比咱們能夠直接爲框架提供一個專門的頁面(/_route)來呈現路由的配置(甚至咱們能夠在這個頁面上讓開發人員能夠直接輸入地址來測試路由的匹配狀況,狀態服務不必定只能看),這樣在開發和測試的時候能夠更方便調試
- 咱們也能夠爲框架提供一個專有工具來查看框架的狀態信息(固然,這個工具其實可能就是鏈接框架的某個網絡服務來獲取數據),這樣即便框架在多個機器中使用,咱們可能也只有一個監控工具便可
若是沒有狀態服務,那麼在運行的時候框架就是一個黑盒,反之若是狀態服務足夠詳細的話,能夠方便咱們排查一些功能或性能問題。不過要注意的一點是,狀體服務可能會下降框架的性能,咱們可能須要對狀態服務也進行一次壓測,排除狀態服務中損耗性能的地方(有些數據的收集會意想不到得損耗性能)。
檢查線程安全
框架對多線程環境支持的是否好,是框架質量的一個重要的評估標準,每每能夠看到甚至有一些成熟的框架也會有多線程問題。這裏涉及幾個方面:
1,你沒法預料框架的使用者會怎麼樣去實例化和保存你的API的入口類,若是你的入口類被用成爲了一個單例,在併發調用的狀況下會不會有單線程問題?
這是一個老話題,以前已經說過不少次,你在設計框架的時候內心若是把一個類定位成了單例的類但卻沒有提供單例模式,你是沒法要求使用者來幫你實現單例的。這其中涉及的不只僅是多線程問題,可能還有性能問題。好比見過某分佈式緩存的客戶端的CacheClient在文檔中要求使用者針對一個緩存集羣保持一個CacheClient的單例(由於其中有了鏈接池),可是用的人仍是每一次都實例化了一個CacheClient出來,幾小時後就會產生幾萬個半死的Socket致使網絡奔潰。又見過某類庫的入口工廠的代碼註釋中寫了要求使用的人把XXXFactory做爲單例來使用(由於其中緩存了大量數據),可是用的人就沒有注意到這個註釋,每一次都實例化了一個XXXFactory,形成GC的崩潰。因此我以爲做爲框架的設計者開發人員,最好仍是把框架的最佳實踐直接作到API中,使得使用者不可能出錯(以前說過一句話,再重複一次,好的框架不會讓使用的人犯錯)。你可能會說對於CacheClient的例子,不可能作成單例的,由於個人程序可能須要用到多個緩存的集羣,換個思路,咱們徹底能夠在封裝一層,經過一個CacheClientCreator之類的類來管理多個單例的CacheClient。即便在某些極端的狀況下,你不能只提供一條路給使用者去走,也須要在框架內作一些檢測機制,及時提醒使用者 "咱們發現您這樣使用了框架,這可能會產生問題,你本意是否打算那樣作呢?"
2,若是你的入口類原本就是單例的,那麼你是類中是否持有共享資源,你的API在併發的狀況下被調用是否能夠確保這些資源的線程安全?在解決多線程問題的時候每每有幾個難點:
- 百密難有一疏,你很難想到這段代碼會有人這樣去併發調用。好比某init()方法,某config()方法,你老是假設使用者會調用而且僅調用一次,但事實不必定這樣,有的時候調用者本身也不清楚個人容器會調用我這段代碼多少次。
- 好吧,解決多線程問題各類煩躁,那就對各類涉及到共享資源的方法所有加鎖。對方法進行粗獷(粒度)的鎖可能會致使性能急劇降低甚至是死鎖問題。
- 自覺得使用了優雅的無鎖代碼或併發容器但卻達不到目的。咱們每每在大量使用了併發集合心中暗自竊喜解決了多線程問題的同時又達到了極佳的性能,但你覺得這樣是解決了線程安全問題但其實根本就沒有,咱們不能假設A和B都方法是線程安全的,但對A和B方法調用的整個代碼段是線程安全的。
對於多線程問題,我沒有好的解決辦法,不過下面的幾條我以爲能夠嘗試:
- 須要很是仔細的過一遍代碼,把涉及到共享資源的地方,以及相關的方法和類列出來,不要去假設什麼,只要API暴露出去了則假設它可能被併發調用。共享資源不必定是靜態資源,哪怕資源是非靜態的,在併發環境下對相同對象的資源進行操做也可能產生問題。
- 通常而言對於公開的API,做爲框架的設計者咱們須要確保全部的靜態方法(或但單例類的實例方法)是線程安全的,對於實例方法咱們能夠不這麼作(由於性能緣由),可是須要在註釋中明確提示使用者方法的非線程安全,若是須要併發調用請自行處理線程安全問題。
- 能夠看看是否有可能讓這些資源(字段)變爲方法內的局部變量,有的時候咱們並非真正的須要類持有一個字段,只是由於多個方法要使用相同的東西,隨手一寫罷了。
- 對於使用頻率低的一些方法相關的一些資源沒有必要使用併發容器,直接採用粗狂的方式進行資源加鎖甚至是方法級別加鎖,先確保沒有線程安全,若是之後作壓測出現性能問題再來解決。
- 對於使用頻率高的一些方法相關的一些資源可使用併發容器,但須要仔細思考一下代碼是否會存在線程安全問題,必要的話爲代碼設計一些多線程環境的單元測試去驗證。
性能測試和優化
以前也提到過,你不會預測到你的項目會在怎麼樣的訪問量下使用,咱們不但願框架和同類的框架相比有明顯的性能差距(若是你作的是一個ORM框架或RPC框架,這個工做就是必不可少的),因此在框架基本完成後咱們須要作Benchmark:
- 肯定幾個測試用例,儘可能覆蓋主流程和一些重要擴展
- 找幾個主流的同類型框架,實現相同的測試用例,實現到時候要單純一點,儘可能不要再依賴其它外部框架
- 爲這些框架和本身的框架,使用壓力測試工具在相同的環境和平臺來跑這些測試用例,使用圖表繪製在不一樣的壓力下的執行時間(以及內存和CPU等主要資源的消耗狀況)
-
若是出現明顯的差距則用性能分析工具進行排查和優化,好比:
- 優化框架內的線程安全的實現方式
- 爲框架內的代碼作一些緩存(緩存反射獲得的元數據等等)
- 減小調用層次
- 這些調整可能會打破原來的主線流程或讓代碼變得難以理解,須要留下相關注釋
- 不斷重壓力測試和優化的過程,每次嘗試優化5%~20%的性能,雖然越到後來可能會越難,若是發現實在沒法優化的話(性能分析工具顯示性能的分佈已經很均勻了),能夠看一下其它框架對於這部分工做實現的代碼邏輯
封裝和擴展
我的以爲一個框架若是隻是能用那是第一個層次,能很方便的進行擴展或二次開發那是另一個層次,若是咱們龍骨階段的工做作的足夠好,框架是一個立體飽滿的框架,那麼這部分的工做量就會小不少,不然咱們須要對框架進行很多的重構以即可以達到這個層次。
-
咱們須要縱覽一下框架的全部類型,看看有哪些類型咱們是打算提供開發人員進行加強、擴展或替換的,對這些類型進行響應的結構調整。
- 好比但願被加強,則須要從繼承的角度來考慮
- 好比但願被擴展,則須要從Provider的角度來考慮
- 好比但願被替換,則須要在配置中提供組件的替換
-
咱們須要再爲這些類型進行精細化的調整:
- 檢查是否該封閉的封閉了,該開放的開放了
- 加強擴展或替換是否會帶來反作用
- 對於新來的外來類型,接收和使用的時候作足夠的檢查
- 相關日誌的完善
重構仍是重構
光是重構這個事情其實就能夠說一本書了,其實我有一點代碼的潔癖,這裏列一些我本身寫代碼的時候注重的地方:
- 格式:每次提交代碼的時候使用IDE來格式化你的代碼和引用(固然,實現可能須要配置IDE爲你喜歡的代碼風格)
- 命名:保持整個類和接口命名統一,各類er,Provider、Creator、Initializer、Invoker、Selector表明的是一件事情,不要使用漢語拼音命名,若是英文不夠好的話多查一下字典,有的時候我甚至會由於一個命名去閱讀一些源代碼看看老外是怎麼命名這個對象或這個方法的
- 訪問控制修飾符:這是一個很是難作好的細節,由於有太多的地方有訪問控制修飾符,到底是給予什麼級別的修飾符每每又取決於框架的擴展。能夠在一開始的時候給儘可能小的權限,在必要的時候慢慢提高,好比對於方法除了必定要給public的地方(好比公共API或實現接口),儘可能都給private,在有繼承層次關係的時候去給到protected,對於類能夠都給默認包/程序集權限,產生編譯錯誤的時候再去給到public
- 屬性/getter、setter:對於非POJO類字段的公開也要仔細想一下, 是否有必要有setter,由於一旦外部能夠來設置類的某個內部字段,那麼不只僅可能改變了類的內部狀態,你還要考慮的是怎麼處理這種改變,是否是有線程安全問題等等,甚至要考慮是否有必要開放getter,是否應該把類內部的信息公開給外部
- 方法:思考每個方法在當前的類中存在是否合理,這是否屬於當前類應該作的事情,方法是否作了太多事情太少事情
- 參數:須要思考,對於調用每個方法的參數,應該是傳給方法,仍是讓方法本身去獲取;應該傳多個參數,仍是封裝一個上下文給到方法
- 常量:儘可能用枚舉或靜態字符串來代替框架使用到的一些常量或幻數,須要爲常量進行一個分類不能一股腦堆在一個常量類Consts中
除了上面說的一些問題,我以爲對於重構,最重要的一句話就是:不要讓同一段代碼出現兩遍,主要圍繞這個原則進行重構每每就會解決不少設計問題,要實現這個目標可能須要:
- 幹差很少活的類使用繼承來避免代碼重複(提煉超類),使用模版方法來把差別留給子類實現
- 構造方法能夠層次化調用,主構造方法只要一個就能夠了,不要在構造方法中實現太多邏輯
- 若是方法的代碼有重複能夠考慮對方法提取出更小的公共方法來調用(提煉方法),也能夠考慮使用Lambda表達式進行更小粒度重複代碼的提取(提煉邏輯)
- 可使用IDE或一些代碼分析工具來分析重複代碼,若是你能想盡一切辦法來避免這些重複的話,代碼質量能夠提升一個層次
其實也不必定是在重構的時候再去處理上面全部的問題,若是在寫代碼的時候都帶着這些意識來寫的話那麼重構的負擔就會小一點(不過寫代碼思想的負擔比較大,須要同時考慮封裝問題、優雅問題、日誌異常問題、多線程問題等等,因此寫一套能用的代碼和寫一套好的代碼其實不是一回事情)。
項目文檔
若是要別人來使用你的框架,除了示例項目來講提供和維護一份項目文檔是頗有必要的,我建議文檔分爲這幾個部分:
-
特性 Features:
- 至關於項目的一個宣傳手冊,讓別人能被你項目的亮點所吸引
- 每個特性能夠是一句話來介紹
-
新手入門 Get started:
- 介紹框架的基本定位和做用
- 從下載開始,經過一步一步的方式讓用戶瞭解怎麼把框架用起來
- 整個文檔的閱讀時間在10分鐘之內
-
新手教程 Tutorials:
- 提供5~10篇文章站在使用者的角度來介紹項目的主要功能點
- 仍是經過一步一步的方式,教你們使用框架完成一個小項目(好比CRUD)
- 介紹框架使用的最佳實踐
- 整個文檔的閱讀時間在8小時內
-
手冊 Manual:
- 介紹項目的定位和理念
- 詳細介紹項目的每個功能點,能夠站在框架設計者的角度多介紹一些理念
- 詳細介紹項目的每個配置,以及默認配置和典型配置
- 詳細介紹項目的每個擴展點和替換點
文檔最好不是帶格式的,方便之後適配各類文檔生成器和開源網站
開源
開源的好處是有不少人能夠看到你的代碼幫助你改進,你的框架也可能會在更多的複雜環境下使用,框架的發展會較快框架的代碼質量也會有很大的提高。
要把框架進行開源,除了上面的各類工做以外可能還有一些額外的工做須要作:
- 選擇一個合適的License,而且檢測本身選擇的License與使用到的類庫的License是否兼容,在代碼頭的地方標記上License。
- 要確保每個人均可以在本身的環境中能夠構建你的代碼,儘可能使用Maven等你們熟悉的構建工具來管理依賴和構建。
- 選擇諸如Github等平臺來管理源代碼,並以良好的格式上傳你的文檔,有條件的話對示例子網站進行部署。
- 若是你但願你的代碼讓更多的人一塊兒來參與開發,那麼須要制定和公開一些規範,好比風格、命名、提交流程、測試規範、質量要求等等。
- 開源後時刻對項目進行關注,對各類反饋和整合請求進行及時的反饋,畢竟開源是讓別人來幫你一塊兒改進代碼,不是單純讓別人來學習你的代碼也不是讓別人來幫你寫代碼。
看到這裏你可能相信我一開始的話了吧,框架可使用到完善能夠商用差距仍是很大的,並且還要確保在迭代的過程當中框架不能偏離開始的初衷不能有很大的性能問題出現,任重道遠。
http://www.cnblogs.com/lovecindywang/p/4447739.html