资讯专栏INFORMATION COLUMN

php业务代码风格的几个建议

tianyu / 3080人阅读

摘要:业务代码风格的几个建议关于循环体内重复调用的问题代码示例是一个可能超出个数据的大变量以上代码块是有非常严重问题的。所以,业务代码应尽量避免使用类似代码。建议每个方法的代码不超过行,除非情况特殊。

业务代码风格的几个建议

关于循环体内重复调用的问题

代码示例:

// $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"]]);
}

以上代码块是有非常严重问题的。

在循环体中,不能重复使用数据库查询太多次,尤其是相似或一致的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的网络协议或多或少都会慢在数据传输上,而且对方业务的实现一般也不建议调用方循环调用,所以如果有批量调用接口的需求,应该要求接口提供方提供批量操作的接口,在循环体外进行操作。

以上代码可修改为:

$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);
}

应将文件内容放在循环体外部。

其他但凡是有耗时或不建议频繁调用的逻辑,都应该写在循环体外。

关于业务层面类调用或方法调用可读性的问题。

关于这一点,先看一下代码示例:

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依赖包中的比较多,写起来比较简单,也不必担心别人看不懂的问题。

关于辅助方法编写的问题

这一点不同的框架有不同的表现,可能有一些框架有自身实现思想的考虑,不方便全局调用一些东西,但目前很多框架都使用了容器,所以使用辅助方法进一步精简代码就有些必要了。

写有辅助方法文件,需要在框架加载过程中,业务使用之前引入,最好是全局引入。

比如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);

关于逻辑块复用和可读性的问题

比如:

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行,除非情况特殊。

关于公共方法书写风格的建议

关于公共方法调用的代码风格,应该遵循调用者最少知道的原则,调用者只需按照对应参数传入即可,后面的逻辑复杂性,原则上不必被调用者知道,且调用者应能够充分知晓正确与错误信息,且应保证其健壮性。

涉及到传入参数为数组的,应告知调用者该数组内部参数的详细说明,或在注释中给出对应示例。

页面下载使用专用文件服务器

涉及到web页面直接生成数据下载的,应尽量使用专用的文件服务器,而不是直接在页面进行下载,且下载应尽量使用异步生成文件。

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

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

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/31235.html

相关文章

  • SOA面向服务基础

    摘要:面向服务面向服务的基础面向服务的三层应用层,服务层,数据层应用层用于给用户展示,,,,安卓。在服务器端,进程保持睡眠状态直到调用信息到达为止。编译完成,提示我们已经在下了。 面向服务 面向服务的基础 面向服务的三层:应用层,服务层,数据层 * 应用层:用于给用户展示,PC,H5,IOS,安卓。 * 服务层:业务逻辑,提供接口(商品,订单,支付,用户,物流)。 * 数据层:提供数据支持(...

    songze 评论0 收藏0
  • 如何让PHP性能问题不再饱受诟病

    摘要:作为开发中应用最广泛的开源脚本语言,凭借库类丰富,使用简单,安全等特点,成为和等互联网巨头和全球超过网站的主要开发语言,然而性能问题是一直以来饱受诟病的,来自开发组的高驰涛同学将为我们带来他对性能优化方面的思考和建议。 PHP作为Web开发中应用最广泛的开源脚本语言,凭借库类丰富,使用简单,安全等特点,成为Facebook和BAT等互联网巨头和全球超过70%网站的主要开发语言,然而性能...

    warnerwu 评论0 收藏0
  • 转鸟哥建议:让PHP7达到最高性能几个建议

    摘要:让达到最高性能的几个建议懒得排版了,伯乐在线链接原文出处惠新宸欢迎分享原创到伯乐头条已经发布了,作为十年来最大的版本升级,最大的性能升级,在多放的测试中都表现出很明显的性能提升,然而,为了让它能发挥出最大的性能,我还是有几件事想提醒下。 让 PHP7 达到最高性能的几个建议 懒得排版了,伯乐在线链接:http://blog.jobbole.com/95657/ 原文出处: 惠新宸(@L...

    dcr309duan 评论0 收藏0
  • 使用Swoole加速Laravel(正式环境中)

    摘要:如需要支持热启动,请自行谷歌,大概原理就是用监控文件变更,如果更新了重启如果正式环境中还可以自己写个部署脚本,后重启服务等,方法很多不一一列举。 1 Laravel的速度瓶颈在哪? 1.1 已有的一些优化方法 1.1.1 laravel官方提供了一些优化laravel的优化方法 php artisan optimize php artisan config:cache php arti...

    vibiu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<