轉眼間成爲一名PHPer已經快整整兩年了,在這期間也對如何寫出可讀性高,便於擴展的代碼有了一些本身的想法。數據庫
場景一:遍歷一個數組獲取新的數據結構編程
也許你會這樣寫:設計模式
// 申明一個新的數組,組裝成你想要的數據
$tmp = [];
foreach ($arr as $k => $v) {
// 取出你想要的數據
$tmp[$k]['youwant'] = $v['youwant'];
...
// 一系列判斷獲得你想要的數據
if (...) {
$tmp[$k]['youwantbyjudge'] = 'TIGERB';
}
...
}
// 最後得要你想要的數組$tmp
-------------------------------------------------------
// 也許你覺着上面的寫法不是很好,那咱們下面換種寫法
foreach ($arr as $k => $v) {
// 一系列判斷獲得你想要的數據
if (...) {
// 複寫值爲你想要的
$arr[$k]['youwantbyjudge'] = 'TIGERB'
}
...
// 幹掉你不想要的結構
unset($arr[$k]['youwantdel']);
}
// 最後咱們獲得咱們的目標數組$arr
複製代碼
接下來咱們使用引用值:數組
foreach ($arr as &$v) {
// 一系列判斷獲得你想要的數據
if (...) {
// 複寫值爲你想要的
$v['youwantbyjudge'] = 'TIGERB'
}
...
// 幹掉你不想要的結構
unset($v['youwantdel']);
}
unset($v);
// 最後咱們獲得咱們的目標數組$arr
複製代碼
使用引用是否是使咱們的代碼更加的簡潔,除此以外相對於第一種寫法,咱們節省了內存空間,尤爲是再操做一個大數組時效果是及其明顯的。bash
場景二:傳遞一個值到一個函數中獲取新的值antd
基本和數組遍歷一致,咱們只須要聲明這個函數的這個參數爲引用便可,以下:數據結構
function decorate(&$arr = []) {
# code...
}
$arr = [
....
];
// 調用函數
decorate($arr);
// 如上即獲得新的值$arr,好處仍是節省內存空間
複製代碼
假若有下面一段邏輯:函數
class UserModel
{
public function login($username = '', $password = '')
{
code...
if (...) {
// 用戶不存在
return -1;
}
code...
if (...) {
// 密碼錯誤
return -2;
}
code...
}
}
class UserController
{
public function login($username = '', $password = '')
{
$model = new UserModel();
$res = $model->login($username, $password);
if ($res === -1) {
return [
'code' => '404',
'message' => '用戶不存在'
];
}
if ($res === -2) {
return [
'code' => '400',
'message' => '密碼錯誤'
];
}
code...
}
}
複製代碼
咱們用try...catch...改寫後:ui
class UserModel
{
public function login($username = '', $password = '')
{
code...
if (...) {
// 用戶不存在
throw new Exception('用戶不存在', '404');
}
code...
if (...) {
// 密碼錯誤
throw new Exception('密碼錯誤', '400');
}
code...
}
}
class UserController
{
public function login($username = '', $password = '')
{
try {
$model = new UserModel();
$res = $model->login($username, $password);
// 若是須要的話,咱們能夠在這裏統一commit數據庫事務
// $db->commit();
} catch (Exception $e) {
// 若是須要的話,咱們能夠在這裏統一rollback數據庫事務
// $db->rollback();
return [
'code' => $e->getCode(),
'message' => $e->getMessage()
]
}
}
}
複製代碼
經過使用try...catch...使咱們的代碼邏輯更加清晰,try...裏只須要關注業務正常的狀況,異常的處理統一在catch中。因此,咱們在寫上游代碼時異常直接拋出便可。this
** 構建函數或方法內部的代碼塊 **
假如咱們有一段邏輯,在一個函數或者方法裏咱們須要格式化數據,可是這個格式化數據的代碼片斷出現了屢次,若是咱們直接寫可能會想下面這樣:
function doSomething(...) {
...
// 格式化代碼段
...
...
// 格式化代碼段[重複的代碼]
...
}
複製代碼
我相信大多數的人應該不會像上面這麼寫,可能都會像下面這樣:
function doSomething(...) {
...
format(...);
...
format(...);
...
}
// 再聲明一個格式花代碼的函數或方法
function format() {
// 格式化代碼段
...
}
複製代碼
上面這樣的寫法沒有任何的問題,最小單元化咱們的代碼片斷,可是若是這個format函數或者方法只是doSomething使用呢?我一般會像下面這麼寫,爲何?由於我認爲在這種上下文的環境中format和doSomething的一個子集。
function doSomething() {
...
$package = function (...) use (...) { // 一樣use後面的參數也能夠傳引用
// 格式化代碼段
...
};
...
package(...);
...
package(...);
...
}
複製代碼
** 實現類的【懶加載】和實現設計模式的【最少知道原則】 **
假若有下面這段代碼:
class One
{
private $instance;
// 類One內部依賴了類Two
// 不符合設計模式的最少知道原則
public function __construct()
{
$this->intance = new Two();
}
public function doSomething()
{
if (...) {
// 若是某種狀況調用類Two的實例方法
$this->instance->do(...);
}
...
}
}
...
$instance = new One();
$instance->doSomething();
...
複製代碼
上面的寫法有什麼問題?
因此咱們使用匿名函數解決上面的問題,下面咱們這麼改寫:
class One
{
private $closure;
public function __construct(Closure $closure)
{
$this->closure = $closure;
}
public function doSomething()
{
if (...) {
// 用的時候再實例化
// 實現懶加載
$instance = $this->closure();
$instance->do(...)
}
...
}
}
...
$instance = new One(function () {
// 類One外部依賴了類Two
return new Two();
});
$instance->doSomething();
...
複製代碼
若是你遇見下面這種類型的代碼,那必定是個黑洞。
function doSomething() {
if (...) {
if (...) {
...
} esle {
...
}
} else {
if (...) {
...
} esle {
...
}
}
}
複製代碼
** 提早return異常 **
細心的你可能會發現上面這種狀況,可能絕大多數else代碼裏都是在處理異常狀況,更有可能這個異常代碼特別簡單,一般我會這麼去作:
// 若是是在一個函數裏面我會先處理異常的狀況,而後提早return代碼,最後再執行正常的邏輯
function doSomething() {
if (...) {
// 異常狀況
return ...;
}
if (...) {
// 異常狀況
return ...;
}
// 正常邏輯
...
}
// 一樣,若是是在一個類裏面我會先處理異常的狀況,而後先拋出異常
class One
{
public function doSomething()
{
if (...) {
// 異常狀況
throw new Exception(...);
}
if (...) {
// 異常狀況
throw new Exception(...);
}
// 正常邏輯
...
}
}
複製代碼
** 關聯數組作map **
若是咱們在客戶端作決策,一般咱們會判斷不一樣的上下文在選擇不一樣策略,一般會像下面同樣使用if或者switch判斷:
class One
{
public function doSomething()
{
if (...) {
$instance = new A();
} elseif (...) {
$instance = new A();
} else {
$instance = new C();
}
$instance->doSomething(...);
...
}
}
複製代碼
上面的寫法一般會出現大量的if語句或者switch語句,一般我會使用一個map來映射不一樣的策略,像下面這樣:
class One
{
private $map = [
'a' => 'namespace\A', // 帶上命名空間,由於變量是動態的
'b' => 'namespace\B',
'c' => 'namespace\C'
];
public function doSomething()
{
...
$instance = new $this->map[$strategy];// $strategy是'a'或'b'或'c'
$instance->doSomething(...);
...
}
}
複製代碼
爲何要使用接口?極大的便於後期的擴展和代碼的可讀性,例如設計一個優惠系統,不一樣的商品只是在不一樣的優惠策略下具有不一樣的優惠行爲,咱們定義一個優惠行爲的接口,最後對這個接口編程便可,僞代碼以下
Interface Promotion
{
public function promote(...);
}
class OnePromotion implement Promotion
{
public function doSomething(...)
{
...
}
}
class TwoPromotion implement Promotion
{
public function doSomething(...)
{
...
}
}
複製代碼
最後我想說的是永遠拒絕在你的Controller裏直接操做DB,爲何?咱們的程序絕大多數的操做基本都是增刪改查,多是查詢的where條件和字段不一樣,因此有時候咱們能夠抽象的把對數據庫增刪改查的方法寫到model中,經過參數暴露咱們的where,fields條件。一般這樣能夠很大程度的提升效率和代碼複用。好比像下面這樣:
class DemoModel implement Model
{
public function getMultiDate($where = [], $fields = ['id'], $orderby = 'id asc')
{
$this->where($where)
->field($fields)
->orderby($orderby)
->get();
}
}
複製代碼
若是有寫的不對的地方,歡迎你們指正,THX~