DevOxx Antwerpen 2016

Het thema voor mij was om meningen te horen. Meningen over hoe dingen kunnen, moeten en vooral ook hoe ze niet moeten. Meningen van ervaren developers die in de praktijk dingen tegen komen.

In de komende paragrafen werk ik die meningen uit. Veel kan leiden tot discussies. Het zijn daarom ook slechts meningen en inzichten. Ik schrijf het daarom ook vanuit mijn gezichtspunt.

Externe services

Ik ben af en toe nogal huiverig om externe services te integreren in applicaties die binnen de corporate gebruikt worden. Wat nou als de service stopt of als de service het even niet doet. Nadeel van deze instelling is dat het ontwikkelen van fancy features langer duurt dan nodig. Even een speech-to-text service koppelen aan een kennissysteem voor wiki vragen wordt erg complex als je dat allemaal intern wilt invullen. Door wel gebruik te maken van speech-to-text van externe aanbieders kan dat veel sneller en is er in een paar uur een hele leuke app te bouwen. Zorg er dan wel voor dat de app het ook doet als de service niet beschikbaar is. Dat er de tekst dan bijvoorbeeld gewoon ingetypt kan worden.
Dit is ook onderdeel van het hele microservices idee. Zorg dat de applicatie als geheel blijft werken, ook als er delen niet meer beschikbaar zijn. Bij Amazon kan je nog prima een boek bestellen als de service die andere boeken aan raadt het niet doet. Beperk het tot service degradatie en voorkom complete onbeschikbaarheid.

Machine learning

Zoals te verwachten was machine learning een hot topic. Tensor Flow is een open source library om hier zelf mee aan de gang te gaan. Ik hoor graag wat voor een moois je ermee gemaakt hebt :)

"Twelve ways to make code suck less"

Venkat Subramanian gaf inzichten in dingen waar je tegenaan loopt als developer.

Code schuld moet je opruimen. Maar weet je wel waar je code schuld zit? Als je krap bij kas zit is de eerste stap altijd om inzichtelijk te krijgen waar de uitgaven en waar de inkomsten zitten en welke schulden er precies zijn en wanneer die afgelost moeten worden. Dat is bij code ook zo. Even goed de tijd nemen om de code schuld te identificeren dient vooraf te gaan aan het oppakken van code schuld. De inzichten leiden dan tot een overzicht waarmee de prioriteiten bepaald kunnen worden.

Snap waarom iets werkt. Laat het niet toe dat het opeens spontaan werkt zonder echt te weten waarom het dan werkt. Het kan dan namelijk ook spontaan weer omvallen. Om het echt te kunnen snappen is het handig om het simpel en leesbaar te houden. Dus geen hele fancy algoritmen of patterns maar gewoon lekker simpel en ook zonder een extra academische studie te begrijpen. Als je code tegenkomt die je niet snapt begin je gewoon opnieuw. Onbegrijpelijke code is slechte code en een enorme code schuld.

TDD is slecht. Het is belangrijk om te weten waarom iets gedaan wordt en moet doen wat het doet. Code wordt dus geschreven naar een business wens. Als die business wens eerst naar een testcase omgezet wordt en dan pas naar code weet je bijna zeker dat het niet meer helemaal aansluit bij de wensen van de business. Begin met het opzetten van een structuur van de code en dan pas de tests en/of de invulling. Als goed beseft wordt dat de communicatie met de business ook bij TDD cruciaal is en dat de vraag echt begrepen moet worden kan TDD wel helpen om te communiceren met die business. Tools als cucumber maken technische implementaties leesbaar voor niet technici.

Verminder garbage variabelen. Garbage variabelen zoals i en n voegen geen functionaliteit toe en dienen zo min mogelijk gebruikt te worden. Streams en lambda's kunnen helpen bij het verminderen van garbage variabelen. In plaats van een loop kan het nu met zoiets als "iterate().filter().sum()".

Liever simpele dan slimme code. Slimme code vergt bestudering door je opvolger en collega's. Dit leidt dan vaak tot uitgebreide javadoc en het kan makkelijk fout gaan. Simpele code snapt iedereen en kan als een boek gelezen worden. Hierdoor ontstaan minder fouten en iedereen durft het aan te passen. Als je erg trots bent op je mooie slimme algoritme is het tijd om je code te wissen en opnieuw te beginnen.

Javadoc beschrijft het wat niet het hoe. In je javadoc moet he beschrijven wat er gebeurt en niet hoe je dat doet. De code is als het goed is dermate simpel dat het hoe direct te zien is. Wat het doet is veel belangrijker. Als je weet wat een methode zou moeten doen kan gecontroleerd worden of de code doet wat het zou moeten doen. Ook aanpassingen kunnen dan gedaan worden. Bijvoorbeeld een optimalere implementatie.

Veel javadoc betekent code schuld. Als er veel javadoc nodig is om te beschijven wat een klasse of methode doet dan is dat een indicatie dat de code beter kan. Netjes opgesplitste code heeft zinvolle klasse en methode namen die verder geen javadoc nodig hebben.

Korte methode kan veel regels hebben. Bij methode lengte gaat het niet om het aantal regels maar de complexiteit. Een methode van veel regels met een groot switch statement kan nog steeds perfect leesbaar zijn. Een methode met weinig regels maar met hoge complexiteit kan al snel te lang zijn. Een goede indicatie hierbij is de diepte van inspringen. Hoe dieper ingesprongen hoe slechter de code.

Zinvolle variabele namen. Variabelen moeten zinvolle namen hebben. Als die niet te bedenken is dan kan het zijn dat het algoritme niet duidelijk is en dat de ode aangepast moet worden om wel met zinvolle variabelen te werken. Denk ook aan methoden die antwoorden geven. Als de antwoord variabele niet verder komt dan 'result' dan is er waarschijnlijk iets mis met de definitie van de methode.

Schrijven doe je eenmalig, lezen veelvuldig. Het schrijven van de code doe je enmalig en met vol begrip van wat je aan het doen bent. Het teruglezen van de code doe je veelvuldig en wordt ook door collega's gedaan. Niet iedereen weet precies wat de bedoeling is en moet dit uit de code kunnen opmaken. Neem de tijd om duidelijke code te schrijven.

Doe code reviews in groepen. Neem een groot scherm en projecteer daar de code op. Ga dan samen met het team door de code heen. 10 tegen 1 dat je gekke dingen ontdenkt of van elkaar kunt leren.

Voeg expres fouten toe. Als je code altijd wel goed is dan wordt de code review lui en de testers geloven het op denk duur ook allemaal wel. Voeg af en toe fouten toe om te borgen dat het testtraject goed functioneert. Een soort van brandoefening maar dan in code.

Overigen

Copy-paste programming van Stack-Overflow leidt tot goede code. Op Stack-Overflow staan veel vragen met daarbij code fragmenten om die te beantwoorden. Daarbij komt daar weer veel commentaar op. Zeker als een antwoord niet juist is leidt dat tot veel correcties en uiteindelijk is het geaccepteerde antwoord heel goed. Copieer dat gewoon en je bent er redelijk zeker van dat je geen gekke dingen doet. Daarnaast snappen anderen dan wat je doet want die komen waarschijnlijk bij dezelfde pagina uit als ze antwoorden zoeken.

Team zwaar belasten kost enorm veel productiviteit. Er is een exponentieel verband tussen de wachtrij en de belasting van het kanaal. Denk aan files. 10% meer auto's leidt al snel tot een verdubbeling van het aantal files. Dit geldt ook voor teams. Als een team ruimte heeft is er tijd om dingen af te ronden en kan er ingesprongen worden op dingen die voorbij komen. Zwaar belaste teams zijn veel tijd kwijt met afstemmen en schakelen tussen opdrachten. Ga dus niet voor maximale belasting maar monitor goed hoe de productiviteit zich ontwikkeld. Als een team niet produceert is het misschien zo dat je werk weg moet halen en niet juist meer moet toevoegen.

Paralleliseren slechts gedeeltelijk nuttig. Als een algoritme voor 95% te paralelliseren is houd je altijd minimaal 5% over. Je kan dus maximaal een factor 20 versnellen met paralelliseren. Bedenk dat 95% enorm hoog is. Vaak is dat percentage veel lager en is de te behalen winst ook veel kleiner. In java8 kan het eenvoudig maar daar komt wel veel overhead bij kijken. Doe het niet blind maar test altijd zeer goed of je echt winst behaalt. En snapt men nog wat je doet?

Kopieer libraries ipv ze als dependency op te nemen. Copy-paste code vanuit de libraries en verander deze waar je dat nodig hebt. Dit voorkomt veelvuldig gebruik van abstracts en interfaces minimaliseert de coupling. In een tijd van microservices is coupling veel erger dan copy-paste. Als de microserver aangepast moet worden schrijf je hem gewoon opnieuw. Het is als het goed is toch maar een simpel dingetje.

Gebruik geen frameworks en containers. Frameworks en containers bieden enorm veel functionaliteit waar je geen gebruik van maakt. Houd het simpel.

Gebruik geen 'null'. 'null' zegt niet veel en kan alles betekenen. Probeer zoveel mogelijk zinvolle waarden te gebruiken en zeker geen 'null'.

Houd loops klein. De processor optimaliseert in niveaus het gebruik van geheugen. Het snelste geheugen is heel klein en om daar gebruik van te kunnen maken moeten loops heel klein zijn. Zo klein dat ze in het L0 cache passen.

Houd data bij elkaar. Door data bij elkaar te houden kan de cpu het L1 cache optimaal gebruiken.

Optional.isPresent() staat gelijk aan '== null' controle. Als de eerste regel in een methode de isPresent() aanroep bevat om te controleren of waarden gezet zijn dan kan je net zo goed '==null' doen. Gebruik zinvolle waarden en dat geldt ook voor Optionals. Maar gebruik wel optionals aangezien de NPE daarmee verleden tijd kan zijn.

Lambda's vergen gewenning. Meerdere if-then-else statements zijn we gewend en lezen we snel weg. Lange Lambda's vergen wat meer inspanning. Zeker als ze op een enkele regel geformatteerd staan. Dit is een kwestie van tijd.

We zijn geen programmeurs maar 'software ambachtsmensen'. We proberen mooie code te maken en zijn trots op het product. We mogen ons best vergelijken met een meubelmaker die we ook een ambachtsman noemen.

Zero downtime upgrade. Bij mircoservices moet je goed nadenken over hoe je kunt upgraden zonder de service te verstoren. Met zoveel kleine services die een voudig upgradebaar moeten zijn is dat extra belangrijk. Het is niet meer doenlijk om voor iedere change een heel proces in te gaan. Het moet helemaal Continuous-Integration achtig kunnen.

Iterators zijn zoooo 2014. Gebruik bijvoorbeeld collection.removeIf().

Gebruik primitives. Primitives gebruiken veel minder geheugen. Een array van 10.000.000 elementen: int[] = 40MB, Integer[] = 160MB, ArrayList<Integer> = 215MB

Foutmeldingen in Lamda's niet fijn. Gebruik methoden als er echt iets gebeurt. Die kan dan meteen zinvolle naam hebben.

Collection naar Stream en terug kan traag zijn. Als je een collectie hebt en je wilt iets op elk element doen dan kan dat met de Java8 forEach heel elegant gedaan worden. Het omzetten van een collectie naar een stream vergt overhead waardoor het soms sneller is om een for-loop te maken. Pas als je echt met streams werkt hebben ze zin. Niet de hele tijd tussen collecties en streams heen en weer schakelen.

Object.equals(a, b) ipv a.equals(b). Dit om NPE's te voorkomen.

Bestanden

Presentatie

Benchmarks.zip