Введя символические имена для инструкции “печать” и “выход”, мы сделали код понятнее. Кроме того, теперь тот, кто будет читать текст функции 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 '+':
case '–':
case '*':
case '/':
case '%':