Учебное пособие по курсу «Технология программирования»

Вид материалаУчебное пособие

Содержание


Работа debugger-а влияет на результаты работы программы!
1. Всегда имейте «отскок» назад.
2. Помните: любая живая драная кошка страшнее бумажного тигра.
3. Сначала работающая программа, а потом повязка «бантиков».
4. Сначала работающая программа, а потом оптимизация.
5. Удобно должно быть пользователю при работе с вашей программой, а не вам при отладке
6. Уже при написании программы планируй модификацию программы.
7. Сохраняйте отладочную версию программы.
8. Помните, что вы должны сдать только работающую версию заказанной пользователем программы.
Подобный материал:
1   2   3   4   5   6   7   8   9   10

8. Отладка.



Большинство руководств посвящено методам написания программ. Там рассказано, как правильно писать программы. Можно сказать, что квинтессенцией всех этих методов было структурное программирование. В той или иной степени, но методы структурного программирования применяют все практикующие программисты. Однако, всем прекрасно известно, что за написанием программы следует отладка. Человек такое существо, которое не может написать программу без ошибки. Таким образом, у любого программиста встаёт вопрос, как найти ту ошибку, из-за которой программа не работает или, другими словами, какие существуют методы отладки программ?

Для начала отметим, что методы и приёмы отладки существенно зависят от:

а) используемых средств программирования: операционная система, среда программирования, язык, технические средства;

б) программируемой задачи;

в) выделенных ресурсов;

г) целей отладки/программы;

и, конечно же,

д) личности программиста: его способностей, опыта и просто здоровья.

Ясно, что отладка программы в коде и отладка программы написанной на алгоритмическом языке существенно отличаются друг от друга. Стоит ли говорить, что отладка в недружественной операционной системе (Dos) и в дружественной (Windows – Cи++Builder) предполагают разные действия программиста. Цели, на которые ориентирована операционная среда, сильно влияют на способы разработки и отладки программ. К примеру: первоначально не предполагалось, что под Windows будут серьёзные разработки ориентированные не на документооборот. Исходно считалось, что Windows – система, управляющая документами в стенах offisa. Даже появление Internet, не заставило раработчиков Windows, сменить парадигму. В результате система Visual Cи++ значительно сложнее Cи++Buildera и менее удобна в использовании. Причём, парадокс, система Windows ориентирована на графический интерфейс, но именно интерфейс под Visual Cи++ писать сложнее, чем консольное приложение. Знатоки обеих систем (Visual Cи++ и Cи++Builder) имеют возможность сравнить.

Я бы ещё раз обратил внимание читателей на то, как первоначально выдвинутые цели, последовательно реализованные, неумолимо приводят к последствиям, которых разработчики не хотели и, более того, стремились избежать.
В общем-то, если вы прошли полпути, читай наполовину реализовали планируемую систему, и даже уже поняли ошибочность некоторых, а может и всех, исходных установок, решиться на повторение всего пути сначала под флагом новых парадигм очень трудно, особенно если вам никто не восстанавливал потраченные ресурсы, а сроки сдачи готового продукта никто не изменял, а конкуренты поджимают.

Другими словами, реализуя проект (отлаживая программы) вы должны как можно чётче представлять конечные цели разрабатываемого продукта. Требования к программам, предназначенным на продажу, программам для внутреннего использования или программ «личного» использования совершенно разные, это в свою очередь обязательно отложит «отпечаток» на отладку вашей программы. Важно понимать, для кого предназначена отлаживаемая программа. Если вы отлаживаете программу лично для себя, то, как правило, максимально сокращаете свои усилия по отладке интерфейса (если, конечно, целью программы не является получение удобного интерфейса): вы стараетесь получить результат минимальными усилиями. Зная, на что рассчитана ваша программа, вы, очень часто, пропускаете, не реализуя, множество технологических проверок и почти всегда подобные программы не документируются.

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

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

Личность программиста: как мы уже ранее отмечали, универсалы встречаются нечасто. Обычно, программист специализируется на одних видах задач – вычислительных, “асушных” и т.д. Программист может самозабвенно заниматься отладкой вычислительной задачи и не справиться с простенькой “асушной” задачей, хотя с алгоритмической точки зрения “асушные” задачи, как правило, проще. Безусловно, бывает и наоборот. Давно замечено, что системщики не любят писать прикладные задачи. Разработчики программного обеспечения, написав и сдав своё ПО, как правило, приступают к другим программным средам, а вот сопровождают их разработанное ПО, чаще всего, совершенно другие люди. Конечно, можно поручить “вычислителю” писать “асушную” задачу, но писать он ёё будет втрое, вчетверо дольше, чем “асушник”, да и реализует порученную задачу коряво. Дело даже не в том, что методы отладки вычислительных задач отличаются от методов отладки других задач, хотя есть и много общего, вычислитель стремится найти истину на кончике пера, а “асушнику” её проще посмотреть на распечатке.


Программист начинает отладку с выбора или построения контрольного примера. Мы предполагаем, что ошибок в контрольном примере нет. Тогда существует несколько вариантов.

1. Задача по своей сути не требует контрольного примера. Например, прекрасная учебная задача о расстановке на шахматной доске восьми ферзей, так чтобы они не били друг друга. Существует 92 решения этой задачи, но в большинстве случаев программист не имеет готового решения. Вручную найти даже одно решение достаточно трудоёмко, да и настоящему программисту будет лень перебирать варианты расстановок, ему проще написать программу. Контрольный пример не нужен, так как решение просто проверяется. Или вы пишете программу решения некоего уравнения. Найденное решение проверяется простой подстановкой найденных корней в решаемое уравнение, другой вопрос, что не выясняется все ли корни этого уравнения найдены.

2. Контрольный пример может быть задан, но он настолько громоздок или его построение настолько трудоёмко, что проще обойтись без контрольного примера. В этих случаях, проверка результатов программы выполняется другими методами, чаще всего сопоставлением результатов работы программы с уже известными практическими данными. Здесь, правда, возникает не простой вопрос: насколько точно данные, полученные программой, должны соответствовать данным, полученным другими методами.

Может быть, что данные, получаемые программой должны удовлетворять неким известным зависимостям и проще проверить удовлетворяют ли результаты работы программы этим зависимостям. Чаще всего для этого также пишется программа.

Работа программы может быть проверена и другой независимой программой,
написанной другим программистом или даже коллективом программистов. Однако это чрезвычайно дорогое удовольствие и заказчики редко идут на заказ одной задачи двум разным исполнителям.

Наконец, можно (но не всегда) задать контрольный пример небольших размеров. Тогда программа отлаживается на этом микропримере, но затем программа,

до сдачи в производство, обязательно должна быть проверена на данных реального объёма. Например, программист написал программу подведения баланса небольшого предприятия и она может прекрасно работать. Однако при переходе на обработку данных крупных предприятий, программе может не хватить ресурсов, например оперативной памяти; или программа станет работать недопустимо долго; или для величины баланса станет не хватать размера отведённых для него переменных: все мы прекрасно помним, что замечательная программа Norton Commander не могла отобразить объём дисков более 2 гигабайт.

3. Контрольный пример есть, но только один. Нередко этого бывает достаточно. Если контрольный пример имеет значительные объёмы, то трудно требовать от заказчика ещё одного контрольного примера.

Тем не менее, всегда предпочтительней иметь несколько контрольных примеров. Если у программиста имеется несколько контрольных примеров, то ему остаётся только разобраться в постановке, написать программу и отладиться.

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

Используйте контрольные примеры, которые находятся ближе всего к среднему. Чем дальше от средних данных, тем менее вероятно появление этих данных в реальности, или по-другому, отлаживайтесь в середине, а не на краях. Отладка на краях, по трудоёмкости, относится к отладке в середине как 5 : 1. На краях больше вероятность ошибки (см. ниже), алгоритмы, используемые на краях, всегда сложнее и более трудоёмки по реализации. Отлаживаясь на краях, вы просто увеличиваете время появления рабочей версии вашей программы. Более того, может случиться, что программа обрабатывает случай, который появляется в одном случае из ста или более, а вот остающиеся 99 случаев программа не в состоянии обработать. Убеждение, что если программа работает на краях, то она будет работать и в середине глубоко ошибочно. Очень часто, середину и края обслуживают разные программы. Всё очень просто, на краях, чаще всего, действуют другие законы: например – поверхностное натяжение и капиллярные эффекты.

Таким образом: отлаживаясь, учитывайте краевой эффект. Краевой эффект – явление всеобщее и его законы действуют и в технологии программирования. Ещё раз подчёркиваем: на краях чаще делают ошибки, как при написании программы, так и в алгоритме. Стандартная ошибка при работе с массивом: ошибка на единичку. Например, вы начинаете индексы массива не с нуля, а с единицы, или при работе с Си-строками забываете про последний символ, который собственно и ограничивает нашу строку. Очень часто программист либо обращается к n+1 элементу массива, либо забывает про элемент № n. Обычно, если программист грубо ошибается с индексом, то он быстро это замечает, но при ошибке на единичку, возможны ситуации, которые трудно правильно диагностировать и даже сданные программы иной раз «грешат» такими ненайденными ошибками.


Принципиально процесс отладки начинается уже при написании исходных модулей. Опытный программист, заранее знает “тонкие места” в программе и пишет программу так, чтобы уже на первых запусках программы было видно, как эти “тонкие места” проходятся. При этом, опытный программист не ленится комментировать программу, даже если он пишет её только для себя! Практика показывает, что к любой программе потом приходится возвращаться и, чаще всего, неоднократно. Даже практикующий программист, а он постоянно чего-то пишет, возвращаясь через месяц к своей же программе, скорее всего сходу не вспомнит назначение всех переменных и ход реализации алгоритма. Нередко же приходится разбираться в чужих программах. Общеизвестна старая истина программиста: «Лучше написать свою программу, чем разбираться в чужой». Это естественно: каждый человек, а программисты не исключение, мыслит по-своему. Разбирая чужую программу, вы должны мыслить, так как мыслит её автор, и помощь комментариев здесь неоценима, если, конечно, они есть и написаны так, что могут помочь при разборе текста программы, а не отвлечь от этого. Комментарии должны быть составлены так, чтобы любой мог понять вашу программу. Г. Майерс [6] замечает, что «отсутствие комментариев в тексте программы – веский повод для увольнения программиста».


Как мы ранее отмечали, чем длиньше программа, тем она тяжелее и дольше отлаживается. Старый закон программирования гласит: “Если ваша рука выводит стопятидесятый оператор в программе – бей по ней линейкой”. Однако, современные среды программирования способствуют написанию длинных модулей. Файл, состоящий из нескольких десятков программ в сумме имеющих две три тысячи строк в визуальных средах обычное дело. От того, что этот файл состоит из множества достаточно коротких программ легче не становится – обозримость и читаемость такого файла остаётся плохой. Принимая во внимание, что чаще всего этот файл просматривается на экране монитора, понимаем, что читабельность такой программы стремится к нулю. Отметим, что программы на Си и Си++ вообще плохо читаемы и трудно понимаемы, а принятая технология работ ещё больше её снижает. К сожалению, эту ситуацию приходится принимать как есть. Автор, сам работая в Cи++Builder, много раз пытался уменьшить длину модулей, но выходило себе дороже. Принципиально можно вернуться к системе – одна программа, один модуль, один файл, но усилия, затрачиваемые на это, не окупаются. Каждый новый обработчик события, каждый новый метод отлаживаемого класса возвращает вас к суровой действительности, заставляя вас производить “ручное” разбиение на отдельные файлы. Если вы не имеете мазохистских наклонностей, то вы быстро прекратите это делать.

Тем не менее, только что приведённое правило, остаётся в силе, но понимать его надо в том смысле, что каждая отдельная программа (подпрограмма) должна быть как можно короче. Программист должен уметь разбить программу на серию подпрограмм так, чтобы собранная программа занимала меньше памяти, а выполнялась быстрее.

Разбиение программы на модули преследует ещё одну цель:

Разбиение программы на модули позволяет отладить программу «кусочками», исходя из предположения, что нечто сложенное из «правильно работающих» кусочков также будет правильно работать. На самом деле это далеко не так. Хорошие кирпичи необходимо ещё правильно сложить.

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

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

Как уже отмечалось, необходимо иметь несколько средних наборов входных данных и хотя бы один крайний, но лучше несколько. Программа сначала проверяется на средних наборах, а затем уж на крайних. Здесь вроде бы всё просто: подставляете данные, запускаете программу, ищите ошибку или запускаете программу на другом наборе входных данных. Беда в том, что вы никогда не сможете выполнить вашу программу абсолютно во всей области определения. Число совокупностей данных бесконечно, что потребует бесконечно много времени на абсолютную отладку, ясно, что времени на отладку у вас несколько меньше.

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

Третий момент: и входных и выходных данных может быть очень много. Допустим, что вы отлаживаете программу, выпускающую годовой баланс предприятия и пусть у вас есть полный отладочный пример – предыдущий годовой баланс. Мало того, что все данные потребуется ввести, их надо ввести без ошибки. Если результат работы вашей программы расходится с ручным результатом, то возникает проблема: где ошибка? Во входных данных? В программе? Или при подсчёте ручного варианта. В бухгалтерии нередки случаи, что приходится ориентироваться на заведомо неверный результат ручного подсчёта, не пересчитывать же, в самом деле, предыдущий годовой баланс.

Четвёртый момент: входные данные должны быть правильны и непротиворечивы! Пользователь не всегда может представить полный правильный набор входных данных для отладочного примера. Иногда это невозможно в принципе: математическое моделирование разработки залежи углеводородов. Самое главное, что данные должны быть непротиворечивы. Что здесь имеется ввиду? Когда у вас широкий спектр входных данных, то всегда возможна такая совокупность данных, которая проходит все технологические контроли, но либо невозможна в природе, либо внутренне противоречива, то есть выводит модель за область допустимых решений. Эту внутреннюю противоречивость может не заметить и специалист.

Но может быть и наоборот, специалист привык полагаться на какие-то данные, как на правильные, но программа находит в них ошибку. В этом случае очень тяжело доказать, что ваша программа работает правильно.

Наконец, программа может находить такие совокупности выходных данных, которые непривычны или неизвестны для специалиста, хотя и правильны. Опять-таки встаёт дилемма правильности работы программы.

Таким образом, как ранее отмечалось, отладка начинается уже на правильном выборе контрольных входных и выходных данных.


Как уже отмечалось выше, способ отладки программы прост и состоит в том, что вы многократно выполняете программу на разных входных данных, получаете или не получаете результат, хотя отсутствие результата тоже результат, и начинаете поиск ошибки. Порядок поиска ошибок всегда один: сначала ищете ошибку в своей программе, мы предполагаем что со входными и выходными данными вы разобрались.

Вторая область, где можно найти ошибку – окружение: среда программирования и операционная система. Ошибка в окружении может быть вызвана совокупностей ваших действий, то есть вы можете найти такую последовательность действий в окружении, которая приводит к ошибке. Чаще всего это бывает у новичков. Как правило, подобная ошибка может быть обойдена другой последовательностью действий. Иногда, средний пользователь действительно находит ошибку окружения. Если подобная ошибка найдена, и пользователь не может уйти от неё, то, скорее всего, придётся менять окружение: может статься, что используемое окружение не может использоваться в данном конкретном случае. Но обычно, изменив программу, программист обходит и явную ошибку окружения. Здесь под изменением программы понимается смена метода вычислений или используемого алгоритма. В любом случае речь идёт о принципиальной переписке программы. Обратите внимание, что в случае обнаружения реальной ошибки в окружении вы вынуждены либо серьёзно изменить свою программу, либо сменить окружение.

Но вы можете, ошибочно применить принципы работы одной среды при работе в другой: например Dos – Windows. Отсюда следует, что следующая область поиска ошибки ВЫ сами, а уж потом область окружения. Программист может не понимать принципов работы окружения и пытаться добиться от окружения чего-то несвойственного именно этому окружению. Очень часто, программист, в конце концов, добивается своего, но усилия, потраченные на решение задачи именно этим путём не окупаются в таком случае.

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

Насчёт области поиска ошибки «ВЫ сами». Программист должен быть в состоянии решить поставленную задачу. Универсальные программисты не так часто встречаются. Чаще программист специализируется на каком-либо виде задач, а как мы уже заметили ранее, вид ошибок и методика поиска их зависит от типа задачи. Одно дело отлаживать программу, выпускающую баланс предприятия и, совершенно другое, отлаживать программу, моделирующую разработку залежи.

Примерно 95% ошибок находятся в программе программиста. Для среднего программиста можно сказать: «Все 100% ошибок – ошибки программы!». Поэтому закон прост  «Ищите ошибку у себя в программе!»


Программа, чаще всего состоит из нескольких модулей. Для исправления ошибки, прежде всего, необходимо локализовать ошибку, то есть определить место её возникновения. Попутно заметим, что если вы долго не можете локализовать или отыскать ошибку, то вы её ищете не там. Конечно, это касается программистов с опытом. Хотя мы и заметили, что надо найти место возникновения ошибки, чаще всего мы локализуем место проявления ошибки. Поскольку диагностика с языке Cи++ одна из самых плохих, то само сообщение об ошибке, как правило, ни о чём не говорит. Это не значит, что текст ошибки не стоит читать: если в тексте сообщается о делении на ноль, то так оно и есть, но чаще вам приходит сообщение вроде «недопустимая операция по адресу YYYY» или «память по адресу ХХХХ не может быть read». Поскольку в 90% программист не знает реального распределения памяти, то сообщение мало чем может помочь. IDE, пока вы работаете в режиме отладки указывает модуль и на оператор в тексте модуля, при выполнении которого произошла ошибка. Это облегчает немного жизнь программиста, беда только в том, что нередко это совсем не тот оператор, при выполнении которого совершилась ошибка. Нередко происходит указание на последний оператор в тексте модуля или на вызов модуля, что означает, что ошибка всё-таки проявляется, скорее всего, в этом модуле.

Заметим, что Visual Cи++ и Cи++Builder имеют средства отладки: debugger.
Visual Cи++ готовит два существенно разных варианта программы: отладочный, который может работать только под debugger-ом и рабочий, самостоятельный exe файл. В Cи++Buildere, если вы запускаетесь по умолчанию, вы всегда работаете под debugger-ом, а рабочий модуль сам по себе. Сам по себе debugger не такое уж плохое средство. Debugger многое позволяет: распечатать состояние любой переменной, выполнить пошаговое выполнение программы, вставить дополнительные промежуточные вычисления, завести собственные точки прерывания/останова. Беда в его универсальности

^ Работа debugger-а влияет на результаты работы программы! То есть, не мудрено получить разные результаты работы программы под debugger-ом и без него. Кроме того, в Cи++Builder debugger перехватывает обработку исключительных ситуаций. Что заставляет при наличии собственных блоков try – catch отлаживаться вне debugger-а и IDE. Таким образом, если вы готовы мириться с этим, то может имеет смысл использовать собственно debugger. Чаще всего работа debugger-а сводится к тому, чтобы посмотреть состояние отдельных переменных на тех или иных шагах прогонки программы. Под debugger-ом вы можете сделать это минимальными усилиями, практически не написав ни строки дополнительного кода. Другой разговор, объём вывода: debugger-у то чего, он будет выводить всё, что вы ему укажете. Объём вывода при этом лавинно растёт. Из этого следует: ничего лучше старой доброй промежуточной печати никто не придумал!

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

Промежуточный вывод может выполняться либо на консоль, либо на экран, либо в файл. Всё зависит от предполагаемого объёма вывода. При выводе в файл не следует забывать, что внешний ввод/вывод идёт через буфер, и при внезапном прекращении работы программы последние сообщения могут быть утеряны, оставшись в буфере ввода/вывода. Таким образом, при выводе в файл, можно неверно локализовать точку проявления ошибки. Однако, использование сообщений на экран (MessageDlg или MessageBox и т.п.) довольно точно локализует область проявления ошибки. Разумеется, сообщения должны быть достаточно информативны и иметь собственную идентификацию. Если вы вставите десяток одинаковых сообщений, то трудно ожидать, что вы поймёте, которое сообщение было выполнено последним.

Промежуточная печать только выводит состояние переменных – понимание причин такого состояния удел программиста. Собственно выяснение причин ненормального состояния переменных, ликвидация этих причин и есть отладка. Отсюда мы видим, что конкретные действия программиста уникальны в каждом случае. Становится понятным, что нельзя научить отладке, но можно научится отлаживаться. Примерно как езда на велосипеде: сколько бы вам не рассказывали, как надо ездить на нём, пока вы не сядете на реальный велосипед и не попрактикуетесь, вы ничему не научитесь.

Разумеется, операторы промежуточной печати тоже должны быть без ошибки. Вставка промежуточной печати имеет и ещё один положительный момент: она меняет немного структуру программы и распределение памяти под переменные и программу. Если при этом меняется сообщение об ошибке, или непонятным образом меняется состояние переменных, то ошибка, чаще всего связана именно со структурой программы и распределением памяти. Скорее всего, вы где-то выходите за границы массива. Либо не задаёте начального значения переменных, представьте, что не инициализируете указатель.

При отладке информационных задач программист, как правило, знает какое должно быть состояние переменных. При отладке вычислительных задач нередко промежуточные результаты неизвестны и программисту. Пусть вы отлаживаете программу обращения матрицы. Всё работает, но конечный результат неверен. Скорее всего, промежуточная печать состояния обращаемой матрицы на каждом шаге мало, что даст: программист не знает, какой она должна быть!


Теперь перечислим, наиболее типичные ошибки. В общем-то, мы их уже называли, но сведём всё воедино.


1. Не заданы начальные значения переменных.

Проявляется, чаще всего в том, что при повторном пуске программы часть переменных имеет значения, отличные от первого. Но желательно в промежутке между пусками программы выполнить пуск любой другой программы с целью смены случайного состояния памяти. Иногда возникает проявление при выполнении цикла: при каждом выполнении состояние переменных меняется.

2. Не восстановление начального состояния переменных или окружения:

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

3.Зацикливание программы:

Здесь комментарии излишне.

4. Выход за границы массива:

Проявление может быть каким угодно, всё зависит от действий выполняемых в программе.

5. Ошибка на единичку:

При работе с массивом вы либо выходите за границы массива на один элемент, либо наоборот не доходите до конца массива также на один элемент, аналогично в начале массива. Но ошибка на единичку может проявляться и в том, что некоторой переменной задаётся начальное значение на единичку больше или меньше требуемого или переменная изменяется на значение отличное на единичку от требуемого.

6. Ошибки, связанные с распределением памяти.

Чаще всего программист не освобождает уже не нужную ему память.

Программист не проверил, получил ли он действительно запрашиваемую память.

7. Ошибки, связанные с вводом/выводом:

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

Программист не закрывает вовремя выходного файла.

8.Ошибка в формате представления данных: Чаще всего при работе с данными типа Data и Time, существует несколько форматов представления дат и времени, зависящих от культурной среды и традиций.

Нередко вывод данных форматными функциями Cи++ - printf выполняется также по неправильным форматам int выводится как float и т.п.

Гораздо серьёзнее, когда программист забывает о размере своих переменных и пытается, например, присвоить переменной описанной как short значение 70000.

9.Ошибка в единицах измерения:

Например, стандартные тригонометрические функции Cи++ работают от аргумента в радианах, а не в градусах, как привыкло большинство. Здесь же следует заметить, что входные, а тем более, выходные данные выражаются в единицах и терминах пользователя. Если пользователь привык расстояние мерить в собственных шагах, то так этому и быть. Американцы привыкли расстояние мерить в милях, высоту в футах, а объём жидкостей в галлонах и баррелях, и если ваш планируемый пользователь американец, то будьте добры, выводить всё в этих неудобных единицах.

10.Ошибки, связанные с инструментом программирования: Эти ошибки специфичны и зависят от используемого компилятора или языка. Например, в Cи++ часто забывают указать второй знак равно в операции сравнения, или

программист пытается изменить состояние переменной в подпрограмме при передаче её по значению.

В любом Си++ возможно неявное использование «чужого» имени. Стандартная ситуация: вы используете чей-то namespace, забываете объявить собственное имя, но используете его в программе, по несчастью в используемом пространстве имён уже есть такое имя: поэтому больше фантазируйте, используйте в программе уникальные имена. Если компилятор позволяет не описывать переменные, то возможны ошибки с форматами представления данных и вообще при серьёзной работе могут появляться загадочные ошибки, так как, например вы можете передать в подпрограмму тип int? а он там может использоваться как float, где и как это аукнется, никто заранее сказать не сможет.

11.Ошибки связанные со средой: Например, Cи++Builder «очень не любит», когда деструкторы классов сами освобождают память, ранее запрошенную для объектов класса, пытаясь ещё раз освободить уже освобождённую память.

Опять-таки в C++Builder мною наблюдалось следующее явление: программа замечательно отрабатывает, всё выдаёт правильно, но при завершении/выходе из программы падает, то есть завершается ненормально. Отсюда ясно, что в программе имеется некий достаточно тонкий «криминал», но найти этот «криминал» очень даже непросто


Список типичных ошибок можно продолжать. Например, мы не включили сюда такие ошибки, как описка. Находить описки в большой программе очень тяжело. Ничего кроме внимательности здесь не требуется. Единственное, что можно сказать: что будьте предельно внимательны и аккуратны и стройте имена ваших переменных так, чтобы вероятность описки была минимальной. То есть, если вы имеете набор имён подобный следующему: А, А1, А12, А21, А121 и т.п., то почти наверняка в большой программе вы будете иметь описки. Если вместо А121 вы напишете А12, то для транслятора ошибок не будет, да и вам найти такую ошибку будет очень трудно.


Наконец несколько общих принципов отладки:


^ 1. Всегда имейте «отскок» назад. Если у вас есть более или менее работающий вариант программы, то перед тем как сделать изменения в программе, сделайте копию программы, а лучше две и храните копии в двух разных местах. При наличии «поворотного» пункта в разработке распечатайте программу на бумагу! и храните распечатки подшитыми. Хранить необходимо все версии программы.

При работе с файлами необходимо иметь копии всех файлов, даже тех, которые вы только читаете: никто не гарантирует, что вы не откроете этот файл как новый.

При корректировке чужих программ всегда отмечайте место, время, автора и суть корректировок, чтобы было ясно кто, когда и где сделал корректировку чужой программы и почему.


^ 2. Помните: любая живая драная кошка страшнее бумажного тигра. Если вам ясно как просто сделать работающую программу, пусть даже не совсем удобную, а может быть и не очень красивую, то сделайте её! Очень часто наличие даже такой программы решает пользователю массу проблем. Другой разговор, что впоследствии надо как-то решить проблемы созданные разработкой такой программы. Помните: лучшее – враг хорошего.


^ 3. Сначала работающая программа, а потом повязка «бантиков». «Бантик» это украшение программы: например красивая заставка перед работой программы. Пользователю нужны результаты работы программы, а не «бантики». Повязка ж «бантиков» отнимает много времени и может сделать вашу программу неработоспособной.


^ 4. Сначала работающая программа, а потом оптимизация. Программа должна быть отлажена и работать. Если вы не отладите программу и начнёте её оптимизировать, то может выясниться, что оптимизировать нечего.


^ 5. Удобно должно быть пользователю при работе с вашей программой, а не вам при отладке: но всё-таки …


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


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


^ 7. Сохраняйте отладочную версию программы. Если через какое-то время вам придётся вернуться к своей программе, наличие отладочной версии очень поможет вам, по крайней мере, вам не придётся писать снова все отладочные операторы.


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


Вопросы к главе 8.


1. Что такое отладка?

2. Требования к контрольному примеру.

3. Что такое отладка на краях?

4. Что такое промежуточная печать?

5. Перечислите наиболее типичные ошибки.

6. Перечислите основные принципы отладки.

7. Что такое – ошибка на единичку?

8. Принципы написания комментариев.

9. Разбиение программы на модули. зачем?
  1. Особенности работы с debugger-ом.
  2. Как следует сегодня понимать принцип «делай программу как можно короче»?
  3. Что такое “отскок” назад?