План занятия по МДК.01.01. Системное программирование на тему Понятие об ошибке программного обеспечения. Источники ошибок

План занятия №___. Понятие об ошибке программного обеспечения. Источники ошибок

Учебная дисциплина: МДК 01.01 Системное программирование

Тема занятия: Понятие об ошибке программного обеспечения. Источники ошибок.

Цели занятия:
Образовательные:
- изучить виды ошибок программного обеспечения, некоторые источники ошибок;
Воспитательные:
- развитие усидчивости;
Развивающие:
- развитие внимательности, логического мышления при нахождении ошибок во фрагментах программ.

Оснащение: план занятия, презентация.

Ход занятия:
1. Организационный момент. (2 мин).
Отметить присутствующих.

2. Актуализация опорных знаний
Хотелось бы сказать, что задача программиста устранить все ошибки. Разумеется, это было бы прекрасно, но часто этот идеал оказывается недостижимым. На самом деле для реальных программ трудно сказать, что подразумевается под выражением "все ошибки". Например, если во время выполнения своей программы мы выдернем электрический шнур из розетки, то следует ли это рассматривать как ошибку и предусмотреть ее обработку? Во многих случаях совершенно очевидно, что ответ будет отрицательным, но в программе медицинского мониторинга или в программе, управляющей телефонными переключениями, это уже не так. В этих ситуациях пользователь вполне обоснованно может потребовать, чтобы система, частью которой является ваша программа, продолжала выполнять осмысленные действия, даже если исчезло энергопитание компьютера или космические лучи повредили его память. Основной вопрос заключается в следующем: должна ли программа сама обнаруживать ошибки?

3. Изучение нового материала.

(Страуструп, Бьярне, Программирование: принципы и практика использования C++. : Пер. с англ. М. : ООО "И.Д. Вильяме", 2011. 1248 с.: ил. Парал. тит. англ., стр.161)
При разработке программ ошибки просто неизбежны, хотя окончательный вариант программы должен быть безошибочным или, по крайней мере, не содержать неприемлемых ошибок. Существует множество способов классификации ошибок. Рассмотрим пример.
Классификация ошибок:
Ошибки во время компиляции. Это ошибки, обнаруженные компилятором. Их можно подразделить на категории в зависимости от того, какие правила языка он нарушают:
синтаксические ошибки;
ошибки, связанные с типами.
Ошибки во время редактирования связей. Это ошибки, обнаруженные редактором связей при попытке объединить объектные файлы в выполняемый модуль.
Ошибки во время выполнения. Это ошибки, обнаруженные в ходе контрольных проверок выполняемого модуля. Эти ошибки подразделяются на следующие категории:
ошибки, обнаруженные компьютером (аппаратным обеспечением и/или операционной системой);
ошибки, обнаруженные с помощью библиотеки (например, стандартной);
ошибки, обнаруженные с помощью программы пользователя.
Логические ошибки. Это ошибки, найденные программистом в поисках причины неправильных результатов.

Ошибки периода компиляции – ошибки, связанные с нарушением синтаксиса программирования или неверным набором текста программы.
Ошибки периода выполнения – связаны, в основном, с неверной формулировкой алгоритма решения задачи, либо с неправильной его записью на алгоритмическом языке.

Если не указано явно, будем предполагать, что ваша программа удовлетворяет следующим условиям.
1. Должна вычислять желаемые результаты при всех допустимых входных данных.
2. Должна выдавать осмысленные сообщения обо всех неправильных входных
данных.
3. Не обязана обрабатывать ошибки аппаратного обеспечения.
4. Не обязана обрабатывать ошибки программного обеспечения.
5. Должна завершать работу после обнаружения ошибки.

Предположения 1 и 2 являются частью основных профессиональных требований, а профессионализм это именно то, к чему мы стремимся. Даже если мы не всегда соответствуем идеалу на 100%, он должен существовать.
При создании программы ошибки естественны и неизбежны. Вопрос лишь в том, как с ними справиться. По нашему мнению, при разработке серьезного программного обеспечения попытки обойти, найти и исправить ошибки занимают более 90% времени. Для программ, безопасность работы которых является первоочередной задачей, эти усилия займут еще больше времени. В маленьких программах легко избежать ошибок, но в больших вероятность ошибок возрастает.
Мы предлагаем три подхода к разработке приемлемого программного обеспечения.
Организовать программное обеспечение так, чтобы минимизировать количество ошибок.
Исключить большинство ошибок в ходе отладки и тестирования.
Убедиться, что оставшиеся ошибки не серьезны.

Источники ошибок
Перечислим несколько источников ошибок.
Плохая спецификация. Если мы слабо представляем себе, что должна делать программа, то вряд ли сможем адекватно проверить все ее "темные углы" и убедиться, что все варианты обрабатываются правильно (т.е. что при любом входном наборе данных мы получим либо правильный ответ, либо осмысленное сообщение об ошибке).
Неполные программы. В ходе разработки неизбежно возникают варианты, которые мы не предусмотрели. Наша цель убедиться, что все варианты обработаны правильно.
Непредусмотренные аргументы. Функции принимают аргументы. Если функция принимает аргумент, который не был предусмотрен, то возникнет проблема, как, например, при вызове стандартной библиотечной функции извлечения корня из -1,2: sqrt(-1.2). Поскольку функция sqrt() получает положительную переменную типа double, в этом случае она не сможет вернуть правильный результат.
Непредусмотренные входные данные. Обычно программы считывают данные (с клавиатуры, из файлов, из средств графического пользовательского интерфейса, из сетевых соединений и т.д.). Как правило, программы выдвигают к входным данным много требований, например, чтобы пользователь ввел число. А что, если пользователь введет не ожидаемое целое число, а строку
Неожиданное состояние. Большинство программ хранит большое количество данных ("состояний"), предназначенных для разных частей системы. К их числу относятся списки адресов, каталоги телефонов и данные о температуре, записанные в объекты типа vector. Что произойдет, если эти данные окажутся
неполными или неправильными? В этом случае разные части программы должны сохранять управляемость.
Логические ошибки. Эти ошибки приводят к тому, что программа просто делает не то, что от нее ожидается; мы должны найти и исправить эти ошибки.

Ошибки во время компиляции
Когда вы пишете программы, на первой линии защиты от ошибок находится компилятор. Перед тем как приступить к генерации кода, компилятор анализирует его в поисках синтаксических ошибок и опечаток. Только если компилятор убедится, что программа полностью соответствует спецификациям языка, он разрешит ее дальнейшую обработку. Многие ошибки, которые обнаруживает компилятор, относятся к категории "грубых ошибок", представляющих собой ошибки, связанные с типами, или результат неполного редактирования кода. Другие ошибки являются результатом плохого понимания взаимодействия частей нашей программы. Новичкам компилятор часто кажется маловажным, но по мере изучения свойств языка и особенно его системы типов вы по достоинству оцените способности компилятора выявлять проблемы, которые в противном случае заставили бы вас часами ломать голову.

Пример: вызов функции
int area(int length, int width); // вычисление площади прямоугольника

Синтаксические ошибки
Что произойдет, если мы вызовем функцию area () следующим образом:
int si = area(7,2; // ошибка: пропущена скобка )
int si = area(7,2) // ошибка: пропущена точка с запятой ;
Int s3 = агеа(7,2); // ошибка: Int это не тип
int s4 = area('7,2); // ошибка: добавлен апостроф
Каждая из этих строк содержит синтаксическую ошибку; иначе говоря, они не соответствуют грамматике языка C++, поэтому компилятор их отклоняет. К сожалению, синтаксические ошибки не всегда можно описать так, чтобы программист легко понял, в чем дело. Это объясняется тем, что компилятор должен проанализировать немного более крупный фрагмент текста, чтобы понять, действительно ли он обнаружил ошибку. В результате даже самые простые синтаксические ошибки (в которые даже невозможно поверить) часто описываются довольно запутанно, и при этом компилятор ссылается на строку, которая расположена в программе немного дальше, чем сама ошибка. Итак, если вы не видите ничего неправильного в строке, на которую ссылается компилятор, проверьте предшествующие строки программы.

Обратите внимание на то, что компилятор не знает, что именно вы пытаетесь сделать, поэтому формулирует сообщения об ошибках с учетом того, что вы на самом деле сделали, а не того, что намеревались сделать. Например, обнаружив ошибочное объявление переменной sЗ, компилятор вряд ли напишет что-то вроде следующей фразы:
"Вы неправильно написали слово Int; не сл
·едует употреблять прописную букву I."
Скорее, он выразится так:
"Синтаксическая ошибка: пропущена «;» перед идентификатором “s3”
"У переменной s3 пропущен идентификатор класса или типа"
"Неправильный идентификатор класса или типа “int”
Такие сообщения выглядят туманными, пока вы не научитесь их понимать и использовать. Разные компиляторы могут выдавать разные сообщения, анализируя один и тот же код. В общем, все эти сообщения можно перевести так:
"Перед переменной s3 сделана синтаксическая ошибка, и надо что-то сделать либо с типом int, либо с переменной s3"
Поняв это, уже нетрудно решить проблему.

Ошибки, связанные с типами
После того как вы устраните синтаксические ошибки, компилятор начнет выдавать сообщения об ошибках, связанных с типами; иначе говоря, он сообщит о несоответствиях между объявленными типами (или о типах, которые вы забыли объявить) ваших переменных, функций и так далее и типами значений и выражений, которые вы им присваиваете, передаете в качестве аргументов и т.д.
int x0 = arena(7,2); // ошибка: необъявленная функция
int xl = area(7); // ошибка: неправильное количество аргументов
int x2 = area("seven",2) ; // ошибка: первый аргумент
// имеет неправильный тип

Не ошибки
Работая с компилятором, вы в какой-то момент захотите, чтобы он угадывал ваши намерения; иначе говоря, захотите, чтобы некоторые ошибки он не считал таковыми. Это естественно. Однако удивительно то, что по мере накопления опыта вы захотите, чтобы компилятор был более придирчивым и браковал больше, а не меньше выражений. Рассмотрим пример.
1) int х4 = аrеа(10,-7); // ОК: но что представляет собой прямоугольник,
// у которого ширина равна минус 7?
2) int х5 = area(10.7,9.3); // ОК: но на самом деле вызывается аrеа(10,9)
3) char х6 = area (100, 9999); // ОК: но результат будет усечен
1) Компилятор не выдаст никаких сообщений о переменной х4. С его точки зрения вызов area(10,-7) является правильным: функция агеа() запрашивает два целых числа, и вы их ей передаете; никто не говорил, что они должны быть положительными.
2) Относительно переменной х5 хороший компилятор должен был бы предупредить, что значения типа double, равные 10.7 и 9.3, будут преобразованы в значения типа int, равные 10 и 9. Однако (устаревшие) правила языка утверждают, что вы можете неявно преобразовать переменную типа double в переменную типа int, поэтому у компилятора нет никаких оснований отвергать вызов area(10.7,9.3).
3) Инициализация переменной х6 представляет собой вариант той же проблемы, что и вызов area(10.7,9.3). Значение типа int, возвращенное после вызова аrеа( 100,9999), вероятно, равное 9999, будет присвоено переменной типа char. В итоге, скорее всего, в переменную х6 будет записано "усеченное" значение -36. И опять-таки хороший компилятор должен выдать предупреждение, даже если устаревшие правила языка позволяют ему не делать этого.
Предотвратить ошибку при вызове функции area (x, у) в модуле main() относительно просто:
if (x<=0) error("неположительное х");
if (у<=0) error("неположительное у");
int area_1 = area(x,у);

Ошибки во время редактирования связей
Любая программа состоит из нескольких отдельно компилируемых частей, которые называют единицами трансляции (translation units). Каждая функция в программе должна быть объявлена с теми же самыми типами, которые указаны во всех единицах трансляции, откуда она вызывается. Для этого используются заголовочные файлы. Кроме того, каждая функция должна быть объявлена в программе только один раз. Если хотя бы одно из этих правил нарушено, то редактор связей выдаст ошибку. Способы исправления ошибок во время редактирования связей рассматриваются в разделе 8.3. А пока рассмотрим пример программы, которая порождает типичную ошибку на этапе редактирования связей.
int area(int length, int width); // вычисляет площадь прямоугольника
int main()
{
int x = area(2,3);
}
Если функция area () не объявлена в другом исходном файле и не связана с нашим файлом с помощью редактора связей, то он сообщит об отсутствии объявления функции area ().

Ошибки во время выполнения программы
Если программа не содержит ошибок, которые можно обнаружить на этапах компиляции и редактирования связей, то она выполняется. Здесь-то и начинаются настоящие приключения. При написании программы можно выявить и устранить ошибки, но исправить ошибку, обнаруженную на этапе выполнения программы, не так легко.

Сообщения об ошибках
Рассмотрим немного иной вопрос: что делать, если вы проверили набор аргументов и обнаружили ошибку? Иногда можно вернуть сообщение "Неправильное значение". Рассмотрим пример.
// Попросим пользователя ввести да или нет;
// Символ 'b' означает неверный ответ (т.е. ни да ни нет)
char ask_user(string question)
{
cout << question « “? (да или нет)\n”;
string answer = “ ”;
сin >> answer;
if (answer =="y" || answer== "yes") return 'y';
if (answer ==”n” jj answer=="no") return n’;
return b’; // 'b', если "ответ неверный"
}
// Вычисляет площадь прямоугольника;
// возвращает -1, если аргумент неправильный
int area(int length, int width)
if (length<=0 || width <=0) return -1;
return length*width;
}

Существует другой способ решить описанную проблему: использовать исключения (exceptions)

4. Закрепление нового материала.
Типичные ошибки при записи условного оператора.
1.4.3 Типичные ошибки
if(x==1) // правильно!
{
y=x+3;
z=y*5;
}
if(x=1) // неправильно!
// выполняется всегда!
{
y=x+3;
z=y*5;
}
ошибочное употребление операции присваивания = вместо операции равенства ==
if(x==1); // неправильно!
// выполняется всегда!
{
y=x+3;
z=y*5;
}
эквивалентно коду:
if(x==1)
{
[пустой оператор];
}
y=x+3;
z=y*5;
if(x==1) // неправильно!
y=x+3;
z=y*5;



отсутствуют фигурные скобки, хотя в условии задумано больше одного оператора
эквивалентно коду:
if(x==1)
{
y=x+3;
}
z=y*5;


5. Самостоятельная работа. (Можно решить на следующем занятии)

Выполнение заданий: найти и исправить ошибки во фрагментах программы.
Обработка ошибок
1 вариант
1. Назовите некоторые советы по отладке программы: что необходимо выполнять при написании кода программы, чтобы было легко понять назначение фрагментов кода.
2. Найдите ошибки в следующих кодах программы и напишите верный вариант.
cout << "Привет, << name << “\n”;
for (int i=0; i<=25; i++)
{
if (a>0) { /* операторы 1*/ }
else { /* операторы 2*/
}
if (b<=0 y = abs(b);
if (b<=0 && b>-25 y = abs(b;
int b=15.6;
double a=7.8;
z:=a+b*10;
coUt>> “Введите название книги: ”;
int mas[3];
mas[0]=15;
mas[3]=23;
cout << "Enter two integers: ";
cin >> x;
cin>>у;
int x, у;
cout << "The sum of " << x << " and " << у << " is " << x + у << '\n';
if (age >= 65); cout<<"Age is greater than or equal to 65\n"); else cout<<"Age is less than 65\n");
int ch;
ch1=35;
х = sqrT(y+10)+2
z = х+3;
for (int k = 0; k<=max; ++l)
cout << “k=" << k << “\n”;
Int s3;
s3=15*5-2;
int pr(int a, int b, int c); // периметр треугольника
int main()
{
...
int x2 = pr(2,3.3,4.5);
int x1 = pr(2,3,3);
cout>>”perimetr3=”<cout>>pr(“2”,“3”,“3.5”);
...
}
Обработка ошибок
2 вариант
1. Назовите некоторые виды ошибок и коротко опишите их: где и когда возникают.
2. Найдите ошибки в следующих кодах программы и напишите верный вариант.
cout << "New word is << str << \n”;
for (k=-5; k<=15; k++)
{
if (m==0) { /* операторы 1*/ }
else /* операторы 2*/}
}
if (a>0 z = pow(a,5);
if (a>0 || b>0 z = b+pow(a,5;
double x=3.8;
int y=5,6;
w:=x-5*y;
int kolich;
ciN<< kolich;
int massiv[5];
massiv[0]=10;
massiv[5]=35;
cout << "Vvedite cenu i kolichestvo monitorov: ";
cin >> c;
cin>>k;
double c;
int k ;
cout << "Obschaya cena" << " ravna " << x * у << '\n';
if (rost < 185); cout<<"Height is less than 185\n"); else cout<<"Height is greater than 185\n");
double kol;
koll=21;
х = Sin(y+10)+2
z = х+3;
for (int n = 0; n>min; ++m) cout << “n=" << n << “\n”;
Double a1;
a1=21*4/9;
int pr(int a, int b, int c); // периметр треугольника
int main()
{
...
int x2 = pr(2,3.3,4.5);
int x1 = pr(2,3,3);
cout>>”perimetr3=”<cout>>pr(“2”,“3”,“3.5”);
...
}


Подведение итогов занятия: выводы, оценки за ответы на вопросы, за решение заданий.

7. Д/з: Выучить основные источники ошибок , СР №25: написать примеры возможных ошибок для нескольких операторов

Подпись преподавателя ________