Project Management Управление рисками в IT-проектах
0%

Управление рисками в IT-проектах

Управление рисками в 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 различаются деталями, но не сутью) выглядит так:

Ключевое здесь — стрелка обратной связи из мониторинга в идентификацию. Реестр рисков, который не менялся месяц, почти наверняка мёртв: проект узнал о себе кучу нового, а документ — нет.

Частота ревью должна соответствовать скорости изменений. Практика, которая работает: 15 минут на топ-5 рисков раз в спринт (можно приклеить к ревью или к планированию — см. Agile и Scrum), плюс полноценный перебор реестра раз в квартал или на входе в новую фазу.


3. Идентификация: как находить то, о чём не думал

Идентификация — самый недооценённый шаг. Ошибка здесь не компенсируется ничем: нельзя митигировать риск, который вы не назвали.

Источники рисков — таксономия

Таксономия нужна не для красоты, а как чеклист против слепых зон. Канонический пример — Taxonomy-Based Risk Identification от SEI (Carnegie Mellon, доклад CMU/SEI-93-TR-006): структурированный опросник, который вытаскивает риски из голов инженеров, а не из головы менеджера.

Pre-mortem: техника, дающая лучший ROI на час времени

Гэри Клейн предложил приём, описанный в HBR, 2007: собрать команду и сказать — «Прошёл год. Проект с треском провалился. Напишите по-отдельности, что произошло». Дальше собрать списки и обсудить.

Почему это работает лучше, чем «давайте подумаем о рисках»:

  1. Снимает социальное давление. Сказать «я боюсь, что не успеем» — значит проявить нелояльность. Сказать «в сценарии провала мы не успели, потому что…» — безопасно.
  2. Prospective hindsight. Исследования, на которые ссылается Клейн, показывают: если попросить людей объяснить уже случившееся событие, они называют причины на ~30% конкретнее, чем при прогнозе. Мозг лучше объясняет прошлое, чем предсказывает будущее.
  3. Индивидуально, потом коллективно — это защищает от группового мышления и якорения на первом высказанном мнении.

Другие рабочие источники: постмортемы прошлых инцидентов (самый недоиспользуемый актив компании), архитектурные 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%» — в «низкую» или «среднюю»? От этого прыгает цвет, а от цвета — бюджет.

Матрица рисков 5×5 и эффект сжатия диапазона

Вывод не «выбросить матрицу», а «использовать по назначению»: матрица — дешёвый фильтр первого прохода, чтобы из 40 рисков выделить 8 значимых. Дальше по этим восьми считают числа. Использовать цвет ячейки как основание для распределения бюджета — методическая ошибка.

Ещё одно требование, без которого качественная оценка вырождается: определите шкалу явно. «Высокая вероятность» должна означать конкретный интервал (например, >60% в горизонте релиза), а «крупное влияние» — конкретную величину (> 10 рабочих дней или > 1 млн ₽). Иначе два человека поставят «средний» разным вещам, и агрегат станет мусором.


5. Количественный анализ: считаем деньги и дни

EMV — ожидаемая денежная стоимость

Базовая величина: EMV = вероятность × влияние. Для риска «вендор не даёт доступ к реплике» с p = 0,6 и влиянием 40 дней EMV = 24 дня. Это не прогноз («мы потеряем 24 дня» — неверно, мы потеряем либо 0, либо ~40), а цена риска для сравнения с ценой митигации. Правильная интерпретация EMV — «сколько разумно заплатить, чтобы риск исчез».

Три ловушки EMV:

  1. Сумма EMV ≠ реалистичный буфер. Складывать EMV независимых рисков корректно для среднего, но среднее — не тот квантиль, по которому берут обязательства. Нужен P80/P90, а он получается только симуляцией.
  2. EMV слепа к катастрофам. Риск с p = 0,001 и ущербом «компания закрывается» имеет крошечный EMV, но принимать его нельзя. Для таких работает не оптимизация ожидания, а правило «не играть в игры с необратимым проигрышем» — см. рассуждения Талеба о ruin problems.
  3. Влияние — тоже распределение, а не число. «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. Жизненный цикл риска и мониторинг

Риск — не строчка, а объект с состояниями:

Переход «Сработал → Issue» — тот, который чаще всего теряют. Риск, ставший реальностью, должен исчезнуть из риск-реестра и появиться в списке проблем с ответственным и сроком. Иначе он висит «красным» месяцами и обесценивает весь документ.

Триггеры: превращаем риск в наблюдаемую метрику

Риск без триггера — это тревога, а не управление. Триггер — заранее описанное наблюдаемое условие, при котором включается план реагирования.

Риск Триггер (наблюдаемый) Действие по триггеру
Не влезем в окно миграции dry-run на копии продакшена > 3,5 ч включаем поэтапную миграцию по шардам
Вендор не даст реплику нет доступа за 10 рабочих дней до спринта X переходим на анонимизированный дамп
Производительность отчётов p99 на стейдже > 1,2 с при 50% нагрузки останавливаем фичи, спринт на индексы
Bus factor по биллингу PR в модуле от единственного автора 4 недели подряд парное программирование, ротация

Правило: если триггер нельзя выразить как число из дашборда, календаря или трекера — формулировка ещё не готова.

Risk burndown: одна картинка на статус-встречу

Суммарная экспозиция Σ p × impact по времени — единственная метрика рисков, которую стоит носить стейкхолдерам.

Risk burndown: плановое и фактическое снижение экспозиции

Что важно понимать про эту кривую:

  • Рост — не провал. Если экспозиция выросла, значит команда нашла то, что раньше не видела. Проекты, где кривая монотонно и гладко падает с первого дня, обычно просто перестали искать.
  • Всплеск снижения после спайка — это визуальное доказательство ценности исследовательских задач для тех, кто спрашивает «зачем вы три дня ничего не разрабатывали».
  • Ненулевой хвост — норма. Это принятые риски, под которые есть резерв. Ноль в конце означает либо идеальный проект, либо неведение.

Реестр как код

Реестр в 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. План реагирования во времени

У серьёзного риска есть не только стратегия, но и расписание: когда покупаем информацию, когда принимаем решение, когда точка невозврата.

Три элемента, которые обязаны быть в любом таком плане:

  1. Покупка информации в самом начале — потому что она меняет оценку всего остального.
  2. Явная точка отсечки (milestone) — дата, после которой мы перестаём надеяться и переключаемся. Без неё команда ждёт вендора до последнего и лишается времени на план B.
  3. План 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. Типичные ошибки

  1. Risk theatre. Реестр заводится под требование PMO, заполняется один раз и не влияет ни на одно решение. Диагностика: спросите, какая строчка бэклога появилась из-за риска. Если ни одна — это театр.
  2. Владелец «команда». Риск без конкретного имени не мониторит никто. Владелец — человек, который отвечает за наблюдение за триггером, а не тот, кто будет чинить.
  3. Ранжирование сложением баллов. p + i или p × i по порядковым шкалам, потом сортировка, потом бюджет — цепочка, где ошибка накапливается на каждом шаге (см. Cox, раздел 4).
  4. Смешение риска и проблемы. Сработавший риск остаётся в реестре красным; реальные новые риски тонут в шуме.
  5. Митигация без изменения плана. «Митигация: быть внимательнее», «митигация: мониторить ситуацию». Если из митигации не выросла задача с оценкой — митигации нет.
  6. Watermelon status. Снаружи зелёный, внутри красный. Возникает там, где сообщение о риске наказывается. Лечится не процессом, а поведением руководителя — подробно в Команда, коммуникации, конфликты и мотивация.
  7. Буфер, размазанный по задачам. Каждый закладывает своё — общий буфер оказывается огромным и всё равно съедается.
  8. Игнор корреляций. Складывать риски как независимые — значит систематически недооценивать хвост, а именно хвост убивает проекты.
  9. Однократная идентификация. Все риски найдены на старте, когда команда знала о проекте меньше всего. Самые дорогие риски обнаруживаются в середине.
  10. Оптимизация среднего там, где важна выживаемость. Для рисков с необратимыми последствиями (утечка данных, потеря денег клиентов, отзыв лицензии) EMV — неподходящий критерий: там работает порог «не допускаем вовсе», даже если это дорого по ожиданию.
  11. Отсутствие бюджета на риски. Митигации без выделенной ёмкости всегда проигрывают фичам и не делаются никогда.

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 Rightarxiv.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 Engineeringprinciplesofchaos.org
  • Richard I. Cook. How Complex Systems Failhow.complexsystems.fail
  • ISO 31000:2018 Risk management — Guidelinesiso.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.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов