Полезные высказывания из книги "Совершенный код" Макконнелла
Средство распространения информации о хороших методиках сыграло бы важную роль.
Для широкого распространения исследовательских разработок требуется от 5 до 15 и более лет.
Ежегодно программистами становятся около 50000 человек.
Число дипломов, вручаемых в отрасли около 35000.
Единственным способом получения информации является изучение горы книг и нескольких сотен технических журналов, дополненное значительным реальным опытом (о конструировании ПО).
Сайт книги cc2e.com/1234.
Конструирование ПО=кодирование.
На конструирование приходится 65% в больших проектах.
Во время конструирования допускается от 50 до 75% ошибок в больших проектах.
Ошибки конструирования дешевле исправлять.
Одними из самых дорогих ошибок в истории, приведшими к убыткам в сотни миллионов долларов, были мелкие ошибки кодирования.
При написании программы только этапа конструирования нельзя избежать.
Почта и сайт автора stevencc@construx.com, stevemcconnell.com.
Книги никогда не создаются в одиночку.
Составляющие разработки ПО за последние 25 лет:
- определение проблемы;
- выработка требований;
- создание плана конструирования;
- разработка архитектуры ПО, или высокоуровневое проектирование;
- детальное проектирование;
- кодирование и отладка;
- блочное тестирование;
- интеграционное тестирование;
- интеграция;
- тестирование системы;
- корректирующее сопровождение.
Проекты бывают формальные, неформальные.
Конструирование не включает определение проблемы.
Конструирование=программирование.
В центре конструирования - кодирование и отладка.
Конкретные задачи, связанные с конструированием:
1.Проверка выполнения условий, необходимых для успешного конструирования;
2.Определение способов последующего тестирования кода;
3.Проектирование и написание классов и методов;
4.Создание и присвоение имён переменным и именованным константам;
5.Выбор управляющих структур и организация блоков команд;
6.Блочное тестирование, интеграционное тестирование и отладка собственного кода;
7.Взаимный обзор кода и низкоуровневых программных структур членами группы;
8."Шлифовка" кода путём его тщательного форматирования и комментирования;
9.Интеграция программных компонентов, созданных по отдельности;
10.Оптимизация кода, направленная на повышение его быстродействия, и снижение степени использования ресурсов.
В конструирование не входят управление, выработка требований, разработка архитектуры, проектирование пользовательского интерфейса, тестирование системы и её сопровождение.
На конструирование обычно уходит 30-80% времени работы.
Требования к приложению и архитектура разрабатываются, чтобы гарантировать эффективность этапа конструирования.
Тестирование системы выполняется после конструирования, чтобы проверить его правильность.
Производительность труда отдельных программистов во время конструирования изменяется в 10-20 раз.
Исходный код - самая свежая документация на ПО.
Тестирование системы должно контролироваться статистически.
Повышение эффективности конструирования позволяет оптимизировать любой проект.
Компетентность в конструировании ПО определяет то, насколько хороший вы программист.
Метафоры позволяют лучше понять разработку ПО.
Использование метафор (аналогий) - моделирование.
Эффективность моделей объясняется их яркостью и концептуальной целостностью.
При чрезмерном обобщении модели вводят в заблуждение.
Хорошие метафоры простые, согласуются с другими метафорами и объясняют многие данные и явления.
Описываемое метафорами поведение предсказуемо и понятно всем людям.
Самая сложная часть программирования - концептуализация проблемы.
90% общего объёма работы на программной системой выполняется после выпуска первой версии.
Популярные метафоры о разработке ПО:
1.Написание письма;
2.Выращивание сельскохозяйственных культур;
3.Процесс формирования жемчужины;
4.Построение дома (невыгодно писать компоненты, которые можно купить готовыми).
Программная система из 1 млн строк требует 69 видов документации, спецификация требований занимает 4000-5000 страниц.
В неудачных проектах конструирование приходится выполнять несколько раз.
Вайнберг "The psychology of computer programming".
Хороших программистов всегда не хватает.
Последовательный подход (вопросы решаются заблаговременно) применяется, когда:
- требования довольно стабильны;
- проект приложения прост и относительно понятен;
- группа разработчиков знакома с прикладной областью;
- проект не связан с особым риском;
- важна долговременная предсказуемость проекта;
- затраты на изменение требований, проекта приложения и кода скорее всего окажутся высокими.
Итеративный подход (вопросы решаются по мере работы) применяется, когда:
- требования относительно непонятны или вам кажется, что они могут оказаться нестабильными по другим причинам;
- проект приложения сложен, не совсем ясен или и то и другое;
- группа разработчиков незнакома с прикладной областью;
- проект сопряжен с высоким риском;
- долговременная предсказуемость проекта не играет особой роли;
- затраты на изменение требований, проекта приложения и кода скорее всего будут низкими.
Итеративные подходы эффективны чаще.
Предварительное условие: ясное формулирование проблемы, которую система должна решать (без намёка на решение).
Процесс программирования:
1) Определение проблемы;
2) Выработка требований;
3) Проектирование архитектуры;
4) Конструирование;
5) Тестирование системы;
6) Будущие улучшения;
Проблема должна быть описана с пользовательской точки зрения (без компьютерных терминов).
Требования подробно описывают, что должна делать программная система.
Функциональность системы определяется пользователем, а не программистом (требования решают споры, сводят к минимуму изменения системы после начала разработки, снижают дополнительные расходы).
Процесс разработки помогает разработчикам и клиентам лучше понять свои потребности.
В среднем проекте требования во время разработки изменяются на 25%, на что приходится 80% повторной работы над проектом.
Изменение требований влечёт пересмотр графика и сметы.
evolutionary prototyping - поставка системы клиенту по частям.
Определить моменты прекращения работы над проектом.
Архитектура описывает какие другие компоненты данный компонент может использовать непосредственно, какие косвенно, а какие вообще не должен использовать; должна описывать организацию классов в подсистемы и обосновывать итоговый вариант.
Интерфейс проектируется на этапе выработки требований либо в архитектуре.
Архитектура должна быть модульной.
Архитектура определяет подходы к безопасности.
В требованиях определяются показатели производительности.
Масштабируемость - возможность системы адаптироваться к росту требований.
Интернационализация - реализация в программе поддержки региональных стандартов.
Локализация - перевод интерфейса программы и реализация в ней поддержки конкретного языка.
Архитектура выражает отношение к избыточной функциональности.
Архитектура чётко описывает стратегию изменений.
В архитектуре должны быть обоснованы важнейшие принятые решения.
Цели должны быть чётко сформулированы.
В архитектуре явно определены области риска, указаны его причины и описаны действия по сведению риска к минимуму.
В проекте без проблем работа над требованиями, архитектурой и предварительным планированием поглощает 20-30% времени.
Программисты, использующие язык, с которым они работали три года или более, примерно на 30% более продуктивны, чем программисты, обладающие аналогичным опытом, но для которых язык является новым.
Программисты, использующие языки высокого уровня, достигают более высокой производительности и создают более качественный код, чем программисты, работающие с языками низкого уровня.
Гипотеза Сапира-Уорфа: способность человека к обдумыванию определённых мыслей зависит от знания слов, при помощи которых можно выразить эту мысль.
Ada, Cobol используются в Минобороны США.
SQL - декларативный язык.
Программируют с использованием языка, а не на языке.
Важный аспект работы проектировщика - анализ конкурирующих характеристик проекта и достижение баланса между ними.
Спроектировать программу можно десятками разных способов.
Существенные свойства - которыми объект должен обладать, чтобы быть именно этим объектом.
Несущественные (акцидентные) свойства - которые не влияют на суть объекта.
Управление сложностью - самый важный аспект разработки ПО.
Э. Дейкстра: ни один человек не обладает интеллектом, способным вместить все детали современной компьютерной программы.Поэтому целью является минимизация объёма программы, о котором нужно думать в конкретный момент времени.
Цель всех методик проектирования ПО - разбиение сложной проблемы на простые фрагменты.
Краткость методов помогает снизить нагрузку на интеллект.
Этому же способствует написание программы в терминах проблемной области, а также работа на самом высоком уровне абстракции.
Как бороться со сложностью? Чаще всего причинами неэффективности являются:
- сложное решение простой проблемы;
- простое, но неверное решение сложной проблемы;
- неадекватное сложное решение сложной проблемы.
Дейкстра: сложность современного ПО обусловлена самой его природой.
Двойственный подход к управлению сложностью:
-старайтесь свести к минимуму объём существенной сложности, с которым придётся работать в каждый конкретный момент времени;
-сдерживайте необязательный рост несущественной сложности.
Внутренние характеристики проекта:
- минимальная сложность;
- простота сопровождения;
- слабое сопряжение;
- расширяемость;
- возможность повторного использования;
- система предусматривает интенсивное использование вспомогательных низкоуровневых классов;
- класс использует минимальное число других классов;
- портируемость;
- минимальная, но полная функциональность (дополнительный код необходимо разработать, проанализировать, протестировать, пересматривать при изменении других фрагментов программы, поддерживать совместимость будущих версий с дополнительным кодом);
- систему можно изучать на отдельных уровнях, игнорируя другие уровни (проектировать дополнительный уровень, скрывающий плохое качество старого кода);
- соответствие стандартным популярным подходам.
Уровни детальности проектирования программной системы:
-вся система;
-разделение системы на подсистемы или пакеты определение взаимодействий между ними (минимальные взаимодействия для облегчения модификации (проще сначала ограничить взаимодействие, а затем сделать его более свободным)).
Отношения между системами должны быть простыми.
В порядке уменьшения простоты:
1) Одна подсистема вызывает методы другой;
2) Одна подсистема содержит классы другой;
3) Наследование классов одной подсистемы от классов другой.
Программа не должна содержать циклических отношений.
Часто используемые подсистемы:
-бизнес-правил;
-пользовательского интерфейса;
-доступа к БД;
-изоляции зависимостей от ОС.
Разделение подсистем на классы обычно требуется во всех проектах.
Объект - динамическая сущность, класс - статическая.
БД: различие между классом и объектом аналогично различию между "схемой" и "экземпляром".
Разделение классов на методы необходимо в каждом проекте и часто оставляется отдельным программистам.
Проектирование методов может включать написание псевдокода, поиск алгоритмов в книгах, размышление над оптимальной организацией фрагментов метода и написание кода.
При проектировании с использованием искусственных объектов и объектов реального мира определите:
-объекты и их атрибуты (методы и данные);
-действия, которые могут быть выполнены над каждый объектом;
-действия, которые каждый объект может выполнять над другими объектами (включение и наследование);
-части каждого объекта, видимые другим объектам, то есть открытые и закрытые части;
-открытый интерфейс каждого объекта (на уровне языка программирования).
Открытый интерфейс - данные и методы, которые объект предоставляет в распоряжение остальным объектам.
Защищённый интерфейс - части объекта, доступные производным от него объектам.
2 вида итерации:
-высокоуровневая, направленная на улучшение организации классов;
-на уровне определённых классов, направленная на детализацию проекта каждого класса.
Дом - абстракция его составляющих.
Абстракция - один из главных способов борьбы со сложностью реального мира.
Абстракция позволяет задействовать концепцию, игнорируя её некоторые детали и работая с разными деталями на разных уровнях.
Создавать абстракции на уровне интерфейсов методов, интерфейсов классов и интерфейсов пакетов.
Инкапсуляция не только представляет сложную концепцию в более простой форме, но и не позволяет взглянуть на какие бы то ни было детали сложной концепции.
Наследование позволяетсоздать универсальные методы для выполнения всего, что основано на общих свойствах дверей, и затем написать специфические методы для выполнения специфических операций над конкретными типами дверей.
Поддержка языком операций вроде Open() или Close() при отсутствии информации о конкретном типе двери вплоть до периода выполнения называется полиморфизмом.
Интерфейсы классов должны быть полными и минимальными, должны сообщать как можно меньше о внутренней работе класса (класс похож на айсберг, большая часть которого скрыта под водой).
Разработка интерфейса класса - итеративный процесс (пытаться создать или использовать другой подход).
Сокрытие аспектов проектирования позволяет значительно уменьшить объём кода, затрагиваемого изменениями (например, применение именованных констант вместо литералов; сокрытие информации: "id=new Id();" вместо "id=++g_maxId;").
Категории секретов:
-секреты, которые скрывают сложность, позволяя программистам забыть о ней при работе над остальными частями программы;
-секреты, которые скрывают источники изменений с целью локализации результатов возможных изменений.
Барьеры, препятствующие сокрытию информации:
-избыточное распространение информации (например, использование литералов вместо констант, распространение кода взаимодействия с пользователем по системе - GUI концентрировать в одном классе (пакете, подсистеме));
-круговая зависимость (B зависит от А, который зависит от B; не позволяет протестировать один класс, пока не будет готова часть другого);
-ошибочное представление о данных класса как о глобальных данных;
-кажущееся снижение производительности.
Крупные программы, использующие сокрытие информации, в 4 раза легче модифицировать, чем программы, его не использующие.
Размышлять о том, что скрыть.
Подготовка к изменениям:
1.Определите элементы, изменение которых кажется вероятным;
2.Изолируйте элементы, изменение которых кажется вероятным.
Интерфейс класса должен скрывать изменения в классе.
Области, изменяющиеся чаще всего:
-бизнес-правила;
-зависимости от оборудования;
-ввод-вывод;
-нестандартные возможности языка;
-сложные аспекты проектирования и конструирования;
-переменные статуса.
В качестве переменных статуса применять не булевы переменные, а перечисления.
Вместо непосредственной проверки переменной использовать методы доступа.
Проектировать систему так, чтобы влияние изменений было обратно пропорционально их вероятности.
Если изменение маловероятно, но его легко предугадать, рассмотрите его внимательнее, чем более вероятное изменение, которое трудно спланировать.
Функции, нужные пользователям - ядро системы, которое не потребуется изменять.
Путём небольших приращений экстраполировать систему, определяя дополнительную часть.
Сопряжение характеризует силу связи класса или метода с другими классами или методами.
Поддерживать сопряжение слабым.
Слабое сопряжение (loose coupling) - классы и методы имеют немногочисленные, непосредственные, явные и гибкие отношения с другими классами.
Эффективный механизм соединения максимально прост.
Отношения модулей должны напоминать отношения деловых партнёров, а не сиамских близнецов.
Критерии оценки сопряжения:
-объём связи (число соединений смежду модулями - число параметров метода, число открытых методов);
-видимость (заметность связи между двумя модулями);
-гибкость(лёгкость изменения связи между модулями).
Самые распространённые виды сопряжения:
-простое сопряжение посредством данных-параметров (передача элементарных типов данных через списки параметров);
-простое сопряжение посредством объекта (модуль создаёт экземпляр объекта, с которым сопряжён);
-сопряжение посредством объекта-параметра (объект 1 требует, чтобы объект 2 передал ему объект);
-семантическое сопряжение (один модуль использует семантические знания о внутренней работе другого модуля): модуль 1 передаёт в модуль 2 управляющий флаг, определяющий дальнейшую работу модуля 2; модуль 2 использует глобальные данные после их изменения модулем 1; семантическое сопряжение опасно тем, что изменение кода в используемом модуле может так нарушить работу использующего модуля, что компилятор этого не определит.
Классы и методы должны упрощать работу.
Шаблоны снижают сложность, предоставляя готовые абстракции, снижают число ошибок, стандартизируя детали популярных решений.
Шаблоны имеют эвристическую ценность, указывая на возможные варианты проектирования.
Шаблоны упрощают взаимодействие между разработчиками, позволяя им общаться на более высоком уровне.
Ловушки шаблонов - насильственная адаптация кода к шаблону, применение шаблона, продиктованное не целесообразностью, а желанием испытать шаблон в деле.
Шаблоны проектирования - эффективный инструмент управления сложностью.
Шаблон - это эвристический принцип.
Связность (cohesion) должна быть максимальна.
Связность характеризует то, насколько хорошо все методы класса или все фрагменты метода соответствуют главной цели.
Формируйте иерархии.
Иерархия - это многоуровневая структура организации информации, при которой наиболее общая или абстрактная репрезентация концепции соответствует вершине, а более детальные специализированные репрезентации - более низким уровням.
Люди в целом находят иерархии естественным способом организации сложной информации.
Рисуя сложный объект, люди рисуют его иерархически.
Формализовать контракты классов (обещания клиентов - предусловия, обязательства класса - постусловия).
Грамотно назначать сферы ответственности.
Проектировать систему для тестирования.
Тщательно рассматривать возвожные причины аварий, а не просто копировать другие успешные проекты.
Тщательно выбирать время присвоения переменной конкретного значения (binding time).
Создавать центральные точки управления - управление может быть централизовано в классах, методах, макросах препроцессора, файлах библиотек.
Рисовать диаграммы - они представляют проблему на более высоком уровне абстракции.
Поддерживать модульность проекта системы - каждый метод или класс должен быть похож на чёрный ящик (известны входы и выходы, но неизвестно что внутри).
Проектировать ПО нужно с использованием разных подходов.
Проектирование - итеративный процесс. После его завершения нужно возвращаться к началу.
Лучше обнаруживать варианты, которые не работают, чем ничего не делать.
Нисходящее (top-down) проектирование начинается на высоком уровне абстракции.
Восходящее (bottom-up) начинается со специфики и постепенно переходит ко всё большей общности.
Если сейчас решение кажется вам чуть-чуть хитрым, для любого, кто будет работать над ним позднее, оно станет головоломкой.
Нисходящая стратегия - декомпозиция.
Восходящая - композиция.
Большинство людей находят разбиение крупной концепции на меньшие части более лёгким, чем объединение небольших концепций в более крупную.
Прототипирование работает плохо, если задача недостаточно конкретна.
Имена прототипных классов и пакетов должны иметь префикс prototype.
Механические действия вытесняют творчество.
Регистрировать протоколы обсуждения проекта и принятые решения при помощи Wiki.
Отправлять резюме дискуссий всем членам группы по электронной почте.
Фотографировать схемы на доске.
Хранить плакаты со схемами проекта.
Использовать UML.
Класс - это набор данных и методов, имеющих общую, целостную, хорошо определённую сферу ответственности.
Классы нужны для максимизации части программы, которую можно игнорировать при работе над конкретными фрагментами кода.
Абстрактный тип данных - это набор, включающий данные и выполняемые над ними операции (служащие одной цели).
Преимущества использования АТД:
-возможность сокрытия деталей реализации;
-ограничение области изменений;
-более высокая информативность интерфейса;
-лёгкость оптимизации кода;
-лёгкость проверки кода;
-удобочитаемость и понятность кода;
-ограничение области использования данных;
-возможность работы с сущностями реального мира, а не с низкоуровневыми деталями реализации.
Принципы использования АТД:
-представлять в форме АТД распространённые низкоуровневые типы данных;
-представлять в форме АТД часто используемые объекты, такие как файлы;
-представлять в форме АТД даже простые элементы;
-обращайтесь к АТД так, чтобы это не зависело от среды, используемой для его хранения.
В ООП каждый АТД можно реализовать как класс (класс дополнительно поддерживает наследование и полиморфизм).
Интерфейс класса - это абстракция реализации класса, скрытой за интерфейсом.
Принципы проектирования интерфейсов:
-выражать в интерфейсе класса согласованный уровень абстракции (смешанные уровни
абстракции делают программу всё менее и менее понятной);
-убедиться в понимании того, реализацией какой абстракции является класс;
-представляйте методы вместе с противоположными им методами (создавать противоположные методы, не имея на то причин, не следует);
-убирать постороннюю информацию в другие классы;
-преобразовывать семантические элементы интерфейса в программные, например, утверждениями (интерфейсы должны как можно меньше зависеть от документации);
-элементы интерфейса должны находиться на одном уровне абстракции;
-не включать в класс открытые члены, плохо согласующиеся с абстракцией интерфейса;
-рассматривать абстракцию и связность вместе.
Хорошая инкапсуляция:
-минимизировать доступность классов и их членов;
-не делать данные-члены открытыми;
-не включать в интерфейс класса закрытые детали реализации;
-класс не должен делать предположений о том, как этот интерфейс будет или не будет использоваться;
-избегать использования дружественных классов;
-не делать метод открытым лишь потому, что он использует только открытые методы;
-ценить лёгкость чтения кода выше, чем удобство его написания;
-избегать зависимости клиентского кода от закытой реализации класса, а не от его открытого интерфейса (должен быть интерфейс, позволяющий разобраться не глядя на реализацию);
-остерегаться жесткого сопряжения - сильной связи между двумя классами.
Включение (containment) - один класс содержит примитивный элемент данных или другой класс.
Включение проще наследования и меньше подвержено ошибкам.
Включение можно реализовать отношением "содержит" (в крайнем случае - закрытое наследование).
Класс должен содержать 9 примитивных типов или 5 сложных объектов.
Наследование помогает избегать повторения кода и данных в нескольких местах, централизуя их в базовом классе (один класс является более специализированным вараинтом другого класса).
Проектировать и документировать классы с учётом возможности наследования или запретить его.
Наследование повышает сложность программы.
Все методы базового класса должны иметь в каждом производном классе то же значение.
Убедиться, что наследуется только то, что нужно.
Не используйте имена непереопределяемых методов базового класса в производных классах.
Перемещайте общие интерфейсы, данные и формы поведения на как можно более высокий уровень иерархии наследования, если это не нарушит абстракцию.
С подозрением относитесь к классам, объекты которых создаются в единственном экземпляре (возможно, класс перепутан с объектом).
С подозрением относитесь к базовым классам, имеющим только один производный класс.
С подозрением относитесь к классам, которые переопределяют метод, оставляя его пустым.
Избегайте многоуровневых иерархий наследования, ограничивая иеррахии наследования максимум 6 уровнями.
Предпочитайте полиморфизм, а не крупномасштабную проверку типов.
Делайте все данные закрытыми, а не защищёнными.
Миксин - простой класс, позволяющий добавлять ряд свойств в другой класс.
Ромбовидная схема наследования - классическая проблема.
Используйте множественное наследование, только тщательно рассмотрев все альтернативные варианты и проанализировав влияние выбранного подхода на сложность и понятность системы.
Ради управления сложностью относитесь к наследованию с подозрением.
Включайте в класс как можно меньше методов.
Блокируйте неявные методы и операторы, которые Вам не нужны (private).
Минимизируйте число разных методов, вызываемых классом.
Избегайте опосредованных вызовов методов других классов (цепочек вызовов).
Вообще минимизируйте сотрудничество класса с другими классами.
Инициализируйте по мере возможности все данные-члены во всех конструкторах.
Создавайте классы-одиночки с помощью закрытого конструктора.
Если сомневаетесь, выполняйте полное копирование, а не ограниченное.
Разумные причины создания класса:
-моделирование объектов реального мира;
-моделирование абстрактных объектов;
-снижение сложности;
-изоляция сложности;
-сокрытие деталей реализации;
-ограничение влияния изменений;
-сокрытие глобальных данных;
-упрощение передачи параметров в метод;
-создание центральныз точек управления;
-облегчение повторного использования кода;
-планирование создания семейства программ;
-упаковка родственных операций;
-выполнение специфического вида рефакторинга.
Классы, создавать которые не следует:
-избегайте создания классов, которые всё знают и всё могут;
-если класс имеет только данные, но не формы поведения, то это не класс;
-если класс имеет только формы поведения, но не даные, то это не класс.
Аспекты классов, зависящие от языка:
-поведение переопределённых конструкторов и деструкторов в дереве наследования;
-поведение конструкторов и деструкторов при обработке исключений;
-важность конструкторов по умолчанию (конструкторов без аргументов);
-время вызова деструктора или метода финализации;
-целесообразность переопределения встроенных операторов языка, в том числе операторов присваивания и сравнения;
-управление памятью при создании и уничтожении объектов или при их объявлении и выходе из области видимости.
В настоящее время использование классов - лучший способ достижения модульности.
Метод - это отдельная функция или процедура, выполняющая одну задачу.
Имя метода говорит о его роли.
Метод должен быть документирован, форматирован.
Входные переменные метода не изменяются.
Метод не работает с глобальными переменными.
Метод имеет одну чётко определённую цель, должен быть защищён от получения плохих данных, не использует магические числа; все параметры используются в методе, параметров у метода не более 7, параметры метода упорядочены.
Методы - самый эффективный способ уменьшения объёма и повышения быстродействия программ.
Разумные причины создания методов:
-снижение сложности;
-формирование понятной промежуточной абстракции;
-предотвращение дублирования кода;
-переопределить небольшой грамотно организованный метод легче, чем длинный и плохо спроектированный;
-сокрытие очерёдности действий;
-сокрытие неудобочитаемых операций;
-изоляция непортируемого кода;
-упрощение сложных булевых проверок;
-облегчение определения неэффективных фрагментов кода.
Один из главных ментальных барьеров, препятствующих созданию эффективных методов, - нежелание создавать простой метод для простой цели.
Высокая связность хуже низкой.
Функциональная связность - метод выполняет одну и только одну операцию.
Последовательная связность - метод содержит операции, которые обязательно выполняются в определённом порядке, используют данные предыдущих этапов и не формируют в целом единую функцию.
Коммуникационная связность - выполняемые в методе операции используют одни и те же данные и не связаны между собой иным образом.
Временная связность - когда операции объединяются в метод на том основании, что все они выполняются в один интервал времени.
Не выполнять в методе конкретные операции непосредственно, а вызывать для их выполнения другие методы.
Неприемлимые виды связности:
Процедурная связность - когда операции в методе выполняются в определённом порядке.
Поместить разные операции в разные методы.
Логическая связность - метод включает несколько операций, а выбор выполняемой операции осуществляется на основе передаваемого в метод управляющего флага.
Случайная связность - каких-либо ясных отношений между выполняемыми в методе операциями нет.
Стремиться создавать методы с функциональной связностью.
Советы по выбору удачных имён методов:
-описывайте всё, что метод выполняет (методы с побочными эффектами избавлять от побочных эффектов);
-избегайте невыразительных и неоднозначных глаголов (роль метода должна быть очевидной);
-не используйте для дифференциации имён методов исключительно номера;
-не ограничивайте длину имён методов искусственными правилами (оптимальная длина имени переменной равняется в среднем 9-15 символам, но имена методов обычно длиннее из-за их сложности);
-для именования функции используйте описание возвращаемого значения;
-для именования процедуры используйте выразительный глагол, дополняя его объектом (кроме ООП языков);
-дисциплинированно используйте антонимы (add/remove, begin/end, create/destroy, first/last, get/put, get/set, increment/decrement, insert/delete, lock/unlock, min/max, next/previous, old/new, open/close, show/hide, source/target, start/stop, up/down);
-определяйте конвенции именования часто используемых операций.
Код требует минимальных изменений, если методы состоят в среднем из 100-150 строк.
Предотвращение ошибок коммуникации между методами:
-передавайте параметры в порядке "входные значения - изменяемые значения - выходные значения";
-подумайте о создании собственных ключевых слов in и out;
-документируйте выраженные в интерфейсе предположения о параметрах;
Типы предположений:
-вид параметров: являются ли они исключительно входными, изменяемыми или исключительно выходными;
-единицы измерения (дюймы...);
-смыслы кодов статуса и ошибок, если для их представленияне используются перечисления;
-диапазоны допустимых значений;
-специфические значения, которые никогда не должны передаваться в метод;
-ограничивайте число параметров метода примерно семью;
-подумайте об определении конвенции именования входных, изменяемых и выходных параметров;
-передавайте в метод те переменные или объекты, которые нужны ему для поддержания абстракции интерфейса;
-сопряжение методов должно быть минимальным;
-используйте именованные параметры;
-убедитесь, что фактические (переданные в метод) параметры соответствуют формальным (объявленные в методе);
-проверяйте все возможные пути возврата значения из функции;
-инициализируйте возвращаемое значение значением по умолчанию в начале функции;
-не возвращайте ссылки или указатели на локальные данные (вместо ссылок - данные-члены).
Макросы:
-разрабатывая макрос, заключайте в скобки всё, что можно;
-заключайте макрос, включающий несколько команд, в фигурные скобки;
-не заменяйте методы макросами;
-называйте макросы, расширяющиеся в код подобно методам, так, чтобы при необходимости их можно было заменить методами;-теоретически встраивание методов может повысить быстродействие;
-не злоупотребляйте встраиваемыми методами.
Защита от неправильных входных данных:
-проверяйте все данные из внешних источников;
-проверяйте значения всех входных параметров метода;
-решите, как обрабатывать неправильные входные данные.
Утверждение - это код, используемый во время разработки, с помощью которого программа проверяет правильность своего выполнения (логическое выражение + сообщение).
Положения по применению утверждений:
-используйте процедуры обработки ошибок для ожидаемых событий и утверждения для событий, которые происходить не должны;
-старайтесь не помещать выполняемый код в утверждения;
-используйте утверждения для документирования и проверки предусловий и постусловий;
Предусловия - это соглашения, которые клиентский код, вызывающий метод или класс, обещает выполнить до вызова метода или создания экземпляра объекта.
Постусловия - это соглашения, которые метод или класс обещает выполнить при завершении своей работы.
-для большей устойчивости кода проверяйте утверждения, а затем всё равно обработайте возможные ошибки.
Способы обработки ошибок:
-вернуть нейтральное значение;
В больших, долгоживущих системах различные части могут разрабатываться несколькими проектировщиками 5-10 лет и более.
-заменить следующим корректным блоком данных;
-вернуть тот же результат, что и в предыдущий раз;
-подставить ближайшее допустимое значение;
-записать предупреждающее значение в файл;
-вернуть код ошибки;
-вызвать процедуру или объект-обработчик ошибок;
-показать сообщение об ошибке, где бы она ни случилась;
-обработать ошибку в месте возникновения наиболее подходящим способом;
-прекратить выполнение.
Корректность предполагает, что нельзя возвращать неточный результат; устойчивость требует всегда пытаться сделать что-то, что позволит программе продолжить работу.
Надо стараться реагировать на неправильные значения параметров одинаково во всей программе.
Предложения по исключениям:
-используйте исключения для оповещения других частей программы об ошибках, которые нельзя игнорировать;
-генерируйте исключения только для действительно исключительных ситуаций;
-не используйте исключения по мелочам (стараться обрабатывать ошибки локально);
-избегайте генерировать исключения в конструкторах и деструкторах, если только вы не перехватываете их позднее;
-генерируйте исключения на правильном уровне абстракции;
-вносите в описание исключения всю информацию о его причинах;
-избегайте пустых блоков catch;
-выясните, какие исключения генерирует используемая библиотека;
-рассмотрите вопрос о централизованном выводе информации об исключениях;
-стандартизуйте использование исключений в вашем проекте;
-рассмотрите альтернативы исключениям;
-преобразовывайте данные к нужному типу в момент ввода;
-методы с внешней стороны баррикады должны использовать обработчики ошибок, поскольку небезопасно делать любые предположения о данных; методы внутри баррикад должны использовать утверждения, так как данные, переданные им, считаются проверенными;
-промышленная версия: должна работать быстро, экономна с ресурсами, не позволяет пользователю делать опасные действия; отладочная версия: может работать медленно, может быть расточительной, может предоставлять дополнительные возможности без риска нарушить безопасность;
-внедрите поддержку отладки как можно раньше;
-исключительные случаи должны обрабатываться так, чтобы во время разработки они были очевидны, а в промышленном коде - позволяли продолжить работу;
-чем жестче требования во время разработки, тем проще эксплуатация;
-используйте встреонный препроцессор;
-используйте отладочные заглушки;
-оставьте код, который проверяет существенные ошибки;
-удалите код, проверяющий незначительные ошибки;
-удалите код, приводящий к прекращению работы программы;
-если на стадии разработки ваша программа обнаруживает ошибку, её надо сделать незаметнее, чтобы её могли исправить;-оставьте код, который позволяет аккуратно завершить работу программы;
-регламентируйте ошибки для отдела технической поддержки;
-убедитесь, что оставленные сообщения об ошибках дружелюбны (выводить телефон и адреса электронной почты, по которым можно о ней сообщить).
Обычно каждый метод тестируется при его создании.
Этапы конструирования классов:
1.Создание общей структуры класса;
2.Конструирование процедур класса;
3.Оценка и тестирование всего класса.
Действия по созданию метода:
1.Проектирование метода;
2.Проверка структуры;
3.Кодирование метода;
4.Пересмотр и тестирование кода.
Применение псевдокода:
-применяйте формулировки, в точности описывающие отдельные действия;
-избегайте синтаксических элементов языков программирования;
-описывайте назначение подхода, а не то, как этот подход нужно реализовать на выбранном языке программирования;
-пишите псевдокод на достаточно низком уровне, так чтобы код из него генерировался практически автоматически.
Сформулируйте задачу, решаемую методом, настолько детально, чтобы можно было переходить к созданию метода.
Затруднения в выборе имени метода могут свидетельствовать о том, что его назначение не совсем понятно.
Метод должен иметь понятное, недвусмысленное имя.
В процессе написания метода думайте о том, как вы будете его тестировать.
Исследуйте функциональность, представляемую стандартными библиотеками.
Не тратьте время на реализацию готового алгоритма, по которому написана кандидатская диссертация.
Подумайте обо всём плохом, что может случиться с вашим методом.
Не теряйте времени на вылизывание отдельных методов, пока не выяснится, что это необходимо.
Если доступные библиотеки не предоставляют нужной функциональности, имеет смысл исследовать литературу с описанием алгоритмов.
Общая идея: раз за разом проходиться по псевдокоду, пока каждое его предложение не станет настолько простым, что под ним можно будет вставить строку программы, а псевдокод оставить в качестве документации.
Этапы кодирования метода:
1.Напишите объявление метода;
2.Напишите первый и последний операторы, а псевдокод превратите в комментарии верхнего уровня;
3.Добавьте код после каждого комментария;
4.Проверьте код;
5.Исправьте неточности.
Длина абзаца кода, как и длина абзаца литературного текста, зависит от высказываемой мысли, а его качество - от понимания автором сути.
Проверьте, не нужна ли дальнейшая декомпозиция кода.
Умозрительно проверьте ошибки в методе.
Только около 5% всех ошибоксвязано с аппаратурой, компиляторам или ОС.
Если вы не знаете, почему это работает, вероятно, оно и не работает на самом деле.
Не спешите и не компилируйте программу, пока не будете уверены, что она верна.
Установите наивысший уровень предупреждений компилятора.
Применяйте проверяющие средства.
Убедитесь, что каждая строка выполняется так, как вы ожидаете.
Леса - код, поддерживающий методы при тестировании и не включаемый в конечный продукт.
Полное перепроектирование нестабильного метода полностью оправданно.
Если качество метода неудовлетворительное, вернитесь к псевдокоду.
Рекурсивное применение псевдокода - разбиение метода на более мелкие при необходимости.
Комментарии должны быть адекватными и полезными.
Неявное объявление переменных - одна из самых опасных возможностей языка.
Объявляйте все переменные.
Отключите неявные объявления.
Используйте конвенции именования.
Проверяйте имена переменных.
Инициализируйте каждую переменную при её объявлении.
Инициализируйте каждую переменную там, где она используется в первый раз (если нельзя инициализировать при объявлении).
В идеальном случае сразу объявляйте и определяйте каждую переменную непосредственно перед первым обращением к ней.
Объявляйте переменные по мере возможности как const.
Не забывайте обнулять счётчики и аккумуляторы.
Специализируйте данные-члены класса в его конструкторе.
Проверяйте необходимость повторной инициализации.
Область видимости - фрагмент программы, в котором переменная известна и может быть использована.
Локализуйте обращения к переменным (уменьшать интервал).
Делайте время жизни переменных как можно короче (измеряется в строках).
Избегайте глобальных переменных из-за большого времени жизни.
Инициализируйте переменные, используемые в цикле, непосредственно перед циклом, а не в начале метода, содержащего цикл.
Не присваивайте переменной значение вплоть до его использования.
Группируйте связанные команды.
Разбивайте группы связанных команд на отдельные методы.
Начинайте с самой ограниченной области видимости и расширяйте её только при необходимости.
Локальная область видимости способствует интеллектуальной управляемости.
Персистентность характеризует длительность существования данных.
Время связывания - момент, когда переменная и её значение связываются вместе.
Так как эффективность программирования зависит от минимизации сложности, опытный программист будет обеспечивать такой уровень гибкости, какой нужен для удовлетворения требований, но не более того.
Последовательные данные соответствуют последовательности команд.
Селективные данные соответствуют операторам if и case.
Итеративные данные соответствуют циклам.
Используйте каждую переменную только с одной целью.
Избегайте переменных, имеющих скрытый смысл (избегайте гибридного сопряжения).
Убеждайтесь в том, что используются все объявленные переменные.
Имя переменной должно полно и точно описывать сущность, представляемую переменной.
Имена должны быть максимально конкретны.
Имя переменной описывает проблему, а не её решение.
Отладка программы требует меньше усилий, если имена переменных состоят в среднем из 10-16 символов.
Более длинные имена лучше присваивать редко используемым или глобальным переменным, а более короткие - локальным переменным или переменным, вызываемым в циклах.
Дополняйте имена, относящиеся к глобальному пространству имён, спецификаторами.
Имена вычисляемых значений дополнять спецификатором из Total, Sum, Average, Max, Min, Record, String, Pointer в конце имени.
В именах лучше использовать антонимы: begin/end, first/last, locked/unlocked, min/max, next/previous, old/new, opened/closed, visible/invisible, source/target, source/destination, up/down.
Индексы циклов - i, j, k.
Если код часто изменяется, модернизируется, копируется, лучше делать осмысленные имена индексов.
Имя флага не должно включать фрагмент flag, потому что он ничего не говорит о сути флага.
Значения флагов лучше сравнивать со значениями перечислений, именованных констант или глобальных переменных, выступающих в роли именованных констант.
Использование временных переменных говорит о том, что программист ещё не полностью понял проблему.
Временным переменным давать точное описание.
Типичные имена булевых переменных: признак завершения - done, признак ошибки - error, признак обнаружения - found, признак успешного завершения - ok или success.
Присваивайте булевым переменным имена, подразумевающие значение true или false.
Используйте утвердительные имена булевых переменных.
Имя константы должно характеризовать абстрактную сущность, представляемую константой, а не конкретное значение.
Обязательно используйте конвенции именования, так как это всегда выгодно.
Советы по созданию конвенции:
Проведите различие между именами переменных и именами методов.
Проведите различие между классами и объектами.
Идентифицируйте глобальные переменные, переменные-члены, определения типов, именованные константы, элементы перечислений, неизменяемые параметры.
Формируйте имена так, чтобы их было легко читать.
Сокращение имён стало пережитком.
Не сокращайте слова только на один символ.
Всегда используйте один и тот же вариант сокращения.
Сокращайте имена так, чтобы их можно было произнести.
Избегайте комбинаций, допускающих неверное прочтение или произношение имён.
Обращайтесь к словарю для разрешения конфликтов имён.
Документируйте очень короткие имена прямо в коде при помощи таблиц.
Указывайте все сокращения в проектном документе "Стандартные аббревиатуры".
Помните, что имена создаются в первую очередь для программистов, читающих код.
Избегайте обманчивых имён или аббревиатур.
Избегайте имён, имеющих похожие значения.
Избегайте переменных, имеющих разную суть, но похожие имена.
Избегайте имён, имеющих похожее звучание, таких как wrap и rap.
Избегайте имён, включающих цифры.
Избегайте орфографических ошибок.
Избегайте слов, при написании которых люди часто допускают ошибки.
Проводите различие между именами не только по регистру букв.
Избегайте смешения естественных языков.
Избегайте имен стандартных типов, переменных и методов.
Не используйте имена, которые совершенно не связаны с тем, что представляют переменные.
Избегайте имён, содержащих символы, которые можно спутать с другими символами.
Избегайте "магических чисел".
Используйте в программе как константы только 0 и 1, а любые другие числа определите как литералы с понятными именами.
Пишите код, предупреждающий появление ошибки деления на 0.
Выполняйте преобразования типов понятно.
Избегайте сравнений разных типов.
Обращайте внимание на предупреждения вашего компилятора.
Проверяйте целочисленность операций деления.
Проверяйте переполнение целых чисел.
Проверяйте на переполнение промежуточные результаты.
Избегайте сложения и вычитания слишком разных по размеру чисел с плавающей запятой.
Избегайте сравнений на равенство чисел с плавающей запятой.
Предупреждайте ошибки округления.
Проверяйте поддержку специальных типов данных в языке и дополнительных библиотеках.
Избегайте магических символов и строк.
Следите за ошибками завышения/занижения на единицу индексов.
Узнайте как ваш язык и система поддерживают Unicode.
Разработайте стратегию интернационализации/локализации в ранний период жизни программы.
Если вам известно, что нужно поддерживать только один алфавит, рассмотрите вариант использования набора символов ISO8859.
Если вам необходимо поддерживать несколько языков, используйте Unicode.
Выберите целостную стратегию преобразования строковых типов.
Используйте логические переменные для документирования программы.
Используйте логические переменные для упрощения сложных условий.
Используйте перечислимые типы для читабельности.
Используйте перечислимые типы для надёжности.
Используйте перечислимые типы для модифицируемости.
Используйте перечислимые типы как альтернативу логическим переменным.
Проверяйте некорректные значения в case с перечислимым типом.
Настройте первый и последний элемент перечислимого типа для использования в качестве границ циклов.
Зарезервируйте первый элемент перечислимого типа как недопустимый.
Точно укажите в стандартах кодирования для проекта, как должны использоваться первый и последний элементы, и неукоснительно придерживайтесь этого.
Помните о подводных камнях в присваивании явных значений элементам перечисления.
Если в языке нет перечислимых типов, их можно имитировать, используя глобальные переменные или классы.
Используйте именованные константы в объявлениях данных.
Избегайте литеральных значений, даже "безопасных".
Если в языке их нет, имитируйте именованные константы с помощью переменных или классов правильной области видимости.
Не используйте для представления одной сущности именованные константы в одном месте и литералы в другом.
Убедитесь, что все значения индексов массива не выходят за его границы.
Обдумайте применение контейнеров вместо массивов или рассматривайте массивы как последовательные структуры (наборов, стеков, очередей и т.п.).
Проверяйте конечные точки массивов.
В многомерном массиве убедитесь, что его индексы используются в правильном порядке.
Остерегайтесь пересечения индексов - перемены мест индексов.
Принципы создания собственных типов:
Создавайте типы с именами, отражающими их функциональность.
Преимущество создания собственных типов в появлении слоя, скрывающего язык.
Избегайте предопределённых типов.
Не переопределяйте предопределённые типы.
Определите подстановки для переносимости.
Рассмотрите вопрос создания класса вместо использования typedef.
Используйте структуры для упрощения списка параметров.
Между методами должна передаваться только та информация, которую необходимо знать.
Используйте структуры для упрощения сопровождения.
Используйте технологию, не основанную на указателях.
Избегайте преднамеренных изменений глобальных данных.
Глобальные данные должны сохранять свои значения, даже если будет запущено несколько копий программы.
Глобальные данные затрудняют повторное использование кода.
Глобальные данные приводят к неопределённому порядку инициализации.
Глобальные данные привносят нарушение модульности и интеллектуальной управляемости.
Глобальные данные подходят для хранения глобальных значений.
Глобальные данные подходят для эмуляции именованных констант, если они не поддерживаются.
Глобальные данные подходят для эмуляции перечислимых типов.
Глобальные данные можно использовать для оптимизации обращений к часто используемым данным.
Применение глобальных переменных позволяет избежать бродячие данные.
Начните с объявления всех переменных локальными и делайте их глобальными только по необходимости.
Различайте глобальные переменные и переменные-члены класса.
Используйте методы доступа.
Требуйте, чтобы в свойствах весь код обращался к данным через методы доступа.
Не валите все глобальные данные в одну кучу.
Управляйте доступом к глобальным переменным с помощью блокировок.
Встройте уровень абстракции в методы доступа (не используйте структуры напрямую).
Выполняйте доступ к данным на одном и том же уровне абстракции.
Разработайте соглашения по именованию, которые сделают глобальные переменные очевидными.
Создайте хорошо аннотированный список всех глобальных переменных.
Не храните промежуточных результатов в глобальных переменных.
Не считайте, что вы не использутете глобальные переменные, поместив все данные в чудовищный объект и передавая его всюду.
Операторы:
Факт зависимости одного выражения от другого должен быть понятен из имен методов.
Организуйте код так, чтобы зависимости между операторами были очевидными.
Называйте методы так, чтобы зависимости были очевидными.
Используйте параметры методов, чтобы сделать зависимости очевидными.
Документируйте неявные зависимости с помощью комментариев.
Проверяйте зависимости с помощью утверждений или кода обработки ошибок.
Располагайте взаимосвязанные действия вместе.
При написании if-выражений:
Сначала напишите код номинального хода алгоритма, затем опишите исключительные ситуации.
Убедитесь, что при сравнении на равенство ветвление корректно.
Размещайте нормальный вариант после if, а не после else.
Размещайте осмысленные выражения после оператора if.
Рассмотрите вопрос использования блока else (в 50-80% случаев использования if следовало применить и else).
Проверяйте корректность выражения else.
Проверяйте возможную перестановку блоков if и else.
Упрощайте сложные проверки с помощью вызовов логических функций.
Размещайте наиболее вероятные варианты раньше остальных.
Убедитесь, что учтены все варианты.
Замените последовательности if-then-else другими конструкциями, которые поддерживает ваш язык программирования.
В case:
-упорядочивайте варианты case по алфавиту или численно;
-поместите правильный вариант case первым;
-отсортируйте варианты по частоте.
Сделайте обработку каждого варианта case простой.
Не конструируйте искусственные переменные с целью получить возможность использовать оператор case.
Используйте вариант по умолчанию только для обработки настоящих значений по умолчанию.
Используйте вариант по умолчанию для выявления ошибок.
Старайтесь не писать код, проваливающийся сквозь блоки оператора case.
Минимизируйте число факторов, влияющих на цикл.
Вынесите за пределы цикла всё управление, какое только можно.
Размещайте вход в цикл только в одном месте.
Размещайте инициализационный код непосредственно перед циклом.
Используйте while(true) для бесконечных циклов.
Предпочитайте циклы for, если они применимы.
Не используйте цикл for, если цикл while подходит больше.
Используйте{и}для обрамления выражений в цикле.
Избегайте пустых циклов.
Располагайте служебные операции либо в начале, либо в конце цикла.
Заставьте каждый цикл выполнять только одну функцию.
При завершении цикла убедитесь, что выполнение цикла закончилось.
Сделайте условие завершения цикла очевидным.
Не играйте с индексом цикла for для завершения цикла.
Избегайте писать код, зависящий от последнего значения индекса цикла.
Рассмотрите использование счетчиков безопасности, чтобы определить, не слишком ли много раз выполняется цикл.
Рассмотрите использование операторов break вместо логических флагов в цикле while.
Остерегайтесь цикла с множеством операторов break, разбросанных по всему коду.
Используйте continue для проверок в начале цикла.
Используйте структуру break с метками, если ваш язык её поддерживает.
Используйте операторы break и continue очень осторожно.
Используйте порядковые или перечислимые типы для границ массивов и циклов.
Используйте смысловые имена переменных, чтобы сделать вложенные циклы читабельными.
Используйте смысловые имена во избежание пересечения индексов.
Ограничивайте видимость переменных-индексов цикла самим циклом.
Делайте циклы достаточно короткими, чтобы их можно было увидеть сразу целиком.
Ограничивайте вложенность тремя уровнями.
Выделяйте внутреннюю часть длинных циклов в отдельные методы.
Делайте длинные циклы особенно ясными.
Простое создание цикла - изнутри наружу.
Используйте return, если это повышает читабельность.
Упрощайте сложную обработку ошибок с помощью сторожевых операторов (досрочного return).
Минимизируйте числов возвратов из каждого метода.
Убедитесь, что рекурсия остановится.
Предотвращайте бесконечную рекурсию с помощью счетчиков безопасности.
Ограничьте рекурсию одним методом.
Следите за стеком при использовании рекурсии.
Не используйте рекурсию для факториалов и чисел Фибоначчи.
Лучше обходиться без goto.
Код с goto переписать с помощью вложенных if.
Код с goto переписать с использованием статусной переменной.
Табличный метод - это схема, позволяющая искать информацию в таблице, а не использовать для этого логические выражения.
Таблица с прямым доступом позволяет обращаться изначально напрямую (есть ещё индексированный и ступенчатый доступ).
Используйте неявное сравнение логических величин с true или false.
Разбивайте сложные проверки на части с помощью новых логических переменных.
Размещайте сложные выражения в логических функциях.
Используйте таблицы решений для замены сложных условий.
В операторах if заменяйте негативные выражения позитивными, меняя местами блоки if и else.
Применяйте теоремы Деморгана для упрощения логических проверок с отрицаниями.
Используйте скобки для явного задания порядка вычисления.
Используйте простой метод подсчета для проверки симметричности скобок ( ( - +1, ) - -1).
Заключайте в скобки логическое выражение целиком.
Организуйте числовые условия так, чтобы они соответствовали порядку точек на числовой прямой.
Неявно сравнивайте логические переменные.
Сравнивайте числа с 0.
Сравнивайте указатели с null.
В C-подобных языках помещайте константы с левой стороны сравнений.
Учитывайте разницу между a==b и a.equals(b) (Java).
Пишите обе скобки блока одновременно.
Всегда используйте скобки для условных операторов (для пояснения).
Привлекайте внимание к нужным выражениям.
Создайте для пустых выражений макрос препроцессора или встроенную функцию DoNothing().
Подумайте, не будет ли код яснее с непустым телом цикла.
Не используйте больше 3 уровней вложенности if.
Упростите вложенные if с помощью повторной проверки части условия.
Упростите вложенные if с помощью блока с выходом do{...break...}while(false);.
Преобразуйте вложенные if в набор if-then-else.
Преобразуйте вложенные if в оператр case.
Факторизуйте глубоковложенный код в отдельный метод.
Используйте более объекто-ориентированный подход.
Перепроектируйте глубоковложенный код.
Основной тезис структурного программирования: любая управляющая логика программы может быть реализована с помощью последовательности, выбора и итерации.
Методики поиска дефектов лучше применять в комбинации.
Обзор кода эффективнее тестирования.
Ошибки в требованиях или архитектуре обычно имеют более широкие следствия, чем ошибки конструирования.
Повышение качества системы снижает расходы на ее разработку.
Средняя производительность труда программистов эквивалентна 10-50 строкам кода на одного человека в день.
Устранение дефектов - самый дорогой и длительный этап разработки ПО.
На создание ПО без дефектов не всегда уходит больше времени, чем на написание ПО с дефектами.
Разработчики допускают в среднем от 1 до 3 дефектов в час при проектировании и от 5 до 8 дефектов в час при кодировании.
Каждый час инспекции кода предотвращает около 100 часов аналогичной работы (тестирования и исправления дефектов).
Поддерживайте парное программирование стандартами кодирования.
Не позволяйте парному программированию превратиться в наблюдение.
Не используйте парное программирование для реализации простых фрагментов.
Регулярно меняйте состав пар и назначаемые парам задачи.
Объединяйте в пару людей, предпочитающих одинаковый темп работы.
Убедитесь, что оба члена пары видят экран.
Не объединяйте в пары людей, которые не нравятся друг другу.
Не составляйте пару из людей, которые ранее не программировали в паре.
Назначьте лидера группы.
Инспектор может проанализировать за час около 500 строк.
Тестирование - самая популярная методика повышения качества.
Тестирование - средство обнаружения ошибок.
Отладка - средство поиска и устранения причин уже обнаруженных ошибок.
Тестированию, выполняемому разработчиками, следует посвящать от 8% до 25% общего времени работы над проектом.
Искусство тестирования заключается в выборе тестов, способных обеспечить максимальную вероятность обнаружения ошибок.
Структурированное базисное тестирование - протестировать каждый оператор программы хотя бы раз.
Проверяйте код тестов.
Планируйте тестирование программы так же, как и ее разработку.
Храните тесты.
Встраивайте блочные тесты в среду тестирования.
Используйте генераторы случайных данных.
Изучите программу, над которой работаете.
Изучите собственные ошибки.
Изучите качество своего кода с точки зрения кого-то, кому придется читать его.
Изучите используемые способы решения проблем.
Изучите используемые способы исправления дефектов.
Формулируя гипотезу, используйте все имеющиеся данные.
Детализируйте тесты, приводящие к ошибке.
Проверяйте код при помощи блочных тестов.
Используйте разные инструменты.
Воспроизведите ошибку несколькими способами.
Генерируйте больше данных для формулирования большего числа гипотез.
Используйте "мозговой штурм" для построения нескольких гипотез.
Составьте список подходов, которые стоит попробовать.
Сохраните подозрительную область кода.
С подозрением относитесь к классам, которые содержали дефекты ранее.
Проверьте код, который был изменён недавно.
Расширьте подозрительный фрагмент кода.
Выполняйте интеграцию инкрементно.
Проверяйте наличие распространённых дефектов.
Обсудите проблему с кем-то другим.
Отдохните от проблемы.
Установите лимит времени для быстрой и грязной отладки.
Составьте список методик грубой силы.
Не полагайтесь на номера строк в сообщениях компилятора.
Не доверяйте сообщениям компилятора.
Не доверяйте второму сообщению компилятора.
Разделяйте программу на части.
Грамотно ищите неверно размещённые комментарии и кавычки.
Прежде чем браться за решение проблемы, поймите её.
Подтвердите диагноз проблемы.
Расслабьтесь (не поддавайтесь соблазну сэкономить время).
Сохраняйте первоначальный исходный код.
Устраняйте проблему, а не её симптомы.
Изменяйте код только при наличии веских оснований.
Вносите в код по одному изменению.
Добавляйте в набор тестов блочные тесты, приводящие к проявлению имеющихся дефектов.
Поищите похожие дефекты.
Выбирайте во время конструирования имена, ясно отличающиеся от других имён.
Используйте утилиты сравнения исходного кода.
Задайте в компиляторе максимально строгий уровень диагностики и устраняйте все ошибки и предупреждения.
Рассматривайте предупреждения как ошибки.
Стандартизуйте параметры компилятора в масштабе всего проекта.
Используйте утилиты расширенной проверки синтаксиса и логики.
Профилируйте код для поиска ошибок.
Даже в хорошо управляемых проектах требования изменяются на 1-4% в месяц.
Современные подходы снижают предсказуемость кодирования.
Факторинг - максимальная декомпозиция программы на составляющие части.
Причины выполнения рефакторинга:
Код повторяется.
Метод слишком велик.
Цикл слишком велик или слишком глубоко вложен в другие циклы.
Класс имеет плохую связность.
Интерфейс класса не формирует согласованную абстракцию.
Метод принимает слишком много параметров.
Отдельные части класса изменяются независимо от других частей.
При изменении программы требуется параллельно изменять несколько классов.
Вам приходится параллельно изменять несколько иерархий наследования.
Вам приходится параллельно изменять несколько блоков case.
Родственные элементы данных, используемые вместе, не организованы в классы.
Метод использует больше элементов другого класса, чем своего собственного.
Элементарный тип данных перегружен.
Класс имеет слишком ограниченную функциональность.
По цепи методов передаются бродячие данные.
Объект-посредник ничего не делает.
Один класс слишком много знает о другом классе.
Метод имеет неудачное имя.
Данные-члены сделаны открытыми.
Подкласс использует только малую долю методов своих предков.
Сложный код объясняется при помощи комментариев.
Код содержит глобальные переменные.
Перед вызовом метода выполняется подготовительный код (после вызова метода выполняется код уборки).
Программа содержит код, который может когда-нибудь понадобиться.
Рефакторинги на уровне данных:
Замена магического числа на именованную константу.
Присвоение переменной более ясного или информативного имени.
Встраивание выражения в код.
Замена выражения на вызов метода.
Введение промежуточной переменной.
Преобразование многоцелевой переменной в несколько одноцелевых переменных.
Использование локальной переменной вместо параметра.
Преобразование элементарного типа данных в класс.
Преобразование набора кодов в класс или перечисление.
Преобразование набора кодов в класс, имеющий производные классы.
Преобразование массива в класс.
Инкапсуляция набора.
Замена традиционной записи на класс данных.
Рефакторинги на уровне отдельных операторов:
Декомпозиция логического выражения.
Вынесение сложного логического выражения в грамотно названную булеву функцию.
Консолидация фрагментов, повторяющихся в разнах частях условного оператора.
Использование оператора break или return вместо управляющей переменной цикла.
Возврат из метода сразу после получения ответа вместо установки возвращаемого значения внутри вложенных операторов if-then-else.
Замена условных операторов (обычно многочисленных блоков case) на вызов полиморфного метода.
Создание и использование "пустых" объектов вместо того, чтобы проверять, равно ли значение null.
Извлечение метода из другого метода.
Встраивание кода метода.
Преобразование объёмного метода в класс.
Замена сложного алгоритма на простой.
Добавление параметра.
Удаление параметра.
Отделение операций запроса данных от операций изменения данных.
Объединение похожих методов путём их параметризации.
Разделение метода, поведение которого зависит от полученных параметров.
Передача в метод целого объекта вместо отдельных полей.
Передача в метод отдельных полей вместо целого объекта.
Инкапсуляция нисходящего приведения типов.
Рефакторинги реализации классов:
Замена объектов-значений на объекты-ссылки.
Замена объектов-ссылок на объекты-значения.
Замена виртуальных методов на инициализацию данных.
Изменение положения методов-членов или данных-членов в иерархии наследования.
Перемещение специализированного кода в подкласс.
Объединение похожего кода и его перемещение в суперкласс.
Рефакторинги интерфейсов классов:
Перемещение метода в другой класс.
Разделение одного класса на несколько.
Удаление класса.
Сокрытие делегата.
Удаление посредника.
Замена наследования на делегирование.
Замена делегирования на наследование.
Создание внешнего метода.
Создание класса-расширения.
Инкапсуляция открытой переменной-члена.
Удаление методов установки значений неизменяемых полей.
Сокрытие методов, которые не следует вызывать извне класса.
Инкапсуляция неиспользуемых методов.
Объединение суперкласса и подкласса, имеющих очень похожую реализацию.
Рефакторинг на уровне системы:
Создание эталонного источника данных, которые вы не можете контролировать.
Изменение однонаправленной связи между классами на двунаправленную.
Изменение двунаправленной связи между классами на однонаправленную.
Предоставление фабричного метода вместо простого конструктора.
Замена кодов ошибок на исключения или наоборот.
Сохраняйте первоначальный код.
Стремитесь ограничить объем отдельных видов рефакторинга.
Выполняйте отдельные виды рефакторинга по одному за раз.
Составьте список действий, которые вы собираетесь предпринять.
Составьте и поддерживайте список видов рефакторинга, которые следует выполнить позже.
Часто создавайте контрольные точки.
Используйте предупреждения компилятора.
Выполняйте регрессивное тестирование.
Создавайте дополнительные тесты.
Выполняйте обзоры изменений.
Изменяйте подход в зависимости от рискованности рефакторинга.
Не рассматривайте рефакторинг как оправдание написания плохого кода с намерением исправить его позднее.
Не рассматривайте рефакторинг как способ, позволяющий избежать переписывания кода.
Тратьте время на 20% видов рефакторинга, обеспечивающих 80% выгоды.
Выполняйте рефакторинг при создании новых методов.
Выполняйте рефакторинги при создании новых классов.
Выполняйте рефакторинг при исправлении дефектов.
Выполняйте рефакторинг модулей, подверженных ошибкам.
Выполняйте рефакторинг сложных модулей.
При сопровождении программы улучшайте фрагменты, к которым прикасаетесь.
Определите интерфейс между аккуратным и безобразным кодом и переместите безобразный код на другую сторону этого интерфейса.
Простое задание явных целей повышает вероятность их достижения.
На 20% методов программы приходятся 80% времени её выполнения.
Сокращение числа строк высокоуровневого кода не повышает быстродействие и не уменьшает объем итогового машинного кода.
Без измерения производительности вы никак не сможете точно узнать, помогли ваши изменения программе или навредили.
Методики, повышающие производительность в одной среде, могут снижать ее в других.
До создания полностью работоспособной программы найти узкие места в коде почти невозможно.
Концентрация на производительности во время первоначальной разработки отвлекает от достижения других целей.
Корректность важнее быстродействия.
Частые причины снижения эффективности:
Операции ввода/вывода.
Замещение страниц памяти.
Системные вызовы.
Интерпретируемые языки.
Ошибки.
Повышение быстродействия исходит из замены дорогой операции на более дешевую.
Главная проблема оптимизации кода: результат любого отдельного вида оптимизации непредсказуем.
Замыкание цикла - принятие решения внутри цикла при каждой его интерации.
Разомкнутый цикл - принятие решения вне цикла.
Если два цикла работают с одним набором элементов, можно выполнить их объединение.
После частичного развёртывания цикла при каждой его итерации обрабатывается не один случай, а два и более.
Вкладывайте более ресурсоемкий цикл в менее ресурсоемкий.
Минимизируйте число обращений к массивам.
Даже слепые белки иногда наталкиваются на орехи.
Назначьте двух человек на каждую часть проекта.
Рецензируйте каждую строку кода.
Введите процедуру подписания кода.
Распространяйте для ознакомления хорошие примеры кода.
Подчеркивайте, что код - это общее имущество.
Награждайте за хороший код.
"Я должен быть в состоянии прочесть и понять любой код, написанный в проекте."
Следуйте систематической процедуре контроля изменений.
Обрабатывайте затраты на каждое изменение.
Оценивайте затраты на каждое изменение.
Относитесь с подозрением к изменениям большого объема.
Учредите комитет контроля изменений.
Соблюдайте бюрократические процедуры, но не позволяйте страху перед бюрократией препятствовать эффективному контролю изменений.
Оценка графика контруирования:
Определите цели.
Выделите время для оценки.
Выясните требования к программе.
Делайте оценки на низком уровне детализации.
Используйте несколько способов оценки и сравнивайте полученные результаты.
Периодически делайте повторную оценку.
Храните сведения об опыте проектов в вашей организации и используйте их для оценки времени, необходимого будущим проектам.
Наибольшее влияние на график программного проекта оказывает размер создаваемой программы.
Если вы отстаете:
Надеяться, что вы сможете наверстать упущенное.
Задержки и отклонения от графика обычно увеличиваются по мере приближения к концу проекта.
Увеличить команду (если есть независимые задачи).
Сократить проект.
При первоначальном планировании продукта разделите его возможности на категории "должны быть", "хорошо бы сделать", "необязательные".
Причины, по которым стоит проводить измерение проекта:
Для любого атрибута проекта существует возможность его измерения, что в любом случае не означает отказа от его измерения.
Отдавайте себе отчет о побочных эффектах измерения.
Возражать против измерений означает утверждать, что лучше не знать о том, что на самом деле происходит.
80% работы выполняют 20% сотрудников.
Если вы хотите контролировать стиль программиста:
Вы вторгаетесь в область, требующую деликатного обращения.
Из уважения к теме используйте термины "предложения" и "советы".
Старайтесь обходить стороной спорные вопросы, предпочитая давать явные поручения.
Предложите программистам выработать собственные стандарты.
Просвещайте менеджеров.
Писать и тестировать маленькие участки программы, а затем комбинировать эти кусочки друг с другом по одному.
Преимущества этой инкрементной итерации:
Ошибки можно легко обнаружить.
В таком проекте система раньше становится работоспособной.
Вы получаете улучшенный мониторинг состояния.
Вы улучшите отношения с заказчиком.
Системные модули тестируются гораздо полнее.
Вы можете создать систему за более короткое время.
При нисходящей интеграции вы создаете те классы, которые находятся на вершине иерархии, первыми, а те, что внизу, - последними.
Систему можно разбить на вертикальные слои.
При восходящей интеграции вы пишете и интегрируете сначала классы, находящиеся в низу иерархии.
При риск-ориентированной интеграции вы решаете, какие части системы будут самыми трудными, и реализуете их первыми.
Ещё один подход - интеграция одной функции в каждый момент времени.
Создавайте сборку ежедневно.
Проверяйте правильность сборок.
Выполняйте дымовые тесты ежедневно.
Поддерживайте актуальность дымового теста.
Автоматизируйте ежедневную сборку и дымовой тест.
Организуйте группу, отвечающую за сборки.
Вносите исправления в сборку, только когда имеет смысл это делать, но не откладывайте внесение исправлений надолго.
Требуйте, чтобы разработчики проводили дымовое тестирование своего кода перед его добавлением в систему.
Создайте область промежуточного хранения кода, который следует добавить к сборке.
Назначьте наказание за нарушение сборки.
Выпускайте сборки по утрам.
Создавайте сборку и проводите дымовой тест даже в экстремальных условиях.
Под "непрерывной" понимается "по крайней мере ежедневная" интеграция.
До 40% времени программист тратит на редактирование исходного кода.
Передовой набор инструментов позволяет повысить производительность более, чем на 50%.
20% инструментария используются в 80% случаев.
Хорошее визуальное форматирование показывает логическую структуру программы.
Абзац кода должен содержать только взаимосвязанные операторы, выполняющие одно задание.
Начало нового абзаца в коде нужно указывать с помощью пустой строки.
Оптимальное число пустых строк в программе - 16%.
Операторы выделяются отступами, когда они следуют после выражения, от которого логически зависят.
Оптимальными являются отступы из 2-4 пробелов.
Формируйте блоки из 1 оператора единообразно.
В сложных выражениях размещайте каждое условие на отдельной строке.
Избегайте операторов goto.
Не используйте форматирование в конце строки в виде исключения для операторов case.
Используйте пробелы, чтобы сделать читаемыми логические выражения.
Используйте пробелы, чтобы сделать читаемыми обращения к массиву.
Используйте пробелы, чтобы сделать читаемыми аргументы методов.
Сделайте так, чтобы незавершенность выражения была очевидна.
Располагайте сильно связанные элементы вместе.
При переносе строк в вызове метода используйте отступ стандартного размера.
Упростите поиск конца строки с продолжением.
При переносе строк в управляющем выражении делайте отступ стандартного размера.
Не выравнивайте правые части выражений присваивания.
При переносе строк в выражениях присваивания применяйте отступы стандартного размера.
Располагайте каждое объявление данных в отдельной строке.
Объявляйте переменные рядом с местом их первого использования.
Разумно упорядочивайте объявления.
Делайте в комментации такой же отступ, как и в соответствующем ему коде.
Отделяйте каждый комментарий хотя бы одной пустой строкой.
Используйте пустые строки для разделения составных частей метода.
Используйте стандартный отступ для аргументов метода.
Если файл содержит более 1 класса, четко определяйте границы каждого класса.
Помещайте каждый класс в отдельный файл.
Называйте файл в соответствии с именем класса.
Отделяйте методы друг от друга с помощью хотя бы 2 пустых строк.
Упорядочивайте методы по алфавиту.
Используйте стиль комментирования, который легко поддерживать.
Используйте содержательные комментарии.
Избегайте комментариев, высосанных из пальца.
Не используйте комментарии в концах строк, относящиеся к нескольким строкам кода.
Используйте комментарии в концах строк для пояснения объявлений данных.
Не используйте комментарии в концах строк для вставки пометок во время сопровождения ПО.
Используйте комментарии в концах строк для обозначения концов блоков.
Описывайте цель блока кода, следующего за комментарием.
Во время документирования сосредоточьтесь на самом коде.
Придумывая комментарий абзаца, стремитесь ответить на вопрос "почему", а не "как".
Используйте комментарии для подготовки читателя кода к последующей информации.
Не размножайте комментарии без необходимости.
Документируйте сюрпризы.
Избегайте сокращений.
Проведите различие между общими и детальными комментариями.
Комментируйте все, что имеет отношение к ошибкам или недокументированным возможностям языка или среды.
Указывайте в комментариях единицы измерения численных величин.
Указывайте в комментариях диапазоны допустимых значений численных величин.
Комментируйте смысл закодированных значений.
Комментируйте ограничения входных данных.
Документируйте флаги до уровня отдельных битов.
Включайте в комментарии, относящиеся к переменной, имя переменной.
Документируйте глобальные данные.
Пишите комментарий перед каждым оператором if, блоком case, циклом или группой операторов.
Комментируйте завершение каждой управляющей структуры.
Рассматривайте комментарии в концах циклов как предупреждения о сложности кода.
Располагайте комментарии близко к описываемому ими коду.
Описывайте каждый метод одним-двумя предложениями перед началом метода.
Документируйте параметры в местах их объявления.
Используйте утилиты документирования кода.
Проведите различие между входными и выходными данными.
Документируйте выраженные в интерфейсе предположения.
Комментируйте ограничения методов.
Документируйте глобальные результаты выполнения метода.
Документируйте источники используемых алгоритмов.
Используйте комментарии для маркирования частей программы.
Опишите подход к проектированию класса.
Опишите ограничения класса, предположения о его использовании и так далее.
Прокомментируйте интерфейс класса.
Не документируйте в интерфейсе класса детали реализации.
Опишите назначение и содержание каждого файла.
Укажите в блочном комментарии свои имя/фамилию, адрес электронной почты и номер телефона.
Включите в файл тег версии.
Включите в блочный комментарий юридическую информацию.
Присвойте файлу имя, характеризующее его содержание.
Лучшие программисты создают программы в 10 раз быстрее коллег.
Изучите процесс разработки.
Экспериментируйте.
Читайте о решении проблем.
Анализируйте и планируйте, прежде чем действовать.
Изучайте успешные проекты.
Читайте.
Читайте другие книги и периодические издания.
Общайтесь с единомышленниками.
Постоянно стремитесь к профессиональному развитию.
Для широкого распространения исследовательских разработок требуется от 5 до 15 и более лет.
Ежегодно программистами становятся около 50000 человек.
Число дипломов, вручаемых в отрасли около 35000.
Единственным способом получения информации является изучение горы книг и нескольких сотен технических журналов, дополненное значительным реальным опытом (о конструировании ПО).
Сайт книги cc2e.com/1234.
Конструирование ПО=кодирование.
На конструирование приходится 65% в больших проектах.
Во время конструирования допускается от 50 до 75% ошибок в больших проектах.
Ошибки конструирования дешевле исправлять.
Одними из самых дорогих ошибок в истории, приведшими к убыткам в сотни миллионов долларов, были мелкие ошибки кодирования.
При написании программы только этапа конструирования нельзя избежать.
Почта и сайт автора stevencc@construx.com, stevemcconnell.com.
Книги никогда не создаются в одиночку.
Составляющие разработки ПО за последние 25 лет:
- определение проблемы;
- выработка требований;
- создание плана конструирования;
- разработка архитектуры ПО, или высокоуровневое проектирование;
- детальное проектирование;
- кодирование и отладка;
- блочное тестирование;
- интеграционное тестирование;
- интеграция;
- тестирование системы;
- корректирующее сопровождение.
Проекты бывают формальные, неформальные.
Конструирование не включает определение проблемы.
Конструирование=программирование.
В центре конструирования - кодирование и отладка.
Конкретные задачи, связанные с конструированием:
1.Проверка выполнения условий, необходимых для успешного конструирования;
2.Определение способов последующего тестирования кода;
3.Проектирование и написание классов и методов;
4.Создание и присвоение имён переменным и именованным константам;
5.Выбор управляющих структур и организация блоков команд;
6.Блочное тестирование, интеграционное тестирование и отладка собственного кода;
7.Взаимный обзор кода и низкоуровневых программных структур членами группы;
8."Шлифовка" кода путём его тщательного форматирования и комментирования;
9.Интеграция программных компонентов, созданных по отдельности;
10.Оптимизация кода, направленная на повышение его быстродействия, и снижение степени использования ресурсов.
В конструирование не входят управление, выработка требований, разработка архитектуры, проектирование пользовательского интерфейса, тестирование системы и её сопровождение.
На конструирование обычно уходит 30-80% времени работы.
Требования к приложению и архитектура разрабатываются, чтобы гарантировать эффективность этапа конструирования.
Тестирование системы выполняется после конструирования, чтобы проверить его правильность.
Производительность труда отдельных программистов во время конструирования изменяется в 10-20 раз.
Исходный код - самая свежая документация на ПО.
Тестирование системы должно контролироваться статистически.
Повышение эффективности конструирования позволяет оптимизировать любой проект.
Компетентность в конструировании ПО определяет то, насколько хороший вы программист.
Метафоры позволяют лучше понять разработку ПО.
Использование метафор (аналогий) - моделирование.
Эффективность моделей объясняется их яркостью и концептуальной целостностью.
При чрезмерном обобщении модели вводят в заблуждение.
Хорошие метафоры простые, согласуются с другими метафорами и объясняют многие данные и явления.
Описываемое метафорами поведение предсказуемо и понятно всем людям.
Самая сложная часть программирования - концептуализация проблемы.
90% общего объёма работы на программной системой выполняется после выпуска первой версии.
Популярные метафоры о разработке ПО:
1.Написание письма;
2.Выращивание сельскохозяйственных культур;
3.Процесс формирования жемчужины;
4.Построение дома (невыгодно писать компоненты, которые можно купить готовыми).
Программная система из 1 млн строк требует 69 видов документации, спецификация требований занимает 4000-5000 страниц.
В неудачных проектах конструирование приходится выполнять несколько раз.
Вайнберг "The psychology of computer programming".
Хороших программистов всегда не хватает.
Последовательный подход (вопросы решаются заблаговременно) применяется, когда:
- требования довольно стабильны;
- проект приложения прост и относительно понятен;
- группа разработчиков знакома с прикладной областью;
- проект не связан с особым риском;
- важна долговременная предсказуемость проекта;
- затраты на изменение требований, проекта приложения и кода скорее всего окажутся высокими.
Итеративный подход (вопросы решаются по мере работы) применяется, когда:
- требования относительно непонятны или вам кажется, что они могут оказаться нестабильными по другим причинам;
- проект приложения сложен, не совсем ясен или и то и другое;
- группа разработчиков незнакома с прикладной областью;
- проект сопряжен с высоким риском;
- долговременная предсказуемость проекта не играет особой роли;
- затраты на изменение требований, проекта приложения и кода скорее всего будут низкими.
Итеративные подходы эффективны чаще.
Предварительное условие: ясное формулирование проблемы, которую система должна решать (без намёка на решение).
Процесс программирования:
1) Определение проблемы;
2) Выработка требований;
3) Проектирование архитектуры;
4) Конструирование;
5) Тестирование системы;
6) Будущие улучшения;
Проблема должна быть описана с пользовательской точки зрения (без компьютерных терминов).
Требования подробно описывают, что должна делать программная система.
Функциональность системы определяется пользователем, а не программистом (требования решают споры, сводят к минимуму изменения системы после начала разработки, снижают дополнительные расходы).
Процесс разработки помогает разработчикам и клиентам лучше понять свои потребности.
В среднем проекте требования во время разработки изменяются на 25%, на что приходится 80% повторной работы над проектом.
Изменение требований влечёт пересмотр графика и сметы.
evolutionary prototyping - поставка системы клиенту по частям.
Определить моменты прекращения работы над проектом.
Архитектура описывает какие другие компоненты данный компонент может использовать непосредственно, какие косвенно, а какие вообще не должен использовать; должна описывать организацию классов в подсистемы и обосновывать итоговый вариант.
Интерфейс проектируется на этапе выработки требований либо в архитектуре.
Архитектура должна быть модульной.
Архитектура определяет подходы к безопасности.
В требованиях определяются показатели производительности.
Масштабируемость - возможность системы адаптироваться к росту требований.
Интернационализация - реализация в программе поддержки региональных стандартов.
Локализация - перевод интерфейса программы и реализация в ней поддержки конкретного языка.
Архитектура выражает отношение к избыточной функциональности.
Архитектура чётко описывает стратегию изменений.
В архитектуре должны быть обоснованы важнейшие принятые решения.
Цели должны быть чётко сформулированы.
В архитектуре явно определены области риска, указаны его причины и описаны действия по сведению риска к минимуму.
В проекте без проблем работа над требованиями, архитектурой и предварительным планированием поглощает 20-30% времени.
Программисты, использующие язык, с которым они работали три года или более, примерно на 30% более продуктивны, чем программисты, обладающие аналогичным опытом, но для которых язык является новым.
Программисты, использующие языки высокого уровня, достигают более высокой производительности и создают более качественный код, чем программисты, работающие с языками низкого уровня.
Гипотеза Сапира-Уорфа: способность человека к обдумыванию определённых мыслей зависит от знания слов, при помощи которых можно выразить эту мысль.
Ada, Cobol используются в Минобороны США.
SQL - декларативный язык.
Программируют с использованием языка, а не на языке.
Важный аспект работы проектировщика - анализ конкурирующих характеристик проекта и достижение баланса между ними.
Спроектировать программу можно десятками разных способов.
Существенные свойства - которыми объект должен обладать, чтобы быть именно этим объектом.
Несущественные (акцидентные) свойства - которые не влияют на суть объекта.
Управление сложностью - самый важный аспект разработки ПО.
Э. Дейкстра: ни один человек не обладает интеллектом, способным вместить все детали современной компьютерной программы.Поэтому целью является минимизация объёма программы, о котором нужно думать в конкретный момент времени.
Цель всех методик проектирования ПО - разбиение сложной проблемы на простые фрагменты.
Краткость методов помогает снизить нагрузку на интеллект.
Этому же способствует написание программы в терминах проблемной области, а также работа на самом высоком уровне абстракции.
Как бороться со сложностью? Чаще всего причинами неэффективности являются:
- сложное решение простой проблемы;
- простое, но неверное решение сложной проблемы;
- неадекватное сложное решение сложной проблемы.
Дейкстра: сложность современного ПО обусловлена самой его природой.
Двойственный подход к управлению сложностью:
-старайтесь свести к минимуму объём существенной сложности, с которым придётся работать в каждый конкретный момент времени;
-сдерживайте необязательный рост несущественной сложности.
Внутренние характеристики проекта:
- минимальная сложность;
- простота сопровождения;
- слабое сопряжение;
- расширяемость;
- возможность повторного использования;
- система предусматривает интенсивное использование вспомогательных низкоуровневых классов;
- класс использует минимальное число других классов;
- портируемость;
- минимальная, но полная функциональность (дополнительный код необходимо разработать, проанализировать, протестировать, пересматривать при изменении других фрагментов программы, поддерживать совместимость будущих версий с дополнительным кодом);
- систему можно изучать на отдельных уровнях, игнорируя другие уровни (проектировать дополнительный уровень, скрывающий плохое качество старого кода);
- соответствие стандартным популярным подходам.
Уровни детальности проектирования программной системы:
-вся система;
-разделение системы на подсистемы или пакеты определение взаимодействий между ними (минимальные взаимодействия для облегчения модификации (проще сначала ограничить взаимодействие, а затем сделать его более свободным)).
Отношения между системами должны быть простыми.
В порядке уменьшения простоты:
1) Одна подсистема вызывает методы другой;
2) Одна подсистема содержит классы другой;
3) Наследование классов одной подсистемы от классов другой.
Программа не должна содержать циклических отношений.
Часто используемые подсистемы:
-бизнес-правил;
-пользовательского интерфейса;
-доступа к БД;
-изоляции зависимостей от ОС.
Разделение подсистем на классы обычно требуется во всех проектах.
Объект - динамическая сущность, класс - статическая.
БД: различие между классом и объектом аналогично различию между "схемой" и "экземпляром".
Разделение классов на методы необходимо в каждом проекте и часто оставляется отдельным программистам.
Проектирование методов может включать написание псевдокода, поиск алгоритмов в книгах, размышление над оптимальной организацией фрагментов метода и написание кода.
При проектировании с использованием искусственных объектов и объектов реального мира определите:
-объекты и их атрибуты (методы и данные);
-действия, которые могут быть выполнены над каждый объектом;
-действия, которые каждый объект может выполнять над другими объектами (включение и наследование);
-части каждого объекта, видимые другим объектам, то есть открытые и закрытые части;
-открытый интерфейс каждого объекта (на уровне языка программирования).
Открытый интерфейс - данные и методы, которые объект предоставляет в распоряжение остальным объектам.
Защищённый интерфейс - части объекта, доступные производным от него объектам.
2 вида итерации:
-высокоуровневая, направленная на улучшение организации классов;
-на уровне определённых классов, направленная на детализацию проекта каждого класса.
Дом - абстракция его составляющих.
Абстракция - один из главных способов борьбы со сложностью реального мира.
Абстракция позволяет задействовать концепцию, игнорируя её некоторые детали и работая с разными деталями на разных уровнях.
Создавать абстракции на уровне интерфейсов методов, интерфейсов классов и интерфейсов пакетов.
Инкапсуляция не только представляет сложную концепцию в более простой форме, но и не позволяет взглянуть на какие бы то ни было детали сложной концепции.
Наследование позволяетсоздать универсальные методы для выполнения всего, что основано на общих свойствах дверей, и затем написать специфические методы для выполнения специфических операций над конкретными типами дверей.
Поддержка языком операций вроде Open() или Close() при отсутствии информации о конкретном типе двери вплоть до периода выполнения называется полиморфизмом.
Интерфейсы классов должны быть полными и минимальными, должны сообщать как можно меньше о внутренней работе класса (класс похож на айсберг, большая часть которого скрыта под водой).
Разработка интерфейса класса - итеративный процесс (пытаться создать или использовать другой подход).
Сокрытие аспектов проектирования позволяет значительно уменьшить объём кода, затрагиваемого изменениями (например, применение именованных констант вместо литералов; сокрытие информации: "id=new Id();" вместо "id=++g_maxId;").
Категории секретов:
-секреты, которые скрывают сложность, позволяя программистам забыть о ней при работе над остальными частями программы;
-секреты, которые скрывают источники изменений с целью локализации результатов возможных изменений.
Барьеры, препятствующие сокрытию информации:
-избыточное распространение информации (например, использование литералов вместо констант, распространение кода взаимодействия с пользователем по системе - GUI концентрировать в одном классе (пакете, подсистеме));
-круговая зависимость (B зависит от А, который зависит от B; не позволяет протестировать один класс, пока не будет готова часть другого);
-ошибочное представление о данных класса как о глобальных данных;
-кажущееся снижение производительности.
Крупные программы, использующие сокрытие информации, в 4 раза легче модифицировать, чем программы, его не использующие.
Размышлять о том, что скрыть.
Подготовка к изменениям:
1.Определите элементы, изменение которых кажется вероятным;
2.Изолируйте элементы, изменение которых кажется вероятным.
Интерфейс класса должен скрывать изменения в классе.
Области, изменяющиеся чаще всего:
-бизнес-правила;
-зависимости от оборудования;
-ввод-вывод;
-нестандартные возможности языка;
-сложные аспекты проектирования и конструирования;
-переменные статуса.
В качестве переменных статуса применять не булевы переменные, а перечисления.
Вместо непосредственной проверки переменной использовать методы доступа.
Проектировать систему так, чтобы влияние изменений было обратно пропорционально их вероятности.
Если изменение маловероятно, но его легко предугадать, рассмотрите его внимательнее, чем более вероятное изменение, которое трудно спланировать.
Функции, нужные пользователям - ядро системы, которое не потребуется изменять.
Путём небольших приращений экстраполировать систему, определяя дополнительную часть.
Сопряжение характеризует силу связи класса или метода с другими классами или методами.
Поддерживать сопряжение слабым.
Слабое сопряжение (loose coupling) - классы и методы имеют немногочисленные, непосредственные, явные и гибкие отношения с другими классами.
Эффективный механизм соединения максимально прост.
Отношения модулей должны напоминать отношения деловых партнёров, а не сиамских близнецов.
Критерии оценки сопряжения:
-объём связи (число соединений смежду модулями - число параметров метода, число открытых методов);
-видимость (заметность связи между двумя модулями);
-гибкость(лёгкость изменения связи между модулями).
Самые распространённые виды сопряжения:
-простое сопряжение посредством данных-параметров (передача элементарных типов данных через списки параметров);
-простое сопряжение посредством объекта (модуль создаёт экземпляр объекта, с которым сопряжён);
-сопряжение посредством объекта-параметра (объект 1 требует, чтобы объект 2 передал ему объект);
-семантическое сопряжение (один модуль использует семантические знания о внутренней работе другого модуля): модуль 1 передаёт в модуль 2 управляющий флаг, определяющий дальнейшую работу модуля 2; модуль 2 использует глобальные данные после их изменения модулем 1; семантическое сопряжение опасно тем, что изменение кода в используемом модуле может так нарушить работу использующего модуля, что компилятор этого не определит.
Классы и методы должны упрощать работу.
Шаблоны снижают сложность, предоставляя готовые абстракции, снижают число ошибок, стандартизируя детали популярных решений.
Шаблоны имеют эвристическую ценность, указывая на возможные варианты проектирования.
Шаблоны упрощают взаимодействие между разработчиками, позволяя им общаться на более высоком уровне.
Ловушки шаблонов - насильственная адаптация кода к шаблону, применение шаблона, продиктованное не целесообразностью, а желанием испытать шаблон в деле.
Шаблоны проектирования - эффективный инструмент управления сложностью.
Шаблон - это эвристический принцип.
Связность (cohesion) должна быть максимальна.
Связность характеризует то, насколько хорошо все методы класса или все фрагменты метода соответствуют главной цели.
Формируйте иерархии.
Иерархия - это многоуровневая структура организации информации, при которой наиболее общая или абстрактная репрезентация концепции соответствует вершине, а более детальные специализированные репрезентации - более низким уровням.
Люди в целом находят иерархии естественным способом организации сложной информации.
Рисуя сложный объект, люди рисуют его иерархически.
Формализовать контракты классов (обещания клиентов - предусловия, обязательства класса - постусловия).
Грамотно назначать сферы ответственности.
Проектировать систему для тестирования.
Тщательно рассматривать возвожные причины аварий, а не просто копировать другие успешные проекты.
Тщательно выбирать время присвоения переменной конкретного значения (binding time).
Создавать центральные точки управления - управление может быть централизовано в классах, методах, макросах препроцессора, файлах библиотек.
Рисовать диаграммы - они представляют проблему на более высоком уровне абстракции.
Поддерживать модульность проекта системы - каждый метод или класс должен быть похож на чёрный ящик (известны входы и выходы, но неизвестно что внутри).
Проектировать ПО нужно с использованием разных подходов.
Проектирование - итеративный процесс. После его завершения нужно возвращаться к началу.
Лучше обнаруживать варианты, которые не работают, чем ничего не делать.
Нисходящее (top-down) проектирование начинается на высоком уровне абстракции.
Восходящее (bottom-up) начинается со специфики и постепенно переходит ко всё большей общности.
Если сейчас решение кажется вам чуть-чуть хитрым, для любого, кто будет работать над ним позднее, оно станет головоломкой.
Нисходящая стратегия - декомпозиция.
Восходящая - композиция.
Большинство людей находят разбиение крупной концепции на меньшие части более лёгким, чем объединение небольших концепций в более крупную.
Прототипирование работает плохо, если задача недостаточно конкретна.
Имена прототипных классов и пакетов должны иметь префикс prototype.
Механические действия вытесняют творчество.
Регистрировать протоколы обсуждения проекта и принятые решения при помощи Wiki.
Отправлять резюме дискуссий всем членам группы по электронной почте.
Фотографировать схемы на доске.
Хранить плакаты со схемами проекта.
Использовать UML.
Класс - это набор данных и методов, имеющих общую, целостную, хорошо определённую сферу ответственности.
Классы нужны для максимизации части программы, которую можно игнорировать при работе над конкретными фрагментами кода.
Абстрактный тип данных - это набор, включающий данные и выполняемые над ними операции (служащие одной цели).
Преимущества использования АТД:
-возможность сокрытия деталей реализации;
-ограничение области изменений;
-более высокая информативность интерфейса;
-лёгкость оптимизации кода;
-лёгкость проверки кода;
-удобочитаемость и понятность кода;
-ограничение области использования данных;
-возможность работы с сущностями реального мира, а не с низкоуровневыми деталями реализации.
Принципы использования АТД:
-представлять в форме АТД распространённые низкоуровневые типы данных;
-представлять в форме АТД часто используемые объекты, такие как файлы;
-представлять в форме АТД даже простые элементы;
-обращайтесь к АТД так, чтобы это не зависело от среды, используемой для его хранения.
В ООП каждый АТД можно реализовать как класс (класс дополнительно поддерживает наследование и полиморфизм).
Интерфейс класса - это абстракция реализации класса, скрытой за интерфейсом.
Принципы проектирования интерфейсов:
-выражать в интерфейсе класса согласованный уровень абстракции (смешанные уровни
абстракции делают программу всё менее и менее понятной);
-убедиться в понимании того, реализацией какой абстракции является класс;
-представляйте методы вместе с противоположными им методами (создавать противоположные методы, не имея на то причин, не следует);
-убирать постороннюю информацию в другие классы;
-преобразовывать семантические элементы интерфейса в программные, например, утверждениями (интерфейсы должны как можно меньше зависеть от документации);
-элементы интерфейса должны находиться на одном уровне абстракции;
-не включать в класс открытые члены, плохо согласующиеся с абстракцией интерфейса;
-рассматривать абстракцию и связность вместе.
Хорошая инкапсуляция:
-минимизировать доступность классов и их членов;
-не делать данные-члены открытыми;
-не включать в интерфейс класса закрытые детали реализации;
-класс не должен делать предположений о том, как этот интерфейс будет или не будет использоваться;
-избегать использования дружественных классов;
-не делать метод открытым лишь потому, что он использует только открытые методы;
-ценить лёгкость чтения кода выше, чем удобство его написания;
-избегать зависимости клиентского кода от закытой реализации класса, а не от его открытого интерфейса (должен быть интерфейс, позволяющий разобраться не глядя на реализацию);
-остерегаться жесткого сопряжения - сильной связи между двумя классами.
Включение (containment) - один класс содержит примитивный элемент данных или другой класс.
Включение проще наследования и меньше подвержено ошибкам.
Включение можно реализовать отношением "содержит" (в крайнем случае - закрытое наследование).
Класс должен содержать 9 примитивных типов или 5 сложных объектов.
Наследование помогает избегать повторения кода и данных в нескольких местах, централизуя их в базовом классе (один класс является более специализированным вараинтом другого класса).
Проектировать и документировать классы с учётом возможности наследования или запретить его.
Наследование повышает сложность программы.
Все методы базового класса должны иметь в каждом производном классе то же значение.
Убедиться, что наследуется только то, что нужно.
Не используйте имена непереопределяемых методов базового класса в производных классах.
Перемещайте общие интерфейсы, данные и формы поведения на как можно более высокий уровень иерархии наследования, если это не нарушит абстракцию.
С подозрением относитесь к классам, объекты которых создаются в единственном экземпляре (возможно, класс перепутан с объектом).
С подозрением относитесь к базовым классам, имеющим только один производный класс.
С подозрением относитесь к классам, которые переопределяют метод, оставляя его пустым.
Избегайте многоуровневых иерархий наследования, ограничивая иеррахии наследования максимум 6 уровнями.
Предпочитайте полиморфизм, а не крупномасштабную проверку типов.
Делайте все данные закрытыми, а не защищёнными.
Миксин - простой класс, позволяющий добавлять ряд свойств в другой класс.
Ромбовидная схема наследования - классическая проблема.
Используйте множественное наследование, только тщательно рассмотрев все альтернативные варианты и проанализировав влияние выбранного подхода на сложность и понятность системы.
Ради управления сложностью относитесь к наследованию с подозрением.
Включайте в класс как можно меньше методов.
Блокируйте неявные методы и операторы, которые Вам не нужны (private).
Минимизируйте число разных методов, вызываемых классом.
Избегайте опосредованных вызовов методов других классов (цепочек вызовов).
Вообще минимизируйте сотрудничество класса с другими классами.
Инициализируйте по мере возможности все данные-члены во всех конструкторах.
Создавайте классы-одиночки с помощью закрытого конструктора.
Если сомневаетесь, выполняйте полное копирование, а не ограниченное.
Разумные причины создания класса:
-моделирование объектов реального мира;
-моделирование абстрактных объектов;
-снижение сложности;
-изоляция сложности;
-сокрытие деталей реализации;
-ограничение влияния изменений;
-сокрытие глобальных данных;
-упрощение передачи параметров в метод;
-создание центральныз точек управления;
-облегчение повторного использования кода;
-планирование создания семейства программ;
-упаковка родственных операций;
-выполнение специфического вида рефакторинга.
Классы, создавать которые не следует:
-избегайте создания классов, которые всё знают и всё могут;
-если класс имеет только данные, но не формы поведения, то это не класс;
-если класс имеет только формы поведения, но не даные, то это не класс.
Аспекты классов, зависящие от языка:
-поведение переопределённых конструкторов и деструкторов в дереве наследования;
-поведение конструкторов и деструкторов при обработке исключений;
-важность конструкторов по умолчанию (конструкторов без аргументов);
-время вызова деструктора или метода финализации;
-целесообразность переопределения встроенных операторов языка, в том числе операторов присваивания и сравнения;
-управление памятью при создании и уничтожении объектов или при их объявлении и выходе из области видимости.
В настоящее время использование классов - лучший способ достижения модульности.
Метод - это отдельная функция или процедура, выполняющая одну задачу.
Имя метода говорит о его роли.
Метод должен быть документирован, форматирован.
Входные переменные метода не изменяются.
Метод не работает с глобальными переменными.
Метод имеет одну чётко определённую цель, должен быть защищён от получения плохих данных, не использует магические числа; все параметры используются в методе, параметров у метода не более 7, параметры метода упорядочены.
Методы - самый эффективный способ уменьшения объёма и повышения быстродействия программ.
Разумные причины создания методов:
-снижение сложности;
-формирование понятной промежуточной абстракции;
-предотвращение дублирования кода;
-переопределить небольшой грамотно организованный метод легче, чем длинный и плохо спроектированный;
-сокрытие очерёдности действий;
-сокрытие неудобочитаемых операций;
-изоляция непортируемого кода;
-упрощение сложных булевых проверок;
-облегчение определения неэффективных фрагментов кода.
Один из главных ментальных барьеров, препятствующих созданию эффективных методов, - нежелание создавать простой метод для простой цели.
Высокая связность хуже низкой.
Функциональная связность - метод выполняет одну и только одну операцию.
Последовательная связность - метод содержит операции, которые обязательно выполняются в определённом порядке, используют данные предыдущих этапов и не формируют в целом единую функцию.
Коммуникационная связность - выполняемые в методе операции используют одни и те же данные и не связаны между собой иным образом.
Временная связность - когда операции объединяются в метод на том основании, что все они выполняются в один интервал времени.
Не выполнять в методе конкретные операции непосредственно, а вызывать для их выполнения другие методы.
Неприемлимые виды связности:
Процедурная связность - когда операции в методе выполняются в определённом порядке.
Поместить разные операции в разные методы.
Логическая связность - метод включает несколько операций, а выбор выполняемой операции осуществляется на основе передаваемого в метод управляющего флага.
Случайная связность - каких-либо ясных отношений между выполняемыми в методе операциями нет.
Стремиться создавать методы с функциональной связностью.
Советы по выбору удачных имён методов:
-описывайте всё, что метод выполняет (методы с побочными эффектами избавлять от побочных эффектов);
-избегайте невыразительных и неоднозначных глаголов (роль метода должна быть очевидной);
-не используйте для дифференциации имён методов исключительно номера;
-не ограничивайте длину имён методов искусственными правилами (оптимальная длина имени переменной равняется в среднем 9-15 символам, но имена методов обычно длиннее из-за их сложности);
-для именования функции используйте описание возвращаемого значения;
-для именования процедуры используйте выразительный глагол, дополняя его объектом (кроме ООП языков);
-дисциплинированно используйте антонимы (add/remove, begin/end, create/destroy, first/last, get/put, get/set, increment/decrement, insert/delete, lock/unlock, min/max, next/previous, old/new, open/close, show/hide, source/target, start/stop, up/down);
-определяйте конвенции именования часто используемых операций.
Код требует минимальных изменений, если методы состоят в среднем из 100-150 строк.
Предотвращение ошибок коммуникации между методами:
-передавайте параметры в порядке "входные значения - изменяемые значения - выходные значения";
-подумайте о создании собственных ключевых слов in и out;
-документируйте выраженные в интерфейсе предположения о параметрах;
Типы предположений:
-вид параметров: являются ли они исключительно входными, изменяемыми или исключительно выходными;
-единицы измерения (дюймы...);
-смыслы кодов статуса и ошибок, если для их представленияне используются перечисления;
-диапазоны допустимых значений;
-специфические значения, которые никогда не должны передаваться в метод;
-ограничивайте число параметров метода примерно семью;
-подумайте об определении конвенции именования входных, изменяемых и выходных параметров;
-передавайте в метод те переменные или объекты, которые нужны ему для поддержания абстракции интерфейса;
-сопряжение методов должно быть минимальным;
-используйте именованные параметры;
-убедитесь, что фактические (переданные в метод) параметры соответствуют формальным (объявленные в методе);
-проверяйте все возможные пути возврата значения из функции;
-инициализируйте возвращаемое значение значением по умолчанию в начале функции;
-не возвращайте ссылки или указатели на локальные данные (вместо ссылок - данные-члены).
Макросы:
-разрабатывая макрос, заключайте в скобки всё, что можно;
-заключайте макрос, включающий несколько команд, в фигурные скобки;
-не заменяйте методы макросами;
-называйте макросы, расширяющиеся в код подобно методам, так, чтобы при необходимости их можно было заменить методами;-теоретически встраивание методов может повысить быстродействие;
-не злоупотребляйте встраиваемыми методами.
Защита от неправильных входных данных:
-проверяйте все данные из внешних источников;
-проверяйте значения всех входных параметров метода;
-решите, как обрабатывать неправильные входные данные.
Утверждение - это код, используемый во время разработки, с помощью которого программа проверяет правильность своего выполнения (логическое выражение + сообщение).
Положения по применению утверждений:
-используйте процедуры обработки ошибок для ожидаемых событий и утверждения для событий, которые происходить не должны;
-старайтесь не помещать выполняемый код в утверждения;
-используйте утверждения для документирования и проверки предусловий и постусловий;
Предусловия - это соглашения, которые клиентский код, вызывающий метод или класс, обещает выполнить до вызова метода или создания экземпляра объекта.
Постусловия - это соглашения, которые метод или класс обещает выполнить при завершении своей работы.
-для большей устойчивости кода проверяйте утверждения, а затем всё равно обработайте возможные ошибки.
Способы обработки ошибок:
-вернуть нейтральное значение;
В больших, долгоживущих системах различные части могут разрабатываться несколькими проектировщиками 5-10 лет и более.
-заменить следующим корректным блоком данных;
-вернуть тот же результат, что и в предыдущий раз;
-подставить ближайшее допустимое значение;
-записать предупреждающее значение в файл;
-вернуть код ошибки;
-вызвать процедуру или объект-обработчик ошибок;
-показать сообщение об ошибке, где бы она ни случилась;
-обработать ошибку в месте возникновения наиболее подходящим способом;
-прекратить выполнение.
Корректность предполагает, что нельзя возвращать неточный результат; устойчивость требует всегда пытаться сделать что-то, что позволит программе продолжить работу.
Надо стараться реагировать на неправильные значения параметров одинаково во всей программе.
Предложения по исключениям:
-используйте исключения для оповещения других частей программы об ошибках, которые нельзя игнорировать;
-генерируйте исключения только для действительно исключительных ситуаций;
-не используйте исключения по мелочам (стараться обрабатывать ошибки локально);
-избегайте генерировать исключения в конструкторах и деструкторах, если только вы не перехватываете их позднее;
-генерируйте исключения на правильном уровне абстракции;
-вносите в описание исключения всю информацию о его причинах;
-избегайте пустых блоков catch;
-выясните, какие исключения генерирует используемая библиотека;
-рассмотрите вопрос о централизованном выводе информации об исключениях;
-стандартизуйте использование исключений в вашем проекте;
-рассмотрите альтернативы исключениям;
-преобразовывайте данные к нужному типу в момент ввода;
-методы с внешней стороны баррикады должны использовать обработчики ошибок, поскольку небезопасно делать любые предположения о данных; методы внутри баррикад должны использовать утверждения, так как данные, переданные им, считаются проверенными;
-промышленная версия: должна работать быстро, экономна с ресурсами, не позволяет пользователю делать опасные действия; отладочная версия: может работать медленно, может быть расточительной, может предоставлять дополнительные возможности без риска нарушить безопасность;
-внедрите поддержку отладки как можно раньше;
-исключительные случаи должны обрабатываться так, чтобы во время разработки они были очевидны, а в промышленном коде - позволяли продолжить работу;
-чем жестче требования во время разработки, тем проще эксплуатация;
-используйте встреонный препроцессор;
-используйте отладочные заглушки;
-оставьте код, который проверяет существенные ошибки;
-удалите код, проверяющий незначительные ошибки;
-удалите код, приводящий к прекращению работы программы;
-если на стадии разработки ваша программа обнаруживает ошибку, её надо сделать незаметнее, чтобы её могли исправить;-оставьте код, который позволяет аккуратно завершить работу программы;
-регламентируйте ошибки для отдела технической поддержки;
-убедитесь, что оставленные сообщения об ошибках дружелюбны (выводить телефон и адреса электронной почты, по которым можно о ней сообщить).
Обычно каждый метод тестируется при его создании.
Этапы конструирования классов:
1.Создание общей структуры класса;
2.Конструирование процедур класса;
3.Оценка и тестирование всего класса.
Действия по созданию метода:
1.Проектирование метода;
2.Проверка структуры;
3.Кодирование метода;
4.Пересмотр и тестирование кода.
Применение псевдокода:
-применяйте формулировки, в точности описывающие отдельные действия;
-избегайте синтаксических элементов языков программирования;
-описывайте назначение подхода, а не то, как этот подход нужно реализовать на выбранном языке программирования;
-пишите псевдокод на достаточно низком уровне, так чтобы код из него генерировался практически автоматически.
Сформулируйте задачу, решаемую методом, настолько детально, чтобы можно было переходить к созданию метода.
Затруднения в выборе имени метода могут свидетельствовать о том, что его назначение не совсем понятно.
Метод должен иметь понятное, недвусмысленное имя.
В процессе написания метода думайте о том, как вы будете его тестировать.
Исследуйте функциональность, представляемую стандартными библиотеками.
Не тратьте время на реализацию готового алгоритма, по которому написана кандидатская диссертация.
Подумайте обо всём плохом, что может случиться с вашим методом.
Не теряйте времени на вылизывание отдельных методов, пока не выяснится, что это необходимо.
Если доступные библиотеки не предоставляют нужной функциональности, имеет смысл исследовать литературу с описанием алгоритмов.
Общая идея: раз за разом проходиться по псевдокоду, пока каждое его предложение не станет настолько простым, что под ним можно будет вставить строку программы, а псевдокод оставить в качестве документации.
Этапы кодирования метода:
1.Напишите объявление метода;
2.Напишите первый и последний операторы, а псевдокод превратите в комментарии верхнего уровня;
3.Добавьте код после каждого комментария;
4.Проверьте код;
5.Исправьте неточности.
Длина абзаца кода, как и длина абзаца литературного текста, зависит от высказываемой мысли, а его качество - от понимания автором сути.
Проверьте, не нужна ли дальнейшая декомпозиция кода.
Умозрительно проверьте ошибки в методе.
Только около 5% всех ошибоксвязано с аппаратурой, компиляторам или ОС.
Если вы не знаете, почему это работает, вероятно, оно и не работает на самом деле.
Не спешите и не компилируйте программу, пока не будете уверены, что она верна.
Установите наивысший уровень предупреждений компилятора.
Применяйте проверяющие средства.
Убедитесь, что каждая строка выполняется так, как вы ожидаете.
Леса - код, поддерживающий методы при тестировании и не включаемый в конечный продукт.
Полное перепроектирование нестабильного метода полностью оправданно.
Если качество метода неудовлетворительное, вернитесь к псевдокоду.
Рекурсивное применение псевдокода - разбиение метода на более мелкие при необходимости.
Комментарии должны быть адекватными и полезными.
Неявное объявление переменных - одна из самых опасных возможностей языка.
Объявляйте все переменные.
Отключите неявные объявления.
Используйте конвенции именования.
Проверяйте имена переменных.
Инициализируйте каждую переменную при её объявлении.
Инициализируйте каждую переменную там, где она используется в первый раз (если нельзя инициализировать при объявлении).
В идеальном случае сразу объявляйте и определяйте каждую переменную непосредственно перед первым обращением к ней.
Объявляйте переменные по мере возможности как const.
Не забывайте обнулять счётчики и аккумуляторы.
Специализируйте данные-члены класса в его конструкторе.
Проверяйте необходимость повторной инициализации.
Область видимости - фрагмент программы, в котором переменная известна и может быть использована.
Локализуйте обращения к переменным (уменьшать интервал).
Делайте время жизни переменных как можно короче (измеряется в строках).
Избегайте глобальных переменных из-за большого времени жизни.
Инициализируйте переменные, используемые в цикле, непосредственно перед циклом, а не в начале метода, содержащего цикл.
Не присваивайте переменной значение вплоть до его использования.
Группируйте связанные команды.
Разбивайте группы связанных команд на отдельные методы.
Начинайте с самой ограниченной области видимости и расширяйте её только при необходимости.
Локальная область видимости способствует интеллектуальной управляемости.
Персистентность характеризует длительность существования данных.
Время связывания - момент, когда переменная и её значение связываются вместе.
Так как эффективность программирования зависит от минимизации сложности, опытный программист будет обеспечивать такой уровень гибкости, какой нужен для удовлетворения требований, но не более того.
Последовательные данные соответствуют последовательности команд.
Селективные данные соответствуют операторам if и case.
Итеративные данные соответствуют циклам.
Используйте каждую переменную только с одной целью.
Избегайте переменных, имеющих скрытый смысл (избегайте гибридного сопряжения).
Убеждайтесь в том, что используются все объявленные переменные.
Имя переменной должно полно и точно описывать сущность, представляемую переменной.
Имена должны быть максимально конкретны.
Имя переменной описывает проблему, а не её решение.
Отладка программы требует меньше усилий, если имена переменных состоят в среднем из 10-16 символов.
Более длинные имена лучше присваивать редко используемым или глобальным переменным, а более короткие - локальным переменным или переменным, вызываемым в циклах.
Дополняйте имена, относящиеся к глобальному пространству имён, спецификаторами.
Имена вычисляемых значений дополнять спецификатором из Total, Sum, Average, Max, Min, Record, String, Pointer в конце имени.
В именах лучше использовать антонимы: begin/end, first/last, locked/unlocked, min/max, next/previous, old/new, opened/closed, visible/invisible, source/target, source/destination, up/down.
Индексы циклов - i, j, k.
Если код часто изменяется, модернизируется, копируется, лучше делать осмысленные имена индексов.
Имя флага не должно включать фрагмент flag, потому что он ничего не говорит о сути флага.
Значения флагов лучше сравнивать со значениями перечислений, именованных констант или глобальных переменных, выступающих в роли именованных констант.
Использование временных переменных говорит о том, что программист ещё не полностью понял проблему.
Временным переменным давать точное описание.
Типичные имена булевых переменных: признак завершения - done, признак ошибки - error, признак обнаружения - found, признак успешного завершения - ok или success.
Присваивайте булевым переменным имена, подразумевающие значение true или false.
Используйте утвердительные имена булевых переменных.
Имя константы должно характеризовать абстрактную сущность, представляемую константой, а не конкретное значение.
Обязательно используйте конвенции именования, так как это всегда выгодно.
Советы по созданию конвенции:
Проведите различие между именами переменных и именами методов.
Проведите различие между классами и объектами.
Идентифицируйте глобальные переменные, переменные-члены, определения типов, именованные константы, элементы перечислений, неизменяемые параметры.
Формируйте имена так, чтобы их было легко читать.
Сокращение имён стало пережитком.
Не сокращайте слова только на один символ.
Всегда используйте один и тот же вариант сокращения.
Сокращайте имена так, чтобы их можно было произнести.
Избегайте комбинаций, допускающих неверное прочтение или произношение имён.
Обращайтесь к словарю для разрешения конфликтов имён.
Документируйте очень короткие имена прямо в коде при помощи таблиц.
Указывайте все сокращения в проектном документе "Стандартные аббревиатуры".
Помните, что имена создаются в первую очередь для программистов, читающих код.
Избегайте обманчивых имён или аббревиатур.
Избегайте имён, имеющих похожие значения.
Избегайте переменных, имеющих разную суть, но похожие имена.
Избегайте имён, имеющих похожее звучание, таких как wrap и rap.
Избегайте имён, включающих цифры.
Избегайте орфографических ошибок.
Избегайте слов, при написании которых люди часто допускают ошибки.
Проводите различие между именами не только по регистру букв.
Избегайте смешения естественных языков.
Избегайте имен стандартных типов, переменных и методов.
Не используйте имена, которые совершенно не связаны с тем, что представляют переменные.
Избегайте имён, содержащих символы, которые можно спутать с другими символами.
Избегайте "магических чисел".
Используйте в программе как константы только 0 и 1, а любые другие числа определите как литералы с понятными именами.
Пишите код, предупреждающий появление ошибки деления на 0.
Выполняйте преобразования типов понятно.
Избегайте сравнений разных типов.
Обращайте внимание на предупреждения вашего компилятора.
Проверяйте целочисленность операций деления.
Проверяйте переполнение целых чисел.
Проверяйте на переполнение промежуточные результаты.
Избегайте сложения и вычитания слишком разных по размеру чисел с плавающей запятой.
Избегайте сравнений на равенство чисел с плавающей запятой.
Предупреждайте ошибки округления.
Проверяйте поддержку специальных типов данных в языке и дополнительных библиотеках.
Избегайте магических символов и строк.
Следите за ошибками завышения/занижения на единицу индексов.
Узнайте как ваш язык и система поддерживают Unicode.
Разработайте стратегию интернационализации/локализации в ранний период жизни программы.
Если вам известно, что нужно поддерживать только один алфавит, рассмотрите вариант использования набора символов ISO8859.
Если вам необходимо поддерживать несколько языков, используйте Unicode.
Выберите целостную стратегию преобразования строковых типов.
Используйте логические переменные для документирования программы.
Используйте логические переменные для упрощения сложных условий.
Используйте перечислимые типы для читабельности.
Используйте перечислимые типы для надёжности.
Используйте перечислимые типы для модифицируемости.
Используйте перечислимые типы как альтернативу логическим переменным.
Проверяйте некорректные значения в case с перечислимым типом.
Настройте первый и последний элемент перечислимого типа для использования в качестве границ циклов.
Зарезервируйте первый элемент перечислимого типа как недопустимый.
Точно укажите в стандартах кодирования для проекта, как должны использоваться первый и последний элементы, и неукоснительно придерживайтесь этого.
Помните о подводных камнях в присваивании явных значений элементам перечисления.
Если в языке нет перечислимых типов, их можно имитировать, используя глобальные переменные или классы.
Используйте именованные константы в объявлениях данных.
Избегайте литеральных значений, даже "безопасных".
Если в языке их нет, имитируйте именованные константы с помощью переменных или классов правильной области видимости.
Не используйте для представления одной сущности именованные константы в одном месте и литералы в другом.
Убедитесь, что все значения индексов массива не выходят за его границы.
Обдумайте применение контейнеров вместо массивов или рассматривайте массивы как последовательные структуры (наборов, стеков, очередей и т.п.).
Проверяйте конечные точки массивов.
В многомерном массиве убедитесь, что его индексы используются в правильном порядке.
Остерегайтесь пересечения индексов - перемены мест индексов.
Принципы создания собственных типов:
Создавайте типы с именами, отражающими их функциональность.
Преимущество создания собственных типов в появлении слоя, скрывающего язык.
Избегайте предопределённых типов.
Не переопределяйте предопределённые типы.
Определите подстановки для переносимости.
Рассмотрите вопрос создания класса вместо использования typedef.
Используйте структуры для упрощения списка параметров.
Между методами должна передаваться только та информация, которую необходимо знать.
Используйте структуры для упрощения сопровождения.
Используйте технологию, не основанную на указателях.
Избегайте преднамеренных изменений глобальных данных.
Глобальные данные должны сохранять свои значения, даже если будет запущено несколько копий программы.
Глобальные данные затрудняют повторное использование кода.
Глобальные данные приводят к неопределённому порядку инициализации.
Глобальные данные привносят нарушение модульности и интеллектуальной управляемости.
Глобальные данные подходят для хранения глобальных значений.
Глобальные данные подходят для эмуляции именованных констант, если они не поддерживаются.
Глобальные данные подходят для эмуляции перечислимых типов.
Глобальные данные можно использовать для оптимизации обращений к часто используемым данным.
Применение глобальных переменных позволяет избежать бродячие данные.
Начните с объявления всех переменных локальными и делайте их глобальными только по необходимости.
Различайте глобальные переменные и переменные-члены класса.
Используйте методы доступа.
Требуйте, чтобы в свойствах весь код обращался к данным через методы доступа.
Не валите все глобальные данные в одну кучу.
Управляйте доступом к глобальным переменным с помощью блокировок.
Встройте уровень абстракции в методы доступа (не используйте структуры напрямую).
Выполняйте доступ к данным на одном и том же уровне абстракции.
Разработайте соглашения по именованию, которые сделают глобальные переменные очевидными.
Создайте хорошо аннотированный список всех глобальных переменных.
Не храните промежуточных результатов в глобальных переменных.
Не считайте, что вы не использутете глобальные переменные, поместив все данные в чудовищный объект и передавая его всюду.
Операторы:
Факт зависимости одного выражения от другого должен быть понятен из имен методов.
Организуйте код так, чтобы зависимости между операторами были очевидными.
Называйте методы так, чтобы зависимости были очевидными.
Используйте параметры методов, чтобы сделать зависимости очевидными.
Документируйте неявные зависимости с помощью комментариев.
Проверяйте зависимости с помощью утверждений или кода обработки ошибок.
Располагайте взаимосвязанные действия вместе.
При написании if-выражений:
Сначала напишите код номинального хода алгоритма, затем опишите исключительные ситуации.
Убедитесь, что при сравнении на равенство ветвление корректно.
Размещайте нормальный вариант после if, а не после else.
Размещайте осмысленные выражения после оператора if.
Рассмотрите вопрос использования блока else (в 50-80% случаев использования if следовало применить и else).
Проверяйте корректность выражения else.
Проверяйте возможную перестановку блоков if и else.
Упрощайте сложные проверки с помощью вызовов логических функций.
Размещайте наиболее вероятные варианты раньше остальных.
Убедитесь, что учтены все варианты.
Замените последовательности if-then-else другими конструкциями, которые поддерживает ваш язык программирования.
В case:
-упорядочивайте варианты case по алфавиту или численно;
-поместите правильный вариант case первым;
-отсортируйте варианты по частоте.
Сделайте обработку каждого варианта case простой.
Не конструируйте искусственные переменные с целью получить возможность использовать оператор case.
Используйте вариант по умолчанию только для обработки настоящих значений по умолчанию.
Используйте вариант по умолчанию для выявления ошибок.
Старайтесь не писать код, проваливающийся сквозь блоки оператора case.
Минимизируйте число факторов, влияющих на цикл.
Вынесите за пределы цикла всё управление, какое только можно.
Размещайте вход в цикл только в одном месте.
Размещайте инициализационный код непосредственно перед циклом.
Используйте while(true) для бесконечных циклов.
Предпочитайте циклы for, если они применимы.
Не используйте цикл for, если цикл while подходит больше.
Используйте{и}для обрамления выражений в цикле.
Избегайте пустых циклов.
Располагайте служебные операции либо в начале, либо в конце цикла.
Заставьте каждый цикл выполнять только одну функцию.
При завершении цикла убедитесь, что выполнение цикла закончилось.
Сделайте условие завершения цикла очевидным.
Не играйте с индексом цикла for для завершения цикла.
Избегайте писать код, зависящий от последнего значения индекса цикла.
Рассмотрите использование счетчиков безопасности, чтобы определить, не слишком ли много раз выполняется цикл.
Рассмотрите использование операторов break вместо логических флагов в цикле while.
Остерегайтесь цикла с множеством операторов break, разбросанных по всему коду.
Используйте continue для проверок в начале цикла.
Используйте структуру break с метками, если ваш язык её поддерживает.
Используйте операторы break и continue очень осторожно.
Используйте порядковые или перечислимые типы для границ массивов и циклов.
Используйте смысловые имена переменных, чтобы сделать вложенные циклы читабельными.
Используйте смысловые имена во избежание пересечения индексов.
Ограничивайте видимость переменных-индексов цикла самим циклом.
Делайте циклы достаточно короткими, чтобы их можно было увидеть сразу целиком.
Ограничивайте вложенность тремя уровнями.
Выделяйте внутреннюю часть длинных циклов в отдельные методы.
Делайте длинные циклы особенно ясными.
Простое создание цикла - изнутри наружу.
Используйте return, если это повышает читабельность.
Упрощайте сложную обработку ошибок с помощью сторожевых операторов (досрочного return).
Минимизируйте числов возвратов из каждого метода.
Убедитесь, что рекурсия остановится.
Предотвращайте бесконечную рекурсию с помощью счетчиков безопасности.
Ограничьте рекурсию одним методом.
Следите за стеком при использовании рекурсии.
Не используйте рекурсию для факториалов и чисел Фибоначчи.
Лучше обходиться без goto.
Код с goto переписать с помощью вложенных if.
Код с goto переписать с использованием статусной переменной.
Табличный метод - это схема, позволяющая искать информацию в таблице, а не использовать для этого логические выражения.
Таблица с прямым доступом позволяет обращаться изначально напрямую (есть ещё индексированный и ступенчатый доступ).
Используйте неявное сравнение логических величин с true или false.
Разбивайте сложные проверки на части с помощью новых логических переменных.
Размещайте сложные выражения в логических функциях.
Используйте таблицы решений для замены сложных условий.
В операторах if заменяйте негативные выражения позитивными, меняя местами блоки if и else.
Применяйте теоремы Деморгана для упрощения логических проверок с отрицаниями.
Используйте скобки для явного задания порядка вычисления.
Используйте простой метод подсчета для проверки симметричности скобок ( ( - +1, ) - -1).
Заключайте в скобки логическое выражение целиком.
Организуйте числовые условия так, чтобы они соответствовали порядку точек на числовой прямой.
Неявно сравнивайте логические переменные.
Сравнивайте числа с 0.
Сравнивайте указатели с null.
В C-подобных языках помещайте константы с левой стороны сравнений.
Учитывайте разницу между a==b и a.equals(b) (Java).
Пишите обе скобки блока одновременно.
Всегда используйте скобки для условных операторов (для пояснения).
Привлекайте внимание к нужным выражениям.
Создайте для пустых выражений макрос препроцессора или встроенную функцию DoNothing().
Подумайте, не будет ли код яснее с непустым телом цикла.
Не используйте больше 3 уровней вложенности if.
Упростите вложенные if с помощью повторной проверки части условия.
Упростите вложенные if с помощью блока с выходом do{...break...}while(false);.
Преобразуйте вложенные if в набор if-then-else.
Преобразуйте вложенные if в оператр case.
Факторизуйте глубоковложенный код в отдельный метод.
Используйте более объекто-ориентированный подход.
Перепроектируйте глубоковложенный код.
Основной тезис структурного программирования: любая управляющая логика программы может быть реализована с помощью последовательности, выбора и итерации.
Методики поиска дефектов лучше применять в комбинации.
Обзор кода эффективнее тестирования.
Ошибки в требованиях или архитектуре обычно имеют более широкие следствия, чем ошибки конструирования.
Повышение качества системы снижает расходы на ее разработку.
Средняя производительность труда программистов эквивалентна 10-50 строкам кода на одного человека в день.
Устранение дефектов - самый дорогой и длительный этап разработки ПО.
На создание ПО без дефектов не всегда уходит больше времени, чем на написание ПО с дефектами.
Разработчики допускают в среднем от 1 до 3 дефектов в час при проектировании и от 5 до 8 дефектов в час при кодировании.
Каждый час инспекции кода предотвращает около 100 часов аналогичной работы (тестирования и исправления дефектов).
Поддерживайте парное программирование стандартами кодирования.
Не позволяйте парному программированию превратиться в наблюдение.
Не используйте парное программирование для реализации простых фрагментов.
Регулярно меняйте состав пар и назначаемые парам задачи.
Объединяйте в пару людей, предпочитающих одинаковый темп работы.
Убедитесь, что оба члена пары видят экран.
Не объединяйте в пары людей, которые не нравятся друг другу.
Не составляйте пару из людей, которые ранее не программировали в паре.
Назначьте лидера группы.
Инспектор может проанализировать за час около 500 строк.
Тестирование - самая популярная методика повышения качества.
Тестирование - средство обнаружения ошибок.
Отладка - средство поиска и устранения причин уже обнаруженных ошибок.
Тестированию, выполняемому разработчиками, следует посвящать от 8% до 25% общего времени работы над проектом.
Искусство тестирования заключается в выборе тестов, способных обеспечить максимальную вероятность обнаружения ошибок.
Структурированное базисное тестирование - протестировать каждый оператор программы хотя бы раз.
Проверяйте код тестов.
Планируйте тестирование программы так же, как и ее разработку.
Храните тесты.
Встраивайте блочные тесты в среду тестирования.
Используйте генераторы случайных данных.
Изучите программу, над которой работаете.
Изучите собственные ошибки.
Изучите качество своего кода с точки зрения кого-то, кому придется читать его.
Изучите используемые способы решения проблем.
Изучите используемые способы исправления дефектов.
Формулируя гипотезу, используйте все имеющиеся данные.
Детализируйте тесты, приводящие к ошибке.
Проверяйте код при помощи блочных тестов.
Используйте разные инструменты.
Воспроизведите ошибку несколькими способами.
Генерируйте больше данных для формулирования большего числа гипотез.
Используйте "мозговой штурм" для построения нескольких гипотез.
Составьте список подходов, которые стоит попробовать.
Сохраните подозрительную область кода.
С подозрением относитесь к классам, которые содержали дефекты ранее.
Проверьте код, который был изменён недавно.
Расширьте подозрительный фрагмент кода.
Выполняйте интеграцию инкрементно.
Проверяйте наличие распространённых дефектов.
Обсудите проблему с кем-то другим.
Отдохните от проблемы.
Установите лимит времени для быстрой и грязной отладки.
Составьте список методик грубой силы.
Не полагайтесь на номера строк в сообщениях компилятора.
Не доверяйте сообщениям компилятора.
Не доверяйте второму сообщению компилятора.
Разделяйте программу на части.
Грамотно ищите неверно размещённые комментарии и кавычки.
Прежде чем браться за решение проблемы, поймите её.
Подтвердите диагноз проблемы.
Расслабьтесь (не поддавайтесь соблазну сэкономить время).
Сохраняйте первоначальный исходный код.
Устраняйте проблему, а не её симптомы.
Изменяйте код только при наличии веских оснований.
Вносите в код по одному изменению.
Добавляйте в набор тестов блочные тесты, приводящие к проявлению имеющихся дефектов.
Поищите похожие дефекты.
Выбирайте во время конструирования имена, ясно отличающиеся от других имён.
Используйте утилиты сравнения исходного кода.
Задайте в компиляторе максимально строгий уровень диагностики и устраняйте все ошибки и предупреждения.
Рассматривайте предупреждения как ошибки.
Стандартизуйте параметры компилятора в масштабе всего проекта.
Используйте утилиты расширенной проверки синтаксиса и логики.
Профилируйте код для поиска ошибок.
Даже в хорошо управляемых проектах требования изменяются на 1-4% в месяц.
Современные подходы снижают предсказуемость кодирования.
Факторинг - максимальная декомпозиция программы на составляющие части.
Причины выполнения рефакторинга:
Код повторяется.
Метод слишком велик.
Цикл слишком велик или слишком глубоко вложен в другие циклы.
Класс имеет плохую связность.
Интерфейс класса не формирует согласованную абстракцию.
Метод принимает слишком много параметров.
Отдельные части класса изменяются независимо от других частей.
При изменении программы требуется параллельно изменять несколько классов.
Вам приходится параллельно изменять несколько иерархий наследования.
Вам приходится параллельно изменять несколько блоков case.
Родственные элементы данных, используемые вместе, не организованы в классы.
Метод использует больше элементов другого класса, чем своего собственного.
Элементарный тип данных перегружен.
Класс имеет слишком ограниченную функциональность.
По цепи методов передаются бродячие данные.
Объект-посредник ничего не делает.
Один класс слишком много знает о другом классе.
Метод имеет неудачное имя.
Данные-члены сделаны открытыми.
Подкласс использует только малую долю методов своих предков.
Сложный код объясняется при помощи комментариев.
Код содержит глобальные переменные.
Перед вызовом метода выполняется подготовительный код (после вызова метода выполняется код уборки).
Программа содержит код, который может когда-нибудь понадобиться.
Рефакторинги на уровне данных:
Замена магического числа на именованную константу.
Присвоение переменной более ясного или информативного имени.
Встраивание выражения в код.
Замена выражения на вызов метода.
Введение промежуточной переменной.
Преобразование многоцелевой переменной в несколько одноцелевых переменных.
Использование локальной переменной вместо параметра.
Преобразование элементарного типа данных в класс.
Преобразование набора кодов в класс или перечисление.
Преобразование набора кодов в класс, имеющий производные классы.
Преобразование массива в класс.
Инкапсуляция набора.
Замена традиционной записи на класс данных.
Рефакторинги на уровне отдельных операторов:
Декомпозиция логического выражения.
Вынесение сложного логического выражения в грамотно названную булеву функцию.
Консолидация фрагментов, повторяющихся в разнах частях условного оператора.
Использование оператора break или return вместо управляющей переменной цикла.
Возврат из метода сразу после получения ответа вместо установки возвращаемого значения внутри вложенных операторов if-then-else.
Замена условных операторов (обычно многочисленных блоков case) на вызов полиморфного метода.
Создание и использование "пустых" объектов вместо того, чтобы проверять, равно ли значение null.
Извлечение метода из другого метода.
Встраивание кода метода.
Преобразование объёмного метода в класс.
Замена сложного алгоритма на простой.
Добавление параметра.
Удаление параметра.
Отделение операций запроса данных от операций изменения данных.
Объединение похожих методов путём их параметризации.
Разделение метода, поведение которого зависит от полученных параметров.
Передача в метод целого объекта вместо отдельных полей.
Передача в метод отдельных полей вместо целого объекта.
Инкапсуляция нисходящего приведения типов.
Рефакторинги реализации классов:
Замена объектов-значений на объекты-ссылки.
Замена объектов-ссылок на объекты-значения.
Замена виртуальных методов на инициализацию данных.
Изменение положения методов-членов или данных-членов в иерархии наследования.
Перемещение специализированного кода в подкласс.
Объединение похожего кода и его перемещение в суперкласс.
Рефакторинги интерфейсов классов:
Перемещение метода в другой класс.
Разделение одного класса на несколько.
Удаление класса.
Сокрытие делегата.
Удаление посредника.
Замена наследования на делегирование.
Замена делегирования на наследование.
Создание внешнего метода.
Создание класса-расширения.
Инкапсуляция открытой переменной-члена.
Удаление методов установки значений неизменяемых полей.
Сокрытие методов, которые не следует вызывать извне класса.
Инкапсуляция неиспользуемых методов.
Объединение суперкласса и подкласса, имеющих очень похожую реализацию.
Рефакторинг на уровне системы:
Создание эталонного источника данных, которые вы не можете контролировать.
Изменение однонаправленной связи между классами на двунаправленную.
Изменение двунаправленной связи между классами на однонаправленную.
Предоставление фабричного метода вместо простого конструктора.
Замена кодов ошибок на исключения или наоборот.
Сохраняйте первоначальный код.
Стремитесь ограничить объем отдельных видов рефакторинга.
Выполняйте отдельные виды рефакторинга по одному за раз.
Составьте список действий, которые вы собираетесь предпринять.
Составьте и поддерживайте список видов рефакторинга, которые следует выполнить позже.
Часто создавайте контрольные точки.
Используйте предупреждения компилятора.
Выполняйте регрессивное тестирование.
Создавайте дополнительные тесты.
Выполняйте обзоры изменений.
Изменяйте подход в зависимости от рискованности рефакторинга.
Не рассматривайте рефакторинг как оправдание написания плохого кода с намерением исправить его позднее.
Не рассматривайте рефакторинг как способ, позволяющий избежать переписывания кода.
Тратьте время на 20% видов рефакторинга, обеспечивающих 80% выгоды.
Выполняйте рефакторинг при создании новых методов.
Выполняйте рефакторинги при создании новых классов.
Выполняйте рефакторинг при исправлении дефектов.
Выполняйте рефакторинг модулей, подверженных ошибкам.
Выполняйте рефакторинг сложных модулей.
При сопровождении программы улучшайте фрагменты, к которым прикасаетесь.
Определите интерфейс между аккуратным и безобразным кодом и переместите безобразный код на другую сторону этого интерфейса.
Простое задание явных целей повышает вероятность их достижения.
На 20% методов программы приходятся 80% времени её выполнения.
Сокращение числа строк высокоуровневого кода не повышает быстродействие и не уменьшает объем итогового машинного кода.
Без измерения производительности вы никак не сможете точно узнать, помогли ваши изменения программе или навредили.
Методики, повышающие производительность в одной среде, могут снижать ее в других.
До создания полностью работоспособной программы найти узкие места в коде почти невозможно.
Концентрация на производительности во время первоначальной разработки отвлекает от достижения других целей.
Корректность важнее быстродействия.
Частые причины снижения эффективности:
Операции ввода/вывода.
Замещение страниц памяти.
Системные вызовы.
Интерпретируемые языки.
Ошибки.
Повышение быстродействия исходит из замены дорогой операции на более дешевую.
Главная проблема оптимизации кода: результат любого отдельного вида оптимизации непредсказуем.
Замыкание цикла - принятие решения внутри цикла при каждой его интерации.
Разомкнутый цикл - принятие решения вне цикла.
Если два цикла работают с одним набором элементов, можно выполнить их объединение.
После частичного развёртывания цикла при каждой его итерации обрабатывается не один случай, а два и более.
Вкладывайте более ресурсоемкий цикл в менее ресурсоемкий.
Минимизируйте число обращений к массивам.
Даже слепые белки иногда наталкиваются на орехи.
Назначьте двух человек на каждую часть проекта.
Рецензируйте каждую строку кода.
Введите процедуру подписания кода.
Распространяйте для ознакомления хорошие примеры кода.
Подчеркивайте, что код - это общее имущество.
Награждайте за хороший код.
"Я должен быть в состоянии прочесть и понять любой код, написанный в проекте."
Следуйте систематической процедуре контроля изменений.
Обрабатывайте затраты на каждое изменение.
Оценивайте затраты на каждое изменение.
Относитесь с подозрением к изменениям большого объема.
Учредите комитет контроля изменений.
Соблюдайте бюрократические процедуры, но не позволяйте страху перед бюрократией препятствовать эффективному контролю изменений.
Оценка графика контруирования:
Определите цели.
Выделите время для оценки.
Выясните требования к программе.
Делайте оценки на низком уровне детализации.
Используйте несколько способов оценки и сравнивайте полученные результаты.
Периодически делайте повторную оценку.
Храните сведения об опыте проектов в вашей организации и используйте их для оценки времени, необходимого будущим проектам.
Наибольшее влияние на график программного проекта оказывает размер создаваемой программы.
Если вы отстаете:
Надеяться, что вы сможете наверстать упущенное.
Задержки и отклонения от графика обычно увеличиваются по мере приближения к концу проекта.
Увеличить команду (если есть независимые задачи).
Сократить проект.
При первоначальном планировании продукта разделите его возможности на категории "должны быть", "хорошо бы сделать", "необязательные".
Причины, по которым стоит проводить измерение проекта:
Для любого атрибута проекта существует возможность его измерения, что в любом случае не означает отказа от его измерения.
Отдавайте себе отчет о побочных эффектах измерения.
Возражать против измерений означает утверждать, что лучше не знать о том, что на самом деле происходит.
80% работы выполняют 20% сотрудников.
Если вы хотите контролировать стиль программиста:
Вы вторгаетесь в область, требующую деликатного обращения.
Из уважения к теме используйте термины "предложения" и "советы".
Старайтесь обходить стороной спорные вопросы, предпочитая давать явные поручения.
Предложите программистам выработать собственные стандарты.
Просвещайте менеджеров.
Писать и тестировать маленькие участки программы, а затем комбинировать эти кусочки друг с другом по одному.
Преимущества этой инкрементной итерации:
Ошибки можно легко обнаружить.
В таком проекте система раньше становится работоспособной.
Вы получаете улучшенный мониторинг состояния.
Вы улучшите отношения с заказчиком.
Системные модули тестируются гораздо полнее.
Вы можете создать систему за более короткое время.
При нисходящей интеграции вы создаете те классы, которые находятся на вершине иерархии, первыми, а те, что внизу, - последними.
Систему можно разбить на вертикальные слои.
При восходящей интеграции вы пишете и интегрируете сначала классы, находящиеся в низу иерархии.
При риск-ориентированной интеграции вы решаете, какие части системы будут самыми трудными, и реализуете их первыми.
Ещё один подход - интеграция одной функции в каждый момент времени.
Создавайте сборку ежедневно.
Проверяйте правильность сборок.
Выполняйте дымовые тесты ежедневно.
Поддерживайте актуальность дымового теста.
Автоматизируйте ежедневную сборку и дымовой тест.
Организуйте группу, отвечающую за сборки.
Вносите исправления в сборку, только когда имеет смысл это делать, но не откладывайте внесение исправлений надолго.
Требуйте, чтобы разработчики проводили дымовое тестирование своего кода перед его добавлением в систему.
Создайте область промежуточного хранения кода, который следует добавить к сборке.
Назначьте наказание за нарушение сборки.
Выпускайте сборки по утрам.
Создавайте сборку и проводите дымовой тест даже в экстремальных условиях.
Под "непрерывной" понимается "по крайней мере ежедневная" интеграция.
До 40% времени программист тратит на редактирование исходного кода.
Передовой набор инструментов позволяет повысить производительность более, чем на 50%.
20% инструментария используются в 80% случаев.
Хорошее визуальное форматирование показывает логическую структуру программы.
Абзац кода должен содержать только взаимосвязанные операторы, выполняющие одно задание.
Начало нового абзаца в коде нужно указывать с помощью пустой строки.
Оптимальное число пустых строк в программе - 16%.
Операторы выделяются отступами, когда они следуют после выражения, от которого логически зависят.
Оптимальными являются отступы из 2-4 пробелов.
Формируйте блоки из 1 оператора единообразно.
В сложных выражениях размещайте каждое условие на отдельной строке.
Избегайте операторов goto.
Не используйте форматирование в конце строки в виде исключения для операторов case.
Используйте пробелы, чтобы сделать читаемыми логические выражения.
Используйте пробелы, чтобы сделать читаемыми обращения к массиву.
Используйте пробелы, чтобы сделать читаемыми аргументы методов.
Сделайте так, чтобы незавершенность выражения была очевидна.
Располагайте сильно связанные элементы вместе.
При переносе строк в вызове метода используйте отступ стандартного размера.
Упростите поиск конца строки с продолжением.
При переносе строк в управляющем выражении делайте отступ стандартного размера.
Не выравнивайте правые части выражений присваивания.
При переносе строк в выражениях присваивания применяйте отступы стандартного размера.
Располагайте каждое объявление данных в отдельной строке.
Объявляйте переменные рядом с местом их первого использования.
Разумно упорядочивайте объявления.
Делайте в комментации такой же отступ, как и в соответствующем ему коде.
Отделяйте каждый комментарий хотя бы одной пустой строкой.
Используйте пустые строки для разделения составных частей метода.
Используйте стандартный отступ для аргументов метода.
Если файл содержит более 1 класса, четко определяйте границы каждого класса.
Помещайте каждый класс в отдельный файл.
Называйте файл в соответствии с именем класса.
Отделяйте методы друг от друга с помощью хотя бы 2 пустых строк.
Упорядочивайте методы по алфавиту.
Используйте стиль комментирования, который легко поддерживать.
Используйте содержательные комментарии.
Избегайте комментариев, высосанных из пальца.
Не используйте комментарии в концах строк, относящиеся к нескольким строкам кода.
Используйте комментарии в концах строк для пояснения объявлений данных.
Не используйте комментарии в концах строк для вставки пометок во время сопровождения ПО.
Используйте комментарии в концах строк для обозначения концов блоков.
Описывайте цель блока кода, следующего за комментарием.
Во время документирования сосредоточьтесь на самом коде.
Придумывая комментарий абзаца, стремитесь ответить на вопрос "почему", а не "как".
Используйте комментарии для подготовки читателя кода к последующей информации.
Не размножайте комментарии без необходимости.
Документируйте сюрпризы.
Избегайте сокращений.
Проведите различие между общими и детальными комментариями.
Комментируйте все, что имеет отношение к ошибкам или недокументированным возможностям языка или среды.
Указывайте в комментариях единицы измерения численных величин.
Указывайте в комментариях диапазоны допустимых значений численных величин.
Комментируйте смысл закодированных значений.
Комментируйте ограничения входных данных.
Документируйте флаги до уровня отдельных битов.
Включайте в комментарии, относящиеся к переменной, имя переменной.
Документируйте глобальные данные.
Пишите комментарий перед каждым оператором if, блоком case, циклом или группой операторов.
Комментируйте завершение каждой управляющей структуры.
Рассматривайте комментарии в концах циклов как предупреждения о сложности кода.
Располагайте комментарии близко к описываемому ими коду.
Описывайте каждый метод одним-двумя предложениями перед началом метода.
Документируйте параметры в местах их объявления.
Используйте утилиты документирования кода.
Проведите различие между входными и выходными данными.
Документируйте выраженные в интерфейсе предположения.
Комментируйте ограничения методов.
Документируйте глобальные результаты выполнения метода.
Документируйте источники используемых алгоритмов.
Используйте комментарии для маркирования частей программы.
Опишите подход к проектированию класса.
Опишите ограничения класса, предположения о его использовании и так далее.
Прокомментируйте интерфейс класса.
Не документируйте в интерфейсе класса детали реализации.
Опишите назначение и содержание каждого файла.
Укажите в блочном комментарии свои имя/фамилию, адрес электронной почты и номер телефона.
Включите в файл тег версии.
Включите в блочный комментарий юридическую информацию.
Присвойте файлу имя, характеризующее его содержание.
Лучшие программисты создают программы в 10 раз быстрее коллег.
Изучите процесс разработки.
Экспериментируйте.
Читайте о решении проблем.
Анализируйте и планируйте, прежде чем действовать.
Изучайте успешные проекты.
Читайте.
Читайте другие книги и периодические издания.
Общайтесь с единомышленниками.
Постоянно стремитесь к профессиональному развитию.