Обложка статьи «FAQ: Почему стандарт C++ поставляется каждые три года?»

FAQ: Почему стандарт C++ поставляется каждые три года?

Перевод статьи «Draft FAQ: Why does the C++ standard ship every three years?»

Херб Саттер, архитектор программного обеспечения в Microsoft и председатель комитета по стандартам ISO C++, рассказал, что команда часто получает от новых членов комитета вопросы о том, почему они так строго следуют графику выпуска. Чтобы прояснить ситуацию и рассказать, что лежит в основе такой точности, он добавил раздел часто задаваемых вопросов (FAQ) к проекту P1000. Но эта информация может быть интересна не только членам комитета, поэтому мы перевели её для вас.

***

Вопросы и ответы

Перед встречей в Кёльне, июль 2019 г.

В стандарте есть ошибки, так может мы должны отложить релиз C++20?

Конечно же нет.

Мы идём по намеченному курсу с определённой скоростью. Исправление ошибок является целью этого последнего года. Именно поэтому этот график устанавливает крайний срок заморозки включения новой функциональности для C++20 на начало 2019, чтобы оставить год для исправления ошибок. В том числе чтобы успеть принять международные комментарии к проекту этим летом. У нас есть время до начала 2020 года (три встречи: Кёльн, Белфаст и Прага), чтобы учесть все замечания из обратной связи и любые другие решения проблем и исправления ошибок.

Если бы у нас была хотя бы ещё одно собрание или два, мы могли бы добавить функциональность, которая почти готова, так может следует отложить C++20?

Конечно же нет.

Просто подождите ещё пару встреч (после Праги) и C++23 будет открыт для бизнеса. Определённая функциональность может быть первой, за что проголосуют в рабочем проекте C++23. Например, так было с концептами. Они были не совсем готовы для переноса из технической спецификации (TS) прямо в C++17. Поэтому они были включены в следующий проект — C++20 — на первом же заседании в Торонто. Это оставляло достаточно времени для доработки и принятия оставшейся противоречивой части технической спецификации, которая требовала немного больше времени для подготовки и была принята в следующем году в Сан-Диего. Теперь у нас есть всё это в сборе.

Эти правила кажутся слишком строгими. Почему мы поставляем релизы международного стандарта (IS) через фиксированные промежутки времени (3 года)?

Потому что это один из двух основных вариантов управления проектами для выпуска C++ IS. И опыт показал, что он лучше, чем другой вариант.

Что за два варианта управления проектом для выпуска C++ IS?

Существует два основных подхода к конечному релизу: выбираем функциональность или выбираем время выпуска. И в зависимости от вашего выбора вы отказываетесь от контроля над другим направлением. Невозможно управлять обоими аспектами одновременно. Всё это можно суммировать следующим образом:

Рассмотрим подробнее.

(1) «Что»: выбираем функции и поставляем их в релиз, когда они будут готовы. При этом нельзя выбрать время выхода релиза.

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

Такой была модель для C++98 (предполагалось, что он выйдет примерно в 1994 году, Бьёрн изначально говорил, если она не будет выпущена в запланированное время, это будет провал) и для C++11 (имел название версии в формате «0x», т. к. ожидалось, что «x» будет около семи). Эта модель заставляла клиентов ждать неопределённое время и приводила к задержке интеграционного тестирования и выпуска. Это в свою очередь привело к большой неопределённости на рынке и вопросам, когда комитет выпустит следующий стандарт (даже среди сообщества разработчиков и внутри комитета были серьёзные сомнения как в 1996, так и в 2009 году). Большинство компиляторов реализовывали стандарт в течение нескольких лет, потому что никто не знал, сколько ещё несовместимых изменений внесёт комитет, работая над выпуском. Это привело к широкой вариации и фрагментации в поддержке компиляторов С++, доступных для сообщества.

Почему мы так делали? Были ли мы глупы? Не совсем, скорее просто неопытны и «оптимистичны». Опора на функиональность — это дорога, проложенная с лучшими намерениями. В 1994–1996 годах (как и в 2007–2009) мы действительно верили, что пропустив очередные 1–3 собрания, мы бы успели к выпуску. Каждый раз это заканчивалось тем, что мы задерживали выход стандарта до четырёх лет. Мы с трудом поняли, что на самом деле нет такой вещи, как задержка на год или даже на два.

К счастью, это изменилось с подходом, основанном на времени выпуска.

(2) «Когда»: выбираем время выпуска и поставляем готовые на данный момент функции. При этом нельзя выбрать функциональность для релиза.

Если функциональность в черновом стандарте требует больше времени для подготовки, вы выдёргиваете его из релиза и поставляете готовое. Вы по-прежнему можете работать с большой и сложной функциональностью, которая требует времени разработки аж на несколько релизов. Просто эта работа выполняется «в отдельной ветке» (отдельно от основного стандарта). После происходит объединение с главным стандартом. При этом разработка каждой функции удобно отделена от основного проекта, пока она не будет готова.

Эта модель работает с 2012 года и мы не хотим возвращать всё назад. Она приводит к поддержанию более высокого качества путём принудительной регулярной интеграции. Работа с черновым IS не пересекается до тех пор, пока он не достигнет приличного уровня стабильности. Это также создаёт предсказуемый цикл поставок, который можно рассчитывать и планировать. В течение этого времени компиляторы получали соответствующие реализации всё раньше и раньше после каждого стандарта (чего никогда не было). В 2020 году мы ожидаем несколько полностью готовых реализаций, выпущенных в том же году, что и заявлено (этого тоже никогда не было раньше). Это польза для всего рынка — для разработчиков, пользователей, преподавателей, всех.

С момента перехода к подходу с фиксированным временем выпуска мы также поставляем больше выполненной работы с более высоким качеством. Это измеряется резким сокращением отчётов о дефектах и ​​комментариями к черновикам каждого стандарта (если что-то не так, откладывается только это).

Насколько мы серьёзны в своём нынешнем подходе? Что, если бы функциональность видного члена комитета была бы «почти готова», подождали бы мы тогда или нет?

Очень серьёзны. И нет, не подождали бы.

У нас есть исторические данные: в Джексонвилле в 2016 году на окончательном собрании по C++17 Бьёрн Страуструп выступил на пленарном заседании с просьбой включить концепты в C++17. Когда не удалось достичь консенсуса, у Страуструпа прямо спросили — не хотел бы он отложить выход C++17 на год, чтобы включить концепты в релиз. Страуструп сказал «нет» без каких-либо колебаний. Он также добавил, что C++17 без концептов важнее, чем C++18 (или даже 19) с ними. Это несмотря на то, что Страуструп работал над ними около 15 лет. Тогда реальный выбор был между поставкой C++17 без концептов и затем C++20 с концептами (подход 2, что мы и сделали) и между переименованием C++17 в 20 (подход 1), что является тем же самым за исключением пропуска выхода C++17 и отсутствием поставки того, что уже было готово для C++17.

Как насчёт чего-то среднего между подходом (1) и подходом (2)? Например, в основном использовать (2), но с «небольшой» гибкостью графика, чтобы занять немного дополнительного времени, когда нужно стабилизировать функцию?

Нет, потому что это уже будет подход (1).

Представьте, что мы отложили выпуск C++20. Реальность такова, что мы бы переключились с подхода (2) на подход (1). Если отложить C++20 для большей «подгонки», стандарт будет задержан как минимум на два года. Не существует такой вещи, как отсрочка на одно-три собрания. В течение этого времени другие люди будут продолжать (справедливо) говорить: «Моей функциональности нужно лишь ещё немного времени. Так как мы пропускаем собрание, давайте добавим и его тоже». И как только мы пропускаем не менее двух лет, C++20 становится C++22 (а то и 23). Но мы уже собираемся выпустить C++23. Он будет поставлен в любом случае и единственное отличие состоит в том, что мы пока не будем поставлять C++20 с большим количеством полностью готовой функциональности и мир будет ждать ещё три года. Задержка не принесёт пользу той завершённой функциональности, которая уже есть.

Таким образом, суть предложения следующая: «Давайте из C++20 сделаем C++22 или 23». Ответ на это прост: «Да, у нас будет и C++23 тоже, но в дополнение к C++20, а не вместо него». Задержка C++20 на самом деле означает пропуск его выхода вместо того, чтобы выпустить хорошую работу, которая стабильна и уже готова.

Но X не работает / требует больше времени на доработку и исправление ошибок!

Нет проблем. Мы можем просто убрать его.

В этом случае составляется документ (нацеленный на EWG или LEWG в зависимости от ситуации), в котором обрисована проблема и предлагается удалить её из рабочего проекта IS. Группы принимают это во внимание. И если они решат, что функция не работает (и пленум даст согласие), она будет отложена до следующего выпуска. На самом деле мы делали это и раньше с концептами в C++0x.

Но согласно подходу (1) мы бы задержали не только эту функциональность, но и весь набор C++20 до выпуска C++23. Это было бы слишком.

Означает ли подход (2) наличие «больших/малых» релизов?

Нет. Подход (2) означает, что вы не можете выбирать набор функций, даже при «большой/малой» детализации.

Модель (2) означает просто «поставляем то, что готово». Это приводит к выпускам, которые:

  • Примерно одинакового (обычного среднего) размера для «меньших» функций, потому что они занимают короткие сроки (менее 3 лет каждый). Поэтому в целом мы видим схожие цифры в каждом релизе.
  • Переменного размера для «больших» функций, которые занимают больше времени (более 3 лет каждый). Поэтому каждый релиз IS получает то, что готово к этому времени. Соответственно, некоторые выпуски могут быть больше других.

Таким образом, C++14 и C++17 были относительно небольшими, потому что большая часть работы с по стандартизации проводилась в долгосрочной перспективе с функциональностью, которая пока жила на бумагах (например контракты) и в отдельной ветке от основной технической спецификации (например концепты).

C++20 большой релиз…

Да. В C++20 много крупных нововведений. Три из самых больших начинаются с букв «co»: концепты (concepts), контракты (contracts), корутины (coroutines). Поэтому, мы могли бы назвать его co_cpp20. Или co_dependent (взаимозависимый). Но мы отвлеклись.

… так не втиснем ли мы слишком много в трёхлетний цикл для C++20?

Нет (см. пункт с переменным размером).

C++20 большой, но не потому, что мы проделали больше работы за эти три года. А потому, что многие долгосрочные элементы (включая как минимум два, над которыми работали в их нынешнем виде с 2012 года) оказались готовы для слияния с основным проектом IS в одном и том же цикле выпуска.

Крупная функциональность всегда занимает много лет. Основное различие между подходом (1) для C++98 или C++11 и подходом (2) теперь таково: в C++98 и C++11 мы придерживали выпуск стандарта, пока они все не были готовы. Теперь мы поставляем большие нововведения вместе с остальной готовой функциональномтью, вместо того, чтобы блуждать в потёмках.

C++20 — это тот же трёхлетний цикл, что и C++14 и C++17. Дело не в том, что было сделано больше за эти 3 года, чем за предыдущие два цикла. Дело в том, что более крупная часть функциональности стала готова к поставке. И если что-то не готово, можно просто убрать это из поставки и доработать в C++23. И если всё так, то нужно ещё объяснить нововведение в документе, который поясняет, как и почему его надо использовать. Это предполагает задержку выхода функции в релиз.

Я думаю, что правильный подход к этому состоит в том, что C++14+17+20 в целом — это наш третий 9-летний цикл (2011–2020 гг.) после C++98 (1989–1998 гг.) и C++11 (2002–2011 гг.). Но поскольку мы придерживались подхода (2), мы также поставляли нововведения, которые были готовы через 3 и 6 лет после начала цикла.

Не лучше ли выявлять ошибки во время разработки продукта, а не когда он был уже выпущен для клиентов?

Конечно.

Но если мы говорим об этом как о причине задержки стандарта, вопрос подразумевает два ложных предположения:

  • (a) — функциональность не была выпущена и использовалась до выпуска (многие нововведения уже имеют опыт использования в производственной среде);
  • (b) — все новые функции могут использоваться вместе, прежде чем стандарт будет поставлен (на самом деле не могут).

Рассмотрим подробнее.

Касательно (a): Большинство основных функций C++20 были реализованы в их нынешнем проектном виде, по крайней мере, в одном поставляющемся компиляторе. И в большинстве случаев они фактически используются в производственном коде (т. е. уже выпущены для клиентов, которые ими довольны). Например, корутины (принятые только пять месяцев назад) использовались в продакшне MSVC уже в течение двух лет и в Clang по меньшей мере в течение года (с очень довольными клиентами).

Касательно (b): Реальность такова, что мы не собираемся находить множество проблем взаимодействия функций, пока пользователи не будут использовать их в продакшне (другими словами до поставки стандарта), потому что разработчики будут ждать пока большинство стандартных поставок не будет реализовано. Спросите команду «любимого компилятора», что происходит при «реализации крупной функции до публикации в стандарте». В ряде случаев им приходилось реализовывать её более одного раза, как и обламывать клиентов — больше одного раза. Поэтому для исполнителей разумно дождаться поставки от комитета.

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

Стандарт никогда не бывает идеальным, разве мы не поставляем ошибки?

Да, поставляем.

Если есть функциональность, которая ещё не готова, мы должны её исключить.

Если есть функциональность, которая может быть улучшена, и мы знаем, что изменение может быть реализовано обратно совместимым способом, это не причина, чтобы не отправлять его сейчас. Это может быть реализовано как расширение в следующем стандарте C++.

Мы намеренно поставляем функции, которые планируем улучшить. Это всё при условии, что у нас есть уверенность, что мы можем сделать это обратно совместимым способом.

Но разве мы не должны стремиться минимизировать ошибки поставки?

Да. Мы стремимся к этому.

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

Уверены ли мы, что наше качество сейчас лучше, чем при подходе (1)?

Да, уверены.

По объективным метрикам, в частности по объёму комментариев национальных органов и сообщениям о дефектах, C++14 и C++17 были нашими самыми стабильными выпусками за всё время. Каждый из них примерно в 3–4 раза лучше по этим показателям, чем C++98 или C++11. Причина этого в регулярных поставках. Сначала мы помещаем крупную функциональность в отдельные ветки технической спецификации (включая полную формулировку их интеграции с главным стандартом), а затем объединяем их, когда уверены, что они более-менее готовы.

Фактически, с 2012 года основной стандарт всегда поддерживался в состоянии, близком к готовому для поставки (даже наши черновые проекты имеют как минимум такое же высокое качество, как и поставляемые стандарты C++98 и C++11). До 2012 года такого не происходило. Теперь мы знаем, что можем выполнять график с высоким качеством, потому что всегда находимся в состоянии близком к готовому для поставки. Учитывая, что C++98 и C++11 были успешными, признание того, что мы сейчас находимся на более высоком уровне качества, означает, что мы работаем в правильном направлении.

C++98 и C++11 заняли около 9 лет и были довольно хорошими продуктами…

Да: 1989–1998 годы и 2002–2011 годы.

…а C++14 и C++17 были меньшими релизами, значит C++20 — крупный релиз?

Я думаю, что правильным сопоставлением будет C++14+17+20 в целом. Это наш третий 9-летний цикл. Но, учитывая подход (2), мы также поставили функциональность, которая была готова через 3 и 6 лет после начала цикла.

Позволяет ли подход (2) ставить объектно-ориентированные цели, как P0592 для C++next?

Конечно, пока он не содержит слов типа «должны включать эту функциональность», иначе это будет подход (1).

Стремление к одним определённым функциям и предоставление им приоритета над другими — это хорошо, но это уже вопрос расстановки приоритетов. Мы по-прежнему будем поставлять только то, что готово. Но мы определённо можем расставлять приоритеты, над чем работать надо в первую очередь. Поэтому у такой функциональности больше шансов быть готовой как можно скорее.