Per un programmatore possedere l’abilità del problem solving è fondamentale. Utile soprattutto (ma non solo) per il debugging, è una delle capacità più ricercate dai recruiter di questa professione. Non c’è nulla di più importante di saper riconoscere che c’è un problema, individuarlo e poi trovare la soluzione. Non importa che poi siate voi a doverlo sistemare: prima della capacità tecnica occorre possedere prontezza mentale.
Alcuni di noi sono più portati a pensare in maniera “ottimizzata”, ma questo non significa che chiunque non si possa allenare a sviluppare il problem solving. Lo sappiamo che nei curriculum tutti avete scritto di possederlo, ma se sapete di aver mentito potete ancora rimediare!
Il problem solving può essere utilizzato per diversi scopi. Solitamente si deve pensare a un metodo, un algoritmo per ottimizzare un processo, oppure si è alla ricerca della causa di un bug complesso. Il primo fondamentale requisito è ovviamente quello di capire il problema. Solo una volta che ci si è assicurati di aver compreso l’obiettivo e la situazione di partenza si può procedere alla risoluzione.
Forse il primo insegnamento che ci danno ad un corso di programmazione e anche il più importante. Affrontare un problema complesso nella sua interezza non porta quasi mai a buoni risultati: spesso, anzi, risulta impossibile da risolvere. Un bravo problem solver è in grado di individuare i sotto-problemi o step necessari per raggiungere l’obiettivo. Nel caso di un bug, ad esempio, è buona pratica isolare classi o parti di codice e valutare un po’ alla volta la logica e l’evoluzione del bug, fino a risalire all’origine.
Un tipico esempio dell’applicazione del principio divide et impera. Credits: catazine.wordpress.com
Da qui poi, nel caso di una risoluzione complessa, che richieda ad esempio un ripensamento della logica del programma, occorre procedere per step risolvendo un sotto-nucleo alla volta, aggiungendo man mano complessità alla soluzione per includere tutto. Lo stesso principio viene applicato al momento della progettazione di un software. Molto utile è stilare una lista di task o sub-goal ordinata, con la descrizione di ciò che serve per raggiungere il singolo obiettivo e le eventuali dipendenze temporali tra i task.
Dopo aver valutato cosa bisogna fare e le dipendenze esistenti, occorre valutare se ci sono delle operazioni che possono essere eseguite in parallelo. Se due task sono indipendenti tra di loro, è conveniente portarli a termine contemporaneamente. Ottimizzazione significa anche riduzione dei tempi morti, che potranno essere riutilizzati per operazioni più impegnative. Una revisione della lista è a questo punto importante: riorganizzare i task in un diagramma potrebbe facilitare la comprensione sul da farsi.
Quando ci si pone un problema, un errore commesso da molti è trovare una soluzione specifica per esso. Sarebbe conveniente, piuttosto, individuare una soluzione generica che possa adattarsi a tutti i problemi di quel tipo. Fondamentale è quindi cercare di categorizzare l’obiettivo e individuare la “famiglia” di problemi alla quale appartiene, così da trovare un algoritmo generale che possa essere riutilizzato al bisogno.
Questo punto è strettamente legato al precedente. Una volta che si è diviso il problema in sotto-problemi e si sono individuate le dipendenze corrette, la prima domanda da porsi è: “Esiste già una soluzione?”. Perché sprecare tempo ed energie se qualcuno ha già trovato la soluzione ai nostri problemi? Ovviamente è necessario, come detto prima, aver compreso bene lo scopo dei nostri sforzi, altrimenti si rischia di applicare la soluzione sbagliata. Non è detto però che si debba trovare l’intero procedimento. In tal caso, si procede nuovamente per sotto-task, cercando prima se qualcuno ha già risolto anche solo parte del nostro problema.
Quest’ultimo concetto è in realtà necessario per poter padroneggiare gli altri. La singola informazione non vive solo nel punto in cui si è presentato il problema, ma ha una sorgente, una destinazione, e attraversa una serie di step e di trasformazioni. Se nel momento di individuare i singoli task non si pensa a questo, si circoscrive la soluzione ad una singola parte, col rischio di non trovare quella ottimale. Occorre sempre tenere a mente il percorso che ha fatto e che farà il dato nel punto in cui lo stiamo maneggiando, in modo da trovare il pezzo perfetto da incastrare in un puzzle più grande.
Il problem solving può essere insegnato, ma fino a un certo punto. Aver compreso i fondamenti e la sua importanza è il punto di partenza, ma occorre fare tanta pratica e affrontare tanti problemi di natura diversa per migliorare. Non abbattetevi se dovesse essere difficile all’inizio: pian piano, risolvendo problemi e imparando dai propri errori, sarà sempre più semplice, fino a diventare naturale.
Questa abilità può essere applicata in qualsiasi aspetto della vita. Di fronte ad un problema, saperlo analizzare e procedere per passi può esserci utile per non fallire e per non arrendersi subito.
Grazie a Coderhood