來源:淘寶搜尋
編碼多年,各種程式碼日夜相伴,如何跟程式碼友好的相處,不光成為職業生涯的一種回憶,也是編寫者功力的直接顯露。
如何看待程式和程式碼呢?那就讓我們從程式定義來談起。如果從業務最終呈現來看,一個程式可以看成是一個真實業務需求的邏輯程式碼對映。如果從程式邏輯結構看,程式就是資料結構加演算法的結合。
這樣看,為滿足更多的業務需求,更好的滿足這些需求,就需要更多的程式程式碼,當程式程式碼堆積達到一定數量後,如何管理好,整理好已有的程式碼將會成為一個只管重要的問題。這個也是一個程式設計師程式設計3~5後,從中級向更高階別探索的一個瓶頸。
滿足需要可工作的程式碼是好的,可被多個需求不斷複用的程式碼,就是更好的了。隨著軟體設計的發展,程式碼的集合,功能邏輯不斷向下沉澱封裝的趨勢越來越明確。使用好一個工具很快,掌握好一種設計思想就要不斷的嘗試和改進了。有專門處理資料的程式碼,有專門處理呈現的程式碼,如何在業務流程中管理配置他們?這些邏輯如何更好的被封裝,被複用。
其實對於PHPer來說,這些思想在處理具體業務來說有些麻煩,這也是PHP的最大優勢非常的自由方便,自由簡單隨意的基本語法,方便的連記憶體資源都不用考慮,很快就可以hello一個,
但這也正是PHP一個先天的重大劣勢,沒有一個系統的成脈絡的設計體系。PHP出生時就是一個單一的滿足業務的語言,並沒有像JAVA一樣有很系統設計體系和原則。在JAVA有三個最基礎的設計原則:1,不支援全域性變數。2,不寫萬能類。3,程式碼必須是類封裝。
JAVA的第一個,第三個原則是在語法上就限制了,第二個原則是評判一個JAVA程式設計師是否入門的標準。PHP相對來說就沒什麼這樣的語法上的設計原則限制,可接觸了一些大公司,真沒有體系原則呀,哎。
但在我們設計思想裡可不能真的沒有原則呀!
PHP程式其實是怎麼方便怎麼來,直譯器很強大,可以遮蔽包容各種思路的程式程式碼,只要語法OK,不在乎程式碼設計。正因早期的PHP太隨意了,入門很容易,不用很好的對程式碼進行有效的管理和方便的複用。隨著PHP的發展,PHP已經告別在PHP3~PHP4時代動態標記語言,但因為向上相容原則,PHP還是一個語法寬鬆的語言,系統化的程式設計原則還沒有強制融入到語言核心中來。
這樣並不代表我們不需要使用成熟的設計思想來完善和編寫我們的程式程式碼。JAVA的程式設計原理和程式碼積累,是JAVA的精髓,隨著時間的積累越發明顯。將JAVA的程式設計思想,引入到PHP的程式設計過程中來。是一個完善PHP程式碼的很好的方法。
1. 程式碼分級封裝
2. 檔案靈活呼叫載入,資源隨用隨建立
3. 平整抗老化的目錄結構
解決這些能為程式編寫過程,帶來非常多的益處。
如何解決呢?
可以通過對於MVC設計思想進行的拆解封裝,來實現清晰,有效,一致性的程式設計思想。
一個程式從邏輯結構上可看做是模型(Model),檢視(View)和控制Controller)三個邏輯塊。
在JAVA中Model層實現系統中的業務邏輯,通常可以用JavaBean或EJB來實現。 View層用於與使用者的互動,通常用JSP來實現。Controller層是Model與View之間溝通的橋樑,通常是router servlet嚮應用端的擴充套件。
可 PHP 沒有這樣清晰的劃分,所以需要將設計思想揉入到程式程式碼中去。
1、程式程式碼類封裝
對於一個應用需求(ex: similar相似商品頁),將需求先分為3個檔案:similar.controller.php;similar.model.php.similar.view.php
通過名字我們也可以看出各檔案中的程式碼用途,剩下就是要給各個檔案中的類進行命名。
因為是這對一個應用的不同操作類,所以需要給各個類加不同的類目字尾,
例如similar.controller.php
1 2 3 4 5 6 7 8 9 10 11 |
//controller 控制程式類 完成此類中的方法是程式各頁面流程,每一個方法對應一個同步請求頁面(以配合頁面的呼叫規則) class similar{ //相似商品頁的展現 functio do_opensearch($_param){ //請求引數過濾 //請求商品資料 //根據呈現規則處理商品資料 //渲染商品資料獲取呈現html //展示呈現html } } |
對應的請求就是
http://s.etao.com/similar/opensearch/
similar.model.php
1 2 3 |
//model 資料操作程式類 完成對此應用的資料請求封裝,返回結果的結構解析處理 class similarMODEL{ } |
similar.view.php
1 2 3 4 5 6 |
//model 呈現操作程式類 完成對此應用的資料呈現封裝,返回給已經處理好的 class similarVIEW{ function view_getDetail($_goodDataArr){ return $html; } } |
2、類呼叫
我們很清晰的將各個邏輯將不同程式碼封裝在了不同檔案和類中了。如何靈活的呼叫呢?PHP是一個解釋語言,沒有像JAVA和C一個的編譯過程,無法享受編譯語言,在預編譯過程中的自動連線功能。
其實現在PHP直譯器也已經意識到這個問題,並在5.3.1以後提出了autoload方法來解決,當直譯器遇到一個非宣告類或者函式時會自動執行autoload去再載入一次,如果還沒有才報錯。
詳情參見 http://php.net/manual/en/language.oop5.autoload.php
但這樣還是一個被動解決方案。下面提供一個主動動態載入方案。加入資源系統調動類
在講解這個類的使用之前先看一下我們之前載入並使用一個類檔案的過程:
檔案:similar.controller.php
1 2 3 |
require_once PATH."similar.model.php"; $sModelObj = new similarMODEL; $goodsData = $sModelObj->getGoodsInfoByNid($nid); |
這是在過程中呼叫,如果在controller類內呼叫的話就需要寫成這樣,
1 2 3 4 5 6 7 8 9 10 11 12 |
require_once PATH."similar.model.php"; class similar{ public $_modelObj; function __construct(){ $this->_modelObj = new similarMODEL; } //相似商品頁的展現 functio do_opensearch($_param){ //請求商品資料 $goodsInfoArr = $this->_modelObj->getGoodsInfoByNid($nid); } }//end class |
如果controller類需要使用另一個MODEL,例如forest,ha3什麼的就要宣告多個類內元素變數。如果利用PHP 的變數傳導特性,建立一個資源排程類,來統一載入和排程需要的類並宣告,使用起來就會方便很多。下面我們來詳細的分解下這個資源排程類的設計,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public static function getObj($_appName,$_typeStr='class') { if($_typeStr=='class'){ $className = $_appName; }else{ $className = $_appName.strtoupper($_typeStr); } //資源物件已建立 直接返回使用 if( isset(self::$_modelObjArr[$className]) && is_object(self::$_modelObjArr[$className]) ){ return self::$_modelObjArr[$className; } //載入檔案資源類檔案 $file = dirname(__FILE__)."{$_appName}/{$_appName}.{$_typeStr}.php"; if( file_exists($file) ){ require_once $file; if( class_exists($className) ){ return self::_createObj($className); }else{ $errStr = "no class {$className} in file {$file}"; //類名錯誤 } } else { $errStr = "no class file {$file}"; //類檔案錯誤 } self::_showErr($errStr); } //建立資源物件 public static function _createObj($_className){ if( isset(self::$_modelObjArr[$_className]) && is_object(self::$_modelObjArr[$_className]) ){ return self::$_modelObjArr[$_className]; }else{ self::$_modelObjArr[$_className] = new $_className(); return self::$_modelObjArr[$_className]; } } //錯誤提示 public static function _showErr($_errTypeStr=''){ echo $_errTypeStr; exit; //errorlog($_errTypeStr); } }//end class |
改用Box資源排程類,載入類程式
檔案:similar.controller.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/** * 載入指定型別的類程式 **/ class Box { //宣告一個程式內資源物件池 public static $_modelObjArr; //獲取一個資源物件 class similar{ function __construct(){ } //相似商品頁的展現 functio do_opensearch($_param){ //請求商品資料 $goodsInfoArr = Box::getObj('similar','model')->getGoodsInfoByNid($nid); $html = Box::getObj('similar','view')->view_getDetail($goodsInfoArr); //其他已封裝好的資料邏輯 $hotHtml = $this->_getHotHtml(); } function do_search($_param){ $goodsInfoArr = Box::getObj('similar','model')->getGoodsInfoByNid($nid); $html = Box::getObj('similar','view')->view_getDetail($goodsInfoArr); } function _getHotHtml(){ $hotInfoArr = Box::getObj('similar','model')->getGoodsInfoByNid($nid); $html = Box::getObj('similar','view')->view_getDetail($goodsInfoArr); return $hotInfoArr; } }//end class |
這樣在一個響應程式內 “similar.model.php”; 檔案只會被載入一次,similarMODEL 也只會被建立一次,
隨使隨呼叫。不用提前宣告,不用考慮重複載入,重複建立。
Box解決了類與類間資源的呼叫和建立。
那如何讓一個controller類檔案中的function 執行呢?我們需要一個請求排程類 Bin。
之前我們的請求響應都市通過websearch的io model來完成。吧請求的path對映到實際檔案中。
例如:
xxxx.taobao.com/similar/opensearch/index.php 或者是 xxx.etao.com/similar/opensearch.php 這樣的檔案,websearch將host替換成實際檔案系統的path。
這樣外部程式可以各種掃描我們的目錄,一旦疏忽就會把一些常用方法暴露出來,ex:xxxx.taobao.com/tools/info.php 什麼的,有風險隱患。
當然加一些過濾條件是可以遮蔽這樣的問題,但會增減webserver的邏輯,一旦有改動就會改動webserver的conf。麻煩。索性我們就webserver踏踏實實的做好io和http打包解包,
讓app server來過濾和靈活配置請求呼叫。再webserver中將所有的訪問都對映(rewitre)到host/dispatch.php,由Bin類來實現排程和分配。實現應用邏輯單一入口呼叫結構。
dispatch.php完成一個平臺分配,效能監控,配置載入,應用啟動,分型別輸入(同步,非同步,PC,mobile),等平臺級功能。
Bin類就需要如下功能: 解析請求,將webserver傳遞的環境變數傳遞到響應的實際處理應用來完成分解,提出similar 應用名稱,opensearch 方法名稱,
還有GET,POST請求引數,組裝成請求引數陣列,
1 2 |
$this->_paramArr = explode( '/', trim(strtok( urldecode($_SERVER["REQUEST_URI"]),'?'),'/')); $this->_paramArr = array_merge($this->_paramArr,$_GET); |
這樣$this->_paramArr[0]就是appname應用名稱
這樣$this->_paramArr[1]就是function方法名稱
平臺流程就可以裝載appname.class.php 檔案並執行 appname->do_function($this->_paramArr);方法。
這樣也支援/similar/opensearch/sort/這樣的傳參,sort是$this->_paramArr[2]的一個引數
如果$this->_paramArr[0]等於ajax也就是對於的這是一個js發起的非同步請求,
要裝載的就是appname.ajax.php,並執行appnameAJAX->ajax_function();
將同步和非同步請求分檔案。減少載入大檔案對記憶體的佔用。
同時也可以將可訪問目錄跟應用目錄物理上分類,讓掃描程式無用。
例如:
/home/website/host/ 這個目錄只放index.php一個檔案,和css,js,images一些沒有加入cdn的呈現渲染檔案。將webserver的docmentroot 或者laction 設定在這個目錄
/home/website/app/ 這個目錄來存放編寫的類程式檔案,配置,由/host/index.php程式載入app目錄下的類程式檔案
一個應用好辦,一堆應用我們怎麼辦呢?下面我們來說說多應用的目錄結構。
3、目錄結構 (抗衰老很重要)
PHP的設計理念這是少又少,連個包的概念都沒有。只好我們自己用目錄來包裝包概念。從訪問結構,開發結構,部署結構上來分別介紹目錄結構的作用.
3a、訪問結構
將訪問目錄host,和應用程式目錄app可以看成是一個website,這個site下面的應用可以這樣的
可以這樣建立
/website/host
/website/app/similaer
/website/app/cmp
/website/app/srp
/website/app/…..
假如管理工具也可以再website下同另起一個目錄
/website/admin/firebox
/website/admin/seoAny
這樣,所有的請求都會到/website/host/index.php由index.php在根據請求規則,載入響應的邏輯程式。
跟/webiste/host並行的有一個system目錄,/website/system/,將box.class.php,bin.class.php,base.class.php等平臺檔案存放其中。
一個響應的執行流程就是
webserver query ->[ /website/host/index.php include /website/system/box,bin,base (應用架構)] 平臺-> [ /website/app/ (應用流程) | /website/admin/]頁面
3b、開發結構
在開發和維護過程中,會有一些同樣地php庫檔案,如curl通訊,xml解析,log,timer,template(appview),等自己開發的類程式,也有像smarty,big2gb等第三方類庫。
各個專案都通用,比價,主搜,我的一淘,那就可以統一建立一個PHPLIBS目錄與website平級
/website/host/
/website/app/
/website/system/
/PHPlibs/etao/ 自己的類庫
/PHPlibs/other/ 第三方類庫
每一個website一個svn
PHPlibs/獨立一個svn 專人維護,多快好省精深專
將template檔案從website/中分離出來建立一個
webtemplate/目錄 跟ued同學共享一個svn, 讓ued同學按前段template類的規則書寫模板檔案。按應用分目錄儲存。
如:
webtemplate/srp
webtemplate/cmp
webtemplate/opensearch
webtemplate/frame 統一呈現模板,頁頭尾
webtemplate/…..
website/app中的程式通過template類方法來呼叫以上模板檔案。
同時在開發和維護過程中也會遇到需要shell來執行的php script ,如定時生成公共頁頭尾,定時取mysql庫中的epid資料等。或者臨時做一個演算法的程式。
那我們就可以在
/website/app/的相關應用中加入app.sh.php程式檔案,
如
/website/app/similar/similar.sh.php 這個檔案的類程式similarSH是通過
/website/script/run.php 來執行的。 script/run.php是一個shell端的控制器響應CLI請求,host/index.php是一個web端的控制器響應CGI請求
還可以在script/run.php 外包裝一個debug.bat的檔案,可已經在dos,或者包裝一個debug.sh 程式再shell下直接互動除錯。
具體參考程式碼
還有一些程式處理檔案,如由vm檔案轉換成的php模板的公共頁頭尾檔案,會有一個跟website平行的webdata檔案,
/website/….
/webdata/pageframe等目錄中,在app中直接呼叫
3c、部署結構
通過上線指令碼分別checkout
/website/
/PHPlibs/
/webtemplate/ svn資源庫
然後打包rpm,推送上線。
單獨上線,也是使用上線指令碼配置檢查出相應檔案,推送到前段生產機。
4、程式碼規範
關於程式碼格式規範,看之前大家也都積極的討論了很久了。
程式碼格式規範,我們在IDE中配置一下,自動使用,並通過不斷的review和提醒中培養好的書寫易讀程式碼的習慣就好了。
程式碼設計規範,是需要根據架構指定,並堅持執行的。
下面談到程式碼設計都是基於剛剛談到的,資源呼叫,請求分配來制定的。
4a、關於目錄
//應用程式
/website/app/….
//對外訪問目錄
/website/host/index.php
/website/host/css
/website/host/js
/website/host/images
//平臺系統檔案
/website/host/images
//除錯,維護指令碼
/website/script/
//模板庫 對應/website/app/下的目錄
/webtemplate/….
//程式類庫
/PHPlibs/
//資料檔案目錄
/webdata
沒什麼好說的,大家體會。
4b、關於檔案
檔案的命名規範主要是指定內部呼叫,和請求呼叫
website/app/appname/appname.apptype.php
apptype: class,model,view,ajax,api,sh ,….
呼叫:
Box::getObj(appname,apptype)->function();
appname.class.php檔案為controller檔案,類中的方法做同步請求響應。
appname.ajax.php檔案也是controller檔案,類中的方法做非同步請求響應。
appname.sh.php檔案也是controller檔案,類中的方法做CLI響應。
appname.api.php檔案可以看成是controller檔案,類中的方法是對一個公共,或通用邏輯的封裝,如獲取圖片完整路徑。
4c、關於類和方法
類命名:
除class檔案中的直接以appname命名,其他的類檔案都是appname+APPTYPE命名,APPTYPE需要大寫。好做語法區分。
方法命名:
在方法宣告中加字首,說明方法的作用,如view類中的方法加html_getSimilarList(), model方法中加入db_getData()從資料庫取,file_getData()從檔案取,url_getData()從引擎或者遠端取
4d、關於變數和註釋
類內變數, 引數入口變數 加_字首, 跟過程中使用的區域性變數,加以區分,增加可讀性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class similar{ public $_obj; function do_getpage($_param){ } } 檔案註釋 = 類註釋 在檔案開頭加入,說明這個類的功能: /** * @package: * @access: MixrenSystemBox.inc.php * Summary: 系統 應用模板控制分配程式; 完成請求分析; 模組載入; 請求處理(執行); * @Created: Fri Dec 25 16:41:02 CST 2009 * @Author: Zhurong * @Generator: EditPlus2 & Dreamweaver & Zend & eclipse */ 方法註釋 /** * 初始化請求 * param引數說明 **/ private function _parseApp(){ $this->_queryStr = urldecode($_SERVER["REQUEST_URI"]); $this->_paramArr = explode( '/', trim(strtok($this->_queryStr,'?'),'/')); //分配請求模組 $appName = DEFAULT_APP_NAME; $this->_className = $appName; $this->_appFile = APP_PATH . "{$appName}/{$appName}.controller.php"; $this->_method = empty($this->_paramArr[0]) ? DEFAULT_APP_METHOD : $this->_paramArr[0]; $this->_method = "do_{$this->_method}"; } 類結尾註釋 class Bin{ }//end class |
為主要過程,演算法,加入註釋,方便維護和閱讀。
在上線前會通過上線指令碼對檔案進行 php -w 去註釋過程,所以註釋寫的長不佔用程式執行過程中的載入記憶體。
5、程式碼監測
專門的程式碼掃描指令碼。
掃描/website/app/下的目錄,檔案,方法,註釋量。
出報告,多少應用目錄; 多少同步響應類;多少非同步響應類;多少同步頁面;多少非同步介面;多少邏輯介面;多少model介面;多少模板;各個檔案的程式碼註釋率;等等。
基於以上設計思想建立了從資料夾-》分段類檔案-》功能模組方法 的樹形結構設計程式架構,最大化實現CAP原則 consistency(一致性),availability(可複用),Partition(有效劃分)
讓PHP程式碼更好的積累。
模板(純html檔案,呈現配置),程式資料配置檔案是被載入到程式中,而不是現在將程式載入到html中一段段解釋執行。