Java 圈复杂度、连接条件和可读性
考虑以下方法(在Java中-请忽略内容): 我有一些计算的插件:Java 圈复杂度、连接条件和可读性,java,static-analysis,Java,Static Analysis,考虑以下方法(在Java中-请忽略内容): 我有一些计算的插件:eV(g)=5和V(g)=5——也就是说,它计算基本和常见的CC 现在,我们可以将上述方法写成: public boolean equals2(Object object) { if (this == object) { return true; } if (object == null || getClass() != object.getClass()) { return
eV(g)=5
和V(g)=5
——也就是说,它计算基本和常见的CC
现在,我们可以将上述方法写成:
public boolean equals2(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
return hashCode() == object.hashCode();
}
这个插件计算出eV(g)=3
和V(g)=3
但我如何理解CC,其值应该是相同的!CC不是计算代码行数,而是计算独立路径。因此,在一行中连接两个(如果)并不能真正减少CC。事实上,它只会让事情变得不那么可读
我说得对吗
编辑
忘了分享这个方便快捷的计算CC的小表格:从一(1)个初始(默认)值开始。对于以下各项的每次出现,添加一(1):
if
语句
while
语句
用于
语句
案例
语句
catch
语句
&&
和|
布尔运算
?:
三元运算符和?:
埃尔维斯运算符
?。
空检查运算符
编辑2
我证明了我的插件没有很好地工作,因为当我将所有内容内联到一行时:
public boolean equals(Object object) {
return this == object || object != null && getClass() == object.getClass() && hashCode() == object.hashCode();
}
它返回CC==1,这显然是错误的。无论如何,问题依然存在:CC是否减少
[A] 5->4,或
[B] 4->3
?转换此
if (hashCode() != object.hashCode()) {
return false;
}
return true;
对此
return hashCode() == object.hashCode();
很明显,即使通过你的快速桌子,CC也会减少1。第二个版本只有一条路径
对于另一种情况,虽然我们无法确切知道您的插件如何计算这些数字,但我们有理由猜测它将if(object==null | | getClass()!=object.getClass())
视为“如果非null对象的类匹配,那么…”,这是一个单一检查,因此只向CC添加一个。我认为这是一个合理的快捷方式,因为空校验可以很容易地被卷进“真实”的检查中,甚至在人脑中。
我的观点是,CC计算IDE插件的主要目的应该是鼓励您使您的代码更易于他人维护。虽然插件中有一个bug(内联的单行条件代码不是特别可维护的),但通过给开发人员更高的可读性代码评分来奖励开发人员的总体想法是值得称赞的,即使它有点不正确
关于你的最后一个问题:如果你严格考虑逻辑路径,CC为5;4如果你考虑案例,你应该考虑编写单元测试;3,如果你考虑别人快速阅读和理解你的代码是多么容易。
返回hashCode()==object.hashCode();花费0,所以你赢了1。它被认为是计算,而不是逻辑分支。
但对于第一种方法,我不知道为什么它要花费5,我计算4。 < P>就风格而言,我认为以下是最可读的:
public boolean equals(Object object) {
return this == object || (object != null && eq(this, object));
};
private static boolean eq(Object x, Object y) {
return x.getClass() == y.getClass()
&& x.hashCode() == y.hashCode(); // safe because we have perfect hashing
}
在实践中,将子类排除在相等之外可能是不对的,并且通常不能假设相等的哈希代码意味着相等的对象。。。因此,我宁愿写这样的东西:
public boolean equals(Object object) {
return this == object || (object instanceof MyType && eq(this, (MyType) object));
}
public static boolean eq(MyType x, MyType y) {
return x.id.equals(y.id);
}
它更短、更清晰,与代码一样具有可扩展性和效率,并且具有更低的圈复杂度(逻辑运算符通常不被认为是计算圈复杂度的分支)。长话短说…
您的方法是计算CC的一个很好的方法,您只需要决定您真正想要用它做什么,并根据需要进行相应的修改
对于第二个示例,CC=3和CC=5似乎都很好
说来话长……
计算CC的方法有很多种。你需要决定你的目的是什么,你需要知道你的分析有哪些局限性
McCabe最初的定义实际上是控制流图的圈复杂度(来自图论)。要计算这个,您需要有一个控制流图,这可能需要比您当前的控制流图更精确的分析
静态分析器希望快速计算度量,因此它们不分析控制流,而是计算一个复杂度度量,比如说,接近它。因此,有几种方法
例如,您可以阅读有关SonarQube的CC度量的讨论,或另一个SourceMeter如何计算McCC的示例
常见的是,这些工具像您一样计算条件语句。
但是,这些指标并不总是与独立执行路径的数量相等。。。至少,他们给出了一个很好的估计
计算CC的两种不同方法(McCabe和Myers扩展):
如果您的目标是估计测试用例的数量,那么V2就是适合您的。但是,如果您想要有一个代码理解的度量(例如,您想要识别难以维护并且应该在代码中简化的方法),V1更容易计算,对您来说已经足够了
此外,静态分析器还可以测量一些额外的复杂性指标(例如嵌套级别)。不喜欢您的表。这意味着如果(x?.y | x?.z){…}
的CC为4?不太可能。我想要2个,但我相信3个。在这种情况下,为什么空检查会添加到CC中?好吧,这是一个快速的表格:)我的方法是将所有三元运算符扩展到if结构,并拆分所有布尔条件;但这不是很快,因此有了这个表。请您扩展一下您的答案,告诉我们如何计算4,好吗?非常感谢。你说这是一个“单一检查”-但这是一个单一的逻辑检查(由人类解释)。为了确认这一点,我已将object==null
更改为object==”
(这没有意义,但会终止逻辑),答案是相同的。此外,我随后又迈出了一步
public boolean equals(Object object) {
return this == object || (object instanceof MyType && eq(this, (MyType) object));
}
public static boolean eq(MyType x, MyType y) {
return x.id.equals(y.id);
}
V_l(g) = number of decision nodes + 1
V_2(g) = number of simple_predicates in decision nodes + 1