Що робити з вихідним файлом на C ++ 11000 рядків?


229

Таким чином, у нас є цей величезний (на 11000 рядків величезний?) Mainmodule.cpp вихідний файл у нашому проекті, і кожного разу, коли мені доводиться його торкатися, я стискаюся.

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

Файл використовується і активно змінюється в декількох (> 10) версіях технічного обслуговування нашого продукту, тому його дуже важко переробити. Якщо я повинен був "просто" розділити його, скажімо для початку, на 3 файли, то об'єднання змін назад з технічними версіями стане кошмаром. А також, якщо ви розділите файл із такою довгою та багатою історією, відстежувати та перевіряти старі зміни в SCCісторії раптом стає набагато важче.

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

Що б ви зробили в цій ситуації? Будь-які ідеї, як перемістити нові функції в окремий вихідний файл, не псуючи SCCробочий процес?

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


15
@BoltClock: Насправді Vim відкриє його досить швидко.
ereOn

58
69305 рядків і підрахунок. Файл у нашій заявці, в який мій колега скидає більшу частину свого коду. Не могла протистояти публікації цього тут. У мене немає нікого в моїй компанії, щоб це повідомити.
Агнел Куріан

204
Я не розумію. Як коментар "кинути цю роботу" може отримати стільки результатів? Деякі люди, здається, живуть у казковій країні, де всі проекти написані з нуля та / або використовують 100% спритний, TDD, ... (помістіть сюди будь-які ваші голосні слова).
Стефан

39
@Stefan: Зустрівшись із подібною базою коду, я зробив саме це. Я не любив витрачати 95% мого часу, працюючи навколо сиру в 10-річній базі коду, і 5% фактично писав код. Тестувати деякі аспекти системи було фактично неможливо (і я не маю на увазі тестовий блок, я маю на увазі фактично запустити код, щоб перевірити, чи працює він). Я не прослужив 6-місячного випробувального періоду, я втомився боротися з програшними битвами та писати код, за яким я не міг стояти.
Бінарний страшник

50
що стосується аспекту відстеження історії розбиття файлу: використовуйте команду копіювання системи управління версіями, щоб скопіювати весь файл, скільки разів ви хочете розділити його, а потім видаліть увесь код із кожної копії, яку ви не хочете у цьому файлі. Це зберігає загальну історію, оскільки кожен з файлів спліт може простежити свою історію назад через розкол (що буде схоже на гігантське видалення більшості вмісту файлу).
rmeador

Відповіді:


86
  1. Знайдіть у файлі відносно стабільний код (не змінюється швидко і не сильно відрізняється між гілками) і може стояти як самостійний підрозділ. Перемістіть це у власний файл, а з цього питання - у свій клас, у всіх галузях. Оскільки він стабільний, це не спричинить (багато) "незграбних" об'єднань, які потрібно застосувати до іншого файлу, ніж той, на якому вони були спочатку зроблені, коли ви з’єднаєте зміни з однієї гілки в іншу. Повторіть.

  2. Знайдіть у файлі код, який в основному стосується лише невеликої кількості гілок і може стояти окремо. Не має значення, швидко змінюється чи ні, через малу кількість гілок. Перемістіть це у власні класи та файли. Повторіть.

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

Це залишає вас з ядром погано керованого коду - він потрібен скрізь, але він різний у кожній гілці (та / або він постійно змінюється, щоб одні гілки бігали за іншими), і все ж це в одному файлі, що ви безуспішно намагаються злитися між гілками. Перестаньте це робити. Відділіть файл назавжди , можливо, перейменувавши його в кожну гілку. Це вже не "головне", це "головне для конфігурації X". Гаразд, ви втрачаєте можливість застосувати одну і ту ж зміну до декількох гілок шляхом злиття, але це в будь-якому випадку ядро ​​коду, де злиття працює не дуже добре. Якщо вам доведеться вручну керувати злиттями, щоб вирішити конфлікти, не можна втратити вручну їх самостійно застосовувати на кожній гілці.

Я думаю, що ви помиляєтесь, сказавши, що тип SCC не має значення, оскільки, наприклад, здібності git, можливо, краще, ніж інструмент для злиття, який ви використовуєте. Таким чином, основна проблема "злиття важко" виникає в різний час для різних ДКК. Однак ви навряд чи зможете змінити SCC, тому питання, ймовірно, не має значення.


Що стосується злиття: я подивився на GIT і на SVN, і на Perforce, і дозвольте сказати, що ніщо, що я бачив, не переважає AccuRev + Araxis за те, що ми робимо. :-) (Хоча GIT може це зробити [ stackoverflow.com/questions/1728922/… ], а AccuRev не може - кожен повинен вирішити сам, чи це частина об'єднання чи аналіз історії.)
Martin Ba

Досить справедливо - можливо, у вас вже є найкращий інструмент. Здатність Git об'єднати зміну, що сталася у Файлі А на гілці X, у Файл B на гілці Y, повинна полегшити поділ розгалужених файлів, але, мабуть, система, яку ви використовуєте, має переваги, які вам подобаються. У всякому разі, я не пропоную вам перейти на git, просто кажу, що SCC тут має значення, але навіть я згоден з вами, що це можна знизити :-)
Стів Джессоп

129

Злиття не буде таким великим кошмаром, як це станеться, коли ви отримаєте файл 30000 LOC у майбутньому. Так:

  1. Зупиніть додавати більше коду до цього файлу.
  2. Розділіть його.

Якщо ви не можете просто зупинити кодування під час процесу рефакторингу, ви можете залишити цей великий файл таким, який є на деякий час, принаймні, не додаючи до нього більше коду: оскільки він містить один "основний клас", ви могли б успадкувати його і зберегти спадковий клас ( es) з перевантаженими функціями в декількох нових невеликих і добре розроблених файлах.


@Martin: на щастя, ви тут не вставили свій файл, тому я не маю поняття про його структуру. Але загальна ідея - розділити її на логічні частини. Такі логічні частини можуть містити групи функцій вашого "основного класу" або ви можете розділити їх на кілька допоміжних класів.
Кирило Васильович Лядвінський

3
З 10 версіями технічного обслуговування та багатьма активними розробниками навряд чи файл можна заморозити досить довго.
Кобі

9
@Martin, у вас є декілька шаблонів GOF, які б виконали трюк, один фасад, який відображає функції mainmodule.cpp, або, як я рекомендував нижче), створити набір класів Command, які кожне відображення до функції / особливість mainmodule.app. (Я розширив про це на свою відповідь.)
ocodo

2
Так, повністю згоден, у якийсь момент ви повинні зупинити додавання коду до нього, або, врешті-решт, його основний модуль 30k, 40k, 50k, kaboom буде просто несправним. :-)
Кріс

67

Мені це здається, що ви стикаєтеся з низкою кодового запаху. Перш за все, схоже, що основний клас порушує принцип відкритого / закритого типу . Це також звучить так, ніби він обробляє занадто багато обов'язків . Завдяки цьому я вважаю, що код більш крихкий, ніж це має бути.

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

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

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

У будь-якому випадку це звучить як великий проект, тож удачі :)


18
Він пахне сильно: пахне тим, що антидіапазон Blob є в будинку ... en.wikipedia.org/wiki/God_object . Його улюблена страва - спагеті код: en.wikipedia.org/wiki/Spaghetti_code :-)
jdehaan

@jdehaan: Я намагався бути дипломатичним щодо цього :)
Брайан Расмуссен

+1 Також від мене не смію торкатися навіть складного коду, який я написав без тестів, щоб покрити його.
Денні Томас

49

Єдине рішення, яке я коли-небудь уявляв подібним проблемам, наступне. Фактичний приріст описаного методу - прогресивність еволюцій. Тут немає революцій, інакше ви потрапите в біду дуже швидко.

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

Після цього ви отримаєте можливість додавати нові функції в нові класи.

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

Я знаю, що це не ідеально, хоча я сподіваюся, що це може допомогти, і процес повинен бути адаптований до ваших потреб!

Додаткова інформація

Зауважте, що Git - це SCC, який може переглядати фрагменти коду з одного файлу в інший. Я чув про це хороші речі, тому це може допомогти, коли ви поступово рухаєтесь своєю роботою.

Git побудований навколо поняття краплі, які, якщо я правильно зрозумів, представляють фрагменти файлів коду. Перемістіть ці фрагменти в різних файлах, і Git знайде їх, навіть якщо ви їх модифікуєте. Крім відео з Лінуса Торвальдса, згаданого в коментарях нижче, я не зміг знайти про це щось зрозуміле.


Посилання на те, як GIT робить це / як ви робите це з GIT, було б дуже вітається.
Мартін Ба

@Martin Git робить це автоматично.
Матвій

4
@Martin: Git робить це автоматично - оскільки він не відстежує файли, він відстежує вміст. Насправді в git набагато складніше просто "отримати історію одного файлу".
Арафангіон

1
@Martin youtube.com/watch?v=4XpnKHJAok8 - це розмова, де Торвальдс говорить про git. Він згадує про це пізніше у бесіді.
Матвій

6
@Martin, погляд на це питання: stackoverflow.com/questions/1728922 / ...
Benjol

30

Конфуцій каже: "Перший крок до виходу з нори - це припинити копати яму".


25

Дозвольте здогадатися: Десять клієнтів з різними наборами функцій та менеджер з продажу, який сприяє "налаштуванню"? Я працював над такими продуктами раніше. У нас була по суті однакова проблема.

Ви визнаєте, що мати величезний файл - це неприємність, але ще більше клопоту - це десять версій, які вам доведеться тримати "актуальними". Це багаторазове обслуговування. SCC може зробити це простіше, але це не може зробити це правильно.

Перш ніж спробувати розбити файл на частини, потрібно повернути десять гілок синхронізовано між собою, щоб ви могли побачити і сформувати весь код відразу. Ви можете робити це по одній гілці одночасно, випробовуючи обидві гілки на одному головному файлі коду. Щоб застосувати власну поведінку, ви можете використовувати #ifdef та друзів, але краще якомога більше використовувати звичайні if / else проти визначених констант. Таким чином, ваш компілятор перевірить усі типи і, швидше за все, усуне "мертвий" об'єктний код у будь-якому випадку. (Можливо, ви хочете вимкнути попередження про мертвий код.)

Після того, як є лише одна версія цього файлу, яка неявно поділяється всіма гілками, тоді простіше розпочати традиційні методи рефакторингу.

#Ifdefs в першу чергу краще для розділів, де зачіпаючий код має сенс лише в контексті інших налаштувань кожної галузі. Можна стверджувати, що вони також представляють можливість для тієї самої схеми злиття гілок, але не залишаються дикими. Один колосальний проект за часом, будь ласка.

За короткий термін файл, схоже, зросте. Це нормально. Те, що ви робите, - це об’єднання речей, які потрібно бути разом. Після цього ви почнете бачити області, які явно однакові незалежно від версії; їх можна залишити в спокої або відремонтувати за бажанням. Інші області будуть чітко відрізнятися залежно від версії. У вас є ряд варіантів у цьому випадку. Один з методів полягає в делегуванні відмінностей об'єктам стратегії для кожної версії. Іншим є отримання клієнтських версій із загального абстрактного класу. Але жодна з цих перетворень неможлива, якщо у вас є десять «підказок» розвитку в різних галузях.


2
Я погоджуюсь, що метою має бути наявність однієї версії програмного забезпечення, але чи не було б краще використовувати конфігураційні файли (час виконання) та не компілювати кумуляцію часу
Есбен Сков Педерсен

Або навіть "конфігураційні класи" для складання кожного клієнта.
тс.

Я вважаю, що конфігурація часу компіляції або часу виконання функціонально не має значення, але я не хочу обмежувати можливості. Конфігурація часу компіляції має ту перевагу, що клієнт не може взломати файл конфігурації для активації додаткових функцій, оскільки він розміщує всю конфігурацію у вихідному дереві замість розгортаного коду "текстуального об'єкта". Зворотний бік полягає в тому, що ви схильні до AlternateHardAndSoftLayers, якщо це час виконання.
Ян

22

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

Перш за все, просто не існує способу, що це легко і вирішиться за кілька хвилин мозкового штурму. Функції, пов'язані у вашому файлі, важливі для вашої програми, а просто вирізання та перенесення їх на інші файли не врятує вашу проблему.

Я думаю, у вас є лише такі варіанти:

  1. Не мігруйте та залишайтеся з тим, що у вас є. Можливо, залиште свою роботу і почніть працювати над серйозним програмним забезпеченням з гарним дизайном. Екстремальне програмування - це не завжди найкраще рішення, якщо ви працюєте над проектом тривалий час з достатньою кількістю коштів, щоб пережити аварію чи два.

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

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


19

Саме ця проблема вирішується в одному з розділів книги "Ефективна робота зі спадковим кодом" ( http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 ).


informit.com/store/product.aspx?isbn=0131177052 дає змогу побачити TOC цієї книги (та 2 зразкові глави). Як довго триває глава 20? (Тільки щоб відчути, наскільки це може бути корисно.)
Мартін Бала,

17
глава 20 - 10 000 рядків, але автор розробив, як розділити її на перетравні шматки ... 8)
Тоні Делрой

1
Його близько 23 сторінок, але з 14 зображеннями. Я думаю, вам це доведеться отримати, ви будете почувати себе набагато впевненіше, намагаючись вирішити, що робити імхо.
Emile Vrijdags

Чудова книга для проблеми, але всі її рекомендації (та інші рекомендації в цій темі) мають загальну вимогу: якщо ви хочете переробляти цей файл для всіх своїх гілок, то єдиний спосіб зробити це - заморозити файл для всіх гілок та внести початкові структурні зміни. Не обійтися. У книзі описаний ітеративний підхід до безпечного вилучення підкласів без автоматичної підтримки рефакторингу шляхом створення дублікатів методів та делегування викликів, але все це є суперечливим, якщо ви не можете змінити файли.
Дан Брайант

2
@Martin, книга відмінна, але вона дуже сильно покладається на тест, рефактор, тестовий цикл, який може бути досить важким від місця, де ти зараз. Я був у подібній ситуації, і ця книга була мені найбільш корисною. У ній є гарні пропозиції щодо негарної проблеми у вас. Але якщо ви не можете отримати якусь тестову запряжку на знімку, всі пропозиції щодо рефакторингу у світі вам не допоможуть.

14

Я думаю, вам було б найкраще створити набір класів команд, які відображаються в точках API головного модуля.cpp.

Після їх встановлення вам потрібно буде перефактурувати існуючу базу коду, щоб отримати доступ до цих точок API за допомогою командних класів, після цього ви зможете рефакторировать реалізацію кожної команди в нову структуру класу.

Звичайно, з одного класу 11 KLOC код, ймовірно, сильно пов'язаний і крихкий, але створення окремих класів команд допоможе набагато більше, ніж будь-яка інша стратегія проксі / фасаду.

Я не заздрю ​​цьому завданню, але з часом ця проблема погіршиться, лише якщо її не вирішити.

Оновлення

Я б припустив, що командний зразок є кращим для фасаду.

Підтримування / організація безлічі різних класів команд над (відносно) монолітним фасадом є кращим. Зіставлення одного фасаду на 11 файл KLOC, ймовірно, потрібно буде розбити на кілька різних груп.

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

Звичайно, обидва варіанти кращі, ніж єдиний 11 KLOC і зростаючий файл.


+1 альтернатива запропонованому мною рішенням з тією ж ідеєю: змінити API, щоб розділити велику проблему на маленькі.
Беньот

13

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

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

Потім можна спробувати переключити всі випуски технічного обслуговування на "новий" main.cpp або будь-яку структуру. Знову ж таки: Ніяких інших змін чи виправлень помилок, оскільки відстеження їх заплутано як пекло.

Інша справа: стільки, скільки ви хочете зробити один великий прохід, коли рефакторинг всієї справи за один раз, ви можете відкусити більше, ніж можете жувати. Можливо, просто виберіть одну чи дві "частини", введіть їх у всі випуски, а потім додайте ще трохи цінності для свого замовника (адже Refactoring не додає прямої вартості, тому це вартість, яка повинна бути виправдана), а потім виберіть іншу одна або дві частини.

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


1
+1 для факторингу та #include назад. Якщо ви зробили це для всіх 10 відділень (трохи роботи там, але керовані), у вас все одно виникне інша проблема - розміщення змін у всіх ваших гілках, але ця проблема не буде ' т розширилися (обов’язково). Це некрасиво? Так, все-таки є, але це може принести трохи проблеми раціональності. Провівши кілька років, займаючись технічним обслуговуванням та обслуговуванням справді, дійсно великого продукту, я знаю, що технічне обслуговування включає багато болю. Принаймні, вчитися на цьому і слугувати застереженням для інших.
Джей

10

Рофле, це нагадує мені про мою стару роботу. Здається, що до того, як я приєднався, все було всередині одного величезного файлу (також C ++). Потім вони розділили його (у абсолютно випадкових точках, використовуючи включає) на приблизно три (все ще величезні файли). Якість цього програмного забезпечення була, як ви могли очікувати, жахливою. Проект становив близько 40 тис. LOC. (не містить майже жодних коментарів, окрім LOTS дубліката коду)

Врешті-решт я зробив повний перепис проекту. Я почав з переробки найгіршої частини проекту з нуля. Звичайно, я мав на увазі можливий (невеликий) інтерфейс між цією новою частиною та рештою. Потім я вставив цю частину в старий проект. Я не переробляв старий код, щоб створити необхідний інтерфейс, а просто замінив його. Потім я зробив невеликі кроки звідти, переписавши старий код.

Я мушу сказати, що на це пішло близько півроку, і не було розроблено стару базу коду поруч із виправленнями помилок.


редагувати:

Розмір становив близько 40 тис. LOC, але новий додаток містив набагато більше функцій і, імовірно, менше помилок у початковій версії, ніж 8-річне програмне забезпечення. Однією з причин переписування було також те, що нам потрібні нові функції, і ввести їх всередині старого коду було майже неможливо.

Програмне забезпечення було для вбудованої системи, принтера для етикеток.

Ще один момент, який я мушу додати, це те, що теоретично проект був C ++. Але це зовсім не ОО, це могло бути C. Нова версія була орієнтована на об'єкти.


9
Кожен раз, коли я чую "з нуля" в темі про рефакторинг, я вбиваю кошеня!
Кугель

Я опинився в дуже схожій звучаній ситуації, хоча основний цикл програми, з яким мені довелося зіткнутися, склав лише ~ 9000 LOC. І це було досить погано.
AndyUK

8

Гаразд, здебільшого, API переписування виробничого коду є поганою ідеєю для початку. Дві речі повинні відбутися.

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

По-друге, вам потрібно взяти цю виробничу версію і створити гілку, яка керує збірками, використовуючи директиви попередньої обробки, щоб розділити великий файл. Розщеплення компіляції за допомогою директив препроцесора JUST (#ifdefs, #includes, #endifs) простіше, ніж перекодування API. Це, безумовно, простіше для ваших угод про надання послуг за угодою та ухвалення постійної підтримки

Тут ви можете просто вирізати функції, які відносяться до певної підсистеми в класі та помістити їх у файл, наприклад, mainloop_foostuff.cpp та включити його в mainloop.cpp у потрібному місці.

АБО

Більш трудомістким, але надійним способом було б розробити внутрішню структуру залежностей із подвійним опосередкуванням того, як вони включаються. Це дозволить вам розділити речі і все одно піклуватися про співзалежності. Зауважте, що цей підхід вимагає кодування позицій, і тому його слід поєднувати з відповідними коментарями.

Цей підхід буде включати компоненти, які використовуються на основі того, який варіант ви збираєте.

Основна структура полягає в тому, що ваш mainclass.cpp буде включати новий файл під назвою MainClassComponents.cpp після блоку операторів типу:

#if VARIANT == 1
#  define Uses_Component_1
#  define Uses_Component_2
#elif VARIANT == 2
#  define Uses_Component_1
#  define Uses_Component_3
#  define Uses_Component_6
...

#endif

#include "MainClassComponents.cpp"

Первинна структура файлу MainClassComponents.cpp була б там, щоб розробити залежності в рамках таких компонентів:

#ifndef _MainClassComponents_cpp
#define _MainClassComponents_cpp

/* dependencies declarations */

#if defined(Activate_Component_1) 
#define _REQUIRES_COMPONENT_1
#define _REQUIRES_COMPONENT_3 /* you also need component 3 for component 1 */
#endif

#if defined(Activate_Component_2)
#define _REQUIRES_COMPONENT_2
#define _REQUIRES_COMPONENT_15 /* you also need component 15 for this component  */
#endif

/* later on in the header */

#ifdef _REQUIRES_COMPONENT_1
#include "component_1.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_2
#include "component_2.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_3
#include "component_3.cpp"
#endif


#endif /* _MainClassComponents_h  */

А тепер для кожного компонента ви створюєте файл компонент_xx.cpp.

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

Використання препроцесора дозволяє розділяти речі, не турбуючись про зміни API, що є кошмаром у виробництві.

Після того, як виробництво налагодиться, ви можете фактично працювати над перепроектуванням.


Це виглядає як результати, але спочатку болючі результати досвіду.
JBRWilkinson

Власне, це техніка, яка застосовувалася в компіляторах Borland C ++ для імітації використання стилю Pascal для управління файлами заголовків. Особливо, коли вони робили початковий порт своєї текстової системи вікон.
Король ельфів

8

Ну я розумію ваш біль :) Я також був у кількох таких проектах, і це не дуже. На це немає простої відповіді.

Один із підходів, який може працювати для вас, - це почати додавати безпечні засоби захисту у всі функції, тобто перевіряти аргументи, до / після умов у методах, а потім, зрештою, додавати одиничні тести для того, щоб захопити поточну функціональність джерел. Після того, як ви отримаєте це, ви будете готові перефактурувати код, тому що у вас з’являться твердження та помилки, що з’являться, попереджаючи вас, якщо ви щось забули.

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


4

Ви не повинні займатися зменшенням розміру файлу, а скоріше зменшенням розміру класу. Вона зводиться до майже те ж саме, але змушує поглянути на проблему під іншим кутом (як @ Брайан Расмуссен передбачає , ваш клас , схоже, доведеться багато обов'язків).


Як завжди, я хотів би отримати пояснення щодо протистояння.
Бьорн Поллекс

4

У вас є класичний приклад відомого дизайнерського антипатерна, який називається blob . Знайдіть трохи часу, щоб прочитати статтю, яку я вказую тут, і, можливо, ви можете знайти щось корисне. Крім того, якщо цей проект настільки великий, як виглядає, слід розглянути певний дизайн, щоб запобігти зростанню коду, який ви не можете контролювати.


4

Це не відповідь на велику проблему, а на теоретичне рішення конкретної частини:

  • З'ясуйте, де ви хочете розділити великий файл на підфайли. Поставте коментарі в якомусь спеціальному форматі в кожному з цих пунктів.

  • Напишіть досить тривіальний сценарій, який розірве файл у підсистемах у цих точках. (Можливо, в спеціальних коментарях є вбудовані імена файлів, які сценарій може використовувати як інструкції, як розділити його.) Він повинен зберігати коментарі як частину розбиття.

  • Запустіть сценарій. Видаліть вихідний файл.

  • Коли вам потрібно об'єднатись із гілкою, спочатку відтворіть великий файл, об'єднавши шматки назад, зробіть злиття, а потім повторно розділіть його.

Крім того, якщо ви хочете зберегти історію файлів SCC, я очікую, що найкращий спосіб зробити це - сказати вашій системі управління джерелом, що окремі файли є копіями оригіналу. Тоді він збереже історію розділів, які зберігалися в цьому файлі, хоча, звичайно, він також запише, що великі частини були "видалені".


4

Одним із способів розділити його без надто великої небезпеки буде історичний погляд на всі зміни лінії. Чи існують певні функції, стабільніші за інші? Гарячі місця змін, якщо ви хочете.

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


Я думаю, інші пропонували подібні речі. Це коротко і до речі, і я думаю, що це може бути дійсною відправною точкою для початкової проблеми.
Мартін Ба

3

Нічого собі, чудово звучить. Я думаю, що пояснити вашому начальникові, що вам потрібно багато часу, щоб переробити звіра, варто спробувати. Якщо він не погоджується, вихід - це варіант.

У будь-якому випадку, те, що я пропоную, це в основному викинути всю реалізацію та перегрупувати її в нові модулі, назвемо ці "глобальні служби". "Головний модуль" буде пересилати лише до цих служб, і будь-який новий код, який ви пишете, використовуватиме їх замість "головного модуля". Це повинно бути здійснено за розумну кількість часу (оскільки це здебільшого копіювання та вставка), ви не порушуєте існуючий код і можете робити це по одній версії обслуговування за один раз. І якщо у вас ще залишається якийсь час, ви можете витратити його на рефакторинг усіх старих залежних модулів, щоб також користуватися глобальними послугами.


3

Мої симпатії - на попередній роботі я стикався з подібною ситуацією з файлом, який був у кілька разів більшим, ніж той, з яким вам доводиться мати справу. Рішення було:

  1. Напишіть код, щоб вичерпно перевірити функцію відповідної програми. Здається, у вас цього вже не буде в руках ...
  2. Визначте який-небудь код, який можна абстрагувати до класу помічників / утиліт. Не потрібно бути великим, просто щось, що насправді не є частиною вашого «основного» класу.
  3. Refactor код, ідентифікований у 2., виділяється в окремий клас.
  4. Перезапустіть свої тести, щоб нічого не було порушено.
  5. Коли у вас є час, перейдіть до 2. і повторіть, як потрібно, щоб зробити код керованим.

Класи, які ви будуєте на етапі 3., ітерації, ймовірно, зростатимуть, щоб поглинати більше коду, відповідного їх недавно очищеній функції.

Я також можу додати:

0: купити книгу Майкла Пір'я про роботу зі спадковим кодом

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


2

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

Якщо ви визначили дієве рішення, перефактуруйте програму відповідно.

Якщо всі спроби створити більш раціональну архітектуру не вдаються, то, принаймні, ви знаєте, що рішення, ймовірно, полягає у перегляді функціональності програми.


+1 - перепишіть його у свій час, хоча в іншому випадку хтось може плювати їх манекен.
Джон Блек

2

Мої 0,05 євроцентів:

Перепроектуйте весь безлад, розділіть його на підсистеми, враховуючи технічні та ділові вимоги (= багато паралельних треків технічного обслуговування з потенційно різною базою коду для кожного, очевидно, необхідність у високій модифікованості тощо).

Розбиваючи на підсистеми, проаналізуйте місця, які найбільше змінилися, і відокремте їх від незмінних частин. Це повинно показати вам проблеми. Відокремте найбільш мінливі частини до власних модулів (наприклад, dll) таким чином, що API модуля можна зберегти недоторканим і вам не потрібно весь час ламати BC. Таким чином, ви можете розгорнути різні версії модуля для різних відділів технічного обслуговування, якщо це потрібно, не змінюючи ядро.

Редизайн, ймовірно, повинен бути окремим проектом, спроба зробити це до рухомої цілі не вийде.

Щодо історії вихідного коду, моя думка: забудьте про новий код. Але зберігайте історію десь, щоб ви могли її перевірити, якщо потрібно. Б'юсь об заклад, вам це не знадобиться так сильно після початку.

Вам, швидше за все, потрібно отримати управлінську плату за цей проект. Ви можете посперечатися, можливо, з швидшим часом розробки, меншою кількістю помилок, легшим обслуговуванням та меншим загальним хаосом. Щось у руслі "Проактивно забезпечуйте надійність та життєздатність наших критичних програмних засобів" :)

Саме так я б почав вирішувати цю проблему хоча б.


2

Почніть з додавання коментарів до нього. З посиланням на те, куди викликаються функції та чи можна переміщувати речі. Це може змусити речі рухатися. Вам дійсно потрібно оцінити, наскільки крихкою є його база. Потім перемістіть спільні біти функціональності разом. Невеликі зміни за один раз.



2

Щось мені здається корисним (і я це роблю зараз, хоча не в масштабі, з яким ви стикаєтеся), - це вилучення методів у вигляді класів (перетворення об'єктів методу). Методи, які відрізняються у різних версіях, стануть різними класами, які можна вводити в загальну базу для забезпечення різної поведінки, яка вам потрібна.


2

Я вважаю це речення найцікавішою частиною вашої публікації:

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

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

По-друге, я створив би десять відділень (по одному для кожної з ваших версій технічного обслуговування).

Я вже відчуваю, як ти скулився Але або ваш джерело управління не працює для вашої ситуації через відсутність функцій, або він не використовується правильно.

Тепер до галузі, над якою ви працюєте, - рефактор, як вважаєте за потрібне, впевнений, що ви не будете засмучувати інші дев'ять гілок вашого продукту.

Мене трохи потурбує те, що ви так багато в своїй головній () функції.

У будь-яких написаних нами проектах я б використовував main () лише виконувати ініціалізацію основних об'єктів - як, наприклад, об'єкта моделювання чи додатка - ці класи повинні продовжувати реальні роботи.

Я б також ініціалізував об'єкт реєстрації додатків головним чином для глобального використання у всій програмі.

Нарешті, в основному я також додаю код виявлення витоку в блоках препроцесора, які гарантують, що він увімкнено лише у збірках DEBUG. Це все, що я додав би до main (). Головна () повинна бути короткою!

Ти це кажеш

> Файл в основному містить "основний клас" (основна внутрішня робота диспетчеризації та координації) нашої програми

Здається, що ці два завдання можна розділити на два окремих об'єкти - координатора та диспетчера роботи.

Коли ви розділите їх, ви можете зіпсувати ваш "робочий процес SCC", але це здається, що чітке дотримання робочого процесу в SCC викликає проблеми з технічним обслуговуванням. Відкиньте його зараз і не озирайтеся назад, бо як тільки ви це поправите, ви почнете спати легко.

Якщо ви не в змозі прийняти рішення, поборіть зуби і нігті зі своїм менеджером за це - ваша програма потребує реконструкції - і погано під звуки цього! Не приймайте "ні" за відповідь!


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

@peterchen - саме проблема. SCC зливаються на рівні файлів. (Тристороння злиття) Якщо ви переміщуєте код між файлами, вам доведеться починати вручну перетворювати модифіковані блоки коду з одного файлу в інший. (Особливість GIT, яку ще хтось згадував в іншому коментарі, просто корисна для історії, а не для злиття, наскільки я можу сказати)
Martin Ba

2

Як ви вже описували, головна проблема полягає в тому, що відрізняється попереднім розбиттям та після поділу, об'єднанням у виправлення помилок тощо. Інструмент навколо нього. Для жорсткого кодування сценарію в Perl, Ruby тощо не знадобиться стільки часу, щоб вирвати більшу частину шуму від різного попереднього спліту проти об'єднання пост-спліт. Робіть все, що найпростіше з точки зору поводження з шумом:

  • видалити певні лінії перед / під час конкатенації (наприклад, включити охорону)
  • при необхідності видаліть інші матеріали з різного виводу

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


2
  1. Ніколи більше не торкайтесь цього файлу та коду!
  2. Лікувати - як щось, до чого ти застряг. Почніть писати адаптери для функціональності, закодованого там.
  3. Пишіть новий код у різних підрозділах і розмовляйте лише з адаптерами, які інкапсулюють функціональність монстра.
  4. ... якщо тільки один із перерахованих вище неможливий, киньте роботу та отримайте нову.

2
+/- 0 - серйозно, де ви живете людям, яким рекомендуєте залишити роботу на основі такої технічної деталі?
Мартін Ба

1

"Файл, в основному, містить" основний клас "(основна внутрішня робота диспетчеризації та координації) нашої програми, тому кожен раз, коли додається функція, вона також впливає на цей файл і кожного разу, коли він росте."

Якщо цей великий SWITCH (який я думаю, що є) стає основною проблемою технічного обслуговування, ви можете повернути його до використання словника та шаблону Command та видалити всю логіку перемикання з існуючого коду до завантажувача, який заповнює цю карту, тобто:

    // declaration
    std::map<ID, ICommand*> dispatchTable;
    ...

    // populating using some loader
    dispatchTable[id] = concreteCommand;

    ...
    // using
    dispatchTable[id]->Execute();

2
Ні, насправді немає великого перемикача. Присудок - це найближче, до якого я можу підійти, щоб описати цей безлад :)
Мартін Ба,

1

Я думаю, що найпростіший спосіб відстежити історію джерела при розділенні файлу був би приблизно таким:

  1. Зробіть копії оригінального вихідного коду, використовуючи будь-які команди копіювання, що зберігають історію, які надає ваша система SCM. Напевно, вам потрібно буде подати цей момент, але поки що немає потреби повідомляти свою систему побудови про нові файли, так що це повинно бути нормально.
  2. Видалити код з цих копій. Це не повинно ламати історію для ліній, які ви зберігаєте.

"використання будь-яких команд копіювання, що зберігають історію, надає ваша система SCM" ... погано, що вона не надає
Мартін Ба,

Дуже погано. Це одне звучить як вагомий привід перейти на щось більш сучасне. :-)
Крістофер Кройцгіг

1

Я думаю, що я зробив би в цій ситуації кулю і:

  1. З'ясуйте, як я хотів розділити файл наверх (на основі поточної версії розробки)
  2. Поставте адміністративний замок на файл ("Ніхто не торкається mainmodule.cpp після п'ятниці о 17:00 !!!"
  3. Проведіть довгі вихідні, застосувавши цю зміну до> 10 версій технічного обслуговування (від найдавнішої до новітньої), аж до поточної версії та включаючи її.
  4. Видаліть mainmodule.cpp з усіх підтримуваних версій програмного забезпечення. Це новий вік - більше немає mainmodule.cpp.
  5. Переконайте керівництво, що ви не повинні підтримувати більше однієї версії технічного обслуговування програмного забезпечення (принаймні, без великого контракту на підтримку $$$). Якщо кожен з ваших клієнтів має свою унікальну версію .... yeeeeeshhhh. Я б додав директиви компілятора, а не намагався підтримувати 10+ вил.

Відстеження старих змін у файлі просто вирішується вашим першим коментарем при реєстрації, де йдеться про "поділ від mainmodule.cpp". Якщо вам потрібно повернутися до чогось недавнього, більшість людей запам'ятають зміну, якщо минув 2 рік, коментар підкаже їм, де їх шукати. Звичайно, наскільки цінним буде повернутися більше ніж на 2 роки, щоб подивитися, хто змінив код і чому?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.