资讯专栏INFORMATION COLUMN

PHPer这样写代码也许更优雅

kidsamong / 923人阅读

摘要:前言转眼间成为一名已经快整整两年了,在这期间也对如何写出可读性高,便于扩展的代码有了一些自己的想法。所以,我们在写上游代码时异常直接抛出即可。通常这样可以很大程度的提高效率和代码复用。

前言

转眼间成为一名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

使用引用是不是使我们的代码更加的简洁,除此之外相对于第一种写法,我们节省了内存空间,尤其是再操作一个大数组时效果是及其明显的。

场景二:传递一个值到一个函数中获取新的值

基本和数组遍历一致,我们只需要声明这个函数的这个参数为引用即可,如下:

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...改写后:

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中。所以,我们在写上游代码时异常直接抛出即可。

使用匿名函数

构建函数或方法内部的代码块

假如我们有一段逻辑,在一个函数或者方法里我们需要格式化数据,但是这个格式化数据的代码片段出现了多次,如果我们直接写可能会想下面这样:

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" => "namespaceA", // 带上命名空间,因为变量是动态的
        "b" => "namespaceB",
        "c" => "namespaceC"
    ];
    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();
    }
}
最后

如果有写的不对的地方,欢迎大家指正,THX~

Easy PHP:一个极速轻量级的PHP全栈框架

扫面下方二维码关注我的技术公众号,及时为大家推送我的原创技术分享

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

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

相关文章

  • 我的 2017 —— 一个 PHPer 的自白

    摘要:大会年,我去了。小会值得一提的是,今年月份,我参加了一个的分享会。出游沙巴这是部门组织的出游,获得了最佳团队,拿到了一笔经费,于是有了这次出游。于是,我的下个目的地是西藏。 转眼间 2017 年过去了。我已经不能说自己是去年的毕业生了,时光匆匆,感觉自己越来越老了。 这一年,我所经历的,让我收获很多,让我懂得很多,让我明白了很多。也许是明确了某一个目标,也许是其它的什么,我觉得,201...

    BenCHou 评论0 收藏0
  • tastphp,为现代化的 phper 准备的 PHP 框架

    摘要:大家好,推荐下我们团队自己研发的框架为现代化的准备的。可拔插,扩展性强。借鉴了等优秀框架。有兴趣的可以关注下。最渴望有人给我们提交。中文文档基础已经写完,剩下努力写中。。。 大家好,推荐下我们团队自己研发的框架:tastphp 为现代化的phper准备的。可拔插,扩展性强。借鉴了Symfony、Laravel、Silex等优秀框架。 有兴趣的可以关注下 tastphp。最渴望有人给...

    meteor199 评论0 收藏0
  • 转:从框架看PHP的五种境界及各自的薪资待遇

    摘要:语言行为及特征状态看不懂任何英语技术,英语文档,凡事没有培训部在搞的,只有英文文档的东西国内一律没大公司在用,都非主流,排斥英文文档和新技术,以及各种超出他学习能力范围的技术。 在撰写此文前首先必须申明的是本人不鄙视任何一种框架,也无意于挑起PHP框架间的战争,更没有贬低某个框架使用者的用意,本文纯粹个人的看法。你可以认为我无知也好,或者装逼也好,请不要试着在任何情况下,随便发起言语的...

    Godtoy 评论0 收藏0
  • Swoole 2019 :化繁为简、破茧成蝶

    摘要:开发负责人创建分支,编写单元测试脚本,编写代码,实现提案中的所有内容,最终发起交叉评审,检查代码,提出改进意见,反馈给开发负责人,继续完善细节。 Swoole开源项目从2012年开始发布第一个版本,到现在已经有近7年的历史。在这七年的时间里: 提交了8821次代码变更 发布了287个版本 收到并解决1161次issue反馈 合并了603次pull request 共有100位开发者...

    adam1q84 评论0 收藏0
  • SegmentFault 技术周刊 Vol.40 - 2018,来学习一门新的编程语言吧!

    摘要:入门,第一个这是一门很新的语言,年前后正式公布,算起来是比较年轻的编程语言了,更重要的是它是面向程序员的函数式编程语言,它的代码运行在之上。它通过编辑类工具,带来了先进的编辑体验,增强了语言服务。 showImg(https://segmentfault.com/img/bV1xdq?w=900&h=385); 新的一年不知不觉已经到来了,总结过去的 2017,相信小伙们一定有很多收获...

    caspar 评论0 收藏0

发表评论

0条评论

kidsamong

|高级讲师

TA的文章

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