认知复杂度主要关注的是代码块的嵌套层次和控制流的复杂性。它与圈复杂度(Cyclomatic Complexity)不同,后者更多地关注代码路径的数量。认知复杂度更注重代码的可读性和理解难度。
我们的代码认知复杂度为什么很高?
嵌套层级太深、else-if 太多。
认知复杂度的计算主要基于以下因素:
嵌套层级
:每增加一层嵌套,复杂度 +1。
条件分支
:每个 if、else if、else、for、while 复杂度 +1。
逻辑运算符
:每个逻辑运算符(如 &&、||、?),复杂度 +1。
捕获异常
: catch 捕获异常语句 复杂度 +1
中断语句
: continue 或 break 复杂度 +1。
递归循环中的每种方法
: 复杂度 +1。
函数调用
:函数调用本身不增加复杂度,但如果函数内部逻辑复杂,会影响整体复杂度。简单函数调用(如 console.log)不增加复杂度。
注意:
let flag3 = (obj && obj.name) || (obj.age && obj.address); // +3
let flag4 = obj && obj.name && obj.age && obj.address; // +1
function example(value) {
// +1
if (value === "A") {
console.log("Option A");
// +1
} else {
// +1 +1
if (value === "B") {
console.log("Option B");
// +1 +1
} else if (value === "C") {
console.log("Option C");
}
}
}
对于 if-else if-else
结构,每增加一个条件分支都会增加认知复杂度。具体来说:
if
或 else if
都会增加 1 点认知复杂度。示例:
function example(value) {
if (value === "A") {
console.log("Option A");
} else if (value === "B") {
console.log("Option B");
} else if (value === "C") {
console.log("Option C");
} else {
console.log("Unknown option");
}
}
在这个例子中,每个 if
和 else if
else
都增加了 1 点认知复杂度,总共增加了 4 点认知复杂度。
在 Sonar 中,if-else if-else
和 switch-case
结构的认知复杂度计算方式有所不同。
以下是它们的主要区别:
虽然 switch-case
也包含多个分支,但 Sonar 对 switch-case
的处理更为宽容,通常不会为每个 case
增加额外的认知复杂度。
相反,整个 switch
语句通常只增加固定的少量复杂度(通常是 1 点)。
示例:
function example(value) {
switch (value) {
case "A":
console.log("Option A");
break;
case "B":
console.log("Option B");
break;
case "C":
console.log("Option C");
break;
default:
console.log("Unknown option");
break;
}
}
在这个例子中,尽管有多个 case
分支,但整个 switch
语句通常只会增加 1 点认知复杂度。
代码可读性:
switch-case
结构通常比多个 if-else if-else
更易于阅读和维护,特别是在处理多个离散值时。认知复杂度:
switch-case
可以减少认知复杂度,尤其是在有多个条件分支的情况下。if-else if-else
结构导致较高的认知复杂度,可以考虑使用 switch-case
来替代。除了选择合适的控制结构外,还可以通过以下方法进一步降低认知复杂度:
通过提前返回减少嵌套层级,这通常被称为“保卫子句”。
// 原始代码 认知复杂度
function processData(data) {
// +2 if+1 && +1
if (data !== null && data !== undefined) {
// +2 if+1 嵌套层级+1
if (data.length > 0) {
// Process data
console.log("Processing data");
}
}
}
// 重构后
function processDataRefactored(data) {
if (!data || data.length === 0) return; //+2
console.log("Processing data");
}
将复杂的逻辑提取到单独的函数中,减少主函数的复杂度。
function processValue(value) {
switch (value) {
case "A":
handleOptionA();
break;
case "B":
handleOptionB();
break;
case "C":
handleOptionC();
break;
default:
handleUnknownOption();
break;
}
}
function handleOptionA() {
console.log("Option A");
}
function handleOptionB() {
console.log("Option B");
}
function handleOptionC() {
console.log("Option C");
}
function handleUnknownOption() {
console.log("Unknown option");
}
将条件与对应的处理函数映射到一个对象中,通过查找映射表来调用
相应的处理函数。。
const handlers = {
A: () => console.log("Option A"),
B: () => console.log("Option B"),
C: () => console.log("Option C"),
default: () => console.log("Unknown option"),
};
function processValue(value) {
(handlers[value] || handlers["default"])();
}
const actionMap = new Map([
["login", () => console.log("Processing login...")],
["register", () => console.log("Processing register...")],
["logout", () => console.log("Processing logout...")],
["resetPassword", () => console.log("Processing reset password...")],
]);
function handleUserAction(action) {
const handler =
actionMap.get(action) || (() => console.log("Unknown action"));
handler();
}
// 调用示例
handleUserAction("register"); // 输出: Processing register...
handleUserAction("unknown"); // 输出: Unknown action
优点:
使用 Map 存储逻辑,查找效率高。
代码结构清晰,易于扩展。
认知复杂度:1 点(actionMap.get(action)的隐含条件)
将每个条件分支逻辑封装到一个独立的函数中,然后根据条件调用
相应的函数。
const strategies = {
condition1: function () {
// 处理逻辑1
console.log("Condition 1 met");
},
condition2: function () {
// 处理逻辑2
console.log("Condition 2 met");
},
condition3() {
// 处理逻辑3
console.log("Condition 3 met");
},
// 更多条件...
default: function () {
// 默认处理逻辑
console.log("Default condition met");
},
};
// 根据条件调用策略
function handleCondition(condition) {
const strategy = strategies[condition] || strategies["default"];
strategy();
}
// 示例调用
handleCondition("condition1"); // 输出: Condition 1 met
handleCondition("unknown"); // 输出: Default condition met
优点:
将逻辑分散到对象中,易于扩展和维护。
认知复杂度降低,代码更清晰。
function handleUserAction(user, action) {
if (user) {
if (user.age >= 18) {
if (action === "login") {
console.log("User logged in");
} else if (action === "register") {
console.log("User registered");
} else if (action === "logout") {
console.log("User logged out");
} else {
console.log("Unknown action");
}
} else {
console.log("User is underage");
}
} else {
console.log("Invalid user");
}
}
问题:
注意:第一个 if 不算嵌套,只有嵌套在内部的 if 才会增加嵌套层级。
if-else
导致认知复杂度高。将不同 action
的处理逻辑映射到一个对象中,通过键值对直接调用对应的处理函数。
const actions = {
login(user) {
console.log("User logged in");
},
register(user) {
console.log("User registered");
},
logout(user) {
console.log("User logged out");
},
default(user) {
console.log("Unknown action");
},
};
function handleUserAction(user, action) {
if (!user) {
console.log("Invalid user");
return;
}
if (user.age < 18) {
console.log("User is underage");
return;
}
const handler = actions[action] || actions.default;
handler(user);
}
认知复杂度: 3
优点:
将条件和对应的处理逻辑存储在一个 Map
中,通过查找表来执行逻辑。
const actionMap = new Map([
["login", (user) => console.log("User logged in")],
["register", (user) => console.log("User registered")],
["logout", (user) => console.log("User logged out")],
]);
function handleUserAction(user, action) {
if (!user) {
console.log("Invalid user");
return;
}
if (user.age < 18) {
console.log("User is underage");
return;
}
const handler =
actionMap.get(action) || (() => console.log("Unknown action"));
handler(user);
}
认知复杂度: 3
优点:
Map
存储逻辑,查找效率高。switch-case
将 action
的逻辑用 switch-case
替代 if-else
。
function handleUserAction(user, action) {
if (!user) {
console.log("Invalid user");
return;
}
if (user.age < 18) {
console.log("User is underage");
return;
}
switch (action) {
case "login":
console.log("User logged in");
break;
case "register":
console.log("User registered");
break;
case "logout":
console.log("User logged out");
break;
default:
console.log("Unknown action");
}
}
认知复杂度:3
优点:
action
的条件较多且离散时,switch-case
比 if-else
更清晰。将不同逻辑拆分为多个小函数,每个函数只负责一个单一的任务。
function validateUser(user) {
if (!user) {
console.log("Invalid user");
return false;
}
if (user.age < 18) {
console.log("User is underage");
return false;
}
return true;
}
function handleLogin(user) {
console.log("User logged in");
}
function handleRegister(user) {
console.log("User registered");
}
function handleLogout(user) {
console.log("User logged out");
}
function handleUserAction(user, action) {
if (!validateUser(user)) return;
if (action === "login") {
handleLogin(user);
} else if (action === "register") {
handleRegister(user);
} else if (action === "logout") {
handleLogout(user);
} else {
console.log("Unknown action");
}
}
认知复杂度:validateUser 2 handleUserAction 5
优点:
最重要的是简化控制流
、减少嵌套层级
、提高可读性
!