Определения

Требование (requirement)
Одна конкретная вещь, которую должна делать (или должна НЕ делать) система, чтобы работать корректно.
Вариант использования (use case)
Описывает в виде списка шагов, каким именно образом система взаимодействует с конечным пользователем или любой другой системой для достижения одной цели пользователя. Содержит опциональные и/или альтернативные способы достижения цели.
Сценарий (scenario)
Один конкретный путь (комбинация шагов) через вариант использования. Линейный, не содержит ветвлений.
Связность (cohesion)
Измеряет степень взаимозависимости элементов модуля, класса или иного объекта. Связность прямо пропорциональна качеству определения ответственности класса (в идеале - одна ответственность), высокая связность выражается в наличии у класса только одного набора очень тесно связанных операций и данных.
Анализ предметной области
Процесс определения, сбора, организации и представления релевантной информации о предметной области, основанный на изучении существующих систем и их истории разработки, знаний, полученных от экспертов, базовой теории и технологиях, применяемых в предметной области.

Резюме глав

Глава 1. Well designed apps rock

  • В хрупком (fragile) приложении достаточно малейших изменений для полного нарушения работоспособности
  • Гибкие приложения строятся на основе принципов ООП - инкапсуляции и делегирования
  • Инкапсуляция в ООП - разбиение приложения на логические части таким образом, чтобы отделить часто изменяющиеся части приложения от более стабильных
  • Делегирование - передача ответственности за выполнение части задачи другому объекту
  • Всегда начинайте проект с определения того, что именно хочет потребитель
  • После того, как базовая функциональность готова, поработайте над дизайном (проектирование), чтобы сделать приложение более гибким
  • Имея функциональный и гибкий дизайн можно применять паттерны проектирования, чтобы код приложения было проще переиспользовать
  • Плохо спроектированное, но работающее приложение может удовлетворить заказчика, но поддерживать и расширять его - это боль
  • Объектно-ориентированное проектирование и анализ дают возможность проектировать ПО таким образом, чтобы удовлетворять как заказчика, так и программиста, т.е. Две ценности ПО - поведение и структура

Глава 2. Gathering requirements

  • Требования - это вещи, которые должна делать система, чтобы работать корректно. Хорошие требования обеспечивают работу системы в соответствии с ожиданиями пользователей
  • Начальные требования обычно исходят от конечных пользователей и/или заказчика
  • Для получения максимально полного набора хороших требований стоит составить варианты использования, детально описывающие, что именно делает система в процессе работы
  • Составные части варианта использования: единственная четкая цель, начальные и конечные состояния (условия), внешний инициатор и один/несколько путей достижения цели
  • Для каждой цели пользователей должен существовать как минимум один вариант использования
  • На основе вариантов использования можно уточнить список требований таким образом, чтобы каждый шаг варианта использования покрывал / реализовывал какое-либо требование. Такой уточненный / расширенныый набор требований обеспечит возможность функционирования системы.
  • Варианты использования необходимо проверить “реальной жизнью”, т.е. предусмотреть альтернативные пути достижения цели пользователя в случае, если что-то идет не так.

Глава 3. Requirements change

  • Требования всегда будут изменяться на протяжении жизни проекта, при этом система должна соответствующим образом измениться, эволюционировать, чтобы удовлетворять новым требованиям
  • При изменении требований первым делом нужно обновить варианты использования
  • Сценарий - это один возможный путь через шаги варианта использования. Вариант использования может содержать несколько сценариев при условии, что все они ведут к достижению одной и той же цели пользователя
  • Альтернативные пути по варианту использования могут быть как отдельными шагами или полностью другим (отличающимся от happy-path) путем через какую-либо часть варианта использования
  • Альтернативные шаги в рамках одного варианта использования обозначаются дополнительным уровнем нумерации, например 6.1 или 5.2.3

Глава 4. Analysis

  • Анализ помогает обеспечить работоспособность системы в реальном мире, а не в идеальных условиях
  • Варианты использования должны быть написаны на естественном языке, понятном программистам, заказчикам и менеджерам
  • Варианты использования стоит писать в том формате, который наиболее полезен всей команде, а не просто продиктован авторитетами
  • Хороший вариант использования точно описывает, что делает система, но ни слова не говорит о том, как она это делает
  • Делегирование (как и инкапсуляция) защищает объекты от изменений реализации других объектов
  • Текстовый анализ (выделение объектов и действий на основе существительных и глаголов) проще проводить для хорошо написанных вариантов использования
  • Существительные в вариантах использования очень важны, даже если в итоге они не превращаются в классы на уровне кода

Глава 5. Good design = flexible software

  • Абстрактные классы - это место для подстановки реальных классов в коде. Они объявляют поведение, а реальные классы его реализуют.
  • Интерфейсы определяют поведение, применимое ко многим типам. Программирование на основании интерфейсов позволяет проще расширять систему, т.к. система сможет работать с любыми классами, реализующими интерфейс, даже с теми, которые пока еще не созданы.
  • Если у какой-то иерархии объектов есть набор свойств, изменяющийся от типа к типу, стоит использовать коллекцию (например Map для java), чтобы обеспечить динамическое хранение этих свойств.
  • Хорошо спроектированное приложение должно быть просто расширять и изменять его поведение, т.е. такое приложение является гибким (flexible) или “мягким” (soft).
  • Классы в гибком приложении обладают высокой связностью. Это означает, что они ответственны за выполнение одной задачи, имеют одну причину для изменения. По сути, это следствие соблюдения Single Responsibility Principle

Глава 6. Solving really big problems

  • “Разделяй и властвуй” - любую большую проблему можно решить, разбив ее на множество функциональных частей и создав решение для каждой из них по отдельности.
  • В начале работы над любым крупным проектом нужно получить высокоуровневый обзор функций ПО (features), из которых впоследствии можно будет вывести требования. Обычно функции включают в себя несколько требований, но иногда эти два термина используют взаимозаменяемо.
  • Общие черты и различия между разрабатываемой системой и уже существующими дает часть необходимой информации для построения списка функций и требований.
  • На этапе анализа предметной области можно пользоваться диаграммами сценариев использования, так как они сфокусированы на общей картине. Такие диаграммы должны учитывать все функции ПО, заявленные и обнаруженные ранее.
  • Действующими лицами на диаграммах вариантов использования могут быть любые сущности, взаимодействующие с системой, но не являющиеся ее частью.
  • Списки функций позволяют понять, что должно делать приложение. Диаграммы сценариев использования дают возможность подумать над тем, как будет использоваться приложение, не погружаясь в лишние детали реализации.

Глава 7. Architecture

  • Варианты определения архитектуры: Архитектура в ИТ
  • Начинать работу необходимо с самых важных функций в списке функций или требований. Выяснить относительную важность отдельных пунктов этого списка помогут три вопроса:
    • Эта функция является основополагающей для этой системы? Иными словами, можем ли мы представить себе подобную систему без этой функции?
    • Что конкретно означает эта функциональность? Расплывчатые формулировки и недостаточное понимание могут потребовать много времени на уточнение впоследствии и создать проблемы при реализации.
    • Как такое вообще можно реализовать? Важно как можно раньше уделить внимание функциям, которые представляют собой реально сложные задачи или совершенно новый опыт программирования (за счет стека технологий, используемых алгоритмов и т.п.). Такие функции очень рискованны, и их следует исследовать в первую очередь.
  • Архитектурная значимость отдельных функций продиктована риском, который они приносят в проект. Начинать работать можно над любой из них, если это уменьшает риски проекта в целом. Однако существует тонкая грань в детализации - слишком пристальное внимание к незначительным деталям само по себе рискованно, так как отнимает время на потенциально ненужную работу.
  • Иногда лучший способ написать отличный код - это отложить написание кода на как можно более позднее время.
  • Для уточнения смысла формулировок отдельных функций нужно:
    1. Собрать у потребителя / заказчика/ конечного пользователя список конкретных примеров.
    2. Провести анализ общих и различающихся деталей, свойств, действий и т.п. Можно воспользоваться техникой из Отличия фрагментов кода как источник абстракций
    3. Составить план реализации
  • На этапе уточнения требований можно прототипировать отдельные сценарии, а не полностью детализированные варианты использования. Это позволит обнаружить пропущенные требования и снизить риски от недостаточно хорошо определенной функциональности.

Глава 8. Design principles

  • Драйвер хорошей архитектуры: Open-Closed Principle
  • DRY - Don’t Repeat Yourself. Этот принцип не столько про дублирующийся код, он, скорее, про то, что каждое требование и каждая функция должна быть реализована (реально написана в коде) только в одном месте.
  • Single Responsibility Principle
  • Liskov Substitution Principle
  • Предоставлять клиентам кода только те классы, с которыми им необходимо работать. Опасно перегружать клиентов знаниями о деталях реализации. Более того, классы, о которых не знают клиенты, могут быть изменены, не оказывая влияние на клиентский код.

Глава 9. Iterating and Testing

  • Два способа организации итеративной разработки: по функциям или по вариантам использования. Оба способа нуждаются в хорошо проработанных требованиях и оба сфокусированы на создании ценности для потребителя. Итеративность заключается в последовательном применении всех этапов разработки для все более мелких составных частей ПО.
  • Разработка по функциям отлично подходит для систем с большим количеством слабо связанных функциональных частей. При этом способе можно работать над функциями любого размера, при условии, что работа сконцентрирована на реализации только одной функции за раз.
  • Разработка по вариантам использования подходит для систем транзакционного типа, в которых сама суть системы определяется долгими сложными процессами. При этом способе разработки обычно усилия сфокусированы на одном сценарии из варианта использования, после его завершения работа продолжается над всеми другими сценариями из этого же варианта использования, до тех пор, пока вариант использования не будет полностью реализован.
  • Тест-кейсы для удобства использования и организации ссылок можно представить в виде таблицы со столбцами: ID, Название, Входы, Ожидаемые выходы, Начальное состояние. Название должно отражать суть проверки, а сама проверка должна быть сосредоточена только на одном конкретном фрагменте функциональности.
  • “Программирование по контракту” предполагает, что обе стороны взаимодействия понимают, какие действия к какому поведению приводят, и придерживаются этого понимания. В этом случае методы могут возвращать null-значения или непроверяемые исключения.
  • “Защитное программирование” предполагает, что в любой момент что-то может пойти не так, поэтому использует многочисленные проверки для того, чтобы избежать возможных проблем. Методы обычно возвращают “пустые” объекты или выбрасывают проверяемые исключения, заставляя клиентов явно их обрабатывать.

Глава 10. The OOA&D Lifecycle

3 шага к отличному ПО:

  1. Заставить приложение работать так, как ожидает пользователь (+ TDD!!!)
    1. Список функций. Определите, что должно делать приложение на самом высоком уровне.
    2. Диаграммы вариантов использования. Зафиксируйте крупные процессы, проходящие в приложении и всех внешних участников / клиентов.
    3. Модули и компоненты. Разделите приложение на функциональные модули, затем решите, в каком порядке будете работать над ними.
    4. Требования. Сформулируйте отдельные требования для каждого модуля, удостоверьтесь, что они вписываются в общую картину.
    5. Анализ предметной области. Сформулируйте то, каким образом варианты использования соотносятся с объектами в приложении, на языке потребителя.
  2. Применить основные положения ОО, чтобы добавить гибкость.
    1. Предварительный дизайн. Определите важные детали реализации объектов и их взаимоотношения. Используйте принципы и шаблоны проектирования.
    2. Реализация. Напишите код, протестируйте его и заставьте работать.
  3. Рефакторинг к поддерживаемому дизайну (переиспользование, легкость расширения)
    1. Постоянный итеративный рефакторинг.

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