php代码规范->如何写出规范且易于理解的项目代码-ZX版

2019年5月17日10:50:12

 前序:

目前是想到哪写到哪,后面有时间在整理成具体文章

很多时候,PHP代码风格过于自由,导致一个项目有N多种写法风格,有些人为了自己认为的技术"高",经常写一些奇奇怪怪的写法比如:

例子1,一个很简单的功能,给你写N个回调函数,导致代码可读性基本为零。,

例子2,比如 if else 不写{} 吧代码块包起来,不是所有的人用的ide都可以识别这种写法,当然php的引擎解析不会有问题

例子3,比如为了代码方便 经常写匿名函数,一层套一层,为了写法更简洁,代码无法复用,代码可读性很低

以上的说到的功能都是一些语法糖,不是所有开发语言都有,是特有写法,不通用,如果只会一种后端代码语言,可能对这块很难理解

这里会涉及到一些Java的代码习惯,为什么PHP需要设计到java代码规范,是因为java的很多代码贡献,编程思想 对整个开发社区的不可磨灭的影响

如果习惯这种代码习惯,你学习Java就很容易,java的生态是它成功的关键,现在java生态很多都支持rest api访问php都可以使用,这个后面说

 

项目代码是基于laravel 5.7做的demo代码

PSR

参看PSR的编写规范,如果完全按照PSR来编写代码,确实不错,但是,很难要求所有人的达到这个标准

中文翻译版 http://psr.phphub.org/

已经通过的标准有1,2,3,4,6,7,这些标准都是一些不错的标准,目前我遵循其中的一部分,为什么是一部分而不是全部,因为不能要求项目组所有人都强制这种代码规范

一些参考规则

https://www.w3cschool.cn/phpkfbmgf/apedj4.html

 

IDE使用

其实不限制具体使用什么ide,但是如果有可能就统一最好,不然有些代码写法在不同的ide,代码提示和代码追踪是比较麻烦的,比如使用容器和回调函数的时候,只能靠自己查找源代码

 

命名规范

建议统一使用驼峰法,变量名也是,尽量使用英文可读翻译的单词做方法名,特别是对于不喜欢写很多注释的同学

文件名称和类型名一致,不要在一个文件写多个类,方便代码阅读和管理

比如:

PurchaseInStorageService.php

代码内容:
namespace App\Service;

use App\Service\BaseService;

class PurchaseInStorageService extends BaseService {

    public $a = 0;
    public static $b = 'string';

    /**
        *
        * @param int $shopId
        * @param int $purchaseOrderId
        * @return type
         */
    public static function inStorageList(int $shopId, int $purchaseOrderId) {
        
    }

}
 
    

 

 

/**

*/

ide会自动吧当前方法的参数作为注释显示出来,方便你编写参数注释

 

这是一种比较常见写法,其实更规范和合理的代码是这样,参考代码分层

代码分层

代码分层的好处就是代码逻辑复用,当需要做一些逻辑聚集操作的时候,可以直接做,不需要在吧需要做的功能全部重写一次,直接调用以前对应的服务层

一般代码分层

控制器层Controller

服务接口层 ServiceInterface

服务实现层 Service

服务抽象层 ServiceAbstract 

模型层 Models

和控制器平级的api层

api版本控制层

计划任务层

逻辑层

其实逻辑层和服务层有些概念部分重叠,适当选取分层

 

但是这里需要注意的是PHP的抽象层和接口层是不能实例化的,相当于模板,只能继承和实现,需要使用注入的方式来实现多态

比如现在symfony也是可以做类似java类似的基于组件的最小化开发,类似spring boot开发的意思

还有一些通用的,但是在PHP用不是太常用的分层

错误处理层,相当于php的错误Handel注册处理函数,自定义错误的处理

aop层,根据规范命名的控制器,或者服务层,增加一些额外附加的功能

过滤器层,对请求的过滤或者控制

这些有一些在php出现的比较少,但是都是一些编程思想的实现,通用语各种语言中

为什么要出现接口层,又出现接口实现层,这个其实有多方面考虑,比如多态,集成多种支付的时候,实现支付接口直接去写业务逻辑,规范代码,需要根据不同的类型的支付,统一代码调用

比如

php

namespace App\Http\ServiceInterface;

interface PayServiceInterface {

    public function doPay();

    public function callBack();
}

有些人说,如果要分细一点,就分得更细,这个主要看系统设计要求和招的开发人员的水平,分层越多,理解起来越难,写的代码就越多,个人建议适当的分层即可。

 

代码规范

1,PHP项目代码最好是无BOM的UTF-8格式,减少乱码出现问题

2,MySQL数据库现在建议 utf8mb4

3,关于代码缩进,有些人习惯用空格,有些习惯用tab键

个人建议使用ide的格式化代码功能,就不需要纠结制表符在不同系统的问题了

4,PHP的关键字,必须小写,boolean值:true,false,null 也必须小写。

下面是PHP的“关键字”,必须小写:

'__halt_compiler', 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor'

其实驼峰法是java比较希望的规范,php使用下划线也是ok的,但是驼峰法习惯之后,更换语言就无压力

如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例:public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;

 

1) 获取单个对象的方法用 get 做前缀。
2) 获取多个对象的方法用 list 做前缀。
3) 获取统计值的方法用 count 做前缀。
4) 插入的方法用 save/insert 做前缀。
5) 删除的方法用 remove/delete 做前缀。
6) 修改的方法用 update 做前缀。

 

5, if else 代码块,注意使用{}吧代码标记起来,

6,switch case 注意case的值不要出现过于复杂的,不然会出现case匹配不上或者出现 1,2,3,4,5,6,7,8,9,10 这种会出现配置出问题,因为1和10匹配会出现问题,if else虽然代码看起来不优雅但是问题出的少

foreach ($iterable as $key => $value) {
 
}

 

foreach ($iterable as $key => &$value) {
 
}

这两种写法在如果是只简单吧$value 修改部分的值的时候,&引用操作效率更高,因为没有额外的中间变量,如果只是循环做成一个新数组,无没有实际差别

7,注意权限修饰符使用,一些特殊服务,比如支付,有些变量只能内部使用或者不能修改的时候,注意加上 final  和private,为了安全,虽然php的是独立进程不会出问题,但是需要养成良好的代码习惯

 

8,类中的常量所有字母都必须大写,单词间用下划线分隔

CONST ORDER_STATUS = 1;

,9,类的调用静态方法效率明显比实例化对象高,使用容器实例化对象,其实源于java的容器,因为是常驻内存所以效率很高,php也是用容器概念,其实就编程思想来说是没问题的,

但是实际效率和静态方法调用的差距,目前不太清楚,没有做过详细测试

10,请求参数安全过滤和转义

$saleOutStorageId = !empty($Request->saleOutStorageId) ? (string) htmlspecialchars(trim($Request->saleOutStorageId), ENT_QUOTES, "UTF-8") : '';

当吧参数传入服务方法的时候,这个注意请限定传入类型

例如Controller

   /**
     * 出库单提交审核
     * @param Request $Request
     * @return type
     * 
     */
    public function submitSaleOutStorage(Request $Request) {

        $saleOutStorageId = !empty($Request->saleOutStorageId) ? (string) htmlspecialchars(trim($Request->saleOutStorageId), ENT_QUOTES, "UTF-8") : '';
        try {
            SaleOutStorageService::submitSaleOutStorage(parent::$shop_id, $saleOutStorageId);
            return response()->json(['code' => 200, 'msg' => '操作成功']);
        } catch (Exception $exception) {
            return response()->json(['code' => 201, 'msg' => $exception->getMessage()]);
        }
    }
 /**
     * 出库单提交审核
     * @param int $shopId
     * @param int $saleOutStorageId
     */
    public static function submitSaleOutStorage(int $shopId, int $saleOutStorageId) {
}

注意类型限定是防止转义的一种办法,关于返回是否需要限定类型,其实可以限定,如果代码要求高的话,在方法尾部加上

public static function submitSaleOutStorage(int $shopId, int $saleOutStorageId) :array{
}

注意try catch finally的使用,注意捕捉好错误,避免前端不好有返回

try catch finally 会用是很重要的,为了代码的健壮性起了很重要的作用

11,代码逻辑拆分到原子操作

    public static function weekArray() {
        $weekArray['0'] = 'Monday';
        $weekArray['1'] = 'Tuesday';
        $weekArray['2'] = 'Wednesday';
        $weekArray['3'] = 'Thursday';
        $weekArray['4'] = 'Friday';
        $weekArray['5'] = 'Saturday';
        $weekArray['6'] = 'Sunday';
        return $weekArray;
    }

比如我要写一个一周相关操作的,所以一周的函数就需要提出来,方便其他人操作,不要全部写到一个方法里面,方便复用

比如我要做某个稍微复杂的判断比如  判断是否是主力合约 ,如果是16以后就是下月是主力合约,如果小于16号是本月是主力合约,返回当月主力合约月份,格式必须是1805这种的

那么这个判断最好拆分到具体的某个方法,而不是写在一起

下面代码是以前的php代码,PHP开发人员看完全没问题,使用下划线最为变量和方法的命名,但是其他语言来,就基本两回事了

    /*
     * 判断是否是主力合约 ,如果是16以后就是下月是主力合约,如果小于16号是本月是主力合约,返回当月主力合约月份,格式必须是1805这种的
     */

    public static function is_main_force($month = '') {
        if (empty($month)) {
            self::$msg = '月份不能为空';
            return false;
        }
        $res = self::is_current_month_over();
        $date = date("y-m-01", time());
        if ($res) {
            //如果大于16号
            $date = date("ym", strtotime("$date  +2 month"));
        } else {
            //小于16号
            $date = date("ym", strtotime("$date  +1 month"));
        }
        if ($month == $date) {
            return true;
        } else {
            return FALSE;
        }
    }
    //当前月是否已过16号
    public static function is_current_month_over() {
        $day = date("d", time());
        if ((int) $day >= 16) {
            return true;
        }
        return FALSE;
    }

 这里还有值得注意的事情就是,PHP数值对比的时候,虽然会自动转换,但是最好是自己手动转换,防止数据异常转换导致无法捕捉的异常bug

 

关于代码拆分和封装是很重要的一个编程思想,能否写出简单,可读性好的代码的关键,这个不是你说你想写成这样就可以写成这样的,需要不断的主动去使用这种写代码的思维写出来,或者模仿成习惯

 

12,不要重复造轮子

比如很多优秀库在github都有,或者本身就在框架自身里面,不需要你自己在去写一套,第一你临时写的思考的不是那么完善,第二如果你不是按照设计模式的思维编写的功能,复用性很差,甚至只能你自己使用

比如laravel有很多不错的第三方扩展库,https://learnku.com/laravel/projects/filter/laravel-library  ,如果实在没有请一定三思而行,多去问问qq群或者社区论坛,不要过于着急的定具体方案,说不定就可以了解其他不错的方案或者库

 

13,SPL的使用

spl里面提供很多好用的东西,比如迭代器,一些数据结构,队列,堆

 这里要着重说一下,会用和不会用spl的说明你对数据结构的认识差距很大,一定要会用

14,try catch finally

 这个是一个标准代码的,友好代码的关键,习惯抛出异常,才能写出健壮的友好的代码

class zx
{

    public static function test(int $tt = 0)
    {
        if (empty($tt) || $tt == 0) {
            throw new Exception("参数错误");
        }
        return 999;
    }

    // service1
    public static function loginService(string $name = '', string $password = '')
    {
        try {
            if (empty($name) || empty($password)) {
                throw new Exception("账号密码不能为空");
            }

            if ($name == 'name' && $password == 'password') {
                return 'OK';
            } else {
                throw new Exception("账号密码错误");
            }
        } catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }

    // service2
    public static function newLoginService(string $name = '', string $password = '')
    {
        try {
            if (empty($name) || empty($password)) {
                throw new Exception("账号密码不能为空");
            }

            if ($name == 'name' && $password == 'password') {
                return 'OK';
            } else {
                throw new Exception("账号密码错误");
            }
        } catch (Exception $e) {
            throw $e;
        }
    }
}

try {
    echo zx::test(0);
} catch (Exception $e) {
    echo $e->getMessage();
} finally {
    // 如果有流操作,或者必须操作的东西,注意在这里关掉流,或者处理必要操作的东西,虽然php有很有效的垃圾回收机制
}

建议service1的写法,直接把异常扔出去  throw $e; 有可能出现提前报错,这个不像java的throws  直接往上级调用者仍异常,有时候出现捕捉的异常不能按你的设计的返回数据格式

 

15,关于一些常规英文单词的缩写,如果你的英文水平不行,可以使用翻译工具来翻译,缩写的一些单词注意一定是常用单词,比如message 简写成msg,opreation简写成op,这样变量或者方法名会短一些

比如代码的格式化,缩短代码的长度让代码读起来不会太冗长

return ['msg'=>'OK','code'=>200,'data'=>$data];

建议不要写成

return [
    'msg' => 'OK',
    'code' => 200,
    'data' => $data
];

如果常见是4-5个返回数据标准,一个简单控制器就十几行代码,会让整个文件的代码变得特别长

 

16,逻辑代码依赖层不要过多

A->B->C->D,建议不要多余三层,因为这样,一旦底层代码有一层代码是需要经常修改和变化的代码,那你的建立在这个上面的代码也会需要经常修改,

建议独立,或者协商比如限定返回和参数,过程不管,只要调用和返回格式不变你的代码就不会容易出错,但是逻辑可能出错,减少逻辑报错,但是还是会有逻辑错误

所以逻辑依赖层不建议过多

17,变量使用之前建议申明好类型和初始化,不要随便操作和使用公共变量

虽然php变量类型由php引擎自己判断,但是为了执行代码效率,减少由于意外赋值或者类型改变引起的异常

 

关于公共变量,有些同学喜欢在__contract里面声明一个类里面绝大多数方法可能使用到的变量,其实这个是个不好的习惯,如果有人因为某些特殊需求

经过调用的时候,修改了公共变量,首先这个问题引发的问题不找查找,其次

class zx
{
    private $resquest;
    
    public function __construct(Resquest $resquest){
        $this->$resquest = $resquest;
    }

比如这样,其实现在很多框架都是容器工厂初始化实例化调用类,效率并不是什么问题

建议的写法是哪里用到那里调用,减少公共资源的内存的消耗,特别某些公共方法或者变量特别大的时候

 

18,在多重循环中,应将最忙的循环放在最内层。



示例:如下代码效率不高。
for (row = 0; row < 100; row++)
{
 for (col = 0; col < 5; col++)
 {
 sum += a[row][col];
 }
}
可以改为如下方式,以提高效率。
for (col = 0; col < 5; col++)
{
 for (row = 0; row < 100; row++)
 {
 sum += a[row][col];
 }
}

尽量循环中不要操作数据库或者任何流,比如文件流,字节流等

19,关于错误抑制符@的使用

虽然它会抑制一些特殊的错误,个人建议是捕捉它们而不是抑制错误,因为有些额外的错误很难通过经验判断出来

20,有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回 滚事务。

21,在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制, 如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。

说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其 它用户,并造成短信平台资源浪费

还有一点就是调用第三接口的时候一定要做好调用接口发送的参数,和返回参数打下日志,虽然会很多,这个是作为排查错误的重要依据

22,关于局部变量的使用

如果有多个foreach循环k,v需要使用不同的

如果有内部变量使用请先申明,不然有时候会出现异常

        $data = [];
        foreach ($array as $k1 => $v1) {
            foreach ($v1 as $k2 => $v2) {
                foreach ($v2 as $k3 => $v3) {
                    $data[] = $v3;
                }
            }
        }

 

 

 

mysql数据库设计和使用规范

1,表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint ( 10 表示是,99 表示否)。 说明:任何字段如果为非负数,必须是 unsigned。

正例:表达逻辑删除的字段名 is_deleted,10 表示未删除,99 表示删除,不建议在数据库0,因为不同的api对0这个特殊解析都有些许不同,特别是对接ios和java app的时候

2,表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只 出现数字。

数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。

说明:MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库 名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。

 有些java开发者习惯数据库也使用驼峰命名,这个是个习惯问题,建议使用小写+下划线风格

如果你在php对接数据库,php使用了驼峰,而数据库又使用,会使代码看起来很奇怪,为什么代码风格不一致,目前未发现laravel的模型可以绑定字段 entity实体

3,【强制】小数类型为 decimal,禁止使用 float 和 double。 说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不 正确的结果。

如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。钱的话也是decimal,注意长度和小数点,因为小数掉后的数据会四舍五入,所以在存到数据库之前做好四舍五入

不然会出现一分钱问题

4,varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长 度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索 引效率。

5,表必备三字段:id, gmt_create, gmt_modified。 说明:其中 id 必为主键,类型为 unsigned bigint、单表时自增、步长为 1。

gmt_create, gmt_modified 的类型均为 date_time 类型,前者现在时表示主动创建,后者过去分词表示被 动更新。

6,表的命名最好是加上“业务名称_表的作用”。

正例:alipay_task / force_project / trade_config

7,合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检 索速度。 正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。

对象 年龄区间 类型 字节 表示范围

人 150 岁之内 unsigned tinyint 1 无符号值:0 到 255

龟 数百岁 unsigned smallint 2 无符号值:0 到 65535

恐龙化石 数千万年 unsigned int 4 无符号值:0 到约 42.9 亿

太阳 约 50 亿年 unsigned bigint 8 无符号值:0 到约 10 的 19 次方

8,

 

索引相关

1,超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时, 保证被关联的字段需要有索引。 说明:即使双表 join 也要注意表索引、SQL 性能。

2,在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度即可。

说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分  度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度 来确定。

3,页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。 说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索 引。

4,如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合 索引的一部分,并且放在索引组合顺序的最后,

避免出现 file_sort 的情况,影响查询性能。

正例:where a=? and b=? order by c; 索引:a_b_c

反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。

5,建组合索引的时候,区分度最高的在最左边。 正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即 可。

说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>? and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列

6,】创建索引时避免有如下极端误解:

1)宁滥勿缺。认为一个查询就需要建一个索引。

2)宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。

3)抵制惟一索引。认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。

7,

 

优化项目代码

 

设计模式,数据结构,算法使用

 

redis缓存使用

 

socket框架

 

微服务

 

转载于:https://www.cnblogs.com/zx-admin/p/10880150.html

你可能感兴趣的:(php代码规范->如何写出规范且易于理解的项目代码-ZX版)