代码规范范例-商城小程序

摘自网络

规范

数据库

以下是对 MySQL Internals Manual :: 26.1.1 Coding Style 的强调与补充。

  • 禁止设置字段允许 NULL,使用默认值代替。

  • 能用 Unique 索引限制唯一,则不要用 Index 索引。

  • 表、字段 Charset 统一 utf8,Collation 统一 utf8_general_ci,存储引擎统一 InnoDB

  • 类似 is_delete 的字段,统一使用 TINYINT(1) 类型,且务必建 Index 索引。

  • 除非情况特殊,严禁使用 TEXT / LONGTEXT / BLOB / LONGBLOB 等类型。

  • 图标、超过255字节的文本,尽量不要存进数据库,会引起 行存储溢出

  • 对于能够使用 INNER JOIN 的场景,尽可能少用 LEFT JOIN,且关联的字段务必建索引。

  • 数据库升级脚本内,对于 CREATE TABLE 等语句,务必加入 IF NOT EXISTS,尽可能避免引起异常。

  • 对于存储 URL 的字段,必须采用 VARCHAR 类型,建议长度:2048 - 8192,参见:https://stackoverflow.com/questions/2659952/maximum-length-of-http-get-request

  • JOIN ON 后面只带关联条件,将固定条件移动至 WHERE 后。

代码

以下是对 PSR-1PSR-2 的强调与补充;有关 PSR 公共规范,请参见:https://github.com/summerblue/psr.phphub.org/tree/master/psrs

  • 局部变量统一使用小驼峰,例如:$goodsList

  • foreach 修改数组元素的值,可以使用引用赋值的方式,例如:

    foreach($list as $k => $v){
        $list[$k]['foo'] = 'bar';
    }
    

    可以优化为:

    foreach($list as $k => &$v){
        $v['foo'] = 'bar';
    }
    unset($v); // 建议随手 unset,否则修改 $v 会改变数组末尾元素的值
    
  • 对于外部不需要访问的属性或方法,尽可能写成 protected / private,以增强健壮性。

  • 对于运行时可能出现的问题(例如类型错误等),尽可能采取「零容忍」态度,且报错越早越好;避免 return false,使用 throw new Exception

  • 尽可能减少代码冗余,将重复的部分提取凝练到一起,例如:

    public function couponInfo()
    {
        $coupon = Coupon::find()->where(['id'=>$this->id])->asArray()->one();
        if($coupon['appoint_type'] == 1){
            $info = Cat::find()->select('id,name')->where(['id'=>json_decode($coupon['cat_id_list'])])->andWhere(['is_delete'=>0,'store_id'=>$this->store_id])->asArray()->all();
        }else if($coupon['appoint_type'] == 2){
            $info = Goods::find()->select('id,name')->where(['id'=>json_decode($coupon['goods_id_list'])])->andWhere(['is_delete'=>0,'store_id'=>$this->store_id])->asArray()->all();
        }else{
            $info = [];
        }
        return [
            'code' => 0,
            'data' => [
                'coupon'=>$coupon,
                'info'=>$info,
            ],
        ];
    }
    

    可以优化为:

    public function couponInfo()
    {
        $coupon = Coupon::find()->where(['id'=>$this->id])->asArray()->one();
        switch($coupon['appoint_type'])
        {
            case 1:
            $info = Cat::find()->where(['id' => json_decode($coupon['cat_id_list'])]);
            case 2:
            $info = Goods::find()->where(['id' => json_decode($coupon['goods_id_list'])]);
            default:
            $info = null;
        }
        if($info){
            $info = $info->select('id,name')->andWhere(['is_delete' => 0, 'store_id' => $this->store_id])->asArray()->all();
        }
        return [
            'code' => 0,
            'data' => [
                'coupon'=>$coupon,
                'info'=>$info,
            ],
        ];
    }
    

    增强代码可读性,降低维护难度。

  • 数据库查询出来的原始数据,将格式转换封装进 模型获取器 进行处理。

  • switch 语句 return 后无需使用 break

  • switch 语句必须带 default 子句,如遇不可能值,则抛出异常。

  • 对于废弃的函数、语句、变量、类,严禁注释、抛出异常或 return,必须删除代码。

  • 禁止使用 rand 函数,请用 mt_rand 代替。

  • 禁止使用 md5 函数,请用 sha1 代替。

  • 提取二维数组内某元素的值作为一个单独的数组,建议使用 array_map

结构

Controllers

  • 控制器基础的方法名称统一,特殊方法可自定义。

  • 控制器中不写逻辑代码,所逻辑代码处理放在 ModelForm 中。

  • Admin 模块

    • actionIndex (列表显示页面)
    • actionCreate (添加数据页面)
    • actionStore (添加数据)
    • actionEdit (编辑数据页面)
    • actionUpdate (更新数据)
    • actionDestroy (删除数据)
    • 其它 (自定义名称)

    例:

    public function actionIdnex()
    {
        return 'index'
    }
    
    ...
    
    public function actionOther()
    {
        return 'other'
    }
    
  • Api 模块

    • actionIndex (列表数据)
    • actionStore (添加数据)
    • actionEdit (单条数据)
    • actionUpdate (更新数据)
    • actionDestroy (删除数据)
    • 其它 (自定义名称)

    例:

    public function actionIdnex()
    {
        return 'index';
    }
    
    ...
    
    public function actionOther()
    {
        return 'other';
    }
    

ModelForms

  • 每一个控制器对应一个 ModelForm 目录,目录名称和控制器名称相同。

    User +
         |- GetUserForm.php
         |- GetUserTypeForm.php
         ...
    
  • 单一原则,一个方法中只做一件事。

    错误:

    public function getUsers()
    {
        $users = User::find()->all();
        
        $data = [];
        foreach($users as $item) {
            $data[] = $item['name'];
            ...
        }
    
        return $data;
    }
    

    正确:

    public function getUsers()
    {
        $users = User::find()->all();
    
        return $data;
    }
    
    public function getResetUsers($users)
    {
        $data = [];
        foreach($users as $item) {
            $data[] = $item['name']
            ...
        }
        
        return $data;
    }
    
  • 需要获取关联数据时,尽可能采用 hasOne / hasMany 定义模型关系,替代原生的 leftJoin 查询;参见:https://www.yiiframework.com/doc/guide/2.0/zh-cn/db-active-record#relational-data

    错误:

    User:find()->alias('u')->leftJoin(['g' => Goods::tableName()], 'o.goods_id=u.id')->asArray()->all();
    

    正确:

    class User extends ActiveRecord 
    {
        public function getGoods()
        {
            return $this->hasMany(Good::className(), ['user_id' => 'id']);
        }
    }
    
    class UserFormModel extends Model
    {
        public function getUsers()
        {
            $users = User::find()->with('goods')->all();
            
            return $users;
        }
    }
    
  • FormModel 中进行数据查询时,尽量不要使用 asArray() 方法。否则导致模型特性消失,无法访问关联模型的数据。

    错误:

    User::find()->asArray()->all();
    

    正确:

    User::find()->all();
    
  • 在条件查询时,不要出现 type=1is_delete=0, status=2 等情况。

    错误:

    User::find()->where(['type' => 1, 'is_delete' = 0, 'status' => 2])->all();
    

    正确:

    class User extends ActiveRecord 
    {
        /**
        * 用户类型:管理员
        */
        const USER_TYPE_ADMIN = 1
        
        /**
        * 用户类型:会员
        */
        const USER_TYPE_MEMBER = 2
        
        /**
        * 用户状态:启用
        */
        const USER_STATUS_TRUE = 1
        
        ...
    }
    
    class UserFormModel extends Model
    {
        public function getUsers()
        {
            $users = User::find()->andWhere(['type' => User::USER_TYPE_ADMIN, 'status' => User::USER_STATUS_TRUE])->all();
            
            return $users;
        }
    }
    

Models

  • 所以关联关系写在对应的模型中(例子在 ModelForm 小节)。
  • 模型中一个字段有着多种情况,应在模型中定义成常量并标识该字段含义(例子在 ModelForm 小节)。

Common

  • 公共逻辑目录:core/models/common
  • 公共逻辑目录分为 Admin(后台管理)Api(小程序接口)
  • 在编写代码的过程中,如果有部分逻辑代码是通用则可以写在 Common 中。例如:创建订单、处理订单、支付等。

响应

HTTP API

响应格式:

{
  "code": <int>,
  "msg": <string>,
  "data": <array> | <object>
}
  • 在 Controller 中,可直接返回数组,以输出 JSON 数据:

    return ['code' => $code, ...];
    
  • 在 Filter 中,可直接使用如下方式设置响应数据,并截断执行:

    \Yii::$app->response->data = $some_response_data;
    return false; // 参见:<https://www.yiiframework.com/doc/guide/2.0/zh-cn/structure-filters#creating-filters>
    
  • api module 的 Controller 中,应使用如下方式输出结构化响应数据:

    return new \app\hejiang\ApiResponse($code, $msg, $data);
    

    或:

    return new \app\hejiang\BaseApiResponse($array);
    

    切勿在输出响应时使用废弃的 json_encoderenderJson 方法,将会引发 app\hejiang\exceptions\InvaildResponseException

模型验证错误

  • 若模型继承 app\models\Model,则可以直接使用 errorResponse 属性:
if(!$this->validate()){
    return $this->errorResponse;
}
  • 其它情况,可使用如下形式输出模型验证错误:
return new \app\hejiang\ValidationErrorResponse($model->errors);

切勿在输出响应时使用废弃的 Model::getModelError 方法。

注释

  • IDE 生成的注释记得改名字,例如:
/**
 * Created by PhpStorm.
 * User: <YOUR NAME>
 * Date: 2017/8/14
 * Time: 17:46
 */
  • 行内注释尽量首部带空格,例如:
// This is a function.
function foo(){
    // do nothing.
}
  • 对于注释无用代码,尽可能不要将 // 打在行首,会引起代码格式化时编辑器误判,例如:

    • 错误:
    function foo(){
        $bar = 1000;
    //    $bar = -1000;
        return $bar;
    }
    
    • 正确:
    function foo(){
        $bar = 1000;
        // $bar = -1000;
        return $bar;
    }
    

异常

抛出与处理

多数情况,异常(exception)对我们可能比较陌生,多数都是框架或系统抛出异常,我们接收并处理。但实际上,如果能够在我们编写的实际业务代码中恰当地抛出异常,将会有意想不到的效果。下面我用几个符合我们实际场景的简明实例来说明。

优化前:

function actionIndex(){
    $result = '';
    $foo = Yii::$app->request->post('foo');
    if(empty($foo)) {
        $result = 'foo error';
    }
    else {
        $bar = Yii::$app->request->post('bar');
        if(empty($bar)) {
            $result = 'bar error';
        }
        $result = 'ok';
    }
    return $result;
}

如上可以发现,代码嵌套层数非常多,当然你可以优化成直接 return 的方式,但下面这个呢:

function actionIndex(){
    $result = '';
    $foo = getValueByName('foo');
    if($foo === null) {
        return 'foo error';
    }
    $bar = getValueByName('bar');
    if($bar === null) {
        return 'bar error';
    }
    return 'ok';
}

function getValueByName($name){
    $value = Yii::$app->request->post($name);
    if($value === null) {
        return null;
    }
    else if(empty($value)){
        return null;
    }
    return $value;
}

如上代码,存在的问题:

  • 错误丢失:在 actionIndex 内我们只能得到的值是 null,无法判断 getValue 内部具体错误是什么;虽然可以给 getValue 方法多增加一个 $error 参数返回具体错误,但在外层也需要多做判断,无疑大大增加代码复杂度,这不是我们想要的。
  • 依赖外层判断:在 getValue 方法内如果出现错误,例如某个值不存在,而这个值可能导致后续系统运行出现 BUG;如果我们直接返回 null,那就无法保证这个错误会在外部被处理,可能在外层代码压根不会有人判断,由此引发更大的隐患。

因此我们将代码进行优化,优化后:

function actionIndex(){
    $result = 'ok';
    try{
        $foo = getValueByName('foo');
        $bar = getValueByName('bar');
    }
    catch(\Exception $e){
        $result = $e->message; // 读取异常的 message 属性
    }
    return $result;
}

function getValueByName($name){
    $value = Yii::$app->request->post($name);
    if($value === null) {
        throw new \Exception('value is null'); // 参数为 message 属性
    }
    else if(empty($value)) {
        throw new \Exception('value is empty'); // 参数为 message 属性
    }
    return $value;
}

如上,代码清爽了不少。而且既能在不改变原有函数结构的基础上,将错误信息完整地传递到外层;又可以保证异常一定会被处理,否则就会报错(例如显示 Yii 框架的错误页面)。

其实,异常应该按照类型进行区分,根据不同类型的异常做不同的处理,比较规范的做法应该如下:

function actionIndex(){
    $result = 'ok';
    try{
        $foo = getValueByName('foo');
        $bar = getValueByName('bar');
    }
    catch(\yii\base\UnknownPropertyException $e){
        $result = $e->message . 'is null';
    }
    catch(\yii\base\InvalidValueException $e){
        $result = $e->message . 'is empty';
    }
    return $result;
}

function getValueByName($name){
    $value = Yii::$app->request->post($name);
    if($value === null) {
        throw new \yii\base\UnknownPropertyException($name);
    }
    else if(empty($value)) {
        throw new \yii\base\InvalidValueException($name);
    }
    return $value;
}

我们也可以自定义异常类,不过在大多数中小型系统的实际业务场景下,略显麻烦。

更多关于 PHP 异常处理的详细介绍,参见:http://www.w3school.com.cn/php/php_exception.asp

备案号:鲁ICP备17018368号-1