說說接口封裝


今天給同事封裝了一個接口,說起接口封裝的事情,其實其實很有的聊。很多時候,說一個服務好,一個服務爛,實際上都是在吐槽服務隊外暴露的接口好壞。不管什么語言,封裝接口,抽象起來,就是由一個函數名,若干個參數,若干個返回值組成的。封裝的好壞,就在這幾個上面。

函數名

首先是函數名。函數名的好壞很明顯,我的觀點,是否簡單,不重復。比如在一個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 異常是默認出錯,在錯誤中找“可修復”的錯誤。錯誤碼是默認正常,在正常中找“可修復”的錯誤。前者更為保守。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM