Doe mij maar een statisch getypeerde taal

 
26 augustus 2011

…en ik zal je vertellen waarom. Ik ben gisteren drieëneenhalf uur tijd kwijtgeraakt doordat de schrijver van de Python-API waar ik tegenaanpraat (een relatief onbekend bedrijfje met de naam Google) een kleine inconsequentie bleek te hebben ingebouwd, iets dat met een statisch getypeerde taal binnen een minuut duidelijk was geworden.

Er bestaan verschillende meningen over wat nou precies een ‘dynamische’ taal is, maar voor deze blog bedoel ik ermee dat variabelen, methode-parameters, en ook je member fields ongetypeerd zijn. Je kan er dus altijd willekeurige waarden instoppen, en in de loop van een enkele methode kan jouw myobject.myfield ook bijvoorbeeld eerst een string, dan een integer, en dan een Employee-object bevatten. Ongetwijfeld handig…???

Dat de formele parameters van methoden ook ongetypeerd zijn, betekent dus ook dat je van de documentatie van de API afhankelijk bent om te weten wat voor objecten je erin moet stoppen. Die is niet altijd even specifiek: toen een methode volgens de documentatie ergens een URL-parameter verwachtte, gebruikte ik braaf atom.url.parse_url(url_string) (er werd wel meer gebruik gemaakt van atom) – om vervolgens een exceptie om mijn oren te krijgen. Ik had namelijk gewoon url_string zelf moeten meegeven. Duidelijk!

Dus ik was in zekere zin al gewaarschuwd. Maar toch. Je verwacht het niet: als je een service-wrapper hebt met de methoden InsertEvent(event) en UpdateEvent(event), en de documentatie zegt van beide dat er een event in moet, dan is het natuurlijk vanzelfsprekend uiteraard logisch dat je voor de een moet zeggen

event.Title = atom.Title(text='bla')

en voor de andere

event.Title = atom.data.Title(text='bla')

omdat je als je dezelfde keuze bij beide aanhoudt, je altijd bij een van de twee een exceptie voor je kiezen krijgt. En oja, bij InsertEvent moet er stiekum een lijst events in om het te laten werken, maar dat vertellen we even niet.

Een halve werkdag naar de knoppen terwijl, zoals gezegd, met een fatsoenlijk getypeerde taal dit soort problemen binnen een minuut boven water komen. Grmbl.

Bij een ongetypeerde taal als Python heb je namelijk geen enkele controle over wat voor data er bij je methode binnenkomt. Je kan dus alleen maar hopen dat die data de juiste methoden bevat om jouw methode zijn werk te kunnen laten doen. En andersom kan je die data wel in een andere methode inschieten, maar je moet maar hopen dat die op zijn beurt geen onverwachte methoden op jouw data aanroept. En dat wordt lastig te analyseren als je op een gegeven moment 6, 7 lagen dieper de stack in bent gedoken.

Een typesysteem daarentegen garandeert je dat de binnenkomende data een bepaalde vorm heeft en je er dus bepaalde dingen mee kan doen. En andersom ook dat als je die data meegeeft aan een andere methode, hij ook de juiste structuur heeft om die andere methode goed zijn werk te kunnen laten doen. Bijvoorbeeld, door aan te geven dat je een String verwacht in plaats van een Url, voorkom je dat argeloze gebruikers zoals ik worden geconfronteerd met dat een Url geen length()-methode heeft. Of erger, dus.

Het typesysteem is daarmee een stuk documentatie geworden, en doordat het door de computer wordt afgedwongen weet je altijd zeker dat het (a) volledig en (b) up-to-date is. Ik ben er altijd een groot voorstander van dat de computer je zoveel mogelijk helpt bij het programmeren. Dit soort boekhouding is daar zeker een voorbeeld van.

Ik heb ooit de bewering gehoord dat eigenlijk alles wat met een typesysteem kan, ook met een goeie set (unit) tests kan. Maar dat geloof ik niet. Ten eerste is het perspectief anders: een unit test is erop gericht om te controleren of je software intern goed werkt, maar geeft geen signaal naar de buitenwereld van hoe het gebruikt moet worden. Je kan ermee wel controleren dat als je een String binnenkrijgt, de methode goed gaat, en als je een Url binnenkrijgt, het (evt. op een specifieke manier) fout gaat. Maar je kan niet afdwingen dat je altijd een String binnenkrijgt, omdat je je aanroepers niet onder controle hebt. Een typesysteem doet dat wel.

Ten tweede kan je je afvragen hoe handig het is om, in plaats van te weten dat je een Employee-object in handen hebt, bij elke methode-aanroep die je wilt doen te controleren of het ding wat je in handen hebt wel een getAfdeling()-methode heeft, en of het resultaat daarvan wel een getPostAdres()-methode heeft, enzovoort. En dan is het hopen dat je object niet zijn state verandert dankzij het testen van die aanroepen.

En daarbij moet je ook nog maar eens weten welke aanroepen er gedaan zullen worden; iets wat redelijk lastig is als je een externe API gebruikt. Bovendien breekt het de abstractie van het alleen moeten weten wat een methode doet i.p.v. hoe hij dat doet.

Natuurlijk, het is een redelijke blunder dat de ene methode verwacht dat de fields van zijn binnenkomende data bestaan uit objecten uit de ene module (namespace) terwijl de andere verwacht dat ze uit een andere module komen. Met een statisch getypeerde taal was dat misschien ook nog wel mogelijk geweest, maar dan had de schrijver zich toch wel in wat meer bochten moeten wringen om dat voor elkaar te krijgen.

Uiteraard is een statisch getypeerde taal ook niet alleszaligmakend. Het feit dat je fields ondanks statische typering toch onzindata (zoals null) mogen bevatten is daar al een voorbeeld van (daarover ooit nog een andere blogpost). En het is wat meer werk omdat je de types hier en daar moet aangeven. Maar in elk geval vang je er wel een bepaalde klasse problemen mee op.


Werken met ?
Kijk dan bij onze mogelijkheden voor starters en/of ervaren IT'ers.


Categorieën: Development


Reacties (5)

  • Ik ben het met je eens dat een statisch getypeerde taal het mogelijk maakt om een ontwikkelaar hints te geven over de call signature van een methode. Overigens is het de editor die je deze hints verschaft, maar dat is een ander punt.

    Dat neemt niet weg dat statische typering soms aanvoelt als een dwangbuis. Ik ben het dus niet met je persoonlijke voorkeur eens. Voor mij is het niet zo zwart-wit als: “Doe mij maar een statisch getypeerde taal”.

    Geplaatst op 04 september 2011 om 14:32 Permalink

    • Jasper Stein schreef:

      Hallo Daan,

      Het is niet de editor maar de compiler en/of de onderliggende runtime die die typegaranties afdwingen. Dus ook met een notepad krijg je die garanties.

      Dat een editor je hints kan geven over welke typen er in een methode moeten is natuurlijk ook weer een grote bonus die het programmeerleven een heel stukje makkelijker maakt. 2-0 voor statische talen ;-)

      Inderdaad zijn er met dynamische talen een aantal ‘vieze’ constructies (vanuit een typerings-oogpunt gezien dan) mogelijk zonder overhead. Het is wel makkelijk om object.get(index) te kunnen zeggen tegen zowel een dictionary als een CookieCollection als tegen een Request zonder dat je een ISupportsGet-interface hoeft te schrijven of objecten eerst moet wrappen. Dus idd is het niet zwart-wit. Maar voor mij wegen de nadelen toch op tegen de voordelen.

      Geplaatst op 05 september 2011 om 11:25 Permalink

      • Erik Mulder schreef:

        Hee Jasper,

        Ik ben het helemaal met je eens, doe mij maar een statisch getypeerde taal! :)
        Zeker met de .NET lambda mogelijkheden wordt het gebruik van extra interfaces eerder een lust dan een straf.

        Er zijn zelfs statische taal freaks die met het concept typering nog een stapje verder gaan. Bijvoorbeeld dat de compiler door middel van de signature van een functie kan checken dat deze nooit een infinite loop in kan gaan. Klinkt te gek? Zie bijvoorbeeld http://wiki.portal.chalmers.se/agda/pmwiki.php

        Groeten, Erik

        Geplaatst op 06 september 2011 om 16:08 Permalink

  • Ik wil hier geen flame-war statisch getypeerde talen v.s. dynamisch getypeerde talen starten. Wel wil ik een nuance aangeven bij het gebruik van tests.

    Jij vindt dat “een unit test is erop gericht om te controleren of je software intern goed werkt”. Ik ben van mening dat een test een breder doel kan hebben. Het kan namelijk ook de verwachtingen die jij hebt over een API testen.

    Je (impliciete) verwachtingen van een API vast te leggen in een test heeft verschillende voordelen. Ten eerste leer je de API kennen. Ten tweede komen onverwachte eigenschappen van de API snel aan het licht. Als laatst heb je een set aan regressie tests voor de API.

    Mocht je een nieuwe versie van de API gaan gebruiken dan toetst de tests of de interne code het op dezelfde manier kan gebruiken.

    Geplaatst op 27 augustus 2011 om 7:13 Permalink

    • Jasper Stein schreef:

      Hallo Daan,

      Je hebt gelijk dat je daar unit tests ook voor kan gebruiken, dat is zeker nuttig, zowel voor statisch als dynamisch getypeerde talen. Immers ook bij een statisch getypeerde taal kan de werking van de API veranderen over verschillende versies, zelfs als de signaturen van de methodes in die API niet veranderen. Iets wat je dan direct detecteert.

      Maar feit blijft wel dat er ook in dat geval nog steeds bepaalde garanties door het typesysteem worden afgedwongen die je anders met een berg extra tests moet controleren. Het schrijven van unit tests wordt ook vergemakkelijkt: ik merk dat ik nu vaak moet gokken wat voor data er in een methode moet, een typesysteem vertelt me dat gelijk. En ik denk dat ze bij Google ook al wel eerder achter hun oren hadden gekrabt als ze zelf de inconsistentie hadden kunnen zien dat voor een update een ander type data nodig is dan voor een insert terwijl het om dezelfde gegevens gaat.

      Het is overigens ook niet mijn bedoeling om een flame-war te starten :-) we zijn per slot met volwassen programmeurs onder elkaar (toch? ;-)). Maar ik ben niet vies van een goeie discussie omkleed met goeie argumenten. En hopelijk ziet iedereen door de ironie heen ipv in gescheld te verzanden :-)

      Geplaatst op 31 augustus 2011 om 9:39 Permalink