Введя символические имена для инструкции “печать” и “выход”, мы сделали код понятнее. Кроме того, теперь тот, кто будет читать текст функции main()
, не будет гадать, как кодируются эти инструкции. Например, не удивительно, если мы решим изменить представление инструкции “выход” на символ 'e'
(от слова “exit”). Для этого не требуется вносить изменения в функцию main()
. Теперь в глаза бросаются строки ">
" и "=
". Почему мы используем эти “магические” литералы в своей программе? Как новый программист, читающий текст функции main()
, сможет догадаться об их предназначении? Может быть, стоит добавить комментарий? Это может оказаться удачной идеей, но использование символического имени более эффективно.
const string prompt = "> ";
const string result = "= "; // используется для указания на то, что
// далее следует результат
Если нам в дальнейшем понадобится изменить приглашение или индикатор результата, будет достаточно просто изменить эти константы. Теперь цикл выглядит иначе.
while (cin) {
cout << prompt;
Token t = ts.get();
while (t.kind ==print) t=ts.get();
if (t.kind == quit) {
keep_window_open();
return 0;
}
ts.putback(t);
cout << result << expression() << endl;
}
7.6.2. Использование функций
Функции должны отражать структуру программы, и их имена должны обеспечивать логическое разделение кода на отдельные части. В этом отношении наша программа до сих пор не вызывала нареканий: функции expression()
, term()
и primary()
непосредственно отражают наше понимание грамматики, а функция get()
выполняет ввод и распознавание лексем. Тем не менее анализ функции main()
показывает, что ее можно разделить на две логически разные части.
1. Функция main()
описывает общую логическую структуру: начало программы, конец программы и обработку фатальных ошибок.
2. Функция main()
выполняет цикл вычислений.
main()
выполняет оба эти действия, то это затемняет структуру программы. Напрашивается выделение цикла вычислений в виде отдельной функции calculate()
.
void calculate() // цикл вычисления выражения
{
while (cin) {
cout << prompt;
Token t = ts.get();
while (t.kind == print) t=ts.get(); // отмена печати
if (t.kind == quit) return;
ts.putback(t);
cout << result << expression() << endl;
}
}
int main()
try {
calculate();
keep_window_open(); // обеспечивает консольный режим Windows
return 0;
}
catch (runtime_error& e) {
cerr << e.what() << endl;
keep_window_open("~~");
return 1;
}
catch (...) {
cerr << "exception \n";
keep_window_open("~~");
return 2;
}
Этот код намного более четко отражает структуру программы, и, следовательно, его проще понять.
7.6.3. Расположение кода
Поиск некрасивого кода приводит нас к следующему фрагменту:
switch (ch) {
case 'q': case ';': case '%': case '(': case ')':
case '+': case '–': case '*': case '/':
return Token(ch); // пусть каждый символ обозначает сам себя
Этот код был неплох, пока мы не добавили символы 'q'
, ';'
и '%'
, но теперь он стал непонятным. Код, который трудно читать, часто скрывает ошибки. И конечно, они есть в этом фрагменте! Для их выявления необходимо разместить каждый раздел case
в отдельной строке и расставить комментарии. Итак, функция Token_stream::get()
принимает следующий вид:
Token Token_stream::get()
// считываем символ из потока cin и образуем лексему
{
if (full) { // проверяем, есть ли в потоке хотя бы одна лексема
full=false;
return buffer;
}
char ch;
cin >> ch; // Перевод:" оператор >> игнорирует разделители пробелы,
// переходы на новую строку, табуляцию и пр.)"
switch (ch) {
case quit:
case print:
case '(':
case ')':
case '+':