Mașina de scris automată

Am scris la un moment dat, ca un soi de exercițiu de doi lei, un mic utilitar pentru automatizarea citirii de la tastatură ale proprietăților unui obiect oarecare, cu structură previzibilă, suficient de simplă și relativ strict standardizată. Prin automatizare mă refer la a nu mai scrie per manus codul pentru a prelua valoarea fiecărei proprietăți ci a spune unei clase oarecare, citește-mi, te rog frumos, un obiect de tipul T și dă-mi instanța nou creată.

Motanul Patraulea e oarecum sceptic, dar nu are de ce

Motanul Patraulea e oarecum sceptic, dar nu are de ce

De curând, ce-a fost o la un moment dat o mică distracție pasageră, mi-a fost utilă în punerea la punct a unui banc de testare manuală pentru un sistem care primea contracte bine definite și suficient de simple și parte a rutinei de testare a fost, desigur, că voiam să furnizez valori manuale și să văd reacția instantanee.

Bun motiv să șterg praful de pe mica clasă antecreeată, s-o duc un pic la sală și s-o integrez ca pe minorități. Ce-a ieșit, după ce-a lepădat câteva piei (și-o mână de stelnițe) pe drum, este de fapt o mini-librărie cu câteva clase adiacente care susțin funcționalitatea de bază, ce arată cam așa (POCO Plain Old CLR Object):

PocoCommandLineReader reader = 
	new PocoCommandLineReader();
CustomerSummary obj = reader
	.Read( "Read customer summary" );

Versiunii inițiale i-am adăugat două lucruri:

– posibilitatea de-a citi și colecții de valori, nu doar cele scalare implicite;
– formatarea colorată a textului prin care utilizatorului i se cere să furnizeze valorile, folosind această clasă, ușor modificată și redenumită.

Regulile pentru ce clase pot face obiectul mașinii de scris automate sunt relativ simple:

– clasa nu trebuie să fie abstractă (și-i neapărat să fie o clasă, nu o structură/struct/value type) și trebuie să aibă musai un constructor public fără parametri;
– sunt scanate proprietățile publice care pot fi citite și scrise, nu sunt statice și, desigur, se urmează lanțul de moștenire (comportamentul nu poate fi modificat);
– tipurile simple de date ce pot fi citite: int, uint, short, ushort, long, ulong, string, bool, float, double, decimal, char, byte, sbyte, Guid;
– tipurile de colecții ce pot fi citite (elementele trebuie să fie de tipul celor mai de sus): vectori (ex. int[]) și liste (ex. IList<int> sau List<decimal>);
– orice proprietate al cărei tip nu poate fi gestionat este ignorată;
– valorile implicite pentru proprietățile de tip simplu sunt cele implicite primite la instanțierea clasei;
– pentru colecții numărul de elemente trebuie furnizat în avans de către utilizator;
– se poate, deși nu-i obligatoriu, decora cu System.ComponentModel.DescriptionAttribute fiecare proprietate pentru care se dorește personalizarea mesajului prin care-i este cerută utilizatorului valoarea respectivă;
– dacă valorile furnizate de utilizator nu pot fi convertite la tipul cerut nu sare-n are, ci folosește valorile implicite corespunzătoare acelui tip.

Exemplu de funcționare

Exemplu de funcționare

Majoritatea valorilor sunt convertite-n mod standard (ex. int.TryParse()), cu câteva excepții notabile pentru:

– Tipul bool: sunt suportate și reprezentările „yes”, „da”, „1” pentru valoarea de adevăr true, respectiv reprezentările „no”, „nu”, „0” pentru valoarea de adevăr false;
– Tipul Guid: se poate folosi reprezentarea „new()” atunci când se dorește generarea unui Guid nou, neimportând efectiv ce valoare e atâta timp cât e o valoarea.

După cum e lesne de-ntrevăzut, dificultăți tehnice nu sunt, poate doar o mână de subtilități, însă, întrucât am fost suficient de prezumțios să cred că mai poate folosi și altora, l-am publicat acoloșa la mine pe github, deci fiecare-i liber să inspecteze după bunul plac și, fiindcă programatorii-s cam ca meseriașii, să-mi spuie că cine-a lucrat acolo și-a bătut joc de mine.