Aby dać ci trochę tła: Pracuję dla firmy z około dwunastoma programistami Ruby on Rails (stażyści +/-). Praca zdalna jest powszechna. Nasz produkt składa się z dwóch części: raczej grubego rdzenia i zbudowanych na nim dużych projektów klientów. Projekty klientów zwykle rozszerzają rdzeń. Zastąpienie kluczowych funkcji nie występuje. Mógłbym dodać, że rdzeń ma kilka dość złych części, które pilnie wymagają refaktoryzacji. Istnieją specyfikacje, ale głównie dla projektów klientów. Najgorsza część rdzenia jest nieprzetestowana (nie tak, jak powinna być ...).
Programiści są podzieleni na dwa zespoły, pracujące z jednym lub dwoma PO dla każdego sprintu. Zwykle jeden projekt klienta jest ściśle powiązany z jednym z zespołów i organizacji producentów.
Teraz nasz problem: raczej często psujemy się nawzajem. Ktoś z Zespołu A rozszerza lub refaktoryzuje podstawową funkcję Y, powodując nieoczekiwane błędy w jednym z projektów klientów Zespołu B. Przeważnie zmiany nie są ogłaszane przez zespoły, więc błędy trafiają prawie zawsze nieoczekiwanie. Zespół B, w tym PO, uważał, że funkcja Y jest stabilna i nie wypróbował jej przed wydaniem, nieświadomy zmian.
Jak pozbyć się tych problemów? Jaką „technikę ogłoszenia” możesz mi polecić?
Odpowiedzi:
Polecam lekturę Working Effective with Legacy Code autorstwa Michaela C. Feathersa . Wyjaśnia, że naprawdę potrzebujesz automatycznych testów, jak możesz je łatwo dodać, jeśli jeszcze ich nie masz, i jaki „zapach śmierdzi”, aby refaktoryzować w jaki sposób.
Poza tym kolejnym zasadniczym problemem w twojej sytuacji wydaje się brak komunikacji między dwoma zespołami. Jak duże są te zespoły? Czy pracują na różnych zaległościach?
Prawie zawsze złą praktyką jest dzielenie zespołów w zależności od architektury. Np. Zespół podstawowy i zespół inny niż podstawowy. Zamiast tego tworzyłbym zespoły w domenie funkcjonalnej, ale wieloskładnikowej.
źródło
To jest problem. Wydajne refaktoryzacja zależy w dużej mierze od zestawu zautomatyzowanych testów. Jeśli ich nie masz, zaczynają się pojawiać problemy, które opisujesz. Jest to szczególnie ważne, jeśli używasz dynamicznego języka, takiego jak Ruby, gdzie nie ma kompilatora do wychwytywania podstawowych błędów związanych z przekazywaniem parametrów do metod.
źródło
Poprzednie odpowiedzi wskazujące na lepsze testy jednostkowe są dobre, ale uważam, że mogą być bardziej podstawowe kwestie do rozwiązania. Potrzebujesz przejrzystych interfejsów, aby uzyskać dostęp do kodu podstawowego z kodu dla projektów klientów. W ten sposób, jeśli refaktoryzujesz kod podstawowy bez zmiany zachowania obserwowanego przez interfejsy , kod drugiego zespołu nie ulegnie uszkodzeniu. Dzięki temu znacznie łatwiej będzie wiedzieć, co można „bezpiecznie” zreformować, a co wymaga przeprojektowania, być może zepsucia interfejsu.
źródło
Inne odpowiedzi uwypukliły ważne punkty (więcej testów jednostkowych, zespołów funkcji, czyste interfejsy do podstawowych komponentów), ale brakuje mi jednego punktu, jakim jest wersjonowanie.
Jeśli zamrozisz zachowanie swojego rdzenia, wykonując wydanie 1 i umieścisz to wydanie w prywatnym systemie zarządzania artefaktami 2 , wówczas każdy projekt klienta może zadeklarować swoją zależność od wersji podstawowej X i nie zostanie zepsuty w następnej wersji X + 1 .
„Zasady ogłaszania” ograniczają się do posiadania pliku ZMIANY wraz z każdym wydaniem lub spotkania zespołu, aby ogłosić wszystkie funkcje każdego nowego wydania podstawowego.
Ponadto uważam, że musisz lepiej zdefiniować, co jest „rdzeniem”, a jaki jego podzbiór to „klucz”. Wydaje się (poprawnie) unikać wprowadzania wielu zmian w „kluczowych komponentach”, ale pozwalasz na częste zmiany w „rdzeniu”. Aby na czymś polegać, musisz zachować stabilność; jeśli coś nie jest stabilne, nie nazywaj tego rdzeniem. Może mógłbym zasugerować nazywanie go komponentami pomocniczymi?
EDYCJA : Jeśli postępujesz zgodnie z konwencjami w systemie wersjonowania semantycznego , każda niekompatybilna zmiana w interfejsie API rdzenia musi być oznaczona poważną zmianą wersji . Oznacza to, że gdy zmienisz zachowanie istniejącego rdzenia lub usuniesz coś, nie tylko dodasz coś nowego. Dzięki tej konwencji programiści wiedzą, że aktualizacja z wersji „1.1” do „1.2” jest bezpieczna, ale przejście z wersji „1.X” do „2.0” jest ryzykowne i należy ją uważnie przejrzeć.
1: Myślę, że nazywa się to klejnotem w świecie Ruby
2: odpowiednik Nexusa w Javie lub PyPI w Pythonie
źródło
Jak powiedzieli inni ludzie, dobry zestaw testów jednostkowych nie rozwiąże twojego problemu: będziesz mieć problem z scalaniem zmian, nawet jeśli każdy zespół testowy przejdzie pomyślnie.
To samo dotyczy TDD. Nie wiem, jak to rozwiązać.
Twoje rozwiązanie jest nietechniczne. Musisz jasno zdefiniować granice „rdzenia” i przypisać komuś rolę „stróżującego psa”, niezależnie od tego, czy jest to główny projektant, czy architekt. Wszelkie zmiany rdzenia muszą przejść przez ten organ nadzorczy. Jest odpowiedzialny za to, aby wszystkie wyniki wszystkich zespołów zostały połączone bez nadmiernych szkód dodatkowych.
źródło
Jako długoterminową poprawkę potrzebujesz także lepszej i terminowej komunikacji między zespołami. Każdy zespół, który kiedykolwiek wykorzysta, na przykład podstawową funkcję Y, musi być zaangażowany w budowę planowanych testów dla tej funkcji. Planowanie samo w sobie uwypukli różne przypadki użycia związane z funkcją Y między dwoma zespołami. Po ustaleniu sposobu działania funkcji i wdrożeniu i uzgodnieniu przypadków testowych wymagana jest dodatkowa zmiana w schemacie implementacji. Zespół wypuszczający tę funkcję jest wymagany do uruchomienia zestawu testowego, a nie zespół, który zamierza go użyć. Zadaniem, które powinno spowodować kolizje, jest dodanie nowej skrzynki testowej od jednego z zespołów. Gdy członek zespołu pomyśli o nowym aspekcie funkcji, która nie jest testowana, powinni mieć swobodę dodawania testowej skrzynki, którą zweryfikowali, przekazując do swojej piaskownicy. W ten sposób jedyne kolizje, które będą miały miejsce, będą na zamierzonym poziomie i powinny zostać przybite, zanim zmieniona funkcja zostanie wypuszczona na wolność.
źródło
Chociaż każdy system potrzebuje skutecznych pakietów testowych (co oznacza między innymi automatyzację) i chociaż testy te, jeśli są stosowane skutecznie, wychwytują te konflikty wcześniej niż są teraz, nie rozwiązuje to podstawowych problemów.
Pytanie ujawnia co najmniej dwa podstawowe problemy: praktykę modyfikowania „rdzenia” w celu spełnienia wymagań dla indywidualnych klientów oraz brak komunikacji między zespołami i koordynacji ich zamiaru wprowadzenia zmian. Żadna z tych przyczyn nie jest podstawowa i zanim będzie można to naprawić, musisz zrozumieć, dlaczego tak się dzieje.
Jedną z pierwszych rzeczy, które należy ustalić, jest to, czy zarówno programiści, jak i menedżerowie zdają sobie sprawę, że jest tutaj problem. Jeśli przynajmniej niektórzy tak robią, musisz dowiedzieć się, dlaczego albo myślą, że nic nie mogą z tym zrobić, albo nie. Dla tych, którzy tego nie robią, możesz spróbować zwiększyć ich zdolność do przewidywania, w jaki sposób ich obecne działania mogą powodować przyszłe problemy, lub zastąpić ich ludźmi, którzy potrafią. Dopóki nie będziesz mieć siły roboczej, która jest świadoma tego, co się dzieje źle, prawdopodobnie nie będziesz w stanie rozwiązać problemu (a być może nawet wtedy, przynajmniej w krótkim okresie).
Analiza problemu może być trudna w kategoriach abstrakcyjnych, przynajmniej początkowo, więc skup się na konkretnym incydencie, który spowodował problem, i spróbuj ustalić, jak to się stało. Ponieważ zaangażowane osoby prawdopodobnie zachowują się defensywnie, musisz być czujny na egoistyczne i post-hoc uzasadnienia, aby dowiedzieć się, co się naprawdę dzieje.
Jest jedna możliwość, o której waham się wspomnieć, ponieważ jest to tak mało prawdopodobne: wymagania klientów są tak zróżnicowane, że nie ma wystarczającej podobieństwa, aby uzasadnić wspólny kod podstawowy. Jeśli tak, to faktycznie masz wiele oddzielnych produktów i powinieneś nimi zarządzać, a nie tworzyć między nimi sztucznego połączenia.
źródło
Wszyscy wiemy, że należy przejść testy jednostkowe. Ale wiemy również, że realistyczne dopasowanie ich do rdzenia jest trudne.
Specjalną techniką, która może być przydatna podczas rozszerzania funkcjonalności, jest próba tymczasowego i lokalnego sprawdzenia, czy istniejąca funkcjonalność nie została zmieniona. Można to zrobić w następujący sposób:
Oryginalny pseudo kod:
Tymczasowy kod testowy na miejscu:
Uruchom tę wersję, niezależnie od istniejących testów na poziomie systemu. Jeśli wszystko jest w porządku, wiesz, że nic nie zepsułeś, i możesz następnie usunąć stary kod. Pamiętaj, że po sprawdzeniu, czy stare i nowe wyniki są zgodne, możesz również dodać kod do analizy różnic, aby uchwycić przypadki, które, jak wiesz, powinny być różne z powodu zamierzonej zmiany, takiej jak naprawa błędu.
źródło
„Przeważnie zmiany nie są ogłaszane przez zespoły, więc błędy trafiają prawie zawsze nieoczekiwanie”
Masz problem z komunikacją? Co (oprócz tego, co wszyscy inni już zauważyli, że powinieneś być rygorystycznym testowaniem) upewniając się, że istnieje właściwa komunikacja? Że ludzie są świadomi, że interfejs, do którego piszą, zmieni się w następnej wersji i jakie będą te zmiany?
I zapewnij im dostęp do co najmniej fałszywego interfejsu (z pustą implementacją) tak szybko, jak to możliwe podczas programowania, aby mogli zacząć pisać własny kod.
Bez tego testy jednostkowe niewiele by zrobiły, poza tym, że w końcowych etapach stwierdzono, że między częściami systemu coś jest nie do zniesienia. Chcesz to wiedzieć, ale chcesz to wiedzieć wcześnie, bardzo wcześnie, a zespoły rozmawiają ze sobą, koordynują wysiłki i faktycznie mają częsty dostęp do pracy wykonywanej przez drugi zespół (tak więc regularnie się zobowiązuje, a nie jednego masywnego popełnić po kilku tygodniach lub miesiącach, 1-2 dni przed dostawą).
Twój błąd NIE znajduje się w kodzie, a na pewno nie w kodzie innego zespołu, który nie wiedział, że zadzierasz z interfejsem, przed którym piszą. Twój błąd jest w procesie rozwoju, braku komunikacji i współpracy między ludźmi. To, że siedzisz w różnych pokojach, nie oznacza, że powinieneś izolować się od innych facetów.
źródło
Przede wszystkim masz problem z komunikacją (prawdopodobnie również związany z problemem budowania zespołu ), więc myślę, że rozwiązanie twojej sprawy powinno koncentrować się na ... no cóż, komunikacji zamiast na technikach programistycznych.
Przyjmuję za pewnik, że nie można zamrozić ani rozwidlić modułu podstawowego podczas rozpoczynania projektu klienta (w przeciwnym razie wystarczy po prostu zintegrować z harmonogramem firmy niektóre projekty niezwiązane z klientem, których celem jest aktualizacja modułu podstawowego).
Pozostaje nam więc problem poprawy komunikacji między zespołami. Można temu zaradzić na dwa sposoby:
Więcej informacji o CI jako procesie komunikacji można znaleźć tutaj .
W końcu masz problem z brakiem pracy zespołowej na poziomie firmy. Nie jestem wielkim fanem imprez integracyjnych, ale wydaje się, że byłyby przydatne. Czy regularnie organizujesz spotkania dla programistów? Czy możesz zaprosić osoby z innych zespołów do retrospekcji projektu? A może masz czasem piwo w piątek wieczorem?
źródło