Clean Code - Hoofdstuk 5 en 6

Ingediend door Dirk Hornstra op 07-mrt-2019 19:18

Eind november 2018 hebben we bij het backend-overleg hoofdstuk 1 en 2 doorgenomen (link), in januari 2019 hoofdstuk 3 en 4 (link). Tijd om door te gaan met de volgende twee hoofdstukken van Uncle Bob.

Hoofdstuk 5.
Dit hoofdstuk gaat over "formatting".

Als je dacht dat de belangrijkste vaardigheid van een goede programmeur het "werkend krijgen" van code is, dan zit je fout. Code is nooit klaar, de klant komt met aanpassingen, uitbreidingen en je moet zelf (of een collega) later nogmaals in een editor je code openen om die aanpassingen door te voeren. Zit er dan een goede structuur in en zit het meeste werk in om een goede aanpassing/uitbreiding te bedenken of zit de meeste tijd in het openen van tig bestanden met code omdat je de functie niet kunt vinden en vervolgens ook nog eens per bestand met code door 1200 regels slecht uitgelijnde code moet zoeken? Succes!..... Code formatting/opmaak is een stuk communicatie en daarom het belangrijkste onderdeel van je baan als developer.

Vertical Formatting
We beginnen met het overzicht van een aantal Java projecten met de bijbehorende hoeveelheden regels code per bestand. Bij JUnit, FitNesse en Time and Money kom je gemiddeld rond de 200 regels. Bij Ant en Tomcat heb je echter uitschieters met meer dan 1.000 regels!

Newspaper Metaphor
Het lezen van code wordt vergeleken met het lezen van een krant. Je begint met de titel, de inleiding geeft je een indruk van waar het artikel over gaat en hoe verder je leest, hoe meer je de details krijgt. Als je jouw code ook op deze manier inricht ben je op de goede weg.

Vertical Openness Between Concepts
Het opnemen van lege regels in je code is een goede manier om zaken die bij elkaar horen ook bij elkaar te houden, omdat je een soort visuele scheiding aanbrengt. Als het goed is plak je niet alle functies onder elkaar, maar plaats je daar een blanco regel tussen.

Vertical Density
Tegenhanger van het scheiden door lege regels is het bij elkaar houden van zaken die juist bij elkaar horen. Als je boven al je variabelen voor een class declareert, dan zul je dit bij elkaar houden. 

Vertical Distance
Hou zaken die bij elkaar horen bij elkaar horen ook bij elkaar. Als je in een class een functie, Add, Delete, Update hebt en nog een functie Print, een functie Debug, een functie LogErrors, zorg dan dat Add, Delete en Update bij elkaar staan en niet dat je 2 functies naar beneden moet bladeren, vervolgens weer 3 functies omhoog, etc.

Vertical Declarations
Declareer je variabelen op het punt waar je ze gebruikt/nodig hebt. Zo houdt je alles bij elkaar. Heb je een tellertje nodig voor een loop, declareer die dan binnen dit statement, zodat er geen verwarring over kan ontstaan dat die variabele "misschien" ook nog ergens anders voor gebruikt wordt.

Instance Variables
De variabelen die je binnen je class gebruikt en waarschijnlijk door zo'n beetje alle functies gebruikt worden. Die declareer je boven in de code en niet ergens halverwege. Volgens Uncle Bob heb je bij C+ nog een scissors rule, waarbij je al die variabelen onderaan plaatst, maar die uitzondering laten we buiten beschouwing: bovenin dus!

Dependent Functions
Soms zijn functies van elkaar afhankelijk. Je hebt een publieke functie A in een class. Die gebruikt een interne functie B, die gebruikt een interne functie C. Nu heeft jouw class nog veel meer functies, maar omdat deze functies eigenlijk "geschakeld" zijn, moet je ze ook in code dicht bij elkaar houden. Met intellisense kun je al heel makkelijk doorklikken, maar als je zelf even een blik op de code werpt en snel ergens doorheen bladert is dit de manier om structuur aan te brengen. Dit punt heeft raakvlakken met Vertical Distance, alleen zijn dat functies die los van elkaar kunnen opereren, maar waarbij je het gevoel hebt dat ze qua werking/functionaliteit bij elkaar horen.

Conceptual Affinity
Dit zijn functies die een bepaalde affiniteit met elkaar hebben. Een 3-tal functies die bijna hetzelfde doen, maar waarbij de parameters verschillen bijvoorbeeld.

Vertical Ordering
Je houdt als volgorde meestal aan dat je eerst functie A in je code plaatst, als functie B wordt aangeroepen binnen functie A, dan plaats je die er onder (en niet er boven).

Horizontal Formatting
Na alle items over verticale positionering komen we nu op de horizontale indeling. Hoe lang ga je je coderegels maken? In de grafiek in het boek zien we dat er veel tot 80 karakters gaan, daarna zet een dalende lijn in. Er wordt verwezen naar de "oude Hollerith regel" van 80 karakters (link), dat was in de tijd van ponskaarten. Uncle Bob geeft aan dat je eigenlijk niet horizontaal moet hoeven scrollen in je editor, maar dat de schermen inmiddels zo groot zijn, dat dit niet meer een goed criterium is. Uncle Bob gaat nog wel eens tot 100 of 120 karakters, maar daar stopt het ook. Zelf werk ik nog wel eens op het schermpje van een MacBook en dan is 80 karakters een goed criterium.

Horizontal Openness and Density
Je kunt in regels de statements aan elkaar plakken (a=b+c*(4+3)), maar ook hier spaties toevoegen om het geheel leesbaar te houden: a = b + c * (4 + 3). 

Horizontal Alignment
Misschien heb je dit in het verleden wel eens gedaan. Bij de declaratie van de variabelen de boel onder elkaar zetten zodat het als rechte lijnen uitlijnt. Komt er een variabele bij met een langere naam, moet je alles weer opvullen met spaties. Niet (meer) doen, het is een stuk code, niet een kruiswoordpuzzel. En als je dit met veel pijn en moeite voor elkaar hebt komt een bepaald opschoningstooltje voorbij en die haalt alle (overbodige) spaties weg.

Indentation
Inspringen dus. Je hebt een if-statement, de statements die daaronder vallen moet je (dus) inspringen, zodat bij een snelle scan van de code je in één keer ziet wanneer iets wel of niet uitgevoerd wordt.

Breaking indentation
De verleiding is groot om hele korte if/while-statements in één regel te plaatsen, dus je slaat de indentation over. Doe het niet. Als je namelijk een (groot) stuk code scant, bestaat het risico dat je daardoor dit stukje over het hoofd ziet en als je daar net een while(true)... hebt staan die je applicatie vast laat lopen, dan maakt het je zoektocht alleen maar langer.

Dummy Scopes
Een if of while statement, waarbij het if of while-statement een actie doet (byte lezen in een buffer) en daarbinnen niets gedaan wordt. Ook hier weer dat het op 1 regel staat en je dus over het hoofd kunt zien dat er een mogelijk langdurige actie uitgevoerd wordt. 

Team Rules
De afspraken hoeven niet je voorkeur te hebben, werk je liever met tabs, maar spreek je als team af met een vast aantal spaties te werken, dan is dat dé afspraak. Als team beheer je de code, dan moet je een consensus hebben over hoe je het met zijn allen gaat onderhouden.

 

Hoofdstuk 6.
Dit hoofdstuk gaat over "objects and data-structures".

Data Abstraction
Gooi je je class helemaal open zodat aanroepende code die het object creëert alles aan kan passen, of scherm je het netjes af met get/set-functies? Die laatste optie heeft de voorkeur, omdat mocht intern de datastructuur veranderen je geen problemen krijgt met "externe" code.

Data/Object Anti-Symmetry
Data-objecten zijn volledig open en bevatten geen functies, terwijl objecten juist afgeschermde data bevatten en met functies het lezen/schrijven van waardes afhandelen.

The Law of Demeter
Op wikipedia staat de uitleg van deze wet: link. Als je een class A hebt met een functie "test", dan mag die functie "test" alleen methoden aanroepen van class A, van een object wat gemaakt is door functie "test", een object wat via de aanroep van de functie "test" meegegeven is en een instance-variabele van class A. Als deze aanroepen je een object teruggeven, mag je daar geen functies van aanroepen (dat zijn "strangers", "vreemden").   

Train Wrecks
Stel, je hebt een class A die een object b heeft van class B. Intern heeft class B een object van class C, class B heeft een functie getC die het interne object van class C teruggeeft. Je zou dan in class A een actie b.getC().properties aan kunnen roepen. Maar dat moet je dus niet doen! Want als de maker van class C besluit properties te hernoemen naar elements, en hij build class B met die update, dan kan onze code niet meer werken, omdat we een niet bestaande property aanroepen.

Hybrids
Er schijnen hybrides te zijn die datastructuur zijn, maar ook functies bevatten. Volgens Uncle Bob moet je dit zeker niet gebruiken omdat het "evil" is. Gelukkig kan ik ook geen voorbeeld bedenken dat ik hier gebruik van heb gemaakt ;)

Hiding Structure
In plaats van dat je een eigenschap opvraagt en daar zelf nog acties op uit moet voeren, kun je beter in het object wat de property bevat een functie maken die jou het juiste resultaat teruggeeft.

Data Transfer Object
Een DTO is een class met alleen maar publieke variabelen en geen functies. Ze worden vaak gebruikt bij berichten van sockets, communicatie met databases. Een andere term die Uncle Bob dropt zijn "beans", dit is een class met private variabelen die via publieke getters en setters te benaderen zijn.

Active Record
Een active record is een speciaal soort DTO. Komen qua structuur ook wel overeen met beans, maar bevatten ook een find en save functie. Dit type objecten zijn vaak representaties van databasetabellen of andere databronnen. Volgens Uncle Bob moet je hier verder afblijven en niet zelf business-rules aan toevoegen maar ze als een gewone datastructuur behandelen en losse objecten aan te maken die deze active records bevatten, maar zelf de afgeschermde businessrules implementeren.

 

Nawoord
Uit hoofdstuk 5, Team Rules, ook bij mijn huidige werkgever hebben we afspraken gemaakt. Want het kwam wel eens voor dat bij het inchecken van code alle tabs door spaties waren vervangen (of andersom) waardoor er zoveel "wijzigingen" waren, zodat je de echte aanpassing bijna niet meer kon zien. We gebruiken in Visual Studio de plug-in CodeMaid, inmiddels al een aantal jaar naar volle tevredenheid! Wat me deed denken aan "maar als ik het nu in Visual Code bekijk, heb je daar ook een (goede) plug-in voor"? Een snelle zoekactie stuurt me naar Reddit (link), maar zoals je kunt lezen is er (nog) geen alternatief voor VS Code. Beautify wordt genoemd (link), maar die richt zich niet op .NET. Nu is het ook niet spoedeisend, misschien nog wel eens een leuk project om als basis te gebruiken om zelf een plug-in voor VS Code te maken!

Je kunt in Visual Studio bij de eigenschappen van Codemaid een Import en Export uitvoeren. Als je de onderstaande instellingen opslaat als een codemaid.config bestand kun je die importeren en heb je dezelfde settings:


 

<?xml version="1.0" encoding="utf-8"?>
<configuration>
     <configSections>
          <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="SteveCadwallader.CodeMaid.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
          </sectionGroup>
     </configSections>
     <userSettings>
          <SteveCadwallader.CodeMaid.Properties.Settings>
               <setting name="Cleaning_SkipRemoveAndSortUsingStatementsDuringAutoCleanupOnSave" serializeAs="String">
                    <value>False</value>
               </setting>
               <setting name="Cleaning_AutoCleanupOnFileSave" serializeAs="String">
                    <value>True</value>
               </setting>
               <setting name="Cleaning_PerformPartialCleanupOnExternal" serializeAs="String">
                    <value>2</value>
               </setting>
               <setting name="Cleaning_ExclusionExpression" serializeAs="String">
                    <value>\.Designer\.cs$||\.Designer\.vb$||\.resx$||\.min\.css$||\.min\.js$||\.cshtml$</value>
               </setting>
          </SteveCadwallader.CodeMaid.Properties.Settings>
     </userSettings>
</configuration>

Hoofdstuk 6 bespreekt objecten en data-structuren. Ben ik zelf ook wel eens tegenaan gelopen, want gebruik je nu een class of een struct? Dit kwam ook terug in de voorbereiding voor certificering, in het boek van 70-483 heb ik aan het begin van de blogpost de verschillen tussen een class en struct besproken: link.