При помощи чего выявляются логические ошибки в программе
Выделяют три типа ошибок: ошибки компиляции, ошибки времени выполнения и логические ошибки.
Ошибки компиляции являются синтаксическими ошибками. Они выдаются при компиляции программы. Программа с синтаксическими ошибками не может быть выполнена.
Ошибки времени выполнения связаны с невозможностью по какой-либо причине выполнить очередное действие, что приводит к аварийному останову программы. Такие ошибки возникают, например, при делении на 0, вычислении логарифма с отрицательным аргументом, переполнении стека при выполнении рекурсивной подпрограммы и т.п.
Логические ошибки возникают при неправильном проектировании алгоритма или по невнимательности при записи или наборе программы. Программа с логическими ошибками может быть выполнена до конца, возможно даже, что при некоторых наборах исходных данных результаты будут правильными.
В процессе устранения логических ошибок можно выделить три этапа:
1. установление факта существования ошибки;
2. локализация ошибки;
3. устранение ошибки.
Существуют 2 метода обнаружения ошибок:
1. статическая (ручная) проверка, которая заключается в анализе программы без выполнения ее на ЭВМ;
2. тестирование - прогон на ЭВМ.
Оба метода требуют наборов тестовых данных, которые должны подбираться параллельно с разработкой алгоритма. В число наборов тестовых данных рекомендуется включить несколько типичных, среди них должны быть корректные и некорректные данные. Тестовые данные должны охватывать предельные случаи. Если в программе есть разветвления, то необходимы наборы тестовых данных для каждой ветви. Если в программе есть циклы, то данные нужно подобрать так, чтобы цикл выполнялся минимально и максимально возможное число раз.
При отладке программ для практического использования нужно подобрать дополнительные тестовые данные:
1. Получить реальные данные у потенциального пользователя.
2. Породить случайным образом наборы тестовых данных.
Ручная проверка
Статистика утверждает, что 70% ошибок можно устранить на этапе ручного тестирования.
Опытные программисты знают, что ошибки в программе делятся на два основных типа. Первая разновидность — это баги, которые вылавливаются при компиляции. К ним относятся преимущественно проблемы с синтаксисом, явная несовместимости типов и т.д. Эту разновидность багов исправляют на этапе разработки, так как компилятор «вылетает по ошибке». Их просто невозможно не заметить.
Второй тип – системные или логические ошибки – намного сложнее выявить. Компилятор их не замечает. Программа полностью работоспособна. Но в некоторых случаях она начинает выдавать результаты, отличные от ожидаемых.
Застраховаться полностью от логических ошибок невозможно. Но вполне реально изучить самые распространенные типы таких багов и проверять на них программу на самых ранних этапах.
Алгоритм – основа всех основ
Написание алгоритма – это самый первый этап разработки, когда идеи только обретают форму еще без привязки к языку программирования. Нередко начинающие программисты относятся к созданию алгоритма «спустя рукава» — делают только «общие наброски» или вообще приступают к кодингу сразу без предварительной проработки логики «на бумаге».
Такой подход возможен при решении учебных задач на 10-15 строк кода. Но при работе над серьезным программным продуктом пренебрежение алгоритмом – почти гарантированный путь к логическим ошибкам и катастрофическим результатам.
Как работать с алгоритмом:
- Начинайте с малого . Запишите алгоритм упрощенно, в виде «черных ящиков» (логических блоков без подробностей их работы). Это поможет оценить работоспособность идеи в целом.
- Двигайтесь сверху вниз . Сначала – общая идея «в целом», далее – детализация основных функций и так далее. Не бойтесь ставить «заглушки» и прорабатывать мелкие детали в последнюю очередь. Двигаться сверху вниз проще и с точки зрения логики, и психологически.
- Пишите команды «от имени компьютера». Помните, что вы имеете дело не с человеком, а с компьютером, который буквально выполняет команды и после каждого шага ждет ответа на вопрос «что делать». Например, логический блок «сохранение документа» будет понятен вам, но не компьютеру. Он вполне подойдет на этапе крупных блоков в качестве заглушки. Но далее придется проработать все действия пошагово с учетом выбранного языка программирования.
- Делите код на отдельные модули (блоки) , которые можно будет запускать отдельно друг от друга. Это сильно облегчит как алгоритмизацию, так и процесс отладки.
- Читайте алгоритм «как будто компьютер» . Проверяйте себя на каждом этапе. Главное правило – одинаковые данные всегда должны вести к одинаковым результатам.
Итак, алгоритм написан и проверен со всех сторон. Выбран язык программирования. Начинается процесс кодинга. Давайте разбираться, на что обращать особое внимание.
«Не туда положил»: о типах данных
Здесь проблемы возникают в двух случаях:
Самый известный пример подобной ошибки – деление двух целых чисел с остатком.
Здесь проблема в другом: компилятор сначала проводит целочисленное деление, так как определяет переменные A и B как относящиеся к типу int. И полученный результат отправляет в переменную C (тип float). Целое значение (32 разряда) прекрасно помещается в 64-разрядный float, отведенный под хранение результата. Компилятор не видит ошибки. А у вас в программе появляются неточные вычисления, которые могут повлечь за собой большие проблемы.
Аналогичным образом компилятор округлит значение до целого и в Python 2. А уже в Python 3 алгоритм преобразования типов сработает иначе: сначала определится тип переменной, куда отправляется значение, а потом будет проводиться деление. После компиляции кода в Python 3 вы получите c=3,125.
Необходимо четко понимать, как работает преобразование типов в выбранном языке программирования. И в случае любых сомнений проверять результаты в отладчике.
Высвобождение ресурсов: до 100% загрузки процессора
Если вы работаете с языком, где реализована автоматическая сборка мусора, внимательно следите за тем, как происходит высвобождение ресурсов. В отдельных случаях эта полезная функция может начать работать во вред: отбирать для себя максимум памяти, загружать дополнительными задачами процессор, замедлять и даже «подвешивать» программу.
Например, в Java этот процесс работает так:
- Виртуальная машина проводит поиск ненужных объектов;
- Составляет из них очередь на удаление;
- По мере продвижения очереди очищает ячейки памяти.
В результате очередь может стать настолько большой, что компьютер перестанет с ней справляться. А до удаления всех ненужных объектов дело может даже не дойти.
Как итог, программа «загрязняет» память служебной информацией. Кроме того, формируется уязвимость: в этой «свалке данных» могут оказаться логины с паролями и другие личные данные.
Намного надежнее своевременно применять функции типа try-with-resources и try-finally. И все ресурсы очищать в том коде, где вы их получили.
И еще: не забывайте закрывать сессии и файлы сразу после того, как они перестают быть нужны. Это должно быть также естественно, как закрыть скобку в коде.
Конфликт потоков: кто первый успеет?
Если программа работает с несколькими потоками одновременно, необходимо исключить ситуацию конфликта потоков. Так бывает, когда процессы наперегонки пытаются работать с общими ресурсами, в итоге нарушают целостность данных и последовательность действий.
Например: первый поток в результате вычислений получает значение 1, отправляет его в переменную. В это время второй поток перехватывает доступ и обнуляет эту переменную. А первый – сохраняет значение. В результате вы планировали запомнить значение 1. А у вас сразу после вычислений сохраняется 0. И далее копятся ошибка за ошибкой.
Чтобы избежать этой проблемы не забывайте при работе с разными потоками ставить блокировки, чтобы они не обращались одновременно к одним и тем же ресурсам. Можно применять и другие методы синхронизации – события, семафоры, критические секции.
Переменные: склонность к глобализации
Эта ошибка популярна у новичков – стремление объявить сразу все переменные и сделать их глобальными. В результате таких действий вы:
- перегружаете ресурсы оборудования;
- получаете множество уязвимостей, которые сложно закрыть;
- усложняете в разы отладку и поиск багов.
На глобальном уровне определяют только необходимый минимум – те самые глобальные переменные, с которыми работают практически все модули. Все остальные объявляйте в тех модулях, где они работают. И не забывайте об идентификаторах ограничения доступа: public, private и protected.
Переполнение буфера в С/С++: «танцы на граблях»
В большинстве современных языках программирования высокого уровня вопрос буферизации решен на автоматическом уровне. Например, Java самостоятельно контролирует размер буфера и определяет границы массивов.
Но нередко для экономии ресурсов программисты используют C-библиотеки. В этом случае очень важно следить за буферизацией. Дело в том, что языки C/С++ очень уязвимы к переполнению буфера. Если он окажется меньше, чем нужно для работы, программа попытается использовать память за пределами выделенного участка. Результат – многочисленные, можно сказать, легендарные ошибки, когда в обрабатываемые данные попадает «неведомый мусор».
Хуже того, это очень известная уязвимость. С 1988 года хакеры пользуются этой «дырой», чтобы подменить адрес возврата в стеке на собственный. Так в программу попадает подставная функция, которая передает управление коду мошенников.
Изучите особенности работы с буфером и методы борьбы с его переполнением, чтобы не пополнить число «танцующих на граблях с 30-летней историей».
Отладка и поиск логических багов
И, напоследок, несколько советов, как выявить проблему, если вы подозреваете, что с программой что-то не так.
- Пользуйтесь возможностями отладчика вашей IDE . Ставьте контрольные точки, отражайте на консоли ход выполнения и значения переменных, переходите в «пошаговый режим» выполнения в наиболее «подозрительных» участках кода. Так вы быстрее сможете локализовать проблему.
- Помните: компилятор может неправильно указывать строку с ошибкой . Если вам повезло, и компилятор помог вам выявить баг, не спешите радоваться. При «завершении с ошибкой» вы видите номер строки, в которой выполнение программы стало невозможным. Если проблема в простейшей опечатке (синтаксис), то строка с багом вам известна. В случае логических ошибок вероятнее всего, проблема появилась на более ранних этапах работы программы. А в указанной строке была попытка использовать ошибочные данные, что и привело к аварийному завершению.
- Старый добрый листинг программы тоже может помочь . Если вы запутались и не знаете, что делать, распечатайте код и попробуйте его «выполнить» как будто вы – и есть компьютер. Шаг за шагом двигайте по командам. Переходите от блока к блоку так, как это делает программа. На каждом этапе вычисляйте и фиксируйте значения переменных (калькулятором пользоваться можно). И сверяйте результаты с ожидаемыми. Все в порядке? Двигайтесь дальше. Что-то не так? Ура! Вы локализовали баг. Можно возвращаться за компьютер и разбираться подробнее в этом фрагменте кода в отладчике.
И самое главное: не бойтесь что-то менять, в том числе, на глобальном уровне. Лучше переписать «сырой» код на раннем этапе разработки практически полностью, чем из-за серьезной логической ошибки терять в скорости и качестве работы программы, пытаясь использовать кучу «заплаток». От ошибок не застрахован никто. Потраченного времени жаль, но это – ваш личный практический опыт. А программа должна работать быстро, надежно и, самое главное, правильно.
Ошибки, которые обнаруживает компилятор, называют синтаксическими ошибками или ошибками компиляции. Синтаксические ошибки являются результатом ошибок в конструкции кода, таких как неправильное написание ключевого слова, пропуск необходимого знака пунктуации или использование открывающей фигурной скобки без соответствующей закрывающей фигурной скобки. Эти ошибки обычно легко обнаружить, поскольку компилятор говорит вам, где они находятся и что стало их причиной. Пример программы с синтаксической ошибкой:
Попытка компиляции приведённого кода:
Будет сообщено о четырёх ошибках, но в действительности программа содержит две ошибки:
- Во второй строке отсутствует ключевое слово void перед main
- Строка Welcome to Java должна быть закрыта закрывающей кавычкой в третьей строчке программы
2. Ошибки во время выполнения
Ошибки во время выполнения – это ошибки, которые приводят к ненормальному обрывы работы программы. Они возникают во время работы программы, если среда обнаруживает операцию, которую невозможно выполнить. Обычно ошибки ввода становятся причинами ошибок во время выполнения. Ошибки ввода возникают, когда программа ожидает от пользователя ввода значения, но пользователь вводит величину, которую программа не может обработать. Например, программа ожидает получение числа, но вместо этого пользователь вводит строку, это приводит к ошибкам в программе, связанным с типами данных.
Другой пример ошибок во время выполнения – это деление на ноль. Это происходит, когда в целочисленном деление делитель равен нулю. Пример программы, которая вызовет ошибку во время выполнения:
3. Логические ошибки
Логические ошибки происходят, когда программа неправильно выполняет то, для чего она была создана. Ошибки этого рода возникают по многим различным причинам. Допустим, вы написали программу, которая конвертирует 35 градусов Цельсия в градусы Фаренгейта следующим образом:
Вы получите 67 градусов по Фаренгейту, что является неверным. Должно быть 95.0. В Java целочисленное деление показывает только часть – дробная часть отсекается, по этой причине в Java 9 / 5 это 1. Для получения правильного результата, нужно использовать 9.0 / 5, что даст результат 1.8.
4. Распространённые ошибки
Пропуск закрывающей фигурной скобки, пропуск точки с запятой, пропуск кавычки для строки и неправильное написание имён – всё это самые распространённые ошибки для новых программистов.
Частые ошибки 1: Пропущенные фигурные скобки
Фигурные скобки используются для обозначения в программе блоков. Каждой открывающей фигурной скобке должна соответствовать закрывающая фигурная скобка. Распространённая ошибка – это пропуск закрывающей фигурной скобки. Чтобы избежать эту ошибки, печатайте закрывающую фигурную скобку всякий раз, когда печатаете открывающую фигурную скобку как показано в следующем примере:
Если вы используете IDE такую как NetBeans и Eclipse, то IDE автоматически вставит закрывающую фигурную скобку каждой введённой вами открывающей фигурной скобки.
Частые ошибки 2: Пропуск точки с запятой
Каждая инструкция заканчивается ограничителем инструкции (;). Часто новые программисты забывают поместить ограничитель инструкции для последней инструкции в блоке как это показано в следующем примере:
Частые ошибки 3: Пропуск кавычки
Строки должны помещаться в кавычки. Часто начинающие программисты забывают поместить кавычку в конце строки как показано в следующем примере:
Если вы используете IDE, такую как NetBeans и Eclipse, то IDE автоматически вставит закрывающую кавычку каждый раз, когда вы ввели открывающую кавычку.
Частые ошибки 4: Неправильное написание имён
Java чувствительная к регистру. Неправильное написание имён – частая ошибка для новых программистов. Например, пишут слово main как Main, а вместо String пишут string. Пример:
Отладка программы – это процесс поиска и устранения ошибок. Часть ошибок формального характера, связанных с нарушением правил записи конструкций языка или отсутствием необходимых описаний, обнаруживает транслятор, производя синтаксический анализ текста программы. Транслятор выявляет ошибки и сообщает о них, указывая их тип и место в программе. Такие ошибки называются ошибками времени трансляции или синтаксическими ошибками.
Ошибочные ситуации могут возникнуть и при выполнении программы, например, деление на нуль или извлечение корня квадратного из отрицательного числа. Такие ошибки называются ошибками времени выполнения.
Программа, не имеющая ошибок трансляции и выполнения, может и не дать верных результатов из-за логических ошибок в алгоритме, т. е. алгоритмических или семантических ошибок. Ошибки подобного рода могут возникнуть на любом этапе разработки программы: постановки задачи, разработке математической модели или алгоритма. Необходим действенный контроль над процессом вычислений, позволяющий предотвращать или своевременно обнаруживать ошибки подобного рода. Для этого используются как качественный анализ задачи, основанный на различного рода интуитивных соображениях и правдоподобных рассуждениях, так и контрольный просчет или тестирование программы.
Тестирование программы – это выполнение программы на наборах исходных данных (тестах), для которых известны результаты, полученные другим методом. Система тестов подбирается таким образом, чтобы
а) проверить все возможные режимы работы программы;
б) по возможности, локализовать ошибку.
При тестировании программы простой и действенный метод дополнительного контроля над ходом её выполнения – получение контрольных точек, т. е. контрольный вывод промежуточных результатов.
Для проверки правильности работы программы иногда полезно также выполнить проверку выполнения условий задачи (например, для алгебраического уравнения найденные корни подставляются в исходное уравнение и проверяются расхождения левой и правой частей).
33. ВИДЫ ОШИБОК В ПРОГРАММАХ
Об ошибках в программе сигнализируют некорректная работоспособность программы либо ее полное невыполнение. В наше время для обозначения ошибки в программе используют термин «Баг» (с англ. Bug-жук).
Есть несколько типов ошибок:
1) Логическая ошибка. Это, пожалуй, наиболее серьезная из всех ошибок. Когда написанная программа на любом языке компилирует и работает правильно, но выдает неправильный вывод, недостаток заключается в логике основного программирования. Это ошибка, которая была унаследована от недостатка в базовом алгоритме. Сама логика, на которой базируется вся программа, является ущербной. Чтобы найти решение такой ошибки нужно фундаментальное изменение алгоритма. Вам нужно начать копать в алгоритмическом уровне, чтобы сузить область поиска такой ошибки. (пример: задача программы вывести сумму двух чисел а и b.
varc,a,b:integer;
2) Синтаксическая ошибка.Каждый компьютерный язык, такой как C, Java, Perl и Python имеет специфический синтаксис, в котором будет написан код. Когда программист не придерживаться "грамматики" спецификациями компьютерного языка, возникнет ошибка синтаксиса. Такого рода ошибки легко устраняются на этапе компиляции.
3) Ошибка компиляции.Компиляция это процесс, в котором программа, написанная на языке высокого уровня, преобразуется в машиночитаемую форму. Многие виды ошибок могут происходить на этом этапе, в том числе и синтаксические ошибки. Иногда, синтаксис исходного кода может быть безупречным, но ошибка компиляции все же может произойти. Это может быть связано с проблемами в самом компиляторе. Эти ошибки исправляются на стадии разработки.
vara:array[1..5] of integer;
6) Ошибки ресурса. Ошибка ресурса возникает, когда значение переменной переполняет максимально допустимое значение. Переполнение буфера, использование неинициализированной переменной, нарушение прав доступа и переполнение стека - примеры некоторых распространенных ошибок.
vara:integer;
7) Ошибка взаимодействия. Они могут возникнуть в связи с несоответствием программного обеспечения с аппаратным интерфейсом или интерфейсом прикладного программирования. В случае веб-приложений, ошибка интерфейса может быть результатом неправильного использования веб-протоколов
Синтаксические ошибки – это ошибки в записи конструкций языка программирования (чисел, переменных, функций, выражений, операторов, меток, подпрограмм).
Семантические ошибки – это ошибки, связанные с неправильным содержанием действий и использованием недопустимых значений величин.
Если вы выполняли все задания для самостоятельной работы, то, наверное, уже заметили, что при написании программного кода допустить ошибку очень просто. Одна из задач разработчика - найти такие ошибки и устранить их (или обеспечить перехват ошибок времени выполнения и нормальную работу приложения даже в случае возникновения этих ошибок).
Все ошибки можно разделить на три большие группы:
- синтаксические (неправильно написан оператор, имя переменной и т. п.). Такие ошибки не требуют больших усилий по их поиску и исправлению. Многие синтаксические ошибки "отлавливаются" редактором кода VBA еще в процессе ввода кода. Об обнаружении других ошибок сообщается в ходе компиляции и запуска программы. При этом компилятор VBA выдает информацию о том, в какой строке кода обнаружена ошибка и в чем она заключается. Рекомендуется проверить данную строку по справке VBA;
- логические. В ходе выполнения программа ведет себя не так, как вы планировали. Главное здесь — найти причину неправильного поведения программы. Обычно для выявления и исправления ошибок такого типа предназначены приемы отладки;
- ошибки времени выполнения (run-time error). Они возникают, когда в процессе выполнения программа столкнулась с проблемой, решить которую она не в состоянии (файл с таким именем уже существует, возник конфликт записей при вставке в базу данных, произведена попытка записать информацию на переполненный диск и т. п.). Заранее предугадать, какая именно неприятность может случиться, очень сложно. Во многом квалификация программиста определяется тем, как он умеет предугадывать возможности возникновения ошибок времени выполнения и обеспечивать
их перехват и обработку.
Если программа делается "для себя" (для автоматизации работы того пользователя, который пишет эту программу), то очень часто перехват ошибок времени выполнения вообще не предусматривается. Возникла ошибка – ничего страшного: открыли программу в отладчике, посмотрели, отчего возникла ошибка, и "исправились". Но если программа пишется для передачи другим пользователям (особенно не очень квалифицированным), то на реализацию обработки ошибок времени выполнения обычно уходит больше времени, чем на создание самой логики программы.
Окна Immediate, Locals и Watch
Главный способ обеспечения безошибочной работы программы - это ее тестирование. При создании крупных программных продуктов на их тестирование часто уходит не меньше времени, чем на создание. Поскольку в наших условиях рассчитывать на то, что тестировать вашу программу будет профессиональный тестер, не приходится, проверять ее придется вам самим. Приведу некоторые советы по тестированию:
- попытайтесь запустить программу при работе с большим количеством документов или когда не открыто ни одного документа;
- посмотрите, как работает программа, когда окно документа развернуто, свернуто или размер его изменен;
- проверьте, как работает программа, когда выделены разные элементы или группы элементов;
- если предусматривается ввод информации, попробуйте специально передать программе неверные значения. Например, если программа ожидает числовых значений, попробуйте ввести строковое значение, значение даты или оставить поле пустым;
- попробуйте прервать работу программы в самый неподходящий момент и потом вновь запустить ее;
- проверьте, как ведет себя программа, когда пропадает сеть, заканчивается свободное место на диске, заканчивается бумага в принтере и т. п.;
- проверьте работу программы под разными версиями Office и операционных систем (в том числе англоязычных и локализованных);
- попробуйте до запуска программы и во время ее работы переставлять системную дату и время, устанавливая самые невероятные значения. Если есть возможность, всегда рекомендуется немного поработать, выполняя обязанности пользователя, для которого создается программа.
Мне очень нравится "диверсионный" подход при тестировании программ. Представьте себе, что вы - вредитель и диверсант, у которого цель- вывести программу из строя. Потом опробуйте те способы, которые вам пришли в голову. Если способ оказался удачным, придумайте для него защиту. Как ни удивительно, но реальная работа пользователей с вашей программой будет очень похожа на действия таких диверсантов.
Читайте также: