我需要拍磚 和 看見你們的意見,為團隊少挖坑
場景:建立訂單
實際流程:
終端呼叫(PC端、移動端APP、微信端、Web端)-->控制器 或 介面-->實際的業務處理-->控制器 或 介面-->終端做出相應處理(控制器可能是渲染對應頁面; 介面返回 JSON資料)
業務處理類動作:
- 檢查使用者是否登陸
- 驗證商品 ID、購買數量等引數
- 檢查該商品是否處於上架中
- 檢查該商品是否可以購買
- 各種檢查...
- 建立訂單
- 記錄 Log
- 返回訂單建立結果給呼叫者
- 建立失敗:...
- 建立成功:[return true|return Order info]
遊戲規則
前後端資料格式約定為 JSON格式如下:
{
code: "00000", // 狀態碼
msg: "操作成功!", // 提示資訊
data: {} // 資料
}
注:"00000":業務成功狀態碼;非"00000"都為業務失敗。
為了防止伺服器端狀態碼氾濫成災,code可以為"",這時 msg 裡面則是相應的錯誤資訊,只為給使用者提示。
導火線
專案開發完畢,測試人員去測試,提如下Bug:
如果使用者未登入,進個人中心,提示使用者未登入,然後會去登陸view;而在下單頁,提示使用者未登入,卻沒有去登陸view。
然後前端童鞋開始去修復該問題,查出如下問題:
- 個人中心伺服器介面返回的資料格式:
{
code: "00008",
msg: "使用者未登入,請登入",
data: [ ]
}
- 下單頁伺服器介面返回的資料格式:
{
code: "",
msg: "使用者未登入,請登入!",
data: [ ]
}
然後前端童鞋對伺服器端童鞋講,這裡你應該返回給我code: "00008"
,我這邊一看 code便知是使用者未登入,就可以做出相應的操作,這裡你只返回提示資訊,我這邊不好做更加細膩的操作。
然後,後端童鞋開始嘗試給該地方新增上 code。
開始著手修改程式碼:
首先找到介面方法裡面發現如下 demo:
php
$order = kernel::single('sysapi_ecoupon_order')->create($params, $msg); if (!$order) { return array('code' => '', 'data' => array(), 'msg' => $msg); } return array('code' => '00000', 'data' => $order);
改方法返回array(); 在外部統一入口、出口處再返回 JSON出去。
sysapi_ecoupon_order
php
public function createNew($params, & $msg) { // 獲取使用者資訊 $member_info = app::get('b2c')->model('members')->get_current_member(); if (empty($member_info)) { $msg = app::get('ecoupon')->_('使用者未登入,請登入!'); return false; } // 繼續下面的業務處理 }
介面呼叫的kernel::single('sysapi_ecoupon_order')->create($params, $msg);
這裡面做實際的業務處理,錯誤資訊是通過 $msg 向上傳遞出去,外部沒辦法通過 $msg 獲知對應的 code。然後給前端童鞋講這種情況沒辦法返回 code給你。
前端就只能通過判斷 msg的方式來修復該問題
然後寫了如下 demo:
javascript
if("使用者未登入,請登入!" == data.msg) { // 使用者未登入,去登入 // ... }
然後提交,測試,通過,上線,N天后
有人跑過來講:下單頁 與 個人中心的提示有點不同,貌似多了個 "!"。(舉例而已,更多的可能是提示不友好、錯別字等情況)
然後後端同學修改為 $msg = app::get('ecoupon')->_('使用者未登入,請登入');
提交,測試不通過,前端同學再修改為if("使用者未登入,請登入" == data.msg)
,提交,測試通過
// 如此反反覆覆
終究有一天:產品、測試,前端、後端混戰了一場。N人,卒.....
重新正視問題
最終前後端得出結論:要想對使用者實現更加友好的體驗,前後端資料必須有個標識具有唯一性
,不變性
。而現在用的 msg卻不具備,還是得用 code。並且這裡前後端極度耦合
msg。
後端童鞋回來繼續修改程式碼,開始著手給這裡新增上相應的 code。
開始思考該怎麼新增 code,現在的問題是 create( ) 方法可能是其他童鞋開發,內部返回的提示資訊,我這邊是呼叫者,不能確定方法內部到底會返回什麼提示資訊,無解。
忽然,有一天想到,我在呼叫該方法之前檢查下使用者有沒有登入就OK了,然後開始寫如下實現:
public function create($params)
{
$member = app::get('b2c')->model('members')->get_current_member();
// 登入驗證
if (empty($member)) {
return array('code' => '00008');
}
$msg = '';
$order = kernel::single('sysapi_ecoupon_order')->create($params, $msg);
if (!$order) {
return array('code' => '', 'data' => array(), 'msg' => $msg);
}
return array('code' => '00000', 'data' => $order);
}
呵呵,好機智的少年。
然後告訴前端,這裡可以返回 code了,前端愉快的刪掉原來那坨判斷 msg的程式碼,而在 ajax請求的地方統一判斷 code就能預知使用者未登入,做出相應的操作。
經測試,上線。一切又回到了美好時光。
隨著時光的流逝,業務的增加,後端童靴發現Order類裡面如下 demo:
php
public function create($params) { $member = app::get('b2c')->model('members')->get_current_member(); // 登入驗證 if (empty($member)) { return array('code' => '00008'); } // 實際業務處理.... } public function getOrderList($params) { $member = app::get('b2c')->model('members')->get_current_member(); // 登入驗證 if (empty($member)) { return array('code' => '00008'); } // 實際業務處理.... } public function getOrderDetail($params) { $member = app::get('b2c')->model('members')->get_current_member(); // 登入驗證 if (empty($member)) { return array('code' => '00008'); } // 實際業務處理.... } // ...
這都是什麼玩意............ 然後開始封裝,稍微好了點
又過了一段時間,有人過來說建立訂單還需要優化體驗,
點選建立訂單提示如下:
- 超過最大購買量——給出提示,繼續留在建立訂單頁
- 該商品已賣光或已下架——引導使用者去商品列表頁
這時,前端童鞋告訴後端童鞋,商品下架的時候,你也應該返回一個狀態碼。
後端童鞋開始打算新增 code,發現如下 demo
php
kernel::single('sysapi_ecoupon_order')->create($params, $msg);
這裡的提示資訊是 $msg 返回的,使用者登入外部可以提前檢測,這裡的商品能否購買要實現新增 code也需要提前檢測,將來要是需要新增類是功能豈不是...... 每需要一個精確的 code返回出去,這裡就需要新增檢測,這裡程式碼將會變得無法直視。
況且這裡本該在業務裡面檢測,一切不那麼友好起來了。
再次思考,程式碼寫的不爽了,一定是哪裡不對
問題所在
開始懷疑 public function create($params, & $msg) { }
這裡不應該是通過 & $msg
來作為 呼叫者與 被呼叫者之間的 錯誤資訊通訊約定,一切的問題都出在了這裡。錯誤訊息向上傳播的約定不合適
如果這裡約定的是 code作為錯誤向上傳播一切的問題即將不復存在。在呼叫業務方法之前的檢測程式碼就都可以去掉了,程式碼簡約,一切又美好起來。
接下來繼續思考,使用 code作為業務處理失敗訊息傳遞問題又來了
- 現在已有的業務程式碼都是如此定義
public function create($params, & $msg)
,怎樣更加友好的替換成 code - 如果使用 code,code只能伺服器端 與 前端約定的一個具有
唯一性
的標識(code 比 msg 對國際化的實現更加容易)但是並不能直接展示給使用者,那麼就需要定義每個 code 的代表的意義 與 對應的提示資訊。那麼問題來了,code 應該已怎樣規範來定義所代表的含義
再來看如下常用的兩種方法定義:
public function create($params, & $msg)
public function create($goodsId, $num, & $msg)
第一種方式,引數通過一個 $params陣列傳遞過來,方法內部在把錯誤提示放到 $msg中。
- 好處:$params是個陣列,裡面引數可以任意新增
- 缺點:該方法呼叫者在外部不能知道該方法需要什麼引數,必須來看該方法內部實現,做出對應的陣列 key的轉換(如: user_id 轉 userId)。方法呼叫者 與 方法實現 極度耦合。維護成本大、出Bug係數高
第二種方式,按基本型別分別傳遞單個引數
- 好處:方法呼叫者根據方法定義就能夠知道方法具體需要的引數,呼叫方法時不需要作 key轉換,只需要傳遞對應的引數即可
- 缺點:引數數目過多時,慘不忍睹
這裡有如下問題:
- 這裡如何已一種更加容易維護,擴充套件的方式來處理(
Java裡面方法引數已物件的方式傳遞可以借鑑
) - 這裡的
& $msg
真的合適嗎,如果是第二種方式定義的方法,以後擴充套件個 $phone 該如何處理?public function create($goodsId, $num, & $msg, $phone='')
這樣麼?怎麼看怎麼蛋疼 - 再來看不通過 & $msg傳遞錯誤資訊之後的程式碼
php
public function create($goodsId, $num) { if ( ? ) { // 返回狀態碼 return '0001'; } if ( ? ) { // 返回狀態碼 return '0002'; } // 建立訂單 // ... // 返回訂單資訊 return $order; }
看似實現了,但是方法呼叫者,怎麼呼叫怎麼蛋疼,一會返回狀態碼,一會返回訂單資訊,完全兩種型別。
綜合以上問題:
得出以下結論:
- 業務處理失敗訊息要以 code 的方式向上傳遞給呼叫者
- 業務處理失敗訊息以引數的方式傳遞不是很適合,並且不能以 return的方式返回
再次思考,最終從 Java裡面想到了一點思路(幸好是 Java出身。疑問:為何面試的時候 Java的工作經驗都不算在 PHP工作經驗裡呢,並沒有因此而加分)
解決方案:
- 自定義一個異常類,包括 codo屬性 和 msg 屬性
- 凡是遇到業務不能正常處理的時候就建立一個異常物件,設定對應的 code 或者 msg屬性(為了減少 code氾濫,這裡的 code 與 msg 可以2選一,如果前端需要做精準的處理,就設定 code,如果只是為了給使用者提示,就只返回 msg,則可以減少一個 code),然後丟擲異常
- 方法呼叫者在外部統一捕捉該異常,如 介面的統一入口出口的方法內部處理
因個人工作時間、專案經歷不多、歸根結底經驗不足。現在將該方案寫下來,還望有經驗的大神拍磚,以免給團隊挖坑,以上 $msg 就是 N久以前埋下的坑。
該文章釋出在自己站點地址:http://www.webdevs.cn/article/91.html