Читаем Программирование онлайн бесплатно
По существу, мы заменили функцию keep_window_open() своим собственным кодом. Обратите внимание на то, что проблема останется нерешенной, если символ окажется следующим после возникновения ошибки, но это маловероятно.
Обнаружив эту проблему, мы написали вариант функции keep_window_open(), аргументом которой была строка, закрывающая окно, как только пользователь вводил ее после приглашения. Таким образом, более простое решение выглядит так:
catch (runtime_error& e) {
cerr << e.what() << endl;
keep_window_open("~~");
return 1;
}
Рассмотрим еще один пример.
+1
!1~~
()
Эти данные вынуждают калькулятор выдавать соответствующие сообщения об ошибках, например
Чтобы выйти, введите ~~
и не прекращать работу, пока пользователь не введет строку ~~.
Входные данные для калькулятора вводятся с клавиатуры. Это затрудняет тестирование: каждый раз, внося улучшение, мы должны напечатать множество контрольных примеров (каждый раз заново!), чтобы убедиться, что программа по-прежнему работает. Было бы лучше, если бы контрольные примеры где-то хранились и вызывать их одной командой. Некоторые операционные системы (в частности, Unix) упрощают эту задачу, позволяя потоку cin считывать данные из файла без модификации программы, а потоку cout — направлять данные в файл. В других случаях мы должны модифицировать программу так, чтобы она использовала файл (подробнее об этом — в главе 10).
Рассмотрим примеры.
1+2; q
1+2 q
Мы хотели бы вывести результат (3) и выйти из программы. Забавно, что строка
1+2 q
приводит к этому результату, а более очевидная строка
1+2; q
вызывает ошибку Ожидается первичное выражение. Где следует искать эту ошибку? Конечно, в функции main(), где обрабатываются символы ; и q. Мы добавили инструкции “печать” и “выход” просто для того, чтобы поскорее получить работающий вариант калькулятора (см. раздел 6.6), а теперь расплачиваемся за эту поспешность. Рассмотрим еще раз следующий фрагмент:
double val = 0;
while (cin) {
cout << "> ";
Token t = ts.get();
if (t.kind == 'q') break;
if (t.kind == ';')
cout << "= " << val << '\n';
else
ts.putback(t);
val = expression();
}
Если обнаруживаем точку с запятой, то вызываем функцию expression(), не проверяя символ q. Эта функция в первую очередь ищет вызов функции term(), которая вызывает функцию primary(), обнаруживающую символ q. Буква q не является первичным выражением, поэтому получаем сообщение об ошибке. Итак, после тестирования точки с запятой мы должны обработать символ q. В этот момент мы почувствовали необходимость несколько упростить логику, поэтому окончательный вариант функции main() выглядит так:
int main()
try
{
while (cin) {
cout << "> ";
Token t = ts.get();
while (t.kind == ';') t=ts.get(); // считываем ';'
if (t.kind == 'q') {
keep_window_open();
return 0;
}
ts.putback(t);
cout << "= " << expression() << endl;
}
keep_window_open();
return 0;
}
catch (exception& e) {
cerr << e.what() << endl;
keep_window_open("~~");
return 1;
}
catch (...) {
cerr << "exception \n";
keep_window_open("~~");
return 2;
}
Это повышает надежность обработки ошибок. Таким образом, теперь можно искать новые пути улучшения калькулятора.
Проверив калькулятор, легко убедиться, что он не слишком элегантно обрабатывает отрицательные числа. Например, выражение
–1/2
является ошибочным.
Для того чтобы калькулятор работал корректно, мы должны были бы написать
(0–1)/2
Однако это неприемлемо.
Обычно такие проблемы выявляются на поздних этапах отладки и тестирования. Только тогда можно увидеть, что на самом деле делает программа, и получить информацию, позволяющую уточнить исходные идеи. Планируя проект, целесообразно сэкономить время и извлечь выгоду из наших уроков. Очень часто первая версия поставляется пользователям без необходимых уточнений из-за напряженного расписания и жесткой стратегии управления, которая не позволяет вносить исправления в спецификацию на поздних этапах разработки. Поздние добавления — это кошмар менеджера. На самом деле, когда программа уже достаточно работоспособна, но еще не готова к поставке, еще не поздно внести дополнения; это самый первый момент, когда можно учесть опыт ее использования. Реалистичное расписание должно учитывать это обстоятельство.




