"Cleaning up" 嵌套 if 语句
"Cleaning up" nested if statements
在我正在创建的控制台程序中,我有一些代码可以通过文件进行解析。解析每一行后,检查语法错误。如果存在语法错误,程序将停止读取文件并转到程序的下一部分。问题是,它非常混乱,因为到目前为止我唯一的解决方案是一系列嵌套的 if 语句或一行 if 语句。嵌套 ifs 的问题是它很快就会变得非常混乱,一系列 if 语句让程序测试一些不需要测试的东西。这是我的问题的一些 sudo 代码(注意我没有使用 return 语句)
显示的是伪代码而不是真实代码,因为它非常大
嵌套如果:
open file;
read line;
//Each if is testing something different
//Every error is different
if (line is valid)
{
read line;
if (line is valid)
{
read line;
if (line is valid)
{
do stuff;
}
else
error;
}
else
error;
}
else
error;
code that must be reached, even if there was an error;
非嵌套 ifs:
bool fail = false;
open file;
read line;
//Each if is testing something different
//Every error is different
if (line is valid)
read line;
else
{
error;
fail = true;
}
if (!error && line is valid)
read line;
else
{
error;
fail = true;
}
if (!error && line is valid)
do stuff;
else
error;
//Note how error is constantly evaluated, even if it has already found to be false
code that must be reached, even if there was an error;
我看过许多不同的网站,但他们的判决与我的问题不同。这段代码在运行时确实有效,但如您所见,它不是很优雅。有没有人对我的问题有更多 readable/efficient 的方法?任何帮助表示赞赏:)
想到两个选项:
选项 1:链读取和验证
这类似于 std::istream
提取运算符的工作方式。你可以这样做:
void your_function() {
std::ifstream file("some_file");
std::string line1, line2, line3;
if (std::getline(file, line1) &&
std::getline(file, line2) &&
std::getline(file, line3)) {
// do stuff
} else {
// error
}
// code that must be reached, even if there was an error;
}
选项 2:拆分为不同的功能
这可能会有点长,但如果你把事情分开(并给所有的东西一个合理的名字),它实际上是非常可读和可调试的。
bool step3(const std::string& line1,
const std::string& line2,
const std::string& line3) {
// do stuff
return true;
}
bool step2(std::ifstream& file,
const std::string& line1,
const std::string& line2) {
std::string line3;
return std::getline(file, line3) && step3(line1, line2, line3);
}
bool step1(std::ifstream& file,
const std::string& line1) {
std::string line2;
return std::getline(file, line2) && step2(file, line1, line2);
}
bool step0(std::ifstream& file) {
std::string line1;
return std::getline(file, line1) && step1(file, line1);
}
void your_function() {
std::ifstream file("some_file");
if (!step0(file)) {
// error
}
// code that must be reached, even if there was an error;
}
这个示例代码有点太简单了。如果在每个步骤中发生的行验证比 std::getline
的 return 值更复杂(在进行实际输入验证时通常是这种情况),那么这种方法的好处是使它更具可读性.但如果输入验证像检查std::getline
一样简单,那么应该首选第一个选项。
一种常见的现代做法是早期 return 使用 RAII。基本上这意味着必须发生的代码应该在 class 的析构函数中,并且您的函数将具有 class 的局部对象。现在,当您遇到错误时,您会提前退出函数(使用 Exception
或只是简单的 return
),并且该本地对象的析构函数将处理必须发生的代码。
代码看起来像这样:
class Guard
{
...
Guard()
~Guard() { /*code that must happen */}
...
}
void someFunction()
{
Gaurd localGuard;
...
open file;
read line;
//Each if is testing something different
//Every error is different
if (!line)
{
return;
}
read line;
if (!line)
{
return;
}
...
}
Is there [...] a more readable/efficient approach on my problem
第 1 步。查找文本解析器的经典示例
答案:一个编译器,它解析文本文件并产生不同类型的结果。
第 2 步。阅读一些编译器如何工作的理论
有很多方法和技巧。书籍、在线和开源示例。简单又复杂。
当然,如果您不感兴趣,可以跳过这一步。
第 3 步。将理论应用于您的问题
翻开理论,你一定不会错过“状态机”、“自动化”等术语。下面是维基百科上的简单解释:
https://en.wikipedia.org/wiki/Automata-based_programming
Wiki 页面上基本上有一个随时可用的示例:
#include <stdio.h>
enum states { before, inside, after };
void step(enum states *state, int c)
{
if(c == '\n') {
putchar('\n');
*state = before;
} else
switch(*state) {
case before:
if(c != ' ') {
putchar(c);
*state = inside;
}
break;
case inside:
if(c == ' ') {
*state = after;
} else {
putchar(c);
}
break;
case after:
break;
}
}
int main(void)
{
int c;
enum states state = before;
while((c = getchar()) != EOF) {
step(&state, c);
}
if(state != before)
putchar('\n');
return 0;
}
或带有状态机的 C++ 示例:
#include <stdio.h>
class StateMachine {
enum states { before = 0, inside = 1, after = 2 } state;
struct branch {
unsigned char new_state:2;
unsigned char should_putchar:1;
};
static struct branch the_table[3][3];
public:
StateMachine() : state(before) {}
void FeedChar(int c) {
int idx2 = (c == ' ') ? 0 : (c == '\n') ? 1 : 2;
struct branch *b = & the_table[state][idx2];
state = (enum states)(b->new_state);
if(b->should_putchar) putchar(c);
}
};
struct StateMachine::branch StateMachine::the_table[3][3] = {
/* ' ' '\n' others */
/* before */ { {before,0}, {before,1}, {inside,1} },
/* inside */ { {after, 0}, {before,1}, {inside,1} },
/* after */ { {after, 0}, {before,1}, {after, 0} }
};
int main(void)
{
int c;
StateMachine machine;
while((c = getchar()) != EOF)
machine.FeedChar(c);
return 0;
}
当然,您应该输入行而不是字符。
此技术可扩展到复杂的编译器,已通过大量实现得到证明。因此,如果您正在寻找“正确”的方法,这里就是。
在我正在创建的控制台程序中,我有一些代码可以通过文件进行解析。解析每一行后,检查语法错误。如果存在语法错误,程序将停止读取文件并转到程序的下一部分。问题是,它非常混乱,因为到目前为止我唯一的解决方案是一系列嵌套的 if 语句或一行 if 语句。嵌套 ifs 的问题是它很快就会变得非常混乱,一系列 if 语句让程序测试一些不需要测试的东西。这是我的问题的一些 sudo 代码(注意我没有使用 return 语句)
显示的是伪代码而不是真实代码,因为它非常大
嵌套如果:
open file;
read line;
//Each if is testing something different
//Every error is different
if (line is valid)
{
read line;
if (line is valid)
{
read line;
if (line is valid)
{
do stuff;
}
else
error;
}
else
error;
}
else
error;
code that must be reached, even if there was an error;
非嵌套 ifs:
bool fail = false;
open file;
read line;
//Each if is testing something different
//Every error is different
if (line is valid)
read line;
else
{
error;
fail = true;
}
if (!error && line is valid)
read line;
else
{
error;
fail = true;
}
if (!error && line is valid)
do stuff;
else
error;
//Note how error is constantly evaluated, even if it has already found to be false
code that must be reached, even if there was an error;
我看过许多不同的网站,但他们的判决与我的问题不同。这段代码在运行时确实有效,但如您所见,它不是很优雅。有没有人对我的问题有更多 readable/efficient 的方法?任何帮助表示赞赏:)
想到两个选项:
选项 1:链读取和验证
这类似于 std::istream
提取运算符的工作方式。你可以这样做:
void your_function() {
std::ifstream file("some_file");
std::string line1, line2, line3;
if (std::getline(file, line1) &&
std::getline(file, line2) &&
std::getline(file, line3)) {
// do stuff
} else {
// error
}
// code that must be reached, even if there was an error;
}
选项 2:拆分为不同的功能
这可能会有点长,但如果你把事情分开(并给所有的东西一个合理的名字),它实际上是非常可读和可调试的。
bool step3(const std::string& line1,
const std::string& line2,
const std::string& line3) {
// do stuff
return true;
}
bool step2(std::ifstream& file,
const std::string& line1,
const std::string& line2) {
std::string line3;
return std::getline(file, line3) && step3(line1, line2, line3);
}
bool step1(std::ifstream& file,
const std::string& line1) {
std::string line2;
return std::getline(file, line2) && step2(file, line1, line2);
}
bool step0(std::ifstream& file) {
std::string line1;
return std::getline(file, line1) && step1(file, line1);
}
void your_function() {
std::ifstream file("some_file");
if (!step0(file)) {
// error
}
// code that must be reached, even if there was an error;
}
这个示例代码有点太简单了。如果在每个步骤中发生的行验证比 std::getline
的 return 值更复杂(在进行实际输入验证时通常是这种情况),那么这种方法的好处是使它更具可读性.但如果输入验证像检查std::getline
一样简单,那么应该首选第一个选项。
一种常见的现代做法是早期 return 使用 RAII。基本上这意味着必须发生的代码应该在 class 的析构函数中,并且您的函数将具有 class 的局部对象。现在,当您遇到错误时,您会提前退出函数(使用 Exception
或只是简单的 return
),并且该本地对象的析构函数将处理必须发生的代码。
代码看起来像这样:
class Guard
{
...
Guard()
~Guard() { /*code that must happen */}
...
}
void someFunction()
{
Gaurd localGuard;
...
open file;
read line;
//Each if is testing something different
//Every error is different
if (!line)
{
return;
}
read line;
if (!line)
{
return;
}
...
}
Is there [...] a more readable/efficient approach on my problem
第 1 步。查找文本解析器的经典示例
答案:一个编译器,它解析文本文件并产生不同类型的结果。
第 2 步。阅读一些编译器如何工作的理论
有很多方法和技巧。书籍、在线和开源示例。简单又复杂。
当然,如果您不感兴趣,可以跳过这一步。
第 3 步。将理论应用于您的问题
翻开理论,你一定不会错过“状态机”、“自动化”等术语。下面是维基百科上的简单解释:
https://en.wikipedia.org/wiki/Automata-based_programming
Wiki 页面上基本上有一个随时可用的示例:
#include <stdio.h>
enum states { before, inside, after };
void step(enum states *state, int c)
{
if(c == '\n') {
putchar('\n');
*state = before;
} else
switch(*state) {
case before:
if(c != ' ') {
putchar(c);
*state = inside;
}
break;
case inside:
if(c == ' ') {
*state = after;
} else {
putchar(c);
}
break;
case after:
break;
}
}
int main(void)
{
int c;
enum states state = before;
while((c = getchar()) != EOF) {
step(&state, c);
}
if(state != before)
putchar('\n');
return 0;
}
或带有状态机的 C++ 示例:
#include <stdio.h>
class StateMachine {
enum states { before = 0, inside = 1, after = 2 } state;
struct branch {
unsigned char new_state:2;
unsigned char should_putchar:1;
};
static struct branch the_table[3][3];
public:
StateMachine() : state(before) {}
void FeedChar(int c) {
int idx2 = (c == ' ') ? 0 : (c == '\n') ? 1 : 2;
struct branch *b = & the_table[state][idx2];
state = (enum states)(b->new_state);
if(b->should_putchar) putchar(c);
}
};
struct StateMachine::branch StateMachine::the_table[3][3] = {
/* ' ' '\n' others */
/* before */ { {before,0}, {before,1}, {inside,1} },
/* inside */ { {after, 0}, {before,1}, {inside,1} },
/* after */ { {after, 0}, {before,1}, {after, 0} }
};
int main(void)
{
int c;
StateMachine machine;
while((c = getchar()) != EOF)
machine.FeedChar(c);
return 0;
}
当然,您应该输入行而不是字符。
此技术可扩展到复杂的编译器,已通过大量实现得到证明。因此,如果您正在寻找“正确”的方法,这里就是。