前言
转眼间成为一名PHPer已经快整整两年了,在这期间也对如何写出可读性高,便于扩展的代码有了一些本身的想法。php
使用引用
场景一:遍历一个数组获取新的数据结构数据库
也许你会这样写:编程
// 申明一个新的数组,组装成你想要的数据 $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
使用引用是否是使咱们的代码更加的简洁,除此以外相对于第一种写法,咱们节省了内存空间,尤为是再操做一个大数组时效果是及其明显的。数组
场景二:传递一个值到一个函数中获取新的值antd
基本和数组遍历一致,咱们只须要声明这个函数的这个参数为引用便可,以下:数据结构
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...改写后:this
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中。因此,咱们在写上游代码时异常直接抛出便可。spa
使用匿名函数
** 构建函数或方法内部的代码块 **
假如咱们有一段逻辑,在一个函数或者方法里咱们须要格式化数据,可是这个格式化数据的代码片断出现了屡次,若是咱们直接写可能会想下面这样:
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(); ...
上面的写法有什么问题?
因此咱们使用匿名函数解决上面的问题,下面咱们这么改写:
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(); ...
若是你遇见下面这种类型的代码,那必定是个黑洞。
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(...) { ... } }
最后我想说的是永远拒绝在你的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(); } }