Преобразование источника C в C ++ - PullRequest
41 голосов
/ 14 октября 2008

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

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

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

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

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

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

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

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

Ответы [ 11 ]

15 голосов
/ 14 октября 2008

Я только что начал примерно то же самое несколько месяцев назад (в десятилетнем коммерческом проекте, изначально написанном на «C ++ - не что иное, как C с умной struct s» философией), я бы предложил используя ту же стратегию, которую вы использовали бы, чтобы съесть слона: принимайте его по одному кусочку за раз. : -)

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

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

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

11 голосов
/ 14 октября 2008

А как же:

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

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

7 голосов
/ 14 июня 2009

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

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

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

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

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

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

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

5 голосов
/ 14 октября 2008

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

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

4 голосов
/ 29 августа 2009

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

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

3 голосов
/ 14 октября 2008

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

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

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

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

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

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

3 голосов
/ 14 октября 2008

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

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

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

2 голосов
/ 29 августа 2009

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

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

1 голос
/ 14 октября 2008

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

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