domenica, giugno 16, 2013

Appunti su HTTP, REST e API

Nota: questo post è stato aggiornato il 27 dicembre in seguito alla richiesta di chiarimenti sulla parte relativa a REST.

Leggendo qua e là documentazione sui servizi REST e sull'uso corretto dei metodi previsti da HTTP, mi sono accorto che spesso mancano alcune informazioni basilari, che permettono di comprendere a fondo i concetti esposti.

Innanzitutto, vi sono due tipi di servizi che possono essere offerti da un server web: quelli pensati per essere utilizzati da un utente umano, tramite il browser, e quelli pensati per essere utilizzati da applicazioni, che a loro volta saranno utilizzate da esseri umani.

Sempre più spesso, un server web offre entrambe le possibilità: nasce offrendo qualche servizio utilizzabili dai propri utenti tramite browser, e successivamente, quando gli utenti chiedono di automatizzare qualche processo, mette a disposizione dei metodi per l'uso degli stessi (o di altri) servizi da parte di applicazioni diverse.

(Scegliete un social network di vostro gradimento, e cercate nel relativo sito web la sezione dedicata agli sviluppatori per rendervene conto.)

Facciamo un esempio. L'ipotetico nuovo sito web kitchen.example.com consente ad una persona di controllare via web l'attrezzatura e le provviste della propria cucina, completamente automatizzata. Tutto questo via web, con un utente umano che usa un normale browser.



L'applicazione dovrà mettere a disposizione anche un'interfaccia per registrare i carichi di provviste in un database. Offrire all'utente una normale interfaccia web, da utilizzare con il proprio browser, per caricare questi dati potrebbe non essere un'idea ottimale (eppure, quante volte è capitato di vedere cose del genere!). Se avete provato a caricare manualmente dati di tipo analogo (insiemi di fotografie, di documenti vari, ecc.) in numeri maggiori a tre / quattro sapete di che cosa sto parlando. L'ideale è di fornire un modo per automatizzare il procedimento di caricamento delle provviste acquistate e di rendere pubbliche le specifiche tecniche relative a questo processo, in modo che, ad esempio, sia possibile sviluppare un'applicazione che carica le informazioni sul server a partire da qualche altra fonte di dati (ad esempio, il programma di controllo degli scontrini di acquisto...).

Se le specifiche rispettano alcune convenzioni, poi, tanto di guadagnato, perché un qualsiasi programmatore sarà in grado di adattare l'esperienza maturata in progetti precedenti al nuovo caso, senza dover inventare ogni volta la ruota.

Le specifiche tecniche sono documenti che spiegano in maniera dettagliata come un programma può invocare operazioni sul server, e vengono comunemente indicate come API (application program interface). Il server che risponde alle richieste di applicazioni sviluppate seguendo queste indicazioni viene a volte indicato come apiserver. L'applicazione che invia le richieste può essere di diversi tipi: uno script bash eseguito da riga di comando con all'interno richiami di cURL, un'applicazioncina web che sfrutta javascript per le richieste, un'applicazione desktop, un'applicazione per smartphone, un'applicazione web ordinaria, ecc.


Gli scenari naturalmente possono essere molto articolati. Potrebbe succedere che una persona interagisca tramite browser con un webserver che a sua volta si basa su un apiserver per elaborare la propria risposta.


Le app degli smartphone spesso sono programmi che sfruttano le API di un servizio, quindi senza rendervene conto avete sfruttato queste cose molte volte nella vostra esperienza di utenti.

Prima di analizzare le differenze che esistono fra applicazioni web ordinarie e applicazioni che definiscono delle API per l'interazione, e di introdurre il discorso di REST, facciamo un piccolo veloce riepilogo di come funziona HTTP. Il web abbonda di esempi al riguardo, per cui non sarò molto dettagliato.

Il client HTTP (user-agent, può essere un browser o qualsiasi altro programma in grado di fare richieste e ricevere risposte) invia al server HTTP (webserver o apiserver) richieste con un metodo, un percorso, la versione del protocollo usato e una serie di intestazioni. Ad esempio:

GET /kitchens/1/cookers/4.html HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

La risposta del server potrebbe essere una pagina web che dà informazioni sullo stato del fornello 4 della cucina 1.

In questo caso, il metodo è GET, la versione di HTTP è 1.1, il percorso è /kitchen/1/cooker/4.html. L'URI completa è http(s)://kitchen.example.com/kitchen/1/cooker/4.html, ma viene suddivisa in due parti per motivi storici, legati al fatto che originariamente un server web poteva ospitare un solo dominio, per cui era di fatto inutile specificare nella richiesta a quale host ci si voleva rivolgere.

(Non fatevi fuorviare dal fatto che ci sia quel .html alla fine del percorso; le applicazioni moderne usano un modulo chiamato URL-rewriting che consente di mappare tutte le richieste verso specifici moduli applicativi, che possono essere scritti in php, perl, python, ruby o ciò che volete, e l'estensione viene usata per determinare il formato con cui si vuole ottenere la rappresentazione -- se avete sviluppato un'applicazione in php e vi hanno detto che il path deve finire per .php, sappiate che non è effettivamente così: nella configurazione tipica, è il file che deve essere eseguito sul server a dover avere l'estensione .php, e si tratta di una cosa diversa.)

I metodi principali per le richieste sono GET (per ottenere dati), POST (per aggiungere dati), PUT (per aggiornare dati già esistenti, o per aggiungerni di nuovi in una posizione determinata dal client e non dal server) e DELETE (per eliminare dati). L'RFC 5789 ha introdotto anche il metodo PATCH (per aggiornare parte dei dati esistenti relativi a una risorsa).

Ciascuno metodo può possedere o meno le seguenti due caratteristiche:
  • sicurezza (l'esecuzione della richiesta non deve avere effetti collaterali, ossia non deve modificare le informazioni sostanziali sul server, escludendo operazioni di log, incremento di contatori, ecc.);
  • idempotenza (richieste ripetute identiche devono portare al medesimo stato dei dati sul server).
metodosicuroidempotente
GETsìsì
PUTnosì
DELETEnosì
PATCHnono
POSTnono

I metodi sicuri, come GET, devono poter essere eseguiti senza correre il rischio di causare effetti sul server. Ad esempio, un browser potrebbe precaricare la pagina successiva di una serie di pagine anche senza che l'utente umano faccia clic sul link corrispondente, al fine di velocizzare l'esperienza di navigazione. Oppure un programma di mirroring deve poter seguire tutti i link di una pagina per creare una copia locale del sito remoto senza causare nessun cambiamento di stato.

(Se non avete mai provato a fare il mirroring di un sito web, potete farlo con strumenti semplici come wget, che dispone dell'apposita opzione.)

In contrasto, i metodi non sicuri, come PUT, DELETE e POST, cambiano lo stato delle informazioni sul server. Ad esempio, un'ipotetica richiesta tipo

DELETE /kitchens/2/sauces/1234 HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

dovrebbe portare alla cancellazione della salsa con codice 1234 dal database delle salse legato alla cucina 2. È bene quindi che tali metodi vengano eseguiti solo quando un utente umano effettivamente vuole usarli.

I metodi idempotenti sono quelli che, anche se eseguiti ripetutamente con la stessa richiesta, portano allo stesso risultato in termini di stato del server (anche se la risposta potrebbe essere diversa). Tornando all'esempio precedente, alla prima richiesta di cancellazione il server potrebbe rispondere che la cancellazione è avvenuta, e ad una seconda richiesta che la risorsa da cancellare non esiste. In entrambi i casi, comunque, lo stato finale sarà che la salsa con id 1234 non esisterà.

Similmente, l'accensione del fuoco del fornello 4 della cucina 2 potrebbe essere fatto con una richiesta del tipo

PUT /kitchens/2/cookers/4 HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

status=on

mentre lo spegnimento potrebbe avvenire con

PUT /kitchens/2/cookers/4 HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

status=off

Il metodo POST, che non è idempotente, serve a causare ogni volta l'aggiunta di informazioni sul server. Quindi, ad esempio, tre richieste ripetute di tipo

POST /kitchens/2/sauces HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

type=tomato&price=1.12&quantity=20&mu=l

dovrebbero portare alla registrazione del carico di 60 litri di salsa di pomodoro.

Notate che con il metodo POST non si dovrebbe indicare l'id della risorsa, perché ne stiamo chiedendo al server di aggiungerla, e sarà esso (o, più concretamente, il dbms) a determinare l'id.

Nella risposta ad una richiesta di tipo POST, il server dovrebbe fornire l'URI della risorsa creata, in modo da consentire eventuali modifiche successive, che potranno essere fatte tramite PUT, se si vogliono sostituire tutte le informazioni presenti, oppure tramite PATCH, se si vogliono modificare solo degli attributi.

Un esempio di richiesta di modifica dell'attributo prezzo potrebbe essere la seguente:

PATCH /kitchens/2/sauces/8 HTTP/1.1
Host: kitchen.example.com
User-Agent: simpleBrowser v.1

price=1.14

L'uso del browser pone dei limiti rispetto a quanto consentito, in generale, dall'HTTP. Il limite maggiore è che l'HTML (non l'HTTP) supporta solo i metodi GET e POST (per vari motivi, che non discuteremo in questa sede). Quando fate clic su un link, il browser fa una richiesta di tipo GET. Quando compilate una form e inviate i dati, il metodo di invio dipende dall'attributo "method" dell'elemento form. Ma non si possono avere form con attributo "method" impostato a DELETE o PUT. Di conseguenza, quando si sviluppa un'applicazione web pensata per essere eseguita da un utente umano tramite browser, il tipo di richiesta potrà essere solo GET o POST: il primo verrà usato quando si devono mostrare informazioni (lista dei prodotti, scheda del prodotto, form per la modifica dei dati di un prodotto, ecc.), il secondo quando dei dati devono essere effettivamente acquisiti (inserimento di un nuovo prodotto, cancellazione di un prodotto, modifica di un prodotto, ecc.).

Immaginando di dover presentare un link per la cancellazione di una risorsa, la soluzione migliore da adottare, in un'ottica di miglioramento progressivo, sarebbe di:
  1. implementare la soluzione con un link ordinario ad una pagina di richiesta della cancellazione in cui viene presentata una form con un pulsante per la cancellazione, che userà il metodo POST per l'invio dei dati;
  2. sul server, fare in modo che la richiesta fallisca se il metodo usato non è POST;
  3. utilizzare codice javascript discreto per sostituire il link ordinario con un link che faccia direttamente il POST dei dati, presentando una finestra di conferma di cancellazione;
  4. eventualmente, aggiungere codice AJAX da attivare su richiesta (javascript può fare richieste asincrone che usano i metodi PUT e DELETE).
Nella definizione di API per applicazioni, visto che non si devono considerare le limitazioni dell'HTML, che, come detto, non prevede la possibilità di specificare metodi diversi da GET e POST come attributo dell'elemento form, è possibile sfruttare al meglio le potenzialità di HTTP.

Per fare delle prove, è possibile usare cURL dalla riga di comando. Ad esempio:

curl -X PUT -d status=off http://kitchen.example.com/kitchens/2/cookers/4

consente di fare la richiesta di spegnimento del fuoco del fornello numero 4 della seconda cucina, come visto precedentemente.

Veniamo ora al concetto di API REST.  Innanzitutto, che cos'è REST?

Si tratta di un'architettura software per la gestione di risorse, basata su principi che delineano come le risorse devono essere definite e indirizzate. La sigla sta per "Representational State Transfer", ed è stata coniata da Roy Fielding. Informazioni dettagliate e ulteriori link si possono trovare nella pagina della Wikipedia. In linea teorica REST potrebbe essere usato anche senza HTTP, ma in pratica HTTP e REST viaggiano quasi sempre in coppia, per cui darò per scontato il fatto che si usi HTTP (spesso nella versione sicura, HTTPS). I concetti fondamentali sono questi:
  1. visto che HTTP prevede già l'uso di metodi specifici per le diverse operazioni, ci si concentra sulla risorsa piuttosto che sul cosa deve essere fatto, per la definizione degli URI, in cui si vedranno riferimenti alle cose, non alle azioni;
    tradotto: nel caso di API per un negozio online, nell'URI non si dovrebbero vedere "verbi" tipo "buy", "pay", ecc., ma solo riferimenti alle risorse effettive; per un pagamento pianificheremo un URI come http(s)://example.com/payment/transaction/1234/amount/150, da richiamare con il verbo POST per effettuarlo
  2. le risorse sono identificate da URI, e diversi URI possono puntare alla stessa risorsa;
    tradotto: la stessa notizia potrebbe essere identificata dall'URI http(s)://example.com/news/2013/10/22/italy/web sia dall'URI http(s)://example.com/news/italy/web/latest (in un dato momento)
  3. una risorsa è diversa dalla sua rappresentazione (posso rappresentare i dati della salsa 1234 in un file XML, in un file JSON, con un'immagine, in una pagina HTML) -- sarà lo user-agent a chiedere quale tipo di rappresentazione gli interessa;
    tradotto: la notizia del 22 ottobre identificata dall'URI http(s)://example.com/news/2013/10/22/italy/web potrebbe essere fornita dal server in formato HTML, JSON, XML, ecc., a seconda di ciò che richiede il browser -- su alcune opzioni disponibili ho scritto un altro post in questo blog
  4. il formato con cui il client specifica come vuole ricevere i dati, secondo HTTP, dovrebbe essere specificato nelle intestazioni, in un campo di "negoziazione del contenuto", ma a fini pratici spesso si usa semplicemente l'estensione;
    tradotto: quando uno user agent richiede una risorsa via HTTP, può specificare nella richiesta il formato con cui desidera ottenere le informazioni (es. Accept: text/plain), ma spesso nelle implementazioni si dà la possibilità di usare l'estensione (es. http(s)://example.com/news/2013/10/22/italy/web.txt)
  5. l'interfaccia è uniforme, ossia le cose si fanno sempre allo stesso modo indipendentemente dal tipo di risorsa (questo vale sia per il metodo HTTP da usare, sia per il tipo di risposta che si ottiene dal server);
    tradotto: per eliminare una risorsa si usa sempre il metodo DELETE, ecc.
  6. la risposta del server contiene un codice HTTP e, spesso ma non sempre, un payload (ossia le informazioni richieste);
    tradotto: il server risponde con un codice tipo 200 OK, oppure 404 File not found, ecc; per alcune risposte potrebbero non esserci altre informazioni da rappresentare (contenuti HTML, testi, file binari, ecc.)
  7. un'applicazione REST dovrebbe essere completamente senza gestione di stato da parte del server (quando l'utente interagisce con un server tramite browser, invece, la sessione viene gestita con una collaborazione tra client e server, che generalmente avviene con l'impostazione di cookies di sessione al momento dell'autenticazione; nelle applicazioni REST il server non dovrebbe mantenere informazioni sulla "sessione", e ogni richiesta dovrebbe essere valutata autonomamente rispetto a quelle precedenti e a quelle successive) -- ciò consente di gestire, ad esempio, il bilanciamento di carico tra più server;
    tradotto: ogni richiesta inviata al server dal client dovrebbe essere in qualche modo indipendente dalle precedenti o, meglio, non deve essere il server a farsi carico di tenere traccia di chi è autorizzato a fare qualche cosa, come nel caso delle sessioni che con un browser vengono avviate dall'utente con il login
  8. le rappresentazioni possono essere memorizzate temporaneamente in una cache, anche a più livelli (come quando tra client e server si frappongono dei proxy);
    tradotto: quando un client invia la richiesta ad un server, può avvalersi di server intermediari (proxy), i quali sono autorizzati a memorizzare la risposta del server per un determinato lasso di tempo, in modo da fornirla ad eventuali altri client che la dovessero richiedere
  9. le rappresentazioni fornite dal server dovrebbero contenere collegamenti ipertestuali (URI) che consentono di passare da uno stato all'altro (questo concetto viene indicato come HATEOAS, Hypermedia as the engine of application state, ma è uno dei principi più violati dell'architettura, come spiegato nel video HATEOAS 101);
  10. quando client e server devono comunicare tra loro, è bene che i dati vengano trasmessi utilizzando formati facilmente gestibili da sistemi automatizzati, come JSON e XML (per quanto riguarda JSON, esistono anche delle proposte di standard di fatto, come JSend, in cui si stabilisce come la risposta del server debba contenere dati relativi all'esito della richiesta) -- notate che gli esempi presentati qui sopra sono semplificati in quanto non usano JSON.
Per molti servizi è disponibile abbondante documentazione sulle API REST che si possono utilizzare, ed è sempre una buona idea dare un'occhiata per ottenere ispirazione sulle pratiche correnti. Inoltre, per approfondimenti, consiglio la lettura di due raccolte di buone pratiche: RESTful Best Practices e  Best Practices for Designing a Pragmatic RESTful API.

domenica, giugno 02, 2013

A good cookbook for Yii development

I just finished reading Alexander Makarov's Yii Application Development Cookbook that I found a really valuable resource to keep at hand if you are developing a web application using Yii.
In its thirteen chapters you are provided with lots of recipes that open your eyes on what the framework does for you under the hood, and on what you can do with minimal effort to improve your application.
The book can be really a time-saving resource: by reading the recipes provided, I ended up many times thinking "If I hadn't read this here, I would have reinvented the wheel, should I have faced the same problem described." For instance, I didn't know about decorators, custom input widgets, some methods automatically called as event
handlers, reusable controller actions, etc., and I found out that a very easy implementation of these concepts is actually just a few lines of code away.
Even for things that I already knew before reading the book, I found it very interesting to compare my solutions with what was described in the book, since I always try to write my code "in the right way",
following the best practices, and I am eager for improvements. Ideas and implementations of simple table inheritance, zii widget customization, role based access control, etc., fell for me in this
category.
Last but not least, the book provides very useful hints for the deployment, when one should fine-tune the application in order to improve the performances.
The only things that appeared to be wrong, from my point of view, is the use of "get", "put" and "post" HTTP methods, that in some recipes seem to be used in the wrong way. In a web application you should never allow somebody to delete something by clicking on an ordinary link, and that means that you should check whether the request is done via the "post" method (Yii provides a postOnly filter for that), and the cookbook should stress that. Another point is that in RESTful applications "post" is used to store new data, while "put" to update data already existing, not viceversa (this is already fixed in the errata page).

venerdì, maggio 17, 2013

Applicazioni web lato server, stato dell'arte

Riporto una serie di considerazioni utili per la progettazione di un'applicazione web, che nelle intenzioni dovrebbero delineare lo "stato dell'arte". Sicuramente si possono considerare altri aspetti (organizzazione del codice, unit testing, internazionalizzazione e localizzazione, ecc.), ma su questi aspetti magari tornerò un'altra volta.

In sintesi:
  1. lo sviluppo dell’applicazione dovrebbe differenziare il codice relativo al trattamento dei dati, quello relativo alla presentazione degli stessi e quello dedicato invece al controllo dell’accesso ai moduli applicativi; questa differenziazione viene comunemente indicata con la sigla MVC (Model – View – Controller), dove per model / modello si intende ciò che serve a rappresentare le informazioni (quindi il trattamento dei dati), per view / vista il codice che serve per la rappresentazione, e per controller / controllore il codice dove viene sostanzialmente deciso quali (categorie di) utenti possono svolgere quali operazioni;
  2. l’applicazione dovrebbe basarsi quanto più possibile sul rispetto dello standard HTTP in merito al significato dei “verbi” espressi nelle richieste (GET, POST, PUT*, DELETE*), con identificazione corretta delle risorse tramite ID univoci, gestione di rappresentazione multiple delle stesse risorse, risposte del server con i codici corretti (200 Ok, 401 Unauthorized, 403 Forbidden, 404 File not found, ecc.), e dopo l'esecuzione di operazioni tramite POST il browser dell'utente dovrebbe essere ridirezionato ad una pagina ottenuta con il metodo GET;
  3. l’accesso a dati presenti in una base di dati dovrebbe avvenire, ogni qualvolta ciò sia possibile, mediante l’uso di librerie / interfacce che forniscano un’astrazione rispetto allo specifico DBMS utilizzato; in questo modo, qualora si decidesse di cambiare il DBMS scelto (o di usare diversi DBMS con le diverse istanze dell’applicazione) non si avrà la necessità di modificare il codice sorgente;
  4. l’esecuzione delle query sul DBMS dovrebbero sfruttare metodologie, quali ad esempio i prepared statements, che consentono di renderle più efficienti e più flessibili nell’uso da parte del programmatore, con il vantaggio aggiuntivo di proteggere da attacchi di tipo SQL injection;
  5. le connessioni al DBSM da parte dell’applicazione dovrebbero essere ridotte al minimo, per cui, se per produrre una pagina web si devono effettuare più query, è bene che vengano eseguite le diverse query nell’ambito della stessa connessione; questo si può ottenere facilmente usando il design pattern denominato Singleton, che fa sì che venga assicurata l’esistenza di una sola istanza di una determinata classe (nel nostro caso, la classe per l’accesso ai dati);
  6. in un sistema software progettato in maniera object-oriented, sarà molto probabilmente naturale definire una classe per ogni relazione presente nella base di dati, creando di fatto un legame tra gli oggetti del mondo dell’applicazione e le corrispondenti tuple del mondo della base di dati; per evitare inutili riscritture di codice, è consigliabile sfruttare strumenti software, denominati ORM (object-relational mappers) che:
    1. generano automaticamente il codice sorgente delle classi necessarie, a partire dallo schema relazionale (o viceversa);
    2. forniscono funzioni per accedere ai dati in modalità object-oriented;
    3. permettono di astrarre dall’uso del codice SQL, in maniera tale da eseguire le query più comuni senza bisogno di scrivere istruzioni SQL (consentendone l’uso, però, in caso di necessità sofisticate, magari con modalità che permettano la costruzione di queryanziché la generazione di stringhe);
    4. mettono a disposizione funzioni predefinite per tutte le operazioni CRUD (Create, Retrieve, Update e Delete), nonché, spesso, per le ricerche, la paginazione dei risultati, la validazione dell’input, ecc.;
  7. l’applicazione dovrebbe avere un unico punto di ingresso (ossia, indipendentemente dall’URL della risorsa richiesta o inviata, dobbiamo essere sicuri che venga sempre eseguito un insieme di istruzioni specifico di controllo del flusso di esecuzione).

Nelle applicazioni sviluppate con il linguaggio di programmazione PHP, è quindi altamente consigliato l’uso di PDO anziché, ad esempio, delle funzioni della serie mysql_*(), che sono deprecate (vedi in merito l'interessante PDO tutorial for MySQL developers) in quanto:
  1. non supportano concetti avanzati quali prepared statements e transazioni;
  2. non mettono a disposizione la possibilità di scrivere codice object-oriented;
  3. necessitano di codice aggiuntivo per la creazione delle query (ad esempio per l’escape di caratteri speciali), che complicano la vita del programmatore e portano spesso a bug;
  4. non permettono una valida gestione delle condizioni di errore;
  5. non sono più mantenute (il che vuol dire che eventuali nuove vulnerabilità scoperte non verranno sistemate).
Inoltre, in una logica di separazione secondo il design pattern MVC, sarà da considerare errata la scrittura di codice che mescola l’accesso ai dati e la visualizzazione dei risultati, e sarà preferibile utilizzare un approccio object-oriented anche per gli oggetti recuperati.

Un esempio “quick and dirty” di codice PHP per la visualizzazione dei dati di un’ipotetica tabella customers potrebbe essere il seguente:

<?php

$host     = 'localhost';
$user     = 'foobar';
$password = 'pXC4FUb6bNFusDBV';
$dbname   = 'foobar';

$type = 'A';
//$type = "'";

// Instaurazione della connessione con il DBMS
$cn = mysql_connect($host, $user, $password);

// Selezione del database su cui verrà eseguita la query
mysql_select_db($dbname, $cn);

// Esecuzione della query
$result = mysql_query("SELECT id, name, city FROM customers WHERE type = '" . $type . "'", $cn);

// Ciclo sulle tuple ritornate dalla query
while ($row = mysql_fetch_array($result))
{
    printf("<p>id=%s, name=«%s», city=%s</p>", $row[0], $row[1], $row[2]);
}

// Liberazione della memoria associata al risultato della query e chiusura della connessione
mysql_free_result($result);
mysql_close($cn);
 

Analizzando il codice, notiamo i seguenti problemi:
  1. la connessione viene aperta e chiusa nel frammento individuato; è però estremamente probabile che, in un’applicazione web, si debbano effettuare più query per ottenere il risultato desiderato (ad esempio per controllare chi è l’utente connessio, se ha le autorizzazioni necessarie, ecc.), e quindi sarebbe bene spostare la gestione della connessione altrove;
  2. non c’è nessuna gestione dell’errore (cosa succederebbe se la password per l’accesso al database non fosse corretta, o se per qualche motivo il DBMS non dovesse rispondere?);
  3. la query è composta direttamente come stringa “hard-coded”, e questo comporta una serie di problemi (che cosa succede se il nome della tabella dovesse cambiare, ad esempio per l’aggiunta di un prefisso? come può essere gestito un log delle query eseguite? ecc.);
  4. l’uso di array indicizzati da numeri interi pone problemi nel caso di modifica della query;
  5. il tipo viene concatenato alla stringa e, a parte la seccatura di dover gestire le virgolette, c’è il piccolo problema che si otterrebbe un errore nel caso esso stesso contenesse delle virgolette all’interno;
  6. se il valore del tipo ci venisse dato in input, saremmo soggetti ad attacchi di tipo SQL-injection;
  7. viene mescolato il codice per la visualizzazione dei dati con quello per il loro recupero (e se un giorno volessimo visualizzare dati provenienti da fonti diverse, come un file XML o JSON?);
  8. siamo vincolati all’uso del DBMS MySQL.
Insomma, ci sono abbastanza motivi per riscrivere il codice utilizzando un po’ di buon senso e buone pratiche.


<?php

require_once('db.php');  // qui ci sarà il codice per la gestione della connessione al DBMS

$type = 'A';

$stmt = $dbh->prepare("SELECT id, name, city FROM customers WHERE type = ?");

$stmt->bindParam(1, $type);

$customers = array();
if ($stmt->execute()) {
  while ($row = $stmt->fetch(PDO::FETCH_OBJ)) {
    $customers[]=$row;
  }
}
else
{
  print_r($stmt->errorInfo());
}

?>

<?php foreach($customers as $customer): ?>
  <p>id=<?php echo $customer->id ?>, name=«<?php echo $customer->name ?>», city=<?php echo $customer->city ?></p>
<?php endforeach ?>

Da notare l'uso della sintassi alternativa per il codice PHP del livello view (in alternativa, molti preferiscono utilizzare template engines che non usano istruzioni PHP, quali smarty e i suoi derivati,
twig, ecc.).
Il file db.php conterrà il codice necessario per l’attivazione della connessione:


<?php

require_once('config.php');

try {
    $dbh = new PDO($connectionString, $user, $password, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));}
catch(PDOException $e) {
    echo 'Error: ' . $e->getMessage();
}

e il file config.php conterrà le
credenziali per l’accesso al DBMS (in cui, per garantire la compatibilità con più DBMS, viene messa una stringa di connessione, come nell'esempio seguente).



$connectionString = "mysql:host=localhost;dbname=foobar";

Nel costruttore dell'oggetto PDO è possibile, come nell'esempio, impostare il modo con cui devono essere gestiti eventuali errori (a parte quello relativo alla connessione), come ad esempio violazioni di vincoli di integrità referenziale. Il default è che PHP rimanga in silenzio, per cui se si vuole gestire eventuali errori è bene impostare il lancio di eccezioni.

Un classico caso di necessità di controllo delle eccezioni si ha quando si deve gestire una transazione con più query delle quali una potrebbe fallire, e si vuole fare in modo che in caso di fallimento tutte le query eseguite vengano annullate. Con PDO si può gestire facilmente la cosa tramite una transazione:

try {
    $dbh->beginTransaction();
    // prima query
    // seconda query
    // terza query
    $dbh->commit();
}
catch (Exception $e)
{
    $dbh->rollBack();
}


Rimane da dire che usando un ORM quale, ad esempio, CActiveRecord, l’estrazione dei dati da una tabella del database diventa poi ancora più semplice, visto che l’esecuzione della query e il recupero dei dati potrebbe ridursi ad una semplice riga di codice:

$customers = Customer::model()->findAllByAttributes(array('type'=>$type));


Note e chiarimenti

* I metodi PUT e DELETE non sono consentiti dall'HTML come valore per l'attributo "method" dell'elemento "form". Una loro implementazione può essere utile in caso di definizione di un'interfaccia RESTful o per rispondere a chiamate AJAX.

lunedì, maggio 13, 2013

"Da: no-replay@" anziché "Da: no-reply@"

Fateci caso, se siete iscritti a qualche newsletter: è più comune di quanto uno potrebbe pensare.


Sto parlando di quelli che mandano le email da un indirizzo fittizio al quale non si desidera ricevere risposte. Legittimo, solo che "rispondere" in inglese si dice reply, non replay (che invece significa "ripetere", "risuonare", "rimandare in onda").

mercoledì, aprile 10, 2013

Bilingual Chart of Accounts / Piano dei conti bilingue

Dovendo predisporre un piano dei conti di prova per l'applicazione DELT, che sto sviluppando, ho fatto un po' di ricerche e confronti, e questo è quello che ne è venuto fuori. Se vi capita di trovare errori, segnalateli lasciando un commento. Grazie.

I had to produce a Chart of Accounts for DELT, a web application I'm developing, so I made some research and double checkings, and here is what came out. If you find any error, please leave a comment. Thank you.

(Il contenuto di questo post è stato spostato all'URL http://blog.learndoubleentry.org/2013/07/any-advice-for-chart-of-accounts.html.)

sabato, marzo 30, 2013

Easter, Ēostre, and Daylight Saving Time

Tomorrow will be a day in which Easter / Ēostre is celebrated (by someone), and it occurs that on the same day there will be the Daylight Saving Time switch (here in Europe).

I was curious about when this coincidence happens again, so I wrote up a little one-liner to find out:
seq 2013 2113 | LANG=C xargs -i ncal -e {} | grep "March" | grep "[2][5-9] \|[3][0-1] "
which, in human words, means, more or less, "for the years from 2013 to 2113, find out the dates of (Western countries) Easter date, then filter them by letting pass only the ones in March, with the day of the month being 25 to 29 or 30 to 31".
If you are interested, this is the result:
March 31 2013
March 27 2016
March 31 2024
March 28 2027
March 28 2032
March 25 2035
March 29 2043
March 25 2046
March 29 2054
March 30 2059
March 26 2062
March 29 2065
March 30 2070
March 26 2073
March 30 2081
March 26 2084
March 31 2086
March 30 2092
March 31 2097
March 28 2100
March 25 2103
March 29 2111

giovedì, marzo 14, 2013

Asymmetric encryption explained

I looked for a basic explanation of how asymmetric encryption actually works to show to my students, but I couldn't find easily something that can be understood without some mathematical background.

So, I wrote a few scripts that tell a story with some little and understandable numbers.

Here's the result:

ASYMMETRIC CRYPTOGRAPHY: A TALE

This is a demonstration of how asymmetric cryptography works.

Two people, Alice and Bob, want to send messages each other without
sharing a common secret.

So, they both choose a private key and generate a public key from that,
sharing only the latter. How can this work?

Imagine that Alice and Bob choose a common prime number, that they both
know. Here, suppose they chose Y=17.

Alice chooses her own private key as 11.

She computes her public key as (17 ^ 11) = 34271896307633, 
and then sends it to Bob.

On the other side, Bob computes his public key 
as 17 ^ 13 = 9904578032905937, and sends it to Alice.

Now, Alice has her own private key (11) and Bob's public key (9904578032905937).
Since she wants to send her message, she first computes a session key
using her own private key and Bob's public key, like here:

The session key is computed as 9904578032905937 ^ 11.
Its value is 
89990311908498647045788321790870571946973640519785658861481127970769\
43851269888414365573845679912196161685840518138931586759580137729961\
7421026433812938863796708814458356310513

With this session key, Alice can encrypt a message with a symmetric key
program.

When Bob receives the file, he computes the session key using his own
private key and Alice's public key, much like Alice did herself.

The session key is computed as 34271896307633 ^ 13,
which is exactly the same Alice has used:
89990311908498647045788321790870571946973640519785658861481127970769\
43851269888414365573845679912196161685840518138931586759580137729961\
7421026433812938863796708814458356310513

Why are the two session keys equal?

Because on one side you have

(17 ^ 13) ^ 11
(which is Bob's public key to the power of Alice's private key)

and on the other side you have
(17 ^ 11) ^ 13
(which is Alice's public key to the power of Bob's private key)

And, of course 17 ^ 13 ^ 11 = 17 ^ 11 ^ 13.



The source code of the script is available on google code. You can run the script setting other values on the command line, like for instance:

./public_key_cryptography_explained.sh 11 13 17

But still, there is a problem. Given the power and the base, it is relatively easy to find the exponent, even with very large numbers. That explains why public and session keys are computed with modular arithmetic: this adds strength to the algorithm because it makes reversibility much much harder. Let's see it in action with a second tale (where the percentage symbol stands for the modulus operator).


ASYMMETRIC CRYPTOGRAPHY: A TALE

This is a demonstration of how asymmetric cryptography works.

Two people, Alice and Bob, want to send messages each other without
sharing a common secret.

So, they both choose a private key and generate a public key from that,
sharing only the latter. How can this work?

Imagine that Alice and Bob choose a common prime number, that they both
know. Here, suppose they chose Y=30983.

They also choose a number to be used for modulus computation. Here,
suppose they chose P=40993.

Alice chooses her own private key as 1087.

She computes her public key as (30983 ^ 1087) % 40993 = 1771, 
and then sends it to Bob.

On the other side, Bob computes his public key 
as (30983 ^ 2083) % 40993 = 26537, and sends it to Alice.

Now, Alice has her own private key (1087) and Bob's public key (26537).
Since she wants to send her message, she first computes a session key
using her own private key and Bob's public key, like here:

The session key is computed as (26537 ^ 1087) % 40993.
Its value is 7225.

With this session key, Alice can encrypt a message with a symmetric key
program.

When Bob receives the file, he computes the session key using his own
private key and Alice's public key, much like Alice did herself.

The session key is computed as (1771 ^ 2083) % 40993,
which is exactly the same Alice has used: 7225.

The source code of this script is available on google code.
You can run it on the command line like this:
./public_key_cryptography_explained_with_mod.sh 15 13 3 17
to obtain the same example given in the nice Diffie-Hellman Key Exchange video.

mercoledì, febbraio 13, 2013

Yii: click on a link, post your request

In a RESTful application written with Yii, I have an action that fixes some data in the db (like, for instance, the width and the height of an image), by checking some extra sources (the file). I needed to show a link that, when clicked, fired a POST request, and not a GET one. The obvious solution was to find out how to do it with some ajax call, but I wanted to see what Yii gave me.

I was happy to find out that Yii's CHtml::link() function already has everything needed. In particular, just adding 'submit'=>true in the $htmlOptions array, like in

<p><?php echo CHtml::link(
    'Fix picture',
    $url=CHtml::normalizeUrl(array('picture/fix','id'=>$model->id)),
    array(
      'submit' => $url,
      'title' => 'Check real size and type and fix data base entries',
      'csrf'=>true,
    )
  )
?></p>

is enough to produce a nice unobtrusive javascript code, like the following:
(in the body)

<p><a title="Check real size and type and fix data base entries" href="/yii/photoalbum/index.php?r=picture/fix&amp;id=6" id="yt0">Fix picture </a></p>

(in the jquery code produced, at the end of the page)

jQuery('body').on('click','#yt0',function(){jQuery.yii.submitForm(this,'/yii/photoalbum/index.php?r=picture/fix&id=6',{'YII_CSRF_TOKEN':'168375210910867a3dfab3db8750c4ad5f5aee2a'});return false;});

I needed to set csrf to true because my main config file has CRSF protection enabled.
I wanted the application to work even if javascript is disabled (Progressive Enhancement), so I prepared a fix.php view anyway, with a standard form containing only the submit button, like here:
<?php $form=$this->beginWidget('CActiveForm'); ?>
    <div class="row submit">
        <?php echo CHtml::submitButton('Fix'); ?>
    </div>
<?php $this->endWidget(); ?>
The form won't be shown if users have their javascript enabled.

domenica, febbraio 10, 2013

Rugby - ragby - regby

Perché in Italia la parola rugby (trascrizione fonetica: ‹ˈrʌɡbi›) viene pronunciata, il più delle volte, "regby" (‹ˈreɡbi›)? Da dove viene l'idea che la u debba essere trasformata in e?

Su forvo.com, si può vedere (e soprattutto sentire) che in tutti i paesi anglosassoni la pronuncia corretta è con una sorta di a, e quindi, vista l'origine e la diffusione dello sport, la parola dovrebbe essere pronunciata così.

Per quanto riguarda l'italiano, Dizionario italiano propone la pronuncia ‹'rugbi›, mentre il vocabolario della Treccani indica ‹rḁ′ġbi›.

Quindi, da dove viene la "e"?


(nell'immagine: particolare da Mêlée de Rugby, Bourgoin-Jallieu, Isère, Free On Line Photos)

mercoledì, febbraio 06, 2013

Screenshot di una pagina web

Se volete fare uno screenshot di una pagina web, e volete automatizzare il procedimento, magari sfruttando la riga di comando, potete usare PhantomJS, un browser comandabile via javascript, utilizzabile senza bisogno di interfaccia grafica (sotto Linux, senza che XWindow sia installato).

Fra l'altro, è possibile ottenere anche un file PDF.

Dopo aver installato PhantomJS, è sufficiente creare un file di testo con un contenuto simile al seguente:
// webpage_screenshot.js
var page = require('webpage').create();
var system = require("system");
page.open(system.args[1], function () {
    page.render(system.args[2]);
    phantom.exit();
});

e lanciare il comando
phantomjs webpage_screenshot.js http://www.example.com example.png
oppure
phantomjs webpage_screenshot.js http://www.example.com example.pdf
PhantomJS può essere usato anche per produrre documenti complessi basandosi su contenuti HTML5, come nell'esempio della fattura presentato su we-love-php.blogspot.nl.

lunedì, febbraio 04, 2013

Regr.lin() vs Linest()

Dovendo preparare un esercizio di statistica, sono andato a rivedermi la guida in linea della funzione REGR.LIN() del foglio elettronico. Personalmente uso OO Calc, ma, alla ricerca di qualche esempio interessante, mi sono fatto portare da Google alla pagina di descrizione della funzione di Microsoft Excel.

L'esempio numero 3 è chiaro nella prima parte, ma nel momento in cui si parla della stima della palazzina da acquistare, le cose si fanno un po' confuse, poiché viene detto

L'imprenditore può così calcolare il valore stimato di una palazzina nella stessa zona, costruita 25 anni prima, con una superficie di 279 metri quadri, tre uffici e due ingressi, utilizzando la seguente equazione:

y = 27,64*2500 + 12530*3 + 2553*2 - 234,24*25 + 52318 = € 158.261


Il valore di 279 metri quadri è incoerente con gli altri, e non ve ne è traccia nell'equazione. La tabella sotto poi riporta il valore 1 anziché 2500.

La stessa guida in inglese permette di chiarire un po' le cose, visto che i valori sono corretti, si fa riferimento a 2500 piedi quadrati, e tutti i conti tornano.

Rimane il mistero della provenienza del valore 279 nel testo in italiano. Una superficie di 2500 piedi quadrati corrisponde a circa 232 metri quadrati, non a 279.

sabato, febbraio 02, 2013

Full example of Ajax-powered text field with Yii

Some days ago I needed to transform a simple text field into an AJAX-powered widget, so that values could be obtained from asyncronous queries to the webserver.

I used a widget from the framework itself, zii.widgets.jui.CJuiAutoComplete, but I had to figure out how to actually use it. I will recap here what I did.

Imagine you have a "tag" field, used to add tags to something. In your view, replace your ordinary widget:

<?php echo $form->textField($tagform,'tag'); ?>

with the new one, AJAX-powered:
<?php $this->widget('zii.widgets.jui.CJuiAutoComplete', array(
  'id'=>'tag',
  'name'=>'PictureAddTagForm[tag]',
  'source'=>$this->createUrl('tag/suggest'),
   'options'=>array(
    'delay'=>500,
    'minLength'=>2,
    ),
  'htmlOptions'=>array(
     'size'=>'20'
     ),
  ))
?>

The "options" array allows you to define a delay after which the AJAX call will be executed, and a minimal number of characters to wait.
Since the source for data is defined as 'tag/suggest', you'll need to define an action in the tag controller:


/**
 * Serves a list of suggestions matching the term $term, in form of
 * a json-encoded object.
 * @param string $term the string to match
 */
public function actionSuggest($term='')
{
  $this->serveJson(Tag::model()->findMatches($term));
}

This action will call a custom function that sends the HTTP header and the JSON-encoded list of tags. The parameter's name, that we'll refer to as $term, is hardcoded in the underlying jqueryui autocomplete widget.

The custom function must be written in the controller or, even better, in the Controller class, since this way it is shared with the other controllers:
// in protected/components/Controller.php
/**
 * Serves an object as a json-encoded string via HTTP.
 * @param string $object the object to send
 */  
public function serveJson($object)
{
  $this->serveContent('application/json', CJSON::encode($object), false);
}

/**
 * Serves a content via HTTP.
 * @param string $type the Internet Media Type (MIME) of the content
 * @param string $content the content to send
 */  
public function serveContent($type, $content)
{
  $this->_serve($type, $content, false);
}

/**
 * Serves a file via HTTP.
 * @param string $type the Internet Media Type (MIME) of the file
 * @param string $file the file to send
 */  
public function serveFile($type, $file)
{
  $this->_serve($type, $file, true);
}

/**
 * Serves something via HTTP.
 * @param string $type the Internet Media Type (MIME) of the content
 * @param string $content the content to send
 * @param boolean $is_file whether the content is a file
 */  
private function _serve($type, $content, $is_file=false)
{
  header("Content-Type: " . $type);
  if ($is_file)
  {
    readfile($content);
  }
  else
  {
    echo $content;
  }
  Yii::app()->end();
}
Now, it's the model's job to get the data that match the term.

/**
 * Retrieves tags that match a term.
 * @param string $term a term to use for matching tags.
 * @return array tags titles that match the term.
 */
public function findMatches($term='')
{
  if(''==$term)
  {
   return array();
  }
  $q = new CDbCriteria();
  $q->addSearchCondition('title', $term);
  $q->select=array('title');
  $tags = self::model()->findAll($q);

  $results=array();
  foreach($tags as $tag)
  {
    $results[]=$tag->title;
  }
  return $results;
}



This function will retrieve from the table all the rows that match the criteria, and return an array with the values actually needed.

venerdì, febbraio 01, 2013

Weird screen resolutions from Firefox

When you collect information about users' screen resolution, it happens you get weird numbers, like 797x448, or 683x348. Just take a look at www.screenresolutions.org to have a list of this kind of weird resolutions, each with a little percentage of users. Where do these numbers come from?

The answer is easy, once you know that screen resolution is detected with a little javascript code accessing the values of screen.width and screen.height, respectively (like in the following example), and that Firefox computes these values taking the zoom level into account.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
 <title>Screen resolution</title>
 <meta http-equiv="content-type" content="text/html;charset=utf-8" />
</head>

<body>
<h1>Screen resolution</h1>

<p>Width x Height: <span id="resolution"></span></p>

<script type="text/javascript">
  window.onload = function() {
    document.getElementById('resolution').innerHTML = screen.width + 'x' + screen.height;
};
</script>
</body>
</html>
If you try this page with Firefox, you can see that changing the resolution (ctrl-plus and ctrl-minus) does affect the numbers you get (press ctrl-0 to reset standard zoom level).

Unfortunately, it seems that there are not valid generic methods for getting the zoom level, at least according to this page on StackOverflow.

lunedì, gennaio 14, 2013

I18n with Yii

Curious about Yii, I wanted to test its I18n features.

The documentation on the web site is a good starting point, but some hints are missing, and I had to google a bit to find out.

You can see which language is currently set by accessing the value of Yii::app()->language.

If needed, you can also change the value, for instance to view a string in different languages. For instance, this code in a view:

<?php foreach(array('en', 'it', 'pl') as $lang): ?>
  <?php Yii::app()->language=$lang ? ?>
  <p><?php echo $lang ? ?>:
  <?php echo Yii::t('App', 'Internationalization experiments') ? ?></p ?>
<?php endforeach ?>

would produce something like:

en: Internationalization experiments
it: Esperimenti con l'internazionalizzazione
pl: Eksperymenty z internacjonalizacją
Yii has a good and "natural" support for language plural rules. This is something everybody programming should take care of. For instance, see how easy it is to manage different plural forms for Polish:

<?php Yii::app()->language='pl' ?>  
 <ul>  
 <?php for($i=0; $i<=30; $i++): ?>  
 <li><?php echo Yii::t('App', '{n} file|{n} files', $i); ?></li>  
 <?php endfor ?>  
 </ul>  

To write a translation string like jeden plik|{n} pliki|{n} plików is enough to obtain

  • 0 plików
  • jeden plik
  • 2 pliki
  • 3 pliki
  • 4 pliki
  • 5 plików
  • 6 plików
  • ...
  • 10 plików
  • 11 plików
  • 12 plików
  • ...
  • 21 plików
  • 22 pliki
  • 23 pliki
  • 24 pliki
  • 25 plików
  • 26 plików
  • ...
Last but not least, if you want to access preferences set in the user's browser, you can follow the excellent example in Rethrown Exception.

sabato, gennaio 05, 2013

Rimuovere i byte BOM dal codice sorgente

È un classico problema che ci si può trovare ad affrontare maneggiando codice sorgente di applicazioni, costituito per lo più da semplici file di testo. Si tratta dei byte del cosiddetto BOM, aggiunti a volte dagli editor all'inizio dei file Unicode per contraddistinguere il tipo di codifica utilizzata (UTF-32, UTF-16, UTF-8).

Un editor esadecimale, o anche più semplicemente il programma od, sono in grado di mostrarci i byte inseriti. Prendiamo ad esempio un file che inizia così:

  /* questo è il punto di avvio dell'applicazione
   */
  require_once('config.php');  // inclusione del file di configurazione   require_once('functions/generic_functions.php');  // inclusione...
Se con od diamo un'occhiata, ci rendiamo conto dei caratteri inseriti nelle prime posizioni:

$ od -a index.php | head
0000000   o   ;   ?   <   ?   p   h   p  cr  nl  cr  nl  sp  sp   /   *
0000020  sp   q   u   e   s   t   o  sp   C   (  sp   i   l  sp   p   u
0000040   n   t   o  sp   d   i  sp   a   v   v   i   o  sp   d   e   l
0000060   l   '   a   p   p   l   i   c   a   z   i   o   n   e  cr  nl
0000100  sp  sp  sp   *   /  cr  nl  cr  nl  sp  sp   r   e   q   u   i
0000120   r   e   _   o   n   c   e   (   '   c   o   n   f   i   g   .
0000140   p   h   p   '   )   ;  sp  sp   /   /  sp   i   n   c   l   u
0000160   s   i   o   n   e  sp   d   e   l  sp   f   i   l   e  sp   d
0000200   i  sp   c   o   n   f   i   g   u   r   a   z   i   o   n   e
0000220  cr  nl  sp  sp   r   e   q   u   i   r   e   _   o   n   c   e

Il file dovrebbe invece iniziare direttamente con il simbolo <, come qui:
$ od -a index.php | head
0000000   <   ?   p   h   p  cr  nl  cr  nl  sp  sp   /   *  sp   q   u
0000020   e   s   t   o  sp   C   (  sp   i   l  sp   p   u   n   t   o
0000040  sp   d   i  sp   a   v   v   i   o  sp   d   e   l   l   '   a
0000060   p   p   l   i   c   a   z   i   o   n   e  cr  nl  sp  sp  sp
0000100   *   /  cr  nl  cr  nl  sp  sp   r   e   q   u   i   r   e   _
0000120   o   n   c   e   (   '   c   o   n   f   i   g   .   p   h   p
0000140   '   )   ;  sp  sp   /   /  sp   i   n   c   l   u   s   i   o
0000160   n   e  sp   d   e   l  sp   f   i   l   e  sp   d   i  sp   c
0000200   o   n   f   i   g   u   r   a   z   i   o   n   e  cr  nl  sp
0000220  sp   r   e   q   u   i   r   e   _   o   n   c   e   (   '   f
Fortunatamente, possiamo facilmente cercare i file "incriminati" per sistemarli, se ne abbiamo bisogno.

Una pagina di stackoverflow abbonda di suggerimenti al riguardo: elegant-way-to-search-for-utf-8-files-with-bom.

Su LinuxAsk si trova poi il suggerimento più carino su come ottenere tutti i byte a partire dal quarto:

tail --bytes=+4 text.txt



giovedì, dicembre 22, 2011

Seconda o terza persona per la descrizione delle funzioni?


Quando si scrive la descrizione di una funzione, a fini di documentazione, si deve indicare cosa essa fa e, in genere, che valore restituisce.

Ad esempio:
 /**
   * Returns the number of pixels in the picture.
      * @return integer the number of pixels in the picture
   */
   public function getPixels()
   {
     return $this->width * $this->height;
   }

La prima riga deve essere in seconda persona (imperativo) o in terza (indicativo presente)? La domanda sorge spontanea, visto che ci sono diverse abitudini, e diverse raccomandazioni. Vedi ad esempiohttp://drupal.org/node/487802#comment-1720946dove si trova questo "studio":

I like As If's explanation in #27. This got me thinking... Coding standards are always so personal -- there are choices in the Drupal standards (and standards at companies I've worked for in the past) that I don't like, but that I recognize as valid choices that simply differ from the style choices I prefer.
So, different people can have different valid opinions on whether function 1-line headers should be 2nd or 3rd person, and today I specifically looked, via web search, for doc standards that mentioned second person, and found one: Python http://www.python.org/dev/peps/pep-0257/ I found many more projects that prescribe 3rd person for doc header one-liners, besides the two I mentioned in my original issue report, but there is at least one thoughtful example of 2nd person being the standard.
I also thought it would be interesting to see what existing doc sets are actually using (independent of whether they have standards) -- both open-source and proprietary. Summary: They are all over the map. Here are some examples:
* The Java API uses 3rd person fairly consistently, although if you poke around, you can find 2nd-person instances: http://java.sun.com/javase/6/docs/api/
* Perl doc is all over the map, and some function doc lines don't even start with a verb (they don't have standards that I could find, and it shows): http://perldoc.perl.org/index-functions.html
* PHP doc uses 3rd person for some sets of functions http://us.php.net/manual/en/ref.filesystem.php, and 2nd person for others http://us.php.net/manual/en/book.strings.php (although there are some 3rd-perso
Opzionin instances in this list, and their standard is 3rd-person)
* MySQL uses 2nd person: http://dev.mysql.com/doc/refman/5.4/en/string-functions.html
* Python is a mix: http://docs.python.org/3.0/library/math.html
* JQuery is a mix: http://docs.jquery.com/Traversing
* Microsoft's MFC classes seem to use mostly 3rd person: http://msdn.microsoft.com/en-us/library/3azzex5f(VS.80).aspx [note to self: don't use parens () in a URL, as Drupal's URL translator doesn't seem to recognize it when pasted in from the URL bar in my browser]
* Google Maps API uses 3rd person: http://code.google.com/apis/maps/documentation/reference.html
* Amazon EC2 API uses 3rd person: http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/


Il problema sorge quando si usano diversi stili nello stesso contesto. Ad esempio, in symfony ci si accorge che l'imperativo e la terza persona singolare sono mescolati anche nella descrizione dei task standard:

plugin
  :add-channel    Add a new PEAR channel
  :install             Installs a plugin
propel
  :build               Generate code based on your schema
  :build-all          Generates Propel model and form classes, SQL and initializes the database

ma la terza persona è comunque prevalente. E a me piace di più (cosa fa la funzione? calcola questo e quello, restituisce il tal valore...).

sabato, febbraio 26, 2011

Il web 2.0 in poche parole

Questo video è molto suggestivo, e illustra un sacco di concetti importanti in meno di cinque minuti: