Как моя команда может избежать частых ошибок после рефакторинга?


20

Чтобы дать вам небольшую предысторию: я работаю в компании с примерно двенадцатью разработчиками Ruby on Rails (+/- стажеры). Удаленная работа является распространенным явлением. Наш продукт состоит из двух частей: довольно толстое ядро ​​и рассчитано на крупные клиентские проекты, основанные на нем. Заказчики обычно расширяют ядро. Перезаписи ключевых функций не происходит. Я мог бы добавить, что ядро ​​имеет несколько довольно плохих частей, которые нуждаются в срочной реорганизации. Есть спецификации, но в основном для клиентских проектов. Худшая часть ядра не проверена (не так, как должно быть ...).

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

Теперь наша проблема: довольно часто мы ломаем вещи друг друга. Кто-то из команды A расширяет или реорганизует основную функцию Y, вызывая непредвиденные ошибки в одном из клиентских проектов команды B. В основном, изменения не объявляются по командам, поэтому ошибки появляются почти всегда неожиданно. Команда B, включая ПО, считала функцию Y стабильной и не тестировала ее перед выпуском, не подозревая об изменениях.

Как избавиться от этих проблем? Какую «технику объявления» вы можете мне порекомендовать?


34
Очевидный ответ - TDD .
Мувисиэль

1
Как приходит вы заявляете , что «Перезапись ключевых особенностей не бывает», и тогда ваша проблема в том , что это действительно произойдет? Различаете ли вы в своей команде «основные» и «ключевые функции», и как вы это делаете? Просто пытаюсь понять ситуацию ...
logc

4
@mouvciel Это и не использовать динамическую типизацию , но этот конкретный совет приходит слишком поздно в этом случае.
Довал

3
Используйте строго типизированный язык, такой как OCaml.
Гай

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

Ответы:


24

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

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

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


В «Мифическом человеко-месяце» я прочитал, что структура кода обычно соответствует структуре команды / организации. Таким образом, это на самом деле не «плохая практика», а просто обычные вещи.
Марсель

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

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

Ну, насколько я понял ОП, он заявил, что команды не разделены на основную и неосновную команду. Команды разделены «на клиента», что по сути «на функциональную область». И это часть проблемы: поскольку всем командам разрешено менять общее ядро, изменения одной команды влияют на другую.
Док Браун

@DocBrown Вы правы. Каждая команда может изменить ядро. Конечно, эти изменения должны быть полезны для каждого проекта. Тем не менее, они работают на разные резервы. У нас есть один для каждого клиента и один для ядра.
SDD64

41

Мы худшие части ядра не проверены (как и должно быть ...).

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


10
Это и рефакторинг в детских шагах и совершение очень часто.
Стефан Биллиет

1
Вероятно, есть множество советов, которые могли бы добавить сюда советы, но все это сводится к этому моменту. Что бы ни касалось шутки OP «как должно быть», показывающей, что они знают, что это само по себе проблема, влияние скриптового тестирования на рефакторинг огромно: если проход стал неудачным, то рефакторинг не сработал. Если все проходы остаются проходами, то рефакторинг мог бы сработать (переход отказов к проходам, очевидно, был бы плюсом, но сохранение всех проходов в качестве проходов важнее, чем даже нетто-выигрыш; изменение, которое нарушает один тест и исправляет пять, может быть улучшение, но не рефакторинг)
Джон Ханна

Я дал вам «+1», но я думаю, что «автоматизированные тесты» - не единственный подход к решению этой проблемы. Лучшее руководство, но систематический контроль качества, возможно, отдельной группой контроля качества, мог бы также решить проблемы с качеством (и, вероятно, имеет смысл проводить как автоматические, так и ручные тесты).
Док Браун

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

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

5

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


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

5

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

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

Тогда «политика объявления» сводится к наличию файла CHANGES вместе с каждым выпуском или к совещанию группы, на котором объявляются все функции каждого нового основного выпуска.

Кроме того, я думаю, что вам нужно лучше определить, что такое «ядро», а какое подмножество этого «ключ». Кажется, вы (правильно) избегаете внесения многих изменений в «ключевые компоненты», но допускаете частые изменения в «ядре». Чтобы на что-то положиться, вы должны сохранять его стабильность; если что-то не стабильно, не называйте это ядром. Может быть, я мог бы предложить назвать его «вспомогательными» компонентами?

РЕДАКТИРОВАТЬ : Если вы следуете соглашениям в системе семантического управления версиями , то любое несовместимое изменение в API ядра должно быть отмечено значительным изменением версии . То есть, когда вы изменяете поведение ранее существующего ядра или удаляете что-то, а не просто добавляете что-то новое. При таком соглашении разработчики знают, что обновление с версии «1.1» до «1.2» безопасно, но переход с «1.X» на «2.0» является рискованным и требует тщательного изучения.

1: я думаю, что это называется гем в мире Ruby
2: эквивалент Nexus в Java или PyPI в Python


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

@DocBrown: я согласен с вами, но я написал, исходя из предположения, что все разработчики являются кооперативными и взрослыми. Это не значит, что я не видел, что вы описываете . Но ключевой частью создания надежной системы является стремление к стабильности. Кроме того, если команде A нужно изменить X в ядре, а команде B нужно изменить X в ядре, то, возможно, X не принадлежит ядру; Я думаю, что это моя другая точка зрения. :)
logc

@DocBrown Да, мы научились использовать одну ветвь ядра для каждого проекта клиента. Это вызвало некоторые другие проблемы. Например, нам не нравится «трогать» уже развернутые системы клиентов. В результате они могут столкнуться с несколькими незначительными скачками версий своего используемого ядра после каждого развертывания.
SDD64

@ SDD64: это именно то, о чем я говорю, - немедленная интеграция изменений в общее ядро ​​не является решением проблемы. Что вам нужно, так это лучшая стратегия тестирования вашего ядра - с автоматическими и ручными тестами.
Док Браун

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

3

Как говорили другие люди, хороший набор модульных тестов не решит вашу проблему: у вас будут проблемы при объединении изменений, даже если каждый набор командных тестов пройдёт.

То же самое для TDD. Я не вижу, как это может решить это.

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


У нас была «сторожевая собака», так как он написал большую часть ядра. К сожалению, он также был ответственным за большинство непроверенных частей. Он был олицетворением YAGNI и был заменен полгода назад двумя другими парнями. Мы до сих пор боремся за рефакторинг этих «темных частей».
SDD64

2
Идея состоит в том, чтобы иметь набор модульных тестов для ядра , который является частью ядра , с участием всех команд, а не отдельных наборов тестов для каждой команды.
Док Браун

2
@ SDD64: вы, кажется, путаете «Тебе это не нужно (пока)» (что очень хорошо) с «Тебе не нужно чистить свой код (пока)» - что крайне вредная привычка и ИМХО совсем наоборот.
Док Браун

Решение сторожевого таймера действительно, действительно неоптимально, ИМХО. Это похоже на встраивание единой точки отказа в вашу систему, и, кроме того, очень медленной, поскольку в ней задействованы человек и политика. В противном случае 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

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

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

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

  • с людьми. Это означает, что ваша компания назначает кого-то в качестве архитектора основного модуля (или любого другого языка, который хорош для высшего руководства), который будет отвечать за качество и доступность кода. Этот человек воплотит ядро. Таким образом, она будет разделена между всеми командами и обеспечит правильную синхронизацию между ними. Кроме того, она также должна выступать в качестве рецензента кода, привязанного к основному модулю, для поддержания его согласованности;
  • с инструментами и рабочими процессами. Внедряя непрерывную интеграцию в ядро, вы сделаете сам код ядра коммуникационной средой. Сначала это потребует определенных усилий (путем добавления к нему автоматических тестовых наборов), но затем ночные отчеты CI будут представлять собой общее обновление статуса основного модуля.

Вы можете найти больше информации о КИ как процессе общения здесь .

Наконец, у вас все еще есть проблема с отсутствием командной работы на уровне компании. Я не большой поклонник событий тимбилдинга, но это похоже на случай, когда они будут полезны. У вас есть встречи на уровне разработчиков на регулярной основе? Можете ли вы пригласить людей из других команд на ретроспективу вашего проекта? Или, может быть, иногда есть пиво в пятницу вечером?

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.