php業務代碼風格的幾個建議

業務代碼風格的幾個建議

  1. 關於循環體內重複調用的問題

    代碼示例:php

    // $array是一個可能超出1000個數據的大變量
    foreach ($array as $ar) {
        $model = new Model();
        $modelData = $model->findOne(['id' => $ar['id']]);
        $modelData['is_less'] = $modelData['tag'] == 3 ? true : false;
        $model2 = new AnotherModel();
        $model2->find(['id' => $ar['id']])->update(['is_less' => $modelData['is_less']]);
    }

    以上代碼塊是有很是嚴重問題的。web

    在循環體中,不能重複使用數據庫查詢太屢次,尤爲是類似或一致的sql,必定要批量查詢獲取數據以後再作相應邏輯層面的處理。若是循環次數較多,不只僅會體如今循環邏輯較慢上,並且在併發讀寫的業務中因爲頻繁讀取硬盤以及鎖表等可能會給數據庫服務器形成巨大壓力。ajax

    因此以上代碼能夠改形成:sql

    $ids = array_columns($array, 'id');
    if ($ids) {
        $model = new Model();
        $modelDataList = $model->where(['in', 'id', $ids])->all();
        $modelDataList = array_combine(array_columns($modelDataList, 'id'), $modelDataList);
    }
    foreach ($array as $ar) {
        $modelData = empty($modelDataList[$ar['id']]) ? [] : $modelDataList[$ar['id']];
        if (!$modelData) {
            continue;
        }
        $modelData['is_less'] = $modelData['tag'] == 3 ? true : false;
        $model2 = new AnotherModel();
        $model2->find(['id' => $ar['id']])->update(['is_less' => $modelData['is_less']]);
    }

    還有一個例子,如:數據庫

    foreach($ids as $id) {
        $data = RpcService::getMyData(['my_id' => $id]);
        $data['field1'] = $data['field2'] + $data['field3'];
        $sendPost = RpcService::sendToBoss(['field1' => $data['field1']]);
    }

    像這種經過接口獲取數據或者更新數據的,通常不能在循環體內重複調用,由於http或者其餘實現rpc的網絡協議或多或少都會慢在數據傳輸上,並且對方業務的實現通常也不建議調用方循環調用,因此若是有批量調用接口的需求,應該要求接口提供方提供批量操做的接口,在循環體外進行操做。json

    以上代碼可修改成:數組

    $dataList = RpcService::getMyDataList(['ids' => $ids]);
    array_walk($dataList, function () {
        // 處理字段
    });
    RpcService::multiSendToBoss(['list' => array_columns($dataList, 'field1')]);

    還有文件讀取也是相似,php讀寫文件效率並不高,應避免頻繁讀寫相同文件。例如:性能優化

    $readFilePath = 'current_file';
    foreach($writeFilePaths as $path) {
        $content = file_get_content(realpath($readFilePath));
        file_put_content($path, $content);
    }

    應將文件內容放在循環體外部。服務器

    其餘但凡有耗時或不建議頻繁調用的邏輯,都應該寫在循環體外。網絡

  2. 關於業務層面類調用或方法調用可讀性的問題。

    關於這一點,先看一下代碼示例:

    class DemoClass 
    {
        
        public $handler = [];
        public function handlerRegister(Handler $handler)
        {
            $this->handler[] = $hanlder;
        }
        
        public function run($id, $params)
        {
            foreach ($this->handler as $h) {
                if ($h->id === $id) {
                    return $h->handle($params);
                }
            }
        }
    }
    
    
    class Handler
    {
        public $id;
        public function handle($params)
        {
            $result = call_user_func($params['callback'], $params['params']);
            $resultData = (new $result)->getData();
            $next = $params['next'];
            return $next($resultData);
        }
    }

    call_user_func、call_user_func_array、Reflection類等能夠對變量裏的某些內容直接實例化或者調用,這在機器編譯運行看來是沒什麼問題的,可是人看的話就比較費心了,你要追根溯源搞清楚調用的是啥,反射的是啥,雖然寫起來很簡單粗暴,但對讀的人不太友好。何況,即使是phpstorm這樣的強大的IDE,也不能幫你識別追溯這些變量的源頭。

    因此,業務代碼應儘可能避免使用相似代碼。

    可是,若是你寫的是底層腳手架,是豐富框架功能的一個工具包,那麼你能夠按照本身的想法來,使用者看不看得懂就不重要了。因此像以上的例子每每出如今vendor依賴包中的比較多,寫起來比較簡單,也沒必要擔憂別人看不懂的問題。

  3. 關於輔助方法編寫的問題

    這一點不一樣的框架有不一樣的表現,可能有一些框架有自身實現思想的考慮,不方便全局調用一些東西,但目前不少框架都使用了容器,因此使用輔助方法進一步精簡代碼就有些必要了。

    寫有輔助方法文件,須要在框架加載過程當中,業務使用以前引入,最好是全局引入。

    好比Yii2中若是要實現一個json返回,須要寫如下代碼:

    Yii::$app->response->format = Response::FORMAT_JSON;
    return [
        'code' => 1,
        'message' => 'success'
    ];

    若是你寫了一個輔助方法以下:

    function ajax(array $data) 
    {
        Yii::$app->response->format = Response::FORMAT_JSON;
        return $data;
    }

    那麼代碼能夠寫成:

    return ajax([
        'code' => 1,
        'message' => 'success'
    ]);

    又好比:獲取一個post提交的全部參數:

    $post = Yii::$app->request->post(); // 獲取全部
    $field1 = Yii::$app->request->post('field1'); // 獲取其中的某個參數

    輔助方法以下:

    function post($key = null, default = null)
    {
        if ($key === null) {
            return Yii::$app->request->post(); // 獲取全部
        } 
        return Yii::$app->request->post($key, $default)
    }

    調用以下:

    $post = post();
    $field1 = post('field1');

    又好比,根據鍵銷燬數組中的某一個值。

    // 原生寫法
    if (isset($arr[$key])) {
        unset($key);
    }
    
    // 輔助方法
    function array_pull(&$arr, $key)
    {
        if (isset($arr[$key])) {
            unset($key);
        }
    }
    // 調用
    array_pull($arr, $key);
  4. 關於邏輯塊複用和可讀性的問題

    好比:

    public function handle($params)
    {
        $dataList = (new Model)->query($params)->all();
        $return = [];
        foreach ($dataList as $data) {
            
            if ($data['key'] == 1) {
                $data['field1'] = "123";
            } else if ($data['key'] == 2) {
                $data['field1'] = '678';
                $data['field2'] = '7hj';
            } else if ($data['key'] == 3) {
                $data['field1'] = 'uyo';
            } else {
                $data['field1'] = 'other';
            }
            
            if ($data['field1'] == "123") {
                $other = [
                    'other1' => 34,
                    'other2' => 35,
                    'other3' => 98
                ];
                $return[] = $other;
            } elseif ($data['field1'] == "678") {
                $other = [
                    'other1' => 341,
                    'other2' => 351,
                    'other3' => 981
                ]; 
                if ($data['field2'] == '7hj') {
                    $other = [
                        'other1' => 3412,
                        'other2' => 3512,
                        'other3' => 9812
                    ]; 
                }
                $return[] = $other;
            } else if ($data['field1'] === 'uyo') {
                $other = [
                    'other1' => 3412,
                    'other2' => 3512,
                    'other3' => 9812
                ]; 
                $return[] = $other;
            } else {
                $other = [
                    'other1' => 34123,
                    'other2' => 35123,
                    'other3' => 98123
                ]; 
                $return[] = $other;
            }
            
        }
    }

    以上代碼出現的問題主要有三個:無註釋、判斷條件太多、邏輯塊沒法複用。

    若是出現更復雜的邏輯,這段代碼無疑可能會超過100多行,這在開發維護人員看起來是很艱難的。

    註釋問題和判斷條件太多可能因爲業務的問題,有時候沒法避免,這裏重點說一下邏輯塊複用。

    以上的代碼片斷中,dataList的獲取,應該是一個獨立的方法,由於未來可能其餘功能也會使用一樣的方法獲取對應數據;針對於data['field1']的取值,也應該是一個獨立的方法;下面對於data['field1']的判斷,也應該是一個獨立方法。這就須要對代碼拆分,既可以保證代碼簡潔性、複用性和可讀性,也避免多個無用變量在一個邏輯塊中積累。

    在代碼邏輯中,一個功能應該由一個方法來實現,一個方法也應該只作一件事。

    這樣把這段代碼拆分以後,將會變爲:

    public function getDataList($params)
    {
        return (new Model)->query($params)->all();
    }
    
    public function handleField($data)
    {
        if ($data['key'] == 1) {
            $data['field1'] = "123";
        } else if ($data['key'] == 2) {
            $data['field1'] = '678';
            $data['field2'] = '7hj';
        } else if ($data['key'] == 3) {
            $data['field1'] = 'uyo';
        } else {
            $data['field1'] = 'other';
        }
        return $data;
    }
    
    public function getOther()
    {
        if ($data['field1'] == "123") {
            $other = [
                'other1' => 34,
                'other2' => 35,
                'other3' => 98
            ];
        } elseif ($data['field1'] == "678") {
            $other = [
                'other1' => 341,
                'other2' => 351,
                'other3' => 981
            ]; 
            if ($data['field2'] == '7hj') {
                $other = [
                    'other1' => 3412,
                    'other2' => 3512,
                    'other3' => 9812
                ]; 
            }
        } else if ($data['field1'] === 'uyo') {
            $other = [
                'other1' => 3412,
                'other2' => 3512,
                'other3' => 9812
            ]; 
        } else {
            $other = [
                'other1' => 34123,
                'other2' => 35123,
                'other3' => 98123
            ]; 
        }
        return $other;
    }
    
    public function handle($params)
    {
        $dataList = $this->getDataList($params);
        $return = [];
        foreach ($dataList as $data) {
            $data = $this->handleField($data);
            $return[] = $other;
        }
        return $return;
    }

    這幾個方法各自承擔一個功能,只完成一件事。handle()方法只是用來組織幾個方法的數據,簡單明瞭。

    建議每一個方法的代碼不超過30行,除非狀況特殊。

  5. 關於公共方法書寫風格的建議

    關於公共方法調用的代碼風格,應該遵循調用者最少知道的原則,調用者只需按照對應參數傳入便可,後面的邏輯複雜性,原則上沒必要被調用者知道,且調用者應可以充分知曉正確與錯誤信息,且應保證其健壯性。

    涉及到傳入參數爲數組的,應告知調用者該數組內部參數的詳細說明,或在註釋中給出對應示例。

  6. 頁面下載使用專用文件服務器

    涉及到web頁面直接生成數據下載的,應儘可能使用專用的文件服務器,而不是直接在頁面進行下載,且下載應儘可能使用異步生成文件。

    例如用戶在點擊頁面下載按鈕以後跳入本身的下載頁面,這個頁面上有本身的文件下載的歷史表格,有狀態標記文件是否能夠進行下載,當後臺將文件生成好上傳到文件服務器以後,會標記成可下載。

    好處:記錄下載歷史、歷史文件下載、下載性能優化、能夠處理大文件。

    壞處:須要多作一個頁面和一張表,且須要等待文件生成上傳至文件服務器的時間。

    文件服務器可使用對象雲。

相關文章
相關標籤/搜索