GMA*_*GMA 27 refactoring readability rust
我正在浏览充满这样代码的代码库:
if let Some(i) = func1() {
if let Some(j) = func2(i) {
if let Some(k) = func3(j) {
if let Some(result) = func4(k) {
// Do something with result
} else {
println!("func 4 returned None");
}
} else {
println!("func 3 returned None");
}
} else {
println!("func 2 returned None");
}
} else {
println!("func 1 returned None");
}
Run Code Online (Sandbox Code Playgroud)
这是一个愚蠢的、简化的例子,但一般模式是:
Option.None)传递给下一个函数。Some,则返回的最终值将用于某些用途。None,则执行停止并记录某种错误 - 通常是一条错误消息,准确通知用户返回了哪个函数None。当然,问题是上面的代码丑陋且不可读。i当您用实际代码中实际含义的变量/函数名称替换 等 时,它会变得更加丑陋func1,并且我的真实代码库中的许多示例都有远远超过四个嵌套的if let。这是箭头反模式的一个例子,它完全失败了斜视测试,并且令人困惑的是错误消息如何以与可能导致错误的函数相反的顺序出现。
难道真的没有更好的方法吗?我想将上面的内容重构为具有更清晰、更扁平的结构,其中所有内容都以合理的顺序出现。if let 链接可能会有所帮助,但 Rust 中似乎还没有提供该功能。我想也许我可以通过使用?和/或提取一些辅助函数来清理东西,但我无法让它工作,如果我可以避免的话,我宁愿不到处提取大量新函数。
这是我能想到的最好的:
let i : u64;
let j : u64;
let k : u64;
let result : u64;
if let Some(_i) = func1() {
i = _i;
} else {
println!("func 1 returned None");
return;
}
if let Some(_j) = func2(i) {
j = _j;
} else {
println!("func 2 returned None");
return;
}
if let Some(_k) = func3(j) {
k = _k;
} else {
println!("func 3 returned None");
return;
}
if let Some(_result) = func3(k) {
result = _result;
} else {
println!("func 4 returned None");
return;
}
// Do something with result
Run Code Online (Sandbox Code Playgroud)
但这仍然感觉非常长和冗长,而且我不喜欢我引入这些额外变量等的方式_i。_j
有什么我在这里没有看到的吗?写我想写的东西最简单、最干净的方法是什么?
Joh*_*ica 26
您可以使用let-else 语句,该功能已添加到版本 1.65中的稳定 rust 中。
RFC 3137
引入一个新的
let PATTERN: TYPE = EXPRESSION else DIVERGING_BLOCK;构造(非正式地称为let-else 语句),与 if-let 表达式相对应。如果指定表达式的模式匹配成功,则其绑定将被引入到周围的范围中。如果它不成功,它必须发散(返回
!,例如返回或中断)。
使用此功能您可以编写:
let Some(i) = func1() else {
println!("func 1 returned None");
return;
};
let Some(j) = func2(i) else {
println!("func 2 returned None");
return;
};
let Some(k) = func3(j) else {
println!("func 3 returned None");
return;
};
let Some(result) = func3(k) else {
println!("func 4 returned None");
return;
};
Run Code Online (Sandbox Code Playgroud)
如果你想在旧版本的 Rust 上尝试,你必须启用不稳定功能:
#![feature(let_else)]
Run Code Online (Sandbox Code Playgroud)
cam*_*024 15
If-let 链接将使这变得更好,但现在(假设您不想每晚使用),轻微的重构可能会有所帮助。例如,将链中除最后一个调用之外的所有调用拉入其自己的函数中,您可以使用?运算符:
fn get_result() -> Option<u64> {
let i = func1()?;
let j = func2(i)?;
let k = func3(j)?;
func3(k)
}
fn main() {
if let Some(result) = get_result() {
// do something
}
}
Run Code Online (Sandbox Code Playgroud)
如果您需要对错误情况进行更细粒度的控制,您可以返回Result:
enum Error {
Func1,
Func2,
Func3,
Func4,
}
fn get_result() -> Result<i64, Error> {
let i = func1().ok_or(Error::Func1)?;
let j = func2(i).ok_or(Error::Func2)?;
let k = func3(j).ok_or(Error::Func3)?;
func4(k).ok_or(Error::Func4)
}
fn main() {
use Error::*;
match get_result() {
Ok(result) => {},
Err(Func1) => {},
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
我想在你的“最终研究”清单上再放两件事:简单的Option::and_then:
let result = func1().and_then(func2).and_then(func3).and_then(func4);\nmatch result {\n Some(result) => \xe2\x80\xa6,\n None => \xe2\x80\xa6,\n}\nRun Code Online (Sandbox Code Playgroud)\n还有稍微棘手但非常方便的anyhow::Context:
use anyhow::Context;\nlet j = func2(i).context("Func2 failed")?;\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
5586 次 |
| 最近记录: |