Управление рисками в IT-проектах
Почти каждый провалившийся проект провалился предсказуемо. Не в смысле «мы точно знали дату катастрофы», а в смысле: за месяцы до финала кто-то в команде уже произносил вслух ту самую фразу — «а если вендор не даст доступ к реплике?», «а если данных окажется вдвое больше?», «а что мы будем делать, если Петя уйдёт?». Фраза звучала, кивали, шли дальше. Управление рисками — это дисциплина, которая превращает такую фразу из фонового шума в артефакт с владельцем, ценой, триггером и бюджетом.
Эта статья — про то, как делать это честно: не рисовать красивые матрицы «на всякий случай», а считать деньги и дни, принимать явные решения и понимать, где сама методика врёт.
Материал опирается на оценку и планирование из Оценка и планирование — если вы ещё не смотрели, что такое конус неопределённости и PERT, начните оттуда: риск-менеджмент во многом есть инженерия хвостов тех самых распределений.
1. Что такое риск: строгое определение
Риск — это неопределённое событие или условие, которое в случае наступления влияет на цели проекта. Определение из PMBOK и ISO 31000 («effect of uncertainty on objectives») содержит три важных слова.
- Неопределённое. Если вероятность равна 1 — это не риск, а проблема (issue). Если 0 — это не риск, а фантазия. Риск живёт строго между.
- Влияет на цели. Нет измеримого влияния на срок, бюджет, объём, качество или репутацию — нет риска. «Legacy-код некрасивый» — не риск. «Из-за отсутствия тестов в биллинге любое изменение тарифов требует ручной регрессии на 3 дня, что не даст выпустить промо к чёрной пятнице» — риск.
- Событие или условие. Условие (condition) — это постоянно действующая уязвимость: bus factor 1, отсутствие стенда, зависимость от внешнего API без SLA. Событие — реализация этой уязвимости.
Важно: риск бывает и положительным (opportunity). Если новая версия библиотеки выйдет в срок, мы сэкономим две недели. Управлять надо и этим — только стратегии зеркальные.
RAID: не путайте четыре разные сущности
Половина бесполезных «риск-реестров» бесполезна потому, что в них свалено всё подряд. Разделяйте:
| Сущность | Вопрос | Кто владеет | Что с ней делают |
|---|---|---|---|
| Risk — риск | «Что может пойти не так?» | владелец риска | оценивают, митигируют, мониторят |
| Assumption — допущение | «Что мы считаем верным без доказательств?» | автор плана | проверяют; непроверенное допущение → риск |
| Issue — проблема | «Что уже пошло не так?» | ответственный за решение | решают, эскалируют |
| Dependency — зависимость | «Кто должен что-то сделать для нас?» | менеджер зависимости | согласуют даты, страхуют |
Полезное правило: невалидированное допущение — это риск в маскировке. Строчка «предполагаем, что команда платежей отдаст API к спринту 4» — самая частая заготовка будущего пожара.
Формулировка риска, которую не стыдно принести на ревью
Плохо: «Проблемы с производительностью». Хорошо, по схеме причина → событие → последствие:
Из-за того, что нагрузочное тестирование запланировано на последний спринт, может случиться, что мы обнаружим отсутствие индексов под новый отчётный запрос, что приведёт к сдвигу релиза на 2–3 недели и штрафу по SLA ~400 тыс. ₽.
Такая формулировка сразу даёт три вещи: где чинить (причина — переносим нагрузочное влево), что мониторить (событие — метрика p99 на стейдже), сколько это стоит (последствие).
2. Процесс: замкнутый цикл, а не разовый ритуал
Классический цикл (PMBOK, ISO 31000, NASA Risk Management Handbook различаются деталями, но не сутью) выглядит так:
подхода] --> B[Идентификация] B --> C[Качественный
анализ] C --> D{Стоит
считать
точнее?} D -- да --> E[Количественный
анализ: EMV,
Монте-Карло] D -- нет --> F[Планирование
реагирования] E --> F F --> G[Исполнение
митигаций] G --> H[Мониторинг:
триггеры, burndown] H -- новые сигналы --> B H -- риск сработал --> I[Issue:
план B, резерв] I --> H H -- риск неактуален --> J[Закрыт] style B fill:#4f8ef7,fill-opacity:0.2 style H fill:#46a758,fill-opacity:0.2 style I fill:#d8504a,fill-opacity:0.2
Ключевое здесь — стрелка обратной связи из мониторинга в идентификацию. Реестр рисков, который не менялся месяц, почти наверняка мёртв: проект узнал о себе кучу нового, а документ — нет.
Частота ревью должна соответствовать скорости изменений. Практика, которая работает: 15 минут на топ-5 рисков раз в спринт (можно приклеить к ревью или к планированию — см. Agile и Scrum), плюс полноценный перебор реестра раз в квартал или на входе в новую фазу.
3. Идентификация: как находить то, о чём не думал
Идентификация — самый недооценённый шаг. Ошибка здесь не компенсируется ничем: нельзя митигировать риск, который вы не назвали.
Источники рисков — таксономия
Таксономия нужна не для красоты, а как чеклист против слепых зон. Канонический пример — Taxonomy-Based Risk Identification от SEI (Carnegie Mellon, доклад CMU/SEI-93-TR-006): структурированный опросник, который вытаскивает риски из голов инженеров, а не из головы менеджера.
Pre-mortem: техника, дающая лучший ROI на час времени
Гэри Клейн предложил приём, описанный в HBR, 2007: собрать команду и сказать — «Прошёл год. Проект с треском провалился. Напишите по-отдельности, что произошло». Дальше собрать списки и обсудить.
Почему это работает лучше, чем «давайте подумаем о рисках»:
- Снимает социальное давление. Сказать «я боюсь, что не успеем» — значит проявить нелояльность. Сказать «в сценарии провала мы не успели, потому что…» — безопасно.
- Prospective hindsight. Исследования, на которые ссылается Клейн, показывают: если попросить людей объяснить уже случившееся событие, они называют причины на ~30% конкретнее, чем при прогнозе. Мозг лучше объясняет прошлое, чем предсказывает будущее.
- Индивидуально, потом коллективно — это защищает от группового мышления и якорения на первом высказанном мнении.
Другие рабочие источники: постмортемы прошлых инцидентов (самый недоиспользуемый актив компании), архитектурные ADR с разделом «последствия», интервью с эксплуатацией, а также анализ «что должно быть правдой, чтобы план сработал» — обратный ход от допущений.
4. Качественный анализ и его честные границы
Первый проход всегда качественный: у нас 40 рисков, надо понять, какими заниматься. Стандартный инструмент — оценка вероятности и влияния по шкале и матрица.
Квадранты читаются так: правый верхний — деньги и внимание прямо сейчас; левый верхний — редкие, но убийственные, для них нужен не столько митигейт, сколько план B и резерв; правый нижний — частые и дешёвые, устраняем причину раз и навсегда (флаки-тесты — это не риск, который «мониторят», это баг процесса); левый нижний — принять и держать в поле зрения.
Почему матрица рисков систематически врёт
Здесь надо сказать неприятное. Тони Кокс в статье «What’s Wrong with Risk Matrices?» (Risk Analysis, 2008) математически показал несколько дефектов, которые не лечатся аккуратностью заполнения:
- Range compression (сжатие диапазона). Ячейка «высокая вероятность × высокое влияние» вмещает риски, отличающиеся по ожидаемому ущербу на два-три порядка. Ярлык один — решения должны быть разные.
- Ranking reversal (переворот ранга). Существуют пары рисков, где матрица ставит A выше B, а корректный расчёт — B выше A. Это не погрешность, это доказуемое свойство порядковых шкал с границами.
- Умножение порядковых шкал бессмысленно. «Вероятность 4 × влияние 5 = 20» — арифметика над метками, а не над величинами. Разница между «низкой» и «средней» вероятностью не равна разнице между «средней» и «высокой».
- Субъективность категоризации. Куда отнести «~30%» — в «низкую» или «среднюю»? От этого прыгает цвет, а от цвета — бюджет.
Вывод не «выбросить матрицу», а «использовать по назначению»: матрица — дешёвый фильтр первого прохода, чтобы из 40 рисков выделить 8 значимых. Дальше по этим восьми считают числа. Использовать цвет ячейки как основание для распределения бюджета — методическая ошибка.
Ещё одно требование, без которого качественная оценка вырождается: определите шкалу явно. «Высокая вероятность» должна означать конкретный интервал (например, >60% в горизонте релиза), а «крупное влияние» — конкретную величину (> 10 рабочих дней или > 1 млн ₽). Иначе два человека поставят «средний» разным вещам, и агрегат станет мусором.
5. Количественный анализ: считаем деньги и дни
EMV — ожидаемая денежная стоимость
Базовая величина: EMV = вероятность × влияние. Для риска «вендор не даёт доступ к реплике»
с p = 0,6 и влиянием 40 дней EMV = 24 дня. Это не прогноз («мы потеряем 24 дня» — неверно,
мы потеряем либо 0, либо ~40), а цена риска для сравнения с ценой митигации. Правильная
интерпретация EMV — «сколько разумно заплатить, чтобы риск исчез».
Три ловушки EMV:
- Сумма EMV ≠ реалистичный буфер. Складывать EMV независимых рисков корректно для среднего, но среднее — не тот квантиль, по которому берут обязательства. Нужен P80/P90, а он получается только симуляцией.
- EMV слепа к катастрофам. Риск с p = 0,001 и ущербом «компания закрывается» имеет крошечный EMV, но принимать его нельзя. Для таких работает не оптимизация ожидания, а правило «не играть в игры с необратимым проигрышем» — см. рассуждения Талеба о ruin problems.
- Влияние — тоже распределение, а не число. «40 дней» — на самом деле 3…40 дней.
Монте-Карло: единственный честный способ сложить риски
Задача: получить распределение общей длительности проекта с учётом и разброса задач, и дискретных рисковых событий.
import random
from dataclasses import dataclass, field
from statistics import mean
@dataclass(frozen=True)
class Task:
"""Задача с трёхточечной оценкой (см. PERT в статье об оценке)."""
name: str
optimistic: float
likely: float
pessimistic: float
def sample(self, rng: random.Random) -> float:
# треугольное распределение: дёшево, устойчиво и не требует подгонки параметров
return rng.triangular(self.optimistic, self.pessimistic, self.likely)
@dataclass(frozen=True)
class RiskEvent:
"""Дискретный риск: либо не случился (0), либо ударил на impact_min..impact_max."""
name: str
probability: float
impact_min: float
impact_max: float
def sample(self, rng: random.Random) -> float:
if rng.random() >= self.probability:
return 0.0
return rng.uniform(self.impact_min, self.impact_max)
@dataclass
class Project:
tasks: list = field(default_factory=list)
risks: list = field(default_factory=list)
def simulate_once(self, rng):
base = sum(t.sample(rng) for t in self.tasks)
hits = {r.name: r.sample(rng) for r in self.risks}
return base + sum(hits.values()), hits
def percentile(xs, q):
"""Линейно интерполированный квантиль — без numpy, чтобы код был самодостаточным."""
s = sorted(xs)
k = (len(s) - 1) * q
lo, hi = int(k), min(int(k) + 1, len(s) - 1)
return s[lo] + (s[hi] - s[lo]) * (k - lo)
def run(project, trials=50_000, seed=42):
rng = random.Random(seed)
totals, contrib = [], {r.name: 0.0 for r in project.risks}
for _ in range(trials):
total, hits = project.simulate_once(rng)
totals.append(total)
for name, days in hits.items():
contrib[name] += days # накапливаем фактический вклад риска
return totals, {k: v / trials for k, v in contrib.items()}
project = Project(
tasks=[
Task("схема БД", 5, 8, 20),
Task("миграция данных", 8, 15, 45),
Task("двойная запись", 6, 10, 18),
Task("переключение", 2, 3, 12),
],
risks=[
RiskEvent("ключевой DBA уходит в отпуск", 0.30, 5, 15),
RiskEvent("объём данных вдвое больше", 0.25, 10, 30),
RiskEvent("вендор не даёт доступ к реплике", 0.15, 3, 40),
],
)
totals, emv = run(project)
print(f"среднее: {mean(totals):6.1f} дн.")
print(f"P50: {percentile(totals, 0.50):6.1f} дн.")
print(f"P80: {percentile(totals, 0.80):6.1f} дн.")
print(f"P95: {percentile(totals, 0.95):6.1f} дн.")
print("вклад рисков (EMV, дн.):")
for name, days in sorted(emv.items(), key=lambda kv: -kv[1]):
print(f" {days:5.1f} {name}")
# CVaR90 — «если попадём в худшие 10% сценариев, чего ждать в среднем»
tail = sorted(totals)[int(0.9 * len(totals)):]
print(f"CVaR90: {mean(tail):6.1f} дн.")
Вывод:
среднее: 62.0 дн.
P50: 59.6 дн.
P80: 75.2 дн.
P95: 92.7 дн.
вклад рисков (EMV, дн.):
5.0 объём данных вдвое больше
3.3 вендор не даёт доступ к реплике
3.0 ключевой DBA уходит в отпуск
CVaR90: 95.3 дн.
Как это читать. Сумма «наиболее вероятных» оценок задач — 36 дней. P50 симуляции — 59,6. P80 — 75,2. Разрыв между 36 и 75 и есть цена, которую платят команды, обещающие сроки по модальным оценкам. Обязательство наружу берут по P80 или P90; внутренний план команды может жить по P50, а разницу оформляют как явный буфер, а не как «постараемся».
CVaR (conditional value at risk) отвечает на вопрос, который важнее P95: не «какая граница хвоста», а «насколько плохо внутри хвоста». Если P95 = 92, а CVaR90 = 95 — хвост короткий, срыв управляем. Если бы CVaR90 оказался 140 — значит, в хвосте живёт сценарий, который убьёт проект, и его надо резать отдельно.
Сложность. Один прогон — O(T + R) по числу задач и рисков; полный расчёт — O(N·(T + R)) по
времени и O(N) по памяти для хранения выборки (память сокращается до O(1) при потоковом расчёте
квантилей, например через t-digest). При N = 50 000 и десятках элементов это доли секунды на CPU —
никаких причин экономить на симуляции нет. Стандартная ошибка оценки квантиля падает как O(1/√N),
поэтому переход с 1 000 итераций на 50 000 сужает доверительный интервал примерно в 7 раз.
Главный подвох Монте-Карло — независимость. Код выше складывает риски как независимые. В жизни они коррелируют: тот же аврал, из-за которого уехала миграция, съест и время на нагрузочное. Если корреляции игнорировать, хвост окажется искусственно тонким. Минимальное лечение — ввести общий фактор (например, множитель «команда перегружена», семплируемый один раз на итерацию и влияющий на все задачи) либо копулы, если нужна строгость.
Reference class forecasting: лекарство от оптимизма
Внутренние оценки систематически оптимистичны — это planning fallacy (Канеман и Тверски). Бент Флювбьерг показал на выборке мегапроектов, что единственный метод с доказанным эффектом — reference class forecasting: не оценивать проект «изнутри», а взять класс похожих проектов и посмотреть их фактическое распределение перерасходов (Flyvbjerg, «From Nobel Prize to Project Management»).
В IT это переводится буквально: «последние шесть миграций БД в нашей компании заняли 1,4–3,1 от исходной оценки; медиана 1,8». Такой множитель — гораздо более сильный прогноз, чем самая подробная декомпозиция. Если у вас есть история проектов — вы обязаны считать этот коэффициент. См. также подход к историческим данным в Kanban и управление потоком, где то же самое делают через распределение cycle time.
Калибровка: имеете ли вы право на свои проценты
Оценка вероятностей — навык, который тренируется и измеряется (Дуглас Хаббард, «How to Measure Anything» и «The Failure of Risk Management»). Проверка простая — Brier score и таблица калибровки по историческим прогнозам.
from collections import defaultdict
# (формулировка, заявленная вероятность, сбылось ли)
history = [
("нагрузочное упрётся в БД", 0.7, True),
("вендор задержит доступ", 0.6, True),
("отпуск DBA сорвёт спринт", 0.3, False),
("релиз откатим", 0.2, False),
("аудит потребует доработок", 0.8, True),
("миграция уложится в окно", 0.9, False),
("нужен второй нагрузочный тест", 0.5, True),
("фича-флаг спасёт откат", 0.9, True),
]
# Brier = средний квадрат ошибки прогноза; 0 — идеал, 0.25 — уровень честной монетки
brier = sum((p - int(hit)) ** 2 for _, p, hit in history) / len(history)
print(f"Brier score: {brier:.3f}")
# таблица калибровки: в корзине «80%» должно сбываться примерно 80%
buckets = defaultdict(list)
for _, p, hit in history:
buckets[round(p * 10) // 2 * 2 / 10].append(hit)
for lo in sorted(buckets):
hits = buckets[lo]
print(f"{lo:9.0%}+ {sum(hits) / len(hits):9.0%} n={len(hits)}")
Brier score: 0.186
20%+ 0% n=2
40%+ 100% n=1
60%+ 100% n=2
80%+ 67% n=3
Корзина «80–90%» сбывается на 67% — классическая самоуверенность. Команда, которая полгода ведёт такой лог, начинает давать проценты, которым можно верить. Команда, которая не ведёт, оперирует числами из воздуха — и тогда весь количественный анализ выше становится театром точности.
6. Стратегии реагирования
На каждый значимый риск выбирается стратегия. Классических пять для угроз и четыре зеркальных для возможностей.
| Стратегия | Суть | Пример из IT | Когда уместна |
|---|---|---|---|
| Avoid — избежать | убрать причину, изменив план или объём | отказаться от кастомного шифрования в пользу managed KMS | влияние катастрофическое и обратимости нет |
| Transfer — передать | отдать финансовые последствия третьей стороне | SLA с неустойкой, страховка, managed-сервис вместо своего кластера | риск финансовый и на рынке есть тот, кому он дешевле |
| Mitigate — снизить | уменьшить вероятность или влияние | спайк-исследование, канареечный релиз, фича-флаги, бэкап + отработанный откат | ROI митигации положительный |
| Accept — принять | осознанно ничего не делать, но записать | «падение отчётов на 2 часа переживём» | цена реакции выше цены риска |
| Escalate — эскалировать | риск вне полномочий команды | зависимость от решения другого юнита или регулятора | владелец риска физически не тут |
Для возможностей: exploit (сделать так, чтобы точно случилось), share (партнёрство), enhance (повысить вероятность), accept.
Два практических правила:
- Accept — это решение, а не отсутствие решения. Принятый риск остаётся в реестре, у него есть резерв («если сработает, потратим 5 дней из буфера») и владелец, который следит за триггером. Разница между «приняли» и «забили» — наличие этих двух полей.
- Mitigate почти всегда бьёт по вероятности через уменьшение неопределённости. Самая недооценённая митигация в IT — спайк: коробочная задача на 1–3 дня, единственный результат которой — знание. Спайк не двигает продукт, но он схлопывает распределение, а значит и хвост.
Считаем ROI митигации, а не «важность»
Митигации конкурируют за то же время команды, что и фичи. Значит, сравнивать их надо деньгами.
from dataclasses import dataclass
@dataclass(frozen=True)
class Mitigation:
name: str
cost: float # чел.-дни, которые придётся вынуть из разработки
p_before: float
p_after: float
impact_before: float # чел.-дни ущерба
impact_after: float
@property
def emv_before(self):
return self.p_before * self.impact_before
@property
def emv_after(self):
return self.p_after * self.impact_after
@property
def benefit(self):
return self.emv_before - self.emv_after
@property
def net(self):
return self.benefit - self.cost
@property
def roi(self):
return self.net / self.cost if self.cost else float("inf")
candidates = [
Mitigation("Спайк на реплике вендора (3 дня)", 3.0, 0.60, 0.15, 40, 40),
Mitigation("Второй DBA в курсе миграции", 6.0, 0.30, 0.30, 15, 4),
Mitigation("Blue-green переключение", 8.0, 0.20, 0.20, 60, 10),
Mitigation("Полный rewrite слоя доступа", 40.0, 0.25, 0.10, 30, 30),
]
print(f"{'митигация':36} {'цена':>6} {'EMV до':>8} {'EMV после':>10} {'нетто':>7} {'ROI':>7}")
for m in sorted(candidates, key=lambda m: -m.roi):
print(f"{m.name:36} {m.cost:6.1f} {m.emv_before:8.1f} {m.emv_after:10.1f} "
f"{m.net:7.1f} {m.roi:6.0%}")
# жадный отбор под бюджет — «сколько дней команда готова потратить на страховку»
budget, chosen, spent = 12.0, [], 0.0
for m in sorted(candidates, key=lambda m: -m.roi):
if m.net > 0 and spent + m.cost <= budget:
chosen.append(m)
spent += m.cost
print(f"\nбюджет {budget:.0f} дн. -> берём: {[m.name for m in chosen]}")
print(f"снижение экспозиции: {sum(m.benefit for m in chosen):.1f} дн. за {spent:.1f} дн. работы")
митигация цена EMV до EMV после нетто ROI
Спайк на реплике вендора (3 дня) 3.0 24.0 6.0 15.0 500%
Blue-green переключение 8.0 12.0 2.0 2.0 25%
Второй DBA в курсе миграции 6.0 4.5 1.2 -2.7 -45%
Полный rewrite слоя доступа 40.0 7.5 3.0 -35.5 -89%
бюджет 12 дн. -> берём: ['Спайк на реплике вендора (3 дня)', 'Blue-green переключение']
снижение экспозиции: 28.0 дн. за 11.0 дн. работы
Разбор результата важнее самого кода:
- Спайк выигрывает с огромным отрывом — типичная картина. Дешёвая покупка информации почти всегда лучший ход, когда неопределённость высока. Это ровно тот же принцип, что и «value of information» у Хаббарда: сначала измеряй то, где незнание дороже всего.
- «Второй DBA» имеет отрицательный ROI по этой модели — и вот здесь надо остановиться и не верить модели слепо. Bus factor даёт эффект на всех будущих рисках, а не только на одном, и в однориск-модели недооценён. Это иллюстрация общего правила: числа — вход в разговор, а не замена разговора.
- Rewrite отрицателен по-честному. «Переписать, чтобы стало надёжнее» — самая дорогая митигация с самой слабой доказанной связью между стоимостью и снижением вероятности.
Жадный отбор под бюджет — это, по сути, дробный рюкзак: сортировка по плотности выгоды даёт O(k log k)
и оптимум для делимых элементов; для неделимых даёт приближение, чего в планировании достаточно.
Резервы: contingency против management reserve
- Contingency reserve — под известные риски (identified). Величина берётся из симуляции: например, P80 − P50 = 15,6 дня. Этим резервом распоряжается команда/менеджер проекта.
- Management reserve — под неизвестные (unknown unknowns). Величина берётся из истории организации (типично 5–15% сверху). Распоряжается спонсор; расход требует явного решения.
Ключевое: буфер должен быть один и общий, а не размазан по задачам. Если каждый инженер
закладывает свои 30% в каждую задачу, буферы съедаются по закону Паркинсона и студенческому
синдрому — работа расширяется до отведённого времени, а старт откладывается до последнего. Это
центральный аргумент метода критической цепи (Голдратт, «Critical Chain»). Агрегированный буфер
меньше суммы индивидуальных ровно из-за пулинга: дисперсия суммы независимых величин растёт как
√n, а не как n.
7. Жизненный цикл риска и мониторинг
Риск — не строчка, а объект с состояниями:
инцидент, интервью Выявлен --> Оценён: p и impact,
владелец назначен Оценён --> Принят: цена реакции >
цены риска Оценён --> Митигируется: выбран план,
задачи в бэклоге Оценён --> Эскалирован: вне полномочий
команды Эскалирован --> Митигируется: спонсор дал
ресурс Митигируется --> Остаточный: план выполнен,
p снижена Принят --> Сработал: триггер
наступил Остаточный --> Сработал: сработал
всё равно Митигируется --> Сработал: не успели Сработал --> Issue: активируется
план B и резерв Issue --> Закрыт: последствия
устранены Принят --> Закрыт: контекст изменился,
риск неактуален Остаточный --> Закрыт: фаза пройдена Закрыт --> [*] note right of Сработал Сработавший риск переходит в issue-трекер: у него уже вероятность = 1 end note
Переход «Сработал → Issue» — тот, который чаще всего теряют. Риск, ставший реальностью, должен исчезнуть из риск-реестра и появиться в списке проблем с ответственным и сроком. Иначе он висит «красным» месяцами и обесценивает весь документ.
Триггеры: превращаем риск в наблюдаемую метрику
Риск без триггера — это тревога, а не управление. Триггер — заранее описанное наблюдаемое условие, при котором включается план реагирования.
| Риск | Триггер (наблюдаемый) | Действие по триггеру |
|---|---|---|
| Не влезем в окно миграции | dry-run на копии продакшена > 3,5 ч | включаем поэтапную миграцию по шардам |
| Вендор не даст реплику | нет доступа за 10 рабочих дней до спринта X | переходим на анонимизированный дамп |
| Производительность отчётов | p99 на стейдже > 1,2 с при 50% нагрузки | останавливаем фичи, спринт на индексы |
| Bus factor по биллингу | PR в модуле от единственного автора 4 недели подряд | парное программирование, ротация |
Правило: если триггер нельзя выразить как число из дашборда, календаря или трекера — формулировка ещё не готова.
Risk burndown: одна картинка на статус-встречу
Суммарная экспозиция Σ p × impact по времени — единственная метрика рисков, которую стоит носить
стейкхолдерам.
Что важно понимать про эту кривую:
- Рост — не провал. Если экспозиция выросла, значит команда нашла то, что раньше не видела. Проекты, где кривая монотонно и гладко падает с первого дня, обычно просто перестали искать.
- Всплеск снижения после спайка — это визуальное доказательство ценности исследовательских задач для тех, кто спрашивает «зачем вы три дня ничего не разрабатывали».
- Ненулевой хвост — норма. Это принятые риски, под которые есть резерв. Ноль в конце означает либо идеальный проект, либо неведение.
Реестр как код
Реестр в Excel умирает первым. Реестр в репозитории рядом с кодом живёт, потому что попадает под ревью, историю изменений и CI.
# risks.yaml — живёт в репозитории, проходит ревью вместе с ADR
- id: RSK-014
title: "Вендор не предоставит доступ к реплике до спринта 5"
cause: "Договор не содержит SLA на предоставление тестового окружения"
effect: "Миграцию нельзя отрепетировать на реальных объёмах; сдвиг релиза"
category: external
owner: "@a.petrov" # человек, а не «команда» и не «PMO»
probability: 0.60 # калиброванная оценка, пересматривается на ревью
impact_days: 40
strategy: mitigate
actions:
- "Спайк: развернуть анонимизированный дамп 1:1 по объёму — 3 дня"
- "Письменный запрос вендору с датой отсечки"
trigger: "Нет доступа за 10 рабочих дней до старта спринта 5"
fallback: "Миграция по шардам с двойной записью, окно вместо 4 ч — 3 ночи по 2 ч"
review_cadence: weekly
status: mitigating
history:
- {date: 2026-05-12, probability: 0.60, note: "заведён после pre-mortem"}
- {date: 2026-06-02, probability: 0.35, note: "вендор подтвердил намерение"}
- {date: 2026-06-20, probability: 0.15, note: "спайк закрыт, дамп развёрнут"}
Поле history — то, что делает возможной калибровку из раздела 5: без истории оценок нельзя узнать,
насколько ваши проценты соответствуют реальности.
Простая CI-проверка, которая ловит 80% гнили реестра:
#!/usr/bin/env bash
# ci/lint-risks.sh — падаем, если реестр деградировал
set -euo pipefail
# 1. У каждого активного риска есть владелец-человек и триггер
yq -e '[.[] | select(.status != "closed") |
select(.owner == null or .trigger == null)] | length == 0' risks.yaml \
|| { echo "Есть активные риски без владельца или триггера"; exit 1; }
# 2. Ни один активный риск не остался без пересмотра дольше 45 дней
python3 - <<'PY'
import datetime, sys, yaml
today = datetime.date.today()
stale = []
for r in yaml.safe_load(open("risks.yaml")):
if r.get("status") == "closed":
continue
last = max(h["date"] for h in r.get("history", [])) if r.get("history") else None
if last is None or (today - last).days > 45:
stale.append(r["id"])
if stale:
print("Протухшие риски (нет ревью > 45 дней):", ", ".join(stale))
sys.exit(1)
PY
8. План реагирования во времени
У серьёзного риска есть не только стратегия, но и расписание: когда покупаем информацию, когда принимаем решение, когда точка невозврата.
Три элемента, которые обязаны быть в любом таком плане:
- Покупка информации в самом начале — потому что она меняет оценку всего остального.
- Явная точка отсечки (milestone) — дата, после которой мы перестаём надеяться и переключаемся. Без неё команда ждёт вендора до последнего и лишается времени на план B.
- План B, подготовленный заранее, а не придуманный в момент срабатывания. Отрепетированный откат — единственный откат, который работает.
9. Инженерные практики — это и есть управление рисками
Самая продуктивная мысль этой статьи: в зрелых командах риск-менеджмент почти не выглядит как риск-менеджмент. Он растворён в инженерных практиках, каждая из которых есть митигация конкретного класса рисков.
| Практика | Какой риск снижает | Механизм |
|---|---|---|
| CI и автотесты | регрессия при изменениях | снижает вероятность |
| Trunk-based + мелкие релизы | «большой взрыв» на релизе | снижает влияние: меньше дельта — меньше ущерб |
| Feature flags | плохая фича в проде | снижает влияние: выключение вместо отката |
| Канареечный релиз | массовый инцидент | ограничивает радиус поражения |
| Blue-green / отработанный откат | необратимость изменения | превращает катастрофу в неудобство |
| Error budgets (SRE) | конфликт «скорость против надёжности» | делает допустимый риск явной величиной |
| Chaos engineering | скрытые отказы в распределённой системе | превращает unknown unknowns в known |
| Постмортемы без обвинений | повтор тех же инцидентов | превращает инцидент в источник рисков |
Две вещи стоит развернуть.
Error budget (Google SRE Book, глава «Embracing Risk») — это оформленное решение «сколько ненадёжности мы покупаем ради скорости». Если SLO = 99,9%, то бюджет ошибок на квартал ≈ 43 минуты недоступности. Пока бюджет не исчерпан — команда деплоит свободно; исчерпан — приоритет автоматически смещается на надёжность. Это лучший из известных механизмов: правило записано заранее, поэтому в момент конфликта не нужно спорить о приоритетах, достаточно посмотреть на число. Подробнее о связанных метриках — в DORA и метрики инженерной эффективности.
Chaos engineering (принципы) — прямая атака на слабое место любой риск-методики: мы оцениваем только те риски, которые сумели вообразить. Управляемый эксперимент («убьём инстанс и посмотрим») переводит неизвестное в известное дешевле, чем это сделает продакшен в пятницу вечером. По сути это тот же спайк, только про надёжность.
Тезис Ричарда Кука из «How Complex Systems Fail» стоит держать в голове: сложные системы работают в состоянии постоянно присутствующих скрытых дефектов, а катастрофа требует совпадения нескольких. Отсюда практический вывод: митигация, которая добавляет независимый барьер, ценнее митигации, которая делает существующий барьер чуть надёжнее.
Связанные материалы: определение готовности и релизный процесс — в Качество, Definition of Done и релизный менеджмент; риски масштабирования процессов на много команд — в Масштабирование процессов.
10. Типичные ошибки
- Risk theatre. Реестр заводится под требование PMO, заполняется один раз и не влияет ни на одно решение. Диагностика: спросите, какая строчка бэклога появилась из-за риска. Если ни одна — это театр.
- Владелец «команда». Риск без конкретного имени не мониторит никто. Владелец — человек, который отвечает за наблюдение за триггером, а не тот, кто будет чинить.
- Ранжирование сложением баллов.
p + iилиp × iпо порядковым шкалам, потом сортировка, потом бюджет — цепочка, где ошибка накапливается на каждом шаге (см. Cox, раздел 4). - Смешение риска и проблемы. Сработавший риск остаётся в реестре красным; реальные новые риски тонут в шуме.
- Митигация без изменения плана. «Митигация: быть внимательнее», «митигация: мониторить ситуацию». Если из митигации не выросла задача с оценкой — митигации нет.
- Watermelon status. Снаружи зелёный, внутри красный. Возникает там, где сообщение о риске наказывается. Лечится не процессом, а поведением руководителя — подробно в Команда, коммуникации, конфликты и мотивация.
- Буфер, размазанный по задачам. Каждый закладывает своё — общий буфер оказывается огромным и всё равно съедается.
- Игнор корреляций. Складывать риски как независимые — значит систематически недооценивать хвост, а именно хвост убивает проекты.
- Однократная идентификация. Все риски найдены на старте, когда команда знала о проекте меньше всего. Самые дорогие риски обнаруживаются в середине.
- Оптимизация среднего там, где важна выживаемость. Для рисков с необратимыми последствиями (утечка данных, потеря денег клиентов, отзыв лицензии) EMV — неподходящий критерий: там работает порог «не допускаем вовсе», даже если это дорого по ожиданию.
- Отсутствие бюджета на риски. Митигации без выделенной ёмкости всегда проигрывают фичам и не делаются никогда.
11. Как это выглядит в проде: три сжатых кейса
Миграция биллинга на новую БД. Топ-риск — не «миграция сломается», а «мы не узнаем, что она сломалась». Рабочая конфигурация: двойная запись, сверка контрольных сумм по выборке, канареечное переключение чтения на 1% трафика, отрепетированный (не описанный — отрепетированный) откат, окно с запасом ×2 от dry-run. Экспозиция снижается не оценками, а архитектурой отката.
Зависимость от внешнего вендора. Риск чаще организационный, чем технический: сроки вендора вам не подчиняются. Митигации: контрактный SLA с датами (transfer), заранее написанный адаптер и стаб-реализация для разработки (mitigate), точка отсечки в календаре (решение вместо надежды), запасной поставщик в шорт-листе (avoid зависимости от единственного).
Bus factor = 1 на критическом модуле. Самая недооценённая митигация — не документация (она устаревает), а ротация задач и парное программирование плюс автоматизированный контроль: метрика «доля файлов, где 90% изменений сделаны одним автором» считается из истории git и попадает на дашборд команды. Риск, который стал метрикой, начинает лечиться сам.
12. Мини-итог и чеклист
Что запомнить:
- Риск = неопределённое событие × измеримое влияние на цели. Нет числа — нет управления.
- Матрица 5×5 — фильтр первого прохода, а не инструмент распределения бюджета (range compression, ranking reversal).
- Обязательства берут по P80/P90 из симуляции, а не по сумме модальных оценок.
- Лучшая митигация в условиях высокой неопределённости — купить информацию (спайк), а не построить защиту от воображаемого сценария.
- Митигации конкурируют с фичами за ёмкость, поэтому выбираются по ROI и под явный бюджет.
- Риск без владельца-человека, триггера и плана B — это тревога, а не риск-менеджмент.
- В зрелых командах риск-менеджмент растворён в инженерных практиках: флаги, канарейки, error budgets, отрепетированные откаты.
Чеклист перед стартом фазы:
- Проведён pre-mortem с индивидуальной записью сценариев провала
- Каждое допущение плана либо проверено, либо превращено в риск
- Шкалы вероятности и влияния определены числами, а не словами
- По топ-5 рискам посчитан EMV и прогнан Монте-Карло на длительность
- У каждого активного риска есть владелец-человек, триггер-метрика и план B
- Митигации отобраны по ROI и стоят в бэклоге как задачи с оценкой
- Contingency-резерв взят как P80 − P50, management reserve согласован со спонсором
- Реестр лежит в репозитории, ревью в календаре, история оценок ведётся
- Есть risk burndown, который показывают стейкхолдерам
Источники
- Tom DeMarco, Timothy Lister. Waltzing with Bears: Managing Risk on Software Projects (Dorset House, 2003) — лучшая книга именно про софтверные риски; оттуда идея risk diagram и тезис «если проект не имеет рисков, за него не стоит браться».
- Douglas W. Hubbard. The Failure of Risk Management: Why It’s Broken and How to Fix It (Wiley) и How to Measure Anything — калибровка, критика балльных шкал, value of information.
- Louis Anthony (Tony) Cox. What’s Wrong with Risk Matrices? Risk Analysis, 28(2), 2008 — doi:10.1111/j.1539-6924.2008.01030.x
- Gary Klein. Performing a Project Premortem. HBR, 2007 — hbr.org/2007/09/performing-a-project-premortem
- Bent Flyvbjerg. From Nobel Prize to Project Management: Getting Risks Right — arxiv.org/abs/1302.3642
- SEI. Taxonomy-Based Risk Identification (CMU/SEI-93-TR-006) — insights.sei.cmu.edu
- Google SRE Book, «Embracing Risk» — sre.google/sre-book/embracing-risk
- Principles of Chaos Engineering — principlesofchaos.org
- Richard I. Cook. How Complex Systems Fail — how.complexsystems.fail
- ISO 31000:2018 Risk management — Guidelines — iso.org/standard/65694.html
- NASA Risk Management Handbook (NASA/SP-2011-3422) — ntrs.nasa.gov
- Eliyahu M. Goldratt. Critical Chain — про агрегированные буферы и студенческий синдром.
- Steve McConnell. Software Estimation: Demystifying the Black Art (Microsoft Press) — конус неопределённости и практика диапазонных оценок.
Что дальше
Мы разобрали, как считать и снижать риски проекта. Но самый частый источник срыва — не архитектура и не вендор, а люди: непроговорённые ожидания, выгорание, конфликт приоритетов, молчание там, где нужен сигнал. Об этом — следующая статья:
Команда, коммуникации, конфликты и мотивация
Если нужно вернуться к основам оценки, на которых стоит количественный анализ рисков — см. Оценка и планирование; общая карта трека — Управление проектами в IT.