2020-08-28

前言

我经常能听到一些对话

狗腿子A:哇 我刚刚去改**项目的代码,看的我有点怀疑人生

狗腿子B: 我现在项目的跟屎山一样

狗腿子C: 我隔壁那哥们每天写代码都特别随性,我有点按耐不住我的刀

今天跟大家聊聊一些 我眼中 隔壁家孩子的编码习惯,而不是代码风格习惯 ,当然还是强烈建议大家代码风格跟psr-12和psr-1靠齐。大家需要Java资料可以加群927197507


psr-1基础编码规范 、psr-12编码规范托充

This specification extends, expands and replaces PSR-2, the coding style guide and requires adherence to PSR-1, the basic coding standard.

不过度的if嵌套判断

案例背景

有个函数需要判断用户是否参与活动

流程图

案例代码


  if (用户 == VIP) {

        if (用户的过期时间 <= 1个月内) {

            if (用户没参加过任务) {

                return true;

            }

        } else {

            return false;

        }

    } else {

        return true

    }



面对这种多条件的判断可以试着用拦截法和逆向思维

拦截法只要符合条件立马返回结果,不再嵌套的if。可以理解成横向判断变成纵向判断。

舒适感

从上往下看 > 从左往右看

逆向思维 大家上学的时候都了解过,与其漫天去找符合的条件还不如找不符合条件,这样的逻辑代码可以少很多。


if (用户 != VIP) {

    return true;

}

if (用户参加过任务) {

    return false;

}

if (用户的过期时间 <= 1个月内) {

    return true;   

}

return false;


不过度的try-catch嵌套

我遇到过很多项目都过度嵌套try-catch导致最上层的try-catch catch了寂寞。

案例代码


function insertUser($data)

{

    try {

        userIsInValid();

    } catch (Exception $exception) {

    }

}

function userIsInValid()

{

    try {

        //逻辑判断

    } catch (Exception $exception) {

        return true;

    }

    return true;


}


这样的代码没有问题,但是如果假设userIsInValid真的发生代码级的错误没法知道那里出问题,虽然不会破坏业务的健壮性。

可能有人说了在Excetion加个日志,但是如果嵌套的try-catch多了,排查日志也是一件很痛苦的事情。

1.尽可能业务最上层包裹异常 除非网络IO请求函数。

2.如果非要异常嵌套 需要定义每个异常的类型。

3.尽可能根据特定的异常进行catch 不建议直接catch Exception。

4.异常和日志是个cp,还是不要忘记了。


function insertUser($data)

{

    try {

        userIsInValid();

    } catch (Exception $exception) {

        // 日志

        // 业务处理

    } catch (HttpException $httpException) {

      // 日志

      // 业务处理

    }

}

function userIsInValid()

{

    //

    return true;

}


不要用if-else做错误类型判断

案例代码 (来源某个网民前段时间咨询)


if ($code === 'NOTENOUGH') {

    packApiData(400014, 'Company have no enough money to pay', [], '企业余额不足');

} elseif ($code === 'AMOUNT_LIMIT') {

    packApiData(400015, 'Amount limit', [], '金额超限或被微信风控拦截');

} elseif ($code === 'OPENID_ERROR') {

    packApiData(400016, 'Appid and Openid does not match', [], 'Openid格式错误或不属于此公众号');

} elseif ($code === 'SEND_FAILED') {

    // 付款错误,要查单来看最终结果

    if ($orderInfo[1]['status'] == 'SUCCESS') {

        // 还是成功给了,扣回余额


        packApiData(200, 'success', [$orderInfo[1]]);

    } else {

        packApiData(400017, 'Weixin pay failed', [], '微信支付付款失败');

    }

} elseif ($code === 'SYSTEMERROR') {

    packApiData(400018, 'Weixin pay server error', [], '微信支付服务器错误');

} elseif ($code === 'NAME_MISMATCH') {

    packApiData(400019, 'Real name mismatch', [], '微信用户的真名校验失败');

} elseif ($code === 'FREQ_LIMIT') {

    packApiData(400020, 'Api request frequently', [], '微信支付接口调用过于频繁,请稍候再请求');

} elseif ($code === 'MONEY_LIMIT') {

    packApiData(400021, 'Company have reached total payment limit', [], '已经达到今日付款总额上限');

} elseif ($code === 'V2_ACCOUNT_SIMPLE_BAN') {

    packApiData(400022, 'This payment account has no real name', [], '用户的微信支付账户未实名');

} elseif ($code === 'SENDNUM_LIMIT') {

    packApiData(400023, 'The number of times the user paid today exceeded the limit', [], '该用户今日收款次数超过限制');

}


这样的代码可能写起来特别舒服,但是后期进行业务的增加改写和时间的沉淀,容易变成让人害怕的屎山代码。

我们用mapping错误码来调整下


function packApiDataByOrderError($code)

{

    $errorCodeMappins = [

        "NOTENOUGH" => [

            "code" => 400014,

            "wx_message" => "Company have no enough money to pay",

            "error_message" => "企业余额不足"

        ],

        "AMOUNT_LIMIT" => [

            "code" => 400015,

            "wx_message" => "Amount limit",

            "error_message" => "金额超限或被微信风控拦截"

        ],

        .....

    ];

    if (array_key_exists($code, $errorCodeMappins)) {

        packApiData(

            $errorCodeMappins[$code]['code'],

            $errorCodeMappins[$code]['wx_message'],

            [],

            $errorCodeMappins[$code]['error_message']

        );

    }

    packApiData(

        999999,

        "undefined message",

        [],

        "未知错误"

    );

}


建议errorCodeMappins不要放在函数内,可以放在类顶部或者专门枚举类。

通过errorCode 可以避免调整主流程代码,能够保证主流程的代码比较精简也能对不同的code进行错误的定义


if ($code == "SEND_FAILED") {

    // 付款错误,要查单来看最终结果

    if ($orderInfo[1]['status'] == 'SUCCESS') {

        // 还是成功给了,扣回余额

        PDOQuery($dbcon, 'UPDATE user SET money=money-? WHERE open_id=?', [$payAmount, $openId], [PDO::PARAM_INT, PDO::PARAM_STR]);

        packApiData(200, 'success', [$orderInfo[1]]);

    } else {

        packApiData(400017, 'Weixin pay failed', [], '微信支付付款失败');

    }

}

packApiDataByOrderError($code);


在合适的场景使用设计模式

上述可能只能针对错误码进行改造,如果万一我们需要不同的错误进行逻辑处理还怎么办。这时候可以考虑用设计模式 (比如用以多态取代条件表达式)

设计模式固好但不要过度使用,不然整个项目更难维护,你要坚信未来的你队友不知道是什么样的生物


$callbackCodeMappings = [

    "SEND_FAILED" => OrderSendFailed::class,

];

if (array_key_exists($code, $callbackCodeMappings)) {

    $class = new $callbackCodeMappings[$code];

    $class->handle();

}

interface OrderStateImp

{

    public function handle($context);

}

class OrderSendFailed implements  OrderStateImp

{

    public function handle($context)

    {

    }

}


$callbackCodeMappings同样建议配置专门枚举文件内。

给出得代码比较粗糙,其实可以更加健壮性的做一些判断

统一处理浮点运算结果

由于php是弱对象语言,所以面对一堆情况总能出现,这个订单数据怎么不对了,接口有问题。


$int = 0.58; var_dump(intval($int * 100));

output:57


在浮点数里面 58是被视为57.999999999999999999999……9999无限接近58

再intval强制转换乘整型的时候就默认采用截取法取整

所以最好养成一个好习惯每次在计算浮点数的时候用

BC Math


$int = 0.58;

intval(strval($int * 100))


或者使用BC MATH


bcmul(0.58, 100, 0);


鼓励用全局错误码来控制错误

写接口的我们对以下的json格式特别熟悉


{

    "success": true,

    "error_code": 0,

    "message": "",

    "results": []

}


对以下的代码也已经熟悉


if (***) {

    $this->error(999,"****", []);

}


这样的结果的错误码容易重复没有统一管理,事实上唯一错误码应该有以下帮助。

1.前端可以根据错误码做逻辑处理

2.根据错误码能直接快速定位到错误代码

建议


namespace App\ErrorCode;

class UserErrorCode

{

    const USER_DISABLE_ERROR = [

        "error_code" => 1050001,

        "message" => "用户已被停用"

    ];

}

$this->error(UserErrorCode::USER_DISABLE_ERROR);


错误码建议

1-2位 - 项目码 | 3-4位 - 模块码 | 5-7位具体业务错误码

可靠的命名规范

不可靠的命名总会让人误导。

比如变量命名为userArrayList 我以为是个数组列表变量,事实上这个特么是个对象列表。

1.做有意义的区分

比如 singleUserItem跟userItem有啥区别

比如 getUserList跟getUsers有啥区别

2.可以通过搜索翻译能知道的变量含义

不要把变量贴入搜索翻译会出现七七八八的东西

3.如果真的不知道该怎么翻试试用拼音把别硬凹了

比如之前做百度的一个接口对接

变量命名为hundredDegree而不是baidu

其他的可以参照《代码简洁之道》

擅用middleware

middleware可以理解成观察者模式,我们开发的接口总会遇到很多同样操作,比如

1.身份检测

2.权限判断

3.请求参数filter调整

4.记录接口信息

5.接口限流

我见过挨个接口去实现、也见过初始化一个ControllerBase的类,实现这些,子类的Controller去继承这些。

其实我们可以抽离成middleware去实现

好处可以根据不同接口对middleware进行组合选择,而不是对代码进行各特殊化处理.

函数的单一职责

最最最最后也是最重要的,代码的恶心大多数来源于函数的职责不清晰,有全都塞在一起的、东一块西一块的。

其实关于单一职责有很多文章在描述,如何去检验或者去写符合标准的单一职责。

画流程图

如果你能把业务的流程图画的特别清晰,那么你的函数的职责也就定下来了。



// 兑换逻辑

function doExchange()

{

    if (checkIsLock()) {


    }

    lock();

    if (!checkUserIsExchange()) {


    }

    costUserPoint();

    exchangeGoods();

}

// 判断是否悲观锁

function checkIsLock(){}

// 上悲观锁

function lock(){}

// 判断用户是否可以兑换

function checkUserIsExchange(){}

// 扣除积分

function costUserPoint(){}

// 兑换商品

function exchangeGoods(){}



最后

留个彩蛋 看看大家怎么实现

写一个函数returnScoreResult,请根据输入的分数,返回对应的成绩的等级。

1.如果分数小于0或者大于100 返回 【无效分数】

2.如果分数>=0,<60 返回 【不及格】

3.如果分数>=60,<70 返回【及格】

4.如果分数>=70,<80 返回 【一般】

5.如果分数>=80, <90 返回 【良好】

5.如果分数>=90, <100 返回 【优秀】

6.如果分数=100 返回【满分】

你可能感兴趣的:(2020-08-28)