———————————————————————————————————————
圈复杂度(Cyclomatic Complexity)是衡量计算机程序复杂程度的一种措施。它根据程序从开始到结束的线性独立路径的数量计算得来的。
圈复杂度越高,代码就越难复杂难维护。坑就越大。。。
例如下面这个函数,圈复杂度为1,意味着代码只有一条路径。:
def add(a, b):
return a + b
对于有一条分支的代码,它的圈复杂度为 2 ,比如下面递归计算阶乘的代码:
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
它的计算方法很简单:
计算公式1:V(G)=E-N+2P。其中,E表示控制流图中边的数量,N表示控制流图中节点的数量,P图的连接组件数目(图的组件数是相连节点的最大集合)。因为控制流图都是连通的,所以P为1.
圈复杂度 | 代码状况 | 可测性 | 维护成本 |
---|---|---|---|
1-10 | 清晰、结构化 | 高 | 低 |
11-20 | 复杂 | 中 | 中 |
21-30 | 非常复杂 | 低 | 高 |
>30 | 不可读 | 不可测 | 非常高 |
———————————————————————————————————————
在 Python 中可以使用 mccabe 包测量程序的圈复杂度。
只需要很简单的一行命令即可安装mccabe
pip install mccabe
运行下面这行命令,就可以检测test.py的圈复杂度
python -m mccabe --min 5 test.py
其中 --min 5 是指最小允许的圈复杂度,高于5的圈复杂度则输出出来,如下图:
第一个输出的结果是,91行的roundRobin函数,复杂度为7.
除了mccabe,现在市场上也有很多检测圈复杂度工具
工具 | 类型 | 系统平台 | 扫描语言 |
---|---|---|---|
PMD/Checkstyle | 免费 | Windows/Linux/Mac | Java,JS |
OClint | 免费 | Mac | OC |
Coverity | 商业 | Windows/Linux/Mac | C/C++,Java,C#,OC/C++,JS,Python,Ruby,PHP |
SourceMonitor | 免费 | Windows | C/C++,C#,VB.NET,Java,Delphi,VB6,HTML |
CCM | 免费 | Windows | JS,C/C+,C# |
HFCCA | 免费 | Windows/Linux/Mac | C/C++,OC |
Lizard | 免费 | Windows/Linux/Mac | C/C++,Java,C#,JS,OC/C++,Swift,Python,Ruby,TTCN-3,PHP,Scala,GDScript |
———————————————————————————————————————
把子程序的一部分提取成另一个子程序,不会降低整个程序的复杂度,只是把决策点移到其他地方,但是这样做可以降低你在同一时间必须关注的复杂度水平。由于重点是要降低你需要在头脑中同时考虑的项目的数量,所以降低一个给定程序的复杂度是有价值的。
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
}
if($str == 'China'){
$result = '中国人';
}
else if($str == 'US'){
$result = '美国人';
}
else if($str == 'France'){
$result = '法国人';
}
变成这样:
$people = [
'China' => '中国人',
'US' => '美国人',
'France' => '法国人'
];
$result = $people[$str];
if((条件1 && 条件2) || !条件1){
return true;
}
else{
return false;
}
变成这样:
if(条件1 && !条件2){
return false;
}
return true;
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);
}
if($x < 1) return 0;
if($y > 10) return 0;
if($z != 0) return 0;
变成这样:
if(get_result($x,$y,$z)) return 0;
$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;
}
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";
}
}
$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;
}
if($name == 'width'){
$width = $value;
}
else if ($name == 'height'){
$height = $value;
}
变成这样:
function setWidth($value){
$width = $value;
}
function setHeight($value){
$height = $value;
}
参考视频:谷歌:简洁代码之道