Жизненный цикл ПО
От требований до вывода из эксплуатации: проектирование, разработка, тестирование, релиз, CI/CD и сопровождение
Вячеслав Федоров
Лекция 8
Разработка и применение ПО
в физических исследованиях
Изложение. Восьмая лекция открывает часть курса об инженерных практиках. Знания языка и алгоритмов недостаточно, когда программа перестаёт быть личным черновиком: ею пользуются коллеги, на её результатах строятся статьи, а поддерживать её приходится годами. Жизненный цикл программного обеспечения охватывает всё происходящее с программой — от разговора, в котором формулируется задача, до дня, когда её снимают с поддержки. В рамках лекции рассматриваются стадии цикла: анализ требований и проектирование, разработка с системой контроля версий и ревью кода, виды тестирования, стратегии выпуска и контейнеры, непрерывная интеграция и поставка, сопровождение и завершение. Надпись на титуле — v1.0, номер первого выпуска программы.
Демонстрации. Слайды с розовым выделением слова «Демонстрация» отмечают места, где изложение прерывается для работы в терминале виртуальной машины lab. На каждом таком слайде проигрывается запись тех же команд, поэтому показ воспроизводим и без живого терминала.
Содержание
Стадии жизненного цикла программы
01
Анализ и проектирование
Изложение. Лекция состоит из шести частей, следующих стадиям жизненного цикла: непрерывная интеграция и поставка, связывающие разработку, тестирование и релиз, выделены в отдельную часть, а завершение рассматривается вместе с сопровождением. Первая — анализ требований и проектирование: документ с требованиями, разбор замысла с коллегами, проверка безопасности, минимально жизнеспособный продукт. Вторая — разработка: системы контроля версий, ветвление и схемы работы с ветками, ревью кода, требования к коду, монорепозиторий. Третья — тестирование: статический анализ, модульные, интеграционные, функциональные и нагрузочные тесты. Четвёртая — релиз: стратегии развёртывания, контейнеры, feature-флаги и выбор момента выпуска. Пятая — непрерывная интеграция и поставка. Шестая — сопровождение работающей системы и завершение жизненного цикла. В конце рассматриваются домашнее задание и требования к проекту курса.
Цель лекции
Программа, которой пользуются другие, проходит путь от требований до снятия с поддержки
Каждая стадия жизненного цикла закрывает вопрос, иначе возникающий позже и по другой цене: что требуется построить, как это устроено, работает ли оно, как довести его до пользователя, кто устраняет сбои в нерабочее время и как его завершить. Научный код проходит те же стадии, только заказчиком, разработчиком, тестировщиком и дежурным оказывается один человек.
Изложение. Типичная история научного кода начинается со скрипта на пятьдесят строк, написанного вечером перед семинаром. Через полгода он разрастается до восьмисот строк, рядом появляются копии с суффиксами final и final_v2, пути к данным записаны прямо в коде, а на другой машине скрипт не запускается. Код, написанный на один раз, незаметно становится многоразовым: его использует вся группа, на него ссылаются в статьях, а устроен он по-прежнему как черновик. Практики жизненного цикла — требования, контроль версий, тесты, воспроизводимый выпуск, сопровождение — превращают такой код в инструмент, которому можно доверять. Разница между научной группой и большой компанией заключается не в наборе стадий, а в степени их формализации.
Для физиков: результат, опубликованный в статье, должен воспроизводиться через годы; для этого необходимы зафиксированная версия кода, зафиксированные зависимости и тесты, подтверждающие, что код по-прежнему считает правильно.
Стадии
жизненного цикла
Стадия Вопрос, который она закрывает
Анализ что требуется построить
Проектирование как это будет устроено
Разработка код и совместная работа над ним
Тестирование получилось ли задуманное
Релиз как продукт доходит до пользователя
Сопровождение кто следит за работающей системой
Завершение как снять продукт с поддержки
Пояснение
Стадии повторяются циклами: требования, выяснившиеся после релиза, запускают цикл заново.
Для физика
У научного кода цикл короче, а роли заказчика, аналитика и приёмщика играет сам разработчик.
Важно
Цена ошибки растёт со стадией: неверно понятое требование обнаруживается на защите.
Изложение. Жизненный цикл программного обеспечения представляет собой набор действий, которые выполняются над программой, чтобы она удовлетворяла ожиданиям пользователей, от возникновения замысла до прекращения поддержки. В нём выделяют семь стадий: анализ, проектирование, разработку, тестирование, релиз, сопровождение и завершение. Каждая стадия закрывает свой вопрос, и пропущенный вопрос не исчезает, а возникает позже и обходится дороже: ошибку в требованиях, найденную при тестировании, приходится исправлять во всех слоях, построенных поверх неё. Стадии повторяются циклами. В каскадной модели они проходят один раз строго по порядку, в итеративных моделях цикл короткий и повторяется много раз: каждая итерация заканчивается работающей версией, а требования уточняются по опыту её использования. Современная разработка почти всегда итеративна.
Для физиков: в научной группе цикл сжимается до нескольких дней, а все роли выполняет один человек; стадии от этого не исчезают, и пропускают их обычно по незнанию.
Анализ и проектирование
Требования, design-review, security-review, MVP
01
Анализ и проектирование
Разработка
Тестирование
Релиз
CI/CD
Сопровождение
Изложение. Первая часть посвящена стадиям до написания кода: как выясняется, что требуется построить, и как из требований выводится устройство системы.
Анализ
требований
Часть SRS Содержание
Назначение цель документа и продукта, границы
Общее описание пользователи, окружение, допущения
Конкретные требования функции, входы и выходы
Интерфейсы форматы данных, API, взаимодействие
Ограничения производительность, оборудование, стандарты
Атрибуты качества надёжность, точность, сопровождаемость
Задачи этапа:
03
планирование работ и ресурсов
Пояснение
Стандарт IEEE 830-1998 отменён и заменён ISO/IEC/IEEE 29148; основное содержание документа сохранилось.
Изложение. На этапе анализа собираются требования к будущему продукту и сводятся в документ — спецификацию требований к программному обеспечению (Software Requirements Specification, SRS). Его структуру задавал стандарт IEEE 830-1998; в 2011 году его заменил стандарт ISO/IEC/IEEE 29148, но основное содержание сохранилось: назначение, общее описание и конкретные требования, в которые входят интерфейсы, ограничения и атрибуты качества. Сроки и бюджет в SRS не включают: это требования к проекту, а не к продукту, и они фиксируются в плане работ. Кроме требований этап включает постановку целей, планирование работ и ресурсов и согласование сроков. Этим этапом пренебрегают из ощущения, что «и так всё понятно», хотя цена ошибки здесь самая высокая: неверно понятое требование обнаруживается в самом конце и увлекает за собой всё построенное поверх него.
Для физиков: в научной группе все задачи этапа умещаются в один разговор с руководителем и полстраницы заметок: что вычисляется, из каких данных, с какой точностью, в каком виде нужен результат и к какому сроку. Первый этап проекта курса — именно такой документ на одну-две страницы.
Проектирование
системы
Если задачу можно решить без написания кода, это лучшее решение
Из требований выводится устройство системы: деление на части и интерфейсы между ними. Решения этого этапа переживают весь написанный впоследствии код: переписать функцию дёшево, изменить деление на части в собранной системе почти невозможно.
Для физика
Готовая библиотека, скрипт из соседней группы или ручная обработка десяти файлов часто дешевле нового кода, который придётся сопровождать.
Изложение. Задача этапа проектирования — выработать архитектуру системы и её интерфейсы на основе требований, полученных на предыдущем шаге. Из требований выводится деление на части — модули, сервисы, хранилища — и способы их взаимодействия: форматы данных, программные интерфейсы, протоколы. Решения этого этапа переживают весь код, написанный впоследствии: переписать отдельную функцию дёшево, а изменить деление на части в собранной системе почти невозможно. Первый вопрос проектирования — нужен ли код вообще. Каждая строка кода требует сопровождения, поэтому задача, решаемая настройкой существующего инструмента, готовой библиотекой или ручной операцией, решена лучше всего.
Для физиков: прежде чем писать собственную обработку, целесообразно поискать готовые инструменты — библиотеки SciPy и Astropy, коды соседних групп, программы, поставляемые с установкой.
Design-review
и security-review
Разбор Что обсуждается Когда
design-review что и зачем делается, сравнение с аналогами, roadmap до начала разработки
security-review доступ, данные, аутентификация, секреты всегда; особенно новый сервис, деньги, персональные данные
01
Мотивация
цели, заказчики, будущие пользователи
02
Аналоги
почему не подходят существующие решения
03
Суть предложения
устройство и интерфейсы
Важно
Самодельная проверка паролей — типичная уязвимость лабораторных сервисов.
Изложение. Прежде чем писать код, замысел, оформленный на паре страниц, показывают коллегам. Design-review отвечает на два вопроса: целесообразна ли разработка и хорошо ли спроектирована система. На разборе обсуждаются мотивация — цели, заказчики и потенциальные пользователи, — сравнение с конкурентами и аналогами, суть предложения и план работ (roadmap). Второй разбор, security-review, посвящён безопасности. Он нужен всегда, но особенно важен, когда создаётся новый сервис, затрагивается функциональность, связанная с деньгами, передаются конфиденциальные или персональные данные, реализуются аутентификация и авторизация.
Для физиков: самым полезным пунктом design-review в науке оказывается сравнение с аналогами — инструмент, уже написанный другой группой, обнаруживается нередко, и полчаса поиска могут сэкономить месяцы работы. Сервис лаборатории, доступный из сети и защищённый самодельной проверкой паролей, является типичной уязвимостью: проверку доступа поручают готовым средствам — SSH и единому входу организации.
MVP —
минимально жизнеспособный продукт
Минимально жизнеспособный продукт обладает минимальными, но достаточными функциями, чтобы им начали пользоваться первые потребители. Смысл — как можно раньше выяснить, тот ли продукт создаётся.
Для физика
MVP лабораторного инструмента: одна команда, один формат входных файлов, без настроек; его уже можно передать коллеге.
Идея
анализ спектров с установки
MVP
одна команда, один формат, один график
Отзывы пользователей
реальные потребности
Следующая версия
форматы, настройки, отчёт
Изложение. Минимально жизнеспособный продукт (Minimum Viable Product, MVP) представляет собой самую маленькую версию, которой уже можно пользоваться и которая удовлетворяет первых потребителей. MVP позволяет как можно раньше получить отзыв и выяснить, тот ли продукт создаётся, вместо того чтобы полгода разрабатывать полную версию по непроверенным требованиям. По отзывам пользователей MVP уточняются требования следующей версии, и цикл повторяется.
Для физиков: MVP лабораторного инструмента обычно выглядит так: одна команда, один формат входных файлов, никаких настроек; он вычисляет задуманное, и его можно передать коллеге. Форматы, настройки и отчёты добавляются, когда выяснится, что они действительно нужны.
Разработка
Контроль версий, ветвление, ревью кода, требования к коду, монорепозиторий
02
Анализ и проектирование
Разработка
Тестирование
Релиз
CI/CD
Сопровождение
Изложение. Вторая часть посвящена не самому коду, а тому, как несколько человек работают над одним проектом, не затирая сделанное друг другом: системам контроля версий, ветвлению, ревью и общим требованиям к коду.
Системы
контроля версий
Система контроля версий хранит историю изменений: кто, что и когда правил, — и позволяет вернуться к любому прошлому состоянию и работать над правками совместно.
Годы Системы
1960-е IEBUPDTE
1970-е SCCS
1980-е RCS, CVS
2000-е Subversion, Git, Mercurial
Централизованные Распределённые
история на сервере у каждого участника
коммит без сети нет да
примеры CVS, Subversion Git, Mercurial
Пояснение
Git написан Линусом Торвальдсом в 2005 году для разработки ядра Linux взамен BitKeeper.
Изложение. Система контроля версий (VCS) является программным обеспечением для работы с изменяющейся информацией. Она хранит несколько версий одного документа, позволяет вернуться к старой версии, определить автора каждого изменения и работать над правками совместно. Решать эту задачу начали в 1960-х годах программой IEBUPDTE, затем появились SCCS в 1970-х, RCS и CVS в 1980-х, Subversion и Git в 2000-х. Поколения различаются тем, где хранится история. Централизованные системы хранят её на сервере: без связи с ним нельзя ни просмотреть историю, ни сделать коммит. Распределённые системы дают каждому участнику полную копию репозитория со всей историей, поэтому работать можно без сети, а синхронизироваться позже. Стандартом де-факто является Git, написанный Линусом Торвальдсом в 2005 году для разработки ядра Linux, когда команда лишилась доступа к использовавшейся до того проприетарной системе BitKeeper. Устройство Git и повседневные команды рассматривались в лекции 0 и в главе книги «Git».
Ветвление
в Git
git switch -c feature # новая ветка git commit -m 'Новая функция' git switch main git merge feature # слияние
Пояснение
Ветка — указатель на коммит: создание мгновенно и ничего не копирует, поэтому ветвиться и сливать можно постоянно.
Изложение. Главная особенность Git — дешёвое ветвление. Ветка является указателем на коммит, поэтому её создание мгновенно и ничего не копирует; частое ветвление и слияние в Git поощряется. Команда git switch -c feature создаёт ветку и переключается на неё (прежняя форма — git checkout -b feature), коммиты в ней не затрагивают основную ветку, а git merge feature сливает изменения обратно. Если обе ветки изменили одни и те же строки, возникает конфликт слияния: Git помечает спорные строки, а решение принимает человек.
Для физиков: ветка позволяет попробовать другой алгоритм или другую калибровку, не изменяя работающую версию; если идея не подтвердилась, ветку удаляют.
Методики
ветвления
Схема Ветки Для чего
Git Flow main, develop, feature, release, hotfix редкие версионированные релизы
GitHub Flow main и короткие ветки через pull request частые выкатки, небольшие команды
GitLab Flow GitHub Flow плюс ветки окружений или релизов выкатка через стенды
Trunk-based короткоживущие ветки, слияние ежедневно CI и feature-флаги
Пояснение
Методика ветвления упорядочивает добавление и публикацию изменений в общем репозитории.
Для физика
Научной группе почти всегда достаточно GitHub Flow: main всегда рабочая, изменения — через pull request.
Изложение. Методика ветвления (workflow) представляет собой договорённость о том, какие ветки существуют и как изменения попадают в общую историю. Git Flow предусматривает две постоянные ветки, main и develop, и вспомогательные ветки feature, release и hotfix, которые удаляются после слияния; схема подходит для продуктов с редкими версионированными релизами. GitHub Flow проще: основная ветка всегда в рабочем состоянии, каждая задача делается в короткой ветке и вливается через pull request после ревью. GitLab Flow дополняет его ветками окружений или релизов, через которые изменения проходят по стендам. Магистральная разработка (trunk-based development) сокращает жизнь веток до дня и опирается на непрерывную интеграцию и feature-флаги. Выбор определяется тем, как часто выходят релизы и сколько версий одновременно поддерживается.
Для физиков: научной группе почти всегда достаточно самой простой схемы — GitHub Flow; Git Flow с постоянной веткой develop и ветками релизов для двух-трёх человек избыточен.
Демонстрация:
ветки и конфликт
01
Граф веток: main, calib, docs
02
Слияние docs: перемотка вперёд
03
Слияние calib: конфликт в config.toml
04
Маркеры конфликта в файле
05
Выбор версии calib и коммит слияния
06
Итоговый граф с коммитом слияния
Что наблюдается
Обе ветки изменили energy_mev; Git остановил слияние и пометил спорные строки, решение принял человек.
root@lab — запись со стенда
Демонстрация. Рассмотрим учебный репозиторий beam: от начальной версии в нём расходятся ветки main с энергией пучка по паспорту ускорителя (1.3 МэВ) и calib с энергией по калибровке спектрометра (1.25 МэВ); ветка docs с описанием запуска отходит от последнего коммита main. git log --oneline --graph --all показывает граф. Слияние docs выполняется перемоткой вперёд (fast-forward): main является предком docs, поэтому Git передвигает указатель main на коммит docs без коммита слияния. Слияние calib останавливается: CONFLICT (content): Merge conflict in config.toml — обе ветки изменили одну строку. git status показывает файл с пометкой UU, а в самом файле спорные строки окружены маркерами <<<<<<< HEAD, ======= и >>>>>>> calib. Решение принимает человек: здесь калибровка по спектрометру признана точнее, и git checkout --theirs config.toml берёт версию сливаемой ветки. После git add и git commit --no-edit граф показывает коммит слияния с двумя родителями.
Pull request
и ревью кода
Ветка задачи
коммиты с понятными сообщениями
Pull request
описание: что и зачем
Проверки CI
линтер и тесты
01
Видимость
правка видна всей команде
02
Обсуждение
до попадания кода в main
03
Проверки
к pull request привязаны автоматические проверки
Пояснение
Ревью распространяет знание о проекте: код перестаёт быть понятным одному человеку.
Изложение. Pull request (в GitLab — merge request) представляет собой запрос на слияние: оформленное предложение влить изменения одной ветки в другую. Он решает три задачи: делает правку видимой членам команды, даёт место для ревью и обсуждения до того, как код попал в основную ветку, и служит точкой, к которой привязываются автоматические проверки. Ревью кода — чтение чужих изменений коллегой: свежий взгляд замечает ошибки и места, очевидные автору, но не читателю. Ревью приносит пользу даже в группе из двух человек и лучше всего работает на небольших изменениях: pull request на пятьдесят строк читают внимательно, а на две тысячи — лишь просматривают. Ревью не заменяет тесты и линтер, а дополняет их: машина проверяет форму, человек — смысл.
Для физиков: в научном коде ревьюер проверяет не только код, но и физику — единицы измерения, знаки, предельные случаи; задание практикума «Ревью чужого кода» посвящено именно этому.
Требования
к коду
Правило Содержание
PEP 8 стиль: отступы, имена, пробелы
длина строки 79 символов, задаётся в pyproject.toml
импорты отсортированы, неиспользуемых нет
docstring PEP 257: что делает функция
имена по смыслу, по-английски; константы вместо магических чисел
Пояснение
Большинство правил проверяют линтеры и форматтеры — ruff, flake8, black — в редакторе и в CI.
# .git/hooks/pre-commit #!/bin/sh ruff check . && python3 -m pytest -q # pyproject.toml [tool.ruff] line-length = 79
Важно
black и ruff format по умолчанию форматируют по 88 символов: длину задают явно.
Изложение. Единые требования к коду позволяют избежать споров на ревью: стиль определяется соглашением, а не вкусом автора. Для Python основой служит PEP 8: отступы в четыре пробела, имена в нижнем регистре с подчёркиваниями для функций и переменных и CapWords для классов, ограничение длины строки; документирующие строки оформляются по PEP 257. Глава книги «Требования к коду» дополняет их правилами курса: отсортированные импорты без неиспользуемых, отсутствие закомментированного кода, guard block вместо лишнего else, исполняемый код под if __name__ == "__main__", именованные константы вместо магических чисел. Большинство правил проверяют линтеры и форматтеры — ruff, flake8, black, isort, — работающие в редакторе и в CI. Хук pre-commit запускает проверки перед каждым коммитом и не даёт зафиксировать код, который их не проходит. black и ruff format по умолчанию форматируют по 88 символов, поэтому длину 79 необходимо задать явно в pyproject.toml, иначе форматтер и линтер будут предъявлять к коду противоречащие требования.
Демонстрация:
хук pre-commit
01
Хук: ruff и pytest перед коммитом
02
Функция momentum с лишним import os
04
Удаление импорта: ruff check --fix
05
Коммит после проверок и тестов
06
История с новым коммитом
Что наблюдается
Хук не пропустил коммит из-за неиспользуемого импорта; после исправления ruff и тесты прошли, коммит создан.
root@lab — запись со стенда
Демонстрация. В репозитории beam находятся модуль physics.py с формулами и тесты, а хук .git/hooks/pre-commit запускает ruff check и pytest. В конец physics.py дописывается функция momentum вместе с неиспользуемым import os, и git commit -qam 'Импульс частицы' завершается без коммита: ruff сообщает physics.py: F401 `os` imported but unused, и хук возвращает ненулевой код. git status показывает, что файл по-прежнему изменён. ruff check --fix удаляет неиспользуемый импорт, функция остаётся, и повторный коммит проходит: ruff печатает All checks passed!, pytest — 5 passed, а git log показывает коммит «Импульс частицы». Хук является локальной копией проверок CI: ошибка обнаруживается за секунды, до того как код попал в общий репозиторий. Хук можно обойти ключом --no-verify, а каталог .git/hooks не копируется при клонировании; таким образом, хук не заменяет проверки на сервере. Общий для всех участников набор хуков описывают файлом .pre-commit-config.yaml фреймворка pre-commit.
Монорепозиторий
или много репозиториев
Монорепозиторий Отдельные репозитории
зависимости всегда согласованы у каждого репозитория свои версии
сквозное изменение один коммит по коммиту в каждом репозитории
инструменты общая сборка и тесты простые, свои у каждого проекта
цена большой репозиторий, сложная сборка согласование версий между проектами
Для физика
В одном репозитории хранят то, что выпускается и версионируется вместе; независимые части — отдельно.
Изложение. Монорепозиторий хранит код множества проектов в одном репозитории с единой системой сборки, тестирования и развёртывания. Это упрощает управление зависимостями — все компоненты всегда согласованной версии, — облегчает взаимодействие между командами и позволяет вносить сквозное изменение одним коммитом, а не набором коммитов в разных репозиториях. Платой являются разросшийся репозиторий и сложные инструменты сборки, которые позволяют проверять только затронутые части. Монорепозитории используют крупные компании, в том числе Яндекс и Google; большинство открытых проектов живёт в отдельных репозиториях. В монорепозитории удобно хранить и копии сторонних зависимостей: сборка не зависит от того, что удалят или подменят в чужом репозитории, как в случаях с пакетом left-pad в 2016 году и библиотекой xz в 2024 году.
Для физиков: лаборатории разумно хранить в одном репозитории всё, что выпускается и версионируется вместе, — например, библиотеку обработки и её конфигурации, — и разносить части, развивающиеся независимо.
Тестирование
Статический анализ, модульные, интеграционные, функциональные и нагрузочные тесты
03
Анализ и проектирование
Разработка
Тестирование
Релиз
CI/CD
Сопровождение
Изложение. Третья часть посвящена проверке того, что написанное соответствует требованиям. Ручная проверка после каждой правки быстро становится невыполнимой; автоматические тесты позволяют один раз описать требуемое поведение и затем проверять его одной командой.
Виды
тестирования
01
Внизу
дешёвые и быстрые проверки, их много, запуск на каждое изменение
02
Вверху
дорогие и медленные проверки, их мало, запуск перед релизом
Пояснение
Каждый уровень выявляет ошибки, невидимые нижнему: модули могут работать порознь и отказывать вместе.
Изложение. После начала разработки необходимо убедиться, что приложение соблюдает требования. Проверки удобно выстроить пирамидой по цене и частоте запуска. Классическая пирамида тестов (М. Кон, 2009) состоит из модульных, интеграционных и сквозных тестов через интерфейс пользователя; в основание часто кладут статический анализ, не запускающий программу. Нагрузочные тесты стоят особняком: они проверяют не правильность, а производительность, и на рисунке помещены на вершину как самые дорогие и редкие. Внизу проверки дешёвые и быстрые: их много, и они запускаются на каждое изменение. Вверху — дорогие и медленные: их мало, и они запускаются перед релизом. Каждый уровень выявляет ошибки, невидимые нижнему: модули, правильно работающие порознь, могут отказывать в связке.
Статический
анализ
Анализатор читает исходный код, не запуская программу, и ищет ошибки, отклонения от стиля и уязвимости: неиспользуемые имена, опечатки, сравнение с None через ==, голые except.
Инструмент Проверяет
ruff, flake8 стиль и очевидные ошибки
pylint сомнительные конструкции
mypy типы по аннотациям
bandit типовые уязвимости
«Самое важное, что я сделал как программист за последние годы, — начал агрессивно применять статический анализ кода»
— Джон Кармак, 2011
Пояснение
Метрики качества: покрытие тестами, цикломатическая сложность, доля дублированного кода.
Изложение. Статический анализ представляет собой автоматизированную проверку исходного кода без его выполнения: поиск недочётов, ошибок и уязвимостей и проверка соответствия соглашениям. По существу, это автоматизированная часть ревью, но не его замена. Анализатор находит неиспользуемые переменные и импорты, опечатки в именах, сравнение с None через ==, голые except, подозрительные конструкции; с аннотациями типов mypy находит передачу значений не того типа. В отчёты о качестве попадают и метрики: покрытие кода тестами, цикломатическая сложность, доля дублированных строк. Заметим, что покрытие получают иначе — выполняя тесты под coverage.py (pytest --cov из плагина pytest-cov): узнать, какие строки выполнялись, не выполнив их, нельзя. Джон Кармак, автор движков Doom и Quake, в 2011 году назвал агрессивное применение статического анализа самым важным, что он сделал как программист за последние годы.
Для физиков: анализатор, встроенный в редактор и в CI, работает без участия человека и находит опечатки в именах переменных до того, как они испортили результат расчёта.
Демонстрация:
статический анализ ruff
01
Правила в pyproject.toml: E4, E7, E9, F, I
02
ruff check: восемь нарушений
03
ruff check --fix: четыре исправлены автоматически
04
Оставшиеся четыре: F403, E711, E722, F405
05
ruff format --diff: пустые строки по PEP 8
Что наблюдается
Четыре нарушения исправлены автоматически; импорт со звёздочкой, сравнение с None, голый except и неопределённое имя требуют решения человека.
root@lab — запись со стенда
Демонстрация. Файл analysis.py содержит типичные недочёты: неиспользуемые импорты os и numpy, импорт со звёздочкой, сравнение best == None, голый except, f-строку без подстановок и обращение к неопределённому имени. Набор правил задан явно в pyproject.toml: E4, E7 и E9 — правила pycodestyle об импортах, инструкциях и ошибках выполнения, F — правила pyflakes, I — порядок импортов. ruff check в кратком формате перечисляет восемь нарушений с кодами правил: I001 — порядок импортов, F401 — неиспользуемые os и numpy, F403 — импорт со звёздочкой, E711 — сравнение с None через ==, E722 — голый except, F541 — f-строка без подстановок, F405 — имя undefined_total, которое может быть не определено или прийти из импорта со звёздочкой. ruff check --fix исправляет четыре нарушения — сортирует импорты, удаляет неиспользуемые, снимает лишний префикс f — и сообщает об оставшихся четырёх. Исправление E711 ruff считает небезопасным и применяет только с ключом --unsafe-fixes: для объекта с переопределённым __eq__ замена == на is меняет смысл; импорт со звёздочкой, голый except и неопределённое имя требуют решения человека. Набор правил по умолчанию меняется от версии к версии: без строки select ruff 0.16 находит в этом файле только пять нарушений, без E711, F403 и F405, поэтому в проекте правила задают явно. ruff format --diff показывает, как форматтер расставит пустые строки между функциями по PEP 8, не изменяя файл.
Модульные
тесты
import pytest from physics import C, gamma @pytest.mark.parametrize('beta, expected', [(0.0, 1.0), (0.6, 1.25), (0.8, 5 / 3)]) def test_gamma(beta, expected): assert gamma(beta * C) == pytest.approx(expected) def test_gamma_rejects_light_speed(): with pytest.raises(ValueError): gamma(C)
Что проверяется Пример
известный ответ γ(0,6c) = 1,25; γ(0,8c) = 5/3
предельный случай v ≪ c: E = mv²/2
инвариант сохранение энергии, симметрия
некорректный вход v ≥ c → ValueError
Важно
Сравнение вещественных чисел — с допуском: pytest.approx, а не ==.
Изложение. Модульный тест (unit test) проверяет корректность отдельной функции или класса, изолированных от остальной программы. Таких тестов пишут много, они выполняются за секунды и запускаются на каждое изменение, что позволяет сразу обнаружить ошибку, внесённую правкой. В Python стандартом де-факто стал pytest: тест — функция с именем test_…, проверка — обычный assert, при неудаче pytest показывает сравниваемые значения. Декоратор parametrize прогоняет один тест на нескольких наборах данных, pytest.raises проверяет, что функция отвергает некорректный вход, а pytest.approx сравнивает вещественные числа с допуском. Модульные тесты пишет программист вместе с кодом или до него.
Для физиков: модульные тесты являются самой ценной разновидностью тестов для научного кода. Формула проверяется на случаях с известным ответом, в предельных случаях и на инвариантах; проверенная однажды, она остаётся проверенной и после того, как через полгода её начнут оптимизировать.
Демонстрация:
тесты pytest
01
Тесты: известные ответы, отказ при v = c, классический предел
02
Первый прогон: три неудачи из пяти
03
Исправление γ: квадрат β
04
Оставшаяся неудача: v ≪ c, 500 006,9 вместо 500 000
05
Устойчивая формула mv²γ²/(γ + 1)
06
Прогон без неудач: пять тестов из пяти
Что наблюдается
Тест предела нашёл потерю точности: γ − 1 при v = 1000 м/с вычисляется с относительной ошибкой 10⁻⁵.
root@lab — запись со стенда
Демонстрация. Модуль physics.py содержит функции gamma и kinetic_energy, тесты — известные значения γ при β = 0, 0,6 и 0,8, отказ при v = c и классический предел кинетической энергии. Первый прогон pytest завершается тремя неудачами: в формуле γ пропущен квадрат, и γ(0,6c) = 1,58 вместо 1,25. После исправления остаётся одна неудача: при v = 1000 м/с кинетическая энергия получается 500 006,9 Дж вместо 500 000. Ошибка уже не в физике, а в арифметике: γ − 1 ≈ 5,6·10⁻¹² вычисляется вычитанием близких чисел, и от точности double остаётся пять знаков. Запишем устойчивую формулу E = mv²γ²/(γ + 1): математически она равна (γ − 1)mc², но не вычитает близкие числа; с ней все пять тестов проходят. Таким образом, тест предельного случая нашёл ошибку, которую не видно на релятивистских скоростях.
Интеграционные
и функциональные тесты
01
Интеграционные
взаимодействие модулей между собой и с внешними системами: базой данных, файлами, сетью
02
Функциональные
сценарий целиком с точки зрения пользователя; вручную или автоматически, например Selenium
Пояснение
Модуль чтения возвращает кэВ, подгонка ожидает МэВ: модульные тесты обоих проходят, ошибку находит интеграционный.
Для физика
Функциональный тест лабораторного инструмента: эталонный файл с установки, вся обработка, сравнение итоговых чисел с заведомо правильными.
Изложение. Модульные тесты хорошо проверяют логику, но изолированной проверки недостаточно: модуль может работать правильно сам по себе и отказывать в связке с другими. Интеграционные тесты объединяют программные модули и проверяют их вместе, а также их взаимодействие с внешними системами — базой данных, файловым хранилищем, чужим API за сетью. Они медленнее модульных и требуют развёрнутого окружения, поэтому их пишут меньше. Функциональные тесты проверяют соответствие продукта задуманному сценарию с точки зрения пользователя, через его интерфейс: вручную или автоматически, для веб-интерфейсов — например, средствами Selenium. Рассмотрим типичный пример: модуль чтения возвращает энергию в килоэлектронвольтах, а модуль подгонки ожидает мегаэлектронвольты; модульные тесты каждого из них проходят, а ошибку в тысячу раз обнаруживает только интеграционный тест, проверяющий модули вместе.
Для физиков: у лабораторного инструмента функциональный тест обычно один. Он берёт снятый с установки эталонный файл, выполняет всю обработку от начала до конца и сравнивает итоговые числа с заведомо правильными.
Нагрузочное
тестирование
Профиль Что проверяет
load ожидаемая штатная нагрузка
stress нагрузка сверх расчётной
spike резкий всплеск и восстановление
soak штатная нагрузка часами и днями
breakpoint рост нагрузки до предела системы
Пояснение
Инструменты: JMeter, Siege, Яндекс.Танк; на Python — locust.
Для физика
Сервис приёма данных с установки проверяют на пиковом потоке событий заранее, а не во время сеанса.
01
измерение производительности
02
проверка пиков и восстановления после них
Изложение. Нагрузочное тестирование измеряет производительность системы под поданной извне нагрузкой, проверяет поведение на пиках и восстановление после них и служит основой планирования ресурсов. Профили нагрузки различаются характером и длительностью; в таблице — названия, принятые в документации инструментов, например k6. Load testing — проверка при ожидаемой штатной нагрузке. Stress testing — поведение за пределами расчётной нагрузки. Spike testing — резкий всплеск запросов и восстановление после него. Soak testing, или endurance testing, — штатная нагрузка в течение часов и дней: так находятся утечки памяти и деградация. Breakpoint testing — постепенное увеличение нагрузки до предела системы с измерением задержек; общим словом performance testing называют все испытания производительности. Перед замером необходимо подать небольшой прогревающий поток запросов (warm-up), наполняющий кеши, иначе первые минуты измерений искажены. Для нагрузки используют JMeter, Siege, Яндекс.Танк, на Python — locust. Нагрузку подают на отдельное окружение, а не на рабочую систему, и обращения к внешним сервисам заменяют заглушками: иначе тест нарушит работу коллег или будет воспринят чужим сервисом как атака.
Для физиков: программа, быстро считающая на прогретых кешах, на первом запуске ведёт себя иначе; сервис приёма данных с установки проверяют на пиковом потоке событий заранее, а не во время сеанса.
Демонстрация:
нагрузочный тест
01
Клиент: 100 запросов при 1–16 параллельных потоках
02
Запуск сервера в фоне: 20 мс на запрос, один поток
03
Пропускная способность — около 45 запросов/с
04
Рост задержки пропорционально числу потоков
Что наблюдается
Пропускная способность ограничена одной очередью — около 45 запросов/с; p50 выросла с 23 до 357 мс при 16 потоках.
root@lab — запись со стенда
Демонстрация. Сервер на http.server обрабатывает запросы в одном потоке и тратит на каждый 20 мс; клиент на стандартной библиотеке отправляет по 100 запросов при 1, 2, 4, 8 и 16 параллельных потоках и печатает число запросов в секунду и медиану и 95-й процентиль задержки. Пропускная способность остаётся около 45 запросов в секунду при любой нагрузке — это предел единственного обработчика, — а задержка растёт пропорционально: 23 мс при одном потоке, 357 мс при шестнадцати. Так выглядит результат испытания на предел (breakpoint testing): найден предел системы и видно, что за ним запросы ожидают в очереди. Следующий шаг — масштабирование: несколько обработчиков или экземпляров сервиса.
Релиз
Стратегии развёртывания, контейнеры, feature-флаги, версии и релизные окна
04
Анализ и проектирование
Разработка
Тестирование
Релиз
CI/CD
Сопровождение
Изложение. Четвёртая часть посвящена выпуску: как новая версия доходит до пользователей и как быстро откатиться при сбое.
Стратегии
развёртывания
Стратегия Как устроена Плюс Минус
blue-green две копии, переключение трафика мгновенный откат двойные ресурсы
canary новая версия для малой доли ошибку видят немногие медленная выкатка
rolling update замена экземпляров по одному без простоя и лишних ресурсов две версии сразу
A/B версии для случайных групп решение по метрикам сложная настройка
Пояснение
Главная забота при выкатке — возможность быстро откатиться.
Для физика
Для сервиса установки аналог canary — новая версия сначала на одном стенде или одном канале.
Изложение. Стратегия развёртывания определяет, как новая версия заменяет старую и как откатиться при неудаче. Blue-green deployment: поддерживаются две идентичные инсталляции, нагрузка идёт на одну из них, новая версия выкатывается на простаивающую, проверяется, и нагрузка переключается; откат — переключение обратно. Плата — удвоенные ресурсы. Canary deployment: новую версию сначала получает небольшая доля пользователей, «канареек»; если число ошибок и потребление ресурсов не выросли, доля увеличивается. Проблема затрагивает немногих, а откат быстрый, но выкатка медленнее. Rolling update: экземпляры приложения заменяются постепенно, по одному или целыми площадками, сервис всё время отвечает; важно задать, сколько экземпляров может быть недоступно одновременно. A/B-тестирование представляет собой контролируемый эксперимент: пользователи случайным образом делятся на группы, каждая получает свою версию, и продуктовое решение принимается по заранее выбранной метрике с проверкой статистической значимости; от канареечной выкатки его отличает цель — выбор лучшего варианта, а не безопасность выпуска. Крупные сервисы сочетают канареечную выкатку, постепенную замену, feature-флаги и A/B-эксперименты.
Контейнеры
и Docker
FROM python:3.12-slim WORKDIR /app COPY app.py . ENV VERSION=1.0 CMD ["python3", "app.py"]
Пояснение
Образ отделяет приложение от инфраструктуры: один слепок работает на любой машине с той же архитектурой процессора.
Для физика
Тег — версия; откат — предыдущий тег; неизменяемая ссылка — дайджест sha256.
Dockerfile
рецепт: базовый образ, файлы, команда
docker build -t beam:1.1
образ — неизменяемый слепок
docker push / pull
реестр образов
docker run beam:1.1
контейнер — запущенный образ
Изложение. Docker является платформой для разработки, доставки и запуска приложений в контейнерах. Образ содержит приложение вместе со средой выполнения — интерпретатором, библиотеками, системными файлами — и собирается по рецепту Dockerfile: базовый образ, копируемые файлы, переменные окружения, команда запуска. Образ неизменяем и хранится в реестре; контейнер — запущенный экземпляр образа, изолированный от остальной системы средствами ядра Linux: пространствами имён и контрольными группами (лекция 3). Docker отделяет приложение от инфраструктуры: один и тот же образ работает на компьютере разработчика, сервере и в CI при одинаковой архитектуре процессора. Образ для arm64 на сервере x86-64 без эмуляции не запустится, поэтому для разных машин собирают многоплатформенный образ: docker buildx build --platform linux/amd64,linux/arm64. Тег образа служит версией, поэтому выкатка — запуск нового тега, а откат — запуск предыдущего. Отметим, что тег — изменяемая ссылка: его можно перенести на другой образ, а базовый python:3.12-slim обновляется с каждым выпуском 3.12.x; неизменяемая ссылка — дайджест (python:3.12-slim@sha256:…). Подробно Docker рассматривается в главе книги «Docker».
Для физиков: сохранённый образ или Dockerfile с базовым образом, закреплённым дайджестом, и зафиксированными версиями библиотек позволяет повторить расчёт из статьи через годы, а сервис приёма данных переносится на другой сервер одной командой.
Демонстрация:
релиз и откат
01
Dockerfile: python:3.12-slim и app.py
02
Сборка beam:1.0, запуск и запрос
03
Версия 1.1 с флагом NEW_FIT=1
04
Откат: снова контейнер beam:1.0
05
Два образа с разными тегами
Что наблюдается
Выкатка и откат свелись к запуску другого тега; флаг NEW_FIT включил новую подгонку при запуске, без изменения кода.
root@lab — запись со стенда
Демонстрация. Сервис app.py на стандартной библиотеке отвечает на запрос номером версии и названием модели подгонки; версия задаётся переменной окружения в Dockerfile, а модель — feature-флагом NEW_FIT. docker build -q -t beam:1.0 собирает образ на основе python:3.12-slim, docker run запускает контейнер, и curl получает {"version": "1.0", "fit": "gauss"}. После правки VERSION=1.1 собирается образ beam:1.1, старый контейнер удаляется, а новый запускается с -e NEW_FIT=1: ответ {"version": "1.1", "fit": "gauss+background"} — флаг включил новую подгонку без изменения кода. Откат — запуск контейнера из beam:1.0, и сервис снова отвечает версией 1.0. docker images beam показывает оба образа: старая версия хранится и готова к повторному запуску.
Feature-флаги
и момент выпуска
01
Feature-флаг
функция включается и выключается без изменения кода: для части пользователей, с мгновенным выключением при проблеме
02
Мораторий на выкатки
change freeze: выкатки запрещены — праздники, распродажи, крупные запуски
03
Релизные окна
заранее оговорённое время выкаток, когда на месте те, кто разберётся с последствиями
Для физика
Для установки: выкатки запрещены во время набора данных и перед конференцией; выкатка — в начале дня, когда автор на месте.
Важно
Флаги копятся: флаг, включённый для всех, удаляют вместе со старой ветвью кода.
Изложение. Feature-флаг является переключателем, включающим или выключающим функциональность без изменения исходного кода и без новой выкатки. Флаг позволяет оставить недописанную функцию в основной ветке выключенной, включить её для подмножества пользователей — сотрудников, одного города, процента запросов — и выключить за секунду при проблеме вместо отката всего релиза. Флаги накапливаются, поэтому флаг, включённый для всех пользователей, удаляют вместе со старой ветвью кода, а навсегда выключенный — вместе с неиспользуемой новой. Мораторий на выкатки (change freeze) — период, когда выкатки в рабочую среду запрещены ради надёжности: праздничные дни, распродажи, громкие маркетинговые запуски. От него отличается feature freeze — этап цикла выпуска, после которого в версию принимают только исправления ошибок: в CPython он наступает с первой бета-версией. Релизные окна — заранее оговорённые промежутки, в которые выкатки разрешены, например будние дни с утра до вечера, чтобы в момент выкатки были на месте люди, способные разобраться с последствиями.
Для физиков: аналог моратория — запрет выкаток во время набора данных на установке и перед конференцией; выкатку делают в начале рабочего дня, а не в конце рабочей недели.
Версии
и теги
Часть Что меняется Пример
MAJOR несовместимые изменения интерфейса 1.4.2 → 2.0.0
MINOR новые возможности с совместимостью 1.4.2 → 1.5.0
PATCH исправления ошибок 1.4.2 → 1.4.3
git tag -a v1.5.0 -m 'Подгонка с фоном' git push origin v1.5.0 git describe # v1.5.0-3-g1e5ddd7
Для физика
В статье указывают версию или хеш коммита кода; архив релиза с DOI (Zenodo) делает расчёт воспроизводимым.
Изложение. Релиз получает номер версии, по которому его можно найти, установить и сослаться на него. Семантическое версионирование (SemVer) записывает версию тремя числами MAJOR.MINOR.PATCH: старшее увеличивается при несовместимом изменении интерфейса, среднее — при новых возможностях с сохранением совместимости, младшее — при исправлении ошибок. Версия фиксируется в Git аннотированным тегом: git tag -a v1.5.0 отмечает коммит, из которого собран релиз, а git describe показывает, сколько коммитов прошло с последнего тега. Тег запускает и конвейер выпуска, который рассматривается в следующей части. Список изменений между версиями ведут в файле CHANGELOG.
Для физиков: в статье указывают версию или хеш коммита кода, которым получен результат; архив релиза с DOI — например, через интеграцию GitHub с Zenodo — позволяет сослаться на код и воспроизвести расчёт спустя годы.
CI/CD
Непрерывная интеграция, непрерывная поставка, непрерывное развёртывание
05
Анализ и проектирование
Разработка
Тестирование
Релиз
CI/CD
Сопровождение
Изложение. Пятая часть посвящена автоматизации: перечисленные проверки и выпуск выполняет конвейер, запускаемый на каждое изменение.
Назначение
CI/CD
01
разработчики занимаются сборкой и ручным тестированием вместо разработки
02
тестирование пропускают, надёжность страдает
03
релизный цикл и время до выхода функции на рынок растут
04
при ручной выкатке легко ошибиться: не тот коммит, забытый файл
Решение — автоматический конвейер, который на каждое изменение собирает, проверяет и выпускает код
Для физика
Релиз, собранный вручную, рано или поздно окажется собран не из того коммита — уже после публикации.
Изложение. Без автоматизации проблем много: разработчики занимаются сборкой и ручным тестированием вместо разработки; этап тестирования под давлением сроков пропускают, и надёжность сервиса страдает; релизный цикл и время до выхода функции к пользователям (time to market) растут; эксплуатация сталкивается с ручными выкатками, в которых легко ошибиться. Для решения этих проблем существуют практики CI/CD: на каждое изменение конвейер автоматически собирает код, проверяет его и при необходимости выпускает. Таким образом, при отсутствии ручных действий пропустить шаг невозможно.
Для физиков: это главный аргумент в пользу CI/CD в науке: релиз, собираемый вручную, рано или поздно окажется собран не из того коммита, без одного теста или с забытым файлом, и обнаружится это через полгода, когда результат уже опубликован.
Три степени
автоматизации
Практика Что автоматизировано Выкатка
Continuous Integration сборка, тесты, анализ на каждый push; ревью минимальный уровень
Continuous Delivery плюс сборка дистрибутива и выкатка на тестовые среды выкатка по решению человека
Continuous Deployment плюс автоматическая выкатка в продакшен без участия человека
Пояснение
Системы: GitHub Actions, GitLab CI, Jenkins, TeamCity.
Для физика
Научной группе обычно достаточно CI: тесты и линтер на каждый push.
Изложение. За тремя похожими аббревиатурами скрываются три степени автоматизации. Непрерывная интеграция (Continuous Integration) — минимальный уровень: код хранится в системе контроля версий, изменения проходят ревью, каждый push запускает автоматическую сборку, модульные тесты и статический анализ, что позволяет обнаружить ошибку через минуты после её появления. Непрерывная поставка (Continuous Delivery) добавляет хранение дистрибутивов в централизованном хранилище и автоматическое развёртывание на тестовые среды: в любой момент есть готовый к выкатке экземпляр, прошедший сборку и тесты, а решение о выкатке в рабочую среду принимает человек. Непрерывное развёртывание (Continuous Deployment) выкатывает прошедшие проверки изменения в рабочую среду автоматически; для этого необходимо быть уверенным в тестах. Конвейеры исполняют GitHub Actions, GitLab CI, Jenkins, TeamCity.
Для физиков: научной группе обычно достаточно первого уровня — конвейера, запускающего тесты и линтер на каждый push.
Конвейер
GitHub Actions
name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.11', '3.12'] steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - run: pip install ruff pytest - run: ruff check . - run: pytest -q
01
Матрица
те же шаги на нескольких версиях Python
02
Секреты
токены — в хранилище секретов, не в коде
03
Версии действий
полный хеш коммита; тег — у доверенных авторов
Пояснение
Тег версии запускает выпуск: пакет в PyPI, документация, PDF в релизе.
Изложение. Рассмотрим конвейер GitHub Actions, описанный файлом .github/workflows/ci.yml в репозитории. Ключ on задаёт события запуска — каждый push и pull request; работа test выполняется на чистой виртуальной машине ubuntu-latest. Матрица strategy.matrix повторяет те же шаги на нескольких версиях Python: если библиотека перестала работать на одной из них, это выясняется сразу. Шаги получают исходники, ставят Python нужной версии и зависимости, запускают линтер и тесты; любой шаг с ненулевым кодом завершения окрашивает проверку pull request в красный. Пароли и токены никогда не записывают открытым текстом: они хранятся в секретах репозитория и подставляются по имени, а закоммиченный секрет считается скомпрометированным навсегда — история Git хранит всё. Чужое действие — код, выполняемый с доступом к секретам, поэтому его версию закрепляют полным хешем коммита: тег, как и ветку, можно перенести на другой коммит, что и произошло в марте 2025 года с действием tj-actions/changed-files. Тег допустим для действий доверенных авторов, например actions/checkout. Конвейер научной библиотеки REDPIC из книги по тегу версии публикует пакет в PyPI, собирает документацию и прикладывает PDF к релизу.
Демонстрация:
конвейер в контейнере
01
Шаги конвейера из ci.yml
02
Те же шаги в python:3.12-slim
03
Код завершения 0: проверка зелёная
05
Непройденные тесты: код завершения 1
Что наблюдается
Чистое окружение ставит зависимости с нуля; ненулевой код завершения pytest делает проверку красной.
root@lab — запись со стенда
Демонстрация. Репозиторий beam содержит формулы, тесты и файл .github/workflows/ci.yml, последние шаги которого — установка ruff и pytest, ruff check и pytest. Те же шаги выполняются в чистом контейнере python:3.12-slim: каталог проекта подключается томом, pip ставит ruff и pytest с нуля, затем запускаются линтер и тесты. Так работает раннер CI: каждый запуск начинается в свежем окружении, что обеспечивает немедленное обнаружение забытой зависимости или файла, существующего только на машине разработчика. Первый прогон печатает All checks passed! и 5 passed, код завершения 0 — проверка зелёная. После правки, возвращающей ошибку в формуле γ, линтер замечаний не находит, а тесты не проходят: 3 failed, код завершения 1 — проверка красная; если в защите ветки main она отмечена обязательной, pull request с такой правкой слить нельзя.
Сопровождение
Наблюдаемость, дежурства, разбор инцидентов, учения, завершение жизненного цикла
06
Анализ и проектирование
Разработка
Тестирование
Релиз
CI/CD
Сопровождение
Изложение. Шестая часть посвящена работающей системе: как узнавать о сбоях раньше пользователей, как разбирать инциденты и как завершать жизненный цикл.
Сопровождение
работающей системы
Практика Назначение
метрики непрерывное измерение: задержки, ошибки, нагрузка
логи журнал событий для разбора
дежурства кто реагирует на сбой и в какие сроки
разбор инцидентов причины без поиска виноватых
chaos engineering намеренные отказы в контролируемых условиях
учения проверка планов восстановления
Для физика
Минимум для лабораторного сервиса: программа ведёт журнал, а об отказе кто-то узнаёт раньше пользователей.
Пояснение
Мониторинг подробно разобран в книге «Site Reliability Engineering», глава 6.
Изложение. Работающую систему необходимо наблюдать: собирать метрики и логи, следить за ними и вовремя замечать сигналы о неполадках. Непрерывное измерение показывает задержки, долю ошибок и нагрузку; логи позволяют восстановить, что происходило перед сбоем. В зрелых командах вокруг этого выстраивается набор практик: дежурства с порядком реагирования на инциденты; разбор каждого сбоя без поиска виноватых — с поиском причин и мер, исключающих повторение; feature-флаги для быстрого выключения проблемной функции; chaos engineering — намеренное внесение отказов в работающую систему ради проверки, что она их переживает; регулярные учения на случай отказов. Выяснять, что план восстановления не работает, лучше в контролируемых условиях, чем во время аварии.
Для физиков: минимум для лабораторного сервиса — программа ведёт журнал, перезапускается после сбоя, а об отказе кто-то узнаёт раньше, чем это заметят пользователи.
Демонстрация:
отказ и перезапуск
01
Контейнер с политикой --restart on-failure
03
Внесённый отказ: процесс завершён
04
Автоматический перезапуск контейнера
05
Журнал: два старта сервиса
06
Ответ сервиса после перезапуска
Что наблюдается
Отказ, внесённый запросом /crash, завершил процесс; Docker перезапустил контейнер, и журнал сохранил оба старта.
root@lab — запись со стенда
Демонстрация. Контейнер beam запускается с политикой перезапуска --restart on-failure и отвечает на запрос версией 1.0. Запрос /crash — внесённый отказ, как в учениях chaos engineering: обработчик завершает процесс с кодом 1, и curl получает пустой ответ (код 52). Через две секунды docker ps показывает контейнер в состоянии Up: Docker перезапустил его сам, и docker inspect сообщает об одном перезапуске. docker logs хранит журнал обоих запусков: «старт версии 1.0», «запрос /» и снова «старт версии 1.0». Сервис снова отвечает. Автоматический перезапуск не устраняет причину отказа: о нём должен узнать человек, а причину разбирают по журналу.
Завершение
жизненного цикла
01
Заблаговременное объявление
сроки и причины прекращения поддержки
02
Путь перехода
замена и инструкция миграции
03
Выгрузка данных
в формате, переживающем сервис
04
Архивирование
репозиторий, README, версии зависимостей
Рано или поздно любой сервис выводится из эксплуатации
Для физика
Код к статье, заброшенный после публикации, должен оставаться воспроизводимым: README, зафиксированные зависимости, архив с DOI.
Изложение. Рано или поздно программу перестают поддерживать: её заменяет новая система, исчезает задача или некому её сопровождать. Завершение жизненного цикла (end of life) тоже требует действий. Об этом объявляют заранее, называя сроки; пользователям дают время и путь перехода на замену; данные выгружают в формате, переживающем сам сервис; инфраструктуру отключают, не оставляя забытых серверов и открытых портов.
Для физиков: в науке этот этап игнорируется чаще, чем где бы то ни было. Код к статье, заброшенный сразу после публикации, встречается повсеместно, а от него зависит, сможет ли кто-то воспроизвести результат через пять лет. Минимум, который необходимо оставлять после себя, — работающий репозиторий с README, зафиксированными версиями зависимостей и архивом релиза.
Итоги
лекции
01
Жизненный цикл: анализ, проектирование, разработка, тестирование, релиз, сопровождение, завершение.
02
Требования и разбор замысла до кода дешевле исправлений после релиза; MVP проверяет замысел рано.
03
Git, короткие ветки и pull request с ревью позволяют работать вместе, не затирая чужое.
04
Пирамида тестов: статический анализ и модульные тесты на каждое изменение, остальное — реже.
05
Выпуск — тег версии и образ; откат — запуск предыдущего образа, флаги выключают функции без выкатки.
06
CI/CD выполняет проверки и выпуск без ручных шагов; сопровождение и завершение тоже требуют работы.
Изложение. Жизненный цикл программного обеспечения состоит из анализа, проектирования, разработки, тестирования, релиза, сопровождения и завершения, и стадии повторяются циклами. Требования, разбор замысла с коллегами и проверка безопасности до написания кода обходятся дешевле исправлений после релиза, а минимально жизнеспособный продукт рано проверяет, тот ли продукт создаётся. Система контроля версий, короткие ветки и pull request с ревью позволяют работать вместе. Тесты выстраиваются пирамидой от статического анализа до нагрузочных. Выпуск фиксируется тегом версии и образом, откат сводится к запуску предыдущего образа, а feature-флаги выключают функции без новой выкатки. CI/CD выполняет проверки и выпуск без ручных шагов, а работающая система требует наблюдения, разбора инцидентов и, в конце, аккуратного завершения.
Следующие лекции продолжают часть об инженерных практиках.
Домашнее задание:
«Котики в мир»
01
Контекст
фронтенд и бэкенд стартапа «Котики в мир» остались в разных ветках
02
Репозиторий
слияние рабочих веток в main, код по каталогам frontend, backend, nginx
03
Docker
отдельные образы бэкенда и фронтенда, сборка через docker compose build
04
Готовность
фронтенд на http://localhost, бэкенд отвечает; трафик только через nginx
Пояснение
github.com/phys-dev/kitty-startup-task: форк, решение в ветке, ревьюер — преподаватель.
Для физика
Та же задача возникает с кодом установки, который участники группы годами вели в разных ветках и копиях.
Изложение. Домашнее задание повторяет ситуацию из практики эксплуатации. В стартапе «Котики в мир» фронтенд-разработчик и бэкенд-разработчик параллельно сделали новые функции, не влили их в основную ветку и покинули проект; задача — привести репозиторий в порядок и подготовить проект к промышленной выкатке. Требования: слияние всех рабочих веток в main; раскладка кода по каталогам frontend, backend и nginx; отдельные Docker-образы бэкенда и фронтенда, взаимодействие которых описано в compose.yaml, со сборкой через docker compose build. Критерии приёмки: чистая основная ветка, логичное разделение кода по каталогам, рабочий Dockerfile каждого сервиса; фронтенд доступен на http://localhost, бэкенд отвечает на запросы API; наружу опубликован только порт 80 у nginx, остальные сервисы доступны лишь внутри сети Compose и обращаются друг к другу по именам сервисов. Репозиторий задания — github.com/phys-dev/kitty-startup-task: его форкают, выполняют задание и приглашают преподавателя ревьюером. При выполнении задания используются главы книги «Git» и «Docker».
Демонстрация:
репозиторий задания
01
Клонирование kitty-startup-task
02
Удалённые ветки: front_code, my_backend, writing_readme
03
Граф с именами веток: четыре линии от начального коммита
Что наблюдается
Три рабочие ветки разошлись от начального коммита и не слиты в main — с этого начинается задание.
root@lab — запись со стенда
Демонстрация. git clone загружает репозиторий задания kitty-startup-task. git branch -a показывает удалённые ветки front_code, my_backend и writing_readme рядом с main. git log --oneline --graph --all --decorate рисует граф с именами веток: от начального коммита отходят четыре линии — front_code с кодом фронтенда, my_backend с кодом бэкенда, writing_readme с описанием проекта и сама main с двумя правками README, содержащими текст задания; ни одна рабочая ветка не слита в main. Ключ --decorate нужен, потому что вывод передан команде head: вне терминала git имена веток не печатает. Задание начинается со слияния веток; при живом показе дополнительно открываются README и содержимое каждой ветки.
Проект
курса
01
Постановка задачи
документ на 1–2 страницы с путём решения и таблицей этапов; последняя неделя ноября
02
Базовая функциональность
реализованы обязательные требования; вторая неделя декабря
03
Расширенная функциональность
часть дополнительных требований; зачётная неделя
Команда 2–3 человека, у каждого своя зона ответственности; задача связана с физикой, язык — Python.
Артефакт Требование
код собирается без IDE, контейнеризация приветствуется
тесты покрытие тестами
документация программный интерфейс
примеры демонстрация функциональности
репозиторий все артефакты в VCS
Изложение. Проект курса проходит те же стадии жизненного цикла. Тему предлагает команда из двух-трёх человек; задача связана с физикой, основной язык — Python, у каждого участника своя зона ответственности. Задание состоит из базовых требований, которые взаимосвязаны и реализуются вместе, и дополнительных, из которых выбирается подмножество. Работа в рамках проекта идёт в три этапа. Первый — постановка задачи: документ на одну-две страницы с уточнённой формулировкой и предполагаемым путём решения, а также таблица оставшихся этапов со сроками, функциональностью и распределением ответственности. Второй — реализация базовой функциональности. Третий — частичная или полная реализация дополнительных требований к зачётной неделе. Артефакты этапов: программный код со сборочными файлами, собирающийся без участия IDE (контейнеризация приветствуется), покрытие тестами, документация программного интерфейса и примеры использования; всё, включая документ первого этапа, размещается в системе контроля версий.
Источники
Изложение. Основой лекции служат главы книги курса об инженерных практиках; порядок изложения следует лекции «Жизненный цикл ПО» Курсов информационных технологий 2024 года. Для самостоятельного изучения рекомендуются стандарт ISO/IEC/IEEE 29148 о требованиях, книга Pro Git, документация pytest, ruff и GitHub Actions, спецификация семантического версионирования и книга Google «Site Reliability Engineering» о сопровождении сервисов.
Спасибо за внимание
Вопросы
phys-dev.github.io/soft-dev-book
Изложение. Надпись на последнем слайде — EOL, end of life, последняя стадия жизненного цикла. Лекция завершается ответами на вопросы.