Kiedy gruboziarnisty kod jest lepszy?

0

Ogólnie dużo zasad programistycznych skłania nas programistów do tworzenia drobnoziarnistego kodu, małych zgrabnych i prostych klas/funkcji, które łatwo i szybko można zrozumieć, ktore najlepiej jeśli odpowiadają jedną rzecz.

Natomiast mnie ciekawi inna rzecz, jaka sytuacja/rzecz/stan sprawia, że świadomie decydujecie się nie dzielić kodu na mniejsze części. W jakich okolicznościach dłuższy kod bez wydzielonej odpowiedzialności jest waszym zdaniem lepszy?

4

Chyba nigdy :P

5

Jeśli przebywasz w latach 80tych XX wieku i masz słaby kompilator, a kod musi działać na słabym sprzęcie, to czasami lepiej będzie zrobić nieco większe funkcje.

3

W dzisiejszych czasach uzasadniają to chyba jedynie jakieś ekstremalne optymalizacje albo praca na jakiś interfejsach co mają po 100 - 200 równorzędnych pól. Właściwie dziś to już chyba dotyczy jedynie języków interpretowanych ( np. JS ).
Jeszcze 10 lat temu pisząc w PHP serwer jednej usługi wywalenie klas i funkcji oraz przerzucenie wszystkiego do jednej funkcji przyspieszyło kod ponad 5 razy (walka była o każdą milisekundę). Nie da się jednak ukryć, że ten kod to koszmar programisty. Wówczas jednak priorytetowa była wydajność ( czas wykonania skryptu ).
Obecnie takie tricki jeszcze czasem stosuję w JavaScript np. gdy robię jakieś efekty graficzne, obliczenia poszczególnych pikseli w dużej tablicy (jedynie w sferze hobbystycznej).

7

job safety. Piszesz taki kod że tylko ty będziesz go umiał utrzymac :D
W innym wypadku nie ma to za bardzo sensu, bo kompilatory i JITy potrafią sobie ten kod zoptymalizować i nie trzeba tego robić ręcznie.

6

Nie wiem czy tego dotyczy pytanie, ale..

Pisząc jakąś nową funkcjonalność - na samym początku - KISS wydaje mi się ważniejsze niż SRP - tzn. jak mam powiedzmy pobrać jedną konkretna informację z jakiegoś datastore to nie robie całej hierarchii klas, interfejsów i całej masy "best practices", tylko próbuje to zamknąć w najmniejszej ilości kodu, jednej klasie/funkcji (zależy od jezyka). Bo wydaje mi się, że może będzie miała ona ciut za dużo odpowiedzialności, ale przynajmniej nie trzeba skakać po jakichś dziwnie nazwanych bytach żeby dowiedzieć się co się tam konkretnie dzieje.
Oczywiście nie pozwalam żeby to się rozrosło i jak tylko zauważam, że będzie się z tego datastore pobierało więcej albo w bardziej złożony sposób to staram się to zrobić już po ludzku, tak żeby to się łatwo rozwijało/utrzymywało.

Czyli w skrócie próbuje unikac overengineeringu :D ale tj. mówie może nie tego dotyczyło pytanie

natomiast co do:

W jakich okolicznościach dłuższy kod bez wydzielonej odpowiedzialności jest waszym zdaniem lepszy

Uważam, że to co opisałem nie jest... lepsze; bardziej bym to nazwał wystarczającym i czytelniejszym. To co jest "lepsze" jest ściśle związany z konkretnym kontekstem

1

Zależy co masz na myśli. Bo jeśli mówimy o np zrobieniu jednej lub dwóch warstw w architekturze zamiast 10 które by wymyślił architekt, no to chyba mówi samo przez się, że chodzi o budżet projektu lub czas wydania mvp.

1

@katakrowa:

Obecnie takie tricki jeszcze czasem stosuję w JavaScript np. gdy robię jakieś efekty graficzne, obliczenia poszczególnych pikseli w dużej tablicy (jedynie w sferze hobbystycznej).

A serio sprawdzałeś, że ma to sens?

Akurat V8 od dawna ma wbudowany inlining ( przez VM JS nie działa jak klasyczne interpretowane języki). Powinno nie być różnicy.

Dodam jeszcze, że w przypadku Javy kilka razy widziałem benchmarki pokazujące, że wieksze funkcje są lepsze - chyba 100% tych benchmarków było zrypanych.

0

Crypto. Wtedy loop-unrolling i inne dziwne wynalazki są często nawet pożądane by kompilator nie wyoptymalizował jakichś rzeczy za nas. W przeciwnym wypadku, praktycznie nigdy.

3

Jeszcze 10 lat temu pisząc w PHP serwer jednej usługi wywalenie klas i funkcji oraz przerzucenie wszystkiego do jednej funkcji przyspieszyło kod ponad 5 razy (walka była o każdą milisekundę).

No tak. Walcz o kazda milisekunde i jednoczesnie pisz w PHP - co za glupota

0

@stivens: nie wiem co w tym dziwnego widzisz, jak patrze sobie na testy to wydajność php jest porównywalna z wydajnością pythona, javy i C#.

3

nie ma nic lepszego niż funkcja zawierająca 5 wywołań innych funkcji, gdzie każda z nich jest w innym pliku i ma 1-3 LoC

bonusowe punkty, gdy one faktycznie są używane jeszcze w innych miejscach, ale są przekazywane jakieś opcjonalne parametry obsługujące jakieś edge case xD

super-wydzielone-mniejsze-części :D

blabla() {
	r1 = dosomething1();
	r2 = dosomething2();
	r3 = dosomething3();
	r4 = dosomething4();
	r5 = dosomething5();
}

File1:	dosomething1() => 1+2;
File2:	dosomething2() => 3+2;
File3:	dosomething3() => 4+2;
File4:	dosomething4() => 5+2;
File5:	dosomething5(if_invoked_from_dupa) => if_invoked_from_dupa ? Dupa_Const + 4 : Super_Internal_Const + 2;

ehh te boby uncle

1
jarekr000000 napisał(a):

A serio sprawdzałeś, że ma to sens?

Zdecydowanie ma sens i to nie tylko w JS. Mam taką specyfikę pracy, że bardzo często obramiam odpowiedzi mające po dziesiątki tysięcy rekordów i wykonuję na nich obliczenia stąd ta tematyka jest mi bardzo bliska i ciągle bieżąca. Wciąż dotyczy to także nowych PHP t.j. np. 7.2 albo 7.3. Podaję poniżej przykład w JS gdzie ten sam kod po wywaleniu do funkcji wykonuje się 2x wolniej niż w jednej funkcji:

screenshot-20201101144900.png
Czasy oczywiście zależą od rozdzielczości i komputera ale proporcja w różnicach poszczególnych opcji powinna być stała.

Wersja bez funkcji - czas wykonywania głównej pętli ~1020ms (na wolnym laptopie):

<html>
<body style='border:0;margin:0;padding:0;'>
  
  <canvas id="screen" style='width:100%;height:100vh;'></canvas>  

  <script>

    var screen = document.getElementById('screen');
    var maxX = screen.clientWidth ;
    var maxY = screen.clientHeight ;
    var ctx = screen.getContext("2d");
    screen.width = maxX ;
    screen.height = maxY ;    
    
    var start = 0 ;    
    var end = Math.PI*2 ;
    var dr = (end-start) / maxX ;
    var loopCounter = 0;      
    
    function rysuj()
    { 
      console.log ( wsp );
      loopCounter++ ;
      var wsp = 1.4+Math.sin(loopCounter/60) ;
      
      var r = start;
      ctx.fillStyle = "#fff";
      ctx.fillRect ( 0, 0, maxX, maxY );
      ctx.fillStyle = "rgba(32,64,128,0.2)";
      console.time("Obliczenia");
      for ( var nadmiarowa = 1; nadmiarowa < 10 ; nadmiarowa++ )
      {
        for ( var xPos = 0; xPos <= maxX; xPos++ )
        {          
          var x = wsp;
          r += dr ;
          for ( var idx = 0; idx < 55 ; idx ++ )
          {
            x = Math.sin( r + x*wsp )*Math.cos( loopCounter/67 ) ;
            ctx.fillRect ( xPos, maxY/2 - ( x * maxY/2 ), 1, 1 );
          }
          /**/
        }
      }
      console.timeEnd("Obliczenia");
      
      setTimeout( function(){rysuj();}, 50 );
    }
            
    rysuj();
      
  </script>
</body>
</html>

Wersja z wydzielonymi funkcjami - czas wykonywania głównej pętli 2100ms (na wolnym laptopie):

<html>
<body style='border:0;margin:0;padding:0;'>
  
  <canvas id="screen" style='width:100%;height:100vh;'></canvas>  

  <script>

    var screen = document.getElementById('screen');
    var maxX = screen.clientWidth ;
    var maxY = screen.clientHeight ;
    var ctx = screen.getContext("2d");
    screen.width = maxX ;
    screen.height = maxY ;    
    
    var start = 0 ;    
    var end = Math.PI*2 ;
    var dr = (end-start) / maxX ;
    var loopCounter = 0;      
    
    function inSubLoop( xPos, r, x, wsp )
    {
      x = Math.sin( r + x*wsp )*Math.cos( loopCounter/67 ) ;
      ctx.fillRect ( xPos, maxY/2 - ( x * maxY/2 ), 1, 1 );
      return x ;
    }
    
    function subLoop ( xPos, r, x, wsp )
    {
      for ( var idx = 0; idx < 55 ; idx ++ )
      {
        x = inSubLoop( xPos, r, x, wsp );
      }
      return x ;
    }

    function rysuj()
    { 
      console.log ( wsp );
      loopCounter++ ;
      var wsp = 1.4+Math.sin(loopCounter/60) ;
      
      var r = start;
      ctx.fillStyle = "#fff";
      ctx.fillRect ( 0, 0, maxX, maxY );
      ctx.fillStyle = "rgba(32,64,128,0.2)";
      console.time("Obliczenia");
      for ( var nadmiarowa = 1; nadmiarowa < 10 ; nadmiarowa++ )
      {
        for ( var xPos = 0; xPos <= maxX; xPos++ )
        {          
          var x = wsp;
          r += dr ;
          x = subLoop ( xPos, r, x, wsp );
        }
      }
      console.timeEnd("Obliczenia");
      
      setTimeout( function(){rysuj();}, 50 );
    }
            
    rysuj();
      
  </script>
</body>
</html>

1

U mnie czasy w tym czymś są takie chrome:
razem kod: ~520ms (po jakimś czasie)
rozbity na funkcje: ~512ms (po jakimś czasie)

Ale i tak z tego nic nie wynika, bo to nie jest żaden benchmark.

1

Najpierw powiem jak rozumiem odpowiedzialność - określa jednoznaczne znaczenie, i swoim zakresem obejmuje tylko to co niezbędne. Można o tym myśleć tak jakby definicja kodu była zamnięta na modyfikacje, bo dana partia reprezentuje wszystko co mieści się w znaczeniu wybranej konstrukcji (wzory, transformacje itp) przy takim kodzie osoba jak potrzebuje zmian to prędzej myśli o stworzeniu czegoś odrębnego, niż o rozszerzeniu pierwotnego znacznia. Przykładowo jak mamy sumę to nikt (chyba nikt) nie myśli o tym, by przysłaniać definicję sumy, funkcją która również potrafi zliczyć co drugi element wybranej sekwencji, bardziej myślimy w kierunku utworzę drugą osobną definicję z jej własnym znaczeniem, prawda?

Jak się można domyśleć wiele rzeczy nie spełnia powyższego rozumowania, taki kod ma dużo bardziej rozmyty charakter, mówimy odpowiedzialność, ale tak naprawdę patrzymy jak przez mgłę. Wybrana partia kodu chociaż ma przypisaną rolę, to wraz z rozwojem projektu kod będzie się rozrastał w różne strony, i jego znaczenie coraz mocniej bardziej odbiegać od tego czym w rzeczywistości jest wybrana partia kodu. Dopóki nie wiadomo co czym jest trudniej jest rozpoznać granice.

Dodatkowo drobnoziarnisty kod to miód dla programistów taki kod, który w pewien sposób podlega zasadom, dzięki temu kod przewidywalny, idiomatyczny, lekki w rozumowaniu, ale z drugiej strony kruchy, bo też bardzo zależy od założeń na jakich bazuje. Co jeśli wybrane założenie z czasem okaże się błędne? Albo nawet jeśli dobre, to co jeśli trzeba od niego odejść. Wtedy dziesiątki podziałów trochę rozmija się z sensem.

Ja mam tak, że jak robię coś pod front, albo mobilne to nie mam wyczucia w którym miejscu dobrze jest wykonać podział, bo tak tutaj widze, że odpowiedzialność w 99% ma rozmyte znaczenie. Mógłbym tak każdy guzik zmieniać w klasę, ale z takich podziałów korzyść na ogół jest średnia, bo każdy komponent ściska trochę struktury, trochę wyglądu, trochę logiki, te komponenty na siebie oddziaływują, co je czyni niepewnym w dalszym użyciu, bo jedno z tych trzech za jakiś czas się zmieni.

Po stronie baz danych np. mógłbym wycisnąć ile się da, by schemat był bardziej znormalizowany, ale z drugiej strony to podniesie mi złożoność zapytań i będę musiał na około używać znacznie więcej unii, left joinów niż gdyby korzystać w tabelkach w tej gorszej postaci. W pewnym stopniu przez dłuższy czas gorsze jest lepsze.

To samo mam jak operuje na projekcie i nie widzę wszystkiego, np. nie wiem jakie ograniczenia narzuci mi praca z wybraną biblioteką. Wyznaczam nową warstwę gdzie współgra się z tą biblioteką, ale w jej obrębie nie próbuje wyznaczać dodatkowych podziałów, abstrakcji i tym podobnych rzeczy kod jest zbyt konkretny by uogólniać, a z drugiej strony nadal dla mnie niejednoznaczny i po części niepewny :D

Także bardziej tego typu rzeczy mówią mi, że coś po drodzę robię źle próbując większe dzielic na mniejsze.

2

@WeiXiao:

ehh te boby uncle

Sorry, ale rozdzielanie na kodu na mniejsze funkcje to robiliśmy jeszcze długo zanim nawet usłyszałem o Uncle Bob. Tak gdzieś od czasów pisania w C... czyli u mnie i znajomych 1993/4.
I to, że tych mniejszych funkcji nikt inny nie używa - to nie jest argument. Nie o to chodzi (chociaż to miły dodatek).

1

@jarekr000000:

Ależ nie mam żadnych wątpliwości że wyodrębnialiście fragmenty kodu do funkcji już dawno temu.

Niemniej jednak wydaje mi się że wiele osób dopiero zaczęło o tym myśleć od klin koda od UB.

I to, że tych mniejszych funkcji nikt inny nie używa - to nie jest argument. Nie o to chodzi (chociaż to miły dodatek).`

No nie jest, bo nie taki był point.

Chodzi o to, że w obie strony można przesadzać(co wydaje się bluźnierstwem w tym temacie), a debugowanie takiego przykładu jak jest wyżej - no hehe mamy klin kod bo wszystko ładnie wydzielone, dekołpling 120% jest męczarnią.

Już lepiej mieć 15 LoC w 1 miejscu niż 5x3 LoC bo jest lepsza czytelność i mniejszy cognitive load, nie trzeba skakać pomiędzy tymi funkcjami (co i tak współczesne IDE bardzo ułatwiają, bo praktycznie 1 kliknięciem się skacze)

Te flagi i parametry przekazujące kontekst do tych metod to tylko taka patologia

0

Edit: po restarcie przeglądarki w teście nie widać różnicy, więc test jest raczej słaby.
Być może wynika to z tego że wykres w trakcie działania zmienia swój stopień złożoności i trzeba by zebrać wyniki dla całości żeby coś powiedzieć o wydajności, dodać jakieś średnie, odchylenia, czas na rozgrzanie...

5

@WeiXiao:

Już lepiej mieć 15 LoC w 1 miejscu niż 5x3 LoC bo jest lepsza czytelność i mniejszy cognitive load, nie trzeba skakać pomiędzy tymi funkcjami (co i tak współczesne IDE bardzo ułatwiają, bo praktycznie 1 kliknięciem się skacze)

Nie zgadzam się.
Po to się robi 5x3 LoC, żeby nie trzeba było skakać między funkcjami. Jeśli jest taka potrzeba... to ktoś coś zrypał - pewnie naming.
Oczywiście naming is hard i prawdopodobieństwo zrypania jest duże, ale prawdopodobieństwo, że ktoś, kto nie umie nazwać sekcji kodu zrobi większą funkcję w sposób czytelny jest też raczej małe.

0

@jarekr000000:

Po to się robi 5x3 LoC, żeby nie trzeba było skakać między funkcjami.

Przy refactorze, bo zmienimy tylko 1 miejscu, a co z przypadkami gdzie ktoś wydziela kod który jest użyty 1x?

W sumie fajnie bo w przyszłości będzie łatwiej użyć, a jeżeli ta przyszłość nie nadchodzi? no to wychodzą pseudo klinkodowe, ale niezbyt fajne w czytaniu funkcyjki :D

5

@WeiXiao:

Musiałem jakoś niejasno napisać. Chodziło mi o ten przypadek kiedy taka funkcja jest używana tylko raz i nawet nie podejrzewam, żeby kiedykolwiek miał ją gdzieś indziej użyć.
Rozdzielanie nadal ma sens. Patrząc na każdą z tych funkcji (głowną i podfunkcje) masz mniej logiki do ogarnięcia.
Jeśli oglądając główną funkcję interesuje cię kod podfunkcji... to ktoś coś zrypał (bywa).

4
jarekr000000 napisał(a):

Rozdzielanie nadal ma sens. Patrząc na każdą z tych funkcji (głowną i podfunkcje)

Pytanie, dlaczego uważamy funkcje za najlepszy sposób na rozdzielenie tego. Dlaczego nie użyć bloku oddzielonego komentarzem, zwijalnego regionu w IDE, bloku wewnątrz klamerek, albo może czegoś zupełnie innego.

Wydzielona funkcja ma stosunkowo duży koszt. Ma osobny zestaw parametrów, przez co albo robię domknięcie (lambdą czy funkcją zagnieżdżoną), albo muszę przekazywać parametry. Inlining niby obniża ten koszt, ale różnie z tym bywa, kompilatory mają limity na rozmiar kodu, psuje to stosy przy wyjątkach, przy kolorowych funkcjach dokłada kolejną maszynę stanów i alokowane obiekty. Nawet jeżeli kompilator umie to wszystko zoptymalizować, to i tak nie przebije dajmy na to bloku oddzielonego gwiazdkami w komentarzu. Zwiększamy skomplikowanie kompilatorów w imię lepszej czytelności, która jest wielokrotnie kwestionowana i nie ma jasnej reguły, jak dzielić funkcje na mniejsze.

Przydałaby się dynamiczna analiza kodu, która potrafiłaby wziąć długą funkcję i logicznie podzielić ją na fragmenty, a potem pozwijać je w IDE. Brzmi jak ciekawy pomysł na magisterkę (model ML + jakaś wtyczka do IntelliJ).

9
  1. Dlaczego funkcje są lepsze od komentarzy i zwijania w IDE?

a) bo automatycznie ułatwiają testowanie,
b) bo, ułatwiają debug,
c) zmniejszają rozmiar problemu - analizując większą funkcję masz w lokalnym zasięgu więcej zmiennych lokalnych itp. nieważne jak sobie to poprzedzielasz komentarzami, funkcja automatycznie przerywa takie zależności,
d) komentarze to w ogóle WTF - myślałem, że już ten temat był wielokrotnie wałkowany, prędzej czy później komentarz rozjedzie się z kodem którego dotyczy, w przypadku funkcji może rozjechać się nazwa (którą łatwo poprawić), ale przynajmniej parametry będą musiały mieć sens
e) co do zrzucania roboty na IDE - jakoś problemów z jakością kompilatorów nie mam, nieważnie jak poryty kod tworzę - natomiast IDE wykrzaczają mi się często i nie trzeba się starać. Kompilowanie kodu, który IDE oznacza cały na czerwono, bo się gubi, to zwykle standard. (nawet w prostackim kotlinie). Dlatego przerzucanie pracy z kompilatorów na IDE to słaby pomysł.

Wydzielona funkcja ma stosunkowo duży koszt.

Jeśli chodzi o performance to jedno pytanie:
A kiedy mierzyłeś?
To nie tylko kompilatory są sprytniejsze, również od dawna hardware coraz lepiej radzi sobie z takim kodem. O ile nie uruchamiasz kodu na 80386 to CPU jest wstanie ogarnąć dużo optymalizacji nawet jesli twój kompilator nie daje rady.

Co więcej - jeszcze 10 lat temu - w javie w czasach jdk 1.5 często bywało tak, że podział na mniejsze funkcje prowadził do lepszej wydajności - po prostu mniejsze funkcje łatwiej inlinować, były szybciej i wydajniej kompilowane - duże od razu przepadały.... ale to raczej "dziwactwa" kompilatorów w jvm/hotstpota. Z tego co wiem obecnie jest mniej takich problemów.

Zwiększamy skomplikowanie kompilatorów w imię lepszej czytelności,

Tu raczej tej jest sprawa jasna - kompilator piszemy raz, i może być skomplikowany - po to właśnie żeby kod dla ludzi mógł być prosty. Temat wielokrotnie wałkowany.

Poruszyłeś też temat kolorowych funkcji - jak dla mnie to zupełnie ortogonalne zagadnienie, do tego co omawiamy. Bo tutaj jednak kod w wersji zbitej i rozproszonej na funkcje async ma w zasadzie inną strukturę. Zresztą mnie osobiście strasznie te wszystkie asyki i suspendy wkurzają (ale to temat na całkiem inną dyskusję).

Z jednym się zgadzam:

to i tak nie przebije dajmy na to bloku oddzielonego gwiazdkami w komentarzu.

Kod w komentarzu wykonuje się zwykle nieskończenie szybko.

Nie wiem czy serio pisałeś (bo część argumentów brzmi jak dyskusje, które toczyłem może 15 lat temu). Ale fragment o komentarzu z gwiazdkami brzmi jak jakiś ostry trolling.

3
jarekr000000 napisał(a):

a) bo automatycznie ułatwiają testowanie,

Jeżeli testujesz tę mniejszą funkcję, to wołasz ją z kilku miejsc, więc nie jest to przypadek, który ja opisuję.

jarekr000000 napisał(a):

b) bo, wbrew temu co piszesz ułatwiają debug,

Nie widzę tej przewagi, ale to subiektywne. Poza tym nigdzie nie pisałem o debugowaniu.

jarekr000000 napisał(a):

c) zmniejszają rozmiar problemu - analizując większą funkcję masz w lokalnym zasięgu więcej zmiennych lokalnych itp. nieważne jak sobie to poprzedzielasz komentarzami, funkcja automatycznie przerywa takie zależności,

Co może być zaletą i wadą, zresztą poruszyłem to w swoim poście. No i to tylko w przypadku "pełnej" funkcji, jeżeli zrobię funkcję zagnieżdżoną lub lambdę, to mogę dalej łapać przez domknięcie (mniej lub bardziej dowolnie, w zależności od języka).

jarekr000000 napisał(a):

d) komentarze to w ogóle WTF - myślałem, że już ten temat był wielokrotnie wałkowany, prędzej czy później komentarz rozjedzie się z kodem którego dotyczy, w przypadku funkcji może rozjechać się nazwa (którą łatwo poprawić), ale przynajmniej parametry będą musiały mieć sens

W opisywanym przeze mnie przypadku będzie tak samo, nic się nie rozjedzie bardziej, niż rozjechałoby się w normalnej funkcji. A kwestię "komentarze to w ogóle WTF" pominę, to jest podejście dogmatyczne, a takie zazwyczaj prowadzi na manowce.

jarekr000000 napisał(a):

d) co do zrzucania roboty na IDE - jakoś problemów z jakością kompilatorów nie mam, nieważnie jak poryty kod tworzę - natomiast IDE wykrzaczają mi się często i nie trzeba się starać. Kompilowanie kodu, który IDE oznacza cały na czerwono, bo się gubi, to zwykle standard. (nawet w prostackim kotlinie).

To jest średnio wartościowy dowód anegdotyczny, więc nawet się nie będę odnosił.

jarekr000000 napisał(a):

Jeśli chodzi o performance to jedno pytanie:
A kiedy mierzyłeś?

Pisałem o koszcie koncepcyjnym, Ty wyciągnąłeś jedno słowo i mówisz o czymś zupełnie innym.

jarekr000000 napisał(a):

To nie tylko kompilatory są sprytniejsze, również od dawna hardware coraz lepiej radzi sobie z takim kodem. O ile nie uruchamiasz kodu na 80386 to CPU jest wstanie ogarnąć dużo optymalizacji nawet jesli twój kompilator nie daje rady.
Co więcej - jeszcze 10 lat temu - w javie w czasach jdk 1.5 często bywało tak, że podział na mniejsze funkcje prowadził do lepszej wydajności - po prostu mniejsze funkcje łatwiej inlinować, były szybciej i wydajniej kompilowane - duże od razu przepadały.... ale to raczej "dziwactwa" kompilatorów w jvm/hotstpota. Z tego co wiem obecnie jest dużo mniej takich.

O tym w ogóle nie pisałem, więc nie będę się odnosił.

jarekr000000 napisał(a):

Tu raczej tej jest sprawa jasna - kompilator piszemy raz, i może być skomplikowany - po to właśnie żeby kod dla ludzi mógł być prosty. Temat wielokrotnie wałkowany.

I wielokrotnie ludzie narzekają, że małe funkcje nie są dla nich czytelniejsze.

jarekr000000 napisał(a):

Poruszyłeś też temat kolorowych funkcji - jak dla mnie to zupełnie ortogonalne zagadnienie, do tego co omawiamy. Bo tutaj jednak kod w wersji zbitej i rozproszonej na funkcje async ma w zasadzie inną strukturę. Zresztą mnie osobiście strasznie te wszystkie asyki i suspendy wkurzają (ale to temat na całkiem inną dyskusję).

Ma tę samą strukturę, jeżeli wydzieliłem fragment funkcji async do osobnej metody, to ta osobna też musi być async (jeżeli robi awaita gdzieś w środku), co dokłada kolejne obciążenie.

jarekr000000 napisał(a):

Kod w komentarzu wykonuje się zwykle nieskończenie szybko.

Dobry suchar, będzie puchar!

2

@jarekr000000:

a) bo automatycznie ułatwiają testowanie,

Ale jakie testowanie? takie funkcje zazwyczaj są internal/private, a testujesz przez publiczne API(no chyba, że masz złożony algorytm), więc nic to nie zmienia.

1

No właśnie, świat jest bardziej skomplikowany niż private/public.
Można takie funkcje zrobić internal, package, public (but not exported) i normalnie testować.
nie mówię, że trzeba takie funkcje osobno testować - to raczej przypadek szczególny kiedy algorytm jest skomplikowany albo coś działa nie tak jak oczekujemy (bo nam się inty przekręcają, bo walneliśmy się w priorytetach operatorów). Czyli raczej nieczęsto w typowym kodzie biznesowym... ale bywa.

2

@jarekr000000: Czyli teraz zaczynasz podnosić argument, że wydzielanie mniejszych funkcji pozwala na testowanie bebechów? A innym razem pewnie będziemy mówili, że testować powinno się tylko przez publiczne API i testowanie internal/private (albo z jakimiś adnotacjami InternalsVisibleTo) to zło? No tak, to się trzyma kupy.

2

@Afish coś mi wmawiasz - czyli wchodzimy w chwyt erystyczny numer już nie pamiętam.

Uważam, że powinno się testować to co jest istotne to działania/akceptacji programu (i faktycznie to pewnie publiczne API) oraz to co się może zepsuć, gdzie się już potknęliśmy. (To takie podejście bug driven).

Tu pewnie mieszasz się z testowaniem mocków (a mieliśmy taką dyskusję)- jesli funkcja publiczna A wywołuje B - to nie znaczy, że testując A testuję czy było wywołane B. Wręcz przeciwne, na tym poziomie istnienie B jest mi obojętne. Testuję publiczne API.
Co nie oznacza, że pomocniczo nie można sobie niezależnie przetestować działania B. To jest zupełnie normalne.

Adnotacji w stylu VisibleToTesting nie używam. W kotlinie mam internal. W javie mam package, albo public. Tym niemniej dyskutujemy o problemie, który naprawdę nieczęsto się pojawia - napisałem ułatwia testowanie - bo masz gotowe (ew. prawie gotowe, bo może zmienisz kwalifikator dostępu). Nie napisałem, że musisz testować.

1

Nie wiem jak dla was ale czym mniej zmiennych (albo wartości :) ) tym łatwiej się debuguje.

3

@scibi92:

pójdźmy krok dalej - im mniej kodu (ogólnie)!

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