- •И. А. Андрианов, д. В. Кочкин, с. Ю. Ржеуцкая
- •Учебное пособие
- •Оглавление
- •1. Основы языка 8
- •1.2.2 Простые типы данных 13
- •6. Паттерны проектирования 159
- •7. Антипаттерны 211
- •2. Работа с памятью 73
- •3. Основы объектно-ориентированного программирования 87
- •4.Обработка исключений 114
- •5. Шаблонные функции и классы. Библиотека стандартных шаблонов 130
- •9. Методы отладки и оптимизации кода 242
- •1. Основы языка
- •1.1.2 Понятие проекта
- •1.2 Простые типы данных
- •1.2.1 Понятие типа
- •1.2.2 Простые типы данных
- •1.2.3 Внутреннее представление простых типов
- •1.2.4 Ключевое слово typedef. Тип size_t
- •1.3 Константы и переменные
- •1.3.1 Литералы
- •1. Числовые константы:
- •2. Символьные константы:
- •1.3.2 Переменные
- •1.3.3 Описание переменных
- •1.4. Выражения. Преобразование типов
- •1.4.1 Операнды и операции
- •1.4.2 Приоритет операций
- •1.4.3 Преобразование типов
- •1.5 Ветвления и циклы
- •1.5.2 Циклы
- •1.6 Массивы, строки
- •1.6.1 Основные понятия
- •1.6.2 Встроенные массивы
- •1.6.3 Cтроки. Обработка строк с завершающим нулём
- •1.7 Указатели и ссылки. Связь указателей и массивов. Библиотека cstring
- •1.7.1 Понятия указателя и ссылки
- •1.7.2 Связь между массивами и указателями
- •1.7.3 Библиотека cstring
- •1.8 Использование типов vector и string
- •1.8.1 Шаблонный класс vector
- •1.8.2 Класс string
- •1.9 Структуры и объединения. Битовые поля
- •1.10.1 Понятие функции
- •1.10.2 Описание функции и прототип функции
- •1.11 Параметры функции. Способы передачи параметров
- •1.11.1 Параметры функции и глобальные переменные
- •1.11.2 Способы передачи параметров в функцию
- •1.11.3 Передача массивов в функцию
- •1.11.4 Параметры-константы
- •1.11.5 Значения параметров по умолчанию
- •1.12.1 Указатель на функцию
- •1.12.2 Функции с переменным числом параметров
- •1.12.3 Перегрузка функций
- •1.12.4 Встроенные (inline) функции
- •1.13 Рекурсивные функции
- •1.14 Пространства имён
- •1.15 Директивы препроцессора. Макросы
- •2. Работа с памятью
- •2.1 Управление выделением и освобождением памяти
- •2.1.1 Статическое и динамическое выделение памяти
- •2.1.2 Способы динамического выделения и освобождения памяти
- •2.2 Динамические структуры данных
- •2.2.1 Основные понятия
- •2.2.2 Примеры реализации динамических структур на основе указателей
- •3. Основы объектно-ориентированного программирования
- •3.1 Основные понятия ооп
- •3.2.1 Описание класса
- •3.2.2 Область видимости элементов класса. Инкапсуляция
- •3.2.3 Первые примеры
- •3.3. Конструкторы и деструкторы.
- •3.4 Указатель this
- •3.5 Перегрузка операций
- •3.6 Дружественные функции и классы
- •3.7 Статические элементы класса
- •3.8 Наследование и полиморфизм
- •3.8.1. Основные понятия
- •3.8.2 Одиночное наследование
- •3.8.3 Множественное наследование
- •3.8.4 Конструкторы и деструкторы классов-потомков
- •3.9. Полиморфизм при наследовании классов
- •3.9.1 Механизмы раннего и позднего связывания
- •3.9.2 Абстрактные классы
- •4.Обработка исключений
- •4.1 Основные понятия
- •4.2 Перехват исключений
- •4.3 Поиск обработчика исключений. Раскрутка стека.
- •4.4 Повторное возбуждение исключений
- •4.5 "Аппаратные" и "программные" исключения
- •4.6 Стандартные классы исключений
- •4.7 Спецификация исключений, возбуждаемых функцией
- •4.8 Исключения в конструкторах при наследовании
- •4.9. Исключения в деструкторах
- •5. Шаблонные функции и классы. Библиотека стандартных шаблонов
- •5.1 Шаблонные функции
- •5.2 Шаблонные классы
- •5.3 Специализация шаблонов
- •5.4 Шаблонные параметры шаблонов
- •5.6 Использование шаблонов для вычислений на этапе компиляции
- •5.5 Разработка шаблонных классов с настраиваемой функциональностью
- •5.7 Библиотека стандартных шаблонов (stl) – основные понятия
- •5.8 Последовательные контейнеры. Итераторы
- •5.9. Адаптеры контейнеров
- •5.10 Ассоциативные контейнеры
- •5.11 Алгоритмы
- •6. Паттерны проектирования
- •6.1 Порождающие шаблоны
- •6.2 Структурные шаблоны
- •6.3 Шаблоны поведения
- •6.4 Шаблон "фабричный метод" (Factory method)
- •6.5 Шаблон "одиночка" (Singleton)
- •6.6 Шаблон "итератор" (Iterator)
- •6.7 Шаблон "наблюдатель" (Observer)
- •6.8 Шаблон "пул объектов" (Object pool)
- •6.9 Шаблон "команда" (Command)
- •6. 10 Шаблон "посетитель" (Visitor)
- •6.11 Дополнительные задания
- •6.11.1 Шаблон Iterator
- •6.11.2 Шаблон Observer
- •6.11.3 Шаблоны Command и Observer
- •6.11.5 Шаблон Visitor
- •6.11.5 Разработка класса − контейнера
- •6.11.6 Оценка производительности кода
- •7. Антипаттерны
- •7.1 Программирование методом копирования и вставки (Copy-Paste Programming)
- •7.2 Спагетти-код (Spaghetti code)
- •7.3 Магические числа (Magic numbers)
- •7.4 Бездумное комментирование
- •7.5 Жесткое кодирование (Hard code)
- •7.6 Мягкое кодирование (Soft code)
- •7.7 Золотой молоток (Golden hammer)
- •7.8 Слепая вера (Blind faith)
- •7.9 Ненужная сложность (Accidental complexity)
- •7.10 Божественный объект (God Object)
- •7.11 Лодочный якорь (Boat anchor)
- •7.12 Поток лавы (Lava flow)
- •7.13 Изобретение велосипеда (Reinventing the wheel)
- •7.14 Программирование перебором (Programming by permutation)
- •8.1 Выведение типов
- •8.2 Списки инициализации
- •8.3 Улучшение процесса инициализации объектов
- •8.4 Цикл for по коллекции
- •8.5 Лямбда-функции
- •8.6 Константа нулевого указателя nullptr
- •8.7 "Умные" указатели
- •9. Методы отладки и оптимизации кода
- •9.1 Отладка кода
- •9.1.1 Основные этапы отладки
- •9.1.2 Инструменты и приёмы отладки
- •9.2 Оптимизация кода
- •9.2.1 Рекомендации по выполнению оптимизации
- •9.2.2 Методики оптимизации кода
- •Заключение
- •Библиографический список
9. Методы отладки и оптимизации кода
Отладка кода и его оптимизация − весьма обширные области, достойные изучения в отдельных учебных курсах. Цель данной главы − лишь вкратце познакомить читателя с данными вопросами.
9.1 Отладка кода
Отладкой называется процесс определения и устранения причин ошибок в программе. Этим она отличается от тестирования, направленного на обнаружение ошибок.
Наверное, лучший способ отладки − изначально писать программы без ошибок. К сожалению, совсем избежать появления ошибок в достаточно большом коде невозможно. Однако, вероятность их появления можно значительно уменьшить путём тщательного анализа требований, грамотного проектирования, соблюдения правил написания высококачественного кода. Наверное, не вызывает сомнений, что в хорошо структурированном коде с продуманной иерархией классов, адекватными названиями идентификаторов и хорошими комментариями количество ошибок будет меньше, а их поиск − проще. Подробнее о написании высококачественного кода (а также об отладке, тестировании, оптимизации и других темах) можно прочитать, например, в [11].
9.1.1 Основные этапы отладки
Основные этапы отладки включают:
1. Обнаружение и стабилизация дефекта
2. Определение источника и причины ошибки
3. Исправление ошибки
4. Тестирование исправления
Обнаружение ошибок относится больше к этапу тестирования, чем отладки (что выходит за рамки данной книги). Однако, зачастую случается, что в процессе отладки одной ошибки мы находим ещё несколько других. В основном же первый этап отладки заключается в стабилизации дефекта, то есть в том, чтобы обеспечить надёжное возникновение ошибки. Если мы обнаружили входные данные, на которых программа всегда выдаёт неправильный результат (или, например, конкретную последовательность нажатий кнопок, при которой программа стабильно "падает"), то остальная часть отладки − уже дело техники: рано или поздно мы эту ошибку устраним.
Сложнее всего бороться с плавающими дефектами, которые проявляются, казалось бы, в случайные моменты времени. Такие дефекты могут возникать, например, из-за отсутствия инициализации переменных, ошибок при работе с памятью, неправильной синхронизации потоков и др.
Этап стабилизации не ограничивается лишь нахождением тестовых входных данных или условий, стабильно приводящих к ошибке. Обычно желательно также выполнить упрощение теста, чтобы получить как можно более простой тест, вызывающий ошибку.
Например, пусть во время тестирования обнаружилось, что наша программа неверно сортирует входной массив из 100 чисел. Выполнять отладку со столь большим массивом не очень удобно. Можно попытаться взять первые 50 элементов и посмотреть, возникнет ли теперь ошибка. Если нет, можно попробовать взять последние 50 элементов, и т.д. Чем проще тест удастся получить, тем обычно легче потом найти источник ошибки.
На следующем этапе выполняется определение источника и причины ошибки. Для этого можно использовать различные подходы и их комбинации.
Проанализировав, на каких входных данных возникает ошибка и как именно она проявляется, можно сформулировать одну или несколько гипотез (предположений) о том, что именно пошло не так. Для проверки своей гипотезы можно сделать, например, следующее:
• придумать несколько дополнительных тестов, на которых данная ошибка должна возникать и на которых не должна, и проверить, так ли это;
• внимательно просмотреть часть программного кода, которая, по вашему предположению, содержит ошибку, и постараться её обнаружить (так называемый "метод пристального взгляда").
Если у вас нет адекватных гипотез о причинах ошибки (либо согласно вашей гипотезе ошибка может располагаться в слишком крупной области кода), то стоит попытаться сократить подозрительную область. Для локализации фрагмента кода с ошибкой используются разные приёмы − отладочная печать, трассировка (пошаговое выполнение под отладчиком), метод "разделяй и властвуй", применение макроса assert и др. − подробнее смотрите в следующем подразделе. Чем точнее удастся локализовать расположение дефекта в коде, тем проще его обнаружить и устранить.
На следующем этапе производится устранение ошибки. Стоит дать совет: вначале следует полностью понять её причину. Попытка устранить дефект, не разобравшись к проблеме до конца, приведёт к устранению симптомов проблемы, а не её самой. Проблема всё равно проявится позже − но, возможно, уже у конечных пользователей.
Например, путь на всех тестовых данных программа выдаёт правильный ответ, и лишь на числе 31 выдаёт ответ на единицу меньше, чем надо. Совершенно неправильно исправлять ошибку так: if (x == 31) answer++; Вместо этого надо разобраться, в чём дело. Скорей всего, программа неправильно работает и на некоторых других данных, которых просто не оказалось в тестах.
Устранив ошибку, имеет смысл подумать, нет ли в программе других ошибок такого же типа. Например, если ошибка была вызвана вашим неправильным пониманием работы какой-то стандартной функции, то вполне вероятно, что эта ошибка встречается во всех местах, где вы эту функцию используете.
На последнем этапе следует выполнить тестирование исправления. Нужно проверить, действительно ли ваше исправление решило проблему. Важно отметить, что после выполнения отладки не помешает также выполнить тестирование работы всей программы в целом. Часто случается, что в процессе исправления одной ошибки в программу случайно добавляются другие, в результате чего программа будет работать неверно на других входных данных. Такое тестирование обычно проводят в автоматическом режиме с помощью специальных программных средств.
