Як моя команда може уникнути частих помилок після рефакторингу?


20

Щоб трохи ознайомитись: я працюю в компанії з приблизно дванадцятьма розробниками Ruby on Rails (+/- інтернів). Віддалена робота є загальною. Наш продукт складається з двох частин: досить жирне серцевина та тонка до великих замовницьких проектів, побудованих на ньому. Клієнтські проекти зазвичай розширюють ядро. Переписування ключових особливостей не відбувається. Я можу додати, що в ядрі є деякі досить погані частини, які потребують нагальної реконструкції. Є специфікації, але в основному для замовницьких проектів. Найгірша частина ядра неперевірена (не так, як це має бути ...).

Розробники розбиваються на дві команди, що працюють з одним або двома ПО на кожен спринт. Зазвичай один проект замовника суворо асоціюється з однією з команд та ОП.

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

Як позбутися від цих проблем? Яку "техніку оголошення" ви можете мені порадити?


34
Очевидна відповідь - TDD .
mouviciel

1
Як приходить ви заявляєте , що «Перезапис ключових особливостей не буває», і тоді ваша проблема в тому , що це дійсно станеться? Чи відрізняєте у своїй команді між "основними" та "ключовими характеристиками", і як це робити? Просто намагаюся зрозуміти ситуацію ...
logc

4
@mouvciel Це і не використовуйте динамічний набір тексту , але конкретна порада в цьому випадку надто пізно.
Doval

3
Використовуйте сильно набрану мову, наприклад OCaml.
Гай

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

Відповіді:


24

Я б рекомендував прочитати « Ефективна робота зі спадковим кодексом» Майкла С. Пір’я . Це пояснює, що вам справді потрібні автоматизовані тести, як ви можете легко їх додати, якщо у вас їх ще немає, і яким "кодом пахне" рефактором яким чином.

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

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


Я читав у "Міфічному людині-місяці", що структура коду зазвичай відповідає структурі команди / організації. Таким чином, це насправді не «погана практика», а просто такий спосіб, як зазвичай йдуть справи.
Марсель

Думаю, що в « Динаміці розробки програмного забезпечення » менеджер Visual C ++ рекомендує яскраво мати функціональні команди; Я не читав "Міфічного чоловіка-місяця", @Marcel, але в AFAIK у ньому перераховані погані практики в галузі ...
logc

Марсель, правда, саме таким чином зазвичай йдуть і йдуть справи, але все більше і більше команд роблять це по-різному, наприклад, в командах. Наявність у складі команд на основі компонентів призводить до браку спілкування під час роботи над функціями перехресних компонентів. Крім цього, це майже завжди призведе до архітектурних дискусій, що не ґрунтуються на призначенні гарної архітектури, а люди, які намагаються перекласти відповідальність на інші команди / компоненти. Отже, ви отримаєте ситуацію, описану автором цього питання. Дивіться також mounttaingoatsoftware.com/blog/the-benefits-of-feature-teams .
Тонмайстер

Ну, наскільки я зрозумів ОП, він заявив, що команди не розбиті на основну та непрофільну команду. Команди розбиті "на кожного клієнта", що по суті є "за функціональним доменом". І це частина проблеми: оскільки всім командам дозволено змінювати загальну основу, зміни однієї команди впливають на іншу.
Док Браун

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

41

У нас найгірша частина ядра неперевірена (як це має бути ...).

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


10
Це і рефакторинг в дитячих кроках і вчинення дуже часто.
Стефан Біллієт

1
Тут, мабуть, є поради, які могли б додати поради, але це все зведеться до цього моменту. Що б то не було про жарти "ОП", як це має бути ", показуючи, що вони знають, що це проблема сама по собі, вплив тестування сценаріїв на рефакторинг величезний: Якщо пропуск став провальним, то рефакторинг не спрацював. Якщо всі пропуски залишаються пропусками, то рефакторинг, можливо , спрацював (переміщення не вдалося пройти, очевидно, було б плюсом, але зберігання всіх пропусків як пропусків важливіше, ніж навіть коефіцієнт підсилення; зміна, яка порушує один тест і виправляє п'ять, може бути поліпшення, але не рефакторинг)
Джон Ханна

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

Хороший момент, але якщо ядро ​​та замовницькі проекти - це окремі модулі (і, тим більше, у динамічній мові, як Ruby), то ядро ​​може змінити як тест, так і пов’язану з ним реалізацію , і зламати залежний модуль, не провалюючи власні тести.
logc

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

5

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


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

5

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

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

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

Крім того, я думаю, вам потрібно краще визначити, що таке "ядро", а який підмножина - "ключ". Ви, здається, (правильно) уникаєте вносити багато змін у "ключові компоненти", але ви допускаєте часті зміни "основних". Для того, щоб на щось покластися, ви повинні тримати це стабільним; якщо щось не є стабільним, не називайте це стрижнем. Можливо, я міг би запропонувати називати це "допоміжними" компонентами?

РЕДАКТУВАННЯ : Якщо ви дотримуєтеся умов у системі Semantic Versioning , то будь-яка несумісна зміна API API повинна бути відзначена суттєвою зміною версії . Тобто, коли ви змінюєте раніше існуючу поведінку ядра або щось видаляєте, а не просто додаєте щось нове. Згідно з цією умовою, розробники знають, що оновлення з версії '1.1' до '1.2' є безпечною, але перехід від '1.X' до '2.0' є ризиковим і потребує ретельного перегляду.

1: Я думаю, що у світі Ruby 2 це називається дорогоцінним каменем
: еквівалент Nexus на Java або PyPI на Python


Дійсно, "версія" є важливою, але коли намагається вирішити описану проблему, заморозивши ядро ​​перед випуском, то ви з легкістю закінчитеся з необхідністю складного розгалуження та об'єднання. Аргументація полягає в тому, що під час фази "нарощування випуску" команди A, A, можливо, доведеться змінити ядро ​​(принаймні, для виправлення помилок), але не прийме змін до ядра від інших команд - тож ви закінчите одну гілку ядро в кожній команді, яке має бути об'єднане "пізніше", що є формою технічної заборгованості. Іноді це нормально, але часто це просто відкладає описану проблему на більш пізній час.
Doc Brown

@DocBrown: Я згоден з вами, але я писав під припущенням, що всі розробники співпрацюють і дорослі. Це не означає, що я не бачив того, що ви описуєте . Але ключовою частиною зробити систему надійною є, ну, прагнення до стабільності. Крім того, якщо команді А потрібно змінити X в ядрі, а команді B потрібно змінити X в ядрі, то, можливо, X не належить до ядра; Я думаю, що це мій інший пункт. :)
logc

@DocBrown Так, ми навчилися використовувати одну гілку ядра для кожного проекту клієнта. Це спричинило деякі інші проблеми. Наприклад, ми не любимо "торкатися" вже розгорнутих клієнтських систем. Як результат, вони можуть зіткнутися з декількома незначними стрибками версії використовуваного ядра після кожного розгортання.
SDD64

@ SDD64: саме це я і говорю - не інтегрування змін відразу до загального ядра також не є рішенням у довгостроковій перспективі. Вам потрібна краща стратегія тестування вашого ядра - також з автоматичними та ручними тестами.
Doc Brown

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

3

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

Те саме для TDD. Я не бачу, як це може вирішити це.

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


У нас був "сторожовий собака", оскільки він написав більшу частину ядра. На жаль, він також відповідав за більшість неперевірених частин. Він був підроблений ЯГНІ і його півроку тому замінили двоє інших хлопців. Ми все ще намагаємось відновити ці "темні частини".
SDD64

2
Ідея полягає у створенні одиничного тестового набору для ядра , що є частиною ядра , із внесками всіх команд, а не окремих тестових наборів для кожної команди.
Doc Brown

2
@ SDD64: ти, мабуть, плутаєш "Ти вже не потребуєш цього" (що дуже гарна річ) із "Вам не потрібно чистити код (поки)" - що є надзвичайно поганою звичкою , а ІМХО зовсім навпаки.
Док Браун

Рішення сторожової служби насправді, дуже субоптимальне, IMHO. Це як побудова єдиної точки відмови у вашій системі, а крім цього дуже повільної, бо вона передбачає людину та політику. Інакше TDD може, звичайно, допомогти у вирішенні цієї проблеми: кожен тест на ядро ​​є прикладом для розробників проектів клієнтів, як передбачається використовувати поточне ядро. Але я думаю, ви відповіли добросовісно ...
logc

@DocBrown: Гаразд, можливо, наші розуміння відрізняються. Основні риси, написані ним, надмірно складні, щоб задовольнити навіть найдивніші можливості. Більшість із них ми жодного разу не стикалися. Складність сповільнює нас, щоб відновлювати їх, з іншого боку.
SDD64

2

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


2

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

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

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

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

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


Перш ніж ми перенесли наш продукт з Java на RoR, ми насправді зробили так, як ви запропонували. У нас один був ядро ​​Java для всіх клієнтів, але їхні вимоги одного разу «порушили», і нам довелося розділити його. Під час цієї ситуації ми стикалися з проблемами на кшталт: "Чувак, клієнт Y має таку приємну основну особливість. Шкода, що ми не можемо передати його клієнту Z, тому що їх ядро ​​несумісне '. За допомогою Rails ми суворо хочемо піти на політику «єдиного ядра для всіх». Якщо це має бути, ми все ще пропонуємо кардинальні зміни, але вони відволікають клієнта від подальших оновлень.
SDD64

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

1

Усі ми знаємо, що одиничні тести - це дорога. Але ми також знаємо, що реально ретро підлаштувати їх до основи важко.

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

Оригінальний псевдо код:

def someFunction
   do original stuff
   return result
end

Тимчасовий тестовий код:

def someFunctionNew
   new do stuff
   return result
end

def someFunctionOld
   do original stuff
   return result
end

def someFunction
   oldResult = someFunctionOld
   newResult = someFunctionNew
   check oldResult = newResult
   return newResult
end

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


1

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

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

Без усього цього, одиничні тести не допоможуть багато, окрім того, як на останніх етапах зафіксують, що між частинами системи щось не збито. Ви хочете це знати, але ви хочете дізнатися це рано, дуже рано, і команди, що спілкуються між собою, координують зусилля, і насправді мають частий доступ до роботи, яку виконує інша команда (тому регулярні доручення, а не одна велика здійснювати через кілька тижнів або місяців, за 1-2 дні до пологів).
Ваша помилка НЕ ​​в коді, звичайно, не в коді іншої команди, яка не знала, що ви возиться з інтерфейсом, про який пишете. Ваша помилка у вашому процесі розвитку, відсутність спілкування та співпраці між людьми. Тільки те, що ти сидиш у різних кімнатах, не означає, що слід ізолювати себе від інших хлопців.


1

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

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

Тож ми залишаємося з питанням спроб покращити комунікацію між командами. Це можна вирішити двома способами:

  • з людьми. Це означає, що ваша компанія визначає когось як основного архітектора модуля (або будь-якого lingo, що підходить для вищого керівництва), який буде відповідати за якість та доступність коду. Ця людина втілить серцевину. Таким чином, вона поділиться всіма командами та забезпечить належну синхронізацію між ними. Крім того, вона також повинна виступати в ролі рецензента коду, присвяченого основного модуля для підтримки його узгодженості;
  • з інструментами та робочими процесами. Наклавши на ядро неперервну інтеграцію , ви зробите сам кодовий код комунікаційним носієм. Спочатку для цього знадобляться певні зусилля (додавання в нього автоматизованих тестових наборів), але потім щомісячні звіти про ІС стануть грубим оновленням стану основного модуля.

Докладніше про CI як процес комунікації ви можете знайти тут .

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

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