Po co refaktoryzować? Nie można od razu napisać dobrze? Czy słaby kod to zawsze zły kod?

4

Był taki film "Dzień Świstaka", w którym gość przeżywał ciągle ten sam dzień 2 lutego. Najpierw przydarzało mu się wiele pechów, np. szedł ulicą i samochód go ochlapywał itp. A później, ponieważ już ten sam dzień przeżył ileś razy, to wiedział, co się stanie i bawił się tym. Wiedział, w którym miejscu przejedzie samochód, wiedział, kogo spotka, co powiedzieć, czego nie mówić itp.

Tak samo z programistami - przy pierwszej implementacji ilość niewiadomych jest zbyt wielka, żeby od razu napisać prosty jasny kod - dlatego wychodzi spaghetti. Ew. jak ktoś jest "bojący się", to będzie stawiał na elastyczność i wzorce i doprowadzi do overengineeringu, co mu niewiele da (bo problem nie jest z jakością kodu, tylko z brakiem doświadczenia programisty w rozwiązywaniu danego typu problemów).

A jak już się rozwiązywało wcześniej jakiś problem ileś razy, to można pisać od razu lepiej, bo się wie, co się sprawdza, co się nie sprawdza. Łatwiej można przewidzieć różne problemy na długo, zanim wystąpią (bo widzieliśmy je już ileś razy). Jeśli jakichś wzorców warto użyć, to wiemy, jakich, a jakich lepiej nie - bo mamy know-how.

Być może software lepiej by wyglądał, gdyby na wczesnym etapie projektu programiści zamiast pisać produkcję, to robili masę PoCy i przepisywali od nowa po ileś razy, nabywając know how. Na późniejszym etapie ciężko przepisywać wszystko, jednak taką taktykę można by zastosować do nowych ficzerów. Zamiast wrzucać brzydki kod, robić implementację po kilka razy i wrzucić coś dopracowanego.

0
jarekr000000 napisał(a):

Patrząc z punktu widzenia Java i C# to te języki nie są już od dawna czysto obiektowe.

To prawda. Nie wiem jak Java, ale C# już od dawna jest brudnooobiektowy.
Funkcyjne wstawki w językach obiektowych mają na celu usunięcie proceduralnych fragmentów (takich jaki pętle z ifami), a nie zmianę ogólnej charakterystyki języka. To się po prostu nie uda.

C# chyba od więcej niż 10 lat. Co oznacza, że nie trzeba się gimnastykować i używać pokrętnych rozwiązań na klasach na proste problemy.

Klasy w języku obiektowym to nie jest pokrętne rozwiązanie, to jest podstawowa struktura organizacji kodu.

Jeśli problemem jest if na 500 linii kodu, to nie naprawi się go hasłem "lambdy" czy "FP". Kod trzeba ogarnąć, a trzymanie 500 linii lambd/funkcji w jednym pliku niekoniecznie będzie lepsze pod względem czytelności. Zwłaszcza nie w języku, który wymaga klamerek i returnów, więc funkcje wewnętrzne wyglądają chyba jeszcze gorzej niż ify.

LukeJL napisał(a):

Tak samo z programistami - przy pierwszej implementacji ilość niewiadomych jest zbyt wielka, żeby od razu napisać prosty jasny kod

Jeżeli się nie komplikuje od razu, to czemu kod ma nie wyjść prosty i jasny?

3

@somekind
To się po prostu nie uda. a próbowałeś, czy to jest założenie które pozwala na zracjonalizowanie sobie nie podejmowania wysiłku nauki nowych/innych paradygmatów programowania tym samym zachowując status quo nie wychodząc ze swojej strefy komfortu?
500 linii lambd/funkcji w jednym pliku nikt chyba nie namawiał do trzymania wszystkiego w jednym pliku.

Wydaje mi się że tutaj jest konflikt starego i nowego podejścia do programowania.
@jarekr000000 uważa FP jako by był kolejnym krokiem naprzód jeśli chodzi o rozwiązywanie problemów związanych z pisaniem oprogramowania,
a takie OOP to archaizm który już powinien odejść do lamusa jak kiedyś np. goto,
a @somekind jest apologetą tego co już zna, potrafi i rozumie, a do nowych/odkrytych na nowo rozwiązań podchodzi sceptycznie/ostrożnie/(niechętnie?)
Poprawcie mnie jeśli się mylę :P

1

Najpierw odpowiedzialbym na inne pytanie - kto jest odbiorca tego kodu i w jakim celu on powstaje. Ewentualnie zapytałbym analogicznie dlaczego piłkarz czasami pudłuje przy strzale na bramkę.

Jeżeli piszesz coś dla siebie i masz nieograniczony budżet czasowy, można zaryzykować stwierdzenie, że od razu piszesz docelowe rozwiązanie najwyższej jakości. Jeżeli grasz sam na pusta bramkę, prawdopodobnie zawsze strzelisz gola. Jednak świat nie jest idealny:

  • czasami trzeba iść na skróty, aby dowieźć określony zakres na nienegocjowalny termin
  • czasami robimy MVP, które być może po teście pójdzie do /dev/null
  • czasami eksperymentujemy z nową technologia, której nie znamy
  • zwykle zamiast spuszczać się nad pięknem kodu opłaca się iść dalej z kolejnym tematem - skupiamy się na celu, a taskow jest zawsze więcej niż rąk do pracy
  • zwykle brak pełnej wiedzy aby zaprojektować docelowe rozwiązanie, wiec implicite zaciagamy dług techniczny. Lepsze to niż overengineering
    (...)

Na koniec zaryzykuję stwierdzenie, że jeśli kod jest mega wypieszczony, odbija się to negatywnie na performance większego kawałka. Przykładem mogą być przeciągające się w nieskończoność CR, bo team reviewujący jest „strażnikiem jakości”. Jakość nie jest binarna, trzeba chodzić na kompromisy.

0

Byłem świadkiem tego jak powstawała mała aplikacja, dość prosta w samych założeniach przez całe 4 miesiące. Pisała to jedna osoba, która miała wywalone na jakieś dzielenie odpowiedzialności a podstawowy zakres dobrych praktyk i testów znała chyba tylko ze słyszenia.
Kiedy aplikacja poszła do testów to sam autor nie był w stanie ogarnąć tego co się tam dzieje.
Połowa rzeczy nie działała, debugowanie było wyjątkowo ciężkie.

Oczywiście, wina całego zespołu - ale to był swojego rodzaju eksperyment.

Tak, czasem (a nawet często) trzeba iść na kompromisy. Nie ma kodu idealnego i trzeba wypracować jakieś akceptowalne standardy.
Tyle że bez przesady. W tym przypadku po 4 miesiącach padła decyzja o przepisaniu tego od zera. A co dopiero gdy przychodzi do pisania softu który rozwija się latami...

1

Po co refaktoryzować? Nie można od razu napisać dobrze? Czy słaby kod to zawsze zły kod?

Pracowałem z kilkoma osobami, co do których mam prawie pewność, że celowo piszą brzydki kod, żeby inni nie rozumieli tego kodu. Kod powinien odwzorowywać biznes, którego problem rozwiązujemy. Czytasz kod co najmniej 10 razy częściej niż go piszesz więc poświęć chwilę i nazwij zmienną tak, żeby ona była zrozumiała dla czytelnika. Ciężko mi policzyć ile błędów spotkałem lub uniknąłem dzięki przypominaniu ludziom, że nie są sami w projekcie i żeby celowo nie komplikowali kodu. Jeżeli kod wygląda na skomplikowany to znaczy, że jest po prostu źle napisany i prawdopodobnie też źle działa.

Bo jak niby ma działać dobrze kod, którego nie da się otestować bo ktoś tworzy klasę Bóg z milionem zależności. Dobra klasa to taka gdzie mogę sobie podmienić zależności i ją otestować bez stawiania miliona frameworków. I to samo tyczy się całych modułów. Jeżeli kontrakt między serwisami jest zrozumiały to łatwo jest te serwisy otestować. Jeżeli jest niezrozumiały to może biznes nie potrzebuje takiego produktu skoro nie da się łatwo wytłumaczyć jego potrzeby.

1

Celowe działanie jest jak najbardziej możliwe - przy skomplikowanym oprogramowaniu można sobie zapewnić "dożywocie" i dostatek ;)

1

Byłem świadkiem tego jak powstawała mała aplikacja, dość prosta w samych założeniach przez całe 4 miesiące. (...)
Kiedy aplikacja poszła do testów

Czyli klasyczny waterfall - najpierw piszemy całą aplikację, a potem "idzie do testów". No to jest właśnie przykład na to, czemu waterfall jest dość ryzykowny.

Tyle że bez przesady. W tym przypadku po 4 miesiącach padła decyzja o przepisaniu tego od zera. A co dopiero gdy przychodzi do pisania softu który rozwija się latami...

Czyli jednak skręcenie z waterfalla w model zwinny, gdzie są kolejne iteracje. I jaki był tego efekt? Przepisali to dobrze? Czy programista, który pisał pierwszą wersję, uczestniczył w pisaniu kolejnej?

3
twoj_stary_pijany napisał(a):

Pracowałem z kilkoma osobami, co do których mam prawie pewność, że celowo piszą brzydki kod, żeby inni nie rozumieli tego kodu.

var napisał(a):

Celowe działanie jest jak najbardziej możliwe - przy skomplikowanym oprogramowaniu można sobie zapewnić "dożywocie" i dostatek ;)

Takie sytuacje występują najczęściej przy zamówieniach publicznych. Osobny przetarg na utrzymanie systemu jeszcze bardziej zwiększa prawdopodobieństwo tego procederu. Na szczęście jest to miecz obosieczny.

1

@LukeJL: nie do końca - wstępne testy robił autor do spółki z osobą z którą się integrował. Oczywiście sam happy path, jak coś nie działało to doprawiał kod curry i jazda dalej. Po 3 miesiącach poszło do QA dlatego, że w tym przypadku ciężko było testować ten kawałek systemu w tak ograniczonym zakresie. Nie było to zbyt mądre podejście no ale też temat nie do końca dotyczy samego podejścia do zarządzania.

Nie zostało to przepisane wcale. Ze względu na koronawirusa firma prawie upadła, pozbyła się prawie całego zespołu i nie było jak tego przepisywać. Jakoś działa, błędy zostały połatane dodatkowym spaghetti i sam projekt już nie jest rozwijany. Nie mniej jednak jeśli ktoś wpadnie na pomysł pracy z tym to świeć panie nad jego duszą

3
nullpt4 napisał(a):

@somekind

To się po prostu nie uda. a próbowałeś, czy to jest założenie które pozwala na zracjonalizowanie sobie nie podejmowania wysiłku nauki nowych/innych paradygmatów programowania tym samym zachowując status quo nie wychodząc ze swojej strefy komfortu?

Nie, nie próbowałem stworzyć obiektowego języka programowania, a następnie dodawać mu mechanizmów funkcyjnych. Za to widzę, jak to wygląda na przykładzie C#, i generalnie język się coraz bardziej komplikuje, coraz gorzej wygląda, i myślę, że jest coraz trudniejszy do nauki dla początkujących.
A funkcje zagnieżdżone nie są ani ładne ani czytelne, w takim np. F# wygląda to dużo lepiej, bo tam jest naturalne, a do C# dostało doklejone na gluty z nosa po 20 latach.

A Ty ilu języków programowania jesteś autorem? Czy też tkwisz w strefie komfortu?

Wydaje mi się że tutaj jest konflikt starego i nowego podejścia do programowania.
@jarekr000000 uważa FP jako by był kolejnym krokiem naprzód jeśli chodzi o rozwiązywanie problemów związanych z pisaniem oprogramowania,
a takie OOP to archaizm który już powinien odejść do lamusa jak kiedyś np. goto,

Jak dla mnie, to nie ma niczego złego, aby oba podejścia współistniały i były używane tam, gdzie lepiej pasuje każde z nich.
Problem w tym, że ktoś na podstawie przykładu, który podałem w kontekście próbuje mi przypisać cechy, których nie posiadam. Pozostaje chyba tylko zadać pytanie o bicie żony.

a @somekind jest apologetą tego co już zna, potrafi i rozumie, a do nowych/odkrytych na nowo rozwiązań podchodzi sceptycznie/ostrożnie/(niechętnie?)

Jak pisałem w komentarzu - podałem przykład, jaki chciałem podać, a nie przykład, którego nie chciałem podawać. Naprawdę przeraża mnie jak bardzo tak banalna rzecz jak rozbicie jednego ifa na strategię okazuje się być tak trudne do zrozumienia na forum programistów.

Ale ogólnie tak, jestem sceptyczny do każdego rodzaju hajpu, i rzucania się jak szczerbaci na suchary na jakaś modną nowość, bo to się często źle kończy, jak mikroserwisy w crudzie albo DDD robione przez ludzi nie rozumiejących enkapsulacji.

3

Nie czytałem wszystkiego, ale było kilka stwierdzeń w stylu "celem pracy jest zysk". Zgadza się. I właśnie dlatego kod powinien być dobry/czysty. Być może już ktoś to pisał, więc powtórzę. Jeśli masz brudny kod, pisany na szybko i potem trzeba coś zmienić.. często może to być jakaś pierdoła, która zabiera dużo czasu... No i tutaj kluczem jest ten właśnie czas. Jeśli masz dobry kod, schludny, to zmiany w nim teoretycznie zajmują mniej czasu niż zmiany w kodzie brudnym. Testy pomagają sprawdzić, ile nowych błędów wygenerowała Twoja poprawka/zmiana. Czas, czas, czas... Czas to pieniądz. Zazwyczaj niestety jest tak, że na początku bardzo oszczędza się na czasie, bo trzeba jak najszybciej wypuścić produkt. Ale to się mści później.

4
Juhas napisał(a):

No i tutaj kluczem jest ten właśnie czas. Jeśli masz dobry kod, schludny, to zmiany w nim teoretycznie zajmują mniej czasu niż zmiany w kodzie brudnym.

Problem w tym, że doprowadzenie do "dobrego, schludnego" kodu może zająć więcej czasu niż dokonanie zmian w tym "brudnym" kodzie. I nie, nie jest tak, że od razu można pisać "dobry kod". Tzn. są oczywiście pewne reguły, których zawsze warto się trzymać, ale one nie zagwarantują że kod będzie czysty. Tak naprawdę, nie ma czystego kodu. Kod budujesz na bazie pewnej architektury, która z kolei opiera się na założeniach co dana aplikacja ma robić. W sytuacji gdy jej odbiorca zmienia swoje oczekiwania, może też okazać się, że przyjęta architektura w ogóle nie pasuje do nowych oczekiwań. To co przed chwilą wydawało się czyste i dopracowane, okazuje się nieodpowiednie - bo nagle pojawia się np. potrzeba komunikacji dwóch elegancko odseparowanych od siebie modułów. Coś co od siebie zależało nagle przestaje zależeć i trzeba te zależności poprzecinać, albo odwrotnie.

3
GutekSan napisał(a):

Problem w tym, że doprowadzenie do "dobrego, schludnego" kodu może zająć więcej czasu niż dokonanie zmian w tym "brudnym" kodzie.

No to jest chyba inicjalny problem tego wątku. Dlatego właśnie lepiej od razu pisać dobry, wtedy nie trzeba tracić czasu na naprawianie złego.

Tak naprawdę, nie ma czystego kodu. Kod budujesz na bazie pewnej architektury, która z kolei opiera się na założeniach co dana aplikacja ma robić. W sytuacji gdy jej odbiorca zmienia swoje oczekiwania, może też okazać się, że przyjęta architektura w ogóle nie pasuje do nowych oczekiwań.

Kod to kod, a architektura to architektura. Jeśli została źle dobrana, to to jest błędna architektura, i w żaden sposób nie zmienia to tego, że kod sam w sobie może być czysty.
To, że ktoś ma azbest na dachu nie oznacza, że nie może mieć porządku w mieszkaniu.

To co przed chwilą wydawało się czyste i dopracowane, okazuje się nieodpowiednie - bo nagle pojawia się np. potrzeba komunikacji dwóch elegancko odseparowanych od siebie modułów.

No i jaki jest problem, żeby takie moduły połączyć? Nawet nie trzeba w tym celu ruszać ich kodu. A wręcz nie powinno się.

Coś co od siebie zależało nagle przestaje zależeć i trzeba te zależności poprzecinać, albo odwrotnie.

Na czym polega przecinanie zależności, że jest to problemem?

2
somekind napisał(a):

No to jest chyba inicjalny problem tego wątku. Dlatego właśnie lepiej od razu pisać dobry, wtedy nie trzeba tracić czasu na naprawianie złego.

No ale twierdzę właśnie, że nie ma czegoś takiego jak "uniwersalnie dobry kod" i tego naprawiania się nie uniknie, bo "uniwersalnie dobry kod" to nie tylko kod czysty, ale i właściwie zaprojektowany.

Kod to kod, a architektura to architektura. Jeśli została źle dobrana, to to jest błędna architektura, i w żaden sposób nie zmienia to tego, że kod sam w sobie może być czysty. To, że ktoś ma azbest na dachu nie oznacza, że nie może mieć porządku w mieszkaniu.

Ok, tylko refaktoryzacja polega nie tylko na naprawianiu złego kodu, ale też na dostosowywaniu architektury.
Kto powiedział, że architektura została źle dobrana? Mogła być dobrana dobrze, tyle że do konkretnego zbioru założeń dotyczących danej aplikacji

No i jaki jest problem, żeby takie moduły połączyć? Nawet nie trzeba w tym celu ruszać ich kodu. A wręcz nie powinno się.

Taki, że pierwotnie te moduły albo klasy nie przewidywały komunikacji między sobą, i nie zaimplementowano mechanizmów na taką komunikację pozwalających.

Coś co od siebie zależało nagle przestaje zależeć i trzeba te zależności poprzecinać, albo odwrotnie.

Na czym polega przecinanie zależności, że jest to problemem?

Na tym, że A zależało od B (wywoływało coś z B, dostawało B, etc.) i nagle przestało, a A wciąż oczekuje czegoś z B, żeby prawidłowo działać. Teraz A musi skądś dostać te informacje skąd inąd. Pewne sznurki trzeba poprzeciągać na nowo.

1 użytkowników online, w tym zalogowanych: 0, gości: 1