phpmd
composer global require "phpmd/phpmd"
## 获取phpmd安装路径
composer global config bin-dir --absolute
phpstorm配置md路径
如果遇到上述验证失败问题,可以先验证bat文件是否可正常运行
在bin文件夹中运行cmd命令窗口
输入命令,程序能正常运行,环境变量中的php命令能正常运行
推断是配置环境变量没重启部分不起效问题
重启后再次验证,结果成功
设置规则
规则集
Code Size Rules: 代码尺寸规则集.
Controversial Rules: 有争议的代码规则.
Design Rules: 软件设计的相关问题规则集.
Naming Rules: 名称太长,规则太短,等等规则集.
Unused Code Rules: 找到未使用的代码的规则集.
规则集所在位置
官网规则简介
当前规则集
规则集和每个规则集中包含的规则的列表。
- 干净代码规则:干净代码规则集包含强制实施干净代码库的规则。这包括来自 SOLID 和对象健美操的规则。
- 代码大小规则:代码大小规则集包含一组规则,用于查找与代码大小相关的问题。
- 有争议的规则:此规则集包含一组有争议的规则。
- 设计规则:设计规则集包含查找软件设计相关问题的规则集合。
- 命名规则:命名规则集包含有关名称的规则集合 - 太长、太短等。
- 未使用的代码规则:未使用的代码规则集包含查找未使用代码的规则集合。
干净代码规则
- BooleanArgumentFlag:布尔标志参数是违反单一责任原则 (SRP) 的可靠指标。可以通过将布尔标志中的逻辑提取到其自己的类或方法中来解决此问题。
- ElseExpression:带有 else 分支的 if 表达式基本上是不必要的。您可以重写条件,使 else 子句不是必需的,并且代码更易于阅读。为此,请使用早期返回语句,尽管您可能需要将代码拆分为几个较小的方法。对于非常简单的赋值,您也可以使用三元运算。
- 静态访问:静态访问会导致对其他类的不可交换的依赖关系,并导致难以测试的代码。避免不惜一切代价使用静态访问,而是通过构造函数注入依赖项。唯一可以接受静态访问的情况是用于工厂方法时。
- IfStatementAssignment:if 子句等中的赋值被视为代码异味。PHP 中的赋值返回正确的操作数作为结果。在许多情况下,这是预期的行为,但可能会导致许多难以发现的错误,尤其是当正确的操作数可能导致零、null 或空字符串时。
- DuplicatedArrayKey:为数组文本中的同一键定义另一个值会覆盖以前的键/值,这实际上使其成为未使用的代码。如果从一开始就知道键将具有不同的值,则定义第一个键通常没有意义。
- MissingImport:通过 use 语句导入文件中的所有外部类,使它们清晰可见。
- 未定义变量:检测何时使用以前未定义的变量。
- ErrorControlOperator:如果可能,应避免错误抑制,因为它不仅会抑制您尝试停止的错误,而且还会抑制您没有预料到会发生的错误。此外,它会减慢代码的执行速度。考虑更改 error_reporting() 级别和/或设置自己的错误处理程序。
代码大小规则
- 圈复杂度:复杂度由方法中的决策点数加上方法条目的决策点数决定。决策点是“如果”、“同时”、“对于”和“案例标签”。通常,1-4 表示低复杂度,5-7 表示中等复杂度,8-10 表示高复杂度,11+ 表示非常高的复杂度。
- NPathComplexity:方法的 NPath 复杂度是通过该方法的非循环执行路径的数量。阈值 200 通常被认为是应采取措施降低复杂性的点。
- 过度方法长度:违反此规则通常表示该方法执行过多操作。尝试通过创建帮助程序方法并删除任何复制/粘贴的代码来减小方法大小。
- ExoveriveClassLength:长类文件表示该类可能尝试执行过多操作。尝试将其分解,并将大小减小到可管理的大小。
- ExoveriveParameterList:长参数列表可以指示应创建一个新对象来包装大量参数。基本上,尝试将参数组合在一起。
- ExoverivePublicCount:类中声明的大量公共方法和属性可能表明可能需要分解该类,因为需要加大工作量来彻底测试它。
- TooManyFields:具有太多字段的类可以重新设计为具有较少的字段,可能是通过某些信息的嵌套对象分组。例如,具有城市/州/邮政编码字段的类可以改为具有一个地址字段。
- TooManyMethods:具有太多方法的类可能是重构的一个很好的嫌疑人,以降低其复杂性并找到一种拥有更细粒度对象的方法。默认情况下,它忽略以“get”或“set”开头的方法。在 PHPMD 10.25 中,默认值从 2 更改为 3。
- TooManyPublicMethods:一个具有太多公共方法的类可能是重构的一个很好的嫌疑人,以降低其复杂性并找到一种拥有更细粒度对象的方法。默认情况下,它忽略以“get”或“set”开头的方法。
- ExoveriveClassComplexity:类的加权方法计数 (WMC) 很好地指示了修改和维护此类所需的时间和精力。WMC 指标定义为类中声明的所有方法的复杂性之和。大量方法还意味着此类对派生类具有更大的潜在影响。
有争议的规则
- 超全局变量:直接访问超全局变量被认为是一种不好的做法。例如,这些变量应封装在框架提供的对象中。
- CamelCaseClassName:使用CamelCase表示法命名类被认为是最佳实践。
- CamelCasePropertyName:使用驼峰大小写表示法命名属性被认为是最佳做法。
- CamelCaseMethodName:使用camelCase表示法命名方法被认为是最佳实践。
- CamelCaseParameterName:使用CamelCase表示法命名参数被认为是最佳实践。
- CamelCaseVariableName:使用camelCase表示法命名变量被认为是最佳实践。
设计规则
- ExitExpression:常规代码中的退出表达式是不可测试的,因此应避免使用。考虑将 exit-expression 移动到某种启动脚本中,其中错误/异常代码返回到调用环境。
- 评估表达式:评估表达式是不可测试的,存在安全风险和不良做法。因此,应避免使用。考虑将 eval 表达式替换为常规代码。
- GotoStatement:Goto 使代码更难阅读,并且几乎不可能理解使用此语言结构的应用程序的控制流。因此,应避免使用。考虑将 Goto 替换为更易于阅读的常规控制结构和单独的方法/函数。
- 子级数量:子级数量过多的类是类层次结构不平衡的指标。应考虑重构此类层次结构。
- 继承深度:具有许多父级的类是不平衡和错误的类层次结构的指标。应考虑重构此类层次结构。
- CouplingBetweenObjects:具有太多依赖项的类会对类的多个质量方面产生负面影响。这包括稳定性、可维护性和可理解性等质量标准
- DevelopmentCodeFragment:像var_dump(),print_r()等函数。通常只在开发过程中使用,因此生产代码中的此类调用是一个很好的指标,表明它们只是被遗忘了。
- EmptyCatchBlock:通常空的try-catch是一个坏主意,因为你正在默默地吞下错误条件,然后继续执行。有时这可能是正确的做法,但通常这表明开发人员看到了异常,不知道该怎么做,因此使用空捕获来使问题静音。
- CountInLoopExpression:在循环表达式中使用count/sizeof被认为是不好的做法,并且是 许多错误,尤其是当循环操作数组时,因为每次迭代都会发生计数。
命名规则
- 长类名:检测何时使用过长的名称声明类或接口。
- ShortClassName:检测类或接口的名称何时非常短。
- 短变量:检测字段、局部或参数的名称何时非常短。
- 长变量:检测何时使用长名称声明字段、形式变量或局部变量。
- 短方法名称:检测何时使用非常短的方法名称。
- ConstructorWithNameAsEnclosingClass:构造函数方法不应与封闭类同名,请考虑使用 PHP 5 __construct 方法。
- 常量命名约定:类/接口常量名称应始终以大写形式定义。
- BooleanGetMethodName:查找名为“getX()”的方法,其中“boolean”作为返回类型。约定是将这些方法命名为“isX()”或“hasX()”。
未使用的代码规则
- 未使用的私有字段:检测私有字段何时声明和/或分配值,但未使用。
- UnusedLocalVariable:检测何时声明和/或分配局部变量,但未使用。
- 未使用的私有方法:未使用的私有方法检测私有方法何时声明但未使用。
- UnusedFormalParameter:避免将参数传递给方法或构造函数,然后不使用这些参数。
备注
本文档基于规则集 xml 文件,该文件取自 PMD 项目的原始源。这意味着此页面上的大部分内容都是PMD社区及其贡献者的智力工作,而不是PHPMD项目的智力工作。
上述规则摘取翻译至官网
CyclomaticComplexity 圈复杂度
1、圈复杂度的概念
圈复杂度(Cyclomatic complexity,CC)也称为条件复杂度,是一种衡量代码复杂度的标准,其符号为V(G)。
麦凯布最早提出一种称为“基础路径测试”(Basis Path Testing)的软件测试方式,测试程序中的每一线性独立路径,所需的测试用例个数即为程序的圈复杂度。
圈复杂度可以用来衡量一个模块判定结构的复杂程度,其数量上表现为独立路径的条数,也可理解为覆盖所有的可能情况最少使用的测试用例个数。
圈复杂度可应用在程序的子程序、模块、方法或类别。
1.1、圈复杂度与出错风险
程序的可能错误和高的圈复杂度有着很大关系,圈复杂度最高的模块和方法,其缺陷个数也可能最多。
圈复杂度大说明程序代码的判断逻辑复杂,可能质量低,且难于测试和维护。
一般来说,圈复杂度大于10的方法存在很大的出错风险。
1.2、圈复杂度与测试
测试驱动的开发 与 较低圈复杂度值 之间存在着紧密联系。
因为在编写测试用例时,开发人员会首先考虑代码的可测试性,从而倾向编写简单的代码(因为复杂的代码难以测试)。
一个好的测试用例设计经验是:创建数量与被测代码圈复杂度值相等的测试用例,以此提升测试用例对代码的分支覆盖率。
2、圈复杂度的计算方法
圈复杂度有两种计算方法:点边计算法和节点判定法。
2.1、点边计算法
圈复杂度由程序的控制流图来计算:有向图的节点对应程序中个别的代码,而若一个程序运行后会立刻运行另一代码,则会有边连接另一代码对应的节点。
如上图所示,E表示控制流图中边的数量,N表示控制流图中节点的数量。
圈复杂度的计算公式为:V(G) = E - N + 2
2.2、节点判定法
圈复杂度的计算还有另外一种更直观的方法,因为圈复杂度所反映的是“判定条件”的数量,所以圈复杂度实际上就是等于判定节点的数量再加上1。
对应的计算公式为:V (G) = P + 1
其中 P 为判定节点数,常见的判定节点有:
- if 语句
- while 语句
- for 语句
- case 语句
- catch 语句
- and 和 or 布尔操作
- ? : 三元运算符
对于多分支的 case 结构或 if - else if - else 结构,统计判定节点的个数时需要特别注意:必须统计全部实际的判定节点数,也即每个 else if 语句,以及每个 case 语句,都应该算为一个判定节点。
下面代码的圈复杂度为:
1(while) + 1(while) + 1(if) + 1 = 4
void sort(int *A)
{
int i = 0;
int n = 5;
int j = 0;
while (i < (n - 1))
{
j = i + 1;
while (j < n)
{
if (A[i] < A[j])
{
swap(A[i], A[j]);
}
}
i = i + 1;
}
}
降低圈复杂度方法
1.提炼函数:
function test($number){
if($number < self::MIN_NUMBER)
{
$number = self::MIN_NUMBER;
}
for($i = 0; $i < $number; $i++){
//some code
}
}
可以替换成下面这种模式:
function test($number){
$number = getMin($number);
for($i = 0; $i < $number; $i++){
//some code
}
}
function getMin($number){
if($number < self::MIN_NUMBER){
return self::MIN_NUMBER;
}
return $number
}
2.替换算法(把复杂算法替换为另一个更清晰的算法):
if($str == 'China'){
$result = '中国人';
}
else if($str == 'US'){
$result = '美国人';
}
else if($str == 'France'){
$result = '法国人';
}
变成这样:
$people = [
'China' => '中国人',
'US' => '美国人',
'France' => '法国人'
];
$result = $people[$str];
3.逆向表达(调换条件表达顺序达到简化复杂度):
if((条件1 && 条件2) || !条件1){
return true;
}
else{
return false;
}
变成这样:
if(条件1 && !条件2){
return false;
}
return true;
4.分解条件(对复杂条件表达式(if、else)进行分解并提取成独立函数):
if(do_some_1($number) || do_some_2($number)){
$number = $number.$someStr1.$someStr2.'123456789';
}
else{
$number = $number.$someStr3.$someStr4.'123456789';
}
变成这样:
if(do_some_fun($number)){
$number = do_some_fun1($number);
}
else{
$number = do_some_fun2($number);
}
5.合并条件(将这些判断合并为一个条件式,并提取成独立函数):
if($x < 1) return 0;
if($y > 10) return 0;
if($z != 0) return 0;
变成这样:
if(get_result($x,$y,$z)) return 0;
6.移除控制标记(可以使用break和return取代控制标记。):
$bool = false;
foreach($arrs as $arr){
if(!$bool){
if($arr == 1){
someFunction();
$bool = true;
}
if($arr == 2){
someFunction();
$bool = true;
}
}
}
变成这样:
foreach($arrs as $arr){
if($arr == 1 || $arr == 2){
someFunction();
}
break;
}
7.以多态取代条件式(将整个条件式的每个分支放进一个子类的重载方法中,然后将原始函数声明为抽象方法。由于php是弱类型语言,这里体现的有点模糊):
switch ($cat){
case ‘fish’:
eatFish();
case ‘moss’:
eatMoss();
}
function eatFish() {
echo "Whale eats fish";
}
function eatMoss() {
echo "Whale eat moss";
}
变成这样:
interface Eat {
function eatFish();
function eatMoss();
}
class Whale implements Eat {
public function eatFish() {
echo "Whale eats fish";
}
public function eatMoss() {
echo "Whale eat moss";
}
}
8.参数化方法(建立单一函数,以参数表达那些不同的值):
$result = min(lastUsage(), 100) * 0.03;
if(lastUsage() > 100){
$result += (min(lastUsage(), 200) - 100) * 0.05;
}
变成这样:
$result = getMin(0,100) * 0.03;
$result += getMin(100,200) * 0.03;
function getMin($start, $end){
if(lastUsage() > $start){
return (min(lastUsage(),$end) - $start);
}
return 0;
}
9.明确函数取代参数(针对该参数的每一个可能值,建立一个独立函数):
if($name == 'width'){
$width = $value;
}
else if ($name == 'height'){
$height = $value;
}
变成这样:
function setWidth($value){
$width = $value;
}
function setHeight($value){
$height = $value;
}