De laatste jaren begint mijn relatie met C++ behoorlijke haat-liefdetrekjes te vertonen. Aan de ene kant is het een enorm krachtige taal waarin je veel problemen prachtig elegant kunt oplossen. Aan de andere kant haal je jezelf nogal wat op de hals als je een groot project in C++ gaat maken: bouwtijden die snel oplopen, afhankelijkheden tussen componenten die lastig te managen zijn en ga zo maar door. En hoewel een stuk moderner geworden met C++11 loopt de taal op sommige gebieden nog steeds achter. Een mooi voorbeeld hiervan vormen enumeratietypes, of enums.
C++-enums zijn niet erg programmeurvriendelijk. Zo bevat de syntax voor het gebruik niet het type, wat de leesbaarheid niet ten goede komt. Er is ook geen standaard om enum-waardes te converteren van en naar een tekstuele representatie, wat vaak nodig is voor loggen en testdoeleinden. Hoewel enums in C++11 wel iets verbeterd zijn (je mag optioneel het type voor de waarde zetten), kan het nog beter. Om die reden hebben we bij Sioux voor een project op ons Development Center alle enums in de volgende vorm geschreven:
namespace Color { enum Enum { RED, GREEN }; std::vector<Enum> values(); std::string name(Enum e); Enum valueOf(std::string str); } std::ostream& operator<<(std::ostream& os, const Color::Enum& e);
Bij deze aanpak moet je voor een waarde het type van de enum noemen, bijvoorbeeld ’currentColor = Color::RED‘ – een stuk leesbaarder. Verder retourneert de functie values een lijst met alle geldige enum-waardes, wat het makkelijk maakt om de enum te itereren met bijvoorbeeld de ’FOREACH‘ van Boost. De functies name en valueOf bieden de mogelijkheid waardes te converteren van en naar een tekstuele representatie. Kers op de taart is operator<<, waarmee enum-waardes direct naar een std::ostream zijn te schrijven. Deze aanpak lost een groot deel van onze enum-perikelen op. Minpuntje is dat je een variabele moet declareren als type Color::Enum.
Het grote nadeel van deze aanpak is echter dat het schrijven van deze constructie en de bijbehorende implementatie een nogal vervelend, repetitief en daarmee foutgevoelig klusje is. Aangezien wij als software-engineers van nature graag zo weinig mogelijk doen, heeft mijn collega Rico Huijbers in Python een script geschreven (onder MIT-licentie beschikbaar op Github) om op een makkelijke manier enums te specificeren en vervolgens automatisch de definitie en implementatie in respectievelijk een .h- en .cpp-bestand te genereren. De definitie van de enum hierboven kan daarmee worden teruggebracht tot:
enumgen.define('colorapp', 'Color', [ ('RED' , 'Red'), ('GREEN' , 'Green'), ])
Een stuk minder typwerk en veel overzichtelijker. Eigenlijk kan codegeneratie helpen in alle situaties waar veel varianten van eenzelfde stuk code moeten worden geproduceerd. Voor hetzelfde project hebben we bijvoorbeeld ook een Python-script gemaakt dat C++-klassen genereert uit een beschrijving van een Can-busprotocol. Een andere mogelijke toepassing is het genereren van klassen die databasetabellen voorstellen. Als er een nieuwe tabel nodig is, hoeft alleen de tabeldefinitie te worden aangepast, waarna alle benodigde code automatisch kan worden gegenereerd.
In feite hebben we met dit script een lichtgewicht domeinspecifieke taal geïmplementeerd. Door een dynamische taal als Python te gebruiken, is het niet nodig om een complete syntax en grammatica te specificeren en de daarbij behorende parsers te implementeren. We gebruiken de flexibele syntax van Python om een mooi leesbare specificatie te schrijven die eigenlijk gewoon uitvoerbare code is. Deze techniek heeft ons veel saai typwerk en ongetwijfeld een aantal bugs bespaard. Het is dan ook een techniek die een permanente plek heeft veroverd in mijn repertoire van softwareontwikkelgereedschappen.