"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;
}

当然,您应该输入行而不是字符。

此技术可扩展到复杂的编译器,已通过大量实现得到证明。因此,如果您正在寻找“正确”的方法,这里就是。