我该如何降低圈复杂度
how could I reduce the cyclomatic complexity?
每当我整理一段正在处理的代码时,我都会得到This function's cyclomatic complexity is too high. (7)
。但我有点困惑,我怎么能用这种方式重写它,让它发挥作用。
这将是一个不断抛出信息的函数:
function () {
var duration = +new Date() - start.time,
isPastHalf = Number(duration) < 250 && Math.abs(delta.x) > 20 || Math.abs(delta.x) > viewport / 2,
direction = delta.x < 0;
if (!isScrolling) {
if (isPastHalf) {
if (direction) {
this.close();
} else {
if (this.content.getBoundingClientRect().left > viewport / 2 && pulled === true) {
this.close();
return;
}
this.open();
}
} else {
if (this.content.getBoundingClientRect().left > viewport / 2) {
if (this.isEmpty(delta) || delta.x > 0) {
this.close();
return;
}
this.open();
return;
}
this.close();
}
}
}
我想听听一些关于如何以这种方式构建代码以避免这种情况的建议。
代码中只有两个操作,但条件太多了。在条件中使用单个if-else语句和布尔运算符。如果这是不可能的,你至少可以
- 删除空行以在一个屏幕页面上获得完整的逻辑
- 添加一些关于分支机构正在做什么(以及为什么)的评论
- 避免提前退货
以下是您的简化功能:
var duration = +new Date() - start.time,
isPastHalf = Number(duration) < 250 && Math.abs(delta.x) > 20 || Math.abs(delta.x) > viewport / 2,
isFarRight = this.content.getBoundingClientRect().left > viewport / 2,
direction = delta.x < 0;
if (!isScrolling) {
if (isPastHalf) {
if (direction)
this.close();
else {
if (isFarRight && pulled)
this.close();
else
this.open();
}
} else {
if (isFarRight) {
// Looks like the opposite of `direction`, is it?
if (this.isEmpty(delta) || delta.x > 0)
this.close();
else
this.open();
} else
this.close();
}
}
和缩写:
var duration = +new Date() - start.time,
isPastHalf = Number(duration) < 250 && Math.abs(delta.x) > 20 || Math.abs(delta.x) > viewport / 2,
isFarRight = this.content.getBoundingClientRect().left > viewport / 2,
direction = delta.x < 0,
undirection = this.isEmpty(delta) || delta.x > 0;
if (!isScrolling) {
if ( isPastHalf && ! direction && !(isFarRight && pulled)
|| !isPastHalf && !undirection && isFarRight )
this.open();
else
this.close();
}
首先,函数可以得到三个结果:什么都不做、调用this.close()
或调用this.open()
。因此,理想情况下,生成的函数只有一个if语句,用于确定使用哪个结果。
下一步是将所有布尔代码提取到变量中。例如var leftPastCenter = this.content.getBoundingClientRect().left > viewport / 2
。
最后,使用布尔逻辑来逐步简化它。
以下是我的做法:
首先,提取所有布尔变量:
function () {
var duration = +new Date() - start.time,
isPastHalf = Number(duration) < 250 && Math.abs(delta.x) > 20 || Math.abs(delta.x) > viewport / 2,
direction = delta.x < 0,
leftPastCenter = this.content.getBoundingClientRect().left > viewport / 2,
positiveDelta = this.isEmpty(delta) || delta.x > 0,
isPulled = pulled === true; // I'll assume the test is needed rather than just using pulled.
if (!isScrolling) {
if (isPastHalf) {
if (direction) {
this.close();
} else {
if (leftPastCenter && isPulled) {
this.close();
return;
}
this.open();
}
} else {
if (leftPastCenter) {
if (positiveDelta) {
this.close();
return;
}
this.open();
return;
}
this.close();
}
}
}
最容易退出的部分是意识到,如果isScrolling
是真的,什么都不会发生。这立即消除了一个层次的嵌套:
// above same
if (isScrolling) { return; }
if (isPastHalf) {
if (direction) {
this.close();
} else {
if (leftPastCenter && isPulled) {
this.close();
return;
}
this.open();
}
} else {
if (leftPastCenter) {
if (positiveDelta) {
this.close();
return;
}
this.open();
return;
}
this.close();
}
}
现在来看调用this.open()
的情况。如果isPastHalf
为真,则仅在!direction
和!(leftPastCenter && isPulled)
时调用this.open()
。如果isPastHalf
为false,则仅当leftPastCenter
和!positiveDelta
:时调用this.open()
// above same
if (isScrolling) { return; }
if (isPastHalf) {
if (!direction && !(leftPastCenter && isPulled)) {
this.open();
} else {
this.close();
}
} else {
if (leftPastCenter && !positiveDelta) {
this.open();
} else {
this.close();
}
}
翻转if(所以this.close()
是第一位的),使代码更整洁,并给出了我的最终版本:
function () {
var duration = +new Date() - start.time,
isPastHalf = Number(duration) < 250 && Math.abs(delta.x) > 20 || Math.abs(delta.x) > viewport / 2,
direction = delta.x < 0,
leftPastCenter = this.content.getBoundingClientRect().left > viewport / 2,
positiveDelta = this.isEmpty(delta) || delta.x > 0,
isPulled = pulled === true; // I'll assume the test is needed rather than just using pulled.
if (isScrolling) { return; }
if (isPastHalf) {
if (direction || (leftPastCenter && isPulled)) {
this.close();
} else {
this.open();
}
} else {
if (!leftPastCenter || positiveDelta) {
this.close();
} else {
this.open();
}
}
}
在不了解你的代码库的情况下,我很难做更多的事情。需要注意的一点是,direction
和我的新变量positiveDelta
几乎相同——您可以删除positiveDelta
,只使用direction
。此外,direction
不是布尔值的好名称,类似movingLeft
的名称会更好。
实际上,所有这些return
语句都混淆了问题,但它们为解决方案提供了提示。
if (direction) {
this.close();
} else {
if (this.content.getBoundingClientRect().left > viewport / 2 && pulled === true) {
this.close();
return; // We'll never `this.open()` if this is true anyway, so combine the booleans.
}
this.open();
}
怎么样:
if (direction || (this.content.getBoundingClientRect().left > viewport / 2 && pulled === true)) {
this.close();
} else {
this.open();
}
至于:
if (this.content.getBoundingClientRect().left > viewport / 2) {
if (this.isEmpty(delta) || delta.x > 0) {
this.close();
return; // Combine the booleans!
}
this.open();
return;
}
简化:
if ((this.isEmpty(delta) || delta.x > 0) || !this.content.getBoundingClientRect().left > viewport / 2) {
this.close();
} else {
this.open();
}
(旁白:原来的帖子省略了一个大括号。如果你(OP)打算这个功能在你的帖子之后继续,那么这个答案是错误的(但你应该更清楚)
结果:我们消除了两个(重复的)决策:
function () {
var duration = +new Date() - start.time,
isPastHalf = Number(duration) < 250 && Math.abs(delta.x) > 20 || Math.abs(delta.x) > viewport / 2,
direction = delta.x < 0;
if (!isScrolling) {
if (isPastHalf) {
if (direction || (this.content.getBoundingClientRect().left > viewport / 2 && pulled === true)) {
this.close();
} else {
this.open();
}
} else {
if ((this.isEmpty(delta) || delta.x > 0) || !this.content.getBoundingClientRect().left > viewport / 2) {
this.close();
} else {
this.open();
}
}
}
}
Bergi已经给出了正确的答案,但对我的口味来说仍然太复杂了。由于我们没有使用fortran77,我认为我们最好使用提前返回。此外,可以通过引入额外的变量来进一步澄清代码:
function doSomething(isScrolling, start, delta, viewport) {
if (isScrolling) return;
// In javascript, values that do not
// change after creation should be
// declared with const.
const duration = +new Date() - start.time;
const isPastHalf = Number(duration) < 250 && Math.abs(delta.x) > 20 || Math.abs(delta.x) > viewport / 2;
const isFarRight = this.content.getBoundingClientRect().left > viewport / 2;
// I'm not sure if my variable names reflect the actual case, but that's
// exactly the point. By choosing the correct variable names for this,
// anybody reading the code can immediatly comprehend what's happening.
const isMovingToLeft = delta.x < 0;
const isMovedPastEnd = isPastHalf && !isMovingToLeft && !(isFarRight && pulled);
const isMovedBeforeStart = !isPastHalf && isMovingToLeft && isFarRight;
if (isMovedPastEnd || isMovedBeforeStart) {
this.open();
else
this.close();
}
}
我更喜欢下面这样一个简单且嵌套较少的代码:
function()
{
var duration = +new Date() - start.time,
isPastHalf = Number(duration) < 250 && Math.abs(delta.x) > 20 || Math.abs(delta.x) > viewport / 2,
direction = delta.x < 0;
if (isScrolling)
{
return;
}
if (isPastHalf)
{
if (direction)
{
this.close();
return;
}
if (this.content.getBoundingClientRect().left > viewport / 2 && pulled == = true)
{
this.close();
return;
}
this.open();
return;
}
if (this.content.getBoundingClientRect().left > viewport / 2)
{
if (this.isEmpty(delta) || delta.x > 0)
{
this.close();
return;
}
this.open();
return;
}
this.close();
return;
}
- 在Redux中,我应该在哪里编写复杂的异步流
- 如何降低JavaScript中Mouseover函数的资源密集度
- JavaScript 访问对象属性的运行时复杂度 O(?) 是多少?
- 圈复杂度路径很重要
- javascript的array.indexOf的时间复杂度是多少?
- HTML DOM 查找的时间复杂度是多少?
- 如何降低这段代码的圈复杂度
- 我该如何降低圈复杂度
- 这个算法的时间复杂度是多少
- JavaScript中parseInt()的时间复杂度是多少?
- JavaScript's array.length的时间复杂度
- 这个NumberComplement函数的时间复杂度是多少?
- 不能使用复杂度度量定义更好的算法
- Javascript检查交集降低了计算复杂度
- JS提示-这个函数的圈复杂度&不要在循环中创建函数
- 排序算法的时间复杂度应该是多少
- Node.js发射器的时间复杂度/效率.removeListener(事件侦听器)
- 逻辑相似代码的圈复杂度
- 降低澄清度
- 在immutable.js中.equals()的复杂度是多少?