MokaByte Numero 23  -  Ottobre 98

di 
Graziano Lo Russo
Programmazione  per pattern
  Pattern chiari, amicizia lunga: una miniserie dedicata a questo importante modello di programmazione



In questo articolo cominceremo a parlare di un argomento molto interessante: i Pattern. Inizierò dando qualche ragguaglio sulla storia e sul modo di pensare "per pattern" In questi ultimi anni sono avvenuti eventi di grande portata per la programmazione: la distribuzione via WEB, la programmazione generica e STL, i Pattern. A partire da questo articolo ha inizio una rubrica sui Pattern. L'argomento non si esaurirà in breve tempo perché, come vedremo, i Pattern sono fondamentalmente una scuola di pensiero che si può applicare a qualsiasi attività.



 

Nascita dei Pattern

I primi a usare i pattern nel progetto di software sono stati Ward Cunningham e Kent Beck nel 1987. Ne parlarono agli OOPSLA di quell'anno e la voce cominciò a correre.  Nel 1992 apparve un articolo di Peter Coad sulla prestigiosa Communications of ACM [Coad92]; nel 1994 furono pubblicati i primi articoli sui pattern sul Journal of Object Oriented Programming [Beck94] e sul C++ Report [Cope94]. L'interesse per i pattern esplose nel 1995, quando venne pubblicato "Design Pattern" di Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides. Ai quattro venne collettivamente affibbiato il nomignolo di "Banda dei Quattro", tanto che "GoF" (Gang of Four) è ormai un acronimo universalmente riconosciuto.
Nel frattempo si erano affermati gli altri "guru" dei Pattern oltre alla Banda dei Quattro: Kent Beck e Ward Cunningham, autori dei primi pattern e del primo seminario sui pattern (OOPSLA 1987), Ken Auer, Doug Lea, Richard Gabriel, James (Cope) Coplien, Douglas Schmidt, la "Banda dei Cinque" - Buschmann, Meunier, Rohnert, Sommerlad, Stal. Il riferimento su WEB del movimento dei pattern è il Wiki Wiki WEB (c2.com).
Christopher Alexander
Christopher Alexander è stato l'ideatore dei pattern. Architetto, in "The Timeless Way Of Building" [Alex79] e nelle opere successive propose i pattern come strumento di progettazione di architettura "a misura d'uomo". Le sue idee vennero immediatamente accolte nella comunità software. Nella prefazione del [GoF] troviamo scritto: "C.Alexander dice: 'Ciascun pattern descrive un problema ricorrente e la sua soluzione, in un modo tale che potrà riusare questa soluzione un milione di volte, senza mai farlo allo stesso modo due volte'. Alexander parlava di pattern per costruire edifici e città, ma ciò che dice è vero per i pattern di object oriented design".
Questo tipo di lateral thinking è sottolineato nella letteratura sui pattern, ma lo troviamo anche nelle opere di altri autori importanti nel software: Marvin Minskji cita Aristotele e Jean Piaget in "Psychology of Computer Vision": per fare un programma che impara è utile sapere come imparano gli esseri umani, specialmente i bambini; Stroustrup cita Popper contro "Platone, Hegel e Marx falsi profeti" per giustificare il C++ come linguaggio multiparadigma; Stepanov cita Occam e la scolastica medievale: per studiare i rapporti fra astratto e concreto, fra generalizzazione e specializzazione, nulla di meglio che la disputa sugli universali; Coplien cita sovente, oltre a Alexander, concetti Zen per l'integrazione del tutto nella parte, e gli architetti dell'Umanesimo italiano, fra cui Leonardo da Vinci e L. B. Alberti (1404-1472), notando anche che Alberti era un avvocato di formazione, non un architetto.
 
La triade: problema, contesto, soluzione
Cominciamo da questa enunciazione: un pattern è una triade composta da un problema, un contesto e una soluzione.
Per "contesto" si intendono le "condizioni al contorno", le forze in azione, di cui il pattern deve tenere conto e che vincolano la scelta della soluzione. Il termine "contesto" è impreciso in inglese quanto lo è in italiano, al punto che alcuni autori lo sostituiscono con l'espressione "forze presenti"; altri riservano la parola "problema" per indicare una brevissima descrizione sintetica (per esempio: come creare una sola istanza di una classe) e descrivono il problema insieme al suo contesto. [GoF] segue quest'ultima convenzione. Io farò invece riferimento allo stile alessandrino.
Innanzitutto: un pattern è una triade. Nessuno dei componenti da solo è un pattern. Soprattutto, una soluzione - un algoritmo, una tecnologia, una struttura organizzativa - da sola non è un pattern. Ci sono già tanti: algoritmi, metodologie, tool, modelli di riferimento. Non voglio dire che algoritmi, metodologie e tool siano inutili; alcuni pattern del [GoF] usano tecniche OO molto complesse. Ma il fuoco dei pattern è su come una soluzione risolve un problema in un contesto, non sulla soluzione in sé.
Il problema, il contesto e la loro discussione in rapporto alla soluzione tutti insieme costituiscono il pattern. Per prima cosa, una situazione diventa un problema solo in un certo contesto; per esempio, avere un'implementazione distinta per ogni oggetto di un sistema di norma non è affatto un problema, ma lo diventa quando l'oggetto è ogni singolo carattere all'interno di un testo trattato da un word processor: piuttosto, si può fare in modo di condividere gli attributi di stato, come font e dimensione, fra oggetti-caratteri distinti, piuttosto che replicarne migliaia di volte. Questo è il pattern Flyweight [GoF].
Un problema non è mai tale di per sé, ma quando esprime scompensi fra le forze in azione in un contesto: nel caso del Flyweight lo scompenso è fra l'esigenza di poter variare in modo indipendente le caratteristiche di ogni carattere e l'occupazione di memoria che risulterebbe dal rappresentare ogni carattere come un oggetto indipendente.
Questa constatazione basta a eliminare un'infinità di falsi problemi. Per esempio, il tempo di esecuzione di un pezzo di codice diventa un problema di efficienza solo in rapporto al contesto - questo è il pattern ThresholdSwitch presentato in [AuerBeck96]. Il [GoF] può essere letto sia come un repertorio di problemi che come un repertorio di soluzioni: spesso sapere riconoscere un problema è la vera parte difficile; trovato il problema la soluzione viene (quasi) da sola.
Il primo passo per entrare nella mentalità dei pattern è imparare a chiedersi "quale è il problema" piuttosto che "quale è la soluzione".
Dal punto di vista dei pattern, dispute come "è meglio Java" piuttosto che "è meglio C++", oppure "è meglio CORBA" piuttosto che "è meglio OLE" o "è meglio Oracle", senza precisare "per fare cosa?", sono senza senso. Piuttosto conviene capire bene quali pattern si addicono di più alla programmazione in C++ e quali alla programmazione in Java: non è detto che siano gli stessi (e non lo sono!).
Ogni soluzione è valida solo all'interno di un certo contesto; cambiando il contesto, cioè le forze all'opera (il linguaggio di programmazione è una delle forze), la soluzione allo stesso problema può essere un'altra. Ovvio? Prendete in mano una qualsiasi pubblicità e vedete un po' se non applica il pattern SonoIoLaSoluzioneDiTuttiITuoiProblemi. Per capire un contesto è necessario spirito di osservazione.
"Per scoprire pattern che siano vivi dobbiamo sempre partire dall'osservazione",dice Alexander [Alex79, pag. 254]. È sorprendente quanto a volte sia difficile frenare certa gente "solution oriented" e indurla a osservare e riflettere, piuttosto che cominciare a scrivere freneticamente codice o pagine e pagine di diagrammi Gantt. In effetti, i pattern non sono affatto solution oriented - sono problem oriented. Il terzo elemento di un pattern è la soluzione.
La soluzione deve essere davvero tale: deve risolvere il problema ed essere realizzabile, anzi deve essere già stata realizzata, non può essere una strategia, una metodologia o un'euristica, e neanche un abbozzo di soluzione o un rinvio del tipo "lascio ad altri il compito di implementare la mia idea tanto bella".
Il pattern State di [GoF], per esempio, non dice solo quando conviene usare una macchina a stati finiti (FSM): indica anche come si fa a realizzare una buona FSM in C++. Un pattern non sottovaluta mai la realizzazione della soluzione, perché una soluzione irrealizzabile non è un pattern.
Un buon pattern risponde in modo armonico alle forze che agiscono in un contesto. Per contrasto, "un cattivo pattern è un pattern incapace di contenere le forze in azione" [Alex79, pag. 129]; un cattivo pattern è come un arredamento mal fatto: scomodo, disarmonico, suscita stress e voglia di andarsene - non è abitabile. Quanto queste sensazioni siano reali lo sappiamo ogni volta che abbiamo da lottare contro certo software.
La soluzione è un terzo di un pattern: quindi è importante - un problema irrisolto non è un pattern - ma solo se è la soluzione al problema di cui parla il pattern. Sembrerà banale e lapalissiano, ma troppe volte si spreca tempo perché si parte dalla soluzione anziché dal problema. Certe volte succede perché si hanno le idee talmente confuse su cosa serva fare che si decide di farlo comunque, mascherandosi dietro all'adesione, alle soluzioni più alla moda (cioè più pubblicizzate): al momento in cui scrivo, OLE, Java e CORBA, così come Object Orientation, "downsizing", "three-tier" e "workflow" sono grandi fonti d'ispirazione. Non sto dicendo che questi approcci siano sbagliati, anzi, ma che applicarli in modo acritico è sbagliato. "Non è importante che un gatto sia bianco o sia nero: l'importante è che acchiappi i topi" (Deng Xiao Ping).
Se si hanno tanti gatti non ha neanche importanza che siano tutti bianchi o tutti neri. Ce ne possono essere persino di tigrati. Ho l'impressione che il problema di certi progetti sia di fare più attenzione alla livrea del gatto che alla sua abilità di cacciatore (e non ditemi che il paragone è peregrino: la cultura orientale e la filosofia Zen hanno avuto la loro parte nel movimento dei pattern).
Per esempio ci sono quelli che per prima cosa decidono sullo strumento, per esempio Java o un tale database, e quando vi azzardate a chiedere " per fare cosa?" vi guardano sprezzanti e sbalorditi per tanta ignoranza. Mi è capitato uno che voleva a tutti i costi usare un certo database "per proteggere il legacy" in un'applicazione nuova per cui non c'era ancora legacy; un altro il cui problema era avere ampio parco di piattaforme su cui fare girare il software ed era stato convinto (potenza della pubblicità!) che la soluzione era usare un ambiente di sviluppo unico su tutte le piattaforme: confondendo così la portabilità del programmatore con quella del programma.
Cosa è un buon pattern?
Quanto abbiamo detto finora non basta a distinguere un buon pattern: ci sono buoni pattern e cattivi pattern, proprio come ci sono buone idee e pessime levate d'ingegno (GoodAndBadPatterns).
Un buon pattern è una triade formata da una soluzione assodata a un problema ricorrente in un contesto specifico.
Per prima cosa, la soluzione deve essere assodata (well proven). Una soluzione inventata lì per lì non è un pattern. Neanche una soluzione provata in laboratorio con una proof of concept è un pattern. Semmai potrà diventarlo. Nel [GoF] compaiono solo pattern che sono stati applicati con successo almeno più volte in progetti software rilasciati. Possibile che una buona soluzione sia stata finora ignorata? Se nessuno l'ha mai messa in pratica è facile che abbia qualche problema nascosto. "La comunità dei pattern è aggressivamente critica verso l'originalità": detto da Coplien, che decide in merito agli interventi da accettare ai PLoP, la cosa ha un certo peso.
I pattern non si inventano: si scoprono. "Come si producono i pattern generativi? Non si producono: si trovano. Essi sono nei sistemi scritti dai virtuosi, nei sistemi che sono un sogno da mantenere" dice R. Gabriel in [PLoP2, pag. xii]; viene detto un sogno da mantenere, non da costruire: perché il software è molto più manutenzione ed evoluzione che scrittura di sistemi nuovi.
Un bell'esempio di scoperta progressiva di un pattern è il curiously recurring template pattern di [Cope95], che inizia proprio osservando un'analogia fra alcuni pezzi di codice presi da applicazioni indipendenti: l'analogia, la ricorrenza di soluzioni simili in domini diversi, indica la presenza di un pattern ancora da scoprire (il pattern curiosamente ricorrente è rimasto finora senza nome; ve ne parlerò in un prossimo futuro).
"Ciò che si ripete è importante" dice R. Gabriel.
Non solo la soluzione, ma anche il problema deve essere ricorrente. Una soluzione a un problema che capita di rado non è un pattern interessante.
Oltre che poco interessante, è uno spreco di tempo. Questione di economia. Un buon progettista conosce bene pochi buoni pattern per i problemi ricorrenti nel suo campo.
Studiare un pattern costa tempo e un buon progettista per prima cosa deve avere la sensibilità a individuare i problemi ricorrenti; una buona architettura software risolve in modo generico i problemi ricorrenti, risolve in modo "cablato" e non riusabile i problemi che ricorrenti non sono. Infine, un buon pattern deve essere collocato in un contesto preciso. Un pattern può essere astratto, ma mai vago.
Questo distingue un pattern da una euristica, come quelle che si trovano in [Riel96]: un'euristica può dire per esempio "usate più la delegazione e meno l'ereditarietà"; un pattern deve invece dire "per rendere più riusabile il progetto di una macchina a stati finiti basata su di un diagramma di stati singola, non nested e con transizioni atomiche, conviene usare la delegazione dinamica del comportamento degli stati secondo il pattern State che si trova a pagina 305 di [GoF]".
Per esempio, anche il Proxy nei [GoF] è un Pattern di delegazione (equivale all'uso di uno smart pointer in C++), ma risolve tutt'un altro genere di problemi, quindi è un altro pattern. Questo spiega perché molti pattern del [GoF] si assomigliano quanto a codice eppure restano pattern diversi; i meccanismi di programmazione offerti da un linguaggio sono meno numerosi dei problemi da risolvere.
Per contro, uno stesso pattern può avere diverse implementazioni, che usano idiomi di programmazione diversi.
Un'euristica è un "principio primo": dietro ogni pattern ci sta almeno un'euristica, quindi le euristiche sono importanti, ma "una cosa che i progettisti esperti sanno è di non risolvere ogni problema partendo dai principi primi" [GoF].
Coplien arriva a citare il motto di L.B. Alberti "le regole della pittura [degli edifici] e le regole degli edifici non sono le stesse, e la padronanza delle prime non assicura il successo nelle seconde" [Cope96], pag. 11.
Questo non vuol dire che le euristiche siano inutili, perché non ci sono solo problemi ricorrenti: le buone euristiche, come quelle di [Riel96], servono ad affrontare quei problemi per cui non ci sono (ancora) i pattern.
 
Pattern Is Real Stuff
Un buon pattern deve essere credibile. Per essere credibile un pattern per prima cosa deve calarsi nella realtà. Non è difficile trovare una soluzione a un problema.
Il difficile è trovare una soluzione che risolva tutto il problema e lo risolva sempre - nell'ambito del contesto dato, beninteso.
La descrizione di un buon pattern è sempre corredata da un buon numero di esempi e di controesempi concreti, tratti da esperienze reali e software rilasciato; almeno metà delle pagine del [GoF] sono dedicate a esemplificazioni.
Solo se un pattern è concreto si riesce a coglierne il significato, la "qualità senza nome". Un pattern astrae dai dettagli - ma astrarre non è sinonimo di semplificare.
"Il diavolo è nei dettagli" dice Coplien; la bontà di un pattern si vede dalla sua capacità di trattare i dettagli, le situazioni limite, i casi speciali. "Una buona soluzione ha abbastanza dettaglio che il progettista sappia come fare, ma è abbastanza generale da indirizzare un largo contesto" (Coplien).
Diagrammi di classi e diagrammi di interazione possono aiutare a spiegare gli esempi e ad essere più concreti, ma la prova della verità di un pattern è il codice che genera (PatternIsRealStuff).
Solo se il codice è convincente il pattern è valido. Viceversa, i buoni pattern nascono dall'osservazione del buon codice; come dice [Riel96], che pure non fa parte del movimento dei pattern, "new ideas are traditionally the product of grass roots mouvements standing at the lowest levels". Il modello MVC è nato dall'osservazione su come era elegante programmare l'interfaccia utente di SmallTalk, non viceversa.
Un corollario è che un pattern deve essere onesto.
I pattern a costo zero non esistono.
Ogni pattern comporta un costo e una complessità in più, mostra i suoi limiti quando cambia l'equilibrio delle forze, può comportarsi male in certe circostanze.
Un buon pattern ammette onestamente i suoi limiti, li descrive dettagliatamente e indica quali soluzioni sono possibili - le soluzioni saranno altri pattern, che a loro volta avranno altri limiti e così via: questo è il potere generativo dei pattern. Per esempio, il pattern Singleton risolve il problema di come creare un'unica istanza di una classe, ma apre il problema di distruggerla, o il problema di sincronizzare gli accessi all'unica istanza.
Per coordinare gli accessi concorrenti a un singleton, la soluzione più semplice è un mutex, ma usarlo pone problemi di inefficienza perché comporta una chiamata di sistema: ecco allora il pattern Double Checked Locking di D.Schmidt che risolve anche questo problema. E così via. Quello che è importante è che a ogni ciclo dell'iterazione cresce la consapevolezza della complessità e dell'intrecciarsi delle forze in gioco. Nessun pattern può risolvere da solo tutti i problemi in un contesto: solo una combinazione di pattern può riuscirci, così come non basta una parola per parlare: ci vuole un linguaggio. Perciò una combinazione di pattern, che collaborano per risolvere i problemi di un contesto, è chiamata linguaggio di pattern (Pattern Language).
Pattern Mining
Così come il software, si sa, non nasce riusabile, il design non nasce come pattern. Non si può inventare un pattern più di quanto si possa scrivere dal nulla software riusabile. Elaborare un pattern è un processo lento e faticoso.
Sono state coniate le espressioni pattern mining [Cope95b] e pattern hatching [Vlissides95] per sottolineare quanta fatica e perseveranza costa scrivere pattern. Ma perché bisogna affaticarsi a scavare se un pattern è solo una soluzione valida a un problema ricorrente in un contesto specifico? Non basta prendere la documentazione di un software che funziona e dire "questo è un pattern"? Una soluzione nasce sempre come soluzione legata a un dominio, mentre un pattern è il progetto di una soluzione indipendente dal dominio (questo è il significato dell'espressione "Design Pattern", non "pattern da usare nel design"). I problemi che un pattern di design risolve sono problemi di design, non problemi di dominio. Quando si sente parlare di pattern specifici per un dominio, p.e. pattern per real time, in realtà si intende dire "pattern per i problemi di design che sono importanti e ricorrenti solo in quel dominio".  Un pattern è indipendente dal dominio e quindi può essere riusato anche al di fuori del dominio in cui è stato trovato la prima volta. Il pattern Flyweight è una soluzione a un problema nel dominio dei word processor, ma si presta anche a risolvere il problema dell'ottimizzazione dei confronti fra stringhe: se si riesce a fare in modo che due stringhe aventi lo stesso contenuto condividano sempre lo stesso buffer di caratteri, allora il confronto per uguaglianza si farà non carattere per carattere ma confrontando direttamente l'indirizzo dei buffer. Un sistema che garantisce che due stringhe con lo stesso contenuto siano la stessa stringa è un ambiente simbolico. Quindi il pattern Flyweight serve anche per implementare un ambiente simbolico in C++.  Com'è possibile che un pattern, che è una soluzione di un problema in una contesto preciso, possa essere applicato a un altro problema in altro contesto?  Un buon pattern non affronta di petto un problema: lo risolve indirettamente, dal di dentro", catturando la struttura nascosta del problema e riconciliando le forze del contesto.
Un esempio è la gestione degli oggetti dinamici in C++. La soluzione di forza bruta è adottare la garbage collection e abolire il rilascio esplicito della memoria.
I pattern di gestione del lifetime di Cargill [Cargill96] non eliminano il rilascio esplicito, ma lo fanno cessare di essere un problema.
Un buon pattern si basa sulla struttura interna di una situazione, al di là dei dettagli contingenti. Per esempio: cos'hanno in comune una GUI con un server applicativo GUI-less? In apparenza ben poco.
Eppure entrambi sono sistemi reattivi: per entrambi il pattern di funzionamento fondamentale è il ciclo "accetta un comando - esegui il comando - propaga i cambiamenti - prepara e invia le risposte - rimettiti in attesa di un comando". Per entrambi il pattern dominante è il CommandExecution, complementato dall'Observer per propagare gli effetti dei cambiamenti - agli elementi della GUI in un caso, al database nell'altro.
D'altra parte esistono forze di contesto diverse nei due casi, per esempio i requisiti di efficienza, di sicurezza e di affidabilità di un server sono ben diversi da quelli di un client. Perciò la stessa soluzione architetturale generale sarà declinata in modo diverso. In questa capacità di esprimere sia la comunanza che la difformità sta la potenza dei pattern. Per conciliare in modo armonico le forze di un contesto bisogna sapere quali sono.
Avere questa conoscenza vuol dire avere capito la struttura profonda del contesto e del problema.
Questa "comprensione profonda" così difficile da definire, questa "qualità senza nome" è ciononostante molto importante: è ciò che distingue un pattern da una patch.
Cataloghi e linguaggi di pattern
Ci sono due modi di esporre dei pattern: per categorie e per linguaggi. Dal mio punto di vista si tratta di due modi complementari di raggruppare i pattern, uno più "orizzontale" e l'altro più "verticale", che non di tipi diversi di pattern.
Una categoria di pattern organizza i pattern secondo il problema. La si usa più o meno come un dizionario: una volta identificato il problema, si apre il libro della categoria di pattern al capitolo che corrisponde al problema e si cerca il pattern che sembra adattarsi meglio. Due tipici esempi di cataloghi di pattern sono [GoF] e [Buschmann96]. I cataloghi di pattern contengono relativamente pochi pattern e ognuno di essi è trattato in modo approfondito, soprattutto per quanto riguarda l'applicabilità e le alternative. 
Un linguaggio di pattern raccoglie pattern che cooperano per risolvere i problemi in un certo contesto. I pattern singoli sono commentati meno approfonditamente che nei cataloghi di Pattern; l'accento è sull'analisi del contesto e sulla collaborazione fra pattern. Alcuni pattern sono molto semplici, al limite si riducono al proprio nome (PatternAsNickname): dare un nome a qualcosa è il primo passo per riuscire a parlarne.
I pattern language sono sviluppati soprattutto nei proceeding dei PLoP, [PLoP1] e [PLoP2]. Esempi di pattern language sono i pattern language per ottimizzare un programma, quelli per disegnare le interfacce utente, quelli sul processo di produzione del software, e così via.
Conclusioni
In questo articolo abbiamo incominciato a scoprire i pattern. Continueremo nel prossimo con i linguaggi di pattern.
Bibliografia
[Alex79] Cristopher Alexander, "The Timeless Way of Building", Oxford Press, 1979.
[AuerBeck96] Ken Auer, Kent Beck, "Lazy Optimization", in [PLoP2], 19-42.
[Beck94] Kent Beck, "Patterns and Software Development", Dr. Dobb's Journal, Febr. 1994, 18-21.
[Buschmann96] Frank Buschmann et al., "A System of Patterns", J.Wiley 1996.
[Cargill96] Tom Cargill, "Managing Dynamic Objects in C++", Dr. Dobb's Journal June 1996, 16-22, 83-84.
[Coad92] Peter Coad, "Object Oriented Patterns", Comm. ACM, Sept. 1992, 151-159.
[Coad95] Peter Coad, "Object Models", Prentice Hall, 1995.
[Cope94] J.O. Coplien, "Generative Pattern Languages", C++ Report, 6(6), 1994.
[Cope94b] James O. Coplien, "Advanced C++ Idioms", Addison Wesley 1994 (II ed. corretta).
[Cope95] James O. Coplien, "Curiously recurring template pattern", C++ Report, Febr. 1995, 24-27.
[Cope95b] James O. Coplien, "Pattern Mining", C++ Report, Oct. 1005, 81-85.
[GoF] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, "Design Patterns", Addison Wesley 1995.
[PLoP1] Vlissides-Coplien-Kerth (eds.), "Pattern Languages of Program Design 1", Addison Wesley, 1995.
[PLoP2] Vlissides-Coplien-Kerth (eds.), "Pattern Languages of Program Design 2", Addison Wesley, 1996.
[Riel96] Arthur Riel, "Object Oriented Design Heuristics", Addison Wesley 1996.
[Vlissides95] John Vlissides, "Pattern Hatching", C++ Report, March_april 1995, 37-39.

 
 
 
 

MokaByte Web  1998 - www.mokabyte.it 
MokaByte ricerca nuovi collaboratori. Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it