Perché il software non è astratto su una scala più grande?

6

Considera il seguente esempio:

L'utente vuole un programma per calcolare alcuni numeri di Fibonacci. Sembra abbastanza facile. pseudocodice:

stdout.write("How many fibonacci numbers do you want to calculate? ")
int count = int(stdin.readline())
while count>0:
    stdout.writeline(calculate_next_fibonacci_number())
    count--

Anche un programma molto semplice come questo è già imperfetto:

  • Il programma scrive su stdout. Questo non è in realtà ciò che il programmatore intendeva, vero? L'intenzione è di mostrare un numero di numeri all'utente, non per scrivere del testo su stdout - non c'è alcuna garanzia che l'utente vedrà mai il testo scritto su stdout.
  • Allo stesso modo, l'input dell'utente viene letto da stdin, che è un'interfaccia di testo (o file, se preferisci), quando in realtà il programma richiede un numero, non un testo. Questo sembra fondamentalmente sbagliato.

Pensiamo a cosa stiamo cercando di fare in modo più astratto. Vogliamo:

  • Calcola un mucchio di numeri. Quanti dipendono dall'utente.
  • mostra i risultati all'utente.

Perché, quindi, non scriveremo codice esattamente così? La maggior parte dei linguaggi di programmazione fornisce questa cosa chiamata "funzione", che accetta parametri e li usa per fare qualcosa. Non suona esattamente come quello che stiamo cercando di fare?

void display_fibonacci_numbers(
        int number "how many fibonacci numbers to calculate"):

        Sequence<int> numbers
        while number>0:
            numbers.append(calculate_next_fibonacci_number())
            number--

        notify(numbers)

display_fibonacci_numbers()

Questo codice è, ovviamente, incompleto - la funzione notify non è implementata da nessuna parte e l'utente ha bisogno di un modo per inserire un numero. Immagino il sistema operativo dell'utente o il desktop manager o qualsiasi altra cosa che si prenda cura di questo: potrebbe visualizzare un terminale, potrebbe generare una GUI, potrebbe dire all'utente di scrivere il numero in aria con il suo naso; in entrambi i casi ( dovrebbe not) non mi riguarda, il programmatore.

Credo che il software dovrebbe essere più astratto.

Alcuni altri esempi.

  • comunicazione tra processi. Come si fa? Prese? Segnali? DBus? File? L'obiettivo non è usare prese, segnali o dbus, è comunicare con un altro processo. Perché non qualcosa come Process.from_name("Music player").play_random_song() ?
  • Download di file. %codice%? Perché non local_file.write(remote_file.read()) , che potrebbe, a seconda della configurazione del sistema, avviare subito il download, ma con una priorità bassa in modo da non rallentare altri download o aggiungerlo alla coda di download per scaricarlo successivamente o altro?
  • Percorsi di file. Perché mai i percorsi dei file sono ancora stringhe (nella maggior parte delle lingue, almeno)? Perché dobbiamo considerare se il separatore del percorso è download(url) o / , se è \ , . o altro? Perché non esiste una classe .. che si occupa di questo?

Andando oltre, perché non applicare il concetto di digitazione anatra ai moduli? Ad esempio, in Python, l'HTML viene spesso analizzato usando il modulo FilePath :

from bs4 import BeautifulSoup

page= BeautifulSoup(urlopen('http://test.at'))

Anche in questo caso, l'obiettivo del programmatore non era quello di utilizzare BeautifulSoup, ma di analizzare HTML. Perché, quindi, direbbe esplicitamente al suo codice di utilizzare il modulo BeautifulSoup? Quello che vuole veramente è

import HTMLParser
page= HTMLParser.parse(urlopen('http://test.at'))

Duck-typing per i moduli: a chi importa quale modulo è finché fa ciò che voglio?

Perché la programmazione non è così? Sto trascurando qualcosa, qualcosa che rende tutto ciò impossibile (o poco pratico)?

    
posta Aran-Fey 09.12.2014 - 14:35
fonte

9 risposte

21

Why is programming not like this?

Perché lo pensi? In realtà, programmare è così. Certo, non puoi farlo in assembler. Quindi abbiamo inventato lingue che possono essere più simili ai pensieri umani.

I bravi programmatori programmano gli stessi esempi che hai dato. Ma non è così facile. Prima devi capire, qual è il requisito. Questo è già abbastanza difficile e la maggior parte delle persone non è in grado di elaborare correttamente ciò di cui hanno bisogno, quindi un buon progettista di software deve effettivamente capire cosa vogliono le persone, non quello che dicono.

Dopo averlo fatto devi vedere i concetti che stanno dietro le cose del mondo reale. Questo è piuttosto filosofico e richiede molta esperienza. E poi, dopo aver fatto questo, devi tradurre questi risultati in una lingua a tua scelta, aggirando le incapacità che ogni lingua ha e scegli il livello di astrazione corretto (non vuoi esternalizzare qualcosa giusto?).

Non tutte le persone sono brave in questi passaggi o anche consapevoli di loro. Anche nei piccoli esempi si può vedere che non è banale. Prendiamo la tua funzione display_fibonacci_numbers e vediamo cosa possiamo migliorare lì (anche se non è codereview.stackexchange qui).

void display_fibonacci_numbers(

La denominazione in realtà non è molto precisa perché sei molto specificato nel tipo di numeri che visualizzi correttamente? Questo dovrebbe essere descritto dal nome della funzione.

    int number "how many fibonacci numbers to calculate"):

Perché è un int ? Questo è un modo per essere concreto e persino sbagliato, perché le inte possono ovviamente avere valori negativi, giusto? Quindi quello che potresti davvero volere è un tipo che può essere usato per calcolare i numeri di Fibonacci. Forse un natural number ?

    Sequence<int> numbers
    while number>0:
        numbers.append(calculate_next_fibonacci_number())
        number--

Questo descrive veramente cosa intendi fare? Nel linguaggio naturale (che è spesso una buona approssimazione) diremmo "creare un elenco del numero specificato di numeri di Fibonacci". Non riesco a leggere nulla sul decremento di un numero qui, non posso leggere nulla su un valore 0 statico qui. Nulla di più su una funzione "calculate_ next _fibonacci_number ()".

Pertanto, in Scala vorrei ad es. scrivi il codice come List.fill(number)(randomFibonacci())

Non legge esattamente come nel linguaggio naturale, ma contiene almeno le stesse informazioni.

Volevo darti questi esempi per dimostrarti che è difficile scrivere software in questo modo. Devi avere esperienze, spendere una buona quantità di tempo per pensare alle cose, essere in grado di trovare i concetti e le astrazioni corretti e POI avere un linguaggio che non ti impedisca di esprimerli in modo conciso. Questo è, perché non molte persone programmano così. E ancora peggio: gli esseri umani si abituano alle cose. Quindi, se si programma sempre in una lingua che lo ostacola nel fare l'ultimo passo, forse continuerà a programmare in una nuova lingua come nella vecchia.

    
risposta data 09.12.2014 - 15:01
fonte
14

Why, then, don't we write code exactly like that?

Lo facciamo. Il tuo esempio di Fibonacci è una chiara violazione del principio di responsabilità singola. Un metodo dovrebbe essere responsabile di essere un generatore che fornisce la sequenza di Fibonacci. Altre parti sono quindi responsabili di prendere N valori da esso, o saltare N valori, o ottenere input, ecc.

I would imagine the user's operating system or desktop manager or whatever to take care of that - it could display a terminal, it could generate a GUI, it could tell the user do write the number into the air with his nose; either way it does not (should not) concern me, the programmer.

A volte non riguarda il programmatore. In questi casi, si utilizza un'astrazione dell'interfaccia utente che esegue la colla per trasformare "ottenere un numero" e "stampare questi numeri" in implementazioni specifiche della piattaforma. Questi esistono già e sono comunemente usati.

Ma ti manca il punto.

La stragrande maggioranza del lavoro di un programmatore non è la realizzazione di questi interi programmi, ma l'incollaggio di pezzi disparati di codice esistente insieme. C'è già un codice per creare la sequenza di Fibonacci. C'è già il codice per prendere il primo N di una sequenza. C'è già il codice per stampare i numeri su questa piattaforma.

L'unica ragione per cui sei lì è perché qualcuno vuole tutti i quei bit di codice insieme. Perché non era il primo N invece? Le specifiche su come ottenere input e output di stampa sono requisiti essenziali quanto la sequenza da utilizzare.

Why not something like Process.from_name("Music player").play_random_song()

AppleScript fa già cose del genere. WCF in molti modi già astrae i meccanismi della comunicazione dalla semantica della comunicazione.

Why not download(url)

C # (e altri) hanno già delle librerie per farlo. Certo, hanno già una semantica ben definita, perché la configurazione è vile. È un codice senza alcuna struttura / processo per rendere il codice di qualità. Peggio ancora, rimuove qualsiasi qualità (e spesso le prestazioni) dal tuo codice reale.

Why is there no FilePath class that takes care of this?

C # (e altri) hanno già delle librerie per fare questo.

Going a step further, why not apply the duck-typing concept to modules?

Alcuni (più esoterici) linguaggi. I moduli sono trattati come oggetti di grandi dimensioni, che possono avere le loro interfacce, essere istanziati, parametrizzati, ecc.

Il problema è che la maggior parte di "come funziona" è definita dall'interfaccia del modulo. Un'implementazione avrà flussi di file con chiusura automatica e un'altra utilizzerà Open e Close . Queste sono interfacce strettamente diverse.

Why is programming not like this?

Ci sono un carico di ragioni.

Probabilmente il più avvincente è questo tipo di scenario . Indipendentemente dal fatto che il tuo sistema operativo controlli il modo in cui l'input / output rende i programmatori un codice migliore più veloce, alcuni uomini d'affari vorranno che tu faccia qualcosa di diverso per "differenziare".

    
risposta data 10.12.2014 - 16:06
fonte
12

Perché i programmi non sono più astratti?

In parole semplici: perché quando ci si abbassa, le astrazioni non fanno nulla. Le implementazioni fanno. E più il tuo programma è astratto, più è divorziato da ciò che sta realmente accadendo, più è difficile capire che cosa stia facendo il codice.

All'inizio, per uno sviluppatore ingenuo, sembra certamente concentrarsi sull'obiettivo di alto livello di ciò che il codice dovrebbe fare e "estrapolare i dettagli di implementazione" rende il codice più facile da capire, ma io rispetto rispettosamente che chiunque creda che questo abbia poca esperienza nel debugging. Quando qualcosa va storto nel tuo codice, specialmente se è già stato rilasciato un codice e hai clienti che sono scontenti e vogliono una correzione ora , più velocemente riuscirai a trovare il problema è, meglio è. E dover scavare strati su strati su strati di astrazioni rende questo più difficile, non più facile.

Poiché il moderno ciclo di sviluppo del software impiega molto più tempo nella manutenzione rispetto allo sviluppo originale, tutto ciò che favorisce uno sviluppo originale rapido e un'elevata astrazione a spese di manutenzione, leggibilità e debuggabilità dovrebbe essere considerato un meta-esempio di ottimizzazione prematura. / p>

Non fraintendermi. Non sono contrario all'astrazione dove ha senso. Ma troppo spesso si vedono persone che aggiungono sempre più astrazioni solo per l'astrazione, e trasforma il codice in un caos di complessità piuttosto che ridurre la complessità. Ricorda il consiglio notoriamente attribuito a Einstein: Tutto dovrebbe essere il più semplice possibile, ma non più semplice.

    
risposta data 09.12.2014 - 14:57
fonte
7

La cosa più importante che ti manca è che l'astrazione ha costi e benefici.

"All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections." -David Wheeler

A volte il costo è molto piccolo, come nel tuo esempio di Fibonacci, quindi anche se non ci guadagna nulla, non importa molto, perché non ci è costato molto neanche. Avvolgere alcune righe di codice in una funzione non è un'operazione che richiede molto tempo o sforzi. A volte il costo è elevato e quindi dobbiamo essere sicuri che ne ricaveremo qualcosa di significativo.

Se dovessimo decidere di creare un'interfaccia di analisi HTML astratta, dedicare del tempo a progettarlo e quindi implementare un'istanza dell'interfaccia che utilizzava BeautifulSoup, abbiamo sprecato il nostro tempo? Probabilmente, se usassimo la nostra fantastica interfaccia solo per chiamare BeautifulSoup. (Nota che la sostituzione proposta per BeautifulSoup nella tua domanda essenzialmente non ha fatto nulla per te, tranne cambiare il nome da BeautifulSoup a HTMLParser.)

Avvolgere qualcosa che non ha bisogno di essere avvolto in primo luogo è una perdita di tempo.

D'altra parte, se stiamo per scrivere un programma che usa BeautifulSoup per la maggior parte delle analisi, ma a volte ha davvero bisogno delle funzionalità di analisi di UglyOatmeal o SloppySandwich, un'interfaccia che li avvolge potrebbe farci risparmiare un sacco di tempo e fatica .

Sembra che pensi che i dettagli non dovrebbero riguardarti come programmatore. Questo è solo a volte vero; alcuni dettagli sono già gestiti dall'hardware, dal sistema operativo, dal linguaggio di programmazione e dalle librerie. Altri dettagli sono fondamentalmente in modi che li rendono impossibili da gestire automaticamente - nel tuo esempio di fibonacci, è importante il comportamento del programma se il tipo di numero utilizzato è un tipo int o un tipo che può gestire numeri più grandi. A volte i dettagli della rappresentazione di qualcosa fanno una grande differenza per le prestazioni.

È bello quando le cose vengono gestite automaticamente per noi, ma di solito c'è un costo di un tipo o dell'altro. Spesso il costo è rappresentato dalle prestazioni, e spesso è la mancanza di controllo su qualcosa.

Anche con bellissimi linguaggi e librerie fantastiche, non tutti i dettagli possono essere gestiti automaticamente, e il software fondamentalmente tratta di gestire un enorme numero di dettagli, quindi non dovremmo essere troppo sorpresi o infastiditi per scoprire che alcuni di loro non potrebbero essere gestito per noi.

Non è che abbiamo bisogno di più astrazione, abbiamo bisogno delle giuste astrazioni nei posti giusti. L'astrazione è uno dei nostri strumenti più potenti nella programmazione, ma ci sono modi per usarlo in modo errato o non realisticamente aspettarsi che faccia magia.

    
risposta data 10.12.2014 - 23:58
fonte
6

Hai scoperto il buon factoring. Congratulazioni.

Perché più persone non programmano in questo modo? Perché è difficile vedere il vantaggio di fare le cose in un modo che sembra più sforzo e pagherà solo più tardi, quando i requisiti cambiano. Le persone sono miopi; anche quelli che possono pensare in modo astratto abbastanza da diventare programmatori sono miopi. Alcuni di loro sono un po 'meno miopi, e tendono a essere quelli che inventano il raro trucco o modello che alla fine riesce a stabilirsi e aumentare un po' la qualità complessiva del software.

Poi qualcuno inventa un nuovo canale di distribuzione brillante con uno strano linguaggio di programmazione kludgy ad hoc per andare con esso, e tutti i progressi vanno subito fuori dalla finestra. Non sentirti male, è solo il modo in cui il mondo è:)

    
risposta data 09.12.2014 - 14:39
fonte
3

La legge di rendimenti decrescenti si applica all'astrazione proprio come qualsiasi altra cosa. È meglio configurare un dispositivo usando cavi di ponticelli o interruttori DIP? Probabilmente gli interruttori DIP, che sono un'astrazione oltre il collegamento dei cavi dei ponticelli. È meglio programmare in linguaggio macchina o linguaggio assembly? La risposta è quasi certamente linguaggio di assemblaggio. È difficile per me pensare a uno scenario in cui non vorresti almeno utilizzare un assemblatore per generare il linguaggio della tua macchina.

Tuttavia, siamo ben al di là delle astrazioni miti che ho appena suggerito, e direi che molti, forse la maggior parte, i programmatori stanno lavorando a un livello di astrazione oltre il punto di rendimenti decrescenti già .

Un esempio: lavoro con diversi sistemi PAAS che tentano di rendere il compito difficile di integrazione con altri sistemi più astratto e più facile. Mi trovo continuamente a chiedermi cosa sta realmente accadendo sotto la facciata dall'aspetto pulito presentato da tali sistemi:

  • Che cosa significa la parola "valore vuoto" in quell'etichetta? Significa che la riga sorgente è presente ma il valore della colonna è nullo? Significa che la riga sorgente è assente? Significa entrambi? Questo particolare strumento PAAS ha persino un concetto di vero / falso / nullo? In tal caso, quali campi sono annullabili (ed è questa impostazione sepolta in profondità in una finestra di dialogo da qualche parte, nel tentativo di rendere l'esperienza utente più astratta)?

  • Quando seleziono l'opzione "aggiungi record al database di destinazione", anche questo aggiornamento verrà registrato? Non vedo un'opzione di "aggiornamento" ... quindi "aggiungi record" deve fare anche gli aggiornamenti ... giusto? (Questo può sembrare un esempio ridicolo, ma è vero. E sì, sono finito su Google cercando di determinare se "add" significasse davvero "aggiungi / aggiorna" per una particolare impostazione in un particolare strumento PAAS - è questo la tua idea di una buona esperienza di sviluppo?)

È difficile rispondere a queste domande, dal momento che tutte le cose del programmatore sono state nascoste in modo studioso, bloccate in implementazioni black box, ecc., nel nome della presentazione di un'interfaccia utente astratta. Finisco per avere difficoltà anche a formulare le domande necessarie (se indirizzate a Google o al supporto tecnico). Mi sto chiedendo cose come il nullability e le operazioni "upsert", ma i miei interlocutori stanno attivamente evitando l'uso di termini tecnici di basso livello. Ma a questi tipi di domande bisogno di rispondere per fare integrazione ...

E il mio ultimo punto suggerisce una possibilità che, sviluppando un linguaggio di calcolo condiviso, potremmo essere in grado di lavorare a un livello più astratto. Se i non programmatori (inclusi i progettisti di GUI) usassero termini standard come nullable, "upsert", chiave esterna, ecc., Allora forse un sacco di cose attualmente fatte usando il codice potrebbe essere fatto graficamente, o in un modo altrimenti più astratto. Ma quando la gente dice "aggiungi" invece di "upsert", perché "upsert" è un linguaggio programmatore spaventoso, beh, devo ricorrere alle mie vecchie competenze e strumenti di basso livello per portare a termine il lavoro.

    
risposta data 10.12.2014 - 15:57
fonte
3

Quello che stai chiedendo è "perché non possiamo usare Z o UML per prototipare e progettare un sistema completamente? "

E la risposta a che è, "Perché, mentre potresti costruire qualcosa che costruisca un sistema basato su una specifica UML o Z, verificando spec diventa altrettanto difficile (se non di più) dell'implementazione della specifica. "

    
risposta data 16.12.2014 - 20:42
fonte
1

Puoi acquistare molte parti prefabbricate nel negozio, ma ci sono ancora macchinisti artigiani che sono necessari per fabbricare parti che non esistono. Le astrazioni ci sono, ma non sempre per ogni ambiente. A un certo punto, qualcuno avrà bisogno di capovolgere un po 'in modo che nessuna astrazione sotto forma di una funzione sarà disponibile.

Utilizzerò la stringa del percorso del file come esempio:

File paths. Why on earth are file paths still strings (in most languages, at least)? Why do we have to deal with whether the path separator is / or \, whether it's ., .., or whatever? Why is there no FilePath class that takes care of this?

A volte queste astrazioni esistono, ma non nel linguaggio di programmazione stesso. Microsoft Windows, .NET Framework e la famiglia di linguaggi di programmazione che utilizzano .NET ed eseguono su Windows hanno questa capacità. link

Non fa realmente parte dei linguaggi di programmazione (VB.NET, C #, F #, ecc.), ma un framework. Vedo questo framework .NET come un'astrazione di tutti gli impianti idraulici di Windows che sono necessari e non fanno parte della logica o degli algoritmi che dobbiamo creare nel nostro codice. Il programmatore può scegliere di utilizzare questo oggetto dal framework .NET o crearne uno proprio. La tua discussione sembra che creare la tua sia una cattiva idea perché c'è qualcosa di meglio che è più disponibile, quindi perché dare allo sviluppatore la scelta? Non sono sicuro di come prendi quella scelta. Impedire a una stringa di posizione del file di poter interagire con il file system in qualsiasi forma o forma? È possibile programmare solo per Windows se si utilizza la struttura e la famiglia di lingue appropriate? Come si ottiene l'URL dal web? Deve presentarsi sotto forma di un oggetto IO .NET?

    
risposta data 10.12.2014 - 15:41
fonte
1

Molti dei tuoi esempi sembrano essere problemi nella ricerca di una soluzione. In uno scenario del mondo reale, la funzione del numero di Fibonacci sarebbe in realtà una funzione che prende 1 o 2 (per sottoinsiemi arbitrari della sequenza di Fibonacci) parametri int e restituisce un elenco di numeri interi, e il processo di chiamata gestirà l'input e la visualizzazione L'output. Se hai il codice che hai fornito nel mondo reale, è perché lo sviluppatore non ha mai inteso che fosse qualcosa di diverso da qualcosa (s) che ha eseguito dalla riga di comando.

Per molti dei tuoi altri esempi, la risposta è spesso semplicemente "perché alla fine avrai bisogno di istanziare qualcosa e fare le cose". Ad un certo punto, per eseguire il codice, qualcosa deve essere un'implementazione effettiva. In questo caso, è una libreria di file system (oltre alla libreria C # Telastyn elencata, c'è os modulo in Python e le librerie relative ai file Java (in java.io e java.nio ). L'aggiunta di un altro livello di astrazione non renderà nulla negli esempi che hai elencato meglio .

C'è un atto di bilanciamento che entra nel processo decisionale sull'opportunità di utilizzare qualche tipo di astrazione - Quanto più complicato è il mantenimento del codice? (come discusso da Mason Wheeler ) Quanto è più semplice scrivere il codice? I benefici che mi aspetto di ottenere da questo giustificano davvero il tempo extra necessario per metterlo a posto? E forse la domanda più importante - Davvero mi aspetto che ci sia più di 1 implementazione qui?

Ti accorgi che arrivi ad un punto, spesso molto prima di quanto sembri pensare, dove aggiungere più astrazione non vale la pena.

    
risposta data 27.12.2014 - 00:34
fonte

Leggi altre domande sui tag