Comprendere il ciclo interno
Il ciclo interno è un concetto fondamentale nello sviluppo software che influisce significativamente sui cicli di produttività e feedback degli sviluppatori. La comprensione e l'ottimizzazione del ciclo interno è fondamentale per le procedure DevOps efficienti e il miglioramento continuo.
Che cos'è il ciclo interno?
Il ciclo interno è il processo iterativo eseguito da uno sviluppatore durante la scrittura, la compilazione e il debug del codice. Rappresenta il ciclo di feedback rapido che si verifica localmente nel computer di uno sviluppatore prima che il codice venga condiviso con il team o distribuito nell'ambiente di produzione.
Caratteristiche chiave
- Esecuzione locale: Viene eseguito interamente sulla workstation dello sviluppatore
- Iterazione rapida: Progettato per commenti e suggerimenti rapidi e rapidi cambiamenti
- Ripetizione frequente: Esecuzione molte volte al giorno durante lo sviluppo attivo
- Attenzione individuale: Ottimizzato per la produttività a singolo sviluppatore
- Attività di pre-commit: Si verifica prima che il codice entri nel controllo della versione
Molti team di sviluppo riconoscono il ciclo interno come qualcosa che vogliono mantenere il più breve possibile perché il feedback più rapido porta a una maggiore produttività e una migliore qualità del codice.
Variazioni del ciclo interno in base alla tecnologia
Le attività specifiche nel ciclo interno di uno sviluppatore dipendono in modo significativo da:
- Tecnologie usate: Linguaggi di programmazione, framework e ambienti di runtime
- Strumenti disponibili: IDE, sistemi di compilazione e framework di test
- Preferenze per gli sviluppatori: Ottimizzazioni e abitudini dei singoli flussi di lavoro
- Tipo di progetto: Applicazioni Web, librerie, microservizi o app per dispositivi mobili
Esempio: Ciclo interno dello sviluppo di librerie
Per lo sviluppo di librerie, un ciclo interno tipico include:
- Codifica: Scrivere o modificare il codice della libreria
- Edificio: Compilare la libreria
- Collaudo: Eseguire test unitari per verificare la funzionalità
- Debug: Risolvere i problemi individuati durante i test
- Commettere: Salvare le modifiche nel repository Git locale
Esempio: Ciclo interno dello sviluppo Web front-end
Per il lavoro di front-end web, il ciclo interno è ottimizzato in modo diverso.
- Codifica: Modificare HTML, CSS e JavaScript
- Impacchettare: Eseguire gli strumenti di compilazione (Webpack, Vite e così via)
- Rinfrescante: Ricaricare il browser per visualizzare le modifiche
- Debug: Usare il browser DevTools per controllare il comportamento
- Commettere: Salvare le modifiche nel repository Git locale
Cambio contestuale
La maggior parte delle codebase moderne comprende più componenti, quindi il ciclo interno di uno sviluppatore può alternare a seconda di ciò che si sta lavorando:
- API back-end: Concentrarsi su codice, compilazione, test, debug
- Interfaccia utente front-end: attenzione al codice, al bundle, all'aggiornamento, all'ispezione
- Schema del database: Concentrarsi sulle migrazioni, sui test, sul rollback
- Infrastruttura: Concentrarsi sulla configurazione, la distribuzione, la convalida
Categorizzazione delle attività del ciclo interno
I passaggi all'interno del ciclo interno possono essere raggruppati in tre categorie di attività generali:
1. Sperimentazione
Attività che aggiungono valore cliente:
- Codifica: Scrittura di nuove funzionalità o correzione di bug
- Progettazione: Pianificazione dell'architettura o delle interfacce utente
- Prototipazione: Esplorazione di nuovi approcci o soluzioni
Caratteristico: Queste attività sono le uniche che aggiungono direttamente valore al prodotto finale.
2. Raccolta dei feedback
Attività che verificano la qualità:
- Compilazione: Compilazione del codice per verificare la sintassi e le dipendenze
- Collaudo: Esecuzione di unit test per convalidare la funzionalità
- Debug: Identificazione e risoluzione dei problemi
- Analisi del codice: esecuzione di linter e analizzatori statici
Caratteristico: Queste attività non aggiungono valore direttamente , ma forniscono feedback essenziale per garantire la qualità e la correttezza del codice.
3. Imposta
Attività necessarie ma che non aggiungono valore o feedback:
- Commettere: Salvataggio del codice nel controllo di versione
- Configurazione: Configurazione degli ambienti di compilazione
- Sincronizzazione: Scaricamento delle modifiche più recenti dai repository remoti
- Aggiornamenti della documentazione: Aggiornamento di file o commenti README
Caratteristico: Queste attività sono necessarie, ma non aggiungono valore al cliente né forniscono commenti e suggerimenti. Se un'attività non è necessaria, è uno spreco e deve essere eliminata.
Categorizzazione di esempio: sviluppo di librerie
Per lo scenario di sviluppo della libreria:
| Activity | Categoria | Purpose |
|---|---|---|
| Coding | Sperimentazione | Aggiunge il valore del cliente |
| Edificio | Raccolta commenti e suggerimenti | Verifica che il codice si compili |
| Test/debug | Raccolta commenti e suggerimenti | Convalida la funzionalità |
| Commit | Imposta | Necessario ma non aggiunge alcun valore |
Annotazioni
L'inserimento dell'impegno nella categoria fiscale può sembrare severo, ma la categorizzazione aiuta a identificare le attività che devono essere ridotte o posticipate fino a quando non siano assolutamente necessarie.
Ottimizzazione del ciclo interno
Dopo aver classificato i passaggi all'interno del ciclo, è ora possibile stabilire i principi di ottimizzazione:
Principi di ottimizzazione di base
1. La velocità è proporzionale al cambiamento
- Traguardo: Eseguire il ciclo il più velocemente possibile
- Principio: Il tempo di esecuzione totale deve essere proporzionale alle dimensioni delle modifiche apportate
- Beneficio: Piccole modifiche ottengono un feedback rapido; le modifiche di grandi dimensioni richiedono più tempo
2. Ottimizzare la qualità del feedback, ridurre al minimo il tempo di feedback
- Traguardo: Ottenere le informazioni più utili nel tempo più breve
- Principio: Bilanciare i test completi e l'iterazione rapida
- Beneficio: Intercettare rapidamente i problemi critici rinviando i controlli meno critici
3. Ridurre o rinviare l'imposta
- Traguardo: Ridurre il sovraccarico non necessario
- Principio: Eliminare i rifiuti e rinviare le attività non critiche
- Esempio: Rinviare gli aggiornamenti della documentazione fino all'ora di commit
4. Combattere la crescita della complessità
- Sfida: Con l'aumento delle codebase, i cicli interni rallentano naturalmente
- Ragione: Più codice significa più test, dipendenze e tempo di compilazione
- Impatto: Anche le piccole modifiche richiedono tempi di raccolta di feedback sproporzionati
Problema della codebase monolitica
Nelle codebase monolitiche di grandi dimensioni, è possibile riscontrare situazioni in cui:
- Piccola modifica: Modificare una funzione
- Costo sproporzionato: Attendere più di 10 minuti per la compilazione completa e la suite di test
- Frustrazione per gli sviluppatori: Calo della produttività man mano che i cicli di feedback rallentano
- Cambio di contesto: Gli sviluppatori perdono concentrazione in attesa di build
Si tratta di un problema da risolvere in modo proattivo.
Strategie per l'ottimizzazione codebase di grandi dimensioni
Teams può adottare diverse strategie per ottimizzare il ciclo interno per codebase di dimensioni maggiori:
1. Compilazioni e test incrementali
Compilare e testare solo le modifiche apportate:
- Sistemi di compilazione intelligenti: Rilevare i file modificati e ricompilare solo i componenti interessati
- Selezione test: Eseguire solo i test interessati dalle modifiche al codice
- Rilevamento delle dipendenze: Comprendere quali test dipendono dal codice
- Utensileria: Usare sistemi di compilazione come Bazel, Buck o Gradle con compilazioni incrementali intelligenti
Vantaggi:
- Riduzione drastica dei tempi di compilazione per piccole modifiche
- Tempo di feedback proporzionale per la modifica delle dimensioni
- Cicli di iterazione più veloci
2. Memorizzazione nella cache dei risultati intermedi
Memorizzare nella cache gli artefatti di compilazione per velocizzare le compilazioni complete:
- Memorizzazione nella cache locale: Archiviare gli oggetti compilati e i risultati dei test in locale
- Memorizzazione nella cache distribuita: Condividere gli artefatti di compilazione tra i membri del team
- Esecuzione remota: Spostare la compilazione su server farm cloud
- Utensileria: Implementare la memorizzazione nella cache con sistemi come ccache, sccache o soluzioni basate sul cloud
Vantaggi:
- Evitare la compilazione ridondante del codice non modificato
- Biuld pulite più veloci dopo commutazioni di ramo
- Riduzione del tempo di esecuzione della pipeline CI/CD
3. Modularizzazione e condivisione binaria
Suddividere la codebase in unità di piccole dimensioni e condividere file binari:
- Estrarre librerie: Spostare le funzionalità condivise in pacchetti separati
- Definire i limiti: Creare chiare interfacce e dipendenze dei moduli
- Pacchetti di versione: Pubblicare versioni stabili delle librerie interne
- Gestire le dipendenze: Usare gli strumenti di gestione pacchetti per usare versioni stabili
Cautela: Questa strategia può essere una spada a doppio taglio se eseguita in modo non corretto (vedere la sezione Cicli aggrovigliati di seguito).
Vantaggi al termine delle operazioni seguenti:
- Unità di compilazione più piccole
- Controllo delle versioni e distribuzione indipendenti
- Limiti architetturali più chiari
- Componenti riutilizzabili tra progetti
Rischi quando fatto male:
- Dipendenze intrecciate che richiedono modifiche su più repository
- Aumento delle imposte dovute al sovraccarico del ciclo esterno
- Problemi di incompatibilità di versione
Informazioni sui cicli complessi
Il concetto di cicli intrecciati illustra cosa accade quando la modularizzazione viene eseguita in modo non corretto, causando l'intreccio dei cicli interni ed esterni.
Ciclo esterno
Prima di comprendere i cicli tangled, è necessario definire il ciclo esterno:
Caratteristiche del ciclo esterno:
- Collaborazione tra team: Il codice viene condiviso con il team tramite richieste pull
- Controlli di qualità: Revisioni del codice, analisi automatizzate, controlli di sicurezza
- Integrazione: Il codice viene unito al ramo principale e distribuito
- Imposta più elevata: Maggiore sovraccarico dovuto alla collaborazione e all'automazione
- Feedback più lento: Da minuti a ore invece che da secondi a minuti
Scenario di modularizzazione
Si consideri questo scenario comune:
Stato iniziale: Un'applicazione monolitica con un framework specifico dell'applicazione che esegue un'operazione di sollevamento pesante.
Decisione di modularizzazione: Estrarre il framework in un pacchetto separato.
Passaggi di implementazione:
- Effettuare il pull del codice in un repository separato: Il codice framework passa a un repository dedicato
- Configurare la pipeline CI/CD: Compilazione e pubblicazione automatizzata per il pacchetto framework
- Aggiungere controlli di qualità: revisioni delle richieste pull, analisi della sicurezza, flussi di lavoro di approvazione
- Pubblica come pacchetto: Framework diventa una dipendenza con controllo delle versioni
Risultato iniziale: Le cose funzionano bene inizialmente. Il monolite consuma versioni stabili del framework.
Quando si verifica un ingarbugliamento
Scenario di problema: È necessario sviluppare una nuova funzionalità che richiede nuove funzionalità complete nel framework.
Il punto di dolore: È ora necessario co-evolvere il codice in due repository separati con una dipendenza binaria tra di esse.
Che succede:
- Aggiungere un metodo al framework: Creare una nuova funzionalità nel repository del framework
- Passare attraverso il ciclo esterno: Revisione del codice, test, analisi di sicurezza, approvazione
- Attendere la pubblicazione del pacchetto: Il pacchetto framework deve essere compilato e pubblicato
- Aggiornare l'applicazione: Modificare l'applicazione per usare il nuovo metodo del framework
- Ripetere: Ogni iterazione richiede un ciclo di ciclo esterno completo
Il problema: Il ciclo interno della codebase originale include ora il ciclo esterno del codice del framework.
Imposta sul ciclo esterno
Il ciclo esterno include un'imposta significativa:
- Revisioni del codice: Attendere che i revisori forniscano commenti e suggerimenti
- Analisi della sicurezza: Controlli automatizzati di vulnerabilità e conformità
- Firma binaria: Firma basata su certificati per i pacchetti pubblicati
- Pipeline di rilascio: Automazione e test della distribuzione
- Controlli di approvazione: approvazioni manuali per le release di produzione
Impatto:Non si vuole pagare questa imposta ogni volta che si aggiunge un metodo a una classe e si vuole usarlo immediatamente.
Soluzioni alternative per gli sviluppatori
Cosa accade in genere:
Hack locali: Gli sviluppatori creano soluzioni alternative per connettere i loop interni:
- Riferimenti ai pacchetti locali: Puntare al file system locale anziché al pacchetto pubblicato
- Moduli secondari Git: Includere l'origine del framework direttamente nell'applicazione
- Collegamenti simbolici: Creare collegamenti tra repository
- Pacchetti pre-release: pubblicare sui feed di test
Conseguenza: Queste soluzioni alternative diventano rapidamente confuse e richiedono comunque di pagare l'imposta sul ciclo esterno alla fine.
Il modo giusto per modularizzare
La modularizzazione non è intrinsecamente cattiva : può funzionare brillantemente quando eseguita correttamente:
Buona modularizzazione:
- Interfacce stabili: Le API del framework cambiano raramente
- Evoluzione indipendente: Framework e applicazione si evolvono separatamente
- Limiti chiari: Responsabilità e contratti ben definiti
- Accoppiamento libero: Dipendenze minime tra i componenti
Modularizzazione non valida:
- Accoppiamento stretto: Il framework e l'applicazione devono cambiare insieme
- Co-evoluzione frequente: Ogni funzionalità richiede modifiche al framework
- Limiti non chiari: Le responsabilità si sovrappongono tra i componenti
- Separazione artificiale: Divisione fatta per motivi organizzativi, non tecnici
Principio chiave: Fare incisioni modulari attentamente in base ai limiti architettonici effettivi, non alla struttura organizzativa.
Procedure consigliate per l'ottimizzazione del ciclo interno
Monitorare e misurare
Tenere traccia delle metriche del ciclo interno:
- Tempo di compilazione: Quanto tempo richiede la compilazione?
- Tempo di esecuzione dei test: Quanto tempo vengono eseguiti i test?
- Ritardo feedback: Tempo trascorso dal salvataggio alla visualizzazione dei risultati
- Soddisfazione degli sviluppatori: Sondaggio del team sui punti critici
Strumenti per la misurazione:
- Creare analisi di sistema
- I profiler delle prestazioni IDE
- Report di esecuzione dei test
- Sondaggi sulla produttività degli sviluppatori
Risolvere i rallentamenti in modo proattivo
Segnali:
- Gli sviluppatori si lamentano di compilazioni lente
- Il cambio di contesto aumenta durante le attese di compilazione
- Il team inizia a ignorare i test in locale
- Le richieste pull includono codice "non testato"
Strategie di risposta:
- Analizzare immediatamente le cause radice
- Assegnare priorità al lavoro di ottimizzazione
- Coinvolgere l'intero team nelle soluzioni
- Misurare i miglioramenti nel tempo
Bilanciare i compromessi
Compromessi chiave da considerare:
| Ottimizzazione | Vantaggio | Cost |
|---|---|---|
| Bild incrementali | Compilazioni locali più veloci | Configurazione di compilazione complessa |
| Memorizzazione nella cache della build | Compilazioni pulite più veloci | Sovraccarico di archiviazione e rete |
| Modularizzazione | Unità di compilazione più piccole | Potenziali cicli intrecciati |
| Minor numero di test | Feedback più rapido | Riduzione della confidenza |
| Esecuzione parallela | Tempo complessivo più veloce | Utilizzo delle risorse più elevato |
Principio: Il miglioramento di un aspetto causerà spesso problemi in un altro. Valutare continuamente i pro e i contro.
Allineamento del team
Responsabilità condivisa:
- Architetti: Progettare per testabilità e modularità
- Gli sviluppatori: Scrivere test efficienti ed evitare dipendenze non necessarie
- DevOps: Fornire l'infrastruttura di compilazione e la memorizzazione nella cache
- Gestione: Assegnare priorità al lavoro di ottimizzazione del ciclo interno
Pratiche culturali:
- Considerare il tempo del ciclo interno come metrica chiave per la produttività
- Rendere la "compilazione lenta" un motivo valido per sospendere il lavoro delle funzionalità
- Festeggiare i miglioramenti dell'inner loop
- Condividere le conoscenze di ottimizzazione tra i team
Punti chiave
Tenere presente questi principi:
- Nessun proiettile d'argento: Non esiste una soluzione universale per l'ottimizzazione del ciclo interno
- Comprendere il problema: Identificare quando si verificano rallentamenti e le relative cause radice
- Misura tutto: Tenere traccia delle metriche per comprendere l'impatto delle modifiche
- Agire in modo proattivo: Risolvere i problemi prima che influiscano gravemente sulla produttività
- Compromessi di saldo: Ogni ottimizzazione ha costi; scegliere saggiamente
- Modularizzare attentamente: Suddividere codebase in base ai limiti tecnici, non alla praticità
- Miglioramento continuo: L'ottimizzazione del ciclo interno è in corso
L'architettura è importante: Le decisioni su come creare, testare ed eseguire il debug delle applicazioni influiranno in modo significativo sulla produttività e sulla felicità degli sviluppatori. Investire tempo per padroneggiare bene questi concetti fondamentali.