今天给同事封装了一个接口,说起接口封装的事情,其实其实很有的聊。很多时候,说一个服务好,一个服务烂,实际上都是在吐槽服务队外暴露的接口好坏。不管什么语言,封装接口,抽象起来,就是由一个函数名,若干个参数,若干个返回值组成的。封装的好坏,就在这几个上面。
函数名
首先是函数名。函数名的好坏很明显,我的观点,是否简单,不重复。比如在一个User类中你封装一个方法,叫做findUser。我就觉得很啰嗦了。你使用的时候会这样使用
User::findUser($id);
那又是何必呢?为什不直接叫做find呢?
User::find($id);
我记得前段时间在网上还看到一篇文章,你见过哪些奇葩的代码。其中就有一些有趣的函数名。在我的视角看来,下面的函数名都很奇葩:
function weizhi() // 中文拼音
function getuserinfo() // 单词和单词没用大小写分割
function getUserIsEnable() // 明明是bool判断却用get开头
基本上,我们选择使用 动词 或者 动词+名词 或者 动词+名词 + 副词
比如
function find()
function getUser()
function getUserByName()
我觉得这些都是很符合人性的函数名。
参数
一句话, 参数尽量不要封装。。。尽量不要太多。。。
尽量不要封装就是,能队外暴露的细节越多,用户使用成本越低,比如,根据地理位置获取地址的函数
// 里面的$coord是一个数组['lat','lng']
function getCityByCoord($coord)
就不如
function getCityByCoord($lat, $lng)
还有不要太多就是如果你参数个数超过5个,就该考虑封装了。封装的时候,我习惯会把一些“不重要的”,“不常用的”封装成一个参数,并且设置这个参数默认值。
// 这里的conditions 可以设置表列名,只能用等号 ['class' => 1]
function getUsers($offset, $limit, $sort, $conditions = [])
返回值
这个返回值就很有的说了。首先遇到的问题是,返回值是否是返回数组。这个问题让我想起了在刚接触php的时候,那时候刚从c#转过来,对接手的项目的一个函数返回值包含什么一直不理解。问了同事,他的回复是,你调用一下就可以知道他们返回什么了。
反正吧,对于php的返回值,我的观点就是,如果你的项目在追求的是快,而且开发人数也不多,那么,你就可以使用数组来做交互。如果你的项目追求的是工程化,模块和模块之间的交互需要人与人的沟通,那么,尽量定义好对象。使用对象进行交互。事实上,像laravel这类追求工程化的框架,你在实现和别人交互的接口的时候,尽量传递的是Model,或者Collection比较好。
比如
// in Service
// return: LocationModel
public function findByName($name){
return LocationModel::where('name', trim($name))->first();
}
异常和错误
接口函数定义好了,可不是就结束了,这个函数是否会抛出异常?是否会返回错误?
对于错误和异常的理解,我的理解是:
- 异常是不能被兼容处理的
- 错误是希望被兼容处理的
我记得在上一个项目,我强烈建议团队小伙伴们在封装对外的soa的sdk的时候,使用的方式是如此:
list($code, $data) = UserService::getUserByName($name);
if ($code) {
// 处理对应的错误
}
php原本的返回值只有一个对象,这里使用list拆成两个对象,一个是code,代表返回值的错误信息,一个是data,代表如果没有错误的话,返回的结构。这个接口,我们在内部做了try_catch,不会抛出异常,所有的信息都以错误码的形式返回。
如果在内部try_catch捕获到了异常,则返回的code会是500。
其实使用异常还是错误码处理错误都是可以的。错误码是写程序的时候最早使用的方法,但是异常机制出现后,各个语言都倾向于使用异常处理错误了。
就php而言,是建议使用异常处理的。它本身的内部也定义了一堆的build-in 异常。
- BadFunctionCallException
- BadMethodCallException
- DomainException
- InvalidArgumentException
- LengthException
- LogicException
- OutOfBoundsException
- OutOfRangeException
- OverflowException
- RangeException
- RuntimeException
- UnderflowException
- UnexpectedValueException
体会下下面两段代码,分别使用异常和错误码处理
const PARAM_ERROR = 100;
const AGE_TOO_BIG = 200;
const INSERT_ERROR = 300;
class UserException extends Exception{
}
function insertUserByField($name, $code, $age) {
$db = db::connect();
if(empty($name) || empty($code) || empty($age)) {
throw new UserException(PARAM_ERROR);
}
if ($age > 15) {
throw new UserException(AGE_TOO_BIG);
}
$ret = $db->insert('user')->create(compact('name', 'code', 'page'));
if (empty($ret)) {
throw new UserException(INSERT_ERROR);
}
return $ret;
}
// 使用
try{
$user = $userService->insertUserByField('foo', 291212, 34);
} catch(UserException $e) {
switch($e->getCode()):
case PARAM_ERROR:
//
case AGE_TOO_BIG:
//
case INSERT_ERROR:
//
default:
//
} catch(Exception $e) {
//
}
和
const OK = 500;
const PARAM_ERROR = 100;
const AGE_TOO_BIG = 200;
const INSERT_ERROR = 300;
const INNNER_ERROR = 500;
function insertUserByField($name, $code, $age) {
try {
$db = db::connect();
if(empty($name) || empty($code) || empty($age)) {
return [PARAM_ERROR, null];
}
if ($age > 15) {
return [AGE_TOO_BIG, null];
}
$ret = $db->insert('user')->create(compact('name', 'code', 'page'));
if (empty($ret)) {
return [INSERT_ERROR, null];
}
return [OK, $ret];
} catch (Exception $e) {
// do log
return [INNNER_ERROR, null];
}
}
// 使用
list($code, $user) = $userService->insertUserByField('foo', 291212, 34);
if ($code) {
switch $code {
case PARAM_ERROR:
//
case AGE_TOO_BIG:
//
case INSERT_ERROR:
//
default:
//
}
}
我认为,golang中的错误处理机制给了我们很好的示范。它有个error机制代表错误,panic机制代表异常。
func getUserByName(name string) (int, error) {
if len(name) == 0 {
return 0, errors.New("param error")
}
//
}
data, err := getUserByName(name)
if err != nil {
....
}
这里的err代表getUserByName的时候有可能返回错误。它也是期望(甚至于强制)调用方处理各种error。但是它并不保证这个函数不会发生panic,一旦发生panic,整个系统也会崩溃。你需要使用recover来捕获。
如果把golang的这种做法应用在php中,上面的例子可能就会变成:
const OK = 500;
const PARAM_ERROR = 100;
const AGE_TOO_BIG = 200;
const INSERT_ERROR = 300;
const INNNER_ERROR = 500;
// 这里对可能出现的exception就不需要管了,只处理希望上层处理的“错误”
function insertUserByField($name, $code, $age) {
$db = db::connect();
if(empty($name) || empty($code) || empty($age)) {
return [PARAM_ERROR, null];
}
if ($age > 15) {
return [AGE_TOO_BIG, null];
}
$ret = $db->insert('user')->create(compact('name', 'code', 'page'));
if (empty($ret)) {
return [INSERT_ERROR, null];
}
return [OK, $ret];
}
// 如果你有框架的话,这里的try catch就可以在框架统一捕获了。
list($code, $user) = $userService->insertUserByField('foo', 291212, 34);
if ($code) {
switch $code {
case PARAM_ERROR:
//
case AGE_TOO_BIG:
//
case INSERT_ERROR:
//
default:
//
}
}
关于异常和错误这块,不同的语言,不同的人有不同的使用习惯,我的看法,golang中对异常和错误的处理机制是最好的。将两者分别对待。
但是在php中,如果需要有个“银弹”说法的话:尽量使用异常来处理。
如果硬要问为什么?基本上,有两个原因:
1 异常的堆栈信息比错误码丰富
2 异常是默认出错,在错误中找“可修复”的错误。错误码是默认正常,在正常中找“可修复”的错误。前者更为保守。