CSS onderhoudbaarheid, structuur en performantie (2)

14 januari 2010, 23:17 |

Gisteren schreef ik een artikel over onderhoudbaarheid en performantie in CSS. Dit onderwerp ligt me nauw aan het hart, dus vandaag: het vervolg!

Dus. Waar waren we? CSS is van nature een ongestructureerde taal.
Gelukkig is HTML van nature wél een gestructureerde taal.

Neem nu volgend stukje HTML:

<div id="navigation">
    <ul>
        <li><a href="#">Home</a></li>
        <li><a href="#">Over Wolf's Little Store</a></li>
        <li><a href="#">Freelance</a></li>
    </ul>
</div>

Hier zit een duidelijke structuur in: een <div> met id="sidebar", die een ongeordende lijst bevat. Elk lijstitem bevat dan weer eens een link. Stel dat we dit eruit willen laten zien als een simpele navigatie door middel van CSS. Je zou waarschijnlijk iets schrijven dat lijkt op:

    #navigation {
        padding: 20px;
    }

    #navigation ul {
        background: #EEE;
    }

    #navigation li {
        border: 1px solid #DDD:
    }

    #navigation a {
        display: block;
        padding: 5px 10px;
    }

Deze CSS kan je nog uitbreiden met vanalles, maar ik hou bewust het voorbeeld simpel. Quizvraag! Wat is er hetzelfde in elke regel?

Antwoord: elke regel is een declaratie die van toepassing is op #navigation. Een goeie truuk om je CSS te structureren, die ik van Bramus geleerd heb, is het volgende:

    #navigation {
        padding: 20px;
    }

        #navigation ul {
            background: #EEE;
        }

            #navigation li {
                border: 1px solid #DDD:
            }

                #navigation a {
                    display: block;
                    padding: 5px 10px;
                    color: red;
                }

Door de indentatie maken we de structuur duidelijk: #navigation, met daarin een <ul>, met daarin <li>, en dan <a>.

Stel dat we nu een nieuw stukje aan onze website bouwen, het contentvlak. We breiden onze HTML uit:

<div id="navigation">
    <ul>
        <li><a href="#">Home</a></li>
        <li><a href="#">Over Wolf's Little Store</a></li>
        <li><a href="#">Freelance</a></li>
    </ul>
</div>
<div id="content">
    <h1 id="titel_artikel">Titel artikel</h1>
    <p>Inhoud lorem ipsum dolor <a href="#">sit</a> amet...</p>
</div>

En dus ook onze CSS:

    #navigation {
        padding: 20px;
    }

        #navigation ul {
            background: #EEE;
        }

            #navigation li {
                border: 1px solid #DDD:
            }

                #navigation a {
                    display: block;
                    padding: 5px 10px;
                }

    #content {
        padding: 20px;
    }

        #content ul {
            background: #EEE;
        }

            #content h1 {
                font-family: Helvetica, Arial, sans-serif;
            }

                #content a {
                    color: black;
                }

Door de techniek van indentatie kan je op het zicht zien: er zijn 2 hoofdblokken, #navigation en #content. Beide hebben specifieke regels.

Wat belangrijk is, is dat als ik iets aanpas in #content, dat dat geen invloed heeft op #navigation. Een principe dat programmeurs toepassen in objectgeoriënteerd programmeren.

De oplettende lezer ziet hier al meteen een optimalisatie. Zoals ik gisteren zei: er zijn eigenlijk twee factoren die het verschil maken tussen goede CSS en een rommeltje.

Er is de ene kant, die noem ik onderhoudbaar:

  1. Je CSS is onderhoudbaar
  2. Je CSS is duidelijk en goed gedocumenteerd, zodat anderen die hierin werken niet verloren lopen en kunnen werken

En de andere kant noem ik performant:

  1. Je CSS file is best zo klein mogelijk
  2. Je hebt best zo weinig mogelijk CSS files

De performance kant is de kant die we kunnen optimaliseren. We kunnen deze CSS namelijk kleiner maken:

    #navigation,
    #content {
        padding: 20px;
    }

        #navigation ul,
        #content ul {
            background: #EEE;
        }

            #navigation li {
                border: 1px solid #DDD:
            }

                #navigation a {
                    display: block;
                    padding: 5px 10px;
                }

    #content h2 {
        font-family: Helvetica, Arial, sans-serif;
    }

        #content a {
            color: black;
        }

In theorie is dit juist, echter is dit weer zwaar in strijd met de onderhoudbaarheid. Dat #navigation en #content allebei een padding van 20 pixels is louter toevallig.

Ja maar Wolf, je hebt gisteren gezegd, als je dezelfde waardes op 2 plaatsen moet aanpassen, is dat niet goed.

Klopt. Maar deze situatie is anders: er is geen relatie tussen #content en #navigation. Als het ontwerp van de website verandert, ga je voor #content moeten bekijken of de padding nog klopt. En je gaat voor #navigation moeten kijken of de padding nog klopt.

Dit is geen goed idee. Voor je het weet heb je een CSS file waar regel 4 verwijst naar iets dat invloed heeft op regel 921.

En als je dan nog eens browser-specifieke code schrijft in verschillende files wordt het helemaal leuk. En dan loop je tegen een deadline aan, en krijg je opeens een paar bug reports binnen. Van een site die nota bene live staat. Van een site waar toevallig die dag een grote budgetteringsvergadering over is. Oeps.


Mijn held in software is Joel. Joel heeft ooit voor Microsoft gewerkt en heeft al zoveel slimme dingen op het internet gezet dat ik uiteindelijk zijn boek heb gekocht. En het op enkele nachten heb uitgelezen.

Maak niet de fout dat dit een boek voor programmeurs is! Iedereen die voor een bedrijf werkt dat websites en/of webapplicaties maakt zou dit moeten lezen. Zelfs als je project manager bent, of informatiearchitect.

Dat terzijde. Eén van de bekendste artikelen, en meteen ook het beste artikel in het hele boek, is The Joel Test: 12 Steps to Better Code.

Stap vijf om betere code te schrijven is: Do you fix bugs before writing new code?

Er zijn verschillende soorten oorzaken van bugs. Voor het gedeelte waar we het nu over hebben, een degelijke frontend schrijven, zijn de meest voorkomende oorzaken:

  1. Rendering engine bugs: door een andere interpretatie, eventueel een foute interpretatie, van de HTML en/of CSS spec wordt een element niet consistent weergegeven tussen verschillende browsers
  2. Wrong reference: CSS code verwijst naar #lollipop, en in de HTML staat er #lollyPop
  3. Syntax errors: Een punt komma of een komma verkeerd en je CSS regel stopt met renderen. Sommige browsers zijn lakser in syntax errorsA.
  4. Specifity: één regel overschrijft de andere, terwijl je juist wil dat de andere regel wordt gebruikt

Er zijn ongetwijfeld nog andere oorzaken van bugs. Maar, ik weet niet of het je opvalt, 3 van de 4 soorten oorzaken zijn de oorzaak van diegene die de code geschreven heeft.

Veel beter dan een bug oplossen is een bug voorkomen.


Hoe voorkom je deze bugs?

Rendering engine bugs

Ha. Hier heb je meteen al pech. Je kan deze bugs niet voorkomen. De rendering bug is er, en als je deze perfect normale CSS regel schrijft:

#sidebar {
    float: left;
    width: 200px;
    margin-left: 20px;
}

Dan trigger je de double margin bug. Ik weet het, ik val in herhaling: ik heb deze bug gisteren al aangehaald. Waarom dit voorbeeld? Het is de gemakkelijkste IE bug.

Wat ik zeg klopt eigenlijk niet. Dit soort bugs kan je wél voorkomen. Maar dit soort bugs voorkomen verreist een uitgebreide kennis van alle rendering engines, hoe ze werken, wat er ondersteund wordt en wat niet, en alle quirks en speciale gevallen.

En dat kan je alleen maar leren door heel veel websites te maken en die bugs tegen te komen, en ze dan eigenhandig op te lossen. Eventueel met de hulp van het internet. Bijvoorbeeld door een vraag te stellen op Stack OverflowB, waar er meer dan genoeg mensen zitten die met plezier de allersimpelste CSS vragen voor jou beantwoorden.

Moet ik mijn websites dan bewust heel simpel vormgeven? Het antwoord is, zoals met zovele dingen in het leven, ja en nee. Je moet een balans vinden tussen de complexiteit van je vormgeving en de uiteindelijke uitvoering in HTML en CSS.

Een tabel om mijn punt te maken:

# Taak Moeilijkheidsgraad
1 Slagschaduw op een vast beeld Gemakkelijk
2 Slagschaduw op een <div> met vaste hoogte en breedte Moeilijker dan 1
3 Slagschaduw met 1-kanaalstransparantie (gif) op een <div> met vaste hoogte en breedte Moeilijker dan 2
4 Slagschaduw met 24-kanaalstransparentie (png24) op een <div> met vaste hoogte en breedte Moeilijker dan 3
5 Slagschaduw met 24-kanaalstransparentie (png24) op een <div> met dynamische hoogte en vaste breedte Moeilijker dan 4
6 Slagschaduw met 24-kanaalstransparentie (png24) op een <div> met dynamische hoogte en breedte Moeilijker dan 5
7 Slagschaduw met 24-kanaalstransparentie (png24) op een <div> met dynamische hoogte en breedte die potentieel dynamisch van hoogte en breedte kan veranderen via een Javascript animatie Moeilijker dan 6
8 Slagschaduw met 24-kanaalstransparentie (png24) op een <div> met dynamische hoogte en breedte die potentieel dynamisch van hoogte en breedte kan veranderen via een Javascript animatie, en van opacity moet veranderen Moeilijker dan 7

Moest je aan deze tabel nog een kolom Browser support toevoegen, en in elke cel zetten Werkt correct in A-grade browsers, dan wordt alles nog een graadje moeilijker. Moest je zeggen: werkt in Webkit based browsers, wordt alles opeens heel gemakkelijk.

De eerste methode om een slagschaduw te maken: je slaat een beeld op, geeft het beeld weer, en klaar is kees. Maar deze methode is in strijd met de onderhoudbaarheid, en ook met de performantie. Zeker als je meerdere beelden met telkens dezelfde schaduw hebt.

Opdracht 2 is ook gemakkelijk: je gebruikt het beeld als achtergrond. Ik ga niet in detail uitleggen wat je allemaal moet doen om 8 werkende te krijgen, maar als je daar geraakt, ben je volgende problemen teggengekomen, afhankelijk van welke oplossing je kiest:

Terug naar de vraagstelling: moet ik mijn websites dan bewust heel simpel vormgeven? Nee, als je weet wat je doet. Als je weet dat je binnen het budget en de tijd van een project oplossing 8 kunt uitvoeren, dan kan je met een gerust hart 8 zo vormgeven.

Dat veronderstelt natuurlijk wel dat jij het ontwerp gemaakt hebt dat je aan het coden bent. Dit is niet voor iedereen het geval. Dus tik die designer op zijn vingers als hij zaken doet die niet realistisch zijn.

Oplossing 8 is niet onmogelijk. Het kan. Maar een project heeft een budget. En een project heeft een deadline. Ik heb eens tegen een relatief technisch onderlegde klant volgende vraag gesteld: heb je graag dat ik een dag spendeer aan coole feature X of dat ik er vandaag voor zorg dat die fucking slagschaduw werkt in Internet Explorer 6?

Het antwoord was, uiteraard, werk aan coole feature X. Geluk kan soms toch in simpele dingen zitten.

Wrong reference

Een tigtal paragrafen terug gaf ik dit voorbeeld voor wrong reference-type bugs:

CSS code verwijst naar #lollipop, en in de HTML staat er #lollyPop

Phil Karlton, in de jaren stillekes van browserland verantwoordelijkvoor de architectuur achter wijlen Netscape, zei:

There are only two hard things in Computer Science: cache invalidation and naming things.

Over die cache invalidation kan ik niet meespreken — ik ben er zeker van dat er nu een paar programmeurs luidop aan het zuchten zijn, en zich een situatie herinneren waar cache invalidation hen uuuuren werk heeft gekost — maar wat betreft het naming gedeelte: een volmondige JA.

Soms is het duidelijk wat voor naam je iets moet geven. De inhoud gaat in #content. En de navigatie in #navigation. Dit zijn conventies die vele mensen gebruiken, die al jaren meegaan, die algemeen begrepen worden.

Meestal moet je rond de #content en de #navigation nog een wrapper steken om layoutredenen. Hier komt het naamgevingprobleem naar boven. Mijn wrappers van de hoofdinhoud heten meestal #main.

Wat voor een kutnaam is #main

Dat is een beetje als een stukje van je software “extras” noemen. Wie weet er nu waar je het over hebt als je #main gebruikt?

Wel. Er is geen oplossing voor het benoemen van dingen. Wat je wel kan doen om zoveel mogelijk misverstanden te vermijden, en zo dus bugs (of liever de oorzaak van bugs) te voorkomen is duidelijk afspreken binnen je team wat voor namen je gaat gebruiken.

Dit heb ik even gepikt van onze bedrijfswiki. Ik heb het zelf geschreven, en de basis is gelegd door meneer Dextro, dus dat mag.

Gebruik logische Engelstalige namen voor classes en id’s.

Vermijd zoveel mogelijk benamingen zoals #left en #right, deze hebben de neiging te veranderen en dan niet meer te kloppen achteraf e.g. #right staat dan links, en het geheel wordt verwarrend

Actieve elementen worden aangeduid met ‘selected’ op het li-element. Maak gebruik van descendant selectors in je CSS om deze aan te duiden (e.g.: #navigation li.selected, #subnavigation li.selected ).

Alle classes en ids in camelCase, de eerste letter een kleine letter

Als iedereen deze regels netjes volgt, dan komen er minder problemen voor. Natuurlijk gaat het gebeuren dat je een plugin gebruikt die geen camelCase classes en IDs gebruikt. En iemand gaat het nodig vinden zich totaal niet aan de regels te houden.

Maar! Hoe meer mensen deze regels volgen, hoe minder bugs. Hoe meer je team samenwerkt, hoe beter je op elkaar afgestemd geraakt. T’is een beetje zoals een voetbalploeg.

Syntax errors

Over syntax errors kan ik kort zijn: valideer je HTML en CSS regelmatig op syntax errors, zeker als je een groot stuk code hebt geschreven.

Over de HTML validator kan ik nog een uurtje doorgaan, maar dat leg ik even buiten de scope van dit artikel.

Specifity

Specifity is één van die dingen in CSS waarvan je denkt dat iedereen weet hoe het werkt, en als je dan eens een vraag erover stelt, weet eigenlijk niemand hoe het werkt.

Als je specifity begrijptD, begrijp je ook waarom:

  • De universal margin/padding reset een slecht idee is (* { margin: 0; padding: 0; } E
  • !important gebruiken als het niet nodig is een slecht idee is
  • Mijn indentatie-hierarchie-truuk van in het begin van dit artikel een goed idee is

Ik ga nog eens iets herhalen:

Er zijn ongetwijfeld nog andere oorzaken van bugs. Maar, ik weet niet of het je opvalt, 3 van de 4 soorten oorzaken zijn de oorzaak van diegene die de code geschreven heeft.

Ik zie je gedachten al verdwalen naar de slechtste CSS’er die je gekend hebt in je carrière.

Het is gemakkelijk om andere mensen de schuld te geven voor slechte code. Echter: verbeter de wereld, begin bij jezelf.

Met een deadline in het achterhoofd is het soms moeilijk om het grote plaatje in het oog te houden. Maar je schiet jezelf in de voet door geen nette code te schrijven, de regels van gestructureerde CSS conveniently even te vergeten.

Ik weet het, want ik heb genoeg projecten waar ik dagelijks inkom waar ik nu van denk: what the fuck were you thinking?. En dat zijn dan nog projecten waar ik helemaal alleen de CSS van geschreven heb.

(Ik spendeer zo nu en dan een uurtje aan het herschrijven van de CSS van grotere sites, en categoriseer mijn tijdsregistratie dan onder “IE6 bug”, maar niet tegen mijn baas vertellen hé.)

Misschien moet ik maar eens dieper ingaan op Sass/Compass, een CSS framework dat eigenhandig allerlei structurele CSS problemen oplost door CSS te beschouwen als een programmeertaal. Misschien.


Voetnota’s

  1. Firefox is heel laks in syntax errors en laat vanalles toe dat anders browsers niet toelaten. Firefox heeft een reputatie van de meest solide browser te zijn om tegen te debuggen. Ziet u de correlatie? Ik heb hier helaas geen data over, dus je zal mij moeten geloven.
  2. Hier stelt bijvoorbeeld iemand een klassieke vraag: waarom staat er witruimte onder mijn beeld? Ik wil helemaal geen witruimte onder mijn beeld! De gemakkelijke conclusie is dat de browser die je gebruikt suckt. De juiste uitleg is dat een beeld *by default* een inline element is. Als je op een bovenliggend element een lijnhoogte definieert, dan heeft dat invloed op het beeld. Dit is geen fout: dit volgt perfect de HTML en CSS spec. Om maar te illustreren dat iedereen ergens moet beginnen, en als je vast zit, je soms beter om hulp kan vragen.
  3. Lees meer in dit artikel: Fading a 24-bit PNG in IE7
  4. Een goed artikel om dit te leren is CSS: Specificity Wars. Of als je Andy Clarke niet zo goed kan hebben, kan je terecht op HTML Dog.
  5. Bekijk de effecten van de universal reset t.o.v. form elements in Eric Meyer: Formal weirdness

Eén reactie op “CSS onderhoudbaarheid, structuur en performantie (2)”

  •  
    Simon zei:

    Ik gebruik de indentatie-hierarchie-truuk nu ook al een tijdje en het is inderdaad een goeie truuk! Veel meer overzicht en het loopt gelijk met je html file. Door die truuk toe te passen verhoog je zeker en vast de onderhoudbaarheid. Je moet ook minder commentaar plaatsen, aan de indentatie te zien is het direct duidelijk wat je stukje css juist doet. Ik gebruik het wel enkel voor de structuur, het positioneren van de divs.

    puik artikel


Reacties gesloten

Het is niet meer mogelijk om reacties te geven op dit bericht. Na plaatsing is het gedurende 2 weken mogelijk om te reageren. Dit om de kwaliteit van de uiteindelijke post hoog te houden en spam tegen te gaan. Mocht je toch nog (persoonlijk) willen reageren, zie de contactpagina.