Jak inteligentnie zarządzać długiem technologicznym za pomocą behawioralnej analizy kodu?

29/10/2020

Data ostatniej aktualizacji: 28.01.2021 02:28

Czym jest dług technologiczny? Najlepsze wyjaśnienie tego terminu, ukutego przez Warda Cunninghama, dostarczył Marin Fowler. Nie potrafimy perfekcyjnie rozwijać systemów informatycznych. Każda dodana lub zmodyfikowana funkcjonalność wprowadza niewielkie braki w jakości oprogramowania. Te braki kumulują się z czasem i utrudniają nam utrzymanie tempa, w jakim dostarczamy nasze systemy na produkcję.

Weźmy pod uwagę system z długiem technologicznym. Bez długu, wdrożenie danej funkcjonalności zajęłoby nam 4 dni, z powodu zadłużenia – 6 dni. Oznacza to, że te dodatkowe 2 dni to odsetki, które musimy zapłacić. Jeśli czas potrzebny na naprawę kodu i usunięcie długu technologicznego wynosi 3 dni, to cała zmiana zajmie nam 7 dni i usunięcie długu nie jest opłacalne. Jeśli jednak wiemy, że nie jest to jedyna zmiana, jaką musimy wprowadzić w danym elemencie oprogramowania, to spędzenie tych dodatkowych 3 dni szybko się zwróci. Należy też pamiętać, że dług technologiczny ma tendencję do kumulowania się. Przyszłe zmiany w komponencie z długiem spowodują tylko jego wzrost, a czas potrzebny na rozwój nowych funkcji wzrośnie z 6 do 10 lub 15 dni.

Jakie są przyczyny powstawania długu technologicznego? Weźmy pod uwagę prawa Lehmana dotyczące ewolucji oprogramowania:

Prawo ciągłych zmian

“System musi być stale dostosowywany lub staje się stopniowo mniej użyteczny”​

Prawo o rosnącej złożoności​

“W miarę jak system ewoluuje, jego złożoność rośnie, chyba że prowadzone są prace mające na celu jego utrzymanie lub ograniczenie tego trendu.”​

Z jednej strony, musimy dodawać nowe funkcje, modyfikować istniejące, aby system pozostał aktualny i użyteczny. Z drugiej strony, nasze prace nad rozwojem systemu zwiększają jego złożoność i jeśli nie podejmiemy odpowiednich działań naprawczych, złożoność ta ostatecznie uniemożliwi ewolucję systemu z powodu rosnących opóźnień i nadmiernych kosztów. Jak wszyscy wiemy, rozwój oprogramowania jest doświadczeniem edukacyjnym. Oznacza to, że rozpoczynając nowy projekt posiadamy bardzo małą wiedzę na temat rzeczywistego problemu, który staramy się rozwiązać oraz dziedziny biznesu, w której pracujemy. Uczymy się po drodze i założenia oraz rozwiązania architektoniczne, które przyjeliśmy na początku projektu, szybko stają się nieaktualne. Zmieniają się wymagania biznesowe, zbliżają się terminy. Często poświęcamy jakość, aby jak najszybciej wprowadzić kod na produkcję. Również dobór technologii nierzadko nie jest optymalny i oparty na „nowych” trendach, a nie na dopasowaniu danej technologii do prowadzonego przez nas projektu. Wszystkie te i wiele innych czynników powodują powstanie długu technologicznego

Zarządzanie długiem technologicznym

Jak możemy poradzić sobie z długiem technologicznym? Moim zdaniem, istnieją trzy kluczowe elementy, o które musimy zadbać, jeśli nie chcemy aby nasz projekt zbankrutował: architektura systemu, zespół i organizacja procesu. Jak wiemy z prawa Conway’a (a raczej z jego konsekwencji) – wszystkie one muszą działać razem, w przeciwnym razie jesteśmy skazani na porażkę.

Architektura systemu na każdym poziomie, od podziału na komponenty/moduły po wewnętrzną strukturę pakietów i klasy powinny oddzielać rzeczy, które mają różne powody do zmiany lub zmianiają się w różnym tempie. Jak zobaczymy w trakcie lektury tego postu, jest to niezwykle istotne. Mieszanie różnych obowiązków w tym samym komponencie zwiększa prawdopodobieństwo wystąpienia wad i dodatkowo wzmacnia trend spadku jakości.
Architektura musi być również kompatybilna z organizacją zespołów, aby zminimalizować straty związane z dodatkową komunikacją i koordynacją oraz wyeliminować kosztowne łączenie kodu z wielu gałęzi rozwojowych.

Nie jesteśmy w stanie stworzyć „idealnej” architektury i organizacji, która będzie odpowiednia przez cały cykl życia projektu. Oznacza to, że musimy stale monitorować stan naszej architektury, jakość kodu, wzorce komunikacji, organizację zespołu, aby właściwie reagować i planować usprawnienia w obszarach, w których przynosi to największe korzyści.

Wielu menedżerów ignoruje potrzebę takich działań. Mają tendencję do obwiniania zespołów programistycznych za braki w jakości oraz kwestionowania ich umiejętności i zaangażowania. Nie jest to oczywiście prawdą. Akumulacja długu technologicznego jest naturalnym procesem i wszyscy uczestnicy projektu są częściowo za nią odpowiedzialni.

Właściwy monitoring, ewolucja architektury systemu oraz organizacja zespołu są szczególnie ważne w projektach wykorzystujących metodyki agile. W takich projektach staramy się doprowadzić nasz kod do produkcji tak szybko, jak to możliwe. Często zespoły zapominają, że należy to robić bez poświęcania jakości kodu. W miarę jak przechodzimy do produkcji, nasz kod jest obecnie poddawany konserwacji, a jak pokazują ostatnie badania naukowe, 40 do 80% kosztów projektu informatycznego jest wydawane na utrzymanie oprogramowania. Brak działania w przypadku zwiększonej złożoności systemu ma konsekwencje dla wszystkich zainteresowanych stron projektu. Dla kierownictwa oznacza to niedotrzymanie terminów, wydłużenie czasu realizacji, brak przewidywalności procesu rozwoju. Dla użytkowników oznacza to wiele błędów, które sprawiają, że ich codzienna praca jest cięższa i frustrująca, obniżając efektywność biznesową. Dla całej organizacji oznacza to dodatkowe koszty i utratę przewagi konkurencyjnej.

Istnieje wiele narzędzi i technik, których obecnie używa większość organizacji do walki z długiem technicznym. Wymieńmy te najpopularniejsze i ich braki:

Analiza behawioralna kodu

Wszystkie te narzędzia są użyteczne, ale potrzebujemy więcej informacji, aby właściwie zarządzać długiem technologicznym. W naszym zespole R&D znaleźliśmy bardzo ciekawe nowe podejście opracowane przez Adama Tornhilla – analizę behawioralną kodu. Swoją koncepcję opisał on w dwóch książkach: oraz “Software Design X-Rays”.. Jego podejście poszerza statyczną analizę o dwa nowe wymiary: czasowy i ludzki. Tak jak lekarz nie może skutecznie zdiagnozować pacjenta tylko na podstawie jego aktualnego stanu i objawów, ale potrzebuje informacji historycznych oraz informacji o jego aktywności (historia choroby pacjenta, ryzyko genetyczne, aktywność fizyczna, nawyki żywieniowe itp.), aby zdiagnozować nasz system, potrzebujemy informacji o ewolucji kodu w czasie oraz o sposobach interakcji twórców z kodem. Na szczęście dla nas, przez lata gromadzimy wszystkie wymagane informacje w naszych systemach kontroli źródeł, takich jak git czy svn.
Przeanalizujmy, jakie informacje możemy uzyskać z naszych repozytoriów za pomocą analizy behawioralnej kodu oraz w jaki sposób pomaga nam to w zarządzaniu długiem technologicznym.

Analiza hotspotów

Pierwszym krokiem, jaki musimy zrobić, aby właściwie uporać się z długiem technologicznym, jest znalezienie miejsc w kodzie, gdzie usprawnienia dadzą nam największą korzyść.

W dużych projektach, z setkami tysięcy linii kodu, tworzonych w różnych językach programowania, nie jest to łatwe zadanie przy użyciu tradycyjnych narzędzi. Ale analiza hotspotów jest właściwym narzędziem, które pomoże nam w tym zadaniu. Hotspot jest elementem naszego kodu (plikiem / klasą), który jest jednocześnie złożony i często zmieniany w analizowanym okresie. Jako miarę złożoności wykorzystamy najprostszą z możliwych – ilość linii kodu. Jako miarę częstotliwości zmian użyjemy liczby commitów zmieniających dany plik. Poniższy rysunek przedstawia jedną z możliwych wizualizacji hotspotów: im większy okrąg, tym bardziej złożony jest dany element, im bardziej czerwony tym większa częstotliwość zmian.

code hotspot visualisation - circles

Czy taka prosta metoda pomiaru może zadziałać? Badania oparte na setkach projektów, różnej wielkości, opracowanych w różnych technologiach przy użyciu różnych procesów rozwojowych, wszystkie prowadzą do tego samego rozkładu częstotliwości zmian.

code change frequency

Tylko niewielka część kodu jest często zmieniana. Większość kodu jest stabilna i rzadko zmieniana. Statystyki dowodzą, że stabilny, niezmodyfikowany kod jest znacznie mniej podatny na błędy niż kod ostatnio zmieniony. Na przykład prawdopodobieństwo wystąpienia usterki w kodzie stabilnym przez rok jest o ⅓ mniejsze niż w kodzie ostatnio zmienianym. Oznacza to, że stabilny, ale złożony kod nie jest naszym zmartwieniem (chyba, że wiemy, że planowane są prace, które będą wymagały zmian w takim kodzie). Inne badania potwierdzają korelację między częstotliwością zmian, a spadkiem jakości. Bazując na statystykach, połączenie liczby linii kodu i częstotliwości zmian okazało się lepsze w przewidywaniu wskaźnika błędów niż jakakolwiek inna kombinacja bardziej złożonych metryk.

Mogę tylko potwierdzić, że taki sam rozkład zaobserwowaliśmy w projektach, które rozwijamy i utrzymujemy tutaj w Altkom Software & Consulting.

Analiza hotspotów pomaga nam wyeliminować ponad 90% kodu z naszych dalszych poszukiwań. Ale musimy kopać głębiej, ponieważ istnienie hotspotu nie musi oznaczać problemu. Oczywiście, skomplikowany komponent o dużej częstotliwości zmian jest najprawdopodobniej problemem, ale zmiany mogą być wynikiem już rozpoczętych ulepszeń.

Jak już powiedziano, w większości przypadków hotspot oznacza, że mamy problem, który przynajmniej wymaga dalszego badania. Duży, często zmieniany plik gromadzi prawdopodobnie wiele odpowiedzialności i powinien zostać podzielony na mniejsze, bardziej spójne komponenty.

Aby zdecydować, czy dany hotspot stanowi problem, czy nie, musimy zagłębić się w jego szczegóły. Pierwsza heurystyka, którą powinniśmy zastosować jest bardzo prosta, musimy sprawdzić nazwę klasy, która została oznaczona jako hotspot. Nie opisowe, ogólne nazwy, nazwy z przyrostkami dają nam wskazówkę, że powinniśmy dokładnie przeanalizować przypadek. Nazwy takie jak State, Helper, Service, Manager są zazwyczaj znakiem, że dana klasa ma więcej niż jedną odpowiedzialność i prawdopodobnie powinna być podzielona na mniejsze, bardziej spójne części. Nazywanie rzeczy w kodzie jest kluczowe dla jego czytelności i łatwości utrzymania. Kod jest o wiele częściej czytany niż modyfikowany, a pamięć robocza naszych mózgów jest ograniczona, dlatego o wiele łatwiej jest ludziom nawigować po kodzie, jeśli nazwy są opisowe i uchwycą cel klasy lub funkcji. Badania wykazały, że my, ludzie, zaczynamy budować mentalny model kodu od nazw klas i funkcji. Nazwy nie opisowe lub zbyt ogólne stanowią przeszkodę na naszej drodze do zrozumienia kodu.

Następnym krokiem jest sprawdzenie trendów złożoności naszego hotspotu. Tym razem będziemy bardziej szczegółowo obliczać złożoność. Będziemy liczyć głębokość wcięć w kodzie. W ten sposób będziemy mogli łatwo wykryć kod o skomplikowanych strukturach, kod zawierający złożone wyrażenia logiczne oraz złożone struktury kontrolne. Takie konstrukcje są zazwyczaj źródłem wielu błędów. Dla przykładu, złożone wyrażenia logiczne są odpowiedzialne za około 20% wszystkich błędów. Patrząc na trend złożoności możemy się upewnić, że programiści już zaczęli zajmować się długiem technicznym i złożoność zaczęła się zmniejszać. Jeśli złożoność wciąż rośnie, zwłaszcza jeśli rośnie szybciej niż całkowita liczba linii, mamy sytuację, która wymaga naszej interwencji.

code complexity chart

Musimy zajrzeć do wnętrza danej klasy i znaleźć obszary, którymi musimy się zająć. Możemy skorzystać z analizy hotspotów na poziomie funkcji. Dzięki niej dowiemy się, które funkcje są najbardziej złożone, a które najczęściej zmieniane.

x-ray code analysis

 

Stosujemy podejście jak w przypadku hotspotów na poziomie plików. Jeśli mamy złożoną funkcję, która nie była zmieniana przez miesiące, nie musimy wykonywać żadnych działań. Ale znalezienie funkcji złożonej, która jest często modyfikowana, jest znakiem, że wymaga ona refaktoringu. Być może funkcja jest zbyt duża i może być zmodularyzowana. Może zawiera pewne złożone wyrażenia, które mogą być przekształcone w osobne funkcje. Analiza Hotspotu może również pokazać nam, które funkcje mają tendencję do zmiany jednocześnie. Może to być znak kopiowania i wklejania lub brakująca abstrakcja.

Po sprawdzeniu podejrzanych funkcji, możemy zaplanować nasze ulepszenia.

Analiza Hotspotu pomaga nam podejmować świadome decyzje, bazując na danych i badaniach naukowych oraz inteligentnie zarządzać długiem technologicznym.

Jak widać, możemy w pewnym sensie przeskalować analizę hotspotów – możemy zastosować ją na poziomie pliku/klasy, możemy wejść głębiej i przeanalizować funkcje wewnątrz danego pliku/klasy. Ale możemy też zastosować ją na poziomie architektonicznym. Potrafimy wyszukiwać hotspoty wśród komponentów architektury systemu. Będzie to niezwykle przydatne w przypadku budowania systemów opartych na mikrousługach lub wszelkiego rodzaju systemów modułowych. Dzięki analizie hotspotów, możemy znaleźć moduły (usługi), które są zbyt duże i często się zmieniają. Ponownie, w większości przypadków przyczyną takiej sytuacji jest fakt, że kod w danym module bierze na siebie zbyt wiele obowiązków.

module health code analysis

Powinniśmy analizować takie moduły i starać się dzielić je na mniejsze i bardziej spójne elementy, tak jak na poziomie klasy. Jak wspomnieliśmy na początku tego postu, niezwykle ważne dla zdrowia systemu jest, aby oddzielić od siebie kod, który może się zmieniać z różnych powodów i z różną częstotliwością. Jak widzieliśmy na wykresach rozkładu częstotliwości zmian – oddzielenie stabilnego kodu od zmiennego kodu zmniejsza obszar, w którym nagromadzenie długu technicznego jest najbardziej niebezpieczne.

Sprzężenie czasowe (Temporal coupling)

Inną ważną koncepcją, która może być analizowana przy użyciu naszych nowych narzędzi, jest sprzężenie czasowe (alias. Change coupling). Sprzężenie czasowe oznacza, że dwa elementy X i Y zmieniają się razem. Możemy analizować jak często się to zdarza. Dzięki danym o sprzężeniu czasowym możemy poszukiwać nieoczekiwanych zależności pomiędzy elementami systemu oraz zależności, które stają się zbyt silne.

temporal coupling - dependencies

W niektórych przypadkach sprzężenie czasowe jest naturalne i nie ma w nim nic złego. Na przykład, gdy dodajemy nową funkcję do komponentu, zwykle dodajemy test do jego modułu testowego. Jest to przykład sprzężenia czasowego, które jest pożądane. Jeśli jednak zaobserwujemy, że wiele zmian w szczegółach implementacji danego komponentu powoduje również zmiany w jej kodzie testowym, oznacza to, że kod testowy prawdopodobnie zależy od szczegółów implementacji, co jest bardzo złe.

Jeśli dwa elementy, które powinny być niezależne od siebie, wykazują oznaki wysokiego sprzężenia czasowego, musimy zbadać ten przypadek: znaleźć, które klasy i funkcje mają tendencję do zmian razem. Może brakuje nam abstrakcji lub należy wyodrębnić cały nowy, oddzielny komponent. Może najwyższy czas zrewidować nasze założenia architektoniczne i wyznaczyć nowe architektoniczne granice pomiędzy komponentami, aby lepiej wspierać dalszą ewolucję systemu.

Sprzężenie czasowe jest również w dużym stopniu związane z prawdopodobieństwem wystąpienia błędów. Wysoki poziom sprzężenia czasowego zwykle skutkuje wysokim współczynnikiem błędów. Badania pokazują, że sprzężenie czasowe jest lepsze do przewidywania defektów niż jakakolwiek metryka złożoności.

Wiedza na temat sprzężenia czasowego pomaga nam w planowaniu refaktoringu, a także w planowaniu testów. Wiedząc, że zmiany w funkcjonalności X powodują zmiany w kodzie związane z Y, możemy zaplanować test regresji obu modułów w przypadku, gdy którykolwiek z nich ulegnie zmianie.

Najlepszym miejscem do rozpoczęcia analizy sprzężenia czasowego są moduły o najwyższej częstotliwości zmian.

Analiza sprzężenia czasowego, tak jak analiza hotspotów, może być wykorzystana do przejścia z poziomu modułów na poziom klasy, a następnie na poziom funkcji.

Badanie sprzężenia czasowego rozszerza naszą analizę z poszczególnych komponentów na relacje między nimi i pomaga nam znaleźć kod, który pozostawiony bez nadzoru może prowadzić do nieoczekiwanych i trudnych do zdiagnozowania błędów. Wszyscy znamy te sytuacje, gdy ktoś dodaje funkcjonalność do modułu A, moduł A jest testowany i działa doskonale, wchodzimy do produkcji, aby dowiedzieć się, że moduły B i C niespodziewanie przestały działać.

Hotspoty, Temporal Coupling, Code Ownership i wzorce komunikacji

Wszyscy pamiętamy prawo Brooksa z „The Mythical Man-Month”: „dodawanie ludzi do opóźnionego projektu opóźnia go jeszcze bardziej”. Dzieje się tak ze względu na dodatkowy koszt komunikacji i koordynacji, który jest wyższy niż wartość pracy tworzonej przez nowych członków zespołu. Kolejną rzeczą jest to, że architektura systemu ogranicza liczbę programistów, którzy mogą efektywnie pracować nad projektem. Zbyt wielu programistów lub programistów niepoprawnie przypisanych do komponentów architektonicznych spowoduje jednoczesną pracę nad tym samym kawałkiem kodu, wykonywaną przez różnych programistów i zespoły. Istnieje korelacja pomiędzy liczbą autorów, którzy jednocześnie modyfikują ten sam komponent, a liczbą wad w danym komponencie. Badania nad kodem źródłowym Linuksa pokazują, że moduły o liczbie autorów większej niż 9 mają 16 razy więcej defektów niż pozostała część bazy kodu.

Możemy wykorzystać analizę behawioralną kodu do wykrycia takich problemów. Widzieliśmy już analizę hotspotów. W projektach z nieoptymalną organizacją zespołów, istnieje silny związek pomiędzy hotspotami a modułami modyfikowanymi przez wielu programistów. Wykorzystując dane z git czy svn możemy sprawdzić ilu programistów modyfikowało dany moduł w analizowanym okresie. Jeśli jest wielu programistów modyfikujących kod hotspota, istnieje szansa, że ma on zbyt wiele obowiązków, co w związku z częstymi modyfikacjami dokonywanymi przez różne osoby prawdopodobnie spowoduje, że kod będzie miał gorszą jakość, niespójności, wady i wiele czasu zostaje straconego na kosztowne merge. Po raz kolejny widzimy znaczenie zasady pojedynczej odpowiedzialności (single responsibility principle.).

Adam Tornhill w swoich książkach przedstawia trzy typowe wzorce własności kodu i opisuje ich konsekwencje.

three typical patterns of code ownership

W pierwszym przypadku całość lub większość kodu w danym komponencie została opracowana przez jedną osobę. W tym przypadku nie mamy żadnych dodatkowych kosztów związanych z komunikacją i koordynacją. Kod komponentu jest spójny, a jego jakość jest w dużej mierze związana jest z umiejętnościami technicznymi autora.

W drugim przypadku mamy wielu programistów pracujących nad komponentem, ale jeden z nich wykonał większość pracy. W tym przypadku, procent kodu stworzonego przez głównego twórcę jest dobrym predyktorem jakości. Badania pokazują, że im większy procent kodu stworzonego przez głównego autora, tym mniejsza liczba błędów. Istnieje również jeszcze silniejsza zależność między błędami a liczbą innych autorów. Liczba błędów zwiększa się wraz z liczbą dodatkowych autorów.

Ostatnim przypadkiem jest duża fragmentacja – wielu autorów z małą ilością zmienionego kodu. Kod ten będzie wymagał starannej analizy i testów, ponieważ będzie źródłem wielu błędów.

Możemy również łączyć informacje o autorach i sprzężeniu czasowym. Po pierwsze, musimy znaleźć głównego autora każdego modułu. Możemy to zrobić poprzez znalezienie dewelopera, który dodał większość kodu do modułu. Często lepszą heurystyką jest szukanie ilości usuniętych linii. W ten sposób prawdopodobnie znajdziemy programistę, który opiekuje się danym komponentem i redukuje złożoność za pomocą technik refaktoringu. Możemy połączyć listę głównych autorów dla każdego modułu lub komponentu z wynikami analizy sprzężenia czasowego. Jeśli moduły czasowo sprzężone mają tego samego głównego autora lub autora pracującego blisko siebie (np. są w tym samym małym zespole), to nie mamy żadnych problemów. Jeśli nie są, to prawdopodobnie znaleźliśmy kosztowny wzorzec komunikacji i koordynacji, gdy programiści z różnych zespołów muszą pracować nad ściśle sprzężonym kodem.

Podsumowanie

Jak wspomniałem na początku tego postu, dług technologiczny nie jest problemem tylko deweloperów. Wpływa on na efektywność całej organizacji. Jeśli nie walczymy z nim to rezultatem tego są: zwiększone koszty, brak przewidywalności naszego procesu dostarczania, systemy o wysokim wskaźniku błędów, które nie pomogą Ci w osiągnięciu Twoich celów biznesowych.

Dług technologiczny powinien być stale monitorowany i obsługiwany na bieżąco. Powinniśmy nim zarządzać w oparciu o rzeczywiste dane i opinie ekspertów.

Dług technologiczny jest w kodzie naszych systemów, ale aby się nim zająć, powinniśmy spojrzeć poza sam kod. Kluczowe znaczenie ma architektura systemu i organizacja zespołu. Powinniśmy monitorować i dostosowywać ją do potrzeb projektu. Architektura i organizacja muszą ewoluować, aby zapewnić optymalne wsparcie dla ewolucji systemu.

Tradycyjne narzędzia bardzo nam pomagają i przynoszą cenne informacje na tematy techniczne, ale nie pomagają nam znaleźć miejsc, gdzie inwestycje szybko się zwrócą. Nie pomogą nam też w znalezieniu problemów organizacyjnych. Powinniśmy dodać behawioralną analizę kodu do listy naszych codziennych praktyk, aby inteligentnie zarządzać długiem technologicznym.

Ten post przedstawia niektóre z technik analizy behawioralnej kodu opisanych przez Adama Tornhilla: hotspoty, sprzężenie czasowe, własność kodu. Jest ich więcej: możemy używać map wiedzy do monitorowania wiedzy o naszym systemie, zwrócić uwagę na code churn aby przeanalizować sposoby, w jaki programiści wchodzą w interakcję z kodem, i wiele innych. Istnieją narzędzia, które automatyzują ten proces. Jednym z nich jest Code Scene, produkt komercyjny stworzony przez Adama na podstawie jego książek. Korzystamy z niego na co dzień w Altkom Software and Consulting, jest on zintegrowany z naszymi środowiskami CI/CD i pomaga na wiele sposobów tworzyć lepsze systemy. Jeśli chcesz spróbować tych nowych technik, gorąco polecam zacząć od książek Adama i pobawić się Code Maat, darmowym narzędziem open-source, które towarzyszy tym książkom.

Autor: Wojciech Suwała, Head Architect
Altkom Software & Consulting

Jeśli już tu jesteś, to z dużym prawdopodobieństwem poszukujesz rozwiązania dla kwestii starych technologii w Twojej firmie! Java 1.4-1.6, .NET w wersjach […]
Będąc precyzyjnym, pytanie tytułowe raczej powinno brzmieć: „Dlaczego niektórzy CIO mają obawy przed zaangażowaniem się we współpracę z typową fabryką […]
In this post, I am going to present how you can use domain-driven design tactical patterns like value object, entity, […]