Mateusz Mazurek – programista z pasją

Czyli o użyciu Pythona i kilku innych technologii do tworzenia świetnej jakości aplikacji w oparciu o stabilny proces dostarczania oprogramowania.

Algorytmika Felietony/inne Inżynieria oprogramowania Programowanie Programowanie webowe Utrzymanie oprogramowania

Czym jest pozorna duplikacja kodu?

Cześć.

Artykuł ten jest efektem pracy nad jednym z projektów. Prawdę mówiąc, to mało pracuję czynnie nad tym projektem, bardziej jestem takim „dobrym duchem” pilnującym zakresu, wyboru rozwiązań, terminów no i ogólnie magicznie sprawiam, że całość „jakoś się toczy”. Projekt został zdekomponowany na mniejsze taski, tak by można było śledzić postęp. Przy jednym z tasków, omawiając już prawie gotowe rozwiązanie, natknąłem się na coś co nazwałem „pozorną duplikacją kodu”.

Co tam zobaczyłem?

Zacząłem pisać ten akapit, po prostu opisując, na co się natknąłem. Po chwili doszedłem do wniosku, że aby czytający mógł zrozumieć problem, potrzebowałby znacznie większej wiedzy domenowej, której nie jestem w stanie tu zamieścić. Posłużę się więc analogicznym przykładem.

Mamy jakiś system. W nim mamy użytkownika. Użytkownik ma 3 ustawienia możliwe do konfiguracji:

  • maksymalny czas pracy w trybie A
  • maksymalny czas pracy w trybie B
  • maksymalny czas pracy w trybie C

Na tym etapie rozwoju tego softu, te trzy wartości są w ten sam sposób przetwarzane, ale wynik każdej z nich oddziałuje na inny element systemu. To co zobaczyłem, to właśnie funkcję, która bierze te trzy, przypadkiem podobne do siebie obliczenia, wrzucone w jedną funkcję. I zdziwienie na twarzy autora, kiedy dopytywałem o powód powstania takiego kodu.

Wejdźmy głębiej

Mamy trzy niezwiązane ze sobą ustawienia. Te trzy wartości w żaden sposób, w domenie rozwiązywanego problemu, nie spotykają się ze sobą. Mamy więc od strony kodu taką sytuację:

Natomiast, ta sama sytuacja, od strony domeny, wygląda tak:

Co powinniśmy zrobić?

Możemy zrobić to co zaproponował autor, czyli tę duplikację uwspólnić, tworząc jeden komponent (obojętnie czy to funkcja, klasa czy cokolwiek innego) który będzie realizował tę funkcję. Jakie mamy konsekwencje takiej decyzji?

Przede wszystkim jedną ogromną – jeśli przyjdzie wymaganie, by zmienić działanie jednej ze ścieżek, to mamy problem. Bo to ta sama ścieżka! To jest sytuacja kiedy właśnie tworzone są bardzo brzydkie IFy, które wymuszają na komponencie, który miał mieć jedno zadanie, obsługę kolejnego zadania.

Drugim podejściem jest odwzorowanie kodu, tak by pasował do domeny. Spowoduje to powstanie duplikacji w kodzie. To co ważne jest to to, że to jest pozorna duplikacja. To że kod wygląda tak samo nie znaczy, że to ten sam kod.

Jak odróżnić duplikację pozorną od faktycznej?

Kod który piszemy to zapis założeń. Duplikacja faktyczna występuje wtedy kiedy duplikujemy implementację tych samych założeń. Z duplikacją pozorna mamy do czynienia, kiedy kod tylko przypadkiem wygląda podobnie a w rzeczywistości jest zapisem różnych założeń. W sytuacji kiedy z wielu miejsc, możemy wykonać tę samą operację i ma ona dać ten sam efekt to produkowanie kopii tej operacji jest błędem. Czasem się zdarza, że ciężko określić co jest czym – tutaj z pomocą może przyjść rozmowa z biznesem.

A i pytanie pomocnicze: Czy istnieje tylko jeden powód do zmiany tego kodu?

Załóżmy, że masz 3 bloki kodu. Wygląda on tak samo/bardzo podobne. Ile może istnieć powodów zmiany tego kodu? Jeśli jeden (np. wymaganie „dodawajmy do obrazków znak wodny”) to jest to ten sam kod. Jeśli natomiast kod może zmienić się w wyniku wielu dróg, to prawdopodobnie jest to pozorna duplikacja.


Czekaj, stop!

Podoba Ci się to co tworzę? Jeśli tak to zapraszam Cię do zapisania się na newsletter:
a w ramach prezentu otrzymasz całkowicie za darmo, dwa dokumenty PDF „6 (nie zawsze oczywistych) błędów popełnianych podczas nauki programowania” który jest jednym z efektów ponad siedmioletniej pracy i obserwacji rozwoju niejednego programisty oraz „Wstęp do testowania w Pythonie”, będący wprowadzeniem do biblioteki PyTest.
Jeśli to Cię interesuje to zapraszam również na swoje social media.

Pułapka słów „prawdopodobnie” i „bardzo podobne”

Oba te słowa padły we wcześniejszym rozdziale. Problem z nimi jest taki, że nie istnieje odgórnie narzucona ilości różnic, której przekroczenie, jednoznacznie określi, że to już jest inny komponent. Jest to dość umowne i raczej zależy od kontekstu, planów na rozwój aplikacji czy po prostu architektury systemu. Nie musimy też od razu robić kopiuj-wklej. Mamy do dyspozycji programowanie obiektowe, wstrzykiwanie zależności czy wzorce projektowe, takie jak fasada czy strategia, które pomogą nam uzyskać kod odporny na różnice i ewentualne zmiany.

Czy IDE umie nam pomóc?

Przyjrzyj się poniższemu kawałkowi kodu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from typing import List


def shift_digits_to_right(words: List[str]) -> List[str]:
    processed = []
    for word in words:
        digits = []
        others = []
        for letter in word:
            if letter.isdigit():
                digits.append(letter)
            else:
                others.append(letter)
        processed.append(''.join(others + digits))

    return processed


print(shift_digits_to_right(['1a1a1a', '2z2z2z']))


def shift_digits_to_left(words: List[str]) -> List[str]:
    processed = []
    for word in words:
        digits = []
        others = []
        for letter in word:
            if letter.isdigit():
                digits.append(letter)
            else:
                others.append(letter)
        processed.append(''.join(digits + others))

    return processed

                             
print(shift_digits_to_left(['1a1a1a', '2z2z2z']))

Jego wynik to oczywiście:

['aaa111', 'zzz222']
['111aaa', '222zzz']

I są to po prostu dwie funkcje które przesuwają wszystkie liczby w prawo albo w lewo, przyjmując listę słów do przetworzenia.

Duplikację widać gołym okiem. IDE reaguje na to tak:

PyCharm, bo o nim mowa, podkreśla słowo „processed” i to w obu funkcjach. Po najechaniu na wyróżnione słowo, IDE informuje nas, co mu się nie podoba:

Po kliknięciu „Show all duplicates like this” otwiera się okno:

które po lewej stronie pokazuje linie która zostały uznane za duplikat a po prawej stronie diff obu fragmentów, pokazujący różnice.

Więc odpowiadając na pytanie z nagłówka – tak, PyCharm informuje nas jeśli znajdzie duplikaty. Oczywiście nie podejmie on za nas decyzji, jakiego typu duplikat to jest, ale wskaże problematyczne miejsca.

Zmierzając do brzegu

Oczywiście ten problem nie jest niczym nowym i pojawia się w różnych opracowaniach, gdzie jest raz lepiej, raz gorzej, opisany. Kończąc, chciałbym powiedzieć, że powinniśmy zachowajmy umiar w uwspólnianiu kodu jak i technice copy-pasta. Każda sytuacja jest indywidualna i powinna być przemyślana oddzielnie, zgodnie z założeniami projektowymi. Wiele pomóc może nam rozmowa z biznesem.

Dzięki za wizytę,
Mateusz Mazurek

A może wolisz nowości na mail?

Subskrybuj
Powiadom o
guest

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

3 komentarzy
Inline Feedbacks
View all comments
Mateusz H.

Hej Mateusz ✋, Ja jestem zwolennikiem pisania wszystkiego od nowa. Lubię także zasadę DRY która trochę kłóci się z założeniem pierwszego zdania. Jednak jak mogę coś od nowa napisać to chętnie to robię ponieważ ucze się w ten sposób a jestem na etapie nauki oraz przebranżawiania więc mi to pomaga 🙂. Ciekaw jestem co to za projekt którego jesteś patronem. Czy polecił byś jakiś fajny kurs do wzorców projektowych w Python? Wiem że wzorce projektowe to uniwersalne zagadnienie ale wolał bym się ich uczyć na przykładach w tym języku 🤓. Na Udemy.com znalazłem kursy ale w języku Java. Może byś… Czytaj więcej »

Mateusz H.

Cześć.

Angielski umiem całkiem dobrze. Chętnie tą książkę przeczytam, skoro listingi są w Pythonie.

Brakuje mi jeszcze wiedzy z MVC oraz wzorców projektowych oraz innych rzeczy ale te bym się chętnie nauczył.

Pozdrawiam.