In Rust, zijn if-uitdrukkingen een krachtig hulpmiddel voor het schrijven van robuuste en efficiënte code. Het idee dat een if-verklaring in Rust een expressie is, in plaats van een verklaring zoals in talen als C en Java, kan helpen om je code beter te structureren en flexibeler te maken. Dit wordt vaak een meer 'Rustic' manier genoemd van programmeren. Bijvoorbeeld, in plaats van het gebruik van een mutable variabele en een standaard if-verklaring, kun je eenvoudig de waarde van een variabele met behulp van een expressie toewijzen, zoals:
Door deze aanpak toe te passen, kun je de complexiteit van je programma verminderen zonder concessies te doen aan de leesbaarheid en het gemak van het beheer van je code.
Naast het schrijven van eenvoudige, leesbare code, komt de noodzaak voor tests om de stabiliteit van je programma te waarborgen. Rust biedt uitstekende hulpmiddelen voor het schrijven van integratietests, zoals de assert_cmd, pretty_assertions en predicates crates. Deze bibliotheken stellen je in staat om te testen hoe je programma reageert op verschillende invoer, wat essentieel is om ervoor te zorgen dat het werkt zoals verwacht. Om te beginnen met testen, kun je een eenvoudige test maken die ervoor zorgt dat je programma faalt wanneer er geen argumenten worden opgegeven:
Deze test controleert of je programma correct de foutmelding “Usage” afdrukt wanneer het zonder argumenten wordt uitgevoerd. Dit is een cruciaal onderdeel van de gebruikerservaring, omdat een slecht beheer van argumenten kan leiden tot verwarring.
Na deze test kun je ook tests schrijven die ervoor zorgen dat je programma goed functioneert wanneer het een geldig argument ontvangt. Bijvoorbeeld:
Deze test verifieert dat je programma succesvol draait met het argument "hello". Dit soort tests helpt bij het valideren van de basale functionaliteit van je programma.
De volgende stap is om te controleren of de uitvoer van je programma overeenkomt met de uitvoer van een standaardcommando, zoals echo. Je kunt dit doen door een bash-script te maken dat de uitvoer van echo voor verschillende argumenten opneemt en deze uitvoer vergelijkt met de uitvoer van je programma. Het script zou bijvoorbeeld kunnen controleren of je programma de juiste uitvoer genereert voor argumenten zoals "Hello there" of "Hello there" zonder een nieuwe regel.
Het script kan er als volgt uitzien:
Deze uitvoerbestanden kunnen later worden vergeleken met de uitvoer van je eigen programma om ervoor te zorgen dat beide dezelfde resultaten opleveren.
Het testen van de uitvoer kan eenvoudig worden gedaan door de inhoud van de verwachte uitvoerbestanden te lezen en deze te vergelijken met de uitvoer van je programma. Bijvoorbeeld, om te controleren of de uitvoer van je programma overeenkomt met de verwachte uitvoer, kun je de volgende test schrijven:
In deze test wordt de inhoud van het bestand hello1.txt gelezen en vergeleken met de uitvoer van het programma. Dit zorgt ervoor dat je programma daadwerkelijk dezelfde uitvoer produceert als de standaard echo-opdracht.
Het gebruik van de Result::unwrap methode is handig, maar kan gevaarlijk zijn als de bestandsinvoer niet kan worden gelezen. Om dit te voorkomen, is het vaak beter om de ? operator te gebruiken, zodat je kunt omgaan met foutscenario's zonder je programma te laten crashen:
Door deze techniek toe te passen, kun je je programma robuuster maken en beter omgaan met onverwachte situaties.
Het is belangrijk om te benadrukken dat de stabiliteit van een programma niet alleen afhankelijk is van het schrijven van tests, maar ook van de manier waarop je fouten en randgevallen afhandelt. Rust biedt een scala aan hulpmiddelen om ervoor te zorgen dat je programma niet alleen de verwachte functionaliteit biedt, maar ook de juiste foutmeldingen en foutbehandeling implementeert wanneer dat nodig is.
Testen kunnen je helpen om te verifiëren dat je programma goed werkt, maar het is even belangrijk om tests te schrijven die de randgevallen en de niet-standaard uitvoer van je programma dekken. Zelfs met eenvoudige programma's kan de complexiteit snel oplopen, en door uitgebreide tests te schrijven, kun je ervoor zorgen dat je code robuust is en goed presteert in verschillende scenario's.
Hoe werkt het gebruik van reguliere expressies en bestandszoekopdrachten in een programma?
Reguliere expressies (regex) zijn krachtige hulpmiddelen in de wereld van tekstverwerking, waarmee gebruikers patronen kunnen zoeken, analyseren en vervangen binnen tekstbestanden. Dit concept is al sinds de jaren vijftig bekend, toen de wiskundige Stephen Cole Kleene het bedacht. Sindsdien is de syntaxis van reguliere expressies steeds verder verfijnd, vooral door de Perl-gemeenschap, die de Perl Compatible Regular Expressions (PCRE) ontwikkelde. In de meeste hedendaagse programmeertalen, inclusief de tool grep, kan men reguliere expressies gebruiken voor het doorzoeken van tekstbestanden.
Wanneer we werken met de commando's van de tool grep, zijn er verschillende opties die we kunnen toepassen. De optie -E of --extended-regexp dwingt grep om reguliere expressies als uitgebreide expressies te behandelen, wat betekent dat bepaalde geavanceerdere syntaxisopties beschikbaar worden. Bijvoorbeeld, de reguliere expressie (.)\1 kan worden gebruikt om herhaalde tekens in een bestand te zoeken, waarbij . staat voor elk willekeurig teken en de \1 verwijst naar de eerste "capturing group". Dit soort expressies zijn typisch voor een uitgebreide reguliere expressie, en daarom is de optie -E nodig.
In tegenstelling tot de uitgebreide expressies, worden standaard reguliere expressies in grep behandeld als basis reguliere expressies. Dit betekent dat bepaalde syntaxis, zoals het gebruik van haakjes voor het groeperen van patronen, niet werkt tenzij expliciet geconfigureerd met de -E vlag.
Het nut van deze opties wordt duidelijk wanneer je zoekt naar specifieke patronen binnen grote hoeveelheden tekst. Als je bijvoorbeeld zoekt naar de reeks “ee”, kun je eenvoudigweg het patroon ee gebruiken. Maar als je naar herhaalde tekens zoekt, wordt de mogelijkheid om een "capturing group" te gebruiken essentieel. Dit gebeurt door een teken in een groep te plaatsen, gevolgd door de referentie naar die groep, zoals eerder beschreven.
Bij de toepassing van reguliere expressies in een programma komen we echter al snel bij een ander punt: hoe zoeken we nu door bestanden? Het vinden van bestanden die aan specifieke zoekcriteria voldoen, is een taak die regelmatig voorkomt bij het werken met tekstverwerkingstools. Een van de belangrijkste concepten hierbij is het navigeren door een bestandssysteem om de juiste bestanden te vinden om te doorzoeken.
In de context van een programma dat werkt met grep, kan men een functie schrijven die door opgegeven directories zoekt om te bepalen welke bestanden moeten worden doorzocht. Als een gebruiker bijvoorbeeld een directory opgeeft zonder de optie om recursief te zoeken, moet het programma een waarschuwing geven en deze directory niet verwerken. Als de recursieve optie wel wordt opgegeven, moet het programma alle bestanden in die directory en subdirectory's verwerken. Een functie die dit doet, kan eenvoudig worden geschreven met behulp van de Rust-taal en de bijbehorende bibliotheken.
Zo'n functie, bijvoorbeeld find_files, zou een lijst van bestanden of directories aannemen en teruggeven wat de bestandsnamen zijn die moeten worden doorzocht. Daarbij moet het programma controleren of de opgegeven bestanden bestaan en of ze daadwerkelijk leesbare bestanden zijn, geen directories (tenzij recursie is ingeschakeld). De output van deze functie zou kunnen variëren van bestandsnamen tot foutmeldingen die aangeven dat een bestand niet bestaat of dat de directory niet op de juiste manier is opgegeven.
Naast het vinden van de bestanden die moeten worden doorzocht, moeten we ons ook richten op het openen van deze bestanden en het doorzoeken ervan op basis van een opgegeven patroon. Dit kan eenvoudig worden geïmplementeerd door gebruik te maken van bestandsleesmethoden, zoals BufReader in Rust, om elk bestand regel voor regel te lezen. Als het bestand via een parameter wordt doorgegeven, bijvoorbeeld via een standaardinvoer (zoals het teken -), moet het programma automatisch invoer vanuit de terminal lezen in plaats van een fysiek bestand.
Het implementeren van deze logica in een programma kan dus bestaan uit twee hoofdonderdelen: het vinden van de juiste bestanden en het uitvoeren van de patroonzoekopdracht in die bestanden. Beide stappen zijn onmisbaar voor de functionaliteit van de toepassing, vooral als je werkt met een groot aantal tekstbestanden die snel moeten worden doorzocht.
Het belangrijkste hierbij is niet alleen om te begrijpen hoe reguliere expressies werken, maar ook hoe je deze in een groter geheel van bestandsbeheer en invoer/uitvoer kunt toepassen. Bijvoorbeeld, als een bestand niet bestaat of een directory niet goed wordt opgegeven, moeten er duidelijke foutmeldingen zijn die de gebruiker waarschuwen. Bovendien kan het programma, afhankelijk van de configuratie, verschillende bestandsindelingen en zoekparameters ondersteunen, waardoor het een krachtig hulpmiddel wordt voor het werken met tekstverwerkingstaken.
Naast de basisfunctionaliteit van reguliere expressies en bestandszoekopdrachten, moet de gebruiker ook begrijpen hoe de prestaties van de toepassing kunnen worden geoptimaliseerd. Het gebruik van recursie kan bijvoorbeeld prestatieproblemen veroorzaken als het aantal bestanden in een directory groot is. Het is belangrijk om na te denken over het geheugenbeheer en de efficiëntie van zoekopdrachten, vooral bij het doorzoeken van grote hoeveelheden data.
Waarom het Cargo Systeem en Integratietests Essentieel zijn voor Rust Programma's
In veel documentatie wordt het nuttige bericht dat het gebruik van een programma uitlegt aangeduid als de gebruiksinstructie. In dit boek zullen de programma’s die we ontwikkelen ook hun eigen gebruiksinstructies afdrukken. Wanneer je een programma uitvoert via Cargo, kun je de ls-opdracht gebruiken om de inhoud van de huidige werkmap te bekijken. Na het uitvoeren van een programma via Cargo, verschijnt er een nieuwe map genaamd ‘target’. Standaard bouwt Cargo een debug-doel, dus je zult de map target/debug zien, die de build-artikelen bevat. Een typische uitvoer kan er als volgt uitzien:
Je kunt de tree-opdracht gebruiken om alle bestanden die door Cargo en Rust zijn aangemaakt te bekijken. Het uitvoerbare bestand dat we zojuist hebben uitgevoerd, moet zich bevinden in target/debug/hello. Je kunt dit bestand direct uitvoeren:
Samenvattend, Cargo vond de broncode in src/main.rs, gebruikte de main-functie daar om het binaire bestand target/debug/hello te bouwen, en voerde het uit. Maar waarom werd het binaire bestand ‘hello’ genoemd en niet ‘main’? Dit antwoord vinden we in Cargo.toml:
De naam van het project die met Cargo werd aangemaakt, wordt tevens de naam van het uitvoerbare bestand. De versie van het programma en de editie van Rust die gebruikt moet worden om het programma te compileren, worden ook gedefinieerd in dit bestand. De editie bepaalt welke veranderingen er in Rust zijn doorgevoerd die niet volledig achterwaarts compatibel zijn. In dit boek gebruiken we de editie 2021 voor alle programma’s. Dit bestand kan ook een lijst bevatten van externe crates die je project gebruikt. In dit geval heeft dit project op dit moment geen externe crates.
In Rust worden bibliotheken 'crates' genoemd, en het is gebruikelijk om semantische versienummers te gebruiken, zoals major.minor.patch. Een wijziging in de majeure versie geeft aan dat er een breuk is in de publieke applicatie-programmeerinterface (API) van de crate.
Het Schrijven en Uitvoeren van Integratietests
Hoewel het programma "Hello, world!" eenvoudig is, zijn er nog steeds zaken die getest kunnen worden. Er bestaan twee hoofdtypen tests die we in dit boek zullen behandelen. "Inside-out" of unit testing is wanneer je tests schrijft voor de functies binnen je programma, en dit zal ik introduceren in Hoofdstuk 5. "Outside-in" of integratietests zijn wanneer je tests schrijft die je programma uitvoeren zoals de gebruiker dat zou doen. Dit is wat we doen voor het programma dat we nu hebben.
In Rust-projecten is het gebruikelijk om een map ‘tests’ aan te maken parallel aan de map ‘src’ voor testcode. Dit kan gedaan worden met de opdracht mkdir tests. Het doel is om het ‘hello’ programma te testen door het via de opdrachtregel uit te voeren, zoals de gebruiker zou doen. Dit is het eerste, eenvoudige testbestand:
De #[test] annotatie vertelt Rust om deze functie uit te voeren wanneer er getest wordt. De assert! macro bevestigt dat een booleaanse uitdrukking waar is. Het project zou er nu als volgt uit moeten zien:
De Cargo.lock file registreert de exacte versies van de afhankelijkheden die gebruikt zijn om je programma te bouwen. Dit bestand moet niet bewerkt worden. De map src bevat de Rust broncode om het programma te bouwen, de map target bevat de build-artikelen, en de map tests bevat de Rust-code voor de tests. Alle tests in dit boek gebruiken assert! om te verifiëren of een verwachting waar is, of assert_eq! om te controleren of iets de verwachte waarde heeft. Aangezien deze test altijd de waarde ‘true’ evalueert, zal het altijd slagen.
Als we nu cargo test uitvoeren, zou de uitvoer moeten zijn:
Als we de waarde ‘true’ wijzigen naar ‘false’, zullen we zien dat de test faalt:
Nu krijg je een testuitvoer zoals:
We kunnen nu een meer nuttige test creëren die een commando uitvoert en het resultaat controleert. De ls-opdracht werkt op zowel Unix als Windows PowerShell, dus we zullen daar mee beginnen:
Door std::process::Command te importeren, kunnen we de ls opdracht aanroepen. De let keyword bindt een waarde aan een variabele, en de mut keyword maakt de variabele wijzigbaar. We voeren het commando uit en vangen de uitvoer op, die een Result is. We verifiëren vervolgens dat het resultaat een Ok variant is, wat betekent dat de actie is geslaagd.
Door cargo test uit te voeren, zou de test nu moeten slagen:
Als we de test willen aanpassen om ‘hello’ in plaats van ‘ls’ uit te voeren, dan wordt het als volgt:
Wanneer we deze test uitvoeren, zal deze echter mislukken, omdat het programma 'hello' niet gevonden kan worden. Dit komt doordat het uitvoerbare bestand zich in de map target/debug bevindt en niet in een van de mappen die in de PATH van het besturingssysteem zijn gedefinieerd. Het moet expliciet via de werkmap uitgevoerd worden:
Door dergelijke testen te schrijven, kunnen we niet alleen de juiste werking van ons programma verifiëren, maar ook onze kennis van Cargo en de manier waarop Rust het buildproces en de besturing van de uitvoerbare bestanden regelt, verbeteren.
Het is belangrijk om te begrijpen dat integratietests zoals deze helpen om te bevestigen dat een programma werkt zoals verwacht vanuit het perspectief van de gebruiker, terwijl unit tests meer gericht zijn op de interne logica van functies. Het effectief testen van zowel de individuele componenten als het hele programma als geheel is essentieel voor het bouwen van betrouwbare software in Rust.
Hoe kan het combineren van strikte programmeertalen zoals Rust met testen zorgen voor betrouwbare en efficiënte software?
Je hebt geleerd hoe je de metadata van een bestand kunt ophalen om alles te vinden, van de eigenaren en de grootte van het bestand tot de laatste wijzigingstijd. Je ontdekte dat directory-items die beginnen met een punt normaal verborgen zijn, wat leidt tot het bestaan van zogenaamde dotfiles en -directories voor het verbergen van programmagegevens. Je hebt de mysteries van bestandstoegang, octale notatie en bitmaskering onderzocht en bent nu beter geïnformeerd over het eigenaarschap van bestanden in Unix. Je hebt ontdekt hoe je impl (implementatie) kunt toevoegen aan een op maat gemaakt type Owner en hoe je dit module kunt scheiden in src/owner.rs en het kunt declareren met mod owner in src/main.rs.
Je leerde om drie schuine strepen (///) te gebruiken om documentatiecommentaren te maken die zijn opgenomen in de documentatie die door Cargo wordt gecreëerd, en die je kunt lezen met behulp van cargo doc. Je hebt gezien hoe je de tabular crate kunt gebruiken om teksttabellen te maken. Je hebt verschillende manieren onderzocht om flexibele tests te schrijven voor programma's die verschillende uitvoer kunnen genereren, afhankelijk van het systeem waarop ze draaien en wie ze uitvoert.
De kracht van testen is iets dat moeilijk te ontkennen is, maar vaak wordt onderschat. Er is altijd de verleiding om snel door te gaan naar de implementatie en de tests voor later te bewaren. Dit kan echter resulteren in instabiliteit en moeilijk te traceren fouten na verloop van tijd. Programmeren zonder testen is als het bouwen van een huis zonder fundering; het kan in eerste instantie goed lijken, maar op de lange termijn komen de gebreken aan het licht. Het toevoegen van tests is soms evenveel werk (of zelfs meer) dan het schrijven van het programma zelf, maar het is een noodzakelijke stap voor het waarborgen van de robuustheid en stabiliteit van de software.
Het schrijven van tests is fundamenteel voor het ontwikkelen van betrouwbare en efficiënte software, zoals de motto van Rust zelf beweert. Het zou daarom vanzelfsprekend moeten zijn om teststrategieën vanaf het begin in overweging te nemen. Daarnaast biedt het gebruik van een strikte taal zoals Rust enorme voordelen voor het detecteren van fouten tijdens de ontwikkelingsfase, waardoor programma's niet alleen betrouwbaarder, maar ook efficiënter worden.
Als je door deze programma’s heen werkt en de bijbehorende tests begrijpt, zul je zien dat dit soort discipline bijdraagt aan het creëren van goed gestructureerde, betrouwbare en onderhoudbare code. De kennis en vaardigheden die je opdoet bij het schrijven van tests zullen je helpen bij het verbeteren van je ontwikkelingsproces, wat uiteindelijk de kwaliteit van je software ten goede komt.
Naast de inhoud die we hebben behandeld, is het belangrijk te begrijpen dat er altijd ruimte is voor verbetering en verfijning. Testen moeten vaak worden aangepast en uitgebreid naarmate de complexiteit van een programma toeneemt. Wat bij een klein programma werkt, kan onpraktisch zijn voor een groter project, dus het vermogen om flexibele en dynamische tests te schrijven is cruciaal. Dit kan bijvoorbeeld betekenen dat je tests schrijft die afhankelijk zijn van systeeminstellingen, uitvoeringsomstandigheden of gebruikersinvoer. Het is belangrijk om na te denken over de verschillende scenario’s die je code mogelijk zal tegenkomen.
Testen zijn geen triviale toevoeging aan je project; ze zijn een integraal onderdeel van een kwaliteitsgericht ontwikkelingsproces. Elk stukje code dat je schrijft moet mogelijk onderworpen worden aan testen, niet alleen om ervoor te zorgen dat het werkt, maar ook om te begrijpen hoe het zich gedraagt in verschillende omstandigheden en om toekomstige problemen te voorkomen.
Was er een samenzwering bij de moord op Kennedy?
Hoe Corrosie de Chemische Verwerking beïnvloedt: Factoren, Soorten en Preventie
Hoe creativiteit in teams kan worden versterkt door samenwerking en communicatie
Wat beïnvloedt de genetica van levensduur en veroudering?

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский