Kiedy gruboziarnisty kod jest lepszy?

0

Załóżmy że mamy jakąś klasę reprezentującą transakcje TransferDto która reprezentuje dane w jakimś kliencie restowym. Chcemy pobrać kolekcję takich dtosów, zweryfikować czy pole code ma jedną z wartości które reprezentuje kod operacji na karcie i zwrócić w metodzie getAccountLastCardTransfers List<TransferDto>(long limit);
IMO ładniejsze w Javie jest:

 .filter(this::isCardTransfer) 

private isCardTransfer(Transfer transfer) {
   return transfer -> CARD_CODES.contains(trasfer.getCode());
}

niż

 filter(transfer -> CARD_CODES.contains(trasfer.getCode());
0

szkoda że przykład był na tyle prosty, że nie ma za bardzo jak pokazać wersji z goto

W ogóle zastanawia mnie to - jak to jest, że ludzie mają jakieś nieuzasadnione wątpliwości jeżeli chodzi o użycie _współczesnego_ goto, ale z wyjątkami które już potrafią znacznie dalej "skoczyć" już nie?

2

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ć ;-)

mysle ze to jest ekstremalny przyklad, bo kod o ktorym piszesz jest obiektywnie zly a osoba ktora za nim lobbuje powinna miec bana na wykonywanie zawodu.
problem jest w tym ze nie ma dobrej rady na kogos kto ma "plecy". taka osoba kazda dobra, powszechnie uznana praktyke podwazy swoim "doswiadczeniem". bo biznesu zwykle nie obchodza zagniezdzenia w kodzie czy nawet czerwone testy, a koszt utrzymywania kupy rozmywa sie na kilku poziomach zarzadzania bo jest zwyczajnie slabo udowadnialny na kolorowych wykresach.

bardziej chodzi mi o zespoly w ktorych ludzie sa wzglednie kumaci i mamy zdania dwa zagniezdzenia ifow sa czasem ok vs no way, if to ogolnie code smell. i wychodzi z tego dyskusja na 350 komentarzy w firmowym githubie po ktorej tak naprawde ludzie sa jeszcze bardziej zdeterminowani do uzywania swojego stylu.

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).

jasne, zdarza sie ze narzucenie szczegolowego standardu ma pozytywny wplyw bo ucina duza czesc swiatopogladowych dyskusji zwlaszcza o opcjonalnych rzeczach ktore dla niektorych sa swiete i beda walczyc do ostatniej kropli krwi zeby je dodac (lub usunac).
z drugiej strony pracowalam z ludzmi ktorzy w swojej arogancji zawsze wymysla jakas teorie ktora do obowiazujacego standardu nie jest (jeszcze) wpisana i beda sie klocic zamiast stwierdzic "ok, ten kod jest wystarczajaco dobry" i isc dalej.
nie twierdze ze dyskusje sa zbedne ale czasem lepiej po prostu konsekwetnie stosowac swoj "lepszy" styl, merytorycznie komentowac fikolki innych i liczyc ze reszta zrozumie swoje bledy :)

0

@jarekr000000: Moim zdaniem tam na stronie 4 z tym przykładem co dałeś 3 kawałki i osądzić czytelność - dla mnie to trochę nieadekwatne podejście inżyniera. Nie jesteśmy humanistami, że możemy moim zdaniem zostawiać coś do interpretacji. Cały problem sprowadza się do ustalenia metryk / reguł jakości, według, którego ten kod można obiektywnie ocenić. Dziwi mnie też, że dla Ciebie opcja inna niż 1 jest czytelniejsza. Opcja 1 nie ma napaćkanych nawiasów, klamer, ma mało składni, a kod jest tak klasyczny z matematyki, że po rzuceniu okiem mniej więcej wiem o co chodzi i jestem w stanie określić co liczy. W kolejnych propozycjach już jest strasznie duży narzut abstrakcji dla mnie, co nawet zniechęca do czytania tego.

1

@MuadibAtrides:

Nie jesteśmy humanistami, że możemy moim zdaniem zostawiać coś do interpretacji. Cały problem sprowadza się do ustalenia metryk / reguł jakości, według, którego ten kod można obiektywnie ocenić.

Proszę, podaj wg jakich metryk oceniasz.

A potem podaj wg jakich kryteriów wybrałeś te metryki.

Przy okazji: Ja mam swoje metryki wg nich wszystkie rozwiązania ulegają dyskwalifikacji ze względu na użycie wyjątku. (Ale takie było założenie).

2

Cała dyskusja niestety utwierdza mnie w przekonaniu, że najpopularniejsze obecnie wytyczne dotyczące metod programowania i projektowania aplikacji powodują, że zdecydowanie zbyt dużo pary idzie w gwizdek a za mało w faktyczne tworzenie rozwiązania. Uważam, że mniejsza "spina" i poluzowanie reguł w wielu przypadkach zdecydowanie przyspieszyłoby realizację niejednego projektu bez znaczących uszczerbków na jego poprawności i czytelności.
Przecież jak ktoś guzik wie na temat projektu albo brakuje mu podstawowej wiedzy domenowej lub technicznej to nawet gdyby wszystkie funkcje w projekcie były jednolinijkowe to i tak nic mu to nie pomoże.

1

@pan_krewetek:
Jedyna sytuacja gdy programista może wstawić jakiegoś kloca i się obronić na code review to gdy ten kawał brązu robi jakieś krytyczne wydajnościowo przetwarzanie typu mnożenie macierzy lub advance w n-body, które nie jest dzielone między komputery a nawet rdzenie.

Warunkiem jest oczywiście to, że musi być to krytyczny element aplikacji, czyli jakiś jej 1% lub mniej kodu znalezione przy pomocy testów wydajnościowych.

W pozostałych przypadkach podział "gruboziarnisty" wg mnie jest przykładem lenistwa, złośliwości, strachu o stanowisko, nieumiejętności lub innej niedbałości.
I nie, w latach 80-tych też robiło się podział na funkcje, do tego służyły GOSUB w BASIC-u lub funkcje i procedury w C/Pascalu.

Przykład z Atari (1978-1982):
https://atariwiki.org/wiki/attach/Atari%20DOS%20II%20Assembly%20Source%20Listing/Atari%20DOS%20II%202.0S%20DOS.SYS%20Assembly%20Source%20Listing.txt

Instrukcje:
RTS - powrót z procedury
JSR - wywołanie procedury
JMP - goto (np. w celu ominięcia sekcji ELSE)

2

@katakrowa:
Masz rację po części.
Są projekty gdzie najważniejszy jest deadline, moment wypuszczenia. Jeśli jest on relatywnie szybki, to nieważne ile sobie długu technicznego narobimy to pewnie nie zaczniemy go serio spłacać (przed tym terminem). (to są projekty gdzie generalnie mamy kilka tygodni na wypuszczenie czegoś na produkcję i relatywnie mały 4-5 osób zespół).
Są projekty, które z natury rzeczy nie mają długiego maintenance - kampania reklamowa, migracja jednorazowa. Wtedy też można często olać jakość.
Jednym z zadań analityka jest dowiedzenie się o tego typu założeniach (priorytetach) w projekcie.

Tym niemniej zwykle nie dyskutujemy o tym jak robić na szybko syf, to robić każdy umi. Nie trzeba z tego dodatkowych szkoleń.

Pracowałem 6 lat na utrzymaniu koszmarnego projektu, który był zrobiony w kilka tygodni na szybko, tylko aby pokazać inwestorowi "że mamy fajny produkt". No to mieliśmy... prawdziwa stajnia Augiasza. Ale gdyby oryginalny team nie posklejał z wielu kawałków gówna czegoś na szybko to nie byłoby inwestora i nie byłoby projektu.
Słaba rzecz, że nikt odpowiednio wcześnie nie pomyślał że trzeba było zmienić to podejście, po tym jak projekt był już na produkcji. W pewnym momencie nie było już developmentu tylko permanentne gaszenie pożarów.

5

W niektórych skrajnych przypadkach dużą metodę można zamienić na klasę, rozbić ją na mniejsze metody i tym samym uniknąć przesyłania w kółko dużego zestawu parametrów bo przeniesiemy je do składowych klasy. Nie było tutaj specjalnie dużych przykładów, więc wezmę mały i na nim też się da pokazać. Zamiast:

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;
}

Można by zaklepać taką klasę (gdzie a, b i c są polami w klasie, więc nie trzeba ich przerzucać):

case class QuadraticPolynomial(a: Double, b: Double, c: Double) {
  val delta = b*b - 4*a*c
  val deltaRootOpt = if (delta < 0) None else Some(Math.sqrt(delta))

  val root1Opt = deltaRootOpt.map(root => (-b + root)/(2*a))
  val root2Opt = deltaRootOpt.map(root => (-b - root)/(2*a))

  val hasPositiveRoot = root1Opt.exists(_ > 0) || root2Opt.exists(_ > 0)
}

I już jest cacy :) Przykład był trochę słaby, więc alternatywa też niekoniecznie jest przekonująca , ale tego typu schemat zdarza mi się sporadycznie zastosować do przerośniętych metod.

Co do samej idei dzielenia metod na mniejsze (oraz wyrażeń na mniejsze, klas na mniejsze, modułów na mniejsze, itd) to mam tendencję by to robić (czyli nie dogmatycznie, ale preferencyjnie). Sensowne nazwanie wydzielonej metody jest trudniejsze niż napisanie komentarza (naming is hard, ale komentarz może być bez sensu, bo to przecież nie nazwa). Czasami wychodzi mi na to, że muszę zrefaktorować kod, by dało się sensownie nazwać te wydzielone kawałki i takie coś zwiększa czytelność. Lepsze nazewnictwo = mniej mylnych intuicji. Zdecydowana większość programistów olewa wyzwanie i wysrywa kiepskie nazwy :(

Zamiast stosować jakieś sztywne reguły dotyczące metryk kodu to raczej zastanawiam się czy mój kod jest czytelny. W pewnym sensie zadaję sobie pytanie czy kod, który napisałem byłby dla mnie czytelny gdybym widział go po raz pierwszy. Jeśli tak, to zmniejsza to presję na dalsze masowanie kodu. Ponadto lubię oddzielić trudniejszą logikę od prostszej, jeżeli ta prostsza ma większe prawdopodobieństwo przeróbek w przyszłości. Prosty kod prościej przerabiać (oczywista oczywistość).

4

@MuadibAtrides: Wprawdzie nie pisałeś do mnie, ale pozwolę sobie odnieść się.

MuadibAtrides napisał(a):

Cały problem sprowadza się do ustalenia metryk / reguł jakości, według, którego ten kod można obiektywnie ocenić.

Tu jest właśnie taki problem, że ludzie często szukają łatwych wytycznych, ale nie po to, aby zrozumieć ich esencję, tylko po to, żeby nie musieć myśleć. Ludzie chcą ryby, a nie wędki. Teraz ktoś używając argumentu "bo ja widziałem" stwierdza, że X to zło (gdzie za X obecnie wstawia się wyjątki, mocki, ORM-y, adnotacje, a w tym temacie nierozbijanie funkcji na mniejsze), ale nie jest w stanie tego uzasadnić obiektywnie i mierzalnie. I o ile rozumiem, że wiele rzeczy w inżynierii wynika po prostu z doświadczenia i oglądania spektakularnych wtop przez lata, o tyle fakt, że jakiś mechanizm może prowadzić do problemów (albo wręcz zazwyczaj prowadzi) jeszcze nie oznacza, że tego mechanizmu nie wolno nigdy stosować. Tylko że ludzie wolą powiedzieć "mechanizm jest zły" zamiast "jeżeli użyjesz mechanizmu tak a tak, to są spore szanse, że nadziejesz się na to i to, przez co będziesz miał taki a taki problem" (a często nawet nie potrafią tego powiedzieć, bo nie wiedzą, jakie problemy mogą wyniknąć, tylko zakuli, żeby tak robić/nie robić).

I szczególnie Java jest tu dobrym przykładem. Wielodziedziczenie implementacji było złe, ale jednak zostało w pewnej formie przywrócone w Javie 8. Problem diamentu był zły, ale nie dość, że wrócił w Javie 8, to jeszcze jego istota jest obecna w Javie od samego początku. Ot, ludzie sparzyli się na kilku kwestiach, więc potem postanowili rozprawić się z nimi raz a dobrze, a jednak z czasem się okazało, że te mechanizmy nie są wcale takie złe.

Więc o ile zgadzam się, że jasna metryka byłaby bardzo przydatna, o tyle chciałbym jej nie dla niej samej, a dla łatwości pokazania procesu i tego, co się stanie, gdy ta metryka urośnie za bardzo.

0
jarekr000000 napisał(a):

@katakrowa:

Tym niemniej zwykle nie dyskutujemy o tym jak robić na szybko syf, to robić każdy umi. Nie trzeba z tego dodatkowych szkoleń.
( ... )W pewnym momencie nie było już developmentu tylko permanentne gaszenie pożarów.

Nie mówię o sytuacjach, w których ktoś wypuścił w świat "g...o" i teraz inny ktoś oczekuje, że zrobi się z niego diament. Też przez ponad 20 lat pracy zdarzyło się wypuścić na światło dzienne niejedną "programistyczną kupę". Część z nich niestety działa do dziś jednak to były z założenia i zawsze rzeczy "jednorazowe" jak nawet nie tymczasowe. Zawsze też byłem świadom tego, że jak będzie konieczność powrotu do tematu to przeprojektowanie i przepisanie jest pierwszym krokiem jaki trzeba podjąć. Wiele razy jakeś małe projekty zaczynaliśmy robić od "0" i nigdy nie było to problemem bo zawsze klient był poinformowany, że ten pierwszy robimy TANIO i SZYBKO czyli nie DOBRZE i wyrażał na to zgodę. Oczywiście inne jest też moja perspektywa oceny bo decyzję podejmowałem z pozycji właściciela firmy i nie byłem narażony na naciski ze strony szefa, który niekoniecznie jest "techniczny".

W przypadku, który opisujesz ktoś w całym procesie nakłamał lub coś ukrył - gdzieś zawiodła ludzka uczciwość. Deadline o ile tłumaczy wypuszczenie bubla byle się zmieścić w terminie to nie tłumaczy jego dalszego rozwoju i brnięcia w nim dalej. Wg mnie ( nie wiem jaka to była skala ) błąd popełnił analityków, architekt lub projektant lub wszyscy razem. Ktoś zapomniał powiedzieć "OK szefie zrobimy to za 2 tygodnie ale jeśli projekt wypali to trzeba będzie go pisać od nowa".

10

Ja powiem tak: jak pracujecie w korpo to możecie przepalać czas na zastanwianie się czy dana funckje rozdzielić na 2 inne. W małych firmach (i nie mówie tu o januszsoftach) takich dywagacji nie ma i uwierzcie można pisac kod 20x szybciej i tym samym taniej niż korpo z tylko delikatnym późniejszym narzutem co do dalszej rozbudowy. Klient happy, firma happy i programiści happy.

2

@mr_jaro: nie wiem co stosujesz, ale u mnie w IntelliJ wystarczy nacisnąć ctrl + alt + m i mogę wyodrębnić metodę/funkcję. Całosć zajmuje zazwyczaj kilkadziesiąt sekund.

4

Zwykle rozbijam tak długo jak:

  • mogę znaleźć prostą, sensowną nazwę dla wydzielonej funkcji
  • liczba parametrów jest rozsądnej długości (0-3)
  • funkcja z której wydzielam kod staje się prostsza (np. znika jeden poziom zagnieżdżenia)
  • wydzielony kod jest na wyraźnie innym poziomie abstrakcji niż to z czego go wydzielam

Natomiast liczba użyć wydzielonej funkcji nie ma dla mnie większego znaczenia, choć oczywiście powtórzenia są jakąś przesłanką aby wydzielać, ale z drugiej strony należy uważać na przypadkowe podobieństwo. Nie każde powtórzenie tego samego fragmentu jest złe i nie należy stosować DRY za wszelką cenę.

W przypadkach ekstremalnej optymalizacji zdarza mi się natomiast specjalnie oddzielać kod rzadko wykonywany od gorącej ścieżki. Kompilatory takie jak LLVM / GCC są ogólnie świetne w inlining (IMHO robią to lepiej niż JVM nawet bez PGO), więc zbyt małe funkcje zwykle nie kosztują nic. Nawet funkcje asynchroniczne kosztują zero. Natomiast w drugą stronę - tj. rozbijanie kodu jest jeszcze w powijakach (i często wymaga PGO; ale nawet kompilatory które używają PGO jak JVM Hotspot C2 tego nie robią w ogóle). Wyrzucenie niekrytycznych ścieżek i oznaczenie ich inline=never (o ile możliwe) poprawia szanse na inline innych ważniejszych części kodu. W Javie też to działa.

0
jarekr000000 napisał(a):

@MuadibAtrides:

Nie jesteśmy humanistami, że możemy moim zdaniem zostawiać coś do interpretacji. Cały problem sprowadza się do ustalenia metryk / reguł jakości, według, którego ten kod można obiektywnie ocenić.

Proszę, podaj wg jakich metryk oceniasz.

A potem podaj wg jakich kryteriów wybrałeś te metryki.

Przy okazji: Ja mam swoje metryki wg nich wszystkie rozwiązania ulegają dyskwalifikacji ze względu na użycie wyjątku. (Ale takie było założenie).

@jarekr000000 Skoro mówimy o czytelności to kryteria według, których próbuje oceniać:

  1. jasność znajomości typu w danym miejscu - im mniej znamy typy w danym miejscu, tym mniej wiemy co robimy
  2. nazewnictwo zmiennych - im bardziej jasno i precyzyjnie określają nazwy co tam ma być - tym lepiej
  3. Jasność / opis i zrozumienie struktury danych wykorzystanych w danym algorytmie / kodzie - im lepiej jestem w stanie zrozumieć strukturę danych i jakie są dane, łatwiej mi zrozumieć co ma robić kod / jaki efekt uzyskać.
  4. Ja to nazywam "napaćkaniem", a widziałem, że @WeiXiao użył zwrotu cognitive load. - im mniej jest szlaczków, znaczków do czytania tym lepiej (nawiasy, klamry, kropki , przecinki itp), im mniej jest skoków by coś prześledzić tym lepiej, im mniej jest syntatic sugar i jest jednoznaczność w ekspresji kodu tym lepiej - dobrym przykładem jest JS, który w nowej edycji wprowadził tyle hieroglifów, że jak się przesadzi to ciężko to się czyta i interpretuje
  5. Jednoznaczna kolejność wykonywania instrukcji - jeśli kod jest tak napisany, że musimy interpretować / analizować jaka jest kolejność to jest to źle napisany. Najprostszy przykład z pythona:
try:
   doSomething()
   return
except Exception as e:
  pass
finally:
  doSomething2()
  return
  1. podział kodu jednoznaczny na funkcje: mutujące, niemutujące, matematyczne, procedury

Sposób doboru kryteriów:

  1. Zaufanie do doświadczenia wykładowcy-praktyka, z okresu studiów. Praktyczne doświadczenie osobiste w debugowaniu kodu legacy w js (autokorekta ciągle mi js 'a z maca zamienia na ssie albo psa :)), Pythonie, innych dynamicznych językach, które pokazuje trudność rozumienia. Trudność utrzymywania kodu, który ma typowanie dynamiczne.
  2. Ogólna praktyka, wykłady na internecie różnych programistów, 4programmers, dyskusje
  3. Osobiste doświadczenie, nie pamiętam kiedy nie przydało mi się rozpisać / zrozumieć strukturę danych, którą przetwarzamy. Im łatwiej ją określić tym lepiej dla nas. Finalnie mam wrażenie, że najważniejsze są dane i stan z A do B dla danych.
  4. Osobiste doświadczenie z programowania i grania w gry komputerowe za pieniądze. To tak jak grasz w rts'a i pierwsze co robisz to wyłączasz wszystkie wodotryski i grafikę dajesz na minimum by skupić się na tym co ważne.
  5. Doświadczenie i praktyka kolegów z C++, którzy pracowali przy różnych kompilatorach. Klasyczny przykład, aby nie używać np. ++x ani x++ tylko jednoznacznie rozpisywać kolejność instrukcji.
  6. Uncle bob, pascal, taki styl przypadł mi do gustu.

@Afish @jarekr000000 ja osobiście u uważam, że trzeba zdroworozsądkowo podchodzić do wszystkich rzeczy i umieć znaleźć złoty środek. I się z Wami zgadzam, że część rzeczy wynika z doświadczenia i pewne rzeczy wiemy, że zadziałają / nie zadziałają - choćby klasyczne wielodziedziczenie w Pythonie jak się ogarnie to fajne rzeczy można z tego wycisnąć. Dalej uważam, że warto szukać tych metryk aby móc nimi się posłużyć / kierować i wyciągać z nich wnioski.

0

@slsy:

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

Ten przykład mi się podoba, bo jest to prawdziwy kod, używany na produkcji w złożonym systemie, a nawet pewnie jakiś core

Chyba zawsze, niezależnie gdzie patrzę - base class libraries, kod frameworka, runtime, kompilatora czy chociażby Linuxa

to zawsze spotykam tego typu kod - pełno ifków, pętelek, zagnieżdżeń

nawet teraz otworzyłem pierwszy lepszy plik w repo Linuxa i mam: (niżej)

A zatem co, czy ten kod piszą słabi ludzie czy co? Dlaczego clean code, wydzielanie funkcyjek nie jest tak popularne w prawdziwym sofcie, który na dodatek jest OSS, więc każdy widzi?

struct afs_vlserver_list *afs_parse_text_addrs(struct afs_net *net,
					       const char *text, size_t len,
					       char delim,
					       unsigned short service,
					       unsigned short port)
{
	struct afs_vlserver_list *vllist;
	struct afs_addr_list *alist;
	const char *p, *end = text + len;
	const char *problem;
	unsigned int nr = 0;
	int ret = -ENOMEM;

	_enter("%*.*s,%c", (int)len, (int)len, text, delim);

	if (!len) {
		_leave(" = -EDESTADDRREQ [empty]");
		return ERR_PTR(-EDESTADDRREQ);
	}

	if (delim == ':' && (memchr(text, ',', len) || !memchr(text, '.', len)))
		delim = ',';

	/* Count the addresses */
	p = text;
	do {
		if (!*p) {
			problem = "nul";
			goto inval;
		}
		if (*p == delim)
			continue;
		nr++;
		if (*p == '[') {
			p++;
			if (p == end) {
				problem = "brace1";
				goto inval;
			}
			p = memchr(p, ']', end - p);
			if (!p) {
				problem = "brace2";
				goto inval;
			}
			p++;
			if (p >= end)
				break;
		}

		p = memchr(p, delim, end - p);
		if (!p)
			break;
		p++;
	} while (p < end);

	_debug("%u/%u addresses", nr, AFS_MAX_ADDRESSES);

	vllist = afs_alloc_vlserver_list(1);
	if (!vllist)
		return ERR_PTR(-ENOMEM);

	vllist->nr_servers = 1;
	vllist->servers[0].server = afs_alloc_vlserver("<dummy>", 7, AFS_VL_PORT);
	if (!vllist->servers[0].server)
		goto error_vl;

	alist = afs_alloc_addrlist(nr, service, AFS_VL_PORT);
	if (!alist)
		goto error;

	/* Extract the addresses */
	p = text;
	do {
		const char *q, *stop;
		unsigned int xport = port;
		__be32 x[4];
		int family;

		if (*p == delim) {
			p++;
			continue;
		}

		if (*p == '[') {
			p++;
			q = memchr(p, ']', end - p);
		} else {
			for (q = p; q < end; q++)
				if (*q == '+' || *q == delim)
					break;
		}

		if (in4_pton(p, q - p, (u8 *)&x[0], -1, &stop)) {
			family = AF_INET;
		} else if (in6_pton(p, q - p, (u8 *)x, -1, &stop)) {
			family = AF_INET6;
		} else {
			problem = "family";
			goto bad_address;
		}

		p = q;
		if (stop != p) {
			problem = "nostop";
			goto bad_address;
		}

		if (q < end && *q == ']')
			p++;

		if (p < end) {
			if (*p == '+') {
				/* Port number specification "+1234" */
				xport = 0;
				p++;
				if (p >= end || !isdigit(*p)) {
					problem = "port";
					goto bad_address;
				}
				do {
					xport *= 10;
					xport += *p - '0';
					if (xport > 65535) {
						problem = "pval";
						goto bad_address;
					}
					p++;
				} while (p < end && isdigit(*p));
			} else if (*p == delim) {
				p++;
			} else {
				problem = "weird";
				goto bad_address;
			}
		}

		if (family == AF_INET)
			afs_merge_fs_addr4(alist, x[0], xport);
		else
			afs_merge_fs_addr6(alist, x, xport);

	} while (p < end);

	rcu_assign_pointer(vllist->servers[0].server->addresses, alist);
	_leave(" = [nr %u]", alist->nr_addrs);
	return vllist;

inval:
	_leave(" = -EINVAL [%s %zu %*.*s]",
	       problem, p - text, (int)len, (int)len, text);
	return ERR_PTR(-EINVAL);
bad_address:
	_leave(" = -EINVAL [%s %zu %*.*s]",
	       problem, p - text, (int)len, (int)len, text);
	ret = -EINVAL;
error:
	afs_put_addrlist(alist);
error_vl:
	afs_put_vlserverlist(net, vllist);
	return ERR_PTR(ret);
}

Albo jakiś losowy fragment z Roslyna

private async Task<ImmutableArray<SemanticEditInfo>> AnalyzeSemanticsAsync(
		EditScript<SyntaxNode> editScript,
		IReadOnlyDictionary<SyntaxNode, EditKind> editMap,
		ImmutableArray<UnmappedActiveStatement> oldActiveStatements,
		ImmutableArray<LinePositionSpan> newActiveStatementSpans,
		IReadOnlyList<(SyntaxNode OldNode, SyntaxNode NewNode, TextSpan DiagnosticSpan)> triviaEdits,
		Project oldProject,
		Document? oldDocument,
		Document newDocument,
		SourceText newText,
		ArrayBuilder<RudeEditDiagnostic> diagnostics,
		ImmutableArray<ActiveStatement>.Builder newActiveStatements,
		ImmutableArray<ImmutableArray<SourceFileSpan>>.Builder newExceptionRegions,
		EditAndContinueCapabilities capabilities,
		bool inBreakState,
		CancellationToken cancellationToken)
	{
		Debug.Assert(inBreakState || newActiveStatementSpans.IsEmpty);

		if (editScript.Edits.Length == 0 && triviaEdits.Count == 0)
		{
			return ImmutableArray<SemanticEditInfo>.Empty;
		}

		// { new type -> constructor update }
		PooledDictionary<INamedTypeSymbol, ConstructorEdit>? instanceConstructorEdits = null;
		PooledDictionary<INamedTypeSymbol, ConstructorEdit>? staticConstructorEdits = null;

		var oldModel = (oldDocument != null) ? await oldDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false) : null;
		var newModel = await newDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
		var oldCompilation = oldModel?.Compilation ?? await oldProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
		var newCompilation = newModel.Compilation;

		using var _1 = PooledHashSet<ISymbol>.GetInstance(out var processedSymbols);
		using var _2 = ArrayBuilder<SemanticEditInfo>.GetInstance(out var semanticEdits);

		try
		{
			INamedTypeSymbol? lazyLayoutAttribute = null;

			foreach (var edit in editScript.Edits)
			{
				cancellationToken.ThrowIfCancellationRequested();

				if (edit.Kind == EditKind.Move)
				{
					// Move is either a Rude Edit and already reported in syntax analysis, or has no semantic effect.
					// For example, in VB we allow move from field multi-declaration.
					// "Dim a, b As Integer" -> "Dim a As Integer" (update) and "Dim b As Integer" (move)
					continue;
				}

				if (edit.Kind == EditKind.Reorder)
				{
					// Currently we don't do any semantic checks for reordering
					// and we don't need to report them to the compiler either.
					// Consider: Currently symbol ordering changes are not reflected in metadata (Reflection will report original order).

					// Consider: Reordering of fields is not allowed since it changes the layout of the type.
					// This ordering should however not matter unless the type has explicit layout so we might want to allow it.
					// We do not check changes to the order if they occur across multiple documents (the containing type is partial).
					Debug.Assert(!IsDeclarationWithInitializer(edit.OldNode) && !IsDeclarationWithInitializer(edit.NewNode));
					continue;
				}

				foreach (var symbolEdits in GetSymbolEdits(edit.Kind, edit.OldNode, edit.NewNode, oldModel, newModel, editMap, cancellationToken))
				{
					Func<SyntaxNode, SyntaxNode?>? syntaxMap;
					SemanticEditKind editKind;

					var (oldSymbol, newSymbol, syntacticEditKind) = symbolEdits;
					var symbol = newSymbol ?? oldSymbol;
					Contract.ThrowIfNull(symbol);

					if (!processedSymbols.Add(symbol))
					{
						continue;
					}

					var symbolKey = SymbolKey.Create(symbol, cancellationToken);

					// Ignore ambiguous resolution result - it may happen if there are semantic errors in the compilation.
					oldSymbol ??= symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
					newSymbol ??= symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;

					var (oldDeclaration, newDeclaration) = GetSymbolDeclarationNodes(oldSymbol, newSymbol, edit.OldNode, edit.NewNode);

					// The syntax change implies an update of the associated symbol but the old/new symbol does not actually exist.
					// Treat the edit as Insert/Delete. This may happen e.g. when all C# global statements are removed, the first one is added or they are moved to another file.
					if (syntacticEditKind == EditKind.Update)
					{
						if (oldSymbol == null || oldDeclaration != null && oldDeclaration.SyntaxTree != oldModel?.SyntaxTree)
						{
							syntacticEditKind = EditKind.Insert;
						}
						else if (newSymbol == null || newDeclaration != null && newDeclaration.SyntaxTree != newModel.SyntaxTree)
						{
							syntacticEditKind = EditKind.Delete;
						}
					}

					if (!inBreakState)
					{
						// Delete/insert/update edit of a member of a reloadable type (including nested types) results in Replace edit of the containing type.
						// If a Delete edit is part of delete-insert operation (member moved to a different partial type declaration or to a different file)
						// skip producing Replace semantic edit for this Delete edit as one will be reported by the corresponding Insert edit.

						var oldContainingType = oldSymbol?.ContainingType;
						var newContainingType = newSymbol?.ContainingType;
						var containingType = newContainingType ?? oldContainingType;

						if (containingType != null && (syntacticEditKind != EditKind.Delete || newSymbol == null))
						{
							var containingTypeSymbolKey = SymbolKey.Create(containingType, cancellationToken);
							oldContainingType ??= (INamedTypeSymbol?)containingTypeSymbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
							newContainingType ??= (INamedTypeSymbol?)containingTypeSymbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;

							if (oldContainingType != null && newContainingType != null && IsReloadable(oldContainingType))
							{
								if (processedSymbols.Add(newContainingType))
								{
									if (capabilities.HasFlag(EditAndContinueCapabilities.NewTypeDefinition))
									{
										semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, containingTypeSymbolKey, syntaxMap: null, syntaxMapTree: null,
											IsPartialEdit(oldContainingType, newContainingType, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? containingTypeSymbolKey : null));
									}
									else
									{
										ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, newContainingType, newDeclaration, cancellationToken);
									}
								}

								continue;
							}
						}

						var oldType = oldSymbol as INamedTypeSymbol;
						var newType = newSymbol as INamedTypeSymbol;

						// Deleting a reloadable type is a rude edit, reported the same as for non-reloadable.
						// Adding a reloadable type is a standard type addition (TODO: unless added to a reloadable type?).
						// Making reloadable attribute non-reloadable results in a new version of the type that is
						// not reloadable but does not update the old version in-place.
						if (syntacticEditKind != EditKind.Delete && oldType != null && newType != null && IsReloadable(oldType))
						{
							if (symbol == newType || processedSymbols.Add(newType))
							{
								if (oldType.Name != newType.Name)
								{
									// https://github.com/dotnet/roslyn/issues/54886
									ReportUpdateRudeEdit(diagnostics, RudeEditKind.Renamed, newType, newDeclaration, cancellationToken);
								}
								else if (oldType.Arity != newType.Arity)
								{
									// https://github.com/dotnet/roslyn/issues/54881
									ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingTypeParameters, newType, newDeclaration, cancellationToken);
								}
								else if (!capabilities.HasFlag(EditAndContinueCapabilities.NewTypeDefinition))
								{
									ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, newType, newDeclaration, cancellationToken);
								}
								else
								{
									semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, symbolKey, syntaxMap: null, syntaxMapTree: null,
										IsPartialEdit(oldType, newType, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null));
								}
							}

							continue;
						}
					}

					switch (syntacticEditKind)
					{
						case EditKind.Delete:
							{
								Contract.ThrowIfNull(oldModel);
								Contract.ThrowIfNull(oldSymbol);
								Contract.ThrowIfNull(oldDeclaration);

								var activeStatementIndices = GetOverlappingActiveStatements(oldDeclaration, oldActiveStatements);
								var hasActiveStatement = activeStatementIndices.Any();

								// TODO: if the member isn't a field/property we should return empty span.
								// We need to adjust the tracking span design and UpdateUneditedSpans to account for such empty spans.
								if (hasActiveStatement)
								{
									var newSpan = IsDeclarationWithInitializer(oldDeclaration) ?
										GetDeletedNodeActiveSpan(editScript.Match.Matches, oldDeclaration) :
										GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, oldDeclaration);

									foreach (var index in activeStatementIndices)
									{
										Debug.Assert(newActiveStatements[index] is null);

										newActiveStatements[index] = GetActiveStatementWithSpan(oldActiveStatements[index], editScript.Match.NewRoot.SyntaxTree, newSpan, diagnostics, cancellationToken);
										newExceptionRegions[index] = ImmutableArray<SourceFileSpan>.Empty;
									}
								}

								syntaxMap = null;
								editKind = SemanticEditKind.Delete;

								// Check if the declaration has been moved from one document to another.
								if (newSymbol != null && !(newSymbol is IMethodSymbol newMethod && newMethod.IsPartialDefinition))
								{
									// Symbol has actually not been deleted but rather moved to another document, another partial type declaration
									// or replaced with an implicitly generated one (e.g. parameterless constructor, auto-generated record methods, etc.)

									// Report rude edit if the deleted code contains active statements.
									// TODO (https://github.com/dotnet/roslyn/issues/51177):
									// Only report rude edit when replacing member with an implicit one if it has an active statement.
									// We might be able to support moving active members but we would need to 
									// 1) Move AnalyzeChangedMemberBody from Insert to Delete
									// 2) Handle active statements that moved to a different document in ActiveStatementTrackingService
									// 3) The debugger's ManagedActiveStatementUpdate might need another field indicating the source file path.
									if (hasActiveStatement)
									{
										ReportDeletedMemberRudeEdit(diagnostics, oldSymbol, newCompilation, RudeEditKind.DeleteActiveStatement, cancellationToken);
										continue;
									}

									if (!newSymbol.IsImplicitlyDeclared)
									{
										// Ignore the delete. The new symbol is explicitly declared and thus there will be an insert edit that will issue a semantic update.
										// Note that this could also be the case for deleting properties of records, but they will be handled when we see
										// their accessors below.
										continue;
									}

									if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(oldDeclaration, newSymbol.ContainingType, out var isFirst))
									{
										// Defer a constructor edit to cover the property initializer changing
										DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);

										// If there was no body deleted then we are done since the compiler generated property also has no body
										if (TryGetDeclarationBody(oldDeclaration) is null)
										{
											continue;
										}

										// If there was a body, then the backing field of the property will be affected so we
										// need to issue edits for the synthezied members.
										// We only need to do this once though.
										if (isFirst)
										{
											AddEditsForSynthesizedRecordMembers(newCompilation, newSymbol.ContainingType, semanticEdits, cancellationToken);
										}
									}

									// If a constructor is deleted and replaced by an implicit one the update needs to aggregate updates to all data member initializers,
									// or if a property is deleted that is part of a records primary constructor, which is effectivelly moving from an explicit to implicit
									// initializer.
									if (IsConstructorWithMemberInitializers(oldDeclaration))
									{
										processedSymbols.Remove(oldSymbol);
										DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
										continue;
									}

									// there is no insert edit for an implicit declaration, therefore we need to issue an update:
									editKind = SemanticEditKind.Update;
								}
								else
								{
									var diagnosticSpan = GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, oldDeclaration);

									// If we got here for a global statement then the actual edit is a delete of the synthesized Main method
									if (IsGlobalMain(oldSymbol))
									{
										diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.Delete, diagnosticSpan, edit.OldNode, new[] { GetDisplayName(edit.OldNode, EditKind.Delete) }));
										continue;
									}

									// If the associated member declaration (accessor -> property/indexer/event, parameter -> method) has also been deleted skip
									// the delete of the symbol as it will be deleted by the delete of the associated member.
									//
									// Associated member declarations must be in the same document as the symbol, so we don't need to resolve their symbol.
									// In some cases the symbol even can't be resolved unambiguously. Consider e.g. resolving a method with its parameter deleted -
									// we wouldn't know which overload to resolve to.
									if (TryGetAssociatedMemberDeclaration(oldDeclaration, out var oldAssociatedMemberDeclaration))
									{
										if (HasEdit(editMap, oldAssociatedMemberDeclaration, EditKind.Delete))
										{
											continue;
										}
									}
									else if (oldSymbol.ContainingType != null)
									{
										// Check if the symbol being deleted is a member of a type that's also being deleted.
										// If so, skip the member deletion and only report the containing symbol deletion.
										var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken);
										var newContainingSymbol = containingSymbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
										if (newContainingSymbol == null)
										{
											continue;
										}
									}

									// deleting symbol is not allowed

									diagnostics.Add(new RudeEditDiagnostic(
										RudeEditKind.Delete,
										diagnosticSpan,
										oldDeclaration,
										new[]
										{
											string.Format(FeaturesResources.member_kind_and_name,
												GetDisplayName(oldDeclaration, EditKind.Delete),
												oldSymbol.ToDisplayString(diagnosticSpan.IsEmpty ? s_fullyQualifiedMemberDisplayFormat : s_unqualifiedMemberDisplayFormat))
										}));

									continue;
								}
							}

							break;

						case EditKind.Insert:
							{
								Contract.ThrowIfNull(newModel);
								Contract.ThrowIfNull(newSymbol);
								Contract.ThrowIfNull(newDeclaration);

								syntaxMap = null;

								editKind = SemanticEditKind.Insert;
								INamedTypeSymbol? oldContainingType;
								var newContainingType = newSymbol.ContainingType;

								// Check if the declaration has been moved from one document to another.
								if (oldSymbol != null)
								{
									// Symbol has actually not been inserted but rather moved between documents or partial type declarations,
									// or is replacing an implicitly generated one (e.g. parameterless constructor, auto-generated record methods, etc.)
									oldContainingType = oldSymbol.ContainingType;

									if (oldSymbol.IsImplicitlyDeclared)
									{
										// If a user explicitly implements a member of a record then we want to issue an update, not an insert.
										if (oldSymbol.DeclaringSyntaxReferences.Length == 1)
										{
											Contract.ThrowIfNull(oldDeclaration);
											ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldDeclaration, newDeclaration, oldSymbol, newSymbol, capabilities, cancellationToken);

											if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(newDeclaration, newContainingType, out var isFirst))
											{
												// If there is no body declared we can skip it entirely because for a property accessor
												// it matches what the compiler would have previously implicitly implemented.
												if (TryGetDeclarationBody(newDeclaration) is null)
												{
													continue;
												}

												// If there was a body, then the backing field of the property will be affected so we
												// need to issue edits for the synthezied members. Only need to do it once.
												if (isFirst)
												{
													AddEditsForSynthesizedRecordMembers(newCompilation, newContainingType, semanticEdits, cancellationToken);
												}
											}

											editKind = SemanticEditKind.Update;
										}
									}
									else if (oldSymbol.DeclaringSyntaxReferences.Length == 1 && newSymbol.DeclaringSyntaxReferences.Length == 1)
									{
										Contract.ThrowIfNull(oldDeclaration);

										// Handles partial methods and explicitly implemented properties that implement positional parameters of records

										// We ignore partial method definition parts when processing edits (GetSymbolForEdit).
										// The only declaration in compilation without syntax errors that can have multiple declaring references is a type declaration.
										// We can therefore ignore any symbols that have more than one declaration.
										ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newDeclaration, newModel, ref lazyLayoutAttribute);

										// Compare the old declaration syntax of the symbol with its new declaration and report rude edits
										// if it changed in any way that's not allowed.
										ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldDeclaration, newDeclaration, oldSymbol, newSymbol, capabilities, cancellationToken);

										var oldBody = TryGetDeclarationBody(oldDeclaration);
										if (oldBody != null)
										{
											// The old symbol's declaration syntax may be located in a different document than the old version of the current document.
											var oldSyntaxDocument = oldProject.Solution.GetRequiredDocument(oldDeclaration.SyntaxTree);
											var oldSyntaxModel = await oldSyntaxDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
											var oldSyntaxText = await oldSyntaxDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
											var newBody = TryGetDeclarationBody(newDeclaration);

											// Skip analysis of active statements. We already report rude edit for removal of code containing
											// active statements in the old declaration and don't currently support moving active statements.
											AnalyzeChangedMemberBody(
												oldDeclaration,
												newDeclaration,
												oldBody,
												newBody,
												oldSyntaxModel,
												newModel,
												oldSymbol,
												newSymbol,
												newText,
												oldActiveStatements: ImmutableArray<UnmappedActiveStatement>.Empty,
												newActiveStatementSpans: ImmutableArray<LinePositionSpan>.Empty,
												capabilities: capabilities,
												newActiveStatements,
												newExceptionRegions,
												diagnostics,
												out syntaxMap,
												cancellationToken);
										}

										// If a constructor changes from including initializers to not including initializers
										// we don't need to aggregate syntax map from all initializers for the constructor update semantic edit.
										var isNewConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
										var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration);
										var isRecordPrimaryConstructorParameter = IsRecordPrimaryConstructorParameter(oldDeclaration);

										if (isNewConstructorWithMemberInitializers || isDeclarationWithInitializer || isRecordPrimaryConstructorParameter)
										{
											if (isNewConstructorWithMemberInitializers)
											{
												processedSymbols.Remove(newSymbol);
											}

											if (isDeclarationWithInitializer)
											{
												AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken);
											}

											DeferConstructorEdit(oldSymbol.ContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);

											// Don't add a separate semantic edit.
											// Updates of data members with initializers and constructors that emit initializers will be aggregated and added later.
											continue;
										}

										editKind = SemanticEditKind.Update;
									}
									else
									{
										editKind = SemanticEditKind.Update;
									}
								}
								else if (TryGetAssociatedMemberDeclaration(newDeclaration, out var newAssociatedMemberDeclaration) &&
										 HasEdit(editMap, newAssociatedMemberDeclaration, EditKind.Insert))
								{
									// If the symbol is an accessor and the containing property/indexer/event declaration has also been inserted skip
									// the insert of the accessor as it will be inserted by the property/indexer/event.
									continue;
								}
								else if (newSymbol is IParameterSymbol or ITypeParameterSymbol)
								{
									diagnostics.Add(new RudeEditDiagnostic(
										RudeEditKind.Insert,
										GetDiagnosticSpan(newDeclaration, EditKind.Insert),
										newDeclaration,
										arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));

									continue;
								}
								else if (newContainingType != null && !IsGlobalMain(newSymbol))
								{
									// The edit actually adds a new symbol into an existing or a new type.

									var containingSymbolKey = SymbolKey.Create(newContainingType, cancellationToken);
									oldContainingType = containingSymbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol as INamedTypeSymbol;

									if (oldContainingType != null && !CanAddNewMember(newSymbol, capabilities))
									{
										diagnostics.Add(new RudeEditDiagnostic(
											RudeEditKind.InsertNotSupportedByRuntime,
											GetDiagnosticSpan(newDeclaration, EditKind.Insert),
											newDeclaration,
											arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));
									}

									// Check rude edits for each member even if it is inserted into a new type.
									ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: oldContainingType != null);

									if (oldContainingType == null)
									{
										// Insertion of a new symbol into a new type.
										// We'll produce a single insert edit for the entire type.
										continue;
									}

									// Report rude edits for changes to data member changes of a type with an explicit layout.
									// We disallow moving a data member of a partial type with explicit layout even when it actually does not change the layout.
									// We could compare the exact order of the members but the scenario is unlikely to occur.
									ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newDeclaration, newModel, ref lazyLayoutAttribute);

									// If a property or field is added to a record then the implicit constructors change,
									// and we need to mark a number of other synthesized members as having changed.
									if (newSymbol is IPropertySymbol or IFieldSymbol && newContainingType.IsRecord)
									{
										DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);

										AddEditsForSynthesizedRecordMembers(newCompilation, newContainingType, semanticEdits, cancellationToken);
									}
								}
								else
								{
									// adds a new top-level type, or a global statement where none existed before, which is
									// therefore inserting the <Program>$ type
									Contract.ThrowIfFalse(newSymbol is INamedTypeSymbol || IsGlobalMain(newSymbol));

									if (!capabilities.HasFlag(EditAndContinueCapabilities.NewTypeDefinition))
									{
										diagnostics.Add(new RudeEditDiagnostic(
											RudeEditKind.InsertNotSupportedByRuntime,
											GetDiagnosticSpan(newDeclaration, EditKind.Insert),
											newDeclaration,
											arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));
									}

									oldContainingType = null;
									ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: false);
								}

								var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
								if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(newDeclaration))
								{
									Contract.ThrowIfNull(newContainingType);
									Contract.ThrowIfNull(oldContainingType);

									// TODO (bug https://github.com/dotnet/roslyn/issues/2504)
									if (isConstructorWithMemberInitializers &&
										editKind == SemanticEditKind.Insert &&
										IsPartial(newContainingType) &&
										HasMemberInitializerContainingLambda(oldContainingType, newSymbol.IsStatic, cancellationToken))
									{
										// rude edit: Adding a constructor to a type with a field or property initializer that contains an anonymous function
										diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, GetDiagnosticSpan(newDeclaration, EditKind.Insert)));
										break;
									}

									DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);

									if (isConstructorWithMemberInitializers)
									{
										processedSymbols.Remove(newSymbol);
									}

									if (isConstructorWithMemberInitializers || editKind == SemanticEditKind.Update)
									{
										// Don't add a separate semantic edit.
										// Edits of data members with initializers and constructors that emit initializers will be aggregated and added later.
										continue;
									}

									// A semantic edit to create the field/property is gonna be added.
									Contract.ThrowIfFalse(editKind == SemanticEditKind.Insert);
								}
							}

							break;

						case EditKind.Update:
							{
								Contract.ThrowIfNull(oldModel);
								Contract.ThrowIfNull(newModel);
								Contract.ThrowIfNull(oldSymbol);
								Contract.ThrowIfNull(newSymbol);

								editKind = SemanticEditKind.Update;
								syntaxMap = null;

								// Partial type declarations and their type parameters.
								if (oldSymbol.DeclaringSyntaxReferences.Length != 1 && newSymbol.DeclaringSyntaxReferences.Length != 1)
								{
									break;
								}

								Contract.ThrowIfNull(oldDeclaration);
								Contract.ThrowIfNull(newDeclaration);

								var oldBody = TryGetDeclarationBody(oldDeclaration);
								if (oldBody != null)
								{
									var newBody = TryGetDeclarationBody(newDeclaration);

									AnalyzeChangedMemberBody(
										oldDeclaration,
										newDeclaration,
										oldBody,
										newBody,
										oldModel,
										newModel,
										oldSymbol,
										newSymbol,
										newText,
										oldActiveStatements,
										newActiveStatementSpans,
										capabilities,
										newActiveStatements,
										newExceptionRegions,
										diagnostics,
										out syntaxMap,
										cancellationToken);
								}

								// If a constructor changes from including initializers to not including initializers
								// we don't need to aggregate syntax map from all initializers for the constructor update semantic edit.
								var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
								var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration);

								if (isConstructorWithMemberInitializers || isDeclarationWithInitializer)
								{
									if (isConstructorWithMemberInitializers)
									{
										processedSymbols.Remove(newSymbol);
									}

									if (isDeclarationWithInitializer)
									{
										AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken);
									}

									DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);

									// Don't add a separate semantic edit.
									// Updates of data members with initializers and constructors that emit initializers will be aggregated and added later.
									continue;
								}
							}

							break;

						default:
							throw ExceptionUtilities.UnexpectedValue(edit.Kind);
					}

					Contract.ThrowIfFalse(editKind is SemanticEditKind.Update or SemanticEditKind.Insert);

					if (editKind == SemanticEditKind.Update)
					{
						Contract.ThrowIfNull(oldSymbol);

						AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, cancellationToken);
						if (newSymbol is INamedTypeSymbol or IFieldSymbol or IPropertySymbol or IEventSymbol or IParameterSymbol or ITypeParameterSymbol)
						{
							continue;
						}
					}

					semanticEdits.Add(new SemanticEditInfo(editKind, symbolKey, syntaxMap, syntaxMapTree: null,
						IsPartialEdit(oldSymbol, newSymbol, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null));
				}
			}

			foreach (var (oldEditNode, newEditNode, diagnosticSpan) in triviaEdits)
			{
				Contract.ThrowIfNull(oldModel);
				Contract.ThrowIfNull(newModel);

				foreach (var (oldSymbol, newSymbol, editKind) in GetSymbolEdits(EditKind.Update, oldEditNode, newEditNode, oldModel, newModel, editMap, cancellationToken))
				{
					// Trivia edits are only calculated for member bodies and each member has a symbol.
					Contract.ThrowIfNull(newSymbol);
					Contract.ThrowIfNull(oldSymbol);

					if (!processedSymbols.Add(newSymbol))
					{
						// symbol already processed
						continue;
					}

					var (oldDeclaration, newDeclaration) = GetSymbolDeclarationNodes(oldSymbol, newSymbol, oldEditNode, newEditNode);
					Contract.ThrowIfNull(oldDeclaration);
					Contract.ThrowIfNull(newDeclaration);

					var oldContainingType = oldSymbol.ContainingType;
					var newContainingType = newSymbol.ContainingType;
					Contract.ThrowIfNull(oldContainingType);
					Contract.ThrowIfNull(newContainingType);

					if (IsReloadable(oldContainingType))
					{
						if (processedSymbols.Add(newContainingType))
						{
							if (capabilities.HasFlag(EditAndContinueCapabilities.NewTypeDefinition))
							{
								var containingTypeSymbolKey = SymbolKey.Create(oldContainingType, cancellationToken);
								semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, containingTypeSymbolKey, syntaxMap: null, syntaxMapTree: null,
									IsPartialEdit(oldContainingType, newContainingType, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? containingTypeSymbolKey : null));
							}
							else
							{
								ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, newContainingType, newDeclaration, cancellationToken);
							}
						}

						continue;
					}

					// We need to provide syntax map to the compiler if the member is active (see member update above):
					var isActiveMember =
						GetOverlappingActiveStatements(oldDeclaration, oldActiveStatements).Any() ||
						IsStateMachineMethod(oldDeclaration) ||
						ContainsLambda(oldDeclaration);

					var syntaxMap = isActiveMember ? CreateSyntaxMapForEquivalentNodes(oldDeclaration, newDeclaration) : null;

					// only trivia changed:
					Contract.ThrowIfFalse(IsConstructorWithMemberInitializers(oldDeclaration) == IsConstructorWithMemberInitializers(newDeclaration));
					Contract.ThrowIfFalse(IsDeclarationWithInitializer(oldDeclaration) == IsDeclarationWithInitializer(newDeclaration));

					var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
					var isDeclarationWithInitializer = IsDeclarationWithInitializer(newDeclaration);

					if (isConstructorWithMemberInitializers || isDeclarationWithInitializer)
					{
						// TODO: only create syntax map if any field initializers are active/contain lambdas or this is a partial type
						syntaxMap ??= CreateSyntaxMapForEquivalentNodes(oldDeclaration, newDeclaration);

						if (isConstructorWithMemberInitializers)
						{
							processedSymbols.Remove(newSymbol);
						}

						DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);

						// Don't add a separate semantic edit.
						// Updates of data members with initializers and constructors that emit initializers will be aggregated and added later.
						continue;
					}

					ReportMemberBodyUpdateRudeEdits(diagnostics, newDeclaration, diagnosticSpan);

					// updating generic methods and types
					if (InGenericContext(oldSymbol, out var oldIsGenericMethod))
					{
						var rudeEdit = oldIsGenericMethod ? RudeEditKind.GenericMethodTriviaUpdate : RudeEditKind.GenericTypeTriviaUpdate;
						diagnostics.Add(new RudeEditDiagnostic(rudeEdit, diagnosticSpan, newEditNode, new[] { GetDisplayName(newEditNode) }));
						continue;
					}

					var symbolKey = SymbolKey.Create(newSymbol, cancellationToken);
					semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, syntaxMapTree: null,
						IsPartialEdit(oldSymbol, newSymbol, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null));
				}
			}

			if (instanceConstructorEdits != null)
			{
				AddConstructorEdits(
					instanceConstructorEdits,
					editScript.Match,
					oldModel,
					oldCompilation,
					newCompilation,
					processedSymbols,
					capabilities,
					isStatic: false,
					semanticEdits,
					diagnostics,
					cancellationToken);
			}

			if (staticConstructorEdits != null)
			{
				AddConstructorEdits(
					staticConstructorEdits,
					editScript.Match,
					oldModel,
					oldCompilation,
					newCompilation,
					processedSymbols,
					capabilities,
					isStatic: true,
					semanticEdits,
					diagnostics,
					cancellationToken);
			}
		}
		finally
		{
			instanceConstructorEdits?.Free();
			staticConstructorEdits?.Free();
		}

		return semanticEdits.Distinct(SemanticEditInfoComparer.Instance).ToImmutableArray();

		// If the symbol has a single declaring reference use its syntax node for further analysis.
		// Some syntax edits may not be directly associated with the declarations.
		// For example, in VB an update to AsNew clause of a multi-variable field declaration results in update to multiple symbols associated 
		// with the variable declaration. But we need to analyse each symbol's modified identifier separately.
		(SyntaxNode? oldDeclaration, SyntaxNode? newDeclaration) GetSymbolDeclarationNodes(ISymbol? oldSymbol, ISymbol? newSymbol, SyntaxNode? oldNode, SyntaxNode? newNode)
		{
			return (
				(oldSymbol != null && oldSymbol.DeclaringSyntaxReferences.Length == 1) ?
					GetSymbolDeclarationSyntax(oldSymbol.DeclaringSyntaxReferences.Single(), cancellationToken) : oldNode,
				(newSymbol != null && newSymbol.DeclaringSyntaxReferences.Length == 1) ?
					GetSymbolDeclarationSyntax(newSymbol.DeclaringSyntaxReferences.Single(), cancellationToken) : newNode);
		}
	}
1

Akurat kod Linuxa jest wyjątkiem, a nie zasadą. Większość ludzi pisze kod "biznesowy", a nie sam system (pewne uproszczenie). Obie te formy mają inne zastosowania (linux częściej jest używany niż czytany, kod typowej apki jest częściej czytany niż musi byc wydajny) i dlatego inaczej się je ocenia

0

@danek:

a drugi przykład - kod kompilatora? a kod bibliotek?

czyli właściwie co, clean code obowiązuje tylko w backendach krudów, a w narzędziach już nie?

0

@WeiXiao: nie, chodzi mi o to, że różne kryteria oceny kodu powinny być przyjęte w zależności od tego, do czego ów kod ma służyć

0

@WeiXiao

Dlaczego clean code, wydzielanie funkcyjek nie jest tak popularne w prawdziwym sofcie?

Wg mnie to kompletnie nie ma znaczenia z punktu widzenia produktu, pod warunkiem że ten ów produkt działa i zarabia na siebie, bo zwyczajnie to zajmuje czas a czas to pieniądz. Są terminy których nie da się przesunąć to się robi jak się robi, potem zadanie na poprawienie tego spada gdzieś do listy zadań do zrobienia, następnie jeżeli nasz produkt zyskuje na popularności to pojawiają się jakieś nowe funkcjonalności to refaktor starszej części ląduje jeszcze dalej, a potem to jak się nie psuje to nikt tego zwyczajnie nie dotyka i zostają takie rzeczy. Z czasem to zwyczajnie narasta, dodatkowo w projektach które są długofalowe(np telco, często projekty te same po 10+ lat) jest rotacja ludzi i każdy pisze jak pisze. Natomiast z punktu widzenia klienta który otrzymuje gotowy produkt, to co mnie obchodzi, czy ty tam jakieś patterny zrobiłeś czy masz ładne funkcyjki itp? Mnie interesuje tylko to czy zrobisz do zadanego terminu, czy wszystko to co chciałem będzie i czy nie będzie jakiś błędów które będą wpływać na działanie, problemy rozwiąże się karami umownymi a support zleci się jakiemuś SH. Wśród znajomych którzy robią przy low-level to sporo narzeka na jakość różnych sterowników przy których pracują, no ale jak widać jakoś to wszystko działa i na siebie zarabia.

1

@DiabolicalOnion:
wydzielanie funkcji żeby było ładnie i zgodnie z jakimś wymyślonym stylem jest średnio produktywne.
Wydzielenie funkcji bo ta wydzielona będzie jeszcze użyta w wielu miejscach w programie oszczędza czas, a czas to pieniądz

0

Potencjalnie robienie jakiegoś mockupu/rzeczy którą później i tak trzeba napisać od zera. I tak jest to niebezpieczne...

3
DiabolicalOnion napisał(a):

@WeiXiao

Dlaczego clean code, wydzielanie funkcyjek nie jest tak popularne w prawdziwym sofcie?

Bo w popularnym sofcie mało kogo w ogóle obchodzi coś takiego jak utrzymanie kodu w przyszłości. Ludzie mają na to generalnie wywalone i już. I nie ma znaczenia, że siedzą 20 lat w branży.

Dzisiaj natrafiłem na nową klasę, której 50% kodu zostało skopiowane z innego miejsca w projekcie. I to znowu senior pisał. Do tego ten skopiowany kod też jest mega słaby i sonar krzyczy na czerwono.
W ogóle ja dostaję takie PR ostatnio, gdzie sonar krzyczy że jest 100+ code smells z czego kilka krytycznych, kilka bugów i ogólnie masa rzeczy na czerwono i ludziom nie wstyd czegoś takiego na CR dać.
No po prostu niektórzy mają taką psychikę że im wszystko jedno i pewnie jescze dochodzi poczucie, że przecież nikt uber seniora z 20-letnim stażem nie wywali za to, że zrobił copy-paste kilkuset linii i jeszcze nie napisał do tego ani jednej linii dokumentacji / uzasadnienia. Czasem mam uczucie że nie ma różnicy czy się pracuje z seniorami czy studentami. Tzn prawie nie ma, bo studentowi przynajmniej można powiedzieć że robi źle i się nie obrazi jak jakaś primadonna.

BTW: Ile razy w innych branżach spotykacie kogoś z długim stażem, kto robi coś byle jak?

0

@Krolik:

Bo w popularnym sofcie

napisałem prawdziwym :P

Ludzie mają na to generalnie wywalone i już. I nie ma znaczenia, że siedzą 20 lat w branży.

Nie zgadzam sie w 100%. Ja patrzyłem na OSS gdzie nie dosc ze zazwyczaj sa wyzsze standardy niz w jakims korpo czy software house (bo m.in ludzie patrza), to jeszcze zazwyczaj ma sie jakis mniejszy lub wiekszy kontakt z tymi maintainerami; i bynajmniej nie powiedzialbym ze maja wywalone, ale moze jestem naiwny?

0
Krolik napisał(a):

No po prostu niektórzy mają taką psychikę że im wszystko jedno i pewnie jescze dochodzi poczucie, że przecież nikt uber seniora z 20-letnim stażem nie wywali za to, że zrobił copy-paste kilkuset linii i jeszcze nie napisał do tego ani jednej linii dokumentacji / uzasadnienia. Czasem mam uczucie że nie ma różnicy czy się pracuje z seniorami czy studentami.

student tak zrobi bo inaczej nie umie
senior tak zrobi ale wie że można lepiej

2
Krolik napisał(a):

BTW: Ile razy w innych branżach spotykacie kogoś z długim stażem, kto robi coś byle jak?

Za każdym razem, gdy chcę coś kupić, wyleczyć, naprawić samochód. O wykańczaniu mieszkania nie wspominając. :)

obscurity napisał(a):

student tak zrobi bo inaczej nie umie
senior tak zrobi ale wie że można lepiej

To zależy jaki senior.
Są tacy, którzy nie zrobią lepiej, bo wiedzą, że nikt nie płaci za lepiej, tylko za to, żeby spełniało standardy, z którymi nie chce się walczyć.
Ale są tacy, którzy nie zrobią lepiej, bo inaczej nie umieją.

0

@Miang:

Co do organizacji kodu biznesowego to kiedyś słyszałem fajną analogię, że kod powinien być jak dobra rozmowa z kolegą/koleżanką a nie jak monolog.
Rozmowa wygląda tak:

  • słuchaj wczoraj byłem w Kinie i potem poszedłem zjeść coś na miasto
  • o super a na czym byłeś
  • na Diunie
  • i jak podobała Cię się rola xxx

Monolog byłby w stylu:
Wczoraj bylem w kinie, oglądałem tam xxx, i zamówiłem do tego duży popcorn z kolą. Kurcze ten popcorn był mocno przesolony i miałem po nim zgagę. W ogóle kino nie posprzątane i ...

Podział na funkcje służy właśnie temu aby kod opowiadał historię i aby szybko można było dotrzeć do tego co mnie interesuje, czyli np. odbiór filmu a nie opowieść o popcornie.

W praktyce za każdym razem jak mam ochotę dodać jakiś komentarz w kodzie to zastępuję go wydzieleniem funkcji lub zmiennej. Jak mam jakiś bardziej złożony warunek logiczny w if to zastępuję go zmienną. Jak mam np. pattern regexp to zastępuję go stałą itd. Inne typowe rzeczy, które skłaniają mnie do refactoringu to na przykład zagnieżdżanie warunków/pętli, długość kodu, bloki try/catch.

DRY, które wspominasz oczywiście jest bardzo dobrą przesłanką do wydzielenia funkcji czy klasy, ale to tylko jedna z wielu przesłanek. Mi równie mocno zależy na tym abym wracając do kodu mógł szybko i sprawnie go debugować i nie poświęcać wiele czasu na jego ponowne zrozumienie. Wydzielanie funkcji w dzisiejszych IDE to są przecież sekundy, bo praktycznie do każdego typu refaktoryzacji masz automaty + skróty klawiaturowe.

0

Czasem dopuszczam opcję takich długich kawałków kodu.

Kiedyś widziałem prezentację o zmienności kodu. Jeśli kod się nie zmienia to nie musi być jakoś specjalnie czytelny, ważne aby był efektywny i otestowany - to dotyczy zwłaszcza wszelkiej maści utilsów, które po wdrożeniu na produkcję po prostu są i działają. To samo się tyczy też klas - nie jest dla mnie żadnym problemem jakaś klasa utilsowa na pięćset linii. Zamiast pięciu klas typu BusinessUtils1, BusinessUtils2... wolę po prostu *BusinessUtils *i tyle.

0

@hadwao:

Podział na funkcje służy właśnie temu aby kod opowiadał historię i aby szybko można było dotrzeć do tego co mnie interesuje, czyli np. odbiór filmu a nie opowieść o popcornie.

No, i twój przykład pokazuje że bezzasadne rozbijanie funkcyjki jedynie sztucznie wydłuża bez żadnej dodatkowej wartości :D

słuchaj wczoraj byłem w Kinie i potem poszedłem zjeść coś na miasto
o super a na czym byłeś
na Diunie

=>

słuchaj wczoraj byłem w Kinie na Diunie, a potem poszedłem zjeść coś na miasto
i jak podobała Cię się rola xxx

Jedyna "funkcyjka" mniej i od razu mamy efektywniejszą komunikację :)


Mi równie mocno zależy na tym abym wracając do kodu mógł szybko i sprawnie go debugować

Nie ma nic lepszego niż skakanie po np. 5 funkcyjkach, a to nawet z bardzo dobrym IDE, gdzie jednym kliknięciem jestem w implementacji metody, a drugim mogę wyskoczyć do poprzedniego położenia kursora, a co gdybym nie miał fajnego IDE pod ręką, bo byłby to jakiś webowy UI typu GitHub czy może klepał z terminala?

3

No, i twój przykład pokazuje że bezzasadne rozbijanie funkcyjki jedynie sztucznie wydłuża bez żadnej dodatkowej wartości

Yyy... chyba nie zrozumiales. Wolisz

przygotujPopcorn()
wybierzFilm()
Ogladaj()

czy

kupZiarenkaKukurydzy()
nastawMikrofalowke()
otworzMikrofalowke()
wlozPaczkeZZiarenkamiKukurydzyDoMikrofalowki()
zamknijMikrofalowke()
wlaczMikrofalowke()
zaczekajAzBedzieGotowe()
wylaczMikrofalowke()
wyjmijPopcorn()
zamknijMikrofalowke()
usiadzPrzedTelewizorem()

?

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