& quot; Очистка & quot; вложенные операторы if

В консольной программе, которую я создаю, у меня есть немного кода, который анализирует файл. После анализа каждой строки она проверяется на наличие синтаксических ошибок. В случае синтаксической ошибки программа прекращает чтение файла и переходит к следующей части программы. Проблема в том, что это очень грязно, так как мое единственное решение до сих пор — это серия вложенных операторов if или строка операторов if. Проблема с вложенным ifs состоит в том, что он очень быстро запутывается, и в серии операторов if выполняется тестирование программы для нескольких вещей, которые не нужно проверять. Вот некоторый sudo-код моей проблемы (обратите внимание, я НЕ использую оператор возврата)

Вместо реального кода показан псевдокод, так как он очень большой

Вложено, если:

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;

Не вложенный, если:

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;

Я просмотрел много разных сайтов, но их вердикты отличались от моей проблемы. Этот код работает во время выполнения, но, как вы можете видеть, он не очень элегантен. Есть ли кто-нибудь, у кого есть более читаемый / эффективный подход к моей проблеме? Любая помощь приветствуется 🙂

2

Решение

Есть ли […] более читаемый / эффективный подход к моей проблеме

Шаг 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;
}

Конечно, вместо символов следует указывать строки.

Эта техника масштабируется до сложных компиляторов, проверенных множеством реализаций. Так что, если вы ищете «правильный» подход, вот он.

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

Это может занять немного много времени, но если вы правильно разберетесь (и дадите всем здравомыслящее имя), оно может быть очень читабельным и отлаживаемым.

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Если возвращаемое значение (которое часто имеет место при реальной проверке входных данных), то такой подход имеет преимущество в том, чтобы сделать его более читабельным. Но если проверка ввода так же просто, как проверка std::getlineТогда первый вариант должен быть предпочтительным.

2

Обычной современной практикой является скорейшее возвращение в РАИИ. По сути это означает, что код, который должен произойти, должен находиться в деструкторе класса, и ваша функция будет иметь локальный объект этого класса. Теперь, когда у вас есть ошибка, вы рано выходите из функции (либо с 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;
}
...
}
1
По вопросам рекламы [email protected]