
Dall'inizio del 2018 ricopro il ruolo di lead/capo/sviluppatore senior nel team — chiamalo come vuoi, ma il punto è che sono completamente responsabile di uno dei moduli e di tutti gli sviluppatori che ci lavorano. Questa posizione mi offre una nuova prospettiva sul processo di sviluppo, poiché sono coinvolto in un numero maggiore di progetti e partecipo più attivamente alle decisioni. Recentemente, grazie a queste due circostanze, ho realizzato quanto profondamente il livello di comprensione influisca sul codice e sull'applicazione.
Il pensiero che desidero esprimere si riduce al fatto che la qualità del codice (e del prodotto finale) è strettamente legata a quanto le persone che progettano e scrivono codice siano consapevoli di ciò che stanno facendo.
Forse ora state pensando: «Grazie, capitano. Certo, sarebbe utile capire cosa si sta scrivendo. Altrimenti, con lo stesso successo potremmo assumere un gruppo di scimmie per battere a caso sulla tastiera e rimanere soddisfatti». E avete assolutamente ragione. Pertanto, prendo per scontato che sappiate che avere una visione generale di ciò che state facendo è necessario. Questo può essere definito come un livello zero di comprensione, e non lo approfondiremo. Approfondiremo invece cosa è necessario comprendere e come ciò influisca sulle decisioni che prendete ogni giorno. Se avessi saputo queste cose in anticipo, mi sarei risparmiato un sacco di tempo sprecato e codice discutibile.
Anche se qui non vedrete una sola riga di codice, ritengo comunque che quanto detto finora sia di grande importanza per scrivere codice di qualità, espressivo.
Primo livello di comprensione: Perché non funziona?
I programmatori di solito raggiungono questo livello nelle fase iniziali della loro carriera, talvolta anche senza alcun aiuto da parte degli altri — almeno secondo le mie osservazioni. Immaginate di aver ricevuto un report di errore: una certa funzione nell'app non funziona, deve essere riparata. Come vi comportereste?
Il principio standard è il seguente:
- Trovare il frammento di codice che causa il problema (come fare ciò è un argomento a parte, che affronto nel mio libro sul codice obsoleto)
- Apportare modifiche a quel frammento
- Assicurarsi che il bug sia stato corretto e che non siano emersi errori regressivi
Ora concentriamoci sul secondo punto: le modifiche al codice. Ci sono due approcci a questo processo. Il primo: comprendere cosa sta accadendo nel codice attuale, identificare l'errore e correggerlo. Il secondo: procedere a tentoni, ad esempio aggiungere +1 a un'istruzione condizionale o a un ciclo, vedere se questa funzione funziona nello scenario desiderato, poi provare qualcos'altro e così via all'infinito.
L'approccio corretto è il primo. Come spiega nel suo libro Code Complete Steve McConnell (che consiglio vivamente), ogni volta che apportiamo modifiche al codice, dobbiamo essere in grado di prevedere con sicurezza come questo influenzerà l'applicazione. Riporto una citazione a memoria, ma se la correzione di un bug non funziona come previsto, questo dovrebbe mettere in guardia seriamente, e devi mettere in discussione tutto il tuo piano d'azione.
Per riassumere, per eseguire una buona correzione di bug che non comprometta la qualità del codice, è necessario comprendere sia la struttura del codice che la fonte del problema specifico.
Secondo livello di comprensione: Perché funziona?
Questo livello si raggiunge in modo molto meno intuitivo rispetto al precedente. Io, essendo ancora un programmatore alle prime armi, l'ho appreso grazie al mio capo, e successivamente l'ho spiegato più volte ai neofiti.
Questa volta immaginiamo che ti siano arrivati due report di bug: il primo riguarda lo scenario A, il secondo lo scenario B. In entrambi gli scenari c'è qualcosa che non va. Pertanto, inizi a lavorare prima sul primo bug. Seguendo i principi che abbiamo derivato per il primo livello di comprensione, esamini attentamente il codice pertinente al problema, scopri perché fa comportare l'applicazione in quel modo nello scenario A e apporti le modifiche sensate che producono esattamente il risultato che ti aspettavi. Tutto procede alla grande.
Poi passi allo scenario B. Ripeti lo scenario nel tentativo di provocare l'errore, ma - sorpresa! - ora tutto funziona come dovrebbe. Per confermare la tua ipotesi, annulli le modifiche apportate durante il lavoro sul bug A e il bug B ritorna. La tua correzione ha risolto entrambi i problemi. Che fortuna!
Non ti aspettavi affatto questo. Hai trovato un modo per correggere l'errore nello scenario A e non hai idea del perché abbia funzionato anche per lo scenario B. A questo punto, è molto allettante pensare che entrambi i compiti siano stati eseguiti con successo. È abbastanza logico: l'idea era di correggere gli errori, giusto? Ma il lavoro non è ancora finito: devi ancora capire perché le tue azioni hanno corretto l'errore nello scenario B. Perché? Perché potrebbe essere che questo funzioni su principi errati, e quindi dovrai cercare un'altra soluzione. Ecco un paio di esempi di casi simili:
- poiché la soluzione non è stata scelto specificamente per l'errore B tenendo conto di tutti i fattori, potresti aver danneggiato involontariamente la funzione C.
- non è escluso che ci sia anche un terzo bug nascosto, legato alla stessa funzione, e che la tua correzione del bug condizioni il corretto funzionamento del sistema nello scenario B su di esso. Adesso tutto sembra andare bene, ma un giorno questo terzo bug verrà notato e corretto. Allora nello scenario B si verificherà di nuovo un errore, e speriamo solo lì.
Tutto ciò introduce caos nel codice e un giorno ti ricadrà addosso — probabilmente nel momento meno opportuno. Dovrai raccogliere la volontà per dedicare tempo a capire perché tutto sembri funzionare, ma ne vale la pena.
Terzo livello di comprensione: Perché funziona?
La mia recente realizzazione è proprio legata a questo livello, e probabilmente è stato proprio questo a darmi il maggior vantaggio se solo ci fossi arrivato prima.
Per essere più chiari, esaminiamo un esempio: il tuo modulo deve essere compatibile con la funzione X. Non conosci molto bene la funzione X, ma ti è stato detto che per la compatibilità con essa devi usare il framework F. Altri moduli che si integrano con X funzionano proprio con quello.
Il tuo codice non è mai stato in contatto con il framework F dalla nascita, quindi implementarlo non sarà così semplice. Ciò comporterà gravi conseguenze per alcune parti del modulo. Tuttavia, sei completamente immerso nello sviluppo: scrivi codice per settimane, testi, rilasci versioni pilota, ricevi feedback, correggi errori di regressione, scopri complicazioni impreviste, non rispetti le scadenze inizialmente concordate, scrivi ancora un po' di codice, testi, ricevi riscontri, correggi errori di regressione: tutto questo per implementare il framework F.
E ad un certo punto realizzi - o forse senti dire da qualcun altro - che il framework F potrebbe non offrirti compatibilità con la funzione X. Forse tutto questo tempo e sforzi sono stati dedicati a qualcosa di completamente diverso.
Qualcosa di simile accadde una volta durante un progetto di cui ero responsabile. Perché è successo? Perché non comprendevo bene quale fosse il nocciolo della funzione X e come fosse collegata al framework F. Cosa avrei dovuto fare? Chiedere a qualcuno che propone il compito di sviluppo di spiegare chiaramente come il piano d'azione previsto porti ai risultati desiderati, invece di semplicemente ripetere ciò che era stato fatto per altri moduli, o credere sulla parola che così si doveva fare per la funzione X.
L'esperienza di questo progetto mi ha insegnato a rifiutare di avviare il processo di sviluppo finché non abbiamo una chiara comprensione del motivo per cui ci viene chiesto di eseguire determinate azioni. Rifiutare in modo diretto. Quando ricevi un incarico, il primo impulso è quello di prenderlo immediatamente, per non sprecare tempo. Ma la politica di 'congelare il progetto finché non entriamo nei dettagli' può ridurre drasticamente il tempo sprecato.
Anche se ti stanno facendo pressione, cercando di farti iniziare a lavorare, anche se non comprendi le motivazioni - resisti. Prima di tutto, chiarisci qual è l'obiettivo di questo compito e decidi se è la strada giusta per raggiungere l'obiettivo. Ho dovuto scoprire tutto questo a mie spese - spero che il mio esempio possa semplificare la vita a chi lo legge.
Quarto livello di comprensione: ???
Nella programmazione c'è sempre qualcosa da imparare e credo di aver toccato solo la superficie del tema della comprensione. Quali altri livelli di comprensione hai scoperto nel corso degli anni di lavoro con il codice? Quali decisioni hai preso che hanno avuto un effetto positivo sulla qualità del codice e delle applicazioni? Quali decisioni si sono rivelate errate e ti hanno insegnato una lezione preziosa? Condividi le tue esperienze nei commenti.
Fonte: habr.com
