В консольной программе, которую я создаю, у меня есть немного кода, который анализирует файл. После анализа каждой строки она проверяется на наличие синтаксических ошибок. В случае синтаксической ошибки программа прекращает чтение файла и переходит к следующей части программы. Проблема в том, что это очень грязно, так как мое единственное решение до сих пор — это серия вложенных операторов 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;
Я просмотрел много разных сайтов, но их вердикты отличались от моей проблемы. Этот код работает во время выполнения, но, как вы можете видеть, он не очень элегантен. Есть ли кто-нибудь, у кого есть более читаемый / эффективный подход к моей проблеме? Любая помощь приветствуется 🙂
Есть ли […] более читаемый / эффективный подход к моей проблеме
Ответ: компилятор, который анализирует текстовые файлы и выдает разные результаты.
Есть много подходов и методов. Книги, онлайн и примеры с открытым исходным кодом. Просто и сложно.
Конечно, вы можете просто пропустить этот шаг, если вы не заинтересованы.
Просматривая теорию, вы не пропустите такие термины, как «конечный автомат», «автоматизация» и т. Д. Вот краткое объяснение Википедии:
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;
}
Конечно, вместо символов следует указывать строки.
Эта техника масштабируется до сложных компиляторов, проверенных множеством реализаций. Так что, если вы ищете «правильный» подход, вот он.
На ум приходят два варианта:
Это похоже на то, как 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
Тогда первый вариант должен быть предпочтительным.
Обычной современной практикой является скорейшее возвращение в РАИИ. По сути это означает, что код, который должен произойти, должен находиться в деструкторе класса, и ваша функция будет иметь локальный объект этого класса. Теперь, когда у вас есть ошибка, вы рано выходите из функции (либо с 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;
}
...
}