Kiedy gruboziarnisty kod jest lepszy?

5

@jarekr000000: Nic Ci nie wmawiam. Zacząłem rozpatrywać przypadek, gdzie można wydzielić fragment większej metody do mniejszej metody tylko dla czytelności, co oznacza, że ta mniejsza metoda nigdy nie była testowana (bo wcześniej była częścią większej metody). Ty powiedziałeś, że takie wydzielenie ułatwia testowanie, co nie odnosi się do mojego przypadku, w którym takie testowanie w ogóle nie powinno mieć miejsca. Co więcej, kłóci się to z konsensusem, że bebechów w taki sposób testować się nie powinno. Jeżeli teraz powiemy, że wydzielanie metod pomaga w testowaniu, to jest to dyskusja o dwóch różnych sprawach.

No ale żeby ta rozmowa była jakaś konkretniejsza, to lepiej przeanalizować kod. Weźmy funkcję oczekującą trójmianu z pierwiastkami rzeczywistymi, zwracającą informację, czy co najmniej jeden pierwiastek jest liczbą dodatnią. Kod w javo/siszarpo/javascripto-podobnej notacji, rzucanie wyjątku jest świadome, żeby pokazać kilka dodatkowych rzeczy:

Wersja 1:

public bool hasPositiveRoot(double a, double b, double c){
	double delta = b*b - 4*a*c;
	if(delta < 0){
		throw new Exception("Polynomial has no real roots");
	}
	
	double x1 = (-b + Math.sqrt(delta))/(2*a);
	double x2 = (-b - Math.sqrt(delta))/(2*a);
	
	return x1 > 0 || x2 > 0;
}

Wersja 2:

public bool hasPositiveRoot(double a, double b, double c){
	double delta = calculateDelta(a, b, c);
	validateRealRoots();
	
	Pair roots = calculateRoots(a, b, delta);
	return roots.Item1 > 0 || roots.Item2 > 0;
}

private double calculateDelta(double a, double b, double c){
	return b*b - 4*a*c;
}

private void validateRealRoots(double delta){
	if(delta < 0){
		throw new Exception("Polynomial has no real roots");
	}
}

private Pair<double, double> calculateRoots(double a, double b, double delta){
	double x1 = (-b + Math.sqrt(delta))/(2*a);
	double x2 = (-b - Math.sqrt(delta))/(2*a);
	
	return Pair.of(x1, x2);
}

Wersja 3:

public bool hasPositiveRoot(double a, double b, double c){
	private double calculateDelta(double a, double b, double c){
		return b*b - 4*a*c;
	}

	private void validateRealRoots(double delta){
		if(delta < 0){
			throw new Exception("Polynomial has no real roots");
		}
	}

	private Pair<double, double> calculateRoots(double a, double b, double delta){
		double x1 = (-b + Math.sqrt(delta))/(2*a);
		double x2 = (-b - Math.sqrt(delta))/(2*a);
		
		return Pair.of(x1, x2);
	}

	double delta = calculateDelta(a, b, c);
	validateRealRoots();
	
	Pair roots = calculateRoots(a, b, delta);
	return roots.Item1 > 0 || roots.Item2 > 0;
}

Wersja 4:

public bool hasPositiveRoot(double a, double b, double c){
	private double calculateDelta(){
		return b*b - 4*a*c;
	}

	private void validateRealRoots(){
		if(delta < 0){
			throw new Exception("Polynomial has no real roots");
		}
	}

	private Pair<double, double> calculateRoots(){
		double x1 = (-b + Math.sqrt(delta))/(2*a);
		double x2 = (-b - Math.sqrt(delta))/(2*a);
		
		return Pair.of(x1, x2);
	}

	double delta = calculateDelta(a, b, c);
	validateRealRoots();
	
	Pair roots = calculateRoots(a, b, delta);
	return roots.Item1 > 0 || roots.Item2 > 0;
}

Wersja 5:

public bool hasPositiveRoot(double a, double b, double c){
	Func<double, double, double, double> calculateDelta = (a, b, c) => b*b - 4*a*c;
	Action<double> validateRealRoots = delta => { if(delta < 0) throw new Exception("Polynomial has no real roots"); };
	Func<double, double, double, Pair<double, double>> calculateRoots = (a, b, delta) => {
		double x1 = (-b + Math.sqrt(delta))/(2*a);
		double x2 = (-b - Math.sqrt(delta))/(2*a);
		
		return Pair.of(x1, x2);
	};

	double delta = calculateDelta(a, b, c);
	validateRealRoots();
	
	Pair roots = calculateRoots(a, b, delta);
	return roots.Item1 > 0 || roots.Item2 > 0;
}

Wersja 6:

public bool hasPositiveRoot(double a, double b, double c){
	Func<double> calculateDelta = () => b*b - 4*a*c;
	Action validateRealRoots = () => { if(delta < 0) throw new Exception("Polynomial has no real roots"); };
	Func<Pair<double, double>> calculateRoots = () => {
		double x1 = (-b + Math.sqrt(delta))/(2*a);
		double x2 = (-b - Math.sqrt(delta))/(2*a);
		
		return Pair.of(x1, x2);
	};

	double delta = calculateDelta(a, b, c);
	validateRealRoots();
	
	Pair roots = calculateRoots(a, b, delta);
	return roots.Item1 > 0 || roots.Item2 > 0;
}

Wersja 7:

public bool hasPositiveRoot(double a, double b, double c){
	// Calculate delta
	double delta = b*b - 4*a*c;
	
	// Validate real roots
	if(delta < 0){
		throw new Exception("Polynomial has no real roots");
	}
	
	// Calculate roots
	double x1 = (-b + Math.sqrt(delta))/(2*a);
	double x2 = (-b - Math.sqrt(delta))/(2*a);
	
	return x1 > 0 || x2 > 0;
}

I teraz tak:

  • Wersja 1 - Nic specjalnego, kilka linijek kodu, ale psuje SRP (bo liczy pierwiastki i porównuje).
  • Wersja 2 - Wydzielone mniejsze metody, których nie powinniśmy testować. Kod jest dłuższy, nie może zostać w pełni zinline'owany (bo rzucamy wyjątek, co psuje stos), więc teoretycznie może być wyższy koszt. Dygresja - nawet dodanie try + finally do metody (z pustymi blokami) może zwiększyć koszt wykonania znacząco. Zauważmy też, że musimy przepychać parametry między metodami.
  • Wersja 3 - To samo, co wersja 2, ale tym razem funkcje te są wewnętrzne.
  • Wersja 4 - Jak wersja 3, ale tym razem parametry łapiemy przez domknięcie.
  • Wersja 5 - Jak wersja 3, ale tym razem zamiast metod zagnieżdżonych mamy nazwane lambdy.
  • Wersja 6 - Jak wersja 5, ale tym razem zamiast parametrów łapiemy przez domknięcia.
  • Wersja 7 - Jak wersja 1, ale bloki "nazwane" komentarzami (jak ktoś ma w języku regiony czy inne przydasie, to może ich użyć zamiast komentarzy).

I teraz pytania:

  • Dlaczego twierdzimy, że wersje 2-6 (z wydzielonymi metodami) są czytelniejsze od wersji 1/7? Nie pytam o parametry, tylko o czytelność.
  • Co lepsze, osobne metody na tym samym poziomie (wersja 2), metody zagnieżdżone (3-4), czy może lambdy (5-6)? I dlaczego?
  • Lepiej przekazywać parametry w tę i nazad (2, 3, 5), czy łapać przez domknięcie (4, 6)? I dlaczego?
  • Dlaczego ignorujemy spadek wydajności wersji 2-6? To jest mierzalny spadek, który zależy od platformy i rodzaju kodu, czasami inline będzie ograniczony przez długość metody, czasami przez wyjątki, czasami przez rodzaje parametrów (ref, out, wskaźniki itp), czasami po prostu kompilator nie będzie inline'ował, bo robi szybką kompilację.
  • Czy jeżeli wersja 7 jest czytelna, to czy dalej chcemy ryzykować utratę wydajności przez użycie wersji 2-6? Jeżeli tak, to dlaczego?

Dla łatwości pominąłem wersję, gdzie metody dodatkowo są async, ale jeżeli byśmy takie mieli, to pytanie ekstra: dlaczego ignorujemy koszt maszyn stanowych?

2

1 i 7 są najczytelniejsze.

2 do odstrzału, bo nic nie wnosi a cognitive load jest znacznie wyższy.

2

Dla mnie przykładem, który mi zapadł w pamięć kiedy nie opłaca się rozdzielać zawsze to np.:
https://koziolekweb.pl/2011/10/22/ekstremalna-obiektowosc-w-praktyce-%e2%80%93-czesc-1-tylko-jeden-poziom-zaglebienia-na-metode/

Tutaj Koziołek rozbija potrójnego fora do 3 funkcji, gdzie przy mnożeniu macierzy dla mnie ten potrójny for jest takim standardowym algorytmem, że nie rozbijałbym tego nigdy w życiu.

Chyba ciężko się nie zgodzić z @WeiXiao - czasami po prostu warto dla nieprzeciążania programisty nie kazać mu skakać i pozwolić mu przeczytać te parę linijek

3

Przyznaje, że dyskutować na kodzie to lubię.
Tutaj może troszkę przykład średni - bo nie mamy funkcji 15LOC którą rozbijamy na 5x3LOC.
Już wyjściowa funkcja to tak 5 linii (+ klamry) - więc taki sobie przykład.
Do tego dosyć popularne w szkole zagadnienie, dlatego czytelność od razu lepsza niż analogicznym kodzie o podobnej złożoności (znana domena).

  1. Przeróbmy na kotlin (wolę to niż pseudojęzyk w którym wszystko może lub nie może działać w zależności kto pyta)
object Roots {
    fun hasPositiveRoot(a: Double, b: Double, c: Double): Boolean {
        val delta = b * b - 4 * a * c
        if (delta < 0) {
            throw Exception("Polynomial has no real roots")
        }

        val x1 = (-b + Math.sqrt(delta)) / (2 * a)
        val x2 = (-b - Math.sqrt(delta)) / (2 * a)
        return x1 > 0 || x2 > 0
    }
}

  1. To moja propozycja 2:
object Roots2 {
    fun hasPositiveRoot(a: Double, b: Double, c: Double): Boolean =
        calcRoots(a, b, c)?.let { (x1, x2) ->
            x1 > 0 || x2 > 0 
        } ?: throw Exception("Polynomial has no real roots")

    private fun calcRoots(a: Double, b: Double, c: Double): Pair<Double, Double>? =
        calcDelta(a, b, c).let { delta ->
            when {
                delta < 0 -> null
                else -> Pair(-b + Math.sqrt(delta) / (2 * a), -b - Math.sqrt(delta) / (2 * a))
            }
        }

    private fun calcDelta(a: Double, b: Double, c: Double) = b * b - 4 * a * c
}

jak widać - nieco więcej kodu. Czytelność do osądu.

  1. W trakcie prac wpadam na pomysł zrobienia bardziej ogólnej funkcji any (której nie ma w standardzie dla Pair)
object Roots3 {
    fun hasPositiveRoot(a: Double, b: Double, c: Double): Boolean =
        calcRoots(a, b, c)?.let { roots -> roots.any{it > 0 }
        } ?: throw Exception("Polynomial has no real roots")

    private fun calcRoots(a: Double, b: Double, c: Double): Pair<Double, Double>? =
        calcDelta(a, b, c).let { delta ->
            when {
                delta < 0 -> null
                else -> Pair(-b + Math.sqrt(delta) / (2 * a), -b - Math.sqrt(delta) / (2 * a))
            }
        }

    private fun calcDelta(a: Double, b: Double, c: Double) = b * b - 4 * a * c
}

fun <A> Pair<A,A>.any(p: (A)->Boolean) = p(this.first) || p(this.second)

czytelność do osądu, ale spodobało mi się, że mam nową generyczną funkcję do użycia (której nie widzę w standardzie)

  1. A co do dlaczego ignorujemy spadek wydajności...

Test w jmh:

    @Benchmark
    public void root1(Blackhole blackhole) {
        blackhole.consume(Roots.INSTANCE.hasPositiveRoot(1.0, -2.0, 1.0));
        blackhole.consume(Roots.INSTANCE.hasPositiveRoot(2.0, -2.0, -10.0));
        blackhole.consume(Roots.INSTANCE.hasPositiveRoot(-2.0,-2.0, +10.0));
        blackhole.consume(Roots.INSTANCE.hasPositiveRoot(2.0,+16.0, 10.0));

    }

    @Benchmark
    public void root2(Blackhole blackhole) {
        blackhole.consume(Roots2.INSTANCE.hasPositiveRoot(1.0, -2.0, 1.0));
        blackhole.consume(Roots2.INSTANCE.hasPositiveRoot(2.0, -2.0, -10.0));
        blackhole.consume(Roots2.INSTANCE.hasPositiveRoot(-2.0,-2.0, +10.0));
        blackhole.consume(Roots2.INSTANCE.hasPositiveRoot(2.0,+16.0, 10.0));
    }

    @Benchmark
    public void root3(Blackhole blackhole) {
        blackhole.consume(Roots3.INSTANCE.hasPositiveRoot(1.0, -2.0, 1.0));
        blackhole.consume(Roots3.INSTANCE.hasPositiveRoot(2.0, -2.0, -10.0));
        blackhole.consume(Roots3.INSTANCE.hasPositiveRoot(-2.0,-2.0, +10.0));
        blackhole.consume(Roots3.INSTANCE.hasPositiveRoot(2.0,+16.0, 10.0));
    }

Wynik - pod java 8

openjdk version "1.8.0_265"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_265-b01)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.265-b01, mixed mode)

# Run complete. Total time: 00:04:17 

Benchmark        Mode  Cnt        Score       Error   Units
Benchmark        Mode  Cnt        Score       Error   Units
RootTest.root1  thrpt   10  2213477.178 ± 62301.769  ops/ms
RootTest.root2  thrpt   10  2247239.569 ± 21727.944  ops/ms
RootTest.root3  thrpt   10  1954872.265 ± 36531.114  ops/ms

Test zrobiony na brudno - małe ilości iteracji itp. nie chciało mi się czekać.
Jak widać mierzalny spadek wydajności jest... dla wersji 3.
Wersja 2 zaliczyła nawet wzrost wydajności, ale to raczej wynik przeprowadzania testu na nieczystym komputerze. (ale 4 minuty czasu powinny większość uśrednić).
!!! Nie testowałem (w jmh) przypadku z wyjątkiem (to oczywiście może mieć wpływ na wyniki... ale dyskusja o performance i wrzucanie w kod wyjątku to i tak jest WTF.

Sam bym pewnie wybrał wersję Roots3... ale w tym przypadku i funkcja wyjściowa mnie nie razi - nie widzę istotnych różnic o które kruszyłbym kopie.
(razi wyjątek, ale takie było dziwne założenie). Zrobiłem funkcje jako wyrażenia (nie bardzo umiem pisać inaczej), ale to nie ma wpływu - oryginał też się da przerobić.
EDIT:
Bonusik inspirowany @stivens
i benchmark na graal

object Roots4 {
    fun hasPositiveRoot(a: Double, b: Double, c: Double): Boolean =
        calcRoots(a, b, c).let{roots->
            if (roots.isEmpty()) {
                throw Exception("Polynomial has no real roots")
            } else {
                roots.any { it >0}
            }
        }

    private fun calcRoots(a: Double, b: Double, c: Double): List<Double> =
        calcDelta(a, b, c).let { delta ->
            when {
                delta < 0 -> emptyList()
                else -> listOf(-b + Math.sqrt(delta) / (2 * a), -b - Math.sqrt(delta) / (2 * a))
            }
        }

    private fun calcDelta(a: Double, b: Double, c: Double) = b * b - 4 * a * c
}

openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment GraalVM CE 20.0.0 (build 11.0.6+9-jvmci-20.0-b02)
OpenJDK 64-Bit Server VM GraalVM CE 20.0.0 (build 11.0.6+9-jvmci-20.0-b02, mixed mode, sharing)


RootTest.root1  thrpt   10  2226808.045 ± 31160.479  ops/ms
RootTest.root2  thrpt   10  2245523.806 ± 11443.862  ops/ms
RootTest.root3  thrpt   10  2252279.079 ± 12245.590  ops/ms
RootTest.root4  thrpt   10  1178294.124 ± 95669.136  ops/ms
//tu zostawiłem komp w spokoju - przez co pierwsze 3 rozwiązania wyszły takie same (różnice w granicach błędu). 
//Co jest mniej więcej oczekiwane dla współczesnego kompilatora.
// 4 z listą też powinno wyjść podobnie, ale ewidentnie gdzieś się tam jvm gubi (może dam radę sprawdzić).

EDIT2: Oczywiście zrypałem
A w zasadzie zapomniałem dodać:

kotlinOptions {
        jvmTarget = "1.8"
}

a wtedy mamy takie wyniki:

RootTest.root1   thrpt   10  105464536.758 ± 744275.505  ops/ms
RootTest.root2   thrpt   10  105784580.750 ± 490385.882  ops/ms
RootTest.root3   thrpt   10  105058999.688 ± 549347.307  ops/ms
RootTest.root4   thrpt   10    1823427.831 ± 290128.766  ops/ms
RootTest.root4v  thrpt   10    2900448.685 ± 129014.900  ops/ms

Ktoś mógłby spytać, a co to root4v? - a to to samo co root4. Tylko, że zamiast kotlinowej listy, jest Lista z Vavr... która w środku jest LinkedListą i jak każdy wie jest wolniejsza...
Tym niemniej, mimo że teraz lista VAVR jest szybsza (na tym samym sprzęcie) niż oryginalne funkcje kompilowane pod java 6. To te funkcje dostały ultra kopa.
(Mam nadzieję, że to nie oznacza, że jmh się wywalił :-), poprawka po przejściu na java8 jest trochę za dobra)

EDIT 3:
@stivens Bingo! - twoja sugestia z komentarza, żeby użyć DoubleArray -wyunik jak na Pair (!!!)

RootTest.root4   thrpt   10  102794229.462 ± 6409465.868  ops/ms
RootTest.root4v  thrpt   10    2951965.456 ±   96099.156  ops/ms

(ale tak po prawdzie cały czas mam wątpliwości, że coś się zepsuło (blackhole nie działa?)

1

A jeszcze taka uwaga: jesli nie ma pierwiastkow rzeczywistych to w szczegolnosci nie ma dodatniego pierwiastka rzeczywistego i zamiast wyjatku nawet nie jakies monady a zwyczajnie "false":)

* oczywiscie to do oryginalnego pomyslodawcy
EDIT: Dobra - doczytalem. Ten wyjatek jest tutaj chyba dla samego wyjatku i pokazania czegos. To juz nic w takim razie.

4

@jarekr000000: Ech, brak mi słów i nawet nie wiem, czy Ty tak na serio, czy mnie trolujesz. Próbowałem zrozumieć argumentację i tok myślenia, w zamian za to dostałem trochę kodu i Czytelność do osądu. Dla mnie raczej EoT.

jarekr000000 napisał(a):
  1. Przeróbmy na kotlin (wolę to niż pseudojęzyk w którym wszystko może lub nie może działać w zależności kto pyta)

Zmieniłeś język i pominąłeś pytania z oryginalnego wpisu, co nie służy dyskusji.

jarekr000000 napisał(a):
  1. A co do dlaczego ignorujemy spadek wydajności...
    (...)
    !!! Nie testowałem (w jmh) przypadku z wyjątkiem (to oczywiście może mieć wpływ na wyniki... ale dyskusja o performance i wrzucanie w kod wyjątku to i tak jest WTF.

No cóż, to jak ignorujemy normalne elementy języka, to dyskusja nie bardzo ma sens.

2

Ponieważ Afish już ze mną nie dyskutuje to małe wyjaśnienie jak unikać tricków erystycznych - finezyjnych (i moim zdaniem nie zamierzonych -ot doświadczony bywalec internetu nabywa tego przez osmozę - ja również).
Pomoga to ogarnąć dyskusję z architektami.

a) nie daj się wciągnąć na dyskusję nad *hipotetycznym * językiem czy (dużo częściej) hiopotetycznym systemem. Wymagania i zasady takiego systemu są oczywiście ustalane ad hoc w ciągu przebiegu dyskusji - na ogół hipotetyczny system z wymaganiami wyciąganymi z kapelusza służy do wciskania korpo frameworka.

Tutaj w hipotetyczny język mógł akurat nie wspierać żadnych z normalnie wspieranych optymalizacji lub konstrukcji (w każdym z tym języków z osobna). Wybrałem więc kotlin, złośliwie mógłbym powiedzieć, że to wręcz idealny miks javo/siszarpo/javascripto-podobnej notacji (bo to akurat prawda - podobnie jak dla TS). Ale prawda jest taka, że za kilka dni mam prezentację na temat wydajności, gdzie omawiam też kotlina i chciałem zrobić coś co mogę potencjalnie sam wykorzystać.

b) nie daj się wciągać w narzucanie alternatyw, tu akurat nie były specjalnie złośliwe, ale opowiadanie się za przykładami, których sam bym nie napisał uznałem za godne zignorowania. (Dla mnie to jak odpowiadanie na pytanie: czy to prawda, że nie bije pan już żony?).

I konkretna rada na temat architektury:
jeśli kiedykolwiek okaże się, że wyjątki wpływają na wydajność waszego kodu (czyli są rzucane przynajmniej kilkadziesiąt razy na sekundę w jakimś fragmencie) - to zrobiliście coś bardzo źle. Trzeba przemyśleć co to znaczy wyjątek. Podany przykład idealnie wręcz ilustruje jak nie używać wyjątków. (ale to na 100% efekt szukania przykładu na szybko - sam nie miałbym pomysłu...).

8
jarekr000000 napisał(a):

Ponieważ Afish już ze mną nie dyskutuje to małe wyjaśnienie jak unikać tricków erystycznych - finezyjnych (i moim zdaniem nie zamierzonych -ot doświadczony bywalec internetu nabywa tego przez osmozę - ja również)( ... )

Nie da się jednak zaprzeczyć, że podany przez @Afish przykład kodu nr 1 / 7 jest zdecydowanie najczytelniejszy a usiłowanie udowodnienia, że jest inaczej jest zwyczajnie śmieszne.

3
jarekr000000 napisał(a):

a) nie daj się wciągnąć na dyskusję nad *hipotetycznym * językiem czy (dużo częściej) hiopotetycznym systemem. Wymagania i zasady takiego systemu są oczywiście ustalane ad hoc w ciągu przebiegu dyskusji - na ogół hipotetyczny system z wymaganiami wyciąganymi z kapelusza służy do wciskania korpo frameworka.

O matko, ale rozkmina. A wystarczyło napisać, który kod jest czytelniejszy, a który nie.

jarekr000000 napisał(a):

b) nie daj się wciągać w narzucanie alternatyw, tu akurat nie były specjalnie złośliwe, ale opowiadanie się za przykładami, których sam bym nie napisał uznałem za godne zignorowania. (Dla mnie to jak odpowiadanie na pytanie: czy to prawda, że nie bije pan już żony?).

Ponownie, celem jest uzasadnienie, dlaczego potrzebujesz innej alternatywy. Ty odpisałeś Czytelność do osądu, to właśnie proszę, żebyś osądził. Ty wolisz unikać określenia się.

jarekr000000 napisał(a):

jeśli kiedykolwiek okaże się, że wyjątki wpływają na wydajność waszego kodu (czyli są rzucane przynajmniej kilkadziesiąt razy na sekundę w jakimś fragmencie) - to zrobiliście coś bardzo źle.

A ja radzę ogarnąć, że nawet dodanie try+finally do metody ciągle powoduje spadek wydajności w różnych platformach. Nie trzeba rzucać wyjątku, nie trzeba nawet łapać, wystarczy dać try finally i już JIT inaczej ogarnia rejestry albo nie może inline'ować. I pisałem już o tym kilka razy w tej dyskusji (też się nie odniosłeś).

1

Skoro już mam wybrać to ze swoich wybieram tą opcję:

fun hasPositiveRoot(a: Double, b: Double, c: Double): Boolean =
        calcRoots(a, b, c).let{roots->
            if (roots.isEmpty()) {
                throw Exception("Polynomial has no real roots")
            } else {
                roots.any { it >0 }
            }
        }

https://github.com/jarekratajski/benchmarkiPochwarki/blob/main/src/main/kotlin/test/Roots4.kt

A co do wydajności - to pokazałem benchmarki - jak widać nie ma różnicy. Tzn. rozbijanie na funkcje nic nie zmienia (zmieniło wprowadzenie List). Poza tym nie byłbym pewny czy try finally (na jvm), które nie skutkuje rzuceniem wyjątku ma faktycznie wpływ. Do zbadania (ale z tego co pamiętam wpływ mierzalny jest 0).

2

To dla rozluźnienia napięcia wkleję kod programu mojego ojca, który nigdy nie był programistą ale czasem lubi coś zaprogramować.


SCREEN 12: CLS: %H=1: RANDOMIZE 0: REM RANDOMIZE TIMER

%N=1*64: %K=07: %L=49: %OGR=1: %A0=%N\32: %NN=%N-%A0
M=0.05: SMAX%=1: SMIN%=50: MAX%=1: MIN%=50
REM	*	*	*	*	*	*	*	*	*
DATA	1,1,1,1,1,1,0,0,0,0,   0,0,0,0,0,0,0,1,0,0,   0,0,1,0,0,0,0,0,1,0
DATA	0,1,0,0,0,1,1,1,0,0,   0,1,0,0,0,0,1,0,0,1
REM	*	*	*	*	*	*	*	*	*

	OPEN "DANE_1" FOR BINARY AS #1
REM	OPEN "DANE_2" FOR BINARY AS #2
	OPEN "TAB_4950" FOR BINARY AS #3
	OPEN "TAB_4800" FOR BINARY AS #4
	OPEN "TAB_KL" FOR BINARY AS #5
REM	OPEN "TAB_DL" FOR BINARY AS #6

DIM STATIC NREK%(4800): DIM STATIC M1%(4800): DIM STATIC M2%(4800)
DIM STATIC NWR%(4800) : DIM STATIC D1%(4800): DIM STATIC D2%(4800)
DIM STATIC K0%(50)  : DIM STATIC K1%(50)  : DIM STATIC K2%(50)
X%=9899: DIM DYNAMIC D%(X%) :Y%=4950: DIM DYNAMIC D0%(Y%)

DIM STATIC KL%(%K:%L,%K:%L) : DIM STATIC LK%(%K:%L)
DIM STATIC  A%(%N,%K:%L)  : DIM STATIC C%(50): REM DIM STATIC  B%(%N,%K:%L)
DIM STATIC AA%(%NN,%K:%L) : REM DIM STATIC BB%(%NN,%K:%L)
DIM STATIC  W%(%N) : DIM STATIC  W(%N): REM DIM STATIC DL%(50)

REM	#################  D  A  N  E  ########################################
FOR C%=1 TO 4950: GET$ #3,1,A$: GET$ #3,1,B$: A%=ASC(A$): B%=ASC(B$)
	D0%(C%)=100*A%+B%
NEXT C%: CLOSE #3

FOR C%=1 TO 4800: GET$ #4,1,A$: GET$ #4,1,B$: A%=ASC(A$): B%=ASC(B$)
	NREK%(C%)=100*A%+B%: M1%(C%)=A%: M2%(C%)=B%
NEXT C%: CLOSE #4

K2%(0)=1: FOR C%=1 TO 50: GET$ #5,1,A$: GET$ #5,1,B$: A%=ASC(A$): B%=ASC(B$)
	K0%(C%)=K0%(C%-1)+K2%(C%-1): K1%(C%)=A%: K2%(C%)=A%+B%
NEXT C%: CLOSE #5

	FOR A%=1 TO 4950: C%=D0%(A%): D%(C%)=A%: NEXT A%
	FOR A%=1 TO 4800: C%=NREK%(A%): B%=D%(C%)
	NWR%(A%)=B%: D1%(A%)=B%\8: D2%(A%)=B% MOD 8
	NEXT A%: ERASE D%: ERASE D0%

ODC%=0: FOR C%=1 TO 50: READ A%: C%(C%)=A%: IF A%=0 THEN ODC%=ODC%+1
NEXT C%

REM	FOR C%=1 TO 50: GET$ #6,1,A$: A%=ASC(A$): DL%(C%)=A%: NEXT C%: CLOSE #6

REM	################# P R Z Y G O T O W A N I E ###########################
	FOR N%=1 TO %N
555		FOR A%=%K TO %L: IF C%(A%)=1 THEN GOTO 401
	B%=K0%(A%)+INT(RND*K1%(A%))
		A%(N%,A%)=B%
401	NEXT A%
GOSUB 333: LOCATE 23,1: ?N%: IF W%<%OGR THEN GOTO 555
	W%(N%)=W%: W%(0)=W%(0)+W%: W(N%)=W(N%-1)+W%: REM *W%
	NEXT N%

	LINE (320,476)-(635,476),7
	LINE (320,250)-(320,476),7
	LINE (320,266)-(635,266),14

	FOR A%=476 TO 266 STEP -30: CIRCLE (320,A%),1,7: NEXT A%
	FOR A%=296 TO 386 STEP 30: LINE (320,A%)-(635,A%),17: NEXT A%
	RMAX%=MAX%: RMIN%=MIN%

	CIRCLE (320+SS%*%H,476-6*RMAX%),1,15
	CIRCLE (320+SS%*%H,INT(476.5-6/N%*W%(0))),1,14
	CIRCLE (320+SS%*%H,476-6*RMIN%),1,11
INPUT, Q$:STOP
REM	#################  S  T  A  R  T  #####################################
	TP=TIMER
1111	SS%=SS%+1: MAX%=1: MIN%=50: IF (SS%*%H)=318 THEN SS%=1

	MM=M-(RMAX%>19)*ABS((4-RMAX%+W%(0)/%N)*M)
LOCATE 23,23: ?"PRAWDOP=";USING "###.###";MM
	
C%=-1:	FOR N%=1 TO (%N-%A0)\2: C%=C%+2
REM	################# S E L E K C J A #####################################
	A1=RND*W(%N): FOR A3%=%A0 TO %N STEP %A0: IF A1<=W(A3%) THEN GOTO 201
	      NEXT A3%
201	FOR A1%=A3%-%A0+1 TO A3%: IF A1<=W(A1%) THEN GOTO 101
		      NEXT A1%
101	A2=RND*W(%N): FOR A3%=%A0 TO %N STEP %A0: IF A2<=W(A3%) THEN GOTO 202
	      NEXT A3%
202	FOR A2%=A3%-%A0+1 TO A3%: IF A2<=W(A2%) THEN GOTO 102
		      NEXT A2%
102	IF A1%=A2% THEN GOTO 101

REM	################# K R Z Y Z O W A N I E ###############################
	MK=RND: FOR B%=%K TO %L: IF C%(B%)=1 THEN GOTO 402
		IF RND<MK THEN AA%(C%,B%)=A%(A1%,B%): BB%(C%,B%)=B%(A1%,B%):_
		AA%(C%+1,B%)=A%(A2%,B%): BB%(C%+1,B%)=B%(A2%,B%) ELSE _
				AA%(C%,B%)=A%(A2%,B%): BB%(C%,B%)=B%(A2%,B%):_
		AA%(C%+1,B%)=A%(A1%,B%): BB%(C%+1,B%)=B%(A1%,B%)
402	NEXT B%

REM	#################  M  U  T  A  C  J  E  ###############################

709	IF RND>MM THEN GOTO 710
403	M%=%K+INT(RND*(%L-%K+1)): IF C%(M%)=1 THEN GOTO 403
	B%=K0%(M%)+INT(RND*K1%(M%))
		D%=D%(M0%(B%)): AA%(C%,M%)=D%: BB%(C%,M%)=B%: GOTO 709
710	IF RND<1-MM THEN GOTO 711
404	M%=%K+INT(RND*(%L-%K+1)): IF C%(M%)=1 THEN GOTO 404
	B%=K0%(M%)+INT(RND*K2%(M%))
		D%=D%(M0%(B%)): AA%(C%+1,M%)=D%: BB%(C%+1,M%)=B%: GOTO 710
711	NEXT N%

FOR A%=1 TO %N-%A0: FOR B%=%K TO %L: A%(A%,B%)=AA%(A%,B%): NEXT B%: NEXT A%
FOR A%=1 TO %N-%A0: FOR B%=%K TO %L: B%(A%,B%)=BB%(A%,B%): NEXT B%: NEXT A%

	FOR N%=%N-%A0+1 TO %N
	FOR A%=%K TO %L: IF C%(A%)=1 THEN GOTO 405
	B%=K0%(A%)+INT(RND*K1%(A%))
		C%=M0%(B%): D%=D%(C%): A%(N%,A%)=D%: B%(N%,A%)=B%
405	NEXT A%: NEXT N%

	TTM%=0: TTW%=0
W%(0)=0: FOR N%=1 TO %N
	GOSUB 333: W%(N%)=W%: W%(0)=W%(0)+W%: W(N%)=W(N%-1)+W%*W%
IF (N% MOD 50)=0 THEN LOCATE 23,1: ?"AKTUALNIE: ";USING "###";N%;MAX%;MIN%
	IF W%=SMAX% THEN TTM%=TTM%+1
	IF W%=SMAX%-1 THEN TTW%=TTW%+1
NEXT N%
        IF MIN%<SMIN% THEN SMIN%=MIN%
        IF MAX%>SMAX% THEN SMAX%=MAX%
LOCATE 21,1: ?"MAX =";USING "##";SMAX%;: ?USING "###";MAX%;
	?"  MIN =";USING "##";SMIN%;: ?USING "###";MIN%;
	?"  Fcelu=";USING "#######";W(%N)
LOCATE 22,1: ?"MAX%=";USING "##.##";TTM%/%N*100;
	?" WMAX%=";USING "##.##";TTW%/%N*100;
TK=TIMER: ?"  TIMER=";USING "###.####";(TK-TP)/60

	LINE (320+SS%*%H,476-6*MAX%)-(320+(SS%-1)*%H,476-6*RMAX%),15
	PSET (320+SS%*%H,INT(476.5-6/N%*W%(0))),14
	LINE (320+SS%*%H,476-6*MIN%)-(320+(SS%-1)*%H,476-6*RMIN%),11
	RMAX%=MAX%: RMIN%=MIN%

IF SS%=10 THEN STOP

GOTO 1111

REM	#######################################################################

LOCATE 22,25: ?"! KONIEC !": INPUT, Q$: CLOSE: STOP

REM	################ P O D P R O G R A M Y ################################

333	ERASE KL%: ERASE LK%
	FOR D1%=%K TO %L-1: IF C%(D1%)=1 THEN GOTO 406
		N&=CLNG(A%(N%,D1%)-1)*619
	FOR D2%=D1%+1 TO %L: IF C%(D2%)=1 THEN GOTO 407
	D3%=NWR%(A%(N%,D2%)): D4%=D3%\8: D5%=D3% MOD 8
SEEK #1,N&+D4%: GET$ #1,1,A$: D6%=ASC(A$)

F%=0:		IF D5%>3 THEN GOTO 1010
		IF D5%=0 AND ((D6% AND   1)>0) THEN F%=1: GOTO 1100
		IF D5%=1 AND ((D6% AND   2)>0) THEN F%=1: GOTO 1100
		IF D5%=2 AND ((D6% AND   4)>0) THEN F%=1: GOTO 1100
		IF D5%=3 AND ((D6% AND   8)>0) THEN F%=1: GOTO 1100
1010		IF D5%=4 AND ((D6% AND  16)>0) THEN F%=1: GOTO 1100
		IF D5%=5 AND ((D6% AND  32)>0) THEN F%=1: GOTO 1100
		IF D5%=6 AND ((D6% AND  64)>0) THEN F%=1: GOTO 1100
		IF D5%=7 AND ((D6% AND 128)>0) THEN F%=1

1100	IF F%>0 THEN KL%(D1%,D2%)=1: KL%(D2%,D1%)=1: _
		LK%(D1%)=LK%(D1%)+1: LK%(D2%)=LK%(D2%)+1
407	NEXT D2%
406	NEXT D1%

195	W%=0
196	D7%=64: FOR D8%=%K TO %L: IF C%(D8%)=1 THEN LK%(D8%)=222: GOTO 408
	IF LK%(D8%)=0 THEN W%=W%+1: LK%(D8%)=66
	IF LK%(D8%)<D7% THEN D7%=LK%(D8%): D9%=D8%
408	NEXT D8%: IF D7%=64 THEN GOTO 199

	FOR D8%=%K TO %L: IF KL%(D8%,D9%)=0 THEN GOTO 198	
	FOR D7%=%K TO %L: IF KL%(D8%,D7%)=0 THEN GOTO 197	
	KL%(D8%,D7%)=0: LK%(D7%)=LK%(D7%)-1
197	NEXT D7%: LK%(D8%)=99
198	NEXT D8%: GOTO 196


199	IF W%>MAX% THEN MAX%=W%
	IF W%<MIN% AND N%<(%N-%A0) THEN MIN%=W%
	IF W%>=SMAX% THEN SMAX%=W%: GOSUB 222
RETURN

222	LOCATE 1,1: FOR A%=01 TO 11: ?_
"                                                                              "
NEXT A%
	FOR A%=12 TO 20: ?"                                       ": NEXT A%

COLOR 8: FOR A%=9 TO 0 STEP -1: FOR B%=0 TO 9
		LOCATE  2*(9-A%)+1,1+4*B%: ? USING "#";A%;B%
	NEXT B%: NEXT A%

	FOR A%=1 TO 10: FOR B%=1 TO 10
        	CIRCLE (32*A%-25,32*B%-16),1,14
        NEXT B%: NEXT A%: COLOR 7

CC%=-1:	FOR A%=%K TO %L: IF LK%(A%)>66 THEN GOTO 190
CC%=CC%+1: B%=A%(N%,A%)
	MY%=M1%(B%)\10: MX%=M1%(B%) MOD 10
	NY%=M2%(B%)\10: NX%=M2%(B%) MOD 10
	DL%=(MX%-NX%)*(MX%-NX%)+(MY%-NY%)*(MY%-NY%)

	XM%=32*MX%+7: YM%=32*(10-MY%)-16: CIRCLE (XM%,YM%),2,15
	XN%=32*NX%+7: YN%=32*(10-NY%)-16: CIRCLE (XN%,YN%),2,15
	LINE (XM%,YM%)-(XN%,YN%),7

	C1%=CC%\4: C2%=CC% MOD 4
LOCATE C1%+1,40+C2%*10: ?USING "###";DL%;M1%(B%);M2%(B%)

190	NEXT A%

CC%=4*(C1%+2)-1: FOR A%=%K TO %L:
	IF (LK%(A%)<=66) OR LK%(A%)=222 THEN GOTO 191
CC%=CC%+1: B%=A%(N%,A%)
	MY%=M1%(B%)\10: MX%=M1%(B%) MOD 10
	NY%=M2%(B%)\10: NX%=M2%(B%) MOD 10
	DX%=ABS(MX%-NX%): DY%=ABS(MY%-NY%): DL%=DX%*DX%+DY%*DY%

	C1%=CC%\4: C2%=CC% MOD 4
LOCATE C1%+1,40+C2%*10
IF DX%>DY% THEN ?USING "###";DL%;: ?" - ";USING "#";DY%;: ?"*";USING "#";DX%: _
	   ELSE ?USING "###";DL%;: ?" - ";USING "#";DX%;: ?"*";USING "#";DY%
191	NEXT A%

RETURN
0

@katakrowa: jest czytelniejszy bo to jest równanie kwadratowe (prawie) każdy pamięta.
To jest przykład pod tezę i podejrzewam że @Afish o tym wie.
Czasami bywają sytuacje w których jednolinijkowe funkcje mają sens, czasami nie ma.
A co wyjątku, wyrzucanie wyjątku gdzie wyjątku nie ma jest o wiele kosztowniejsze niż funkcje jednolinijkowe. Od tego jest np. Option albo Either.

1

Co do wyjątków i ich kosztu to jest ciekawy off topic.

Co prawda, uważam, że rozwiązanie jest proste: wyjątków w kodzie nie powinno być, o ile nie są wyjątkowe. Więc w kontekście wydajności trochę średnio... ALE
Czasami korzystamy z cudzego kodu, który używa albo deklaruje wyjątki.. no i zachodzi pytanie jaki to ma wpływ?
Z mojego doświadczenia to nowoczesne JVM ładnie eliminują wpływ wszystkich tych nierzucanych wyjątków, ale oczywiście wszystkiego nie widziałem.
Dla rozpoczęcia dyskusji daję taki przykład:

object TryCost {

    fun trivialSum(a: Long, b: Long) =
        (a..b).sum().let {
            if ( it > 200000000) {
                -1
            } else {
                it
            }
        }

    fun tryCatchSum(a: Long, b: Long) =
        (a..b).sum().let {
            try {
                if ( it > 200000000) {
                    throw Exception("should not happen")
                } else {
                    it
                }
            } finally{

            }

        }
}

A tu test - tak ułożony, żeby wyjątek nie leciał.
https://github.com/jarekratajski/benchmarkiPochwarki/blob/main/src/jmh/java/jmh/TryTest.java

U mnie różnic w czasach nie ma. Ale pewnie (prawie na pewno) da się ten przykład tak przerobić, że kompilator w jvm się pogubi. Pytanie.. jak w najprostszy sposób to zrobić: czyli deklarujemy wyjątek, który nie leci, ale pogrążamy wydajność (względem tożsamego kodu, który tego wyjątku nie deklaruje).

1

Czytelność to w dużej mierze przyzwyczajenie. Jak ktoś całe życie pisał obiektowo to kazdy kod w FP wyda mu się dziwny i odwrotnie

1

w dużej mierze przyzwyczajenie.

Fajnie by mieć jakies obiektywne metryki, które choćby dawały wskazówkę- niestety, z tym jest problem. Puty programowałem imperatywnie to mocno polegałem na cyclomatic complexity.
To jest dość fajna, obiektywna metryka - co prawda kod o CC 1 nie musi być dobry ani czytelny. Tak samo kod o CC 7 nie musi być strasznie zły.
Ale kod o CC 11 to już raczej na pewno dobry nie był. Można było wstawić w build i w sonara jakieś "dzwonki alarmowe" i zwykle wielkich kłótni na ten temat nie było.

Niestety, w kodzie funkcyjnym (nawet troche) CC zmierza do jedynki. Co nie oznacza, że kod jest prosty.
Widziałem kilka ciekawych metryk bazujących na obciążeniu poznawczym, jest w cholerę "papierów" w tym temacie.
ale koniec końców żadnego gotowego narzędzia do np. kotlina czy scali nie znalazłem (choćby jako plugin do IDE) - w końcu temat olałem.

1
scibi92 napisał(a):

Czasami bywają sytuacje w których jednolinijkowe funkcje mają sens, czasami nie ma.

No to pokaż konkret, gdzie ma sens, a gdzie nie ma.

jarekr000000 napisał(a):

Skoro już mam wybrać to ze swoich wybieram tą opcję:

Dlaczego? I dlaczego odrzucasz pozostałe?

jarekr000000 napisał(a):

Z mojego doświadczenia to nowoczesne JVM ładnie eliminują wpływ wszystkich tych nierzucanych wyjątków, ale oczywiście wszystkiego nie widziałem.

Dlatego mówię o platformach ogólnie.
https://github.com/davidmarkclements/v8-perf
https://blog.adamfurmanek.pl/2020/07/18/net-inside-out-part-20/

Do innych platform zapewne też da się skonstruować przykłady, gdzie try bez wyjątku spowodował spowolnienie. I ciągle podtrzymuję pytanie: czy jeżeli jest różnica w wydajności wydzielonego małego kawałka kodu, to czy wydzielamy, czy nie? I czym się kierujemy przy podjęciu decyzji?

0

@Afish wyrzucę później bo ciężko napisać na komórce.
Nie mniej bawi mi mnie to że piszesz o wydajności a s kodzie rzucasz wyjątkiem w sytuacji niewyjatkowej.

1

@Afish:

Dlaczego? I dlaczego odrzucasz pozostałe?

Nie odrzucam. Ten wariant mi się podoba najbardziej.

czy jeżeli jest różnica w wydajności wydzielonego małego kawałka kodu, to czy wydzielamy, czy nie? I czym się kierujemy przy podjęciu decyzji?

Oczywiście, że wydzielamy jeśli widać poprawę czytelności kodu (co jak widzimy wyżej może być dyskusyjne).

Wydajnością przejmujemy się i ewentualnie psujemy kod kiedy jest taka potrzeba - czyli:
Kod jest w sekcji mającej istotny wpływ na wydajność systemu, który to wpływ jest mierzony powtarzalnymi benchmarkami. Wówczas może być potrzeba napisania nawet całkiem brzydko wyglądającego kodu (warto patrzeć na wpływ procentowy).

W praktyce ludzi, którzy powołują się na wydajność spotykam często, ale ludzi którzy tą wydajność faktycznie mierzą i to w jeszcze jakiś choćby minimalnie sensowny sposób - prawie wcale.

0

W sumie to jak ktos, tutaj na forum, ma z czyms problem i wstawi kod gdzie jedna metoda ma 200+ linijek to mi sie nawet czytac tego nie chce. Bo przeciez mi za to nie placa tutaj.

Ale jak rozumiem w tym momencie dyskutujecie juz tylko o przypadku "jest sobie 15 linijek, rozbijamy czy nie?"? Pytam bo w poscie otwierajacym dyskusje bylo dosyc ogolnie.

0
jarekr000000 napisał(a):

Oczywiście, że wydzielamy jeśli widać poprawę czytelności kodu (co jak widzimy wyżej może być dyskusyjne).

No i powoli docieramy do sedna. To po czym poznajesz "poprawę czytelności kodu"?

jarekr000000 napisał(a):

Kod jest w sekcji mającej istotny wpływ na wydajność systemu, który to wpływ jest mierzony powtarzalnymi benchmarkami. Wówczas może być potrzeba napisania nawet całkiem brzydko wyglądającego kodu (warto patrzeć na wpływ procentowy).

Okej, czyli rozumiem, że w ogólności nie przejmujemy się wydajnością? Które z tych rzeczy są okej z punktu wydajności:

  • zrobienie algorytmu o wyższej złożoności, jeżeli kod z niższą złożonością dałoby się "łatwo" uzyskać?
  • łączenie niezmiennych stringów przez jakiś + zamiast jakiegoś bufora?
  • alokowanie jakichś kolekcji po drodze zamiast używania widoków na nich (IEnumerable, Stream czy jak to sobie nazwiemy)?
  • kopiowanie całych wartości zamiast przekazywania przez referencję?
  • alokowanie maszyn stanowych (dla generatorów, korutyn, CPS)?
  • alokowanie domknięć (przykładowo zrobienie foreacha z lambdą zamiast normalnej pętli)?
  • tworzenie "niepotrzebnych" obiektów (na przykład Random inicjalizowany zbyt często, nowe wątki zamiast puli itp)?
  • wyliczenie jakieś niezmiennej wartości wielokrotnie (na przykład wywołanie gettera, jeżeli jest to "zwykły getter")?
  • wyliczenie warunków końcowych pętli przy każdym obrocie (jeżeli się nie zmieniają)?

To tak na szybko, przykładów można dorzucić wiele innych. Szukam jakiegoś "ogólnego wskazania", co jest okej, a co jednak lepiej "naprawić" od razu.

0

https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/volume/persistentvolume/pv_controller.go#L58 . Trudno mi powiedzieć, czy to dobre podejście dla tego problemu, ale na pewno jest to jakiś argument do dyskusji

1

W ogóle to nie rozumiem takiego skupienia na wydajności. Optymalizować powinno się jak jest co, a i tak najwięcej czasu idzie na połączeniach z bazą danych.
Czytelność jest ważniejsza niż wydajność w 90%

2

@Afish:

Okej, czyli rozumiem, że w ogólności nie przejmujemy się wydajnością? Które z tych rzeczy są okej z punktu wydajności:

Jeśli zadajesz to pytanie to nie rozumiesz co napisałem wyżej, albo nie czytasz. Napisałem już, że o wydajność walczy się tam gdzie ta wydajność ma wpływ na działanie systemu.
Czyli dotyczy to kilku kodu - procent oczywiście zależy od systemu. W małym batchu każda linijka wpływa na wydajność, w dużym erp pewnie 1% kodu.
W punktach, które przytoczyłeś są dość smaczne kawałki do dyskusji, ale zostawiam to innym.

No i powoli docieramy do sedna. To po czym poznajesz "poprawę czytelności kodu"?

To jest kwestia gustu - chętnie miałbym tu obiektywną miarę, ale obecnie nie mam dobrej, którą bym potwierdził stosowaniem w praktyce.

3

@stivens:

Ale jak rozumiem w tym momencie dyskutujecie juz tylko o przypadku "jest sobie 15 linijek, rozbijamy czy nie?"? Pytam bo w poscie otwierajacym dyskusje bylo dosyc ogolnie.

To zależy od tych 15 linijek.
15 linijek, w których jest jakaś konfiguracja systemu ({blaUrl ="http://...." [...] maxUsers = 1;}) można nie ruszać. Choć zwykle da sie to rozbić na logiczne "rozdziały".
5 linijek w których są dwie pętle for i if raczej bym nie zostawił.

Zamiast liczyć linijkami można policzyć - ile jest użytych funkcji/operacji, ile zadeklarowanych zmiennych, ile gałęzi (if/switch) i na podstawie tego wybrać jakieś granice.

0
jarekr000000 napisał(a):

@Afish:

Jeśli zadajesz to pytanie to nie rozumiesz co napisałem wyżej, albo nie czytasz. Napisałem już, że o wydajność walczy się tam gdzie ta wydajność ma wpływ na działanie systemu.
Czyli dotyczy to kilku kodu - procent oczywiście zależy od systemu. W małym batchu każda linijka wpływa na wydajność, w dużym erp pewnie 1% kodu.

Dopytuję o Twoje reguły. Zrzucenie widoku kolekcji do listy i potem zrobienie Contains kontra zrzucenie tego samego widoku do zbioru i zrobienie Contains będzie zmianą kilku literek w kodzie (ToSet zamiast ToList), ale jeżeli to nie ma wpływu na działanie systemu, to olewamy?

jarekr000000 napisał(a):

W punktach, które przytoczyłeś są dość smaczne kawałki do dyskusji, ale zostawiam to innym.

Wygodne.

jarekr000000 napisał(a):

To jest kwestia gustu - chętnie miałbym tu obiektywną miarę, ale obecnie nie mam dobrej, którą bym potwierdził stosowaniem w praktyce.

Okej. To jak wyjaśniłbyś juniorowi, który kod jest bardziej czytelny dla Ciebie?

4

@Afish:

Dopytuję o Twoje reguły. Zrzucenie widoku kolekcji do listy i potem zrobienie Contains kontra zrzucenie tego samego widoku do zbioru i zrobienie Contains będzie zmianą kilku literek w kodzie (ToSet zamiast ToList), ale jeżeli to nie ma wpływu na działanie systemu, to olewamy?

Nie mam tego problemu - mój widok kolekcji ma contains i do niczego nie muszę zrzucać. (ale przyznaje, że np. w java.util. takie dylemty są - mi się nie chce mieć takich problemów).
Tym niemniej nie widzę też specjalnego problemu ze zrzucaniem do czegokolwiek. Operując na listach i kolekcjach zadaję sobie pytanie - jakie są szanse, że ta kolekcja będzie mieć więcej niż 1000 elementów. Ponieważ prawie zawsze 0 to wiadomo - jest mi wszystko jedno.

Okej. To jak wyjaśniłbyś juniorowi, który kod jest bardziej czytelny dla Ciebie?

To jest celne pytanie. Jakbym znał na nie dobrą odpowiedź to pewnie bym był bogaty. W praktyce potrafię mętnie w mniej więcej 2 lata wytłumaczyć - dużo używając przy tym machania rękami. I nie wiem czy dobrze tłumaczę :-)

0
jarekr000000 napisał(a):

@Afish:

Ponieważ prawie zawsze 0 to wiadomo - jest mi wszystko jedno.

Okej.

jarekr000000 napisał(a):

To jest celne pytanie. Jakbym znał na nie dobrą odpowiedź to pewnie bym był bogaty. W praktyce potrafię mętnie w mniej więcej 2 lata wytłumaczyć - dużo używając przy tym machania rękami.

Okej.

1

Jeszcze co do ładności kodu.
Dawno temu miałem na to prostą odpowiedź:
https://en.wikipedia.org/wiki/Cyclomatic_complexity

Były narzędzia, można było dla każdej metody wyznaczyć CC i ustawić dopuszczalne granice. Trochę kłótni bywało, ale ogólnie udawało się konsensus ustalić. Dawno temu max to było 11, w ostatnich projektach co pamiętam to 6 bodajże. Taka miara nie pokaże co prawda czy nazewnictwo jest sensowne, ale jasno wskazuje kiedy funkcja jest skomplikowana.
Można to było zabetonować w buildzie, albo dawać ostrzeżenia na sonarze. Tak czy siak był zawsze jakiś obiektywny, posiłkowy argument przy review.
(Do tego mamy jeszcze mnóstwo innych metryk).

Niestety w kodzie funkcjyjnym, gdzie nie ma za bardzo pętli, ifów jest niewiele, a try catch to już w ogóle sie nie pojawia - cała ta metryka się wywala.
Kod ultra skomplikowany a formalnie CC = 1 :-) To znany problem i ludzie kombinują różne inne alternatywy.
Ta jest dość prosta:
https://en.wikipedia.org/wiki/Halstead_complexity_measures
nadaje się do prawie każdego jezyka... ale nigdy nie stosowałem. Nie mam pojęcia jak sie sprawdza (metryka się sprawdza, jeżeli można wybrać numerek który będzie dla prawie całego zespołu (poza architektem) oznaczał zły kod). Nie sprawdza się jak jest dużo bezowocnych dyskusji na temat samej metryki ( miałem takie na LOC).

6

mysle ze to kwestia doswiadczenia, zdrowego rozsadku i przede wszystkim sytuacji. religijne podejscie do tego typu rzeczy to strata czasu bo stworzenie jednoznacznych regul zawsze kogos skrzywdzi i spowoduje zazwyczaj bezplodne dyskusje.
przykladowo czasem funkcja w stylu

mul(a, b)  {
    return a * b;
}

poprawia czytelnosc bo mozesz sobie wywolac cos w stylu list.reduce(mul) zamiast pisac co chwile lambdy czy inne pokraki, innym razem, przy bardziej imperatywnym algorytmie pisanie int notional = mul(qty, px) to zwyczajne cudowanie.

ale odpowiadajac na pytanie OP - jesli funkcja jest latwa do zrozumienia i kod sie nie powtarza to nie widze powodu do dzielenia jej na mniejsze.
przy nowym kodzie zazwyczaj naturalnie wychodza mi male klasy i funkcje, a te duze sa bardzo proste (typu jakies zbiory constantow czy moduly ktory jedynie spinaja podsystemy).

1

religijne podejscie do tego typu rzeczy to strata czasu bo stworzenie jednoznacznych regul zawsze kogos skrzywdzi i spowoduje zazwyczaj bezplodne dyskusje.

W zasadzie brzmi sensownie.. tyle, że w moich projektach od zawsze było tak, że skrzywdzony to był architekt (nie zawsze niemiecki), który nie mógł wrzucić zupełnie przecież czytelnego kodu, z piętnastoma ifami, kilkoma pętlami i całym alfabetem na zmienne. Zupełnie czytelnego bo są komętarze i nie podlegającemu review, bo architektów nie ma potrzeby sprawdzać ;-)
W tym sensie - wredny check w CI to była nasza ostatnia linia obrony. Również do obrony przed zespołami z innych krajów, o innym poczuciu estetyki. Fakt, brałem udział w iluś głupich dyskusjach nad kodem, który nie przeszedł np. sonara, ale jeszcze smutniejsze były dyskusje gdzie nie można było pode(t)przeć się jakaś prostą religijna formułką (typu max CC = 7, maxLOC = 20). W przypadku posiadania takich twardych reguł czasami udawało się wyegzekwować dopasowanie kodu (a czasami wiadomo //CHECKSTYLE:off).

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