ousterhout-quality-program

автор: Rob ZappПока нет установокПока нет лайковОбновлено 21 сентября 2026 г.Категория: Ревью кода

Что он делает

Используйте всякий раз, когда пишется или просматривается код, создающий или изменяющий границу — новый модуль, класс, компонент, помощник, хук, сервис или обёртку; любое извлечение или централизация общего кода; любой момент «давайте сделаем это переиспользуемым» — а также при явном обзоре, рефакторинге или проектировании модуля. Оценивает, оправдывает ли абстракция своё существование: глубина модуля, стоит ли скрывать решение по дизайну, защищает ли дублированный код общий инвариант или просто рифмуется, стабильный ли интерфейс. Предохраняет от механического применения SOLID/Clean Code, приводящего к множеству поверхностных классов. Также определяет тест стоимости для читателя (код, который легко читать и изменять как людям, так и агентам) и процедуру рефакторинга существующей кодовой базы до этого стандарта.

Установка открывает эту карточку в вашем десктопном приложении AgentsRoom. Если приложение ещё не установлено, вы попадёте на страницу загрузки.

SKILL.md

---
name: ousterhout-quality-program
description: Используйте всякий раз, когда пишется или просматривается код, создающий или изменяющий границу — новый модуль, класс, компонент, помощник, хук, сервис или обёртку; любое извлечение или централизация общего кода; любой момент «давайте сделаем это переиспользуемым» — а также при явном обзоре, рефакторинге или проектировании модуля. Оценивает, оправдывает ли абстракция своё существование: глубина модуля, стоит ли скрывать решение по дизайну, защищает ли дублированный код общий инвариант или просто рифмуется, стабильный ли интерфейс. Предохраняет от механического применения SOLID/Clean Code, приводящего к множеству поверхностных классов. Также определяет тест стоимости для читателя (код, который легко читать и изменять как людям, так и агентам) и процедуру рефакторинга существующей кодовой базы до этого стандарта.
---

# Программа качества Ousterhout

## Обзор

Задача модуля — скрыть сложность за небольшим интерфейсом. Основной показатель — **глубина**: глубокий модуль предлагает простой интерфейс поверх значительной функциональности; интерфейс мелкого модуля почти так же сложен, как и его реализация, поэтому он ни во что не окупается. Сложность — это то, что вы ощущаете, когда изменение заставляет вас понимать или трогать код, которого вы не ожидали — Ousterhout выделяет два источника: **зависимости** (нельзя изменить A, не изменив B) и **неочевидность** (важная информация не очевидна).

Ousterhout сам по себе говорит, каким *ощущается* хороший модуль. Он наиболее силен в сочетании с несколькими другими взглядами, которые подсказывают, где должны быть границы и как к ним безопасно двигаться. Этот навык — именно такой объединённый взгляд.

## Где на самом деле происходят ошибки в ревью

Два сбоя, которые этот навык призван исправить — неоднократно наблюдаемые в коде, написанном агентами, — связаны с **исправлением**, а не с самим вердиктом разделять/не разделять:

1. **Поверхностное исправление.** При наличии шести `as unknown as` кастов, неподготовленный рецензент централизует их в один универсальный хелпер `castRows<T>()` — аккуратнее, но неочевидность остаётся. Глубокое исправление — типизированные мапперы row→domain с тестами, написанными в первую очередь (применяя Пarnas: каст — это запах отсутствующей границы; Beck: докажи маппинг, прежде чем его переносить). Приведение запаха в порядок — не значит его устранение.
2. **Рефлексивное извлечение.** При повторении одной и той же логики обновления в трёх соседних компонентах каждый неподготовленный рецензент говорил «извлечь общий хелпер» — рефлекс DRY. Правило этой программы, расширяющее Metz: ждите инварианта, а не третьего похожего случая — централизуйте, когда код защищает общее правило, а не когда он рифмуется.

Когда вы рекомендуете исправление, проверьте его по двум критериям: убирает ли оно неочевидность или просто перемещает её, и защищает ли извлечение инвариант или просто устраняет дублирование формы?

## Пропорциональный фильтр

Пропускайте этот взгляд, если изменение не добавляет нового экспортируемого/импортируемого имени, не создаёт новый модуль/класс/компонент/хелпер/хук/сервис/обёртку и ничего не централизует. Чистые переименования, механические кодмоды, правки конфигураций/данных и однострочные исправления исключаются. В случае сомнений выполните только два основных теста (глубина, инвариант) и остановитесь.

## Полное правило

Каждый кусок сгенерированного или проверенного кода проходит через линзу Ousterhout перед тем, как задача считается выполненной — не только явные дизайн-ревью — за исключением изменений ниже пропорционального фильтра (нет новой границы, нет централизации: переименования, кодмоды, правки конфигураций). Два теста: (1) **Глубина** — новый интерфейс должен скрывать существенно больше, чем открывать; интерфейс, не проще того, что он оборачивает, ни во что не окупается. (2) **Инвариант** — извлекайте общий код только тогда, когда он защищает общее правило, никогда потому, что три места рифмуются; исправление должно убирать неочевидность, а не перемещать её (централизация шести кастов в один хелпер — это всё ещё шесть кастов). Когда изменение создаёт или меняет границу, сначала найдите, как устоявшийся продукт решает проблему такой формы и масштаба, и примите его соглашения, если нет явной причины не делать этого (узнанный из тренинга паттерн — это утверждение, а не источник), затем выполните проверки ниже.

## Когда использовать

- Решаете, стоит ли новая класс/функция/хук своего интерфейса или это просто мелкий проходной слой.
- Файл превышает порог размера, и вы решаете, *как* его разделить, а не просто что разделять.
- Повторяющийся код подталкивает вас к извлечению общего хелпера.
- Проектируете или ревьюите границу вокруг бизнес-правила (проверка области авторизации, правило денег/округления, защита перехода в конечном автомате, правило хранения данных).
- Интерфейс собирается расшириться параметром или особым случаем.
- Приводите существующую кодовую базу к этому стандарту — см. раздел «Рефакторинг существующей кодовой базы к этому стандарту» ниже.

**Не для:** тривиальных механических правок или когда структура уже диктуется соглашением проекта — см. выше Пропорциональный фильтр. В таких случаях следуйте `karpathy-guidelines` для дисциплины хирургических изменений и навыку разработки через тесты для безопасности рефакторинга, если они доступны.

## Взгляды

Каждый взгляд добавляет ровно один вопрос. Ousterhout — основа; остальные исправляют его слепые зоны.

| Линза | Один вопрос, который она добавляет | Когда она имеет приоритет |
|---|---|---|
| **Ousterhout** — глубокие модули | Скрывает ли этот интерфейс больше, чем раскрывает? | Стандартный каркас. |
| **Parnas** — сокрытие информации | Какое решение в дизайне (которое, вероятно, изменится) скрывает этот модуль? | *Причина*, по которой модуль должен быть глубоким. Если он ничего не скрывает, что меняется, глубина косметическая. |
| **Brooks** — существенное против случайного | Устраняет ли это случайную сложность или просто перемещает существенную сложность домена? | Убивает «рефакторинги», которые перемещают беспорядок, не уменьшая его. |
| **Evans** — Domain-Driven Design | Названа ли эта граница на языке домена, а не общими утилитами? | Переименуйте `utils`/`helpers` — назовите границу по инварианту, который действительно есть в этом репозитории. |
| **Fowler** — рефакторинг / запахи | Какой самый маленький безопасный шаг к более глубокой архитектуре? | Превращает «должно быть глубже» в конкретные шаги за проходящими тестами. |
| **Beck** — простой дизайн, тестирование в первую очередь | Доказал ли я текущее поведение перед углублением шва? | Тормоз для преждевременной архитектуры. Сначала сделайте, чтобы работало и было протестировано, затем углубляйте нужный шов. |
| **Hickey** — простой против легкого | Перемешивает ли это несвязанные концепции или это действительно одна концепция? | Мелкий помощник обычно *легкий* (рядом, быстро), а не *простой* (мало перемешанных концепций). Предпочитайте простой. |
| **Metz** — дублирование лучше неправильной абстракции | Защищает ли этот повторяющийся код общий инвариант или просто похож (правило этой программы, расширяющее Metz)? | Metz: дублирование дешевле неправильной абстракции — встроьте неправильную абстракцию обратно, а не гните её. Эта программа расширяет её: **не** централизуйте из-за повторения; централизуйте только если защищаете реальный инвариант. Терпите дублирование, пока инвариант не проявится. |
| **Закон Хайрума** — наблюдаемое поведение | Будут ли вызывающие зависеть от поведения за пределами контракта этого интерфейса? | Аргумент в пользу маленьких, стабильных интерфейсов: каждое наблюдаемое поведение в итоге становится критически важным. |

## Рецепт комбинирования

Применяйте в этом порядке — последующие линзы важны только если предыдущие пройдены:

1. **Metz — ворота допуска.** Заслуживает ли эта граница/абстракция вообще существования? Правило этой программы, расширяющее Metz: извлекайте только если код защищает общее правило — три похожих элемента не являются выявленным инвариантом. Если нет, остановитесь здесь.
2. **Parnas / Ousterhout** — Скрывайте изменчивое решение (область авторизации, правило округления, защиту перехода, правило хранения) за глубоким модулем.
3. **Evans** — Назовите этот модуль на языке домена, а не `utils`.
4. **Beck / Fowler** — Для существующего кода закрепите текущее поведение тестами, затем рефакторьте к нему маленькими безопасными шагами. Для свежесгенерированного кода нет текущего поведения — напишите тест, определяющий желаемое поведение.
5. **Hickey** — Отказывайтесь от интерфейсов, которые смешивают несвязанные концепции только потому, что рабочие процессы похожи.

## Структурный анти-паттерн

**Механический SOLID / Clean Code порождает мелкие модули.** Догматическое чтение —
один класс на ответственность, извлекать каждую функцию, держать всё маленьким —
даёт рой классов с интерфейсами, сложными как их тела. Когда правило говорит «раздели это», спросите, какое *решение* скрывает разделение (Parnas) и скрывает ли оно больше, чем раскрывает (Ousterhout). Если оно ничего не скрывает, что меняется, не разделяйте. Эта защита особенно важна под давлением рефакторинга («почисти это», «этот файл слишком большой») — при спокойном анализе рецензенты уже сопротивляются; в середине рефакторинга с мандатом на видимые изменения рождается рой мелких файлов.

## Распространённые ошибки

- **Разделение только по размеру.** Модуль запроса на 400 строк, скрывающий одно связное решение, может быть глубже, чем четыре модуля по 100 строк, каждый из которых протекает те же соединения.
- **Назвать разделение `helpers`/`utils`.** Если не можете назвать на языке домена (Evans), граница, вероятно, неправильная.
- **Извлечение при втором повторении.** Правило этой программы, расширяющее Metz: ждите инварианта, а не третьего похожего.
- **Углубление до закрепления поведения.** Beck: без теста, доказывающего текущее поведение, «углубляющий» рефакторинг — это переписывание.
- **Считать проходной вызов модулем.** Обёртка, которая просто пересылает аргументы, добавляет интерфейс и ничего не скрывает — по определению мелкая.
- **Путать рифму с инвариантом.** Лучшее доказательство общего инварианта — совместное изменение: копии исправлялись или менялись вместе в истории (одна и та же ошибка исправлена в двух местах). Похожие, меняющиеся независимо, — это рифмы; оставьте их дублированными.
- **Убирать запах, а не устранять его.** Централизация шести кастов в один общий помощник — аккуратная версия той же неясности. Глубокое исправление — назвать границу, которую этот каст маскировал.

## Стоимость для читателя: третий тест

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

- **Находимость?** Одно имя на одну концепцию, написанное одинаково везде, доступное через простой текстовый поиск. Недостатки: имена, собранные из строк, связывание через побочный эффект импорта, цепочки реэкспорта, скрывающие определение, два имени для одной концепции.
- **Может ли читатель остановиться раньше?** Контракт находится в начале файла или над экспортом: что он обещает, что скрывает, что никогда не делает. Недостаток: контракт можно вывести только прочитав тело.
- **Проверяемо машиной?** Точные типы на входе и выходе каждой границы, чтобы проверка типов заменяла чтение вызывающих. Недостатки: `any`, голые словари, булевы флаги, смысл которых живет в теле.
- **Видима ли связность?** Места, которые должны меняться вместе, обеспечены (общий тип, тест, единый источник) или, если нет, помечены в обеих точках. Признак скрытой связности — совместные изменения в истории, о которых ничего не говорится в коде.
- **Без шума?** Нет комментариев, повторяющих код, нет закомментированного кода, нет мертвых веток, нет комментариев истории изменений, нет устаревших путей рядом с их заменой.
- **Предсказуемость?** Макет следует существующему шаблону репозитория; тест там, где читатель его ожидает, и запускается самостоятельно.

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

Для маркеров в коде и карты репозитория используйте `context-audit`, где доступно: его якорь `AIDEV-NOTE:` (один невосстановимый факт плюс ссылка на источник, максимум две строки, на месте) — это соглашение для связности, которую нельзя обеспечить.

## Рефакторинг существующей кодовой базы по этому стандарту

Ретрофит оценивается так же, как новый код; отличается порядок и сдержанность. Большую часть кодовой базы следует оставить без изменений.

1. **Перепись, только для чтения.** Перечислите границы (модули, сервисы, общие помощники). Для каждой записи: скрытое решение или "нет"; размер интерфейса относительно тела; партнеры по совместным изменениям из истории; дефекты, усложняющие чтение. Пока ничего не меняйте.
2. **Ранжируйте по изменчивости, а не по уродству.** Приоритет — как часто код меняется, умноженное на стоимость его чтения. Холодный код, который работает, остается как есть, даже если поверхностный. Важная сложность домена остается на месте (Брукс).
3. **Назначьте одно исправление на каждую проблему:**
   - прослойка или обертка, ничего не скрывающая: удалите её, вызывающие используют то, что она оборачивала;
   - неправильная абстракция, изогнутая флагами и особыми случаями: встроьте обратно (Метц), затем ищите настоящую инварианту;
   - поверхностные сиблинги, разделяющие одно решение: объедините их за одним интерфейсом;
   - утечка решения (вызывающие знают формат, правило, схему): опустите его в модуль, который им владеет;
   - общее имя (`utils`, `helpers`, `manager`): переименуйте по скрытому решению или растворите в вызывающих;
   - не типизированная граница: типизируйте её и замените приведения типов на маппер, который они маскировали;
   - скрытая связность: обеспечьте её или пометьте обе точки;
   - шум: удалите.

   Рифмы, которые меняются независимо, не получают исправления.
4. **Сначала закрепите поведение.** Ни одно исправление не начинается, пока тест не докажет текущее поведение затрагиваемого кода (Бек). Рефакторы сохраняют поведение; изменение поведения — отдельный коммит.
5. **Разбейте работу на единицы, которые может выполнить один агент.** Одна граница на единицу. Каждая единица называет файлы, которыми владеет, контракт, который должна сохранить, и команду, доказывающую это самостоятельно. Ни две параллельные единицы не пишут в один файл; общие файлы (бочки, реестры, таблицы маршрутов) имеют одного владельца или ждут интеграции. Изменения интерфейсов, от которых зависят несколько единиц, вносятся первыми, как отдельная единица.
6. **Измерьте результат.** Выберите репрезентативное изменение до начала и посчитайте файлы и строки, которые читатель должен загрузить для него; посчитайте снова после. Экспортируемые имена и общее число строк должны уменьшаться или оставаться на месте. Рефакторинг, добавляющий интерфейсы, должен иметь заявленную причину.
7. **Остановитесь**, когда останется холодное, важное или рифмованное.

Связанные навыки, где доступны: `repo-review` (тип дизайна) создает перепись как советующий артефакт; `design-cleanup` запускает цикл исправления и повторного сканирования случайной сложности; `context-audit` добавляет якоря и карту кода; `ousterhout-build-deep` — это чеклист автора для агентов, выполняющих единицы.

## Где это применяется

Этот навык — слой обзора и суждения: используйте его, чтобы решить, является ли абстракция глубокой, названа по правильному решению и стоит ли её выделять. `find-shared-code` использует его как тест допуска при поиске в недавней истории кода, достойного совместного использования. В приложении ниже приведено рассуждение каждого автора.

---

## Приложение: Глубокий разбор линз

Режимы сбоев, которые ловит каждый автор, и единственный ход, который он предлагает. Таблица выше — это краткая справка; здесь — обоснование.

### Оустерхоут — Глубокие модули (основа)

*Философия проектирования программного обеспечения.*

- **Глубина** = выгода (скрытая функциональность) ÷ стоимость (сложность интерфейса). Глубокий модуль скрывает много за малым. Поверхностный модуль имеет интерфейс почти такой же сложный, как тело, поэтому он ничего не дает.
- **Сложность** — всё, что затрудняет понимание или изменение системы. Два источника:
  - **Зависимости** — нельзя изменить одну часть, не затронув другую.
  - **Неочевидность** — важная информация не очевидна из кода.
- **Симптомы:** усиление изменений (одно решение — много правок), когнитивная нагрузка (сколько нужно держать в голове), неизвестные неизвестные (нельзя сказать, какой код затронет изменение).
- **Ключевой ход:** тянуть сложность *вниз* — модуль поглощает сложный случай, чтобы вызывающим не пришлось. Параметры конфигурации и прослойки толкают сложность *вверх* к вызывающему; это поверхностность.

Ловит: интерфейсы, которые протекают реализацию; помощники, которые не помогают.

### Парнас — Сокрытие информации (почему глубина важна)

*О критериях, используемых при разбиении систем на модули (1972).*

- Разбивайте вокруг **решений по дизайну, которые, вероятно, изменятся**, а не вокруг шагов вычисления. Каждый модуль скрывает одно такое решение.
- Это прямой предок глубокого модуля. Модуль глубокий *потому что* он скрывает решение, которое в противном случае распространялось бы на вызывающие его части.

Подводные камни: «модуль», который ничего изменчивого не скрывает — его глубина косметическая. Спросите: что меняется за этим интерфейсом, чего вызывающие никогда не видят? Если ответ «ничего», граница — украшение.

### Брукс — Существенная и случайная сложность

*Нет серебряной пули.*

- **Существенная** сложность присуща предметной области (оценка действительно настолько сложна). **Случайная** сложность — это то, что навязывают наши инструменты и структура.
- Удаляема только случайная сложность. Рефакторинг, который «очищает» код, просто перемещая существенную сложность предметной области из одного файла в другой, ничего не сделал.

Подводные камни: перестановки, замаскированные под упрощение. Спросите: снизилась ли общая сложность или она просто сместилась?

### Эванс — Domain-Driven Design

*Domain-Driven Design.*

- Границы должны называться на **вездесущем языке** предметной области, а не общими утилитарными терминами. Модуль с названием `helpers` ничего не называет; модуль `AccessScope` или `PricingPolicy` называет инвариант.
- Ограниченные контексты не позволяют бизнес-инвариантам просачиваться через швы.

Подводные камни: правильное разбиение с бессмысленными именами. Если вы не можете назвать модуль на языке предметной области, вероятно, граница проведена неправильно.

### Фаулер — Рефакторинг и запахи кода

*Рефакторинг.*

- Предоставляет конкретные, безопасные, именованные действия (Extract Function, Move Field, Replace Conditional with Polymorphism), чтобы перейти от текущего дизайна к более глубокому.
- Каждое действие сохраняет поведение и невелико, поэтому остается обратимым.

Подводные камни: разрыв между «это должно быть глубже» и знанием следующего коммита. Оустерхоут задает цель; Фаулер — путь.

### Бек — Простой дизайн, тестирование сначала

*Test-Driven Development; XP.*

- Четыре правила простого дизайна в опубликованном порядке Бека: проходят тесты, нет дублирования, раскрывают намерение, минимальное количество элементов. Эта программа следует более позднему переупорядочиванию Фаулера/Хейнса — намерение перед дублированием — потому что это служит расширяющему инварианту программы по Метцу (см. Метц ниже): не действуйте по дублированию, пока не сможете назвать намерение, которое оно защищает.
- Тестирование сначала — тормоз для преждевременной архитектуры. Сначала заставьте работать и докажите поведение, затем углубляйте шов, который теперь защищают тесты.

Подводные камни: архитектура, построенная до закрепления поведения. Без теста, доказывающего текущее поведение, «углубляющий» рефакторинг — это непроверенный перепис.

### Хики — Просто vs Легко

*Simple Made Easy.*

- **Просто** = не переплетено: одна концепция, не переплетенная с другими (объективно).
- **Легко** = под рукой, знакомо, быстро доступно (относительно вас).
- Эти два понятия независимы. Мелкий помощник обычно *легок* — быстро пишется, рядом — но не *прост*, если он переплетает несвязанные задачи.

Подводные камни: удобство, маскирующееся под дизайн. Предпочитайте конструкции, которые сохраняют концепции непереплетенными, даже если переплетенная быстрее набирается.

### Метц — Предпочитайте дублирование неправильной абстракции

*«Неправильная абстракция» (2016).* 

- Дублирование гораздо дешевле неправильной абстракции. Абстракция, извлеченная слишком рано, заставляет каждого будущего вызывающего обходить предположения, которые никогда не были верны для всех.
- Когда абстракция оказывается неправильной, средство Метца — встроить её обратно и позволить дублированию вернуться, а не подгонять под случай, для которого она не предназначалась.
- **Правило этой программы, расширяющее Метца: не централизуйте из-за повторения кода. Централизуйте, когда это защищает реальный, общий инвариант.** Пока инвариант не проявился, терпите дублирование.

Подводные камни: чрезмерная централизация — мелкий общий помощник, вокруг которого теперь все вынуждены работать. Это противовес механическому «DRY любой ценой».

### Закон Хайрума — Наблюдаемое поведение становится контрактом

*«При достаточном числе пользователей каждое наблюдаемое поведение вашей системы будет кем-то использовано.»*

- Что бы интерфейс ни делал — порядок, время, текст ошибки — кто-то в итоге на это опирается. Поэтому поверхность, которую вы показываете, больше, чем задокументированная.
- Это поддерживает предпочтение Оустерхоута к **маленьким, стабильным интерфейсам**: чем меньше вы показываете, тем меньше может случайно стать критичным.

Подводные камни: широкие интерфейсы, которые окаменеют. Каждое дополнительное наблюдаемое свойство — будущий ограничитель.

### Как они сочетаются

- **Parnas → Ousterhout:** скрыть изменчивое решение → модуль глубокий.
- **Brooks:** подтвердить, что глубина убрала сложность, а не просто переместила.
- **Evans:** назвать границу на языке предметной области.
- **Beck → Fowler:** закрепить поведение, затем рефакторить малыми безопасными шагами.
- **Metz:** сопротивляться централизации, пока инвариант не станет реальным.
- **Hickey:** держать интерфейс одной концепцией.
- **Hyrum:** держать интерфейс маленьким, чтобы он оставался стабильным.

Опасность — смешать Оустерхоута с механическим прочтением SOLID или Clean Code: это порождает множество крошечных классов и функций с мелкими интерфейсами — прямо противоположное глубоким модулям. Оустерхоут с Метцем в качестве противовеса — это противоядие.

Теги

designarchitecturereviewrefactoringousterhout

Полезные материалы

Скачать AgentsRoom

Запускай всех своих ИИ-агентов во всех проектах из одного окна.

БесплатноСкачать AgentsRoom

Приложение-компаньон: следите за агентами на ходу

Используйте Claude, Codex, Antigravity CLI или другого поставщика AI.

Установить расширение
Chrome Web Store

Отправляйте баги и запросы прямо в ваш публичный бэклог.

Мульти-проекты
Мульти-провайдер
Мульти-агенты
Статус онлайн
Diff и коммиты
Мобильное приложение
Live-превью
Команды агентов
Тесты в браузере
Разработка от backlog
Библиотека промптов
Библиотека навыков
Все функции