Perché i programmatori hanno bisogno di limiti

I limiti rendono migliore l'arte, il design e la vita.

Veniamo da una cultura di No Limits o Push the Limits, ma in realtà abbiamo bisogno di limiti. Stiamo meglio con loro, ma devono essere i limiti giusti.

Censura per una musica migliore

Quando c'erano limiti imposti esternamente a ciò che si poteva dire in una canzone, in un libro o in un film, gli scrittori dovevano fare affidamento su metafore per evidenziare un punto particolare.

Prendi il classico di Cole Porter del 1928, Let's Do It (Let's innamorarci). Sappiamo tutti cosa intendessero con "It" e non è "Fall in Love". Sospetto che dovessero aggiungere il parentetico al titolo per evitare la censura.

Passiamo rapidamente al 2011 e guardate la Slob di Three 6 Mafia sulla mia manopola. Ad eccezione della prima strofa che in realtà è metaforica, il resto del testo è sul naso.

Mettendo da parte i meriti del talento artistico (o la loro mancanza) per un momento, la canzone di Cole Porter allude a ciò che la canzone di Three 6 Mafia solleva su di noi in dettagli lancinanti che non lasciano nulla all'immaginazione.

Il problema è che se non ti iscrivi a fare l'amore come descritto nel testo di Three 6 Mafia, troverai la canzone nel peggiore dei casi volgare e nel migliore dei casi manchi il segno. Ma con Cole Porter, l'ascoltatore può evocare il proprio amore personale facendo fantasia.

Quindi le limitazioni possono dare alle cose un fascino maggiore.

Lo squalo non funziona

Il piano originale di Steven Spielberg nel raccontare la storia di Jaws era mostrare lo squalo. Ma è stato sempre rotto. Il più delle volte, non potevano mostrare lo squalo, la stella del film.

Questo film che ha inaugurato il Blockbuster non esisterebbe nella sua attuale incarnazione se le difficoltà meccaniche non imponessero limiti su ciò che Spielberg potrebbe fare.

Perché questo film è di gran lunga superiore a quello in cui viene mostrato lo squalo? Perché, ancora una volta, le parti mancanti vengono riempite da ciascun visualizzatore. Prendono le loro fobie personali e le proiettano sullo schermo. Quindi la paura è PERSONALIZZATA per ogni spettatore.

Gli animatori lo sanno da anni. Riproduci la schermata di arresto anomalo, quindi taglia le conseguenze. Questo ha due vantaggi. Non è necessario animare l'incidente e l'incidente si verifica nella mente dello spettatore.

Molte persone pensano di aver visto sparare alla madre di Bambi. Non solo non la vediamo sparare, non la vediamo mai DOPO che le hanno sparato. Ma la gente giurerà di aver visto entrambi. Ma non è MAI mostrato.

Quindi i limiti possono migliorare le cose. Molto meglio.

Scelte, scelte ovunque

Immagina di essere un pittore e ti chiedo di dipingermi un quadro e tutto ciò che dico è "Dipingimi qualcosa di bello. Qualcosa che mi piacerà molto. "

Ora vai nel tuo studio e ti siedi lì a fissare una tela bianca. E fissare e fissare incapace di dipingere. Perché?

Perché ci sono troppe possibilità. Puoi letteralmente dipingere qualsiasi cosa. Non ho posto limiti a te. E questo è il problema che ti ha paralizzato. Questo è noto come il paradosso della scelta.

Se invece dicessi di dipingermi un paesaggio che mi piacerà, questo eliminerebbe almeno la metà delle infinite possibilità. Anche se rimangono ancora infinite possibilità, qualsiasi pensiero di un ritratto può essere rapidamente respinto.

Se andassi ancora oltre e dicessi che adoro i paesaggi marini con onde che si infrangono sulla spiaggia durante un tramonto dorato, ci sarebbe ancora un numero infinito di possibili dipinti, ma quei limiti in realtà ti aiutano a pensare a cosa dipingere.

E prima che tu lo sappia, puoi iniziare a disegnare un paesaggio marino.

Quindi le limitazioni possono semplificare la creazione.

L'hardware è più facile del software

Con l'hardware, non vedrai mai un transistor o un condensatore condiviso da più componenti nel computer. I resistori nei circuiti della tastiera non sono accessibili, condivisi o interessati dalla scheda grafica.

La scheda grafica ha i suoi resistori che possiede esclusivamente. Gli ingegneri hardware non lo fanno perché vogliono vendere più resistori. Lo fanno perché non hanno scelta.

Le Leggi dell'Universo impongono che ciò non può essere fatto senza creare scompiglio. L'universo impone regole agli ingegneri hardware, vale a dire limitare ciò che è possibile.

Queste limitazioni rendono più semplice pensare e lavorare con l'hardware rispetto al software.

Nulla è impossibile nel software

Ora taglia il software dove quasi tutto è possibile. Non c'è nulla che limiti un ingegnere del software dalla condivisione di una variabile con ogni parte del programma. Questa è nota come variabile globale.

Nella programmazione del linguaggio assembly, puoi semplicemente saltare a qualsiasi punto del codice e iniziare a eseguirlo. Puoi farlo in qualsiasi momento. Puoi persino scrivere sui dati facendo sì che il programma esegua codice involontario. Questo è un metodo utilizzato dagli hacker che sfruttano le vulnerabilità di Buffer Overflow.

In genere, il sistema operativo limita ciò che il programma può fare a cose al di fuori del programma. Ma non ci sono limiti a ciò che il tuo programma può fare per il codice e i dati di cui è proprietario.

È la mancanza di limiti che rende così difficile scrivere e mantenere software.

Come limitare correttamente lo sviluppo del software

Sappiamo che abbiamo bisogno di limiti nello sviluppo del software e sappiamo, per esperienza, che le limitazioni in altri sforzi creativi possono esserci di beneficio.

Sappiamo anche che non possiamo fare affidamento sulla società per censurare casualmente il nostro codice o su alcune carenze meccaniche per limitare i nostri paradigmi. E non possiamo aspettarci che gli utenti specifichino i requisiti in misura tale da richiedere limiti adeguati nella progettazione.

Dobbiamo limitarci. Ma vogliamo assicurarci che queste limitazioni siano benefiche. Quindi quali limiti dovremmo scegliere e come dovremmo decidere?

Per rispondere a questa domanda, dobbiamo fare affidamento sulla nostra esperienza e anni di pratica per aiutarci a guidarci nel perseguimento dei limiti appropriati. Ma lo strumento più utile che abbiamo è i nostri fallimenti passati.

Il dolore delle nostre azioni passate, ad es. toccando la stufa, ci informa bene su quali limiti dovremmo metterci se vogliamo evitare un'agonia simile.

Lascia andare il mio popolo

All'inizio, le persone scrivevano programmi con il codice che saltava dappertutto. Questo si chiamava Spaghetti Code poiché seguire questo tipo di codice era come seguire un singolo filo in una ciotola di spaghetti.

L'industria si rese conto che questa pratica era controproducente e inizialmente vietò l'uso dell'istruzione GOTO dal codice che era stato scritto in lingue che lo consentivano.

Alla fine, i nuovi linguaggi di programmazione sono stati venduti nel merito di non supportare GOTO. Questi erano conosciuti come linguaggi di programmazione strutturale. E oggi, tutte le lingue di alto livello tradizionali sono prive di GOTO.

Quando ciò si è verificato, alcuni si sono lamentati del fatto che le nuove lingue erano troppo restrittive e che se avessero avuto solo GOTO, avrebbero potuto scrivere il codice più facilmente.

Ma le menti più progressiste hanno vinto e dobbiamo ringraziarle per l'estinzione di uno strumento così distruttivo.

Ciò che le menti progressive hanno capito è che il codice viene letto più di quanto sia scritto o modificato. Quindi potrebbe essere un po 'meno conveniente per un piccolo gruppo di foot-draggers, ma a lungo andare saremmo molto meglio con questa limitazione.

I computer possono ancora fare GOTO. In realtà, hanno bisogno di. È solo che, come industria, abbiamo deciso di impedire ai programmatori di utilizzarli direttamente. Tutti i linguaggi per computer vengono compilati in base al codice che utilizza GOTO. Ma i progettisti del linguaggio hanno creato costrutti che impiegavano ramificazioni più disciplinate, ad es. usando un'istruzione break per uscire da un ciclo for.

L'industria del software ha notevolmente beneficiato delle limitazioni dettate dai progettisti del linguaggio.

Porta le catene

Quindi quali sono i GOTO di oggi e cosa hanno in serbo per noi programmatori ignari i progettisti di lingue?

Per rispondere a questo, dovremmo guardare agli attuali problemi che incontriamo quotidianamente.

  1. Complessità
  2. riutilizzabilità
  3. Stato mutabile globale
  4. Digitazione dinamica
  5. analisi
  6. La fine della legge di Moore

Come possiamo limitare ciò che i programmatori sono autorizzati a fare per risolvere i problemi di cui sopra?

Complessità

La complessità cresce nel tempo. Ciò che inizia come un sistema semplice, nel tempo, si evolverà in un sistema complesso. Ciò che inizia come un sistema complesso, nel tempo, si evolverà in un casino.

Quindi, come possiamo limitare i programmatori per contribuire a ridurre la complessità?

Per uno, possiamo costringere i programmatori a scrivere codice completamente decomposto. Sebbene ciò sia difficile da fare se non addirittura impossibile, possiamo creare lingue che incoraggino e premiano questo comportamento.

Molti linguaggi di programmazione funzionale, specialmente quelli puri, fanno entrambe queste cose.

Scrivere una funzione che è un calcolo ti costringe a scrivere cose in un modo molto decomposto. Ti costringe anche a pensare attraverso il tuo modello mentale del problema.

Possiamo anche forzare limitazioni su ciò che i programmatori possono fare nelle loro funzioni, vale a dire rendere tutte le funzioni pure. Le funzioni pure sono quelle senza effetti collaterali, ovvero le funzioni non possono accedere a dati esterni a se stessi.

Le funzioni pure si occupano solo dei dati che sono passati a loro, quindi calcolano i loro risultati e li restituiscono. Ogni volta che chiamate una funzione pura con gli stessi ingressi, produrrà SEMPRE le stesse uscite.

Ciò rende il ragionamento sulle funzioni pure molto più semplice del non puro poiché tutto ciò che possono fare è completamente contenuto all'interno della funzione. Puoi anche testarli più facilmente poiché sono unità autonome. E se il calcolo è costoso, è possibile memorizzare nella cache i risultati. Se hai gli stessi ingressi, sai che le uscite sono sempre le stesse. Uno scenario perfetto per una cache.

Limitare i programmatori a sole funzioni pure limita notevolmente la complessità poiché le funzioni possono avere solo un effetto locale e aiutano gli sviluppatori a scomporre naturalmente le proprie soluzioni.

riutilizzabilità

L'industria è alle prese con questo problema quasi da quando esiste la programmazione. Prima avevamo le biblioteche e poi la programmazione strutturata e poi l'ereditarietà orientata agli oggetti.

Tutti questi approcci hanno un fascino e un successo limitati. Ma l'unico metodo che funziona sempre e viene utilizzato da quasi tutti i programmatori è Copia / Incolla aka copypasta.

Se stai copiando e incollando il codice, lo stai facendo in modo sbagliato.

Non possiamo limitare i programmatori da Copia / Incolla fintanto che stiamo ancora scrivendo programmi come testo, ma ciò che possiamo fare è dare loro qualcosa di meglio.

La programmazione funzionale ha caratteristiche standard che sono molto meglio di copypasta, vale a dire. Funzioni di ordine superiore, curricula e composizione.

Le funzioni di ordine superiore consentono ai programmatori di passare parametri che sono dati e funzioni. Nelle lingue che non lo supportano, l'unica soluzione è copiare e incollare la funzione e quindi modificare la logica. Con le funzioni di ordine superiore, la logica può essere passata come parametro sotto forma di funzione.

Il curriculum consente di applicare i parametri a una funzione, uno alla volta. Ciò consente ai programmatori di scrivere versioni generalizzate delle loro funzioni e quindi "inserire" alcuni parametri per creare versioni più specializzate.

Composizione consente ai programmatori di assemblare funzioni come Legos ™ che consente loro di riutilizzare funzionalità che loro o altri hanno incorporato in una pipeline in cui i dati passano da una funzione all'altra. Le pipe Unix ne sono una forma semplicistica.

Quindi, sebbene non possiamo eliminare copypasta, possiamo renderlo superfluo attraverso il supporto del linguaggio e attraverso revisioni del codice che non lo consentono nelle nostre basi di codice.

Stato mutabile globale

Questo è probabilmente il problema più grande nella programmazione che la maggior parte delle persone non capisce è un problema.

Ti sei mai chiesto perché la maggior parte delle soluzioni per programmare i glitch vengono risolte riavviando il computer o riavviando l'applicazione offensiva? Questo è a causa dello stato. Il programma ha corrotto il suo stato.

Da qualche parte nel programma, lo stato è stato modificato in modo non valido. Questi sono alcuni dei bug più difficili da correggere. Perché? Perché sono così difficili da riprodurre.

Se non riesci a riprodurlo in modo affidabile, non hai modo di sapere se lo hai effettivamente risolto. Puoi testare la tua correzione e non succede. Ma è perché l'hai riparato o perché non è ancora successo?

La corretta gestione dello stato è la cosa più importante che puoi fare nel tuo programma per garantire l'affidabilità.

La programmazione funzionale risolve questo problema ponendo limiti ai programmatori a livello linguistico. I programmatori non possono creare variabili mutabili.

All'inizio, potrebbe sembrare che siano andati troppo lontano e potresti affinare i forconi mentre leggi questo. Ma quando lavori effettivamente con tali sistemi, puoi vedere che lo stato può essere gestito mentre rende immutabili tutte le strutture di dati, cioè quando una variabile ha un valore che non può mai cambiare.

Ciò non significa che lo stato non possa cambiare. Significa solo che per farlo devi passare lo stato corrente in una funzione che produce un nuovo stato. Prima che i bit-twiddlers escano di nuovo dai forconi, puoi stare certo che esistono meccanismi per ottimizzare queste operazioni sotto le coperture tramite la condivisione strutturale.

Si noti che sotto le coperte è dove avvengono le mutazioni. Proprio come ai vecchi tempi in cui i GOTO venivano eliminati, il compilatore o il runtime eseguono ancora i GOTO. Non è disponibile solo per il programmatore.

E quando devono verificarsi effetti collaterali, i Linguaggi funzionali hanno dei modi per contenere parti potenzialmente pericolose del programma. In buone implementazioni, queste parti del codice sono chiaramente contrassegnate come pericolose e separate dal codice puro.

E quando il 98% del codice è privo di effetti collaterali, i bug nello stato corrotto possono essere solo nel 2%. Ciò offre al programmatore la possibilità di combattere per trovare questo tipo di bug poiché le parti pericolose vengono rimosse.

Quindi, limitando i programmatori alle sole (o per lo più) funzioni pure, creiamo programmi più sicuri e più affidabili.

Digitazione dinamica

C'è un'altra lunga e vecchia battaglia tra la tipizzazione statica e la digitazione dinamica. La tipizzazione statica è il punto in cui il tipo di variabile viene verificato al momento della compilazione. Dopo aver definito il tipo, il compilatore può aiutarti a assicurarti di utilizzarlo correttamente.

L'argomento contro la tipizzazione statica è che pone un onere inutile per il programmatore e che riempie il codice con informazioni di digitazione dettagliate. E queste informazioni di tipo sono sintatticamente rumorose perché sono in linea con le specifiche delle funzioni.

La tipizzazione dinamica è dove il tipo di variabile non è né specificato né verificato al momento della compilazione. In effetti, la maggior parte delle lingue che utilizzano la digitazione dinamica non sono lingue compilate.

L'argomento contro la tipizzazione dinamica è che mentre ripulisce drasticamente il codice, tutti gli abusi della variabile non verranno colti dal programmatore. Non verrà catturato fino a quando il programma non verrà eseguito. Ciò significa che, nonostante tutti i migliori sforzi, i bug di tipo entreranno in produzione.

Quindi qual è la migliore? Dal momento che stiamo prendendo in considerazione la possibilità di limitare i programmatori qui, potresti aspettarti un argomento per la scrittura statica nonostante il rovescio della medaglia. Bene, sì, ma non sarebbe bello se potessimo avere il meglio di entrambi i mondi?

A quanto pare, non tutti i sistemi di tipizzazione statica sono creati allo stesso modo. Molti linguaggi di programmazione funzionale supportano l'inferenza del tipo in cui il compilatore può dedurre i tipi di funzioni che stai scrivendo nel modo in cui le usi.

Ciò significa che possiamo avere la digitazione statica senza tutto il sovraccarico di specificare. Le migliori pratiche impongono che la digitazione sia specificata anziché inferita, ma in linguaggi come Haskell ed Elm, la sintassi per la digitazione è in realtà non invadente e abbastanza utile.

I linguaggi non funzionali, ovvero i linguaggi imperativi, che sono tipizzati staticamente tendono a gravare il programmatore con la specifica di tipi con scarso ritorno sull'investimento.

Al contrario, i sistemi di tipo di Haskell ed Elm in realtà aiutano meglio il codice del programmatore e li informano al momento della compilazione quando il programma non funzionerà correttamente.

Quindi, limitando i programmatori alla buona tipizzazione statica, il compilatore può effettivamente aiutare a rilevare bug, inferire tipi e aiutare nella codifica invece di gravare lo sviluppatore con informazioni dettagliate, intrusive e di tipo.

analisi

La scrittura del codice di prova è la rovina dell'esistenza del programmatore moderno. Molte volte gli sviluppatori trascorrono più tempo a scrivere il codice di test rispetto al codice originale che stanno testando.

Scrivere un codice di prova per funzioni che si interfacciano con database o server Web è difficile se non addirittura impossibile da automatizzare. Di solito, ci sono 2 opzioni.

  1. Non scrivere test
  2. Mock-up del database o del server

L'opzione n. 1 non è ovviamente un'ottima opzione, ma molte persone prendono questa strada poiché prendere in giro sistemi complessi può richiedere più tempo del tempo impiegato a scrivere il modulo che si sta provando a testare.

Ma se limitiamo il codice a funzioni pure, allora non possono interfacciarsi direttamente con il database perché ciò causerebbe effetti collaterali o mutazioni. Dobbiamo ancora accedere al database ma ora abbiamo il nostro pericoloso livello di codice come strato di interfaccia molto sottile che lascia pura la maggior parte del modulo.

Testare le funzioni pure è molto più semplice. Ma dobbiamo ancora scrivere il codice di prova, la rovina della nostra esistenza. O noi?

Si scopre che ci sono programmi per testare automaticamente i tuoi programmi funzionali. L'unica cosa che il programmatore deve fornire è quali proprietà devono rispettare le tue funzioni, ad es. qual è la funzione inversa. Il tester automatico Haskell si chiama QuickCheck.

Quindi, limitando la maggior parte delle funzioni ad essere pure, rendiamo i test molto più semplici e in alcuni casi puramente banali.

La fine della legge di Moore

La legge di Moore non è in realtà una legge, ma più un'osservazione che la potenza di calcolo raddoppierà ogni 2 anni.

Questo è vero da oltre 50 anni. Ma purtroppo, abbiamo raggiunto limiti nell'attuale tecnologia. E potrebbero essere necessari decenni per sviluppare una tecnologia non basata sul silicio con cui costruire computer.

Fino ad allora, il modo migliore per raddoppiare la velocità del computer è raddoppiare i core, ovvero il numero di motori di elaborazione nella CPU. Il problema non è che i produttori di hardware non possono darci più core. Il problema è batterie e software.

Raddoppiare la potenza di elaborazione significa raddoppiare la potenza consumata dalla CPU. Questo scaricherà le batterie ancora più velocemente di quanto facciamo oggi. La tecnologia della batteria è molto indietro rispetto all'appetito insaziabile degli utenti.

Quindi, prima di aggiungere altri core per scaricare le batterie, forse dovremmo ottimizzare l'uso dei core che già abbiamo. È qui che entra in gioco il software. Attualmente, le lingue imperative rendono molto difficile eseguire i programmi in parallelo.

Per farlo oggi, l'onere è a carico dello sviluppatore. Il programma deve essere suddiviso e tagliato in parti parallele. Questo non è un compito semplice. E in effetti, con linguaggi come JavaScript, i programmatori non possono controllarlo poiché il loro codice non può essere eseguito in parallelo, ovvero è Single Threaded.

Ma con Pure Functions, non importa quale ordine vengano eseguiti. Tutto ciò che conta è che gli input siano disponibili. Ciò significa che il compilatore o il sistema di runtime può determinare quali funzioni eseguire e quando.

Limitando le funzioni alle funzioni pure, libera il programmatore dal doversi preoccupare del parallelismo.

I programmi funzionali dovrebbero essere in grado di sfruttare meglio le macchine multi-core senza ulteriore complessità per lo sviluppatore.

Fare di più con meno

Come abbiamo visto, imporre limiti, se fatti correttamente, può migliorare drasticamente la nostra arte, design e vita.

Gli ingegneri hardware hanno notevolmente beneficiato dei limiti naturali dei loro strumenti, rendendo più semplice il loro lavoro e consentendo loro di compiere grandi progressi negli ultimi decenni.

Non è il momento che noi, ingegneri del software, imponiamo dei limiti a noi stessi in modo che anche noi possiamo fare di più con meno.

Se ti è piaciuto, fai clic su in basso in modo che altre persone lo vedano qui su Medium.

Se vuoi unirti a una comunità di sviluppatori web che si apprendono e si aiutano a vicenda nello sviluppo di app Web utilizzando la Programmazione funzionale in Elm, controlla il mio gruppo Facebook, Scopri la programmazione Elm https://www.facebook.com/groups/learnelm/

Il mio Twitter: @cscalfani