
Il miglior modo di affrontare lo sviluppo di un’applicazione Android non esiste. Ci sono vari approcci, ognuno con i suoi pro e i suoi contro.
Tuttavia, per chi inizia o non ha mai avuto un approccio strutturato, può essere utile conoscere un approccio che “funziona” e che garantisce un quantitativo di bug il più basso possibile. Anche solo come punto di partenza per poi sperimentare altre strategie.
Quanto segue dunque non vogliono essere dogmi, regole o “must” da seguire, ma la mia opinione su quali sono le strategie migliori lì fuori per consegnare app in tempo, senza bug o con il minor numero e che rendono felici i nostri clienti e il nostro capo.
Table of Contents
Rompi tanto le palle sul definire i requisiti
A volte magari potrai sembrare pedante o rompicoglioni. Succede. Tuttavia, molti perdono questa componente fondamentale. Ovvero definire nel dettaglio quali sono i requisiti operativi prima di iniziare il lavoro.
Non solo l’happy path, ma ogni percorso alternativo e tutti i possibili casi di errore, con eventuali popup o bottomsheet per “recuperare” l’utente e non farlo schiantare nel nulla.
Partire a cannone senza sapere nel dettaglio cos’è necessario fare è garanzia di insuccesso.
A volte si preferisce non fare domande per “non rompere”, per sembrare intelligente o per mostrare di aver capito. E’ l’errore più grande da fare. Se sbagli, te ne renderai conto quando è troppo tardi, facendo una figura ben peggiore. Meglio sembrare idiota o rompipalle, che incompetente.
Chiediti: cosa devo fare? Come lo devo fare? E, se non è chiaro, perché lo devo fare?
E’ fondamentale avere chiari i requisiti al 100%, altrimenti è ovvio che il risultato non sarà allineato con le aspettative. Una volta che i requisiti sono chiari, sarà anche importante strutturare delle storie utente piccole abbastanza da permetterci di mantenere il focus quando ce ne occupiamo.
Avere una storia con 10-15 requisiti rende impossibile ricordarseli tutti e seguirli nel modo corretto senza perdere pezzi per strada. Raccogliere, strutturare, scrivere e tenere aggiornati i requisiti è sicuramente poco divertente, ma è un’attività fondamentale da fare. Peggio viene fatta, peggiore sarà il software finale e più saranno le modifiche da fare quando magari potrebbe essere troppo tardi.
Vuoi essere il primo a vedere i prossimi articoli? Entra nella nostra community 🙂
Purtroppo la maggior parte delle persone trova noioso scrivere requisiti, controllarli e verificare che tutto sia corretto. Tuttavia è la fase più importante dello sviluppo.
E’ fondamentale anche definire un design di riferimento che mostra tutti i casi possibili in modo che sia chiaro cosa ci sia da fare. Se non abbiamo a disposizione designer, potrebbe tornarci utile utilizzare gli use case degli UML per definire tutti i possibili scenari. E’ naturale che la grafica potrebbe non piacere o essere il fattore più discusso successivamente, ma almeno il software farà quanto richiesto.
Non solo tutto ciò è necessario che lo sappia tu, ma tutto il team. E’ fondamentale dunque presentare i requisiti e verificare che tutti i componenti del team ne siano consapevoli, siano d’accordo con i requisiti e non ci siano dubbi. Saltare questi step può darti l’illusione di andare più veloce, in realtà avrai molti più problemi alla fine.
Se non sei tu il responsabile del team potresti non poter fare molto a riguardo, ma potrebbe essere un punto da sollevare in un’eventuale retrospective in cui proponi la tua opinione.
Trasla i requisiti in test specifici
Molti ci danno poca importanza non essendo “divertente” fare test, ma come fai a verificare che i tuoi requisiti vengono rispettati al 100%?
E, soprattutto, come fai a essere sicuro che facendo modifiche non “rompi” qualcosa o, peggio, cambi il comportamento di qualche componente dimenticandoti requisiti definiti anni fa? Magari da sviluppatori che non sono più nel progetto?
Il tuo compito come programmatore non è solo fare software, ma anche verificare che rispetti i requisiti. Personalmente trovo divertente anche fare i test, perché mi permettono di riflettere su ciò che devo fare realmente. Ed è una “sfida” trovare tutti gli scenari possibili e verificare che siano coperti.
In tal senso il mio consiglio è usare il tastino che ti permette di vedere la coverage dei tuoi test quando li lanci:
Questo tasto ti mostrerà tutto il codice che non è testato. Basterà guardare sulla parte sinistra e vedrai il codice testato in verde, mentre quello non testato in rosso:
Questo ti permetterà rapidamente di capire se non hai gestito dei casi e non hai previsto dei comportamenti che il tuo codice invece fa. In modo da essere sicuro di aver coperto tutto nel modo migliore. Naturalmente non ha senso che copri anche codice tipo funzioni di una riga che lanciano eventi. Allo stesso tempo però meglio coprire quelle righe che non perdersi dei pezzi che poi risulteranno importanti.
Nello specifico, dovresti coprire il 100% o quasi del codice di questi componenti:
- ViewModel
- UseCase
- Eventuali oggetti estratti con logica a se stante
Fare i test inoltre ti obbliga a pensare ai requisiti. A verificarli. Ad avere un modo univoco per verificare di aver gestito tutte le opzioni. Questo inoltre ti permette di verificarlo a ogni modifica che fai al codice. Altrimenti, potresti testare a mano il codice e verificare che tutti i casi funzionano. Poi fare una piccola modifica, convinto che impatta solo l’happy path, e quindi non essere portato a verificare ogni singolo scenario.
In fase di code review, ti permette anche di comprendere meglio il tuo flusso di pensiero. Se infatti i test non hanno senso o è palese che manca un caso d’uso, lo vedi velocemente. Per chi fa verifica ci mette relativamente poco a comprendere come funziona il tuo codice e che casi hai coperto.
Come vedi anche dal grafico sopra, il tempo di realizzo verrà velocemente recuperato in fase di verifica del codice. Il tuo codice infatti avrà una qualità media molto più alta e avendo tutto il codice coperto, non rischi di introdurre nuovi bug fixando quelli che ti vengono segnalati. Rispetto a chi non ne fa potresti aver bisogno del 5% o 10% del tempo per risolvere i problemi. Inoltre, non rischierai di introdurre regressioni senza accorgertene.
Vuoi essere il primo a vedere i prossimi articoli? Entra nella nostra community 🙂
Molti saltano questo passaggio dicendo che “non hanno tempo”, “dobbiamo andare veloce”, “il project manager non vuole che facciamo i test”.
Ormai però c’è l’IA, in particolare cursor. Dandogli il tuo codice, ti basterà chiedergli di strutturare una suite di test per il tuo codice. Certo, devi avere un’idea di quello che devi fare, ma ti renderà il compito a un costo molto ridotto.
Se non sai fare test, qui c’è un mio talk che ti insegna a fare test per Android che ti darà i concetti fondamentali per iniziare a farlo con successo. Dovessi voler approfondire ulteriormente, trovi un sacco di contenuti utili in questo repository che raccolgono materiale utile in tal senso.
Nel mio talk non parlo dello screenshot testing che tuttavia ho scoperto essere molto efficace. Ma il buon Piero ci ha risolto il problema, quindi puoi imparare rapidamente come farlo.
Ormai fare test se vuoi consegnare app di qualità è semplicemente obbligatorio.
Git-Flow e Feature Flag vs Trunk Based Development
C’è molto hype nella community per il Trunk Based Development e c’è un “odio” generalizzato per le merge/pull request e in genere git flow.
C’è anche molto hype per il TDD e il pair programming. Principalmente perché sono tra i principi dell’eXtreme Programming. Della serie “se fai TDD e pair programming non ti servono le code review. Pertanto ha senso integrare sempre, basta PR”.
Avendo lavorato facendo TDD, pair programming e anche code review contemporaneamente, la mia opinione è un po’ diversa.
Intanto, fare pair programming è molto provante. Farlo dal vivo è ancora fattibile, da remoto è abbastanza complesso. Se hai gran feeling con il tuo partner può anche essere divertente e imparare molto. Peccato che sia qualcosa di abbastanza raro. Molti colleghi magari ci puoi andare d’accordo in generale, ma dover condividere la tastiera con loro per il 100% del tuo tempo lavorativo, lo trovo molto impegnativo.
Non è solo la mia opinione. Ho avuto un collega che mi ha confessato di aver preso letteralmente sonno durante una sessione di pair programming. Succede.
Mi è anche capitato di fare pair programming con sviluppatori senior che non volevano mollare la tastiera. E fare magari anche 8h filate a guardare altre persone programmare. Non molto divertente. E anche poco utile.
Anche facendo pair programming, venivano fuori molti punti di miglioramento durante le code review. Pertanto, dire che con il pair programming ne puoi fare a meno, lo vedo molto ottimistico.
Quindi, se a uno piace fare pair programming e ha dei colleghi con cui va d’accordo, ben venga assolutamente. Anche se non è per tutti e imporlo a livello di team lo trovo molto provante. Se poi lavorate da remoto ancora di più.
Trovo molto stupefacente che molti project manager non vogliono che venga fatto per “velocizzare”. Si è molto più produttivi in pair programming. Troppo a mio avviso, nella maggior parte dei casi non serve tutto questo impegno per deliverare software di qualità. Ma farlo è solo un beneficio per l’azienda, per cui non comprendo chi non lo vuole far fare.
Detto questo, il trunk based development nasce dall’esigenza di “integrare il prima possibile”. Se infatti fai un branch e aspetti molto tempo prima di integrarlo con main, andrai molto probabilmente incontro a conflitti molto spiacevoli.
Vuoi essere il primo a vedere i prossimi articoli? Entra nella nostra community 🙂
Tuttavia, anche usando Git-Flow, puoi integrare rapidamente. Basta:
- Fare storie piccole. Avendo pochi requisiti, farai tendenzialmente anche pull request piccole. Verranno dunque verificate più rapidamente, portando a un’integrazione rapida.
- Invece che fare epic branch, usa il più possibile i Feature Flags. Questo ti permetterà di integrare subito il codice, anche se non è finito, potendolo nascondere in caso devi fare un rilascio. E abilitandolo solo quando è pronto. Evitando tuttavia di avere epic branch per molto tempo che non vengono aggiornati.
- Se sei costretto a fare epic branch, cerca di tenerli aggiornati il più possibile. Molti si dimenticano o non hanno voglia. Ci sta, ma poi la paghi tutta alla fine.
In questo modo dovresti ridurre la maggior parte dei problemi e i “LGTM” di chi vede una pull request di +2000 righe senza neanche aprirla.
Ogni variante che ho visto rispetto a Git-Flow lo trovo peggiorativo. Estrarre un release branch all’inizio, per esempio, è facile che porti ad avere release branch multipli. Con relativi problemi di integrazione. Se ognuno lavora su un suo branch e poi integra alla fine, ha lo stesso problema forse anche di più.
Il mio consiglio è imparare bene Git-Flow, provare a seguirlo pedissequamente con i Feature Flag, e poi provare eventuali varianti. E’ difficile che sapendo usare bene Git-Flow sceglierete altre alternative.
Test manuale delle pull request prima di integrarle
L’errore più grande di quando si hanno dei test automatici, è che ci si fida dei test automatici 🙂
I test automatici sono molto utili, ma molte volte provando l’applicazione davvero ci sono dei problemi che emergono che non vengono coperti dai test. In particolar modo problemi di concorrenza o problemi relativi alla UI come animazioni, navigazione, scrolling non corretto, ecc.
Vedendo anche delle pull request può sembrare tutto corretto e validato, provandolo invece potremmo accorgerci di scenari non coperti. O non interamente gestiti.
Per questo motivo, ha molto senso testare le pull request prima di integrarle con il main branch. E’ vero che questo tende a rallentare l’integrazione, ma meglio integrare qualcosa che sta in piedi, che non qualcosa che ha dei bug e potrebbe bloccare un rilascio.
La sfida più grande è che spesso non ci sono dei QA dedicati o, se ci sono, non hanno tempo per testare le merge request prima di integrarle. Avendo altri compiti da fare. Sperare che i programmatori testino il codice è abbastanza utopico 🙂 già fare codice per i test è arduo, chiedergli di testare manualmente è molto arduo.
Tuttavia, più si riduce il feedback loop tra “faccio codice” e “lo testo e trovo bug”, più velocemente vengono risolti e più si riducono i problemi che arrivano alla fine. E’ ovvio che questo non è sempre così fattibile da fare. Personalmente cerco sempre di farlo quando faccio una code review, ma comprendo che pochi programmatori siano disposti a farlo.
La clean architecture è caldamente consigliata
Quando ho iniziato a programmare c’erano veramente grandi dubbi su come programmare per Android e come impostare un’app. Per venire incontro a questo problema, Google si è messo una mano sulla coscienza, è ha definito le sue guide sulla Clean Architecture.
Nel dubbio, ha creato anche un progetto chiamato Now In Android con un esempio di come viene implementata. In modo da rendere molto ovvio come va strutturata un’app.
Tuttavia, mi è capitato di vedere ancora ragazzi che proponevano architetture custom. Con navigator inventati da loro. Oppure grande uso di classi base invece di usare la composizione, cosa che generalmente dovrebbe essere fatta al contrario.
Oppure iniziare un progetto in MVP o in RxJava o senza Jetpack Compose o peggio ancora Java 💀. Ormai sono tecnologie che non ha senso utilizzare per nuovi progetti. E ha senso iniziare a migrare quelli vecchi per non rimanere indietro.
Vuoi essere il primo a vedere i prossimi articoli? Entra nella nostra community 🙂
Non avessi familiarità con use case, repository, moduli, ecc. è il momento di studiare come impostare correttamente un’app seguendo la clean architecture. Un buon punto di partenza potrebbe essere questo video o questo video.
Naturalmente ci sono tante altre architetture alternative all’MVVM “standard” di Google. Tipo Mavericks di Airbnb. O MVI o altre soluzioni. La mia opinione è che conviene avere qualcosa di più standard possibile e che segue il più possibile le indicazioni di Google. Non perché siano intrinsecamente migliori o più efficaci. Semplicemente perché riduce il tempo di ingresso di nuovi programmatori nel progetto.
Poi se vuoi provare Mavericks o MVI per un progetto personale ci sta. MVI per certi versi è anche una soluzione più scalabile rispetto all’MVVM proposto da Google. Tuttavia ragionare su ciò che è standard e per cui ci sono già esempi, è generalmente più semplice e genera meno discussioni.
Se un nuovo sviluppatore deve impararsi un framework custom, è un costo che non ha senso accollarsi. Se poi questo framework è nostro, è ancora peggio. Perché potremmo avere bug e quindi dovremmo anche mantenerlo, documentarlo e risolverne i problemi.
Lo stesso vale per la modularizzazione. Mettere eccessivi moduli perché vogliamo fare una nostra architettura invece di seguire quanto suggerito da Google può portare a problemi, rallentamenti e costi di ingresso per nuovi sviluppatori.
In sostanza, ogni decisione custom ha un comportamento molto simile a quello che vediamo in questa vignetta:
In generale, ha senso seguire il più possibile gli standard della community e ridurre al massimo soluzioni custom che ci danno l’illusione di andare più veloce. In realtà sono un costo che esplode nel tempo.
Continuous integration / Continuous delivery
Ne ho parlato anche nel mio talk sul testing ma un elemento fondamentale per la verifica del codice sono la continuous integration e la continuous delivery.
La prima ci permette di verificare a ogni commit che quello che stiamo facendo non sta “spaccando” altri requisiti e funzionalità. In particolare avremmo dei tool di analisi statica (come ktlint che ci permette di verificare che il nostro codice segue le guideline sul codice kotlin per Android) e la nostra suite di test che verifica che non stiamo rompendo main prima di mergiare la nostra pull request.
Questo ci permette anche di evitare discussioni con i colleghi. Se c’è un errore sui test o il codice non segue ktlint, non rischiamo di passare per “rompipalle” perché segnaliamo l’ennesima volta un errore minore. Sarà ktlint a bloccare automaticamente la pull request. Inoltre, lanciando il comando ./gradlew ktlintFormat nella maggior parte dei casi questi problemi vengono risolti automaticamente.
Il secondo, ci permette automaticamente di pubblicare l’app in testing e pubblicarla nello store. Evitando errori manuali nel processo, perdere tempo nel farlo o di dimenticare qualcuno quando lo facciamo. Sono strumenti ormai standard e oserei dire quasi “obbligatori” in progetti che hanno più di 6 mesi di vita con l’ambizione di durare per un tempo di almeno 1-2 anni.
Evita codice duplicato
Sembra un problema ovvio, tuttavia non lo è. Molto spesso ci troviamo a scrivere codice duplicato. Magari per semplici popup o codice che riteniamo “non così importante”. Peccato che se facciamo un errore lì, è difficile tener traccia di tutti i punti in cui abbiamo messo quel popup e risolvere il problema ovunque.
Una delle strategie per ridurre questo problema è usare le Micro Feature. Da quando le utilizziamo è sicuramente uno degli strumenti più utili che abbiamo per riusare il codice. E centralizzare tutta la parte logica. Riducendo quindi i problemi di manutenzione. Ed evitando anche di avere classi enormi con varie funzionalità. Ma splittandole in elementi più piccoli e anche più facili da riutilizzare.
Conclusioni
Spero di averti dato qualche spunto per migliorare il tuo processo di sviluppo per produrre applicazioni migliori. E magari averti dato qualche risorsa da approfondire per imparare di più. Dovesse esserti utile, entra nella nostra community per non perderti i prossimi articoli 🙂
Commenti recenti