我如何避免这种混乱??,我想以某种方式压缩代码,关于我最喜欢使用javascript的await / promise功能的回调,但是对于嵌套,如果我真的不知道,该怎么办。谢谢。关于重构此烂摊子的任何技巧都将非常有用。
router.delete("/user/:username", passport.authenticate("jwt", { session: false }), (req, res) => {
var { username = null } = req.params;
if (username != null) {
User.getUserById(req.user.id, (err, destroyerUser) => {
if (err) {
res.json({ success: false, msg: "User not found" });
} else {
if (destroyerUser) {
if (destroyerUser.role == "Admin" || destroyerUser.role == "Moderator") {
User.getUserByUsername(username, (err, user) => {
if (!err) {
if (user) {
if (user._id.toString() != destroyerUser._id.toString()) {
if (destroyerUser.role == "Admin") {
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
} else {
res.json({ success: true, msg: "Successfully deleted user" });
}
})
} else if (destroyerUser.role == "Moderator") {
if (user.role == "Moderator" || user.role == "Admin") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
} else {
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
} else {
res.json({ success: true, msg: "Successfully deleted user" });
}
})
}
}
} else {
res.json({ success: false, msg: "You can't delete yourself" });
}
} else {
res.json({ success: false, msg: "User not found" });
}
} else {
res.json({ success: false, msg: "Error finding user" });
}
})
} else {
res.json({ success: false, msg: "You don't have sufficient permissions" })
}
} else {
res.json({ success: false, msg: "Destroyer user not found" });
}
}
});
} else {
res.json({ success: false, msg: "Username is not valid" });
}
})
答案 0 :(得分:2)
首先,就像@LawrenceCheron所说的那样,您应该首先进行验证,而不要在else语句中进行验证。
代替写作
if (destroyerUser) {
// Do something
} else {
// Exit
}
你可以写
if (!destroyerUser) {
// Exit
}
其次,您可以将userId提取到一个变量中,而不是链接对象键,即
代替写作
if (user.role == "Moderator" || user.role == "Admin") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
}
你可以写
const userRole = user.role;
if (userRole == "Moderator" || userRole == "Admin") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
}
最后是复杂的语句,例如
if (user._id.toString() != destroyerUser._id.toString()) {
// ...
}
可以提取为较小的功能
const isSameUserId = (id, sample) => id === sample;
if (!isSameUserId(user._id.toString(), destroyerUser._id.toString()) {
// ...
}
或结合以上
const isSameUserId = (id, sample) => id === sample;
const userId = user._id.toString();
const destroyerId = destroyerUser._id.toString();
if (!isSameUserId(userId, destroyerId)) {
// ...
}
答案 1 :(得分:1)
这种代码我最喜欢的样式或模式之一是“早期终止”(我不知道它是否有一个众所周知的名称,但这就是我所说的)。而不是在else中嵌套带有错误路径的Happy Path。我将“错误路径”设置为“提前终止”条件。
if (!username) {
res.json({ success: false, msg: "Username is not valid" });
return;
}
try {
// Get user a custom function you wrote to make sure it utilizes Promises
let destroyerUser = await getUser(req.user.id);
if (!destroyerUser) {
res.json({ success: false, msg: "Destroyer user not found" });
return;
}
if (destroyerUser.role != "Admin" && destroyerUser.role != "Moderator") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
return;
}
// Continue this pattern all the way down
// Nesting remains minimal, code is easy to follow
// The trick is to invert your condition logic
// then place a return so that all the code below won't execute
// thus early termination when validation fails
}
catch(err) {
res.json({ success: false });
return;
}
以及如何使用回调函数并将其转换为Promise函数的示例(异步/等待必需)。
function getUser(userId) {
// Return a promise to the caller that will be resolved or rejected
// in the future. Callers can use Promise then or Await for a result.
return new Promise((resolve, reject) => {
User.getUserById(userId, (err, user) => {
// If there is an error, call reject, otherwise resolve
err ? reject(err) : resolve(user);
});
});
}
答案 2 :(得分:1)
嵌套侧面金字塔可以做一些事情。一种非常普遍的技术是先处理错误,然后再执行常规代码。因此,假设您有:
if (everythingFine) {
//do normal operation
} else {
//handle error
}
这是 OK ,但您一旦遇到问题便真的遇到了问题
if (everythingFine) {
//do operation
if (operationSuccessful) {
//do next step
} else {
//handle error with operation
}
} else {
//handle error
}
您立即开始堆叠越来越多的块。而且,成功的逻辑通常很大,因为您以后需要做更多的事情。错误处理逻辑可能只是一行。因此,如果您想知道发生了什么,则在出错时,需要滚动很多。
为避免这种情况,您可以提早退出。转换代码的步骤相同:
if
的条件以检查是否成功,而不是检查错误if
块和else
块。return
添加到if
。else
块并将其展平。您将获得以下内容
if (!everythingFine) {
//handle error
return;
}
//do operation
if (!operationSuccessful) {
//handle error with operation
return;
}
//do next step
现在您将删除嵌套并删除else块,因为您将只return
并退出该函数。如果您的错误处理是执行中已经存在的throw
语句,那么您就不需要return
。
接下来的事情是,您在这里有一些重复的逻辑:
if (destroyerUser.role == "Admin") {
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
} else {
res.json({ success: true, msg: "Successfully deleted user" });
}
})
} else if (destroyerUser.role == "Moderator") {
if (user.role == "Moderator" || user.role == "Admin") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
} else {
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
} else {
res.json({ success: true, msg: "Successfully deleted user" });
}
})
}
}
无论destroyerUser
是管理员还是主持人,两种情况下您都呼叫相同的deleteByOne
。在这两种情况下,您还都返回相同的成功消息。因此,您在代码中有六个不同的分支,而您只有三个结果:
+----------------+-----------+-----------------+-----------------------------------------+
| destroyer role | user role | deletion result | operation result |
+----------------+-----------+-----------------+-----------------------------------------+
| Moderator | Moderator | N/A | "You don't have sufficient permissions" |
| Moderator | Admin | N/A | "You don't have sufficient permissions" |
| Moderator | <other> | success | "Successfully deleted user" |
| Moderator | <other> | error | "Error deleting user" |
| Admin | <any> | success | "Successfully deleted user" |
| Admin | <any> | error | "Error deleting user" |
+----------------+-----------+-----------------+-----------------------------------------+
相反,您可以只检查主持人是否先执行删除操作,如果他们 do 具有足够的权限,则他们是管理员,然后只需执行一次删除操作即可:
if (destroyerUser.role == "Moderator" && (user.role == "Moderator" || user.role == "Admin")) {
res.json({ success: false, msg: "You don't have sufficient permissions" })
return;
}
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
return;
}
res.json({ success: true, msg: "Successfully deleted user" });
})
因此,如果我们应用这两件事:
然后您的代码如下:
router.delete("/user/:username", passport.authenticate("jwt", { session: false }), (req, res) => {
var { username = null } = req.params;
if (username == null) {
res.json({ success: false, msg: "Username is not valid" });
return;
}
User.getUserById(req.user.id, (err, destroyerUser) => {
if (err) {
res.json({ success: false, msg: "User not found" });
return;
}
if (!destroyerUser) {
res.json({ success: false, msg: "Destroyer user not found" });
return;
}
if (destroyerUser.role !== "Admin" && destroyerUser.role !== "Moderator") {
res.json({ success: false, msg: "You don't have sufficient permissions" })
return;
}
User.getUserByUsername(username, (err, user) => {
if (err) {
res.json({ success: false, msg: "Error finding user" });
return;
}
if (!user) {
res.json({ success: false, msg: "User not found" });
return;
}
if (user._id.toString() === destroyerUser._id.toString()) {
res.json({ success: false, msg: "You can't delete yourself" });
return;
}
if (destroyerUser.role == "Moderator" && (user.role == "Moderator" || user.role == "Admin")) {
res.json({ success: false, msg: "You don't have sufficient permissions" })
return;
}
Cyclic.deleteOneById({ _id: user._id }, (err) => {
if (err) {
res.json({ success: false, msg: "Error deleting user" });
return;
}
res.json({ success: true, msg: "Successfully deleted user" });
})
})
});
})
一个注释-我将条件destroyerUser.role == "Admin" || destroyerUser.role == "Moderator"
转换为destroyerUser.role !== "Admin" && destroyerUser.role !== "Moderator"
是因为我不喜欢长表达式前的布尔NOT:
if (!(destroyerUser.role == "Admin" || destroyerUser.role == "Moderator"))
很容易错过,因为您首先看到OR和两个表达式。幸运的是,对于布尔表达式not (A or B) = not A and not B
使用De Morgan's law可以很容易地更改它。因此,我将其更改为该方式。
您可以通过添加新功能来处理所有错误来进一步减少某些代码重复。在所有情况下,除了传递不同的消息外,您都执行相同的操作,因此您可能只需要:
const error = msg => res.json({ success: false, msg });
例如,您可以致电error("User not found")
。它并没有真正减少您拥有的代码量,但是这种方式更加一致。另外,如果您决定在错误响应中添加更多内容(例如,发送其他标头,或者您想尝试翻译错误消息),那么您将拥有一个中心位置。