Il punto di vista di uno sviluppatore iOS su codebase gonfiate, trappole del legacy e il test dei 20 MB.
Sono uno sviluppatore iOS dal 2008, specializzato in streaming e podcasting. Ho iniziato a lavorare con HLS su iPhone nel 2009, e nel corso degli anni ho costruito, mantenuto, debuggato e a volte ereditato applicazioni di streaming di ogni dimensione.
E ho sviluppato un'opinione piuttosto decisa su cosa significhi davvero "clean code" in questa industria. Non la versione da manuale. Quella vera. Quella che scopri quando apri il progetto Xcode di qualcun altro e il tuo primo istinto è chiudere il coperchio.
Ho esaminato centinaia di annunci di lavoro e contratti freelance per sviluppatori iOS nel corso degli anni. Quasi ogni singolo annuncio elenca con orgoglio "clean code" e "best practices" tra i requisiti. Principi SOLID. MVVM. Copertura dei test unitari. Design pattern. L'intero programma.
E poi apri il progetto.
Il divario tra gli annunci di lavoro e la realtà
Ecco cosa ho trovato realmente aprendo quei progetti di streaming presumibilmente "clean code": file XIB del 2014 che gestiscono ancora flussi di interfaccia critici. Bridging header Objective-C più lunghi di intere applicazioni. Tre layer di rete diversi che coesistono perché nessuno ha osato rimuovere quello vecchio. Un pattern Coordinator impilato sopra un pattern Navigator impilato sopra un pattern Router, perché ogni nuovo lead architect ha aggiunto il suo preferito senza rimuovere il precedente.
Il "clean code" nella maggior parte degli annunci di lavoro per app di streaming è un'aspirazione, non una descrizione. Descrive ciò che il responsabile delle assunzioni vorrebbe che la codebase sembrasse, non ciò che vi aspetta nel repository.
Ecco la verifica che nessuno scrive nella descrizione del lavoro: quel pattern Coordinator di cui sono così orgogliosi? Coordina altri tre pattern di navigazione che nessuno ha avuto il tempo di rimuovere. Quei principi SOLID? Si sono dissolti nel momento in cui il CEO ha voluto una funzionalità consegnata entro venerdì. I test unitari? Coprono la schermata di login e assolutamente nient'altro.
Non sto lanciando pietre da una casa di vetro. Ho contribuito io stesso a questo tipo di entropia. Tutti gli sviluppatori mobile lo hanno fatto. Ma penso che l'industria dello streaming abbia una versione particolarmente grave di questo problema, e ho una teoria su come individuarlo istantaneamente.
La regola dei 20 MB: un test decisivo per la qualità del codice delle app di streaming
Pensate a cosa fa realmente un'applicazione di streaming. Recupera un manifesto (una playlist HLS, un MPD DASH), invia segmenti a un lettore video, visualizza un'interfaccia attorno ad esso (canali, EPG, impostazioni), e questo è praticamente tutto. Il contenuto viene trasmesso in streaming. La parte pesante — il video, l'audio, le miniature — vive su un CDN, non nel binario della vostra app.
Ecco quindi la mia teoria: se un'applicazione di streaming pesa più di 20 MB sull'App Store, ogni megabyte oltre quella soglia è probabilmente codice "sporco". Non malevolo, non necessariamente buggato, ma codice che non dovrebbe essere lì. Codice che riflette decisioni architetturali sbagliate accumulate in anni di compromessi.
Guardiamo alcuni numeri. La mia ultima app, My TV Channel, pesa 9 MB sull'App Store. Nove. Funziona nativamente su iPhone, iPad, Mac (Apple Silicon), Apple TV e Apple Vision Pro. Supporta 9 lingue. Gli utenti possono creare e guardare canali TV lineari 24 ore su 24, 7 giorni su 7, con programmazione VOD-to-Live, notifiche push, download offline, picture-in-picture, multiview, ricerca avanzata con input vocale, modalità solo audio, canali privati e un sistema completo di gestione dei contenuti per i creatori. Sono più funzionalità di molte app di streaming mainstream.
Andate a verificare sull'App Store voi stessi. La maggior parte delle principali app di streaming pesa tra 80 e 200 MB. Alcune vanno anche oltre. È da 10 a 20 volte più pesante di My TV Channel. Queste app hanno lo stesso compito principale: trasmettere video in streaming. Eppure portano l'equivalente di decine di My TV Channel nei loro binari.
Certo, i grandi servizi di streaming hanno bisogno di una parte di quel peso extra. Supportano ampie matrici di dispositivi, funzionalità di accessibilità, DRM offline, a volte giochi. Ma più di 150 MB per quella che è, fondamentalmente, un'applicazione di streaming video? Quel divario racconta una storia di codice legacy, compromessi cross-platform e anni di debito architetturale accumulato.
Da dove viene il gonfiamento
Dopo anni di consulenza su progetti di streaming, continuo a vedere gli stessi schemi che gonfiano i binari delle applicazioni ben oltre il necessario.
Tecnologie di interfaccia legacy. I file XIB e Storyboard sono un classico. Erano il modo standard per costruire interfacce iOS per anni, e incorporano grafi di oggetti serializzati, vincoli di layout, a volte persino riferimenti a immagini direttamente nel binario. Molte app di streaming trasportano ancora XIB dal loro lancio iniziale, patchati e mantenuti ma mai migrati verso SwiftUI o anche pattern UIKit moderni. Ogni file XIB è debito tecnico congelato con un'estensione .xib.
L'overhead dei framework cross-platform. React Native, Flutter, Kotlin Multiplatform: ognuno aggiunge il proprio runtime, il proprio layer di collegamento, le proprie astrazioni. Per un'app di streaming dove il componente più critico in termini di prestazioni è comunque il lettore video nativo, il compromesso tra comodità cross-platform e dimensione del binario raramente ha senso. Finite per distribuire un motore JavaScript (o una VM Dart, o un runtime KMP) solo per renderizzare una griglia di miniature che SwiftUI o UIKit gestisce in una frazione della dimensione.
È la stessa logica che ha portato i team backend nella tana del coniglio dei microservizi. Ricordate quando ogni startup aveva bisogno di Kubernetes e 47 container Docker per servire una API REST che una singola app Django poteva gestire? L'equivalente mobile è distribuire tre runtime di framework per visualizzare una lista di video. Lo schema è identico: adottare complessità architetturale che risolve problemi che non avete, a un costo che pagherete per anni.
L'accumulo di design pattern. Questo è più sottile. Non si manifesta direttamente come megabyte, ma moltiplica file, classi e astrazioni, il che aumenta le dipendenze a tempo di compilazione, i metadati incorporati e la dimensione del binario. Ho visto progetti di streaming con un ViewModel, un Coordinator, un UseCase, un Repository, un DataSource, un Mapper e un'Entity separati per ogni singola schermata. Sono sette file dove due basterebbero. Moltiplicate per 30 schermate e ottenete 210 file invece di 60, ognuno dei quali aggiunge i propri metadati di classe al binario.
VIPER è il peggior colpevole in questo senso. È stato progettato per un mondo UIKit dove i view controller massicci erano un problema reale. Portare VIPER in un progetto SwiftUI è come portare un camion dei pompieri per una candela. Le view SwiftUI sono già leggere, stateless e componibili per design. Avvolgere ciascuna di esse in uno stack VIPER (View, Interactor, Presenter, Entity, Router) aggiunge cinque file e tre livelli di indirezione per ciò che SwiftUI gestisce con una struct e una classe @Observable. Ho visto team farlo comunque perché "è la nostra architettura", e il risultato è sempre lo stesso: più boilerplate che logica di business.
Asset incorporati che dovrebbero essere remoti. Font incorporati, animazioni incorporate (i file JSON Lottie sono famigerati per questo), immagini placeholder incorporate in risoluzione 3x per ogni classe di dispositivo. In un'app di streaming, quasi ogni risorsa visiva potrebbe essere caricata on demand da un CDN. Incorporarle è la strada della pigrizia, e si vede sulla bilancia.
Codice morto da funzionalità abbandonate. I framework di A/B testing lasciano dietro di sé condizionali che non vengono mai ripuliti. Esperimenti di funzionalità falliti restano nella codebase perché "potremmo riprenderli". L'SDK di analytics del fornitore di due generazioni fa viene ancora compilato perché qualcuno ha dimenticato di rimuoverlo dal Podfile.
Il costo reale: quando il codice sporco decide la vostra roadmap
Il gonfiamento del binario è una cosa. Ma la peggiore conseguenza di una codebase sporca non è la dimensione del download. È ciò che succede in ogni riunione di pianificazione dello sprint.
Conoscete la frase. Un product manager chiede una nuova funzionalità — diciamo picture-in-picture, modalità solo audio, un semplice aggiornamento dell'interfaccia — e le prime parole che escono dalla bocca del lead developer sono: "Sarà complicato."
Quella frase è il test olfattivo del codice sporco. Non "ci vorrà tempo", non "dobbiamo pensare ai casi limite", ma complicato. La codebase è diventata così ingarbugliata che nessuno può prevedere cosa romperà in altri tre moduli toccandone uno.
Nelle codebase pulite, aggiungere funzionalità sembra naturale. Trovate il layer giusto, lo estendete, rilasciate. Nelle codebase sporche, è come un intervento chirurgico su un paziente senza cartella clinica. Ogni modifica richiede prima una spedizione archeologica. Ogni stima arriva con un moltiplicatore di rischio che nessuno osa mettere nero su bianco. Ogni sprint porta la riserva tacita: "supponendo che nulla di inaspettato si rompa."
Ho visto progetti di streaming dove l'implementazione di una semplice funzionalità "continua a guardare" richiedeva modifiche attraverso sette layer architetturali, due bridging header e un event bus personalizzato che nessuno comprendeva più pienamente. Questa non è ingegneria del software. È una negoziazione con ostaggi con la propria codebase.
E la parte che i team di prodotto raramente afferrano: il codice sporco non vi rallenta solo oggi. Decide cosa potrete costruire domani. Le funzionalità non vengono rifiutate perché sono cattive idee. Vengono rifiutate perché "la nostra architettura non lo supporta", che è un modo gentile per dire "ci siamo costruiti un vicolo cieco cinque anni fa e nessuno vuole ammetterlo". La codebase diventa il product manager de facto, ponendo il veto alle funzionalità attraverso la sola frizione.
Verifica di realtà: i vostri concorrenti con codebase più pulite consegneranno la stessa funzionalità in due settimane mentre il vostro team sta ancora mappando grafi di dipendenze in Miro. Non sono più intelligenti. Semplicemente non devono combattere il proprio codice prima di combattere il mercato.
Il test LLM: un nuovo modo per misurare la pulizia del codice
Oltre alla dimensione del binario, c'è un altro test decisivo che uso ultimamente, uno che non esisteva cinque anni fa.
Puntate un LLM sulla vostra codebase. Se non riesce a capirla, neanche la vostra prossima assunzione ci riuscirà.
Strumenti come Claude Code, OpenAI Codex e altri ambienti di sviluppo assistiti dall'IA sono molto bravi a navigare codebase ben strutturate. Possono leggere un progetto Swift pulito, comprenderne l'architettura, identificare bug, suggerire correzioni e implementare nuove funzionalità con una guida minima.
Ma provate a puntarli su un'app di streaming legacy con 15 anni di pattern accumulati, Objective-C e Swift mescolati, tre approcci diversi di dependency injection e un sistema di build tenuto insieme da script shell personalizzati. Il LLM farà fatica. Allucinirà relazioni tra classi che non esistono. Suggerirà correzioni che rompono altre parti del sistema. Produrrà più crash di uno sviluppatore senior.
Questa non è una limitazione dell'IA. È uno specchio. Se un LLM addestrato su milioni di repository non riesce a interpretare l'architettura del vostro progetto, significa che le vostre astrazioni perdono, i vostri nomi sono incoerenti, le vostre dipendenze sono aggrovigliate e la struttura del vostro progetto non segue nessuna convenzione riconoscibile.
Pensatelo come il test definitivo di "onboarding del nuovo sviluppatore". Un LLM approccia la vostra codebase con zero conoscenza istituzionale, solo riconoscimento di pattern e una comprensione ampia delle convenzioni di programmazione. Se si perde, anche il vostro prossimo sviluppatore junior si perderà. E il vostro prossimo sviluppatore senior passerà i primi tre mesi a districare gli stessi nodi, solo che ve li fatturerà.
Ora ribaltate la prospettiva. Quando la vostra codebase è pulita, succede qualcosa di interessante: strumenti come Claude Code non si limitano a revisionare il vostro codice, costruiscono con voi. Uso Claude Code su My TV Channel, e in una codebase pulita, può implementare una nuova funzionalità, scrivere i test e aprire una PR nel tempo che mi serviva per scrivere un ticket Jira. Legge l'architettura, comprende le convenzioni e produce codice che si integra.
È qui che le cose diventano interessanti per i team che ancora lavorano con sprint di due settimane. Gli sprint sono stati progettati per gestire l'incertezza umana: quanto tempo ci vorrà, a cosa ci impegniamo, quando facciamo la demo. Ma quando un agente IA può navigare in modo affidabile nella vostra codebase e consegnare codice funzionante in poche ore, è lo sprint stesso che diventa il collo di bottiglia. La riunione di pianificazione dura più dell'implementazione. Il cerimoniale attorno alla stima di una funzionalità costa più tempo della sua realizzazione.
Ovviamente, questo funziona solo se la codebase è pulita. Puntate Claude Code su un mostro infestato da VIPER, pieno di XIB e multi-framework, e tornerà ad allucinare. Lo strumento non crea la velocità. L'architettura pulita la crea. Claude Code si limita a rivelarla. E le codebase sporche? Non rallentano più solo i vostri sviluppatori umani. Impediscono anche all'IA di aiutarvi, e questo diventerà un handicap sempre più costoso.
Com'è davvero il clean code in un'app di streaming
Ho costruito My TV Channel da zero con questi principi in mente. Ecco cosa penso significhi il clean code per le applicazioni di streaming specificamente.
Seguire le convenzioni della piattaforma. Le Human Interface Guidelines di Apple esistono per un motivo. I componenti standard UIKit o SwiftUI vi offrono accessibilità, Dynamic Type, Dark Mode e localizzazione gratuitamente. Ogni componente personalizzato che costruite al loro posto è uno che dovete mantenere, testare su diversi dispositivi e debuggare quando Apple cambia qualcosa in una nuova versione di iOS. My TV Channel funziona su cinque piattaforme Apple con una codebase condivisa perché si appoggia sull'interfaccia nativa della piattaforma, non su qualche astuto layer di astrazione.
Mantenere un grafo delle dipendenze poco profondo. Un'app di streaming ha bisogno di un lettore video, un layer di rete e un layer di persistenza. Oltre a questo, ogni dipendenza di terze parti dovrebbe essere messa seriamente in discussione. Non tutte le dipendenze sono cattive. Uso Kingfisher per il caching delle immagini in My TV Channel perché fa una cosa bene, è attivamente mantenuto e la sua impronta binaria è ragionevole rispetto al valore che fornisce. Questa è l'asticella. La dipendenza risolve un problema reale meglio di quanto potreste fare con le API della piattaforma? È mantenuta? La dimensione è proporzionale al valore? Se la risposta a una qualsiasi di queste domande è no, non dovrebbe essere nel vostro Podfile. Il problema non è usare librerie. Il problema è usarne cinque quando una basterebbe, o usare un framework reattivo da 5 MB quando l'async/await nativo di Swift e Combine fanno lo stesso lavoro senza codice esterno.
Trattare la dimensione del binario come una funzionalità, non come una metrica. Gli utenti su reti cellulari, gli utenti con iPhone da 64 GB, gli utenti in mercati dove lo spazio di archiviazione è limitato: tutti traggono beneficio da un'app più leggera. Apple mostra la dimensione dell'app in modo prominente sull'App Store per un motivo. Un download da 9 MB si installa in pochi secondi su qualsiasi connessione. Un download da 150 MB richiede il Wi-Fi per molti utenti e compete per lo spazio con foto, messaggi e altre app a cui tengono di più.
Cancellare il codice. La parte più difficile nel mantenere una codebase pulita non è scrivere nuovo codice. È cancellare quello vecchio. I feature flag che non sono stati attivati da sei mesi, gli eventi di analytics che nessuno controlla nella dashboard, i percorsi di migrazione per formati di dati di tre versioni fa. Tutto deve andare via. Il miglior codice è il codice che non esiste.
Una sfida all'industria
Lo streaming è una delle categorie più competitive dell'App Store. Eppure la maggior parte delle app OTT distribuisce binari da 5 a 20 volte più grandi del necessario, trasporta anni di codice legacy e segue pattern architetturali che neanche l'IA riesce a districare.
La prossima volta che vedete un annuncio di lavoro per uno sviluppatore iOS in un'azienda di streaming che richiede "pratiche di clean code" e "architettura moderna", fate loro una sola domanda: quanto è grande il vostro binario?
Se la risposta supera i 50 MB per quello che è fondamentalmente un lettore video con un catalogo di contenuti, sapete esattamente cosa vi aspetta in quel repository.
E se volete vedere com'è un'app di streaming costruita da zero con principi di clean code, senza legacy, senza compromessi cross-platform, senza debito accumulato: scaricate My TV Channel e verificate la dimensione voi stessi.
9 MB. Cinque piattaforme. Tutte le funzionalità. Ecco com'è il clean code.