Преобразование источника C в C++

Как бы вы пошли о преобразовании достаточно большой (>300 КБ), достаточно зрелой базы кода C в C++?

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

Я имею в виду общую стратегию:

  1. Скомпилируйте все в C++ подмножество C и получите это работающим.
  2. Преобразуйте модули в огромные классы, чтобы все перекрестные ссылки были ограничены именем класса, но все функции и данные оставались бы статическими членами, и это работает.
  3. Преобразуйте огромные классы в экземпляры с соответствующими конструкторами и инициализированными перекрестными ссылками; при необходимости замените статический доступ к элементу косвенным доступом; и заставить это работать.
  4. Теперь подойдите к проекту как к нецензурному OO-приложению и напишите модульные тесты, в которых можно отслеживать зависимости, и разложите на отдельные классы, где их нет; цель здесь - переходить от одной рабочей программы к другой при каждом преобразовании.

Очевидно, что это будет довольно много работы. Существуют ли какие-либо тематические исследования / военные истории о таком переводе? Альтернативные стратегии? Другие полезные советы?

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

Примечание 2: источнику почти 20 лет, и, возможно, 30% оттока кода (измененные строки + добавленные / предыдущие строки) в год. Другими словами, он сильно поддерживается и расширяется. Таким образом, одной из целей будет повышение управляемости.

[Ради вопроса, предположим, что перевод на C++ является обязательным, и что оставить его на C не вариант. Смысл добавления этого условия состоит в том, чтобы отсеять ответы "оставь это в C".]

11 ответов

Решение

Только что начав примерно то же самое несколько месяцев назад (в 10-летнем коммерческом проекте, изначально написанном на "C++, это всего лишь C с умным struct"Философия", я бы предложил использовать ту же стратегию, которую вы использовали бы, чтобы съесть слона: принимать по одному кусочку за раз.:-)

Насколько это возможно, разбейте его на этапы, которые можно сделать с минимальными эффектами на другие части. Создание фасадной системы, как предположил Федерико Рампони, является хорошим началом - как только все имеет фасад C++ и обменивается данными через него, вы можете изменить внутренние компоненты модулей с достаточной уверенностью, что они не могут повлиять на что-либо вне их.

У нас уже была частичная система интерфейса C++ (из-за предыдущих небольших усилий по рефакторингу), поэтому в нашем случае этот подход не был сложным. После того, как все мы связались как объекты C++ (что заняло несколько недель, работая над совершенно отдельной веткой исходного кода и интегрируя все изменения в основную ветку, как они были утверждены), очень редко мы не могли скомпилировать полностью рабочая версия, прежде чем мы уехали на день.

Переключение еще не завершено - мы сделали паузу два раза для промежуточных выпусков (мы нацелены на выпуск точных выпусков каждые несколько недель), но это идет полным ходом, и ни один клиент не жаловался на какие-либо проблемы. Наши сотрудники отдела контроля качества нашли только одну проблему, которую я тоже помню.:-)

Как насчет:

  1. Компилируем все в C++ подмножество C и работаем, и
  2. Реализуете набор фасадов, оставляя код C неизменным?

Почему "перевод на C++ обязателен"? Вы можете обернуть C-код без необходимости конвертировать его в огромные классы и так далее.

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

Основная идея состоит в том, чтобы назначить группы данных как классы, а затем получить инструмент для рефакторинга кода, чтобы переместить эти данные в классы, переместить функции только с этими данными в эти классы и пересмотреть все обращения к этим данным для вызова классов,

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

Инструментом, способным выполнить эту задачу, является наш набор инструментов для реинжиниринга программного обеспечения DMS. DMS имеет сильные C-парсеры для чтения вашего кода, захватывает C-код как деревья абстрактного синтаксиса компилятора (и в отличие от обычного компилятора) может вычислять анализ потока по всему вашему SLOC 300K. DMS имеет внешний интерфейс C++, который можно использовать в качестве "внутреннего" конца; каждый пишет преобразования, которые отображают синтаксис C в синтаксис C++.

Основная задача реинжиниринга C++ в большой системе авионики дает некоторое представление о том, на что похоже использование DMS для этого вида деятельности. См. Технические документы по адресу www.semdesigns.com/Products/DMS/DMSToolkit.html, в частности о реинжиниринге моделей компонентов C++ с помощью автоматического преобразования программы.

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

Да, я связан с компанией, будучи ее главным архитектором.

Я написал бы классы C++ через интерфейс C. Не прикасайтесь к коду C уменьшит вероятность путаницы и значительно ускорит процесс.

Как только у вас есть интерфейс C++; тогда это тривиальная задача копирования + вставки кода в ваши классы. Как вы упомянули - на этом этапе очень важно выполнить модульное тестирование.

GCC в настоящее время находится в середине перехода к C++ из C. Они начали с того, что переместили все в общее подмножество C и C++, очевидно. Сделав это, они добавили предупреждения в GCC за все, что они нашли в -Wc++-compat, Это должно помочь вам в первой части вашего путешествия.

Что касается последних частей, когда у вас все будет компилироваться с помощью компилятора C++, я бы сосредоточился на замене вещей, которые имеют идиоматические аналоги C++. Например, если вы используете списки, карты, наборы, битовые векторы, хеш-таблицы и т. Д., Которые определены с помощью макросов C, вы, вероятно, получите много, переместив их в C++. Аналогично с OO вы, вероятно, найдете преимущества, когда вы уже используете идиому C OO (например, структурное наследование) и где C++ обеспечит большую ясность и лучшую проверку типов в вашем коде.

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

Давайте бросим еще одну глупую идею:

  1. Скомпилируйте все в C++ подмножество C и получите это работающим.
  2. Начните с модуля, преобразуйте его в огромный класс, затем в экземпляр и создайте интерфейс C (идентичный тому, с которого вы начали) из этого экземпляра. Пусть оставшийся C-код работает с этим C-интерфейсом.
  3. Выполните рефакторинг по мере необходимости, увеличивая подсистему ОО из кода C по одному модулю за раз, и отбрасывайте части интерфейса C, когда они становятся бесполезными.

Вероятно, необходимо учитывать две вещи, помимо того, как вы хотите начать, на том, на чем вы хотите сосредоточиться, и на чем вы хотите остановиться.

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

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

Программное обеспечение, над которым я работаю, представляет собой огромную, старую базу кода, которая была "преобразована" из C в C++ много лет назад. Я думаю, что это потому, что графический интерфейс был преобразован в Qt. Даже сейчас это все еще в основном похоже на C-программу с классами. Разбиение зависимостей, вызванных открытыми членами данных, и рефакторинг огромных классов с помощью процедурных методов-монстров на более мелкие методы и классы никогда не происходили, я думаю по следующим причинам:

  1. Нет необходимости менять работающий код, который не нуждается в улучшении. Это приводит к появлению новых ошибок без добавления функциональности, и конечные пользователи не ценят это;
  2. Надежность рефакторинга сделать очень и очень сложно. Многие фрагменты кода настолько велики, а также настолько важны, что люди едва ли осмеливаются прикасаться к нему. У нас достаточно обширный набор функциональных тестов, но получить достаточную информацию о покрытии кода сложно. В результате трудно установить, имеется ли уже достаточно тестов для выявления проблем во время рефакторинга;
  3. Окупаемость инвестиций сложно установить. Конечный пользователь не получит выгоды от рефакторинга, поэтому он должен быть в сниженных затратах на обслуживание, которые первоначально возрастут, потому что при рефакторинге вы вводите новые ошибки в зрелый, то есть практически без ошибок код. И сам рефакторинг тоже будет дорогостоящим...

NB. Я полагаю, вы знаете книгу "Эффективная работа с устаревшим кодом"?

Вы упоминаете, что ваш инструмент - это компилятор, и что: "На самом деле, сопоставление с образцом, а не просто сопоставление типов, в множественной диспетчеризации было бы еще лучше".

Возможно, вы захотите взглянуть на maketea. Он обеспечивает сопоставление с образцом для AST, а также определение AST из абстрактной грамматики, а также посетителей, трансформеров и т. Д.

Если у вас небольшой или академический проект (скажем, менее 10000 строк), переписывание, вероятно, ваш лучший вариант. Вы можете учесть это как хотите, и это не займет слишком много времени.

Если у вас есть реальное приложение, я бы посоветовал его скомпилировать как C++ (что обычно подразумевает в основном исправление прототипов функций и т. П.), А затем поработать над рефакторингом и переносом ОО. Конечно, я не согласен с философией, что код должен быть ОО структурирован, чтобы быть приемлемым кодом C++. Я бы сделал пошаговое преобразование, переписывание и рефакторинг по мере необходимости (для функциональности или для включения модульного тестирования).

Вот что я бы сделал:

  • Поскольку коду уже 20 лет, откажитесь от анализатора синтаксического анализатора и замените его одним из более новых, основанных на lex/yacc/bison(или чем-либо подобным) и т. Д. Кодом C++, гораздо более понятным и простым для понимания. Быстрее развиваться, если у вас есть BNF под рукой.
  • Как только это будет установлено в старый код, начните упаковывать модули в классы. Замените глобальные / общие переменные интерфейсами.
  • Теперь то, что у вас будет, будет компилятором в C++ (хотя и не совсем).
  • Нарисуйте диаграмму классов всех классов в вашей системе и посмотрите, как они общаются.
  • Нарисуйте еще один, используя те же классы, и посмотрите, как они должны общаться.
  • Рефакторинг кода для преобразования первой диаграммы во вторую. (это может быть грязно и сложно)
  • Не забудьте использовать код C++ для всего нового добавленного кода.
  • Если у вас есть время, попробуйте заменить структуры данных одну за другой, чтобы использовать более стандартизированный STL или Boost.
Другие вопросы по тегам