Skrivet av Ingetledigtnamn:
Jag har uppfattat det som att du, när du ser en rad av funktionsanrop (a()->b()->c()->d()), reflexmässigt slår bakut och säger message chaining.
Stämmer, en typ av kod jag brukar söka efter för att tolka problem i kod. Ser jag sådant tittar jag lite mer för att utesluta eventuella misstag från min sida (är medveten om att ingen utvecklare gillar att skriva om kod). Men sedan säger jag till. Det är ett grovt fel av utvecklare att skriva sådan kod.
Skrivet av Ingetledigtnamn:
I många fall har du rätt i att resultatet av detta blir coupling, men det är inte så att en rad av anrop automatiskt innebär den skadliga coupling vi talar om här. Jag försökte påvisa att det inte är de kedjade anropen i sig som skapar coupling utan att det faktum att koden kommer bero på att de olika anropen returnerar ett resultat av en viss typ. Om a() returnerar något som inte har en b-metod kommer det inte kompilera. Om du bryter kedjan till enstaka anrop och stoppar resultaten i temporärer kommer du få precis samma beroenden. Dessa kommer ställa till det vid refactoring även utan att anropen är kedjade.
Coupling är så stort så det är egentligen värt en en eller kanske till och med flera trådar i sig.
Men för att ta en ganska simpel sak och då koppla det till rust. Om en utvecklare skriver ett objekt med kanske 5 medlemmsvariabler. Ibland måste de här variablerna tilldelas med en viss ordning. Hur vet andra programmerare det?
Annat problem med att kjedja anrop så här är att man MÅSTE läsa koden. Personligen kräks jag på sådant. Det går inte att skriva kod utan att veta om hur objekten fungerar internt.
Skillnaden mellan två team där ena teamet följer fasta mönster som gör att de slipper läsa andras kod, jämför dem med annat team där koden är skriven för att läsas. Det är extremt stor skillnad och det första teamet kan skriva enorma mängder utan att tappa fart. Detta medan teamet där koden måste lösas, där tar det stop och de får kämpa med att komma ihåg allt.
Fördelen med att kedja är att det ser snyggt ut och det är lätt att förklara. Skulle du och jag lära ut en skolklass i programmering, du beskriver att kedja och jag försöker förklara problemen. Om det är 30 elever skulle du lätt få med dig 29, kan jag övertyga 1 så är jag glad.
Svåra saker är svåra att lära ut, saknas det något att relatera till. Bakgrunder till varför man löser saker på olika sätt är det i princip omöjligt.
Det är precis som diskussionen vi har här (ursäkta skrytet), vissa saker är otroligt bra tips men få nappar och det beror ofta på att man inte stött på problematiken eller har svårt att relatera till annat som man gjort.
Skrivet av Ingetledigtnamn:
I pipeline-fallet är map, reduce, filter och andra funktioner metoder i container-klassen. De är designade för att man skall kunna göra just pipelining. Jag måste säga att
let evens: Vec<_> = vec![1, 2, 3, 4].into_iter().filter(|x| x % 2 == 0).collect();
är lite elegantare än
std::vector<int> nums = {1, 2, 3, 4};
std::vector<int> evens;
std::copy_if(nums.begin(), nums.end(), std::back_inserter(evens),
[](int x) { return x % 2 == 0; });
Argumentet "elegant" har i min bok väldigt låg prioritet. Skriver jag kod så skrivs den inte för att läsas, har jag lyckats så kommer koden glömmas bort, ingen kommer någonsin att läsa den koden igen. Så bra är sällan att man lyckas så den dagen koden läses igen så brukar det handla om att det är fel i koden och därför skrivs kod så den är lätt att hitta fel i.
Koden du visar, där är båda exemplen dåliga. Det tar längre tid för någon annan utvecklare att läsa och förstå koden än vad det tog för utvecklaren att skriva koden. Det är också typiska exempel som så ofta används men som inte är realistiska. Normal kod har i princip alltid så mycket mer att hantera än filtrera en list så den tar ut jämna tal.
Det är det svåraste i koden som lösningar anpassas efter. Lätt kod kan i princip skrivas hur som helst, det är nästan svårt att skriva lätt kod så den inte går att förstå.
Samtidigt så reagera jag på annat, du skriver variabkerna nums och evens. Är det för jobbigt att skriva ut hela namn?
För varje förkortning du skriver gör du det svårare för andra utvecklare att läsa din kod.
*tydligare kod*
std::vector<int> vectorNumber = {1, 2, 3, 4};
std::vector<int> vectorEvenNumber;
std::copy_if(vectorNumber.begin(), vectorNumber.end(), std::back_inserter(vectorEvenNumber),
[](int iNumber) { return (iNumber % 2) == 0; });
Skrivet av Ingetledigtnamn:
Kallar du detta coupling? Visst, det förutsätter att map och filter returnerar en container men det är ett designbeslut man har tagit för att kunna pipelining. Skulle det ställa till det om du vill ändra på map och filter, jajamänsan, men du kommer med största sannolikhet inte behöva göra refactoring på container-klasserna. Detta är kedjade anrop som inte ger coupling, alla beroenden är inom container-klassen.
Det där var INTE coupling, du har en lösning för att räkna ut något lokalt och allt är temporärt (alltså ingen state eller liknande inblandat). Utvecklaren har all information den behöver och det är ok då. Enda kritiken på den typen av lösning är att det kan vara svårläst beroende på hur lång kedjan är och då också svår att debugga. Vad sådan kod normalt behöver är bra kommentarer.
Skrivet av Ingetledigtnamn:
I builder-fallet ser jag inte som en begränsning att returnera self från "del-konstruktorerna". Ok, koden beror på att man gör det, men så gör man i Rust. Coupling, njäe. Förresten, är det inte analogt med att en constructor i C++ implicit returnerar objektet du just initierat. Tycker du det är en begränsning att du inte kan returnera en annan typ från en constructor?
Håller med om att just coupling är svagare men däremot att logik för hur objektet skapas sprids i kod och den typen av kod är svårare att refaktorera. Flexibilitet och reglerna kapslas inte in i objektet utan att utvecklare måste lära sig hur objektet fungerar internt, det är inte bra.
Skrivet av Ingetledigtnamn:
Kör du inte själv med snarlika tekniker där du delegerar till common() att sköta delar av constructor-jobbet? I Rust behöver du inte göra allt i en funktion utan kan bryta upp det i mindre, naturliga bitar.
Om jag eller enligt mig så bra kod för en klass, då är det helt ok att göra det interna jobbet komplext, det gör inget så länge det inte exponeras utåt. Sköter klassen en sak så är det ok. Är det svår kod så dokumentera. Det viktiga är inte att skriva snygg kod i en klass utan som jag skrev ovan, optimalt är att ingen någonsin skall behöva läsa koden i klassen igen.