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页面直接生成数据下载的,应尽可能使用专用的文件服务器,而不是直接在页面进行下载,且下载应尽可能使用异步生成文件。

    例如用户在点击页面下载按钮以后跳入本身的下载页面,这个页面上有本身的文件下载的历史表格,有状态标记文件是否能够进行下载,当后台将文件生成好上传到文件服务器以后,会标记成可下载。

    好处:记录下载历史、历史文件下载、下载性能优化、能够处理大文件。

    坏处:须要多作一个页面和一张表,且须要等待文件生成上传至文件服务器的时间。

    文件服务器可使用对象云。

相关文章
相关标签/搜索