起因
之前說過自己被分配了一個重構代碼的任務:
這是一個有6、7年歷史,多人經手的老系統,PHP 語言,分布在50台機器上運行。代碼使用最簡單的結構,沒有使用任何完整框架,甚至有三四千行代碼的文件,修改時最新的 IDE 都無法幫得上忙,發生問題時排查困難。特別是程序員在寫代碼時,想引用之前的方法找不到,自己定義新的方法又無處放,只好在已經很雜亂的文件上堆砌。
由於此系統一直在運行中,甚至各機器日頂峰QPS有近1000,而且作為一個業務系統,之前甚至沒有CR,里面遍布一些比較“奇怪”的邏輯和寫法,看得人頭皮發麻,遷移這個系統無異於給行駛的汽車換輪子。
寫跟此項目遷移相關的文章,不僅是為了分享經驗,更是為了在遷移的步驟中能梳理一下思緒,盡量少踩坑,也順便沉淀一下知識。
文章經常被人爬,而且還不注明原地址,我在這里的更新和糾錯沒法同步,這里注明一下原文地址:http://www.cnblogs.com/zhenbianshu/p/7773930.html
問題
首先是定位問題,之前的代碼有以下問題:
- 文件:代碼組織邏輯不明顯,手動加載文件不方便,且文件代碼行數很多,有很多不必要的引用。
- 代碼:函數定義邏輯不清,根據功能找函數不方便;且代碼耦合度高,導致復用率低;多處定義全局變量,很可能會被某處引用並修改,引發異常。
- 配置:配置分散在各文件中,引用不易查,改動時無法保證完全改動。
- 測試:無測試項,改動后風險不可控。
針對這些問題,考慮將代碼遷移到 Yaf 框架下,將其重構:
- 使用 Yaf 框架管理代碼組織,使用命名空間實現易加載、按需加載。
- 使用命名空間和類從邏輯上聚合方法,避免全局變量風險;代碼分層,分離數據和邏輯,提高數據代碼和部分邏輯代碼的復用率;
- 配置數據統一管理,避免多處依賴,降低配置修改風險;
- 添加 phpunit 單元測試,降低代碼修改風險;
談談框架
框架
我們在多人合作開發大型項目時,必然要考慮到如何使代碼復用率最高,如何讓一個開發者可以在龐大的項目里迅速找到自己想要的方法。時間久了,大家會總結出一個套路解決上述問題,每次面對開發任務時都按照同樣的方式開發。一些有經驗的高手開發者會抽象出這個套路,整理並實現為框架。
所以框架是為解決一個開放性問題而設計的具有一定約束性的支撐結構。從其定義的幾個方面來分析:
- 解決問題:框架要解決的問題是
開發規范和效率問題,使用同一種規則,能大大降低開發者決定很多策略時的心智負擔。 - 約束性:無規矩不成方圓,既然是規范,那么一定有其約束性。框架一般會對文件、方法、命名、類等進行約束。
- 支撐結構:框架只是一個支撐結構,適用性廣,它像一個貨架,開發者把代碼貨物擺到對應的地方即可。
為了深入了解框架的思想,我之前也寫過一個自己的PHP框架:GitHub-zhenbianshu-Sqire_Framwork,還有配套的博客三篇:搭建自己的PHP框架心得 。
框架只是實現了 MVC 的設計模式和 簡單的路由,有對此感興趣的同學可以 fork 下看一下。
Yaf
Yaf 學 PHP 應該都有所了解,這里不過多介紹。 它作為用 C 編寫的PHP擴展存在,效率自然不用多說,選用 Yaf 更多是因為它作為框架的“自由”。
Yaf 最大限度地給開發者自由,開發者可以定義代碼結構,在路由各步驟間定義個性化需求。而Yaf 只在最適合的時候提供一些幫助,恰好足夠滿足開發需求,又不會添加多余的規則和限制。
就如同我們在使用導航軟件時,傳統框架一般會在地圖標出一條路線,這條路線可能會為了你並不需要飯店或賓館而繞遠路,而開發者必須沿着這條路走;Yaf則只會指明方向,走直線或彎路全憑自己實現。
當然我們也要為自由付出一些“代價”,缺少了框架的指導,項目分層和結構這些糾結的事就要自己來確定了。
結構
代碼結構是我來設計的,參考了幾個已有項目的結構,也盡量兼容當前項目的寫法,讓同事盡量容易接受。不敢說是最好的,至少目前來看是最適合的了。
整體結構
作為一線開發者,為了整理出最適合開發者開發的代碼結構我做了很多嘗試。最后結合 MVC 和三層架構(三層架構:UI 表示層、BLL 業務邏輯層、DAL 數據訪問層)整理出了目前的四層代碼結構:

考慮到MVC中的M層會因為業務擴展,變得邏輯復雜,最后臃腫得不好維護;而三層架構中表示層太單薄,View不易控制。最后修改為 BLL/DAL/V/C;
由上至下為:
- V: 接口數據的輸出、日志、文件、view頁面;
- C: controllers 控制器、后台腳本;
- BLL: 業務邏輯 Service;
- DAL: 數據訪問層,包括內部數據的訪問:Db, 和外部接口數據的訪問:Api。
支持層
在四層代碼結構之外,預留了兩塊結構作為全局支持:
Tools:由於禁止跨層調用的限制,一些函數的調用可能會很麻煩。於是提供全局可用的工具,開發者可以在各層按需加載這些工具。
config:提供集中的文件配置,通過划分清楚的文件夾可以幫助快速找到並修改配置。
除此之外,將一些很常用的方法和常量注冊為全局,省去了不必要的頻繁加載;同時也借用了Yaf 內置全局變量提供了公共數據透傳功能。
文件結構
下面是被我刪減到無法再刪減的文件結構,供人參考,也希望有人能提出改進意見。
├── app
│ ├── Bootstrap.php
│ ├── controllers
│ │ ├── Error.php
│ │ └── Mobile
│ │ └── Search.php
│ └── library
│ ├── Api
│ │ └── Util.php
│ ├── Db
│ │ └── User
│ │ └── Addr.php
│ ├── Service
│ │ └── Order
│ │ └── Count.php
│ └── Tools
│ └── Http.php
├── config
│ ├── application.ini
│ ├── error
│ │ └── api.php
│ └── rewrite.php
├── public
│ └── index.php
├── scripts
└── tests
├── controllers
│ └── UserSortTest.php
└── phpunit.xml
思考
除此之外,還有些比較糾結的問題:
Cache 和 Db
在當前代碼結構中,我把 Redis Cache 和 Mysql Db 整合放到了同一層 Db 層。
主要考慮到:
- 項目穩定,數據庫類型可控,不會再擴展了;
- 大部分數據直接使用 Redis 作 Db,即單一數據庫;
- 中間再添加一層數據調控層的話開發為了遵守不跨層的規范會寫很多無意義的代碼;
不知道會不會埋坑,不過即使后期復雜,將這一層再拆分也不會有較大風險。
靜態方法 or 類方法
項目中絕大部分邏輯都是增刪改查或數據處理,於是我在底層方法普遍使用靜態方法,由於靜態方法不需要實例化對象,無論是在開發還是運行都比使用類方法效率高。
當然類方法是完全支持的,適用一些復雜的長流程數據處理。
業務邏輯划分,實體 or 邏輯
業務邏輯層中,文件分類是最糾結的事。如用戶操作訂單的相關邏輯:
- 如果按照實體來拆分,用戶類和訂單類都無法完全精確地表示。
- 而如果按照邏輯來拆分,多種多樣的操作邏輯也同樣讓人抓狂。
目前主要使用了按照實體拆分,只考慮被操作對象,如查詢用戶訂單的邏輯放在用戶類,而用戶刪除訂單的邏輯放在訂單類。
另外也會提取一些專有且復雜的邏輯實現一些邏輯類,如對訂單有多種類型的排序,都放在訂單類中基本不會被復用到,則抽象出一個訂單排序類。
小結
現在項目剛確定了代碼結構,重構了基礎方法,業務代碼還在持續遷移中,下次會聊一聊怎么通過 框架路由 和 Nginx 配置進行灰度測試。
由於是業務部門,業務的開發是頭等大事,代碼遷移工作只能排在業務需求后面,而且開發人員也不足,整理之前的奇葩邏輯花費太多時間,所以遷移進行得很慢,此系列文章偶有更新,歡迎有相同經歷的同學關注或發現意見。
關於本文有什么問題可以在下面留言交流,如果您覺得本文對您有幫助,可以點擊下面的 推薦 支持一下我,博客一直在更新,歡迎 關注 。
