https://fgheysels.github.io/
Volgens mij returned ShowModal een enum, die aangeeft wat het 'Modalresult' was. In Delphi kan dit dus mrOk, mrCancel, mrYes, mrNo, zijn als ik het me nog goed herinner..oisyn schreef op maandag 17 januari 2011 @ 14:05:
Wat is daar mis mee dan? Je laat eerst een modal dialog zien, en als die call returnt dan kun je kijken of er op Ok of op Cancel gedrukt was. Zo zou het in win32 iig werken, geen idee hoe de Delphi API daarmee omgaat.
Idem in .NET: ShowDialog() returned ook een DialogResult enum waarde.
/spuit-elvendertig dus
https://fgheysels.github.io/
Hoezo niet?niet meer makkelijk returnen uit een inner loop oid.
Mwah, juist het toevoegen van de extra methodes verhoogt de leesbaarheid. Wanneer je alles laat staan kun je op detail niveau heel goed zien wat er gebeurt, maar het hogere niveau is nauwelijks terug te vinden.CodeCaster schreef op maandag 17 januari 2011 @ 14:03:
Zeggen dat vijftien regels lang is voor een functie, zonder dat je weet wat 'ie doet? Zeker importfunctionaliteit (wat je al snel hebt als je met bestanden werkt) kun je soms beter imperatief/procedureel doen dan strikt OO waarbij je voor elk lullig, éénmalig voorkomend taakje een nieuwe procedure of zelfs klasse mag schrijven, dan blijft het nog overzichtelijk doordat alle acties die met de data worden uitgevoerd op één plek staan.
Wanneer pieppiep zelf al aangeeft dat de code te partitioneren is (alinea's toevoegen) geeft al aan dat de methode uit verschillende stappen bestaat. In 99.9% van de gevallen kun je die delen weer in aparte methoden onderbrengen. Hierdoor is het overzicht van de originele methode beter. Je hebt immers geen 53 regels meer nodig om te zien wat er gedaan wordt. Daarnaast bieden de nieuw geextraheerde methoden een ideale plek om op de standaard plek (voor de methode) commentaar te geven op wat daar gebeurt (en bij een fatsoenlijke IDE krijg je die terug wanneer je de informatie opvraagt bij die methode)
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Dat ik eerder uit automatisme eigenlijk alles compleet threadsave maakte terwijl dat eigenlijk vaak helemaal niet nodig is omdat het object toch maar binnen 1 thread bruikbaar was. Niet dat alles nu een classmember wordt, maar ik merk dat het prettiger werkt wanneer niet alle methoden een berg parameters hebben omdat ik eigenlijk overal de state aan het meegeven ben die eigenlijk ook wel in het object zelf zou kunnen.whoami schreef op maandag 17 januari 2011 @ 14:23:
[...]
wat ?
Bedoel je dat je je local variables 'promoveert' tot class-members ?
Als voorbeeld had ik een SAX parser waar ik op een gegeven moment in de abstracte parser implementatie zelf de tag stack bij ging houden ipv het bij de methoden mee te geven en enkele utilility methoden rechtstreeks op die stack te laten wereken ipv methoden maken die eigenlijk ook wel static konden worden omdat ze toch niks met het object zelf deden.
AbstractSaxParser.java
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Sorry, ik ben het hier echt totaal niet mee eens.Janoz schreef op maandag 17 januari 2011 @ 14:37:
[...]
Hoezo niet?
[...]
Mwah, juist het toevoegen van de extra methodes verhoogt de leesbaarheid. Wanneer je alles laat staan kun je op detail niveau heel goed zien wat er gebeurt, maar het hogere niveau is nauwelijks terug te vinden.
Wanneer pieppiep zelf al aangeeft dat de code te partitioneren is (alinea's toevoegen) geeft al aan dat de methode uit verschillende stappen bestaat. In 99.9% van de gevallen kun je die delen weer in aparte methoden onderbrengen. Hierdoor is het overzicht van de originele methode beter. Je hebt immers geen 53 regels meer nodig om te zien wat er gedaan wordt. Daarnaast bieden de nieuw geextraheerde methoden een ideale plek om op de standaard plek (voor de methode) commentaar te geven op wat daar gebeurt (en bij een fatsoenlijke IDE krijg je die terug wanneer je de informatie opvraagt bij die methode)
Dat voorbeeld van extract till you drop vind ik ook vreselijk om te zien. Het eerste stukje code was gewoon goed leesbaar en het laatste is een hoop kleine dingetjes die gezamelijk 'iets' doen.
Een fatsoenlijke IDE laat zien wat een functie doet als je met je muis erover gaat maar dat wil ik helemaal niet doen als het niet nodig is. In dit extract till you drop moet ik dus telkens met de muis over de code gaan of zelf over het scherm zoeken waar de functie staat en die bekijken of de functienaam moet duidelijk zijn. Als je teveel functies gaat creeren vind ik zelf de namen over het algemeen steeds slechter worden, je kan er mijns inziens beter minder hebben met duidelijkere namen.
Verder is 1 functie van 50 regels die in 5 groepjes van 10 regels met in die 10 regels 5 regels code en 5 regels commentaar veel beter de volgorde te zien.
486DX2-50 16MB ECC RAM 4x 500MB Drive array 1.44MB FDD MS-Dos 6.22
Vaak maak ik ergens pas een functie van op het moment dat het een herbruikbare unit is, dus als ik hem op een 2e plek nodig heb of dat de control flow anders te complex wordt. 85 regels zonder control flow kan veel overzichtelijker zijn dan diezelfde code over 10 functies verspreid.
[ Voor 9% gewijzigd door MBV op 17-01-2011 15:19 ]
Verder over commentaar. In principe zou je zo min mogelijk commentaar moeten gebruiken*. Het grote probleem met commentaar is dat het niet door de compiler wordt gecompileerd, niet door de unittests wordt getest en niet door automatische refactoring meegenomen wordt. Het is een handmatige actie om het commentaar in lijn met de code te houden en niks is schadelijker voor de onderhoudbaarheid wanneer het commentaar niet overeenkomt met de uiteindelijke werking.
Als je evenveel commentaar als code nodig hebt om je code leesbaar te houden is er IMHO toch echt iets mis aan code. Daarnaast zul je eerst de afzonderlijke blokjes van 10 regels moeten bekijken wat er precies in dat blok gebeurt voordat je een uitspraak kunt doen over wat de hele methode doet.Verder is 1 functie van 50 regels die in 5 groepjes van 10 regels met in die 10 regels 5 regels code en 5 regels commentaar veel beter de volgorde te zien.
* let op, ik zeg zo min mogelijk.Een beetje zoals Einstein ooit zei: "Make things as simple as possible, but not simpler"
Het doel is niet om je class/module zo kort mogelijk te houden. Het doel is om een enkele functie zo klein mogelijk te houden. Zolang je naamgeving duidelijk is is vervolgens elke functie goed leesbaar zonder dat je de functies zelf weer erbij hoeft te pakken. Maar mocht je ze nodig hebben, met een beetje fatsoenlijke IDE spring je met CTRL-click er heen en met back weer terug naar je originele functie.MBV schreef op maandag 17 januari 2011 @ 15:18:
Vergeet ook niet dat een functie weer 4 regels ruimte kost: 1 witregel, 1 regel met functiedeclaratie, 2 regels met accolades. Daarnaast nog wat commentaar natuurlijk. Er past dus minder effectieve code op je scherm, waardoor je het overzicht eerder kwijt raakt.
Ik niet. Een verzameling statements die eigenlijk 1 actie uitvoert refactor ik vaak al naar een losse methode met als naam die actie. (Maar eigenlijk ontwikkel ik vaak wat meer topdown waarbij ik mijn hoofd algoritme uittik met nog niet bestaande methoden die ik later implementeer)Vaak maak ik ergens pas een functie van op het moment dat het een herbruikbare unit is, dus als ik hem op een 2e plek nodig heb of dat de control flow anders te complex wordt. 85 regels zonder control flow kan veel overzichtelijker zijn dan diezelfde code over 10 functies verspreid.
Ik ben erg benieuwd naar je lap code van 85 regels die niet overzichtelijker zou kunnen.
[ Voor 46% gewijzigd door Janoz op 17-01-2011 15:29 ]
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Ook vind ik vaak het automatisch gegenereerde commentaar onduidelijk en dus overbodig.
Interface naam is IAction en functie heet Execute dan krijg je 'Executes the action.'
Maar een idee van een lange functie.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| void StaOpEnGaNaarDeBus()
{
ZetDeWekkerUit();
StapUitBed();
DoeKledingAan();
GaNaarBeneden();
ZetComputerAan();
SchenkMelkIn();
LogComputerIn();
MaakBoterhamKlaar();
OpenTweakersSite();
while (MelkNietOp && BroodNietOp)
{
NeemHapOfSlok();
LeesNieuwsOfForum();
}
ZetComputerUit();
DoeJasAan();
GaNaarBuitenEnLoopNaarBus();
} |
Ik zie hier niet zo snel dingen die je als groepje in een extra functie kan plaatsen.
486DX2-50 16MB ECC RAM 4x 500MB Drive array 1.44MB FDD MS-Dos 6.22
Dit is dan wel weer een goed moment om te kijken of je een soort XML config file nodig heb met plugins en interfaces.PiepPiep schreef op maandag 17 januari 2011 @ 15:53:
Zo min mogelijk commentaar vind ik een mooie uitspraak, als de code zelf al leesbaar is hoeft er idd geen commentaar bij.
Ook vind ik vaak het automatisch gegenereerde commentaar onduidelijk en dus overbodig.
Interface naam is IAction en functie heet Execute dan krijg je 'Executes the action.'
Maar een idee van een lange functie.
code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20void StaOpEnGaNaarDeBus() { ZetDeWekkerUit(); StapUitBed(); DoeKledingAan(); GaNaarBeneden(); ZetComputerAan(); SchenkMelkIn(); LogComputerIn(); MaakBoterhamKlaar(); OpenTweakersSite(); while (MelkNietOp && BroodNietOp) { NeemHapOfSlok(); LeesNieuwsOfForum(); } ZetComputerUit(); DoeJasAan(); GaNaarBuitenEnLoopNaarBus(); }
Ik zie hier niet zo snel dingen die je als groepje in een extra functie kan plaatsen.
Voor mij mag een functie een regel of 40-50 zijn, das ongeveer een scherm vol. Dat moet uit ongeveer 3-4 alinea's bestaan en eentje voor fout afhandeling. Zodra daaronder/in de buurt een functie komt met een zelfde alinea dan moet dat in een functie, anders niet blijf je bezig en krijg je van de vreselijke 1-3 regel code functies die niet handig te debuggen zijn.
Je moet ook niet zozeer naar het aantal regels kijken als wel naar hoeveel taken een methode uitvoert.
Een methode zou in principe maar één taak uit moeten voeren waarbij die taak de ene keer iets meer omvat dan de andere. En dan kun je dingen die je vaak doet (database-connectie) wel extracten.
Even een losse tip: Ik gebruik voor Visual Studio de extensie VS10x Code Map.
Hiermee heb ik in een sidebar een mooi overzicht van mijn properties, methods en events etc. mooi gegroepeerd per zelf aangegeven region.
486DX2-50 16MB ECC RAM 4x 500MB Drive array 1.44MB FDD MS-Dos 6.22
Volgens mij ben ik het compleet met jou eens voor wat betreft het maken van code, zeker ook Clean Code gelezen?Janoz schreef op maandag 17 januari 2011 @ 15:21:
...verhaal...
Ik snap echter niet waarom je dit hebt gemaakt:
1
2
3
4
5
6
7
8
9
10
11
| protected List<String> getStackTail(int offset) { return stack.subList(offset, stack.size()); } protected String getNodeName() { return stack.getLast(); } protected boolean isStackSize(int i) { return i == stack.size(); } |
Dit lijken gewoon wrappers van bestaande functionaliteit. Dat vind ik dan weer iets minder mooi. getStackTail zou nog kunnen, maar de andere 2 lijken mij op het eerste gezicht overbodig.
Battle.net - Jandev#2601 / XBOX: VriesDeJ
1
| protected String NodeName { get { return stack.getLast(); } } |
stack is private en dit zijn de enige 3 bewerkingen die ik nodig had, en ja, ik heb ook Clean Code gelezen. Ga binnenkort weer 10 boeken weggeven tijdens een workshop waarmee ik mijzelf member of the CLUB maakJan_V schreef op maandag 17 januari 2011 @ 16:09:
[...]
Volgens mij ben ik het compleet met jou eens voor wat betreft het maken van code, zeker ook Clean Code gelezen?
Ik snap echter niet waarom je dit hebt gemaakt:
Java:
1 2 3 4 5 6 7 8 9 10 11 protected List<String> getStackTail(int offset) { return stack.subList(offset, stack.size()); } protected String getNodeName() { return stack.getLast(); } protected boolean isStackSize(int i) { return i == stack.size(); }
Dit lijken gewoon wrappers van bestaande functionaliteit. Dat vind ik dan weer iets minder mooi. getStackTail zou nog kunnen, maar de andere 2 lijken mij op het eerste gezicht overbodig.
Java kent geen properties, maar enkel getters en setters. Je C# voorbeeld is dus equivalent (en wanneer ik C# gedaan zou hebben zou ik het inderdaad ook op jou manier doen).Davio schreef op maandag 17 januari 2011 @ 16:13:
Ik zou van de tweede dan sowieso een property maken, kan dat ook in Java?
C#:
1 protected String NodeName { get { return stack.getLast(); } }
[ Voor 24% gewijzigd door Janoz op 17-01-2011 16:27 ]
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Het probleem met deze code is dat hij beweert 'sta op en ga naar bus' doet, terwijl er voornamelijk gegeten, gedronken en nieuwsgelezen wordt. Daarnaast zou ik 'zet wekker uit' eerder thuis vinden horen bij de trigger die je wakker maakt en dus eigenlijk een abstractie niveau hoger moeten zittenPiepPiep schreef op maandag 17 januari 2011 @ 15:53:
code:
1 2 3 4void StaOpEnGaNaarDeBus() { // .. }
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Ik ben 'nu' clean code aan het lezen. Veel informatie en goede voorbeelden om over je code na te denken. Nu de rest hier nog meekrijgen.Janoz schreef op maandag 17 januari 2011 @ 16:19:
[...]
stack is private en dit zijn de enige 3 bewerkingen die ik nodig had, en ja, ik heb ook Clean Code gelezen. Ga binnenkort weer 10 boeken weggeven tijdens een workshop waarmee ik mijzelf member of the CLUB maak
[...]
Java kent geen properties, maar enkel getters en setters. Je C# voorbeeld is dus equivalent (en wanneer ik C# gedaan zou hebben zou ik het inderdaad ook op jou manier doen).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| void foo() { // wat code for (int i = 0; i < num; i++) { if (conditie1(i)) return; if (conditie2(i) break; // wat andere code } } |
Als je de inhoud van de for loop verhuist naar een eigen functie, dan heb je dus een returnvalue met 3 mogelijke waarden, waar je dan weer op moet controleren om te bepalen of je moet breaken of returnen oid. Het wordt er vaak ook niet leesbaarder op ofzo, dus dan kun je je afvragen waarom je sowieso nou per se zo nodig de boel wilt opdelen omdat iemand heeft bepaald dat X regels nou eenmaal te lang is.
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
486DX2-50 16MB ECC RAM 4x 500MB Drive array 1.44MB FDD MS-Dos 6.22
Ah op die manier, maar als je de initialisatie code extract, en wat andere code extract dan zit (conditie codes zijn al extracted) dan zit je nog steeds ruim onder de 15
Maar ik moet zeggen dat ik een dergelijke constructie niet veel gebruik, maar dat zal ook wel komen omdat we compleet verschillende typen software ontwikkelen.
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Wat ík bedoelde is dat je een class maakt voor het opslaan van de local state, zodat je niet 15 argumenten mee hoeft te geven aan een functie, en dat een functie dan ook (zeker in het geval van Java) die argumenten aan kan passen.whoami schreef op maandag 17 januari 2011 @ 14:23:
[...]
wat ?
Bedoel je dat je je local variables 'promoveert' tot class-members ?
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
1.Code moet duidelijk zijn;
2.Het moet onderhoudbaar zijn;
3.Wanneer een methode groot wordt: Splits deze op maar laat het de duidelijkheid niet in de weg zitten. dwz. Als je het op kan splitsen maar het voegt alleen complexiteit toe qua onderhoud omdat het minder duidelijk is. Doe het dan niet. (of zorg ervoor dat het wel duidelijk is natuurlijk
You know, I used to think it was awful that life was so unfair. Then I thought, wouldn't it be much worse if life were fair, and all the terrible things that happen to us come because we actually deserve them?
Aanrader? Ook als je Code Complete 2 hebt?
@avalaxy
Ik heb code complete nog niet gelezen, maar als ik dit mag geloven is Clean Code zeker een aanvulling. Daarnaast kost het boek nog geen E19 op amazon, dus om de prijs hoef je het ook niet te laten.
[ Voor 68% gewijzigd door Janoz op 17-01-2011 16:53 ]
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Dat is heel simpel: als er meer dan 2 regels in staat, kan je het opsplitsen in 2 functies. Ik zou hem andersom willen stellen: mijn doel is een functie zo groot mogelijk te maken, zodat hij nog altijd 1 coherent ding doet, met een maximum van 50 regels (ongeveer 1 scherm). Het hangt heel erg af van het soort project hoe groot mijn functies worden, vaak maar een paar regels, maar er zitten altijd een paar functies tussen van 50 regels. Bijv een functie om de argumenten te verwerken, command-line help weer te geven, dat soort dingen. Ik zal misschien eens de LOC-statistieken van mijn afstudeerproject (parser met visitor-pattern, dus niet helemaal representatief) opzoeken, kijken hoe lang de langste functie was.Janoz schreef op maandag 17 januari 2011 @ 15:21:
Het doel is niet om je class/module zo kort mogelijk te houden. Het doel is om een enkele functie zo klein mogelijk te houden.
Dus als jij een functie hebt van 50 statements die 1 actie uitvoeren, dan refactor je die naar een functie van 50 statements die 1 actie uitvoeren. Leg uit, hoe doe je dat?Ik niet. Een verzameling statements die eigenlijk 1 actie uitvoert refactor ik vaak al naar een losse methode met als naam die actie.
Ik heb even geen voorbeeld dat ik hier neer wil dumpen, maar laatst had ik een python-script dat 30 argumenten verwerkte. application.py had dus een switch/case van 100 regels waarin wat booleans werden aan/uit gezet (elke case had hooguit 2 regels, bij meer regels had ik er een functie van gemaakt). Betere suggesties?Ik ben erg benieuwd naar je lap code van 85 regels die niet overzichtelijker zou kunnen.
1
2
3
4
| def handleArgs(argsList) if (argName == 'first'): handleArgFirst(head(argsList)) handleArgsNotFirst(tail(args)) |
Zoiets dus?
[ Voor 5% gewijzigd door MBV op 17-01-2011 17:00 ]
Dus je gaat members toevoegen aan een class die alleen maar zinnig zijn tijdens een bepaalde method call?Janoz schreef op maandag 17 januari 2011 @ 16:42:
Dus eigenlijk een soort structs? Dat doe ik niet. Ik doe eerder wat whoami aangeeft.
Dus niet dat je bijv. een inner class maakt met de betreffende state en de methods die hebt geëxtract dan members van die inner class maakt dan he.
[ Voor 20% gewijzigd door .oisyn op 17-01-2011 17:11 ]
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Bedoel je dan enkele argumenten met 30 mogelijke waardes? Of daadwerkelijk 30 argumenten? In dat laatste geval is de lengte van je functie niet verwonderlijk, maar ook niet het grootste probleemMBV schreef op maandag 17 januari 2011 @ 16:56:
Ik heb even geen voorbeeld dat ik hier neer wil dumpen, maar laatst had ik een python-script dat 30 argumenten verwerkte.
Zelf hou ik ~ 15 regels per functie aan als ideaal maximum. Inclusief comments moet het makkelijk op een scherm kunnen, en een class probeer ik binnen 150 regels te houden. Ga ik daar overheen is het bijna altijd wel mogelijk om dingen te refactoren. Juist omdat je gedurende de levensduur van je applicatie nog wel dingen erbij wilt zetten, bugfixes wilt implementeren, etc. Een OS project waar ik aan werk heeft classes van 12.000 regels ertussen zitten, zelfs de meest simpele bugfix kost me daar uren aan uitzoekwerk omdat alles weer linkt naar alles en een fix op de ene plek kan vanalles stuk maken op een compleet andere plek.
Overigens hoeft het gebruik van korte functies niet te betekenen dat je ook veel class variabelen gebruikt, of ellenlange parameterlijsten. Als je echt veel verschillende informatie van een functie ook in meerdere andere functies wilt hebben kun je daar prima objecten / structs / andere taal-specifieke wrappers voor gebruiken. En die kun je juist weer makkelijk refactoren zodat je code er alleen maar beter leesbaar door wordt, in plaats van vijftien verschillende variabelen continue gebruiken in een functie van honderd regels.
[ Voor 20% gewijzigd door FragFrog op 17-01-2011 17:14 ]
In een methode van >50 statements zou ik dan inderdaad beginnen om die 50 statements te extracten naar een nieuwe method. Daarna verwacht ik dat ik die 50 regels nog wel verder op kan delen.MBV schreef op maandag 17 januari 2011 @ 16:56:
Dus als jij een functie hebt van 50 statements die 1 actie uitvoeren, dan refactor je die naar een functie van 50 statements die 1 actie uitvoeren. Leg uit, hoe doe je dat?
In dat geval zou ik zeker Clean Code eens lezen aangezien een argumenten parser daar 1 van de case studies is.Ik heb even geen voorbeeld dat ik hier neer wil dumpen, maar laatst had ik een python-script dat 30 argumenten verwerkte. application.py had dus een switch/case van 100 regels waarin wat booleans werden aan/uit gezet (elke case had hooguit 2 regels, bij meer regels had ik er een functie van gemaakt). Betere suggesties?
Python:
1 2 3 4 def handleArgs(argsList) if (argName == 'first'): handleArgFirst(head(argsList)) handleArgsNotFirst(tail(args))
Zoiets dus?
In het kort, ja, het kan korter en netter.
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Niet bij een bepaalde method call, wel bij bepaalde method calls..oisyn schreef op maandag 17 januari 2011 @ 16:59:
Dus je gaat members toevoegen aan een class die alleen maar zinnig zijn tijdens een bepaalde method call?
Dus niet dat je bijv. een inner class maakt met de betreffende state en de methods die hebt geëxtract dan members van die inner class maakt dan he.
Kijk, ik ga niet de 'je moet zo min mogelijk parameter gebruiken'-regel oplossen door de 6 parameters die mijn methode nodig heeft te wrappen in een class en die meegeven. Kijk, maar 1 parameter \o/. Ik kijk eerder of ik ze alle 6 wel nodig heb en of het echt parameters zouden zijn. Uiteindelijk blijkt dan vaak dat enkele van die parameters eigenlijk gewoon state zijn die je overal in je class gebruikt. Maar ik vind het lastig om hier zo over te discussiëren zonder concrete voorbeelden omdat je dan nogal snel tegen terminologie verschillen aanloopt.
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
[ Voor 13% gewijzigd door alienfruit op 17-01-2011 23:33 ]
Dat zoiets kan bestaan is al onbegrijpelijk!alienfruit schreef op maandag 17 januari 2011 @ 23:24:
Ach, ik heb hier een klasse rondslingeren van 1760 regels aan code. Een echte God class, die de flow van een systeem managed. Met vier interne state machines die afhankelijk zijn van externe processen (distributed media ontvangst over GPRS, CCA/CCR over SOAP, webservice-based event logging). Zonder unit tests, end-to-end testing. Een prima kandidaat om te refactoren als er tijd en geld voor was...
Niet zelf geschreven trouwens
Ik ben al blij dat het stabiel werkt...Caelorum schreef op maandag 17 januari 2011 @ 23:42:
Dat zoiets kan bestaan is al onbegrijpelijk!
Dat valt wel mee, hoor.Caelorum schreef op maandag 17 januari 2011 @ 23:42:
[...]
Dat zoiets kan bestaan is al onbegrijpelijk!
[ Voor 13% gewijzigd door CodeCaster op 18-01-2011 11:14 ]
https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...
Elke keer als het product bijna af is, zou er een nieuwe taal / methodiek uitkomen die helemaal hip is en wordt alles opnieuw geschreven.
How many programmers does it take to change a lightbulb?
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
In dit boek het boek Working Effectively with Legacy Code (http://www.amazon.com/Wor...el-Feathers/dp/0131177052) wordt uitgelegd hoe je zoiets 'heel eenvoudig' kunt doen.MBV schreef op dinsdag 18 januari 2011 @ 10:59:
@alienfruit: Ik heb hier een klasse van 15000 regels in de CPP, de headerfile is 2000 regels. Een soort mother-of-all-factories, maar dan met nog 'een paar' functies die handig zijn om overal bij de hand te hebbenUnittesten is bijna onmogelijk, omdat bijna alle klasses afhankelijk zijn van die klasse, en daardoor de hele wereld wordt geinclude.
Niet zelf geschreven trouwens
Tijdens het lezen vond ik de theorie op zich wel leuk, maar inderdaad, met zo'n enorme klasse zie ik dat in de praktijk niet snel gebeuren. Dat is het nadeel vaak, in theorie weet je vaak precies wel hoe het moet, maar ja, de praktijk is altijd anders.
Battle.net - Jandev#2601 / XBOX: VriesDeJ
Waar gewerkt wordt op basis van betaalde uren, i.p.v onbeperkte tijd die ook nog eens niks kost, is dat helemaal niet zo heel raar hoor.Caelorum schreef op maandag 17 januari 2011 @ 23:42:
[...]
Dat zoiets kan bestaan is al onbegrijpelijk!
Soms heb je tijd om iets helemaal tot op het bot mooi te maken, maar soms moet het gewoon af in xx uur omdat er gewoon niet meer geld is. En ja dan kun je zeggen dat je het niet zo willen doen, maar dat vind de instantie die mijn hypotheek maandelijks afschrijft niet zo'n heel goed idee. Principe's zijn leuk, maar je verdient er zo weinig aan of mee
Driving a cadillac in a fool's parade.
Ik herken het wel. Ik zit nu op een project waar toch wel best wat mis is. Maar wanneer ik dat met een beetje scouting insteek benader maak ik het bij elke fix of uitbreiding telkens weer een stukje netter. Paar unittestjes erbij, stukje refactoren. Dat kost echt geen extra tijd. Die tests zul je toch wel moeten schrijven (zeker met brakke software kun je niet zonder tests om voor jezelf te bewijzen dat de boel blijft werken). Dat kost je misschien een half uurtje, maar die win je direct terug wanneer je je aanpassing doet omdat je je test kunt blijven gebruiken.
Er is maar 1 iemand die echt verantwoordelijk is voor het brak blijven van de software waar je aan moet werken, en dat ben je toch echt zelf. Verkondigen dat je er de tijd niet voor krijgt is een zwak excuus.
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Ik kan me voorstellen dat sommigen het niet 'durven' te doen, omdat ze niet volledig bekend zijn met de materie. Die wel ongeveer weten hoe iets verbeterd moet worden, maar niet precies. Wanneer je dan iets gaat refactoren ben je vaak al veel te snel en te diep bezig. Dan wil je het al snel te groot aanpakken.
Moet toegeven dat ik ook geen grote wijzigingen durf te maken bij m'n nieuwe werkgever. Wil de theorie eerst uitproberen op iets kleinere projecten (en wat credits opbouwen), voordat ik grote veranderingen ga toepassen.
Kan uiteraard nooit kwaad om een unittest project te bouwen. Hoeft niet eens een unit testen te bevatten, kan ook prima met scenario testen (in het begin), omdat je anders overal al snel DI moet gaan toepassen of allemaal gebinde objecten moet gaan mocken op een of andere manier.
Battle.net - Jandev#2601 / XBOX: VriesDeJ
Bij een bug is het nog makkelijker. Over het algemeen ben je best wat tijd kwijt bij het zoeken naar de bug. Hier is het juist nog veel waardevoller om dat middels unittests te doen. Uiteindelijk is het dan de bedoeling dat je een unittest weet te maken die faalt vanwege juist die bug. Daarna kun je de bug fixen en kun je met die unittest aantonen dat het nu wel werkt. En ook hier krijg je een steeds hogere testdekking waardoor je op een gegeven moment eens aan wat kleine refactordingetjes kunt denken.
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Verwijderd
Hiervoor was het inderdaad ook functies met enorme lappe code erin die wel 50 dingen deden en er werd veel gecopy/paste, drama om te debuggen/onderhouden. Heerlijk nu die korte functies icm. een goede IDE.
Wat is er zo speciaal aan main()Verwijderd schreef op dinsdag 18 januari 2011 @ 22:44:
Wat vinden jullie de maximale lengte van een C/C++ main functie? Hier wordt vooral ingegaan op member functies e.d. Maar in C++ (en al helemaal in C) moet je toch echt de main functie ook dingen laten doen...
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Lekker praktische insteek ook. Als de klant de aanpassing over een week moet hebben ga jij em dan uitleggen dat dat niet kan omdat je ergens anders code zit aan te passen? Zal wel overheidswerk zijn dan ofzo want mijn klanten zitten daar echt niet op te wachten.Verkondigen dat je er de tijd niet voor krijgt is een zwak excuus
Moet je ook nog gaan uitleggen dat die code eigenlijk bagger is, gaat die kant nog harder steigeren omdat het dus de eerste keer ook al brak was.
De lengte van een functie is echt de moeite van de discussie niet waard eigenlijk : een functie is zo lang als tie moet zijn en niet langer maar ook niet korter.
Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.
Als jij om wat voor reden dan ook vijftig forms wil spawnen in je main()-procedure dan doe je dat toch lekker.Verwijderd schreef op dinsdag 18 januari 2011 @ 22:44:
Wat vinden jullie de maximale lengte van een C/C++ main functie? Hier wordt vooral ingegaan op member functies e.d. Maar in C++ (en al helemaal in C) moet je toch echt de main functie ook dingen laten doen...
Of die code niet gegroepeerd kan worden in logisch genaamde procedures is een tweede vraag.
[ Voor 9% gewijzigd door CodeCaster op 18-01-2011 23:54 ]
https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...
Verwijderd
Nou, zeker in C, gebeurd daar vaak nog wel het een en ander. Ook op embedded platformen, daar is het ook inefficienter om overal een functie aanroep te doen.
[ Voor 13% gewijzigd door .oisyn op 19-01-2011 02:26 ]
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Als je maar een week de tijd hebt en je zit ergens aan andere code te werken, dan moet je dingen beter plannen.farlane schreef op dinsdag 18 januari 2011 @ 23:45:
[...]
Lekker praktische insteek ook. Als de klant de aanpassing over een week moet hebben ga jij em dan uitleggen dat dat niet kan omdat je ergens anders code zit aan te passen? Zal wel overheidswerk zijn dan ofzo want mijn klanten zitten daar echt niet op te wachten.
Daarnaast kost het refactoren van een enkele functie nauwelijks tijd (en helemaal weinig als je refactoring-tools gebruikt). Het idee is namelijk dat je kleine aanpassingen maakt en die telkens test, zoals beschreven in het originele Refactoring boek. Dit is trouwens ook de manier hoe je het in de wiskunde doet. Je schrijft niet een draak van een expressie in 1x om, maar vervangt geleidelijk stap voor stap subexpressies.
Bij wie ligt de verantwoordelijkheid voor die brakke code?Moet je ook nog gaan uitleggen dat die code eigenlijk bagger is, gaat die kant nog harder steigeren omdat het dus de eerste keer ook al brak was.
- Als je zelf de code hebt geschreven, dan lijkt het me duidelijk dat je jezelf ermee geholpen hebt.
- Indien de code reeds bestond en door een andere partij geschreven is, dan had je bij aanvang van de opdracht moeten aankaarten dat de kwaliteit van de code zodanig is, dat onderhoud meer werk kost.
Het is geen wet dat als een functie langer dan N regels beslaat, dat de functie per definitie te lang is. Het zijn vuistregels dat er zeer waarschijnlijk iets mis en dat je de boel kunt refactoren. Het is vergelijkbaar met code coverage. Als je geen 100% code coverage haalt, wil dat niet zeggen dat je testsuite niet goed is, maar het is wel raadzaam om te controleren welke zaken niet gedekt worden.De lengte van een functie is echt de moeite van de discussie niet waard eigenlijk : een functie is zo lang als tie moet zijn en niet langer maar ook niet korter.
Daarnaast bestaan er trouwens refactoring-technieken die een functie langer kunnen maken, zoals bijv. Introduce ExplainingVariable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| if ( (platform.toUpperCase().indexOf("MAC") > -1) && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0 ) { // do something } // Introduce Explaining Variable --> final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1; final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1; final boolean wasResized = resize > 0; if (isMacOs && isIEBrowser && wasInitialized() && wasResized) { // do something } |
Een groei van 2 regels.
Ipsa Scientia Potestas Est
NNID: ShinNoNoir
Iddfarlane schreef op dinsdag 18 januari 2011 @ 23:45:
De lengte van een functie is echt de moeite van de discussie niet waard eigenlijk : een functie is zo lang als tie moet zijn en niet langer maar ook niet korter.
Persoonlijk heb ik best wel een hekel eraan als alles totaal over-engineered wordt, oftewel dat alles tot in de puntjes ge-extract en ge-refactored wordt. Dit soort code is namelijk prima voor de originele progger, alleen kost het vet veel tijd als collega/extern progger om de code te begrijpen! En al helemaal er ook nog eens een niet-logische of niet consistente naamgeving aan de methoden gegeven worden.
In het voorbeeld dat eerder werd aangehaald: Ik heb liever het eerste dan wel 2e stukje code dan de spaghetti op het einde.
Daarnaast is de lengte afhankelijk van de taal waarin je progt en het doel van je functie.
Voorbeelden:
- PHP library klasse kan methoden hebben van gemiddeld 20 tot 40 regels per functie...
- terwijl een JAVA GUI code lappen (lees 80 tot 100 regels) voor enkele functies heeft.
- Extende klassen in C# die super kort zijn per functie omdat ze alles door zetten naar hun parent ^^
[ Voor 8% gewijzigd door Camulos op 19-01-2011 08:48 ]
Not just an innocent bystander
Integendeel denk ik. Als ik een functie van 200 regels voor m'n neus krijg dan zal ik echt heel erg goed moeten gaan lezen wat er nu precies allemaal gebeurt. Bij een functie van 5 regels zie je dat direct.Camulos schreef op woensdag 19 januari 2011 @ 08:43:
[...]
Dit soort code is namelijk prima voor de originele progger, alleen kost het vet veel tijd als collega/extern progger om de code te begrijpen!
Dat is een kwestie van code-standaard en goed code reviewen en iemand die daar niet op let erop aanspreken.En al helemaal er ook nog eens een niet-logische of niet consistente naamgeving aan de methoden gegeven worden.
Je vergeet even dat jij die functie van 5 regels eerst moet vindenCartman! schreef op woensdag 19 januari 2011 @ 08:48:
Integendeel denk ik. Als ik een functie van 200 regels voor m'n neus krijg dan zal ik echt heel erg goed moeten gaan lezen wat er nu precies allemaal gebeurt. Bij een functie van 5 regels zie je dat direct.
Nu vind ik 200 regels ook best lang
Not just an innocent bystander
Thanx! is een mooie toevoeging.Davio schreef op maandag 17 januari 2011 @ 16:01:
Even een losse tip: Ik gebruik voor Visual Studio de extensie VS10x Code Map.
Hiermee heb ik in een sidebar een mooi overzicht van mijn properties, methods en events etc. mooi gegroepeerd per zelf aangegeven region.
Dat lijkt me geen enkel probleem, gewoon doorklikken in je IDE. En als iedereen netjes werkt weet je meestal wel ongeveer waar iets zit, of je klikt gewoon wat rond in je IDE dus.Camulos schreef op woensdag 19 januari 2011 @ 09:12:
[...]
Je vergeet even dat jij die functie van 5 regels eerst moet vindenEen functie van 200 regels refactoren naar 5-regel-functies.. (40 functies ongeveer?). Een wir-war van functies doorspitten om die ene functie te vinden...
Het is natuurlijk wel de bedoeling dat je de gerefactorde functie grotendeels kunt begrijpen zonder de details erop na te hoeven slaan. Hierbij is van groot belang dat de kleinere functies een duidelijke naam hebben.Camulos schreef op woensdag 19 januari 2011 @ 09:12:
[...]
Je vergeet even dat jij die functie van 5 regels eerst moet vindenEen functie van 200 regels refactoren naar 5-regel-functies.. (40 functies ongeveer?). Een wir-war van functies doorspitten om die ene functie te vinden...
Mocht je geinteresseerd zijn in de details van een bepaalde functie, dan helpt een IDE je om er naar toe te springen. Het is echter niet de bedoeling om bij elke functieaanroep die je tegenkomt tijdens het lezen meteen de diepte in te gaan.**
(** Dit is vooral een slechte manier om bijv. een recursieve functie f te begrijpen. Tijdens het lezen van f kom je gegarandeerd een aanroep van f tegen. Om f te begrijpen moet je dus eerst f begrijpen?
Ipsa Scientia Potestas Est
NNID: ShinNoNoir
Verwijderd
Die mensen heten klanten, managers en ontwikkelaars. Je kan het je in het bedrijfsleven gewoon niet permitteren altijd purist te zijn. Dat levert vertraging op. Soms moet iets ad hoc gebeuren en dan zul je uiteindelijk pragmatisme over purisme gaan prefereren.Er schijnen nu eenmaal mensen rond te lopen die nette code onbelangrijk vinden, zolang het maar werkt.
De spaghettivorming is iets van kwadratisch aan het aantal onderhoudsbeurten
Overigens vind ik dat verhaal over 1 functie-call in een method ook echt onzin. Je code wordt er alleen minder leesbaar en onderhoudbaar door. Daarnaast maak je debuggen ook alleen maar lastiger, omdat je je call-stack onnodig groot maakt (wat daarnaast ook nog eens een negatieve invloed heeft op performance). Je moet gewoon logische stukken groeperen. Niet extracten om het extracten.
[ Voor 29% gewijzigd door Verwijderd op 19-01-2011 09:55 ]
Vooral leuk bij 2-laags-recursie (of hoe dat ook heet): f roept g aan, en g roept in bepaalde gevallen f weer aan. "Volgens mij heb ik daarnet net zo'n functie gezien, welke malloot gaat dit nou copy/pasten?" Oh ja, recursie...RayNbow schreef op woensdag 19 januari 2011 @ 09:36:
(** Dit is vooral een slechte manier om bijv. een recursieve functie f te begrijpen. Tijdens het lezen van f kom je gegarandeerd een aanroep van f tegen. Om f te begrijpen moet je dus eerst f begrijpen?)
Nee, ik zit niet ergens anders code aan te passen. Lees nog eens mijn beschrijving door. Ik doe mijn aanpassing en laat de code netter achter dan dat ik hem gevonden heb. Als dat bij elke aanpassing gebeurt evolueert de code vanzelf naar nettere code.farlane schreef op dinsdag 18 januari 2011 @ 23:45:
[...]
Lekker praktische insteek ook. Als de klant de aanpassing over een week moet hebben ga jij em dan uitleggen dat dat niet kan omdat je ergens anders code zit aan te passen? Zal wel overheidswerk zijn dan ofzo want mijn klanten zitten daar echt niet op te wachten.
En overheid? Dat valt wel mee. Niet alles anders dan cowboy omgevingen is overheid.
Hier is al eerder op gereageerd. Het is echt niet de klant die brakke code geschreven heeft. Als je nu meer tijd voor je aanpassing nodig hebt dan is dat de consequentie van de kantjes die je team er eerder vanaf gelopen heeft. Verder zijn jullie blijkbaar niet goed in het onderkennen en communiceren van de technical debt en daarnaast kost nette code niet meer tijd dan brakke aanpassingen.Moet je ook nog gaan uitleggen dat die code eigenlijk bagger is, gaat die kant nog harder steigeren omdat het dus de eerste keer ook al brak was.
Het is geen keiharde regel. Het is een leidraad. Bij enorme lange lappen code is de kans veel groter dat je met ononderhoudbare code te maken hebt. Het is een belangrijke metric die wij gebruiken om onderhoudbaarheid te kunnen afleiden.De lengte van een functie is echt de moeite van de discussie niet waard eigenlijk : een functie is zo lang als tie moet zijn en niet langer maar ook niet korter.
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
+1 !! kan het niet beter verwoordenVerwijderd schreef op woensdag 19 januari 2011 @ 09:49:
Overigens vind ik dat verhaal over 1 functie-call in een method ook echt onzin. Je code wordt er alleen minder leesbaar en onderhoudbaar door. Daarnaast maak je debuggen ook alleen maar lastiger, omdat je je call-stack onnodig groot maakt (wat daarnaast ook nog eens een negatieve invloed heeft op performance). Je moet gewoon logische stukken groeperen. Niet extracten om het extracten.
Not just an innocent bystander
en nog een +1
"The purpose of computing is insight, not numbers." -- Richard Hamming
Het heeft niks met purisme te maken, maar alles met professionaliteit. Ook bij een adhoc aanpassing kost het geen extra tijd om daar een unittestje bij te bouwen.Verwijderd schreef op woensdag 19 januari 2011 @ 09:49:
Die mensen heten klanten, managers en ontwikkelaars. Je kan het je in het bedrijfsleven gewoon niet permitteren altijd purist te zijn. Dat levert vertraging op. Soms moet iets ad hoc gebeuren en dan zul je uiteindelijk pragmatisme over purisme gaan prefereren.
Dat hoeft dus helemaal niet. Dat gebeurt alleen wanneer je je code laat rotten.De spaghettivorming is iets van kwadratisch aan het aantal onderhoudsbeurten
Je kunt het natuurlijk in het belachelijke trekken. Een functie hoeft echt niet uit 1 regel te bestaan. Echter wanneer bij voorbaat je onderhoudbaarheid in gaat leveren vanwege vermoedelijk performance verlies dan ben je imho niet goed bezig.Overigens vind ik dat verhaal over 1 functie-call in een method ook echt onzin. Je code wordt er alleen minder leesbaar en onderhoudbaar door. Daarnaast maak je debuggen ook alleen maar lastiger, omdat je je call-stack onnodig groot maakt (wat daarnaast ook nog eens een negatieve invloed heeft op performance). Je moet gewoon logische stukken groeperen. Niet extracten om het extracten.
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Verwijderd
Tja, dat vind jij, dat vind ik, maar wat je ziet is toch dat er dingen on-the-fly veranderd gaan worden, om het snel even iets op te leveren. Een regeltje code in een method, zonder opnieuw te testen.Janoz schreef op woensdag 19 januari 2011 @ 10:38:
[...]
Het heeft niks met purisme te maken, maar alles met professionaliteit. Ook bij een adhoc aanpassing kost het geen extra tijd om daar een unittestje bij te bouwen.
Dat is niet leuk, maar dagelijkse praktijk. Er zijn nog legio bedrijven waar unit-testen uberhaupt niet vanzelfsprekend is. Ik heb bij een multinational gewerkt waar unit-testen nu pas geimplementeerd wordt. En nu klaagt men al over het feit dat dat tijd kost.
Daar zijn we het dus eens, het sloeg voornamelijk op deze blogpost: http://blog.objectmentor....ing-extract-till-you-drop die door iemand gelinkt werd.Je kunt het natuurlijk in het belachelijke trekken. Een functie hoeft echt niet uit 1 regel te bestaan. Echter wanneer bij voorbaat je onderhoudbaarheid in gaat leveren vanwege vermoedelijk performance verlies dan ben je imho niet goed bezig.
Dat valt toch wel mee. Ik werk ook best veel op embedded platformen, maar mijn main function is nooit erg lang. Je roept een paar initialisatie routines aan, en dan start je je main loop/scheduler. Meestal zal de compiler toch ook nog wel het een en ander inlinen, dus dan is de overhead van een functie aanroepen ook geen argument meer.Verwijderd schreef op woensdag 19 januari 2011 @ 00:53:
[...]
Nou, zeker in C, gebeurd daar vaak nog wel het een en ander. Ook op embedded platformen, daar is het ook inefficienter om overal een functie aanroep te doen.
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Die blogpost haal ik ook altijd aan ja, briljant gewoon.CodeCaster schreef op maandag 17 januari 2011 @ 11:14:
Wat zei Joel daar ook alweer over?
[...]
Oh die was gelukkig pas twee keer genoemd.![]()
Nederlands is makkelijker als je denkt
Ik ga toch liever voor leesbaarheid van mijn code dan voor micro-optimalisatie middels het minimaliseren van functie-aanroepen.Woy schreef op woensdag 19 januari 2011 @ 10:48:
[...]
Dat valt toch wel mee. Ik werk ook best veel op embedded platformen, maar mijn main function is nooit erg lang. Je roept een paar initialisatie routines aan, en dan start je je main loop/scheduler. Meestal zal de compiler toch ook nog wel het een en ander inlinen, dus dan is de overhead van een functie aanroepen ook geen argument meer.
MBV schreef op woensdag 19 januari 2011 @ 09:56:
[...]
Vooral leuk bij 2-laags-recursie (of hoe dat ook heet): f roept g aan, en g roept in bepaalde gevallen f weer aan. "Volgens mij heb ik daarnet net zo'n functie gezien, welke malloot gaat dit nou copy/pasten?" Oh ja, recursie...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| -- De priemgetallen: primes = 2 : oddprimes oddprimes = filter isPrime [3,5..] -- Wanneer is iets priem? -- Nou, als het maar 1 priemfactor heeft: isPrime n = case (primeFactors n) of [p] -> True _ -> False -- Maar hoe bereken je de priemfactoren van een getal n? -- Nou, we delen gewoon herhaaldelijk door elk priemgetal: primeFactors n = factor n primes where factor m (p:ps) | p*p > m = [m] | m `mod` p == 0 = p : factor (m `div` p) (p:ps) | otherwise = factor m ps |
Mutual recursion.
Ipsa Scientia Potestas Est
NNID: ShinNoNoir
Het zijn alleen niet altijd micro-optimalisaties. Zo hebben wij onlangs wat vaak aangeroepen virtual methods ontdaan van het virtual zijn. Op embedded platforms waar een cycle relatief lang duurt is de impact nog veel groter.Davio schreef op woensdag 19 januari 2011 @ 10:55:
[...]
Ik ga toch liever voor leesbaarheid van mijn code dan voor micro-optimalisatie middels het minimaliseren van functie-aanroepen.
De mens is slecht in recursie. Als jij 3 of 4 functies diep moet raak je al snel het overzicht kwijt.Cartman! schreef op woensdag 19 januari 2011 @ 09:32:
[...]
Dat lijkt me geen enkel probleem, gewoon doorklikken in je IDE. En als iedereen netjes werkt weet je meestal wel ongeveer waar iets zit, of je klikt gewoon wat rond in je IDE dus.
[ Voor 30% gewijzigd door .oisyn op 19-01-2011 11:29 ]
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Dan ga je er voor t gemak even vanuit dat alle code die je schrijft recursief is. We maken vast heel verschillende producten, ik gebruik t maar zelden iig..oisyn schreef op woensdag 19 januari 2011 @ 11:28:
[...]
De mens is slecht in recursie. Als jij 3 of 4 functies diep moet raak je al snel het overzicht kwijt.
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
En daarom is het niet de bedoeling om bij elke functieaanroep de diepte in te gaan..oisyn schreef op woensdag 19 januari 2011 @ 16:41:
ik heb het over in je hoofd steeds weer opnieuw een functie in en maar proberen te onthouden wat je 'stack' was.
Als je iets ziet als...
1
2
| xs = range(10) random.shuffle(xs) |
...spring je niet naar de definitie van random.shuffle, maar behandel je het als een black box.
Ipsa Scientia Potestas Est
NNID: ShinNoNoir
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
(Sowieso geldt dat het aantal statements dat je nodig hebt om een bepaalde taak uit te voeren nogal verschilt per taal. In C heb je relatief veel code nodig voor het alloceren en vrijgeven van resources ten op zichte van bijvoorbeeld C++, waar dat soort dingen meestal automatisch via constructors en destructors gebeuren, maar dat maakt de code nauwelijks moeilijker om te begrijpen.)
Ik heb het ook eens met (o.a.) .oisyn dat het weinig zin heeft om meer procedures te introduceren tenzij daarbij ook abstractie plaatsvindt. Als je de hele context expliciet moet meegeven, dan heb je alleen maar meer code nodig om hetzelfde gedaan te krijgen, en de resulterende code is dan niet eenvoudiger om in z'n geheel te begrijpen. Afsplitsen van functionaliteit is mijns inziens pas zinnig als je daardoor ófwel codeduplicatie voorkomt (soms moet je dan helaas nog steeds veel contextinformatie meegeven), óf onafhankelijke functionaliteit die eerst verweven was ontwart. Kortom: als het afsplitsen van functionaliteit tot makkelijker te begrijpen code leidt.
Tuurlijk, als je echt wilt weten hoe iets geimplementeerd is, dan kun je altijd naar de definitie springen. Dit doe je echter niet als je al bezig bent om de functie te begrijpen waarin deze functieaanroep (in mijn voorbeeld random.shuffle) plaatsvindt..oisyn schreef op woensdag 19 januari 2011 @ 17:41:
Tot je wilt weten hoe die geïmplementeerd is.
Daarvan vind ik helaas alle iteraties van de code lelijk.Volgens mij ging de discussie over http://blog.objectmentor....ing-extract-till-you-drop.
Ipsa Scientia Potestas Est
NNID: ShinNoNoir
Verder was dit mijn eerste project bij mijn baas en was de documentatie in een taal waar ik niet vloeiend in ben. Acht het project waarop het gebaseerd is (v1) was nog erger... Wat ik mijn nu al een tijd afvraag hoe werk je met unit tests als je zoveel afhankelijkheden hebt?
Ik snap de werkwijze ( en ook de impuls om dat soort dingen te doen ), maar elke aanpassing die je maakt kost tijd, hoe dan ook. En in brakke code kost het meer tijd want kleine aanpassingen hebben meestal meer gevolgen dan je kunt zien in een korte tijd. Jouw oplossing daarvoor is tests schrijven, wat ook tijd kost.Janoz schreef op woensdag 19 januari 2011 @ 10:06:
Nee, ik zit niet ergens anders code aan te passen. Lees nog eens mijn beschrijving door. Ik doe mijn aanpassing en laat de code netter achter dan dat ik hem gevonden heb. Als dat bij elke aanpassing gebeurt evolueert de code vanzelf naar nettere code.
Imho moet je je gewoon blijven afvragen of dat wat je aan het doen bent wel iets gaat opleveren, anders dan alleen 'nettere code'.
Ik heb het over code die uit hetzelfde 'nest' komt, dus waarvoor jij/ik verantwoordelijk bent. Dat kan code van jezelf zijn, of code geschreven door iemand/iemanden ander en waarvoor je eigenlijk niet de verantwoordelijkheid wilt, omdat je weet dat het geen goede code is. Ook al heb je er geen hand in gehad, je moet alsnog met de billen bloot op dat moment. Wat de oorzaak ook geweest is, de klant zal niet blij zijn om te horen dat het brakke code is.Hier is al eerder op gereageerd. Het is echt niet de klant die brakke code geschreven heeft. Als je nu meer tijd voor je aanpassing nodig hebt dan is dat de consequentie van de kantjes die je team er eerder vanaf gelopen heeft.
Met het tweede ben ik het helemaal eensch, maar soms heb je daar gewoon geen invloed op.Verder zijn jullie blijkbaar niet goed in het onderkennen en communiceren van de technical debt en daarnaast kost nette code niet meer tijd dan brakke aanpassingen.
Het eerste punt vind ik erm, vaag. Wat bedoel je daar mee, dat ik/wij niet weten dat goede software maken belangrijk is?
Als leidraad zou het kunnen functioneren misschien.Het is geen keiharde regel. Het is een leidraad.
Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.
Ja, en als die functie is opgesplitst in 5 niveaus diep, dan spring je en spring je en wordt de code zo onoverzichtelijk als de pest.RayNbow schreef op woensdag 19 januari 2011 @ 18:20:
[...]
Tuurlijk, als je echt wilt weten hoe iets geimplementeerd is, dan kun je altijd naar de definitie springen.
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
Uiteraard kost een aanpassing tijd. Ik neem verder aan dat we het eens zijn dat een aanpassing in nette code minder tijd kost dan een aanpassing in rotte code. Wat ik echter daarnaast nog beweer is dat een aanpassing maken en de code netter achter te laten dan je hem gevonden hebt niet meer tijd hoeft te kosten dan de aanpassing adhoc doorvoeren en werkend te krijgen.farlane schreef op woensdag 19 januari 2011 @ 20:29:
[...]
Ik snap de werkwijze ( en ook de impuls om dat soort dingen te doen ), maar elke aanpassing die je maakt kost tijd, hoe dan ook. En in brakke code kost het meer tijd want kleine aanpassingen hebben meestal meer gevolgen dan je kunt zien in een korte tijd. Jouw oplossing daarvoor is tests schrijven, wat ook tijd kost.
Als het enkel daarom ging dan zou nu gelijk alle rotte code gerefactord worden op het moment dat er daadwerkelijk een wijziging nodig wasImho moet je je gewoon blijven afvragen of dat wat je aan het doen bent wel iets gaat opleveren, anders dan alleen 'nettere code'.
Als de waarheid vervelend is vindt niemand dat fijn om te horen. Het is echt niet pas een probleem op het moment dat je het probeert op te lossen. Het is nu al een probleem. Zeker wanneer je de schijn op wilt houden dat de code niet brak is lijkt het mij het handigste om er beetje bij beetje voor te zorgen dat de code niet meer brak is.Ik heb het over code die uit hetzelfde 'nest' komt, dus waarvoor jij/ik verantwoordelijk bent. Dat kan code van jezelf zijn, of code geschreven door iemand/iemanden ander en waarvoor je eigenlijk niet de verantwoordelijkheid wilt, omdat je weet dat het geen goede code is. Ook al heb je er geen hand in gehad, je moet alsnog met de billen bloot op dat moment. Wat de oorzaak ook geweest is, de klant zal niet blij zijn om te horen dat het brakke code is.
Nee, eerder dat jullie niet weten wat de gevolgen van jullie technical debt is, of dit niet over weten te brengen op het project managment/sales afdeling. Dat kan aan jullie liggen, maar ligt over het algemeen vaker aan de managment/sales afdeling.Met het tweede ben ik het helemaal eensch, maar soms heb je daar gewoon geen invloed op.
Het eerste punt vind ik erm, vaag. Wat bedoel je daar mee, dat ik/wij niet weten dat goede software maken belangrijk is?
Lees gewoon het eerder aangehaalde boek eens en probeer het zo nu en dan eens toe te passen.Als leidraad zou het kunnen functioneren misschien.
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Dat klopt wel, maar het probleem is dat ook rotte code meestal wel redelijk werkt, alleen de uitzonderingen op de regel laten de boel in het honderd lopen. Die uitzonderingen echter koment niet vaak voor, zodat de klant van mening is dat het product redelijk werkt, behalve die paar 'speciale situaties' die een onevenredige hoeveelheid werk vergen om op te lossen.Janoz schreef op woensdag 19 januari 2011 @ 22:50:
Als de waarheid vervelend is vindt niemand dat fijn om te horen. Het is echt niet pas een probleem op het moment dat je het probeert op te lossen.
Ah ok, tja ik ben me daar maar al te bewust van en ik zie het als mijn taak om het management er ook van te blijven overtuigen. Echter, prioriteiten liggen vaak ergens anders op het moment dat puntje bij paaltje komt, en het momentum van de 'andere partij' is op dat moment vaak moeilijk te stoppen.Nee, eerder dat jullie niet weten wat de gevolgen van jullie technical debt is, of dit niet over weten te brengen op het project managment/sales afdeling. Dat kan aan jullie liggen, maar ligt over het algemeen vaker aan de managment/sales afdeling.
Als ik tijd heb ga ik dat zeker doen ..Lees gewoon het eerder aangehaalde boek eens en probeer het zo nu en dan eens toe te passen.
Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.
Dat zou je met Dependency Injection moeten oplossen om zo verschillende mock objecten te kunnen maken. Echter ben ik er daar ook nog niet helemaal uit hoe ik zoiets moet gaan ontwikkelen.alienfruit schreef op woensdag 19 januari 2011 @ 20:03:
Wat ik mijn nu al een tijd afvraag hoe werk je met unit tests als je zoveel afhankelijkheden hebt?
Je krijgt dan een constructor met mogelijk 10 verschillende interfaces als parameter. Volgens mij is dat inderdaad de bedoeling, zo kun je iedere gebruikte klasse binnen de klasse die je wilt testen aanroepen met een benodigde interface. Het is dan wel noodzakelijk dat iedere aangeroepen klasse ook zo werkt natuurlijk.
Zeker in het boek Working Effectively with Legacy Code wordt hier op ingegaan, maar omdat ik dat boek zo droog vind, kom ik er niet echt door en blijft het ook niet echt hangen.
Battle.net - Jandev#2601 / XBOX: VriesDeJ
Maar op bestaande code is dat een stuk lastiger.Jan_V schreef op donderdag 20 januari 2011 @ 07:53:
[...]
Dat zou je met Dependency Injection moeten oplossen om zo verschillende mock objecten te kunnen maken. Echter ben ik er daar ook nog niet helemaal uit hoe ik zoiets moet gaan ontwikkelen.
DI hoeft niet perse via de constructor te gaan natuurlijkJe krijgt dan een constructor met mogelijk 10 verschillende interfaces als parameter. Volgens mij is dat inderdaad de bedoeling, zo kun je iedere gebruikte klasse binnen de klasse die je wilt testen aanroepen met een benodigde interface. Het is dan wel noodzakelijk dat iedere aangeroepen klasse ook zo werkt natuurlijk.
Ik ga dat boek ook eens bestellen, het lijkt me wel interessant want ik loop ook vaak tegen het probleem van Legacy Code aan, waar je aan de ene kant van alles aan wil veranderen, maar aan de andere kant niet durft omdat je bang bent dat er van alles omvalt, omdat het afhankelijk is van de quircks van het systeem.Zeker in het boek Working Effectively with Legacy Code wordt hier op ingegaan, maar omdat ik dat boek zo droog vind, kom ik er niet echt door en blijft het ook niet echt hangen.
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Hoeft niet natuurlijk, maar als je het niet aan de constructor mee geeft, waar zou je het dan willen doen?Woy schreef op donderdag 20 januari 2011 @ 09:16:
[...]
DI hoeft niet perse via de constructor te gaan natuurlijk
[...]
In een overload van een methode of iets dergelijks, of een property?
Nadeel is dan dat je weer in de gaten moet houden of je wel tegen het juiste object praat, anders zit je alsnog het hele systeem te testen.
Of zou je het op een andere manier willen doen?
Battle.net - Jandev#2601 / XBOX: VriesDeJ
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Ja, maar in .NET kun je de properties gelijk initialiseren bij het aanmaken (Object Initialization), werkt zo:Janoz schreef op donderdag 20 januari 2011 @ 13:23:
Mijn voorkeur gaat altijd uit naar setters (of properties bij .net). Dat maakt het imho altijd wat leesbaarder dan een reeks parameters in de constructor.
1
2
3
4
5
6
| MyObject o = new MyObject() { PropString = "Jaep", PropInt = 3, PropBool = false }; |
Dan hoef je je constructor niet onnodig te overloaden. Parameters in constructors zou je eigenlijk alleen moeten gebruiken voor verplichte properties / fields.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| private IOtherClass1 _otherClass1 = null; public IOtherClass1 OtherClass1 { get { if (_otherClass1 == null) { _otherClass1 = new OtherClass1(); } return _otherClass1; } set{ _otherClass1 = value; } } |
Default je 'normale' object terug krijgen en als je iets anders wilt, dan een andere klasse setten.
Heb er nog steeds een minder warm gevoel bij als bij een constructor overloaden, je moet nu namelijk nog steeds weten welke klassen er allemaal worden gebruikt binnen je te testen 'unit'. Wanneer je deze in de constructor hebt staan, dan weet je zeker dat ze allemaal zijn geinitialiseerd met je eigen gekozen object. Wellicht initialiseer je teveel (heb je maar 1 object nodig ipv alle 5).
Misschien moet ik er eens een nachtje over slapen om het voordeel te zien.
Battle.net - Jandev#2601 / XBOX: VriesDeJ
Ik ken .NET niet, maar zo te zien zou dat zeker mijn voorkeur hebben. Bij spring (DI bij java) kun je middels een annotatie op de setter aangeven dat een dependecy verplicht is. Dan krijg je nog steeds een foutmelding wanneer je (runtime) vergeet het verplichte veld te vullen.Davio schreef op donderdag 20 januari 2011 @ 13:52:
[...]
Ja, maar in .NET kun je de properties gelijk initialiseren bij het aanmaken (Object Initialization), werkt zo:
C#:
1 2 3 4 5 6 MyObject o = new MyObject() { PropString = "Jaep", PropInt = 3, PropBool = false };
Dan hoef je je constructor niet onnodig te overloaden. Parameters in constructors zou je eigenlijk alleen moeten gebruiken voor verplichte properties / fields.
Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'
Huh? Zo maak je in Java een instantie van een ter plekke gedefinieerde class. (Anonymous inner class dus.)Davio schreef op donderdag 20 januari 2011 @ 13:52:
[...]
Ja, maar in .NET kun je de properties gelijk initialiseren bij het aanmaken (Object Initialization), werkt zo:
C#:
1 2 3 4 5 6 MyObject o = new MyObject() { PropString = "Jaep", PropInt = 3, PropBool = false };
Dan hoef je je constructor niet onnodig te overloaden. Parameters in constructors zou je eigenlijk alleen moeten gebruiken voor verplichte properties / fields.
De lengte van een method of function is ondergeschikt aan zo'n beetje al het andere. Een aardige tip voor de refactorjunkies is kijken naar de oppervlakte van de code. Als je veel geneste constructies hebt, 10 geneste for-loops anyone?, dan loont het om te onderzoeken of er een aantal uit kunnen worden getrokken.
Hey ... maar dan heb je ook wat!
Dat is enigzins vreemd. De DI constructie zou er voor moeten zorgen dat de container die de class load dat automatisch invult. Dat lijkt me juist het hele doel van DI. Toch?Janoz schreef op donderdag 20 januari 2011 @ 15:25:
[...]
Ik ken .NET niet, maar zo te zien zou dat zeker mijn voorkeur hebben. Bij spring (DI bij java) kun je middels een annotatie op de setter aangeven dat een dependecy verplicht is. Dan krijg je nog steeds een foutmelding wanneer je (runtime) vergeet het verplichte veld te vullen.
Hey ... maar dan heb je ook wat!
Dat is echter ook niks anders dan een andere manier van schrijven vanDavio schreef op donderdag 20 januari 2011 @ 13:52:
[...]
Ja, maar in .NET kun je de properties gelijk initialiseren bij het aanmaken (Object Initialization), werkt zo:
C#:
1 2 3 4 5 6 MyObject o = new MyObject() { PropString = "Jaep", PropInt = 3, PropBool = false };
1
2
3
4
| MyObject o = new MyObject(); o.PropString = "Jaep"; o.PropInt = 3; o.PropBool = false; |
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
1
2
3
4
5
| Object obj = new Object( pietje, henkje, klaasje ); |
Een lelijke oplossing om toch een soort van 'named' params te krijgen is het builder pattern misbruiken. Nee dit moet je niet overnemen, nee dit is niet ongelofelijk slim om te doen, en ja, named arguments zou een goed iets zijn:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| class PietjeBuilder { public String pietje; public Object klaas; public int henk; } class Pietje { private final String pietje; private final Object klaas; private final int henk; // ja ook dit zou beter moeten kunnen. Zelfs met IDE hulp wordt je hier simpel van. public Pietje(String pietje, Object klaas, int henk) { this.pietje = pietje; this.klaas = klaas; this.henk = henk; } } public static void main(String[] args) { PietjeBuilder pietjeBuilder = new PietjeBuilder(); pietjeBuilder.pietje = "HENKJE"; pietjeBuilder.klaas = RandomObjectBuilderFactory.getFactory().newInstance(new Random().nextInt(1337)); pietjeBuilder.henk = 1337; Pietje pietje = new Pietje( pietjeBuilder.pietje, pietjeBuilder.klaas, pietjeBuilder.henk ); } |
Named parameters, verplicht in te vullen @ build-time, wat wil je nog meer?
Fake edit: Ow wacht, dit kan ook nog:
1
2
3
4
5
6
| PietjeBuilder pb = new PietjeBuilder(); Pietje pietje = new Pietje( pb.pietje = "henk", pb.klaas = new Object(), pb.henk = 3771 ); |
[random idee] Misschien is er een tooltje te bouwen die automagisch dit soort builders voor elk object aanmaakt oid[/random idee]
Echte edit: Wat ook nog kan: initializer block misbruiken, bouw je een subclass inline. Helaas geen compile-time-verplichte-velden-verplichting.
1
2
3
4
5
| Pietje pietje = new Pietje() {{ setPietje("henk"); setKlaasje("piet"); setLeetje(1337); }}; |
Echte edit 2: Wat V zegt dus.
[ Voor 6% gewijzigd door YopY op 20-01-2011 22:03 ]
Inderdaad, maar je moet een initializer blok gebruiken om de variabelen/setters aan te roepen. Correcte Java syntax is dus:Killemov schreef op donderdag 20 januari 2011 @ 17:00:
[...]
Huh? Zo maak je in Java een instantie van een ter plekke gedefinieerde class. (Anonymous inner class dus.)
1
2
3
4
5
6
| MyObject obj = new MyObject() { { setA("a") setB("b") } }; |
Nadeel is dat dit natuurlijk niet voor final klassen werkt.
Moderne JVM talen (lees: Scala) hebben dit wel, geen builders meer nodig! En dus significant minder codeYopY schreef op donderdag 20 januari 2011 @ 21:58:
Nee, je hebt geen named variables bij constructors...
1
2
3
| case class Foo(aap: String, noot: String, mies: String) val foo = Foo( mies = "mies", noot = "noot", aap = "aap" ) |
[ Voor 28% gewijzigd door RSchellhorn op 20-01-2011 22:10 ]
"Ik heb zo veel soep gegeten, dat kan een mens niet aan. Ik heb zo veel soep gegeten, kan bijna niet meer staan. Ik zat daar maar te slurpen achter die grote kop en als ik bijna klaar was, dan schepten ze weer op!" (Hans Teeuwen)
Ah, het ging me even om het verschil tussen C# en Java. Kleine brainfuck als je van de een naar de ander gaat dus.RSchellhorn schreef op donderdag 20 januari 2011 @ 22:00:
[...]
Inderdaad, maar je moet een initializer blok gebruiken om de variabelen/setters aan te roepen. Correcte Java syntax is dus:
Java:
1 2 3 4 5 6 MyObject obj = new MyObject() { { setA("a") setB("b") } };
Hey ... maar dan heb je ook wat!
Todo: Scala lerenRSchellhorn schreef op donderdag 20 januari 2011 @ 22:00:
Moderne JVM talen (lees: Scala) hebben dit wel, geen builders meer nodig! En dus significant minder code
Scala:
1 2 3 case class Foo(aap: String, noot: String, mies: String) val foo = Foo( mies = "mies", noot = "noot", aap = "aap" )
Nu kun je met Spring Framework op een paar manieren een object initialiseren:
* 'Gewoon', dwz params op volgorde doorgeven
* Per positie, ongeveer hetzelfde als 'gewoon' maar dan geef je een index-parameter mee
* Per type, dus een ctor die een string en een int verwacht geef je een int en een string en Spring zoekt vanzelf uit welke waarbij hoort (hoef je de volgorde niet te weten / aan te passen)
* Per naam (sinds Spring 3.0). Copypasta-voorbeelden:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| <!-- gewoon --> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> </bean> <!-- positie / index --> <bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean> <!-- type --> <bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean> <!-- naam --> <bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateanswer" value="42"/> </bean> |
Natuurlijk is dit allemaal runtime / startup time (alhoewel er mogelijk tools zijn die dit on-the-fly nakijken), en is het veel typ-intensiever dan het gewoon in de code te doen. Aan de andere kant, zo heb je wel veel controle over je applicatie zonder dat je de daadwerkelijke code in hoeft te duiken. Maar het nut en de onzin van Spring's DI en objectinitialisatie kun je een topic op zich aan wijden.
Als het noodzakelijk is dat je de diepte in moet springen, dan is de code gewoon brak. Een functie behoort (grotendeels) begrijpbaar te zijn zonder de intieme details van elke aangeroepen functie te kennen. Het is soms geen eens mogelijk om de intieme details van een aangeroepen functie te kennen, omdat je niet over de broncode beschikt..oisyn schreef op woensdag 19 januari 2011 @ 21:38:
[...]
Ja, en als die functie is opgesplitst in 5 niveaus diep, dan spring je en spring je en wordt de code zo onoverzichtelijk als de pest.
Het voorbeeld in het Extract till you Drop artikel vind ik in ieder geval onduidelijk. Op basis van de naamgeving van de functies alleen kun je niet de werking van de class uitvogelen. Je wordt hier genoodzaakt om door de code heen te springen.
Maar goed, als we het toch over springen-naar-definities hebben, hoe zou je de definitie van primes in m'n eerder geposte Haskell voorbeeld proberen te begrijpen?

^ Toch niet door telkens naar elke definitie te springen, hoop ik?
Ipsa Scientia Potestas Est
NNID: ShinNoNoir
Exactly my point. Dus we zijn het gewoon eensRayNbow schreef op vrijdag 21 januari 2011 @ 10:34:
Het voorbeeld in het Extract till you Drop artikel vind ik in ieder geval onduidelijk.
Die is vrij simpel te begrijpen. Of ie ook correct is is een ander verhaal (als in, ik zie dat je definities kloppen, maar of de recursie ook goed gaat heb ik wat meer tijd voor nodig).Maar goed, als we het toch over springen-naar-definities hebben, hoe zou je de definitie van primes in m'n eerder geposte Haskell voorbeeld proberen te begrijpen?
[ Voor 10% gewijzigd door .oisyn op 21-01-2011 11:20 ]
Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.
https://gist.github.com/779495
1
| primes = 2 : filter isPrime [3,5..] |
Op dezelfde manier zou ik isPrime gewoon zo schrijven:
1
| isPrime n = primeFactors n == [n] |
Maar dit heeft niet echt met het afsplitsen van functies te maken.
Voor de rest zijn alle functies die je gebruikt gewoon "nodig" in de zin dat je ze niet redelijkerwijs kunt inlinen zonder het gehele programma aanzienlijk complexer te maken, ook al zijn de functies zelf relatief kort (wat niet ongebruikelijk is in functionele programmeertalen).
Dus kom ik weer uit op de cyclomatic complexity van eerder. Het is geen perfecte metric, maar het lijkt me geen slechte leidraad om de complexiteit zo laag mogelijk te houden, en van programma's met vergelijkbare complexiteit de kortste de voorkeur te geven. (In het voorbeeld hierboven zou ik bijvoorbeeld zeggen dat "primeFactors n == [n]" simpeler is dan de variant met een case-statement, en dus de voorkeur geniet, zeker aangezien in dit geval er geen verschil in efficiëntie is.)
[ Voor 10% gewijzigd door Soultaker op 21-01-2011 13:38 ]
Ah, okay..oisyn schreef op vrijdag 21 januari 2011 @ 11:20:
[...]
Exactly my point. Dus we zijn het gewoon eens
Om welke recursie gaat het? Die wederzijdse recursie tussen primes en primeFactors (waarbij het van belang is dat primeFactors niet meer elementen van primes consumeert dan dat er al geproduceerd zijn) of de recursie van de hulpfunctie factor?[...]
Die is vrij simpel te begrijpen. Of ie ook correct is is een ander verhaal (als in, ik zie dat je definities kloppen, maar of de recursie ook goed gaat heb ik wat meer tijd voor nodig).
In dit specifieke voorbeeld zou ik zeggen dat er voor beide definities wat te zeggen valt, maar ik vind de een niet moeilijker of makkelijker te lezen dan de ander.Soultaker schreef op vrijdag 21 januari 2011 @ 13:36:
@RayNbow: de vraag is echter of jou code duidelijker wordt van het afsplitsen van méér functies. Ik vind de aparte declaratie van oddprimes in jouw code zeker overbodig. Dit is korter en leest makkelijker:
Haskell:
1 primes = 2 : filter isPrime [3,5..]
Er bestaan trouwens wel alternatieve manieren om een lijst priemgetallen te genereren waarbij het wel handig is om een naam te geven aan de oneven priemgetallen.
Die definitie is inderdaad korter en makkelijker te begrijpen.Op dezelfde manier zou ik isPrime gewoon zo schrijven:
Haskell:
1 isPrime n = primeFactors n == [n]
Maar dit heeft niet echt met het afsplitsen van functies te maken.
(Het kost wel een extra vergelijking tegenover een simpele pattern match, maar da's verwaarloosbaar
Ipsa Scientia Potestas Est
NNID: ShinNoNoir
Builders zijn leuk, maar je hebt geen compile-time checken of je wel alle waarden toekent - dat krijg je pas @ runtime, waardoor bugs later opgemerkt worden.alienfruit schreef op vrijdag 21 januari 2011 @ 11:49:
Ik doe het altijd op deze manier (builders):
https://gist.github.com/779495
Tijd voor een taalfeature waarbij je @compile-time verplicht functies aan kunt laten roepen.