2010-06-09 2 views
36

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

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

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

+29

+1 Это большой коллективный вздох, который вы только что слышали, это многие пользователи SO, которые работают с устаревшим кодом ... –

+7

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

+0

Я бы определенно поговорил с вашим начальником о переписывании некоторой программы –

ответ

35

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

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

  1. Создайте набор регрессии/тестирования. Вы будете запускать тестовый пакет каждый раз, когда вы выполните партию изменений кода. Вы должны иметь это уже, и это будет полезно не только для этой задачи рефакторинга. Потратьте время, чтобы сделать его всеобъемлющим. Осуществление создания набора тестов поможет вам ознакомиться с кодом.
  2. Отразить проект в вашей системе контроля версий. Вооружившись набором тестов и игровой площадкой, вы сможете вносить большие изменения в код. Вы не будете бояться разбить яйца.
  3. Сделать эти поля структуры частными. Этот шаг требует очень мало изменений кода, но может иметь большой выигрыш. Выполните одно поле за раз. Попробуйте сделать каждое поле private (да или защищено), а затем изолировать код, доступ к которому. Простейшим, самым неинтрузивным преобразованием было бы сделать этот код friend function. Рассмотрим также, что этот код является методом. Преобразование кода как метода прост, но вам также придется преобразовать все сайты вызовов. Один не обязательно лучше другого.
  4. Уточните параметры каждой функции. Маловероятно, что для любой функции требуется доступ ко всем 30 полям структуры, переданным в качестве аргумента. Вместо передачи всей структуры передаются только необходимые компоненты. Если на самом деле функция действительно требует доступа ко многим различным полям структуры, это может быть хорошим кандидатом для преобразования в метод экземпляра.
  5. Const-ify как можно больше переменных, параметров и методов. Много старого кода C не использует 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 предназначены для соответствия синтаксису массива C. Одним из преимуществ является то, что вы можете исключить эту дополнительную переменную length, переданную каждой функции вместе с массивом.
  9. Конвертация malloc/free на new/delete. Основной целью этого шага является подготовка к будущему рефакторингу. Простое изменение кода C от malloc до new не дает вам многого. Это преобразование позволяет добавлять конструкторы и деструкторы к этим структурам и использовать встроенные средства автоматической памяти C++.
  10. Заменить локализацию new/delete операции с семейством std::auto_ptr. Цель этого шага - сделать код безопасным.
  11. Выбросьте исключения, в которых коды возврата обрабатываются путем их пузыри. Если код C обрабатывает ошибки, проверяя специальные коды ошибок, затем возвращая код ошибки своему вызывающему и т. Д., Продублируя код ошибки в цепочке вызовов, тогда этот код C, вероятно, является кандидатом на использование исключений. Это преобразование фактически тривиально. Просто throw код возврата (C++ позволяет вам набрасывать любой тип, который вы хотите) на самом низком уровне. Вставьте оператор try{} catch(){} в место в коде, который обрабатывает ошибку. Если подходящее место не существует для обработки ошибки, рассмотрите обертывание тела main() в операторе try{} catch(){} и его регистрации.

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

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

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

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

+1

любите свой первый пункт: это должно быть, прежде чем кто-либо возьмет на себя портирование. – Syd

+0

Отличный совет, все кроме # 6. Нет абсолютно никакой проверки времени на компиляцию с ссылкой. Константные указатели (в отличие от указателя на константу) часто лучше, так как все проблемы жизни и псевдонимы более очевидны. –

+0

+1 за продуманный, полезный ответ, опираясь на опыт. – djna

3

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

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

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

12

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

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

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

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

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

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

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

+1

+1 для книги «Перья». Он полон здравого смысла. –

2

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

+0

+1 Я согласен с модуляцией кода перед преобразованием в C++ –

+1

Лучше все еще, modularise, тогда оставьте его на C, если нет веских причин для изменения языка. –

20

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

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

+2

его не только количество строк кода, но и сколько и сложных. Это зависит от того, что он делает. 7000 строк кода из 1 источника могут быть эквивалентны 70 000 другим. –

+7

+1 для тестирования. – Bill

+1

@Neil: Книга «Перья», в которой Дэвид рекомендовал подробно рассказать о «обложке и коде», который включает в себя приведение в действие текущего поведения с помощью модульных тестов, а затем подтверждение того, что изменения кода все еще проходят тесты. Отличный совет! –

4

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

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

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

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

4

Очень маловероятно, что это упражнение будет достигнуто. Хороший код 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++, такие как классы и шаблоны, которые , подходящий для применения, может улучшить сцепление в модуле. Если ваш проект должен создать единое статически связанное приложение, то у вас нет модульной системы, и вы можете позволить себе не заботиться о модульности. Если вы хотите создать что-то вроде anti-grain geometry, что является прекрасным примером использования шаблонов для объединения разных алгоритмов и структур данных, тогда вам нужно сделать это на C++ - довольно хорошо, что ничто иное не было столь же мощным.

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

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

+2

«Хороший код C уже более модульный, чем обычно C++» - это не имеет смысла для меня. Конечно, если вы группируете методы вместе с одной и той же ответственностью в класс, вы улучшаете модульность. –

+2

Вы путаете модульность с муфтой. – 2010-06-09 16:32:28

+0

@ Нил, с кем вы обращаетесь? –

19

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

+6

+1 для поднятия вопроса: что такое _real_ цель упражнения? –

+1

Я бы ожидал, что цель упражнения - атаковать эту проблему: «Это полный беспорядок, чтобы попытаться понять». – Ether

5

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

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

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

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

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

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

5

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

2

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

Подведение итоговИдеи Фаррелла, он говорит:

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

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

Удачи вам!

Смежные вопросы