Подтвердить что ты не робот

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

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

Будучи хорошим работником, я кивнул головой, да, и вернулся к моему столу, где я сижу сейчас, задаваясь вопросом, как в мире взять этот код и "модулизовать" его. Это уже в 20 исходных файлах, каждый со своей целью и функцией. Кроме того, существуют три "основные" структуры. каждая из этих структур имеет 30 плюс поля, многие из которых являются другими, меньшими структурами. Это полный беспорядок, чтобы попытаться понять, но почти каждая функция в программе передается указатель на одну из структур и сильно использует структуру.

Есть ли какой-либо чистый способ для меня обучать этому классу? Я решил сделать это, если это можно сделать, я просто не знаю, как начать.

4b9b3361

Ответ 1

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

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

  • Создать регрессионный/тестовый пакет.. Вы запускаете тестовый пакет каждый раз, когда вы завершаете пакет изменений кода. Вы должны иметь это уже, и это будет полезно не только для этой задачи рефакторинга. Потратьте время, чтобы сделать его всеобъемлющим. Осуществление создания набора тестов поможет вам ознакомиться с кодом.
  • Отделить проект в вашей системе контроля версий. Вооружившись набором тестов и игровой площадкой, вы сможете вносить большие изменения в код. Вы не будете бояться сломать некоторые яйца.
  • Сделать эти поля структуры частными. Этот шаг требует очень небольшого количества изменений кода, но может иметь большой выигрыш. Выполните одно поле за раз. Попытайтесь сделать каждое поле private (да или защищено), затем изолируйте код, который обращается к этому полю. Простейшим, самым неинтрузивным преобразованием было бы сделать этот код a friend function. Рассмотрим также, что этот код является методом. Преобразование кода как метода прост, но вам также придется преобразовать все сайты вызовов. Один не обязательно лучше другого.
  • Уточнить параметры каждой функции. Маловероятно, что для любой функции требуется доступ ко всем 30 полям структуры, переданным в качестве аргумента. Вместо передачи всей структуры передаются только необходимые компоненты. Если функция действительно требует доступа ко многим различным полям структуры, то это может быть хорошим кандидатом для преобразования в метод экземпляра.
  • Const-ify как можно больше переменных, параметров и методов. Много старого кода C не использует const либерально. Просматривая снизу вверх (внизу графика вызовов, то есть), вы добавите более сильные гарантии для кода, и вы сможете идентифицировать мутаторы от не-мутаторов.
  • Заменить указатели ссылками, где это разумно. Цель этого шага не имеет ничего общего с тем, чтобы быть более С++ - как раз для того, чтобы быть более похожим на С++. Цель состоит в том, чтобы идентифицировать параметры, которые никогда не были NULL и которые никогда не могут быть повторно назначены. Подумайте о ссылке как утверждении времени компиляции, в котором говорится, что это псевдоним действительного объекта и представляет один и тот же объект во всей текущей области.
  • Заменить char* на std::string. Этот шаг должен быть очевиден. Вы можете значительно сократить количество строк кода. Кроме того, интересно заменить 10 строк кода на одну строку. Иногда вы можете исключить целые функции, целью которых было выполнение операций на C-строках, которые являются стандартными в С++.
  • Преобразование массивов C в std::vector или std::array. Опять же, этот шаг должен быть очевиден. Это преобразование намного проще, чем преобразование с char в std::string, потому что интерфейсы std::vector и std::array предназначены для соответствия синтаксису массива C. Одно из преимуществ заключается в том, что вы можете исключить эту дополнительную переменную length, переданную каждой функции вместе с массивом.
  • Преобразовать malloc/free в new/delete. Основной целью этого шага является подготовка к будущему рефакторингу. Простое изменение кода C от malloc до new не дает вам многого. Это преобразование позволяет добавлять конструкторы и деструкторы к этим структурам и использовать встроенные средства автоматической памяти С++.
  • Заменить операции локализации new/delete с семейством std::auto_ptr. Цель этого шага - сделать код безопасным.
  • Выбрасывать исключения, когда обратные коды обрабатываются путем их пузырьков.Если код C обрабатывает ошибки, проверяя специальные коды ошибок, затем возвращая код ошибки своему вызывающему и т.д., Продублируя код ошибки в цепочке вызовов, тогда этот код C, вероятно, является кандидатом на использование исключений. Это преобразование фактически тривиально. Просто throw код возврата (С++ позволяет вам набрасывать любой тип, который вы хотите) на самом низком уровне. Вставьте оператор try{} catch(){} в место в коде, который обрабатывает ошибку. Если для обработки ошибки не существует подходящего места, рассмотрите возможность обертывания тела main() в инструкции try{} catch(){} и регистрации его.

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

и в меньшей степени:

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

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

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

Я чувствую вашу боль, хотя...

Ответ 5

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

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

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

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

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

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

Ответ 6

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

Ответ 7

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

Как уже указывал @David, книга Рефакторинга обязательна.

Из вашего описания это похоже на то, что большая часть кода уже является "методом класса", где функция получает указатель на экземпляр структуры и работает на этом экземпляре. Поэтому его можно было бы легко преобразовать в код С++. Разумеется, это не сделает код более понятным или лучше модульным, но если это основное желание вашего босса, это можно сделать.

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

Ответ 8

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

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

в класс С++ с

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

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

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

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

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

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

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

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

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

Поэтому будьте очень осторожны, что ваш менеджер означает "модульный".

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

Ответ 9

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

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

7000 LOC может показаться много, но многое из этого будет шаблоном.

Ответ 10

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

Ответ 11

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

Подводя итог идеям доктора Фаррелла, он говорит:

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

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

Удачи!