Qu’est-ce que c’est Rubyesque?

12 April 2010 9:21 Iain Hecker Ruby

Onlangs was er bij Finalist een discussieavond en presentatie over de filosofie van Ruby en hoe Rubyisten graag programmeren. Ruby code volgens deze filosofie noemen we ‘Rubyesque’. Hierbij was het mijn taak om een presentatie te geven over wat Rubyesque inhoudt, waarna we met z’n allen er over zouden discussiëren.

De aanleiding was het feit dat we op zoek zijn naar meer Rubyisten om ons team te versterken. Deze avond was om te kijken of er mensen binnen Finalist waren die nu nog geen Ruby doen, maar daar wel interesse in hebben en wilden weten of Ruby hun aansprak. Verder is een dergelijke discussie natuurlijk leerzaam voor alle betrokkenen.

Het was mijns inziens een succesvolle avond, dus bij deze een “korte” samenvatting.

Over programmeren en de ‘flow’

Het gaat hier allemaal over meningen en meningen verschillen. Door veel blogs en code te lezen meen ik te zien dat er toch enige concensus bestaat binnen de Ruby community over wat Rubyesque inhoudt, alhoewel dit niet zo vaak expliciet uitgesproken wordt. Om die reden pretendeer ik ook niet compleet te zijn, of de absolute waarheid te spreken.

Programmeren wordt vaak met allerlei andere vakken vergeleken om maar uit te leggen hoe men denkt over zijn/haar vak. Binnen de Ruby community wordt programmeren het meest vergeleken met een ambachtelijk vak. Ik zou graag zelfs een stapje verder gaan door het programmeren te vergelijken met kunst of het spelen van een muziekinstrument. Programmeren is een creatief proces en je probeert met hetgeen je maakt betekenis uit te drukken. Dat kan alleen als je het instrument wat je gebruikt goed onder controle hebt.

Elke programmeertaal heeft zijn eigen stijl, zijn eigen ‘flow’. Het is verstandig om deze flow te volgen. Het is makkelijker om te doen wat de makers van de programmeertaal in gedachten hadden dan er tegenin te gaan. Probeer bijvoorbeeld geen inheritance te doen in een taal die d.m.v. prototyping werkt. Als je de flow van een taal volgt dan is je code ook beter onderhoudbaar door je medeprogrammeurs.

Over Ruby

In Ruby is alles gericht op de blijheid van de programmeur. Immers, een blije programmeur is beter gemotiveerd zijn uiterste best te doen, levert betere code op, en zal zich verantwoordelijk voelen om geen slechte code over te dragen. En van betere code wordt iedereen blij.

Het is de computer die het zware werk moet doen en niet de programmeur. Dit betekent dat Ruby niet bijzonder snel is, maar omdat computerkracht goedkoper is dan mankracht, is dat niet heel erg.

Ruby heeft een aantal kenmerken om het leven van de programmeur leuker te maken:

  • Dynamisch en open om makkelijk zaken naar je hand te kunnen zetten
  • Weinig regels, dus makkelijker te onthouden
  • Voor vaak voorkomende taken zijn ‘shortcuts’
  • Veel mogelijkheden om code te herbruiken (zie bijv. mijn post over modules)
  • Optionele interpunctie om leesbaarder te zijn
  • Methoden zoals attr_accessor om jezelf minder te hoeven herhalen

Dynamisch

Bij een dynamische taal is het niet belangrijk welk type een object heeft, maar wat het kan. Dit heet duck typing. Zolang een object maar kan wat het moet doen, maakt het niet uit wat het precies is. Niet alleen maakt dit het maken van mock-objecten erg makkelijk, je bent ook een hoop kopzorgen kwijt en je hoeft inheritance niet te misbruiken om aan functie definities te voldoen.

Een mooi voorbeeld hiervan is de Rack specificatie. Het zegt over de headers alleen dat het een object moet zijn dat reageert op de methode each. Dit geeft programmeurs erg veel vrijheid, ze kunnen een hash gebruiken, maar ook een eigen object, zonder dat het hoeft over te erven van hash.

De flow in Ruby is dat er zelden gecontroleerd wordt welke objecten als parameters gebruikt worden. De controle op correct gebruik van objecten wordt als storend ervaren en leidt af van de bedoeling van de code. De verantwoordelijkheid van het juist gebruiken van een library ligt bij de gebruiker en niet de maker van de library.

Open

In Ruby is niks definitief. Dit zorgt ervoor dat je dingen kan toevoegen en veranderen in elk object, inclusief core classes. Dit wordt monkey patching of duck punching genoemd. Dit zorgt ervoor dat je nog mooiere een leesbaardere code kan schrijven, zoals:

15.minutes.ago

Niemand hoeft uit te leggen wat deze code doet. Niemand hoeft documentatie te lezen om te begrijpen wat de intentie is van deze code. Monkey patching helpt enorm bij het verduidelijken van je code.

Ik begrijp dat dit voor sommige mensen wild en extreem kan overkomen. Het past niet binnen de strakke regels die gelden in andere talen. Toch zijn dit soort technieken fijn en lichtelijk verslavend. Het is makkelijk in gebruik en maakt je code zo kort en duidelijk, dat wanneer ik in een andere taal werk ik me erger aan het feit dat ik soms iets meer moeite moet doen om hetzelfde te bereiken.

Monkey patching is wel gevaarlijk. Je bent zelf verantwoordelijk voor het verantwoord omgaan met deze techniek. Gebruik tests om ervoor te zorgen dat je geen dingen overhoop haalt die je niet bedoeld had.

Interpunctie

Ruby staat toe dat je, zolang het niet ambigu wordt, haakjes weg te laten. En dat doen Rubyisten graag. Het is voor een groot deel een kwestie van smaak, maar in de ogen van de meeste Rubyisten is het belangrijk dat code goed en makkelijk te lezen moet zijn.

Daarom schrijven we liever dit:

class Post
  belongs_to :author, :dependent => :destroy
end

Dan de variant met alle interpunctie in beeld:

class Post
  belongs_to(:author, {:dependent => :destroy})
end

Het is niet belangrijk dat {:dependent => :destroy} een hash is. Het is belangrijk dat de relatie afhankelijk is van de auteur en dat de post vernietigd wordt als de auteur vernietigd wordt. Dat is namelijk de betekenis van de code. Hetzelfde geldt voor belongs_to. Het is niet interessant dat dit een methode is en dat de rest de parameters zijn.

Door interpunctie te schrappen kunnen onze hersenen zich beter focussen op wat er staat, in plaats van welke objecten er gebruikt worden om dit te bereiken.

Weinig regels, veel shortcuts

Ruby kent maar een handje vol taal constructies. Er zijn drie taal-elementen, namelijk Objecten, Methoden en Closures. Er is dus niks bijzonders aan classes en modules. Het zijn gewoon objecten. Voor de rest zijn er een paar taal constructies zoals if, while, en logica operators zoals && en ||.

Verder zijn de meeste keywords shortcuts naar taal-constructies of methoden die vaak voorkomende taken makkelijker moeten maken. Zoals het definiëren van een klasse:

class Car < Vehicle
  # ...
end

Dit wordt door de Ruby interpreter gelezen als:

Car = Class.new(Vehicle) do
  # ...
end

Dit is dus gewoon een methode die aangeroepen wordt op een object. De eerste parameter is een ander object en een closure. Je kan het zelf ook zo schrijven en er mee spelen als je dat wilt (zoals dynamisch een enorme lijst van klassen maken).

Een paar andere voorbeelden:

counter += 1                   # met shortcut
counter = counter + 1          # zonder shortcut, maar ook zonder interpunctie
counter = counter.+(1)         # zonder shortcut, met alle interpunctie (ja, + is gewoon een methode)
 
@user ||= User.new             # dit wordt de 'teapot'-operator of de 'or-or-equals' genoemd
@user = @user || User.new      # als @user bestaat gebruik die dan, anders maak een nieuwe user en stop die in de @user variabele
 
exit unless busy or cancelled  # 'unless' is een shortcut voor 'if !()'
exit if !(busy or cancelled)   # 'if' achter een zin is ook een shortcut
if !(busy or cancelled)        # voor 'if' er omheen
  exit
end

Er zijn nog veel meer shortcuts. Waarom doet Ruby dit? Waarom staat Ruby toe dat je soms dingen iets anders formuleert? Het gaat niet alleen om leesbaarheid, maar vooral om de wens dat je makkelijk de vertaalslag kan maken tussen wat er in je hoofd zit en de code.

Hoe kleiner de stap is van gedachten naar code, hoe makkelijker het is om te programmeren. In Ruby heb je alle vrijheid om jezelf uit te drukken zoals je zelf wilt. Dat hoeft niet noodzakelijk goed uit te pakken. Als je een onduidelijk beeld hebt van het probleem, schrijf je onduidelijke code. Er is niks wat je tegenhoudt om slechte code te schrijven. Het mes snijdt aan twee kanten.

Intentie

Code hoort zijn intentie duidelijk te maken. Ruby code is Rubyesque als het de methoden gebruikt die ik net beschreven heb om de intentie duidelijk te maken. Omdat Ruby zo'n heldere en schone syntax heeft, worden de meeste DSL's (Domain Specific Languages) geschreven in Ruby zelf.

De naamgeving van variabelen en methoden is erg belangrijk. Als je de code leest moet het in 1x duidelijk zijn wat de bedoeling is van de code.

Een fantastisch voorbeeld is Rspec, een van de meest gebruikte test frameworks van Ruby:

describe Post do
 
  context "when it's not approved" do
    subject { Post.new(:approved => false) }
    it { should_not be_publishable }
  end
 
  context "when it's approved" do
    subject { Post.new(:approved => true) }
    it { should be_publishable }
  end
 
end

De gehele Ruby trukendoos wordt gebruikt om ervoor te zorgen dat ik makkelijk mijn specificaties en tests kan schrijven. Dit lijkt misschien niet meer op "normale" Ruby code, maar dat is het nog steeds. Niks meer dan objecten, methoden en closures.

Het Rubyesque hier aan is wat Rspec je toe in staat stelt. Er is zo min mogelijk afleiding van de betekenis. Ik kan in een oogopslag zien of mijn code nog wel voldoet aan de wensen van de klant en door de test te draaien zien of de code werkt zoals gespecificeerd. En nog fijner: ik hoef er amper moeite voor te doen! Rspec maakt Test Driven Development erg eenvoudig.

Geïnteresseerd naar de code bij dit voorbeeld? Kijk hier.

Kort en bondig

Het is Rubyesque om dingen zo kort mogelijk op te schrijven. Dat betekent niet alleen korte regels (ik vind een regel tussen 80 en 100 karakters een acceptabele lengte), maar ook korte methoden. De code smell detector Reek zal bijvoorbeeld al een waarschuwing geven als je methode langer is dan 6 regels. Mag je dan geen langere methoden schrijven? Natuurlijk wel. Is het verstandig? Nee.

Korte zinnen zijn namelijk veel beter leesbaar voor mensen. En het zijn mensen die de code moeten onderhouden en begrijpen. En zo zijn we weer terug bij de filosofie achter Ruby. Het is gemaakt om begrijpbaar te zijn voor mensen. Dat de computer er meer moeite voor moet doen om het te begrijpen is minder belangrijk.

Conclusie

Ruby is een vrije taal, waarin heel veel mag en kan. Het doel is om blij te worden van je werk. Dit is gedaan door een taal te maken die dicht bij menselijke taal staat. Dit zorgt ervoor dat je begrijpelijke code kan schrijven. Als je programmeert in Ruby probeer dan dezelfde doelstelling aan te houden.

De meeste mensen komen vanuit Java of PHP en dan lijkt Ruby een vage en magische taal. Dat valt alles mee. Ruby is makkelijk te leren en te begrijpen. De basis is erg klein. Rubyisten programmeren niet volgens strakke regels en dogma's, maar experimenteren met verschillende interpretaties van hetzelfde probleem.

Hopelijk heb je nu een idee gekregen van de filosofie achter Ruby. Ik zou iedereen adviseren rond te kijken naar de verschillende filosofiën van andere talen en te kijken bij welke filosofie je jezelf het beste thuis voelt. Voel jij je thuis bij de Ruby filosofie en heb je de discipline die nodig is om Ruby te kunnen schrijven, dan kan ik je Ruby van harte aanbevelen.

10 reacties »

  1. Leuk artikel, jammer dat ik niet bij de presentatie kon zijn.
    Wat ik me wel afvraag na het lezen van dit artikel: wat onderscheid Rubyesque nu precies van algemeen aanvaarde best practices? Behalve het stukje over het voordeel van het dynamisch zijn van Ruby, kun je de rest ongeveer ook teruglezen in Code Complete of andere boeken die niet Ruby-specifiek zijn. Volgens mij zijn dit gewoon best practices waar bijna iedere programmeur zich aan zou moeten conformeren.
    Natuurlijk heeft Ruby zo z’n specifieke dingen, en dat zorgt ervoor dat het anders is dan bijvoorbeeld Java of Groovy, maar dat is nu eenmaal het bestaansrecht van een taal.
    Wel is het natuurlijk zo dat met name Rails veel dingen wat toegankelijker heeft gemaakt en daardoor voor een verandering in de wereld van webframeworks heeft gezorgd. Eigenlijk is dat veel interessanter vind ik :-) (zoals Convention over Configuration, DRY, etc.).

    Je schrijft ook: De controle op correct gebruik van objecten wordt als storend ervaren en leidt af van de bedoeling van de code.
    Alex Payne van Twitter ziet dat toch wel als een nadeel van Ruby voor het gebruik in grote systemen die een lange uptime moeten hebben. In een interview met Bill Venners van Artima schrijft hij: ” I’d definitely want to hammer home what Steve said about typing. As our system has grown, a lot of the logic in our Ruby system sort of replicates a type system, either in our unit tests or as validations on models. I think it may just be a property of large systems in dynamic languages, that eventually you end up rewriting your own type system, and you sort of do it badly. You’re checking for null values all over the place. There’s lots of calls to Ruby’s kind_of? method, which asks, “Is this a kind of User object? Because that’s what we’re expecting. If we don’t get that, this is going to explode.” It is a shame to have to write all that when there is a solution that has existed in the world of programming languages for decades now.”

    Maar goed, in veel real life Ruby applicaties zal je inderdaad niet zo snel tegen dit probleem aanlopen (Java’s static typing zit dan ook regelmatig in de weg).

    Martin Sturm April 12, 2010 12:55

  2. @Martin Sturm: Op zich gaan veel van deze zaken ook voor andere talen op… Alleen Ruby biedt er net de mogelijkheden voor.

    Diederick April 12, 2010 13:20

  3. @martin:

    >> wat onderscheid Rubyesque nu precies van algemeen aanvaarde best practices?

    Ben blij dat jij dat algemene best parctices vindt :)

    >> “You’re checking for null values all over the place.”

    Dat probleem los je niet op met een static typing

    >> There’s lots of calls to Ruby’s kind_of? method, which asks, “Is this a kind of User object? Because that’s what we’re expecting. If we don’t get that, this is going to explode.”

    En dan wat? Raise je een error als je niet het juiste type object krijgt? Dat is ook een explosie. Of log je de boel alleen? Op het moment dat je een object krijgt waar je niks mee kan, is het in dynamische talen al te laat. De code is al aan het uitvoeren. Wat dit met lange uptimes te maken heeft weet ik niet.

    iain April 12, 2010 15:02

  4. Monkey punching??

    Robin April 12, 2010 17:38

  5. Ik was wel bij de avond en heb zeer genoten van de uitleg van Rubyesque. De avond bevestigde hoe ik dacht dat professionele rubyisten denken over hun taal. Als ik ga spelen met een nieuwe taal dan probeer ik het te classificeren naast de anderen die ik al ken. Een van de criteria is hoeveel vrijheid de taal geeft. Een andere is hoe de gevorderde programmeurs zichzelf zien.

    Iedere programmeur die zijn vak al een hele tijd uitoefent zal zeggen dat mens leesbare code belangrijker is dan computer efficiënte code. Dit heeft niets met de taal te maken. Wat leesbaar is, is voor iedereen weer anders. Dit heeft meer te maken of je alles expliciet wilt zien of genoegen neemt met wat de syntax impliceert. Dit heeft meer te maken met de code style discussie dan met de code standards die we allemaal aanhouden.

    Bij het proces van vertellen aan de computer wat die moet doen heb je voor elk aspect een regel nodig. Als er veel regels zijn dan hoef je minder zelf te bedenken. Als er weinig regels zijn moet je de ongedekte aspecten zelf invullen. De vrijheid van een taal hangt heel dicht samen met of iets expliciet moet zijn, een regel heeft, of niet. Als er geen regel is heb je de vrijheid om het zelf in te vullen, maar als je de belangrijke aspecten niet invult zul je zeker in de problemen komen. Sommige talen streven naar ultieme vrijheid. Anderen streven naar een volledige set van regels. Wat voor hippie of control-freak ben jij?

    In het boek “Coders at Work” stelt de interviewer altijd de vraag: Do you consider yourself a scientist, an engineer, an artist or a craftsman? Elke taal is bedacht door een programmeur die in minstens één van deze omschrijvingen past. Een taal erft de eigenschappen van de programmeur. De taal die het beste bij jou past is waarschijnlijk de taal die geschreven is door een programmeur die zichzelf hetzelfde ziet als jou.

    De argumenten om VOOR Ruby te kiezen zouden ook juist redenen kunnen zijn om het niet te doen. Ik ben het daarom helemaal eens met de conclusie.

    Het niet nodig hebben van static typing in Ruby past helemaal bij de filosofie dat “de verantwoordelijkheid van het juist gebruiken van een library ligt bij de gebruiker en niet de maker van de library”. Een vereiste hierbij is dat je kunt zien waar het object aan moet voldoen als het erin gaat. Documentatie van de public API moet dan heel uitgebreid zijn of je moet kunnen zien wat er gebeurd. Ruby levert de laatste optie. Je gaat static typing missen als je niet goed meer kunt achterhalen wat er gebeurd.

    Nico Klasens April 12, 2010 21:59

  6. dynamische talen lenen zich op een heel andere manier voor debugging. Vanuit javascript oogpunt: Afhankelijk van je debug mode zou je al je classes kunnen instrumenteren met error handling. In een enterprise omgeving wil je een gebruiker niet lastig vallen met errors.

    Checken van types hoeft ook echt niet overal, maar in interfaces is het absoluut aan te raden. Dat is wel iets wat je met static typing oplost, maar het brengt ook weer een sloot restricties met zich mee. Je helpt hiermee de gebruiker van de code enorm.

    rikkert April 13, 2010 15:41

  7. Het feit dat Groovy je keuze geeft waar je wel en niet statische typering wil gebruiken vind ik zelf wel een groot voordeel van Groovy boven Ruby (dynamisch) en Java (statisch).

    Nils Breunese April 13, 2010 22:59

  8. @rikkert:

    Gebruikers, als in eind-gebruikers, bezoekers van de website bijv, willen natuurlijk helemaal geen errors zien. In production-mode zal je dus een vriendelijke error-pagina moeten maken, terwijl de errors gelogd worden voor de developers. In development en test-mode wil je die errors gewoon gelijk zien zodra ze gebeuren. Dit heeft niks met statisch of dynamische code te maken.

    De gebruiker van de ‘interface’ is een programmeur. Die wilt tijdens het ontwikkelen ook errors zien.

    Ik ontken niet dat type-restricties in interfaces niet helpen, maar dan alleen in een statische taal. In een dynamische taal als Ruby geef je alleen een andere error die je wellicht toch al gekregen zou hebben. Alles in Ruby is immers runtime. Of ik nu de error krijg dat de eerste parameter een Eend-object moet zijn, en niet een Kat-object of dat de methode ‘vlieg()’ niet bestaat op mijn Kat-object, ik kan met beiden uit de voeten. Ik heb denk ik zelfs meer aan de tweede error, aangezien ik daarmee wel mijn Vleermuis-object kan gebruiken. Als ik als type restrictie ‘Vogel’ zou hebben had ik mijn Vleermuis-object ook niet kunnen gebruiken, terwijl ik alleen geinteresseerd ben in het vlieggedrag.

    Statisch vs. Dynamisch is ook een eindeloze discussie. Ik durf niet te zeggen welke beter is. Ik durf wel te zeggen dat in een taal die dynamisch is, je niet statisch moet proberen te programmeren. Dat levert alleen maar ellende op.

    iain April 14, 2010 13:27

  9. [...] Is de code Rubyesque? [...]

    RailsConf 2010, Tutorials: Avoiding and Fixing Rails Antipatterns | Finalist Developers Blog June 8, 2010 5:09

  10. [...] het artikel wat ingaat op de gedachte achter Rubyesque: “Qu’est-ce que c’est Rubyesque?” kwam al naar voren dat korte zinnen beter leesbaar zijn voor mensen. En dat het mensen zijn [...]

    CoffeeScript, een mooie manier om JavaScript te schrijven | Finalist Developers Blog November 2, 2012 10:21

Reageer

RSS feed for comments on this post · TrackBack URI