PHPer這樣寫代碼也許更優雅

前言
轉眼間成爲一名PHPer已經快整整兩年了,在這期間也對如何寫出可讀性高,便於擴展的代碼有了一些本身的想法。php

使用引用
場景一:遍歷一個數組獲取新的數據結構數據庫

也許你會這樣寫:編程

// 申明一個新的數組,組裝成你想要的數據
$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

使用引用是否是使咱們的代碼更加的簡潔,除此以外相對於第一種寫法,咱們節省了內存空間,尤爲是再操做一個大數組時效果是及其明顯的。數組

場景二:傳遞一個值到一個函數中獲取新的值antd

基本和數組遍歷一致,咱們只須要聲明這個函數的這個參數爲引用便可,以下:數據結構

function decorate(&$arr = []) {
    # code...
}

$arr = [
    ....
];
// 調用函數
decorate($arr);
// 如上即獲得新的值$arr,好處仍是節省內存空間

使用try...catch...
假若有下面一段邏輯:函數

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...改寫後:this

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中。因此,咱們在寫上游代碼時異常直接拋出便可。spa

 


使用匿名函數
** 構建函數或方法內部的代碼塊 **
假如咱們有一段邏輯,在一個函數或者方法裏咱們須要格式化數據,可是這個格式化數據的代碼片斷出現了屢次,若是咱們直接寫可能會想下面這樣:

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();
...

上面的寫法有什麼問題?

  • 不符合設計模式的最少知道原則,類One內部直接依賴了類Two
  • 類Two的實例不是全部的上下文都會用到,因此浪費了資源,有人說搞個單例,可是解決不了實例化了不用的尷尬

因此咱們使用匿名函數解決上面的問題,下面咱們這麼改寫:

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();
...

減小對if...else...的使用

若是你遇見下面這種類型的代碼,那必定是個黑洞。

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(...)
    {
        ...
    }
}

控制器拒絕直接的DB操做

最後我想說的是永遠拒絕在你的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();
    }
}
相關文章
相關標籤/搜索