Что делать с исходным файлом C ++ из 11000 строк? - PullRequest
226 голосов
/ 01 сентября 2010

Итак, в нашем проекте есть огромный исходный файл mainmodule.cpp (размером 11000 строк?), И каждый раз, когда мне приходится его трогать, я съеживаюсь.

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

Файл используется и активно изменяется в нескольких (> 10) версиях обслуживания нашего продукта, поэтому его очень трудно реорганизовать. Если бы я «просто» разделил его, скажем для начала, на 3 файла, то объединение изменений из версий обслуживания станет кошмаром. А также, если вы разделите файл с такой длинной и богатой историей, отслеживание и проверка старых изменений в истории SCC внезапно станет намного сложнее.

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

Что бы вы сделали в этой ситуации? Любые идеи о том, как переместить новые функции в отдельный исходный файл, не испортив рабочий процесс SCC?

(Примечание по инструментам: мы используем C ++ с Visual Studio; мы используем AccuRev как SCC, но я думаю, что тип SCC здесь на самом деле не имеет значения; мы используем Araxis Merge для фактического сравнение и объединение файлов)

Ответы [ 34 ]

128 голосов
/ 01 сентября 2010

Слияние не будет таким большим кошмаром, как это будет, когда вы получите 30000 LOC-файлов в будущем. Итак:

  1. Прекратите добавлять код в этот файл.
  2. Разделить его.

Если вы не можете просто остановить кодирование во время процесса рефакторинга, вы можете оставить этот большой файл как есть хотя бы на некоторое время, не добавляя в него больше кода: так как он содержит один «основной класс», который вы может наследовать от него и хранить унаследованные классы с перегруженными функциями в нескольких новых небольших и хорошо спроектированных файлах.

83 голосов
/ 01 сентября 2010
  1. Найдите некоторый код в файле, который относительно стабилен (не меняется быстро и не сильно различается между ветвями) и может выступать в качестве независимой единицы.Переместите это в свой собственный файл, и в этом отношении в его собственный класс, во всех ветвях.Поскольку он стабилен, это не вызовет (много) «неуклюжих» слияний, которые должны быть применены к файлу, отличному от того, в котором они были изначально созданы, при слиянии изменений из одной ветви в другую.Повторите.

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

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

Это оставляет вас с ядромплохо управляемого кода - он нужен везде, но он отличается в каждой ветви (и / или постоянно меняется, так что некоторые ветви работают позади других), и все же он находится в одном файле, который вы безуспешно пытаетесь объединить между ветвями,Перестань.Разветвите файл навсегда , возможно, переименовав его в каждой ветви.Это больше не «главное», это «главное для конфигурации X».Итак, вы теряете возможность применять одно и то же изменение к нескольким ветвям путем слияния, но это в любом случае ядро ​​кода, где слияние работает не очень хорошо.Если вам все равно придется вручную управлять слияниями, чтобы справиться с конфликтами, то не составит труда вручную применить их независимо для каждой ветви.

Я думаю, что вы ошибаетесь, говоря, что тип SCC не 'Это не имеет значения, потому что, например, возможности git по слиянию, вероятно, лучше, чем используемый вами инструмент слияния.Таким образом, основная проблема, «объединение затруднено», возникает в разное время для разных SCC.Однако вы вряд ли сможете изменить SCC, поэтому проблема, вероятно, не имеет значения.

66 голосов
/ 01 сентября 2010

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

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

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

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

В любом случае, это звучит как крупный проект, так что удачи:)

49 голосов
/ 01 сентября 2010

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

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

Как только это будет сделано, вы получите возможность добавитьновые функциональные возможности в новых классах.

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

Я знаю, что это не идеально, хотя я надеюсь, что это может помочь, и процесс должен быть адаптирован к вашим потребностям!

Дополнительная информация

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

Git построен вокруг понятия BLOB-объектов, которые, если я правильно понимаю, представляют собой фрагменты файлов кода.Переместите эти части в разные файлы, и Git найдет их, даже если вы измените их.Кроме видео от Линуса Торвальдса , упомянутого в комментариях ниже, я не смог найти что-то ясное по этому поводу.

29 голосов
/ 02 сентября 2010

Конфуций говорит: «Первый шаг к выбору из ямы - это прекратить копать яму».

25 голосов
/ 01 сентября 2010

Позвольте мне угадать: десять клиентов с различными наборами функций и менеджер по продажам, который продвигает «настройку»?Я работал над такими продуктами раньше.По сути, у нас была та же самая проблема.

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

Прежде чем пытаться разбить файл на части, вам нужно привести десять ветвей обратно в синхронизацию друг с другом, чтобы вы могли видеть исформировать весь код сразу.Вы можете делать это по одной ветке за раз, проверяя обе ветки на один и тот же файл основного кода.Чтобы реализовать пользовательское поведение, вы можете использовать #ifdef и друзей, но лучше использовать обычные if / else против определенных констант.Таким образом, ваш компилятор проверит все типы и, скорее всего, все равно удалит «мертвый» объектный код.(Возможно, вы захотите отключить предупреждение о мертвом коде.)

Если существует только одна версия этого файла, неявно совместно используемая всеми ветвями, тогда довольно легко начать традиционные методы рефакторинга.1008 * #ifdefs в первую очередь лучше подходит для разделов, где уязвимый код имеет смысл только в контексте других настроек для каждой ветви.Можно утверждать, что они также предоставляют возможность для той же схемы слияния ветвей, но не бредят.Пожалуйста, один колоссальный проект за раз.

В скором времени файл будет расти.Хорошо.То, что вы делаете, это объединение вещей, которые должны быть вместе.После этого вы начнете видеть области, которые явно одинаковы, независимо от версии;они могут быть оставлены в покое или рефакторинг по желанию.Другие области будут четко различаться в зависимости от версии.У вас есть несколько вариантов в этом случае.Один из методов - делегировать различия объектам стратегии для каждой версии.Другой способ заключается в получении клиентских версий из общего абстрактного класса.Но ни одно из этих преобразований невозможно, если у вас есть десять «советов» по ​​развитию в разных отраслях.

22 голосов
/ 01 сентября 2010

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

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

Я думаю, у вас есть только эти варианты:

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

  2. Разработайте макет того, как вы хотели бы, чтобы ваш файл выглядел, как только он будет разделен. Создайте необходимые файлы и интегрируйте их в свое приложение. Переименуйте функции или перегрузите их, чтобы получить дополнительный параметр (может быть, просто логическое значение?). После того, как вам нужно поработать над своим кодом, перенесите функции, над которыми вы хотите работать, в новый файл и сопоставьте вызовы функций старых функций с новыми функциями. Вы по-прежнему должны иметь свой основной файл таким образом и по-прежнему иметь возможность видеть изменения, которые были в него внесены, когда речь идет о конкретной функции, которую вы точно знаете, когда она была передана на аутсорсинг и так далее.

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

19 голосов
/ 01 сентября 2010

Именно эта проблема решается в одной из глав книги «Эффективная работа с устаревшим кодом» (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).

14 голосов
/ 01 сентября 2010

Думаю, вам лучше создать набор команд классов, которые сопоставляются с точками API mainmodule.cpp.

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

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

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

Обновление

Я бы предположил, что шаблон Command предпочтительнее Фасада.

Желательно поддерживать / организовывать множество различных командных классов на (относительно) монолитном Фасаде. Отображение одного Фасада в файл 11 KLOC, вероятно, придется разбить на несколько разных групп.

Зачем пытаться выяснить эти фасадные группы? С помощью шаблона Command вы сможете органично группировать и организовывать эти небольшие классы, что обеспечит вам большую гибкость.

Конечно, оба варианта лучше, чем один 11 KLOC и растущий файл.

13 голосов
/ 01 сентября 2010

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

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

Затем вы можете попытаться переключить все выпуски обслуживания на «новый» main.cpp или что-то еще в вашей структуре. Опять же: никаких других изменений или исправлений нет, потому что их отслеживание - адское замешательство.

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

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

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