博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
PHPer这样写代码也许更优雅
阅读量:6121 次
发布时间:2019-06-21

本文共 6671 字,大约阅读时间需要 22 分钟。

前言

转眼间成为一名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' => '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();    }}

最后

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

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

图片描述

转载地址:http://zomka.baihongyu.com/

你可能感兴趣的文章
解决zabbix 3.0中1151端口不能运行问题
查看>>
售前工程师的成长---一个老员工的经验之谈
查看>>
Get到的优秀博客网址
查看>>
dubbo
查看>>
【Git入门之四】操作项目
查看>>
老男孩教育每日一题-第107天-简述你对***的理解,常见的有哪几种?
查看>>
Python学习--time
查看>>
在OSCHINA上的第一篇博文,以后好好学习吧
查看>>
高利率时代的结局,任重道远,前途叵测
查看>>
Debian 6.05安装后乱码
查看>>
欢迎大家观看本人录制的51CTO精彩视频课程!
查看>>
IntelliJ IDEA中设置忽略@param注释中的参数与方法中的参数列表不一致的检查
查看>>
关于软件开发的一些感悟
查看>>
uva 10806
查看>>
纯CSS3绘制的黑色图标按钮组合
查看>>
Linux中环境变量文件及配置
查看>>
从0开始学Flutter
查看>>
mysql操作入门基础之对数据库和表的增删改查
查看>>
IIS负载均衡
查看>>
分布式事务,EventBus 解决方案:CAP【中文文档】
查看>>