zoukankan      html  css  js  c++  java
  • 说说接口封装

    今天给同事封装了一个接口,说起接口封装的事情,其实其实很有的聊。很多时候,说一个服务好,一个服务烂,实际上都是在吐槽服务队外暴露的接口好坏。不管什么语言,封装接口,抽象起来,就是由一个函数名,若干个参数,若干个返回值组成的。封装的好坏,就在这几个上面。

    函数名

    首先是函数名。函数名的好坏很明显,我的观点,是否简单,不重复。比如在一个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 异常是默认出错,在错误中找“可修复”的错误。错误码是默认正常,在正常中找“可修复”的错误。前者更为保守。

  • 相关阅读:
    hdu 2647 Reward
    hdu 2094 产生冠军
    hdu 3342 Legal or Not
    hdu 1285 确定比赛名次
    hdu 3006 The Number of set
    hdu 1429 胜利大逃亡(续)
    UVA 146 ID Codes
    UVA 131 The Psychic Poker Player
    洛谷 P2491消防 解题报告
    洛谷 P2587 [ZJOI2008]泡泡堂 解题报告
  • 原文地址:https://www.cnblogs.com/yjf512/p/6525970.html
Copyright © 2011-2022 走看看