Как обновить старый код C? [закрытый]

36
задан Bhargav Rao 1 July 2018 в 09:02
поделиться

11 ответов

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

Я делал это много раз, то есть преобразовывал старый код на Си в Си++. Выгоды могут вас удивить. Конечный код может быть вдвое меньше исходного, когда вы закончите работу, и гораздо проще для чтения. Кроме того, вы, скорее всего, обнаружите хитрые ошибки языка Си. Вот шаги, которые я бы предпринял в вашем случае. Маленькие шаги важны, потому что при рефакторинге большого объема кода вы не можете сразу перейти от А к Я. Вы должны пройти через небольшие промежуточные шаги, которые, возможно, никогда не будут развернуты, но которые могут быть проверены и помечены в любом RCS, который вы используете.

  1. Создайте набор регрессий/тестов. Вы будете запускать набор тестов каждый раз, когда завершаете пакет изменений в коде. У вас уже должен быть такой набор, и он будет полезен не только для этой задачи рефакторинга. Потратьте время, чтобы сделать его всеобъемлющим. Упражнение по созданию набора тестов поможет вам ознакомиться с кодом.
  2. Сделайте ветвление проекта в выбранной вами системе контроля ревизий. Вооружившись набором тестов и веткой игровой площадки, вы сможете вносить большие изменения в код. Вы не будете бояться разбить несколько яиц.
  3. Сделайте эти поля struct приватными. Этот шаг требует совсем немного изменений в коде, но может принести большую отдачу. Действуйте по одному полю за раз. Попробуйте сделать каждое поле приватным (да, или защищенным), затем изолируйте код, который обращается к этому полю. Самым простым и неинтрузивным преобразованием будет сделать этот код дружественной функцией. Рассмотрите также возможность превращения этого кода в метод. Преобразовать код в метод просто, но вам придется также преобразовать все сайты вызовов. Один вариант не обязательно лучше другого.
  4. Сузьте параметры каждой функции. Маловероятно, что какой-либо функции требуется доступ ко всем 30 полям структуры, переданной в качестве аргумента. Вместо того чтобы передавать всю структуру, передайте только необходимые компоненты. Если функция действительно требует доступа ко многим различным полям struct, то она может быть хорошим кандидатом для преобразования в метод экземпляра.
  5. Конст-ифицируйте как можно больше переменных, параметров и методов. Во многих старых кодах на языке Си не используется const. Пробираясь снизу вверх (снизу графа вызовов, то есть), вы добавите в код более сильные гарантии и сможете отличить мутаторов от не-мутаторов.
  6. Замените указатели ссылками там, где это целесообразно. Цель этого шага не имеет ничего общего с тем, чтобы стать более C++-подобным только ради того, чтобы стать более C++-подобным. Цель - определить параметры, которые никогда не являются NULL и которые никогда не могут быть переназначены. Думайте о ссылке как об утверждении во время компиляции, которое говорит: это псевдоним допустимого объекта и представляет тот же объект во всей текущей области видимости.
  7. Замените char* на std::string. Этот шаг должен быть очевидным. Вы можете значительно сократить количество строк кода. Кроме того, забавно заменить 10 строк кода одной строкой. Иногда вы можете устранить целые функции, целью которых было выполнение операций со строками на языке C, которые являются стандартными в C++.
  8. Преобразуйте массивы C в std::vector или std::array. Опять же, этот шаг должен быть очевидным. Это преобразование намного проще, чем преобразование из char в std::string, потому что интерфейсы std::vector и std::array разработаны в соответствии с синтаксисом массивов языка Си. Одним из преимуществ является то, что вы можете избавиться от лишней переменной length, передаваемой в каждую функцию вместе с массивом.
  9. Convert malloc/free to new/delete. Основная цель этого шага - подготовиться к будущему рефакторингу. Простое изменение кода на Си с malloc на new не принесет вам большой пользы. Это преобразование позволяет добавить конструкторы и деструкторы к этим структурам, а также использовать встроенные в C++ автоматические средства работы с памятью.
  10. Замените локализацию операций new/delete на семейство std::auto_ptr. Цель этого шага - сделать ваш код безопасным для исключений.
  11. Бросайте исключения везде, где коды возврата обрабатываются путем их объединения. Если код на C обрабатывает ошибки, проверяя специальные коды ошибок, затем возвращая код ошибки вызывающей стороне, и так далее, код ошибки по цепочке вызовов, то этот код на языке Си, вероятно, является кандидатом на использование исключений. Это преобразование на самом деле тривиально. Просто бросьте код возврата (C++ позволяет бросать любой тип, который вы хотите) на самом низком уровне. Вставьте оператор try{} catch(){} в то место в коде, которое обрабатывает ошибку. Если подходящего места для обработки ошибки нет, подумайте о том, чтобы завернуть тело main() в оператор try{} catch(){} и записать его в журнал.

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

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

Последний шаг.

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

35
ответ дан 27 November 2019 в 05:17
поделиться

Очень маловероятно, что это упражнение что-то даст. Хороший код на C уже более модульный, чем обычно бывает в C++ - использование указателей на структуры позволяет компилируемым единицам быть независимыми так же, как pImpl в C++ - в C вам не нужно раскрывать данные внутри структуры, чтобы раскрыть ее интерфейс. Таким образом, если вы превратите каждую функцию C

// Foo.h
typedef struct Foo_s Foo;
int foo_wizz (const Foo* foo, ... );

в класс C++ с помощью

// Foo.hxx
class Foo {
    // struct Foo members copied from Foo.c
    int wizz (... ) const;
};

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

Есть много вещей, которые дают классы в C++, но модульность - не одна из них.

Спросите своего начальника, какие бизнес-цели достигаются этим упражнением.

Примечание по терминологии:

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

Для обоих языков интерфейсом модуля по традиции является заголовочный файл. Рассмотрим string.h и string как определяющие интерфейсы для простых модулей обработки строк в C и C++. Если в реализации string.h есть ошибка, то устанавливается новый модуль libc.so. Этот новый модуль имеет тот же интерфейс, и все, что динамически связано с ним, немедленно получает преимущества новой реализации. И наоборот, если в std::string есть ошибка в обработке строк, то каждый проект, использующий его, должен быть перестроен. C++ вносит в системы очень большую степень связанности, которую язык никак не смягчает - на самом деле, лучшие применения C++, полностью использующие его возможности, часто гораздо более тесно связаны, чем эквивалентный код на C.

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

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

Так что будьте очень осторожны с тем, что ваш менеджер подразумевает под "модулизацией".

Если каждый файл уже имеет "свое собственное назначение и функцию" и "каждой функции в программе передается указатель на одну из структур", то единственным отличием от классов будет замена указателя на структуру неявным указателем this. Это никак не повлияет на степень модульности системы, более того (если структура определена только в Си-файле, а не в заголовке), это уменьшит модульность.

4
ответ дан 27 November 2019 в 05:17
поделиться

На самом деле 7000 строк кода - это немного. Для такого небольшого количества кода может потребоваться полная переписывание. Но как будет называться этот код? Предположительно, вызывающие абоненты ожидают C API? Или это не библиотека?

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

20
ответ дан 27 November 2019 в 05:17
поделиться

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

19
ответ дан 27 November 2019 в 05:17
поделиться

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

Как уже предположил @David, книга по рефакторингу просто необходима.

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

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

4
ответ дан 27 November 2019 в 05:17
поделиться

Во-первых, скажите своему боссу, что вы не продолжите, пока не получите:

http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672

и менее степень:

http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

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

Это сводится к внесению небольших изменений (извлечение метода, перемещение метода в класс и т. Д.) И последующему тестированию - здесь нет коротких путей.

Но я чувствую вашу боль ...

12
ответ дан 27 November 2019 в 05:17
поделиться

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

Я вижу два кошмарных сценария:

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

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

Выделение класса, который будет владеть этой логикой и ее данными, может быть полезным. Вопрос о том, лучше ли это делать на C или C ++, остается открытым.(Циник во мне говорит: «Я программист на C, отличный C ++ - шанс узнать что-то новое!»)

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

5
ответ дан 27 November 2019 в 05:17
поделиться

Попытайтесь упростить код, прежде чем менять его на C ++. В основном, хотя я думаю, что он просто хочет, чтобы вы преобразовали функции в методы класса и преобразовали структуры в члены данных класса (если они не содержат указателей на функции, если они есть, то преобразовывайте их в фактические методы). Можете ли вы связаться с исходным кодировщиком (ами) этой программы? Они могут помочь вам разобраться, но в основном я буду искать тот фрагмент кода, который является «двигателем» всего этого, и на его основе создавать новое программное обеспечение.Кроме того, мой босс сказал мне, что иногда лучше просто переписать все это целиком, но существующая программа является очень хорошим ориентиром для имитации поведения во время выполнения. Конечно, специализированные алгоритмы сложно перекодировать. Могу вас заверить в одном: если этот код не самый лучший, то позже у вас будет много проблем. Я бы подошел к вашему боссу и продвигал тот факт, что вам нужно переделывать с нуля части программы. Я только что был там и очень рад, что мой руководитель дал мне возможность переписывать. Теперь версия 2.0 на световые годы опережает исходную версию.

2
ответ дан 27 November 2019 в 05:17
поделиться

Я прочитал эту статью под названием "Сделайте плохой код хорошим" от http://www.javaworld.com/javaworld/jw-03-2001/jw-0323-badcode.html?page=7 . Она адресована пользователям Java, но все ее идеи вполне применимы к вашему случаю, я думаю. Хотя по названию кажется, что она предназначена только для плохого кода, я думаю, что статья предназначена для инженеров по обслуживанию в целом.

Если резюмировать идеи доктора Фаррелла, он говорит:

  1. Начните с простых вещей.
  2. Исправьте комментарии
  3. Исправьте форматирование
  4. Следуйте соглашениям проекта
  5. Пишите автоматизированные тесты
  6. Разбейте большие файлы/функции
  7. Перепишите код, который вы не понимаете

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

Удачи!

2
ответ дан 27 November 2019 в 05:17
поделиться

Я думаю, что лучшим подходом было бы полностью переписать код, но вы должны спросить своего босса, с какой целью он хочет, чтобы вы « начали помещать старый код C в классы c ++ ». Уточняйте подробности

5
ответ дан 27 November 2019 в 05:17
поделиться

С «всего» 7000 строк кода на C, вероятно, будет проще переписать код с нуля, даже не пытаясь понять текущий код.

И нет автоматизированного способа сделать или даже помочь модульности и рефакторингу, которые вы предполагаете.

7000 LOC может звучать как многое, но многое из этого будет шаблонным.

3
ответ дан 27 November 2019 в 05:17
поделиться