Discussioni di semplicità

8

Recentemente nella mia azienda abbiamo avuto un po 'di dibattito sull'astrazione contro la semplicità. Una scuola di pensiero che definirei "ASCIUTTA e l'astrazione non possono fare danni" e porta a un codice come questo:

def make_foo_binary(binaryName, objFiles, fooLibsToLinkAgainst)
    make_exe_task(binaryName, objFiles.ext('.o'), fooLibsToLinkAgainst)
end

e questo:

class String
    def escape_space
        return self.gsub(' ', '\ ')
    end
end

Il mio punto di vista è che la creazione di un'astrazione come questa, che è usata solo in un posto, rende il codice meno leggibile, dal momento che stai sostituendo una chiamata di funzione con cui il lettore ha familiarità con (gsub) con un altro che loro " Non ho mai visto prima (escape_space), che dovranno leggere se vogliono capire come funziona effettivamente il codice. La sostituzione è essenzialmente descritta in inglese ("escape space") e l'inglese è notoriamente vago. Ad esempio, senza guardare la definizione, non sai se sfugge a tutti gli spazi bianchi, o solo al carattere dello spazio.

C'è molto scritto che canta le lodi di ASCIUTTO e astrazione. Qualcuno è a conoscenza di fonti che descrivono i limiti dell'astrazione? Che cantare le lodi e discutere la pragmatica di mantenere il codice semplice?

Modifica: posso trovare testi che incoraggiano la semplicità nella vita o nella scrittura (in inglese), ad es. "Semplifica, Semplifica!" Di Thoreau o Strunk and White's "La scrittura vigorosa è concisa". Dov'è l'equivalente per la programmazione?

    
posta Martin C. Martin 09.08.2012 - 15:18
fonte

5 risposte

9

Certo: Joel Spolsky (potresti aver sentito parlare di lui) dice :

All non-trivial abstractions, to some degree, are leaky.

Abstractions fail. Sometimes a little, sometimes a lot. There's leakage. Things go wrong. It happens all over the place when you have abstractions.

Inoltre, vedi KISS e YAGNI di cui parla Jeff Atwood :

As developers, I think we also tend to be far too optimistic in assessing the generality of our own solutions, and thus we end up building elaborate [solutions] around things that may not justify that level of complexity. To combat this urge, I suggest following the YAGNI (You Aren't Gonna Need It) doctrine. Build what you need as you need it, aggressively refactoring as you go along; don't spend a lot of time planning for grandiose, unknown future scenarios. Good software can evolve into what it will ultimately become.

(vedi il link per la citazione esatta)

La mia interpretazione di queste citazioni:

  1. È davvero difficile creare buone astrazioni: è molto più facile creare quelle schifose.

  2. Se aggiungi un'astrazione per la quale non ce n'è bisogno o che è sbagliata, ora disponi di codice aggiuntivo che deve essere testato, sottoposto a debug e mantenuto. E se devi tornare indietro e refactoring, ora hai più peso morto che ti trascina giù.

risposta data 09.08.2012 - 15:45
fonte
7

Non ho familiarità con la lingua o il runtime dei tuoi esempi, ma gsub non significa niente per me. Tuttavia, escape_space è molto più significativo. Sono completamente in disaccordo con la tua tesi secondo cui le chiamate a funzioni familiari non dovrebbero essere sostituite da chiamate a funzioni che non hanno mai visto prima . Se dovessi portare questo argomento agli estremi, allora si dovrebbe mai aggiungere una nuova funzione a un programma: astrae sempre le chiamate di funzioni familiari e le sostituisce sempre con questa nuova chiamata sconosciuta definita dall'utente.

Uno sviluppatore dovrebbe mai e poi mai leggere il codice per capire come funziona. Prima di tutto, lui o lei non dovrebbe preoccuparsi di come funziona, ma dovrebbe essere in grado di capire come usarlo e quali effetti ha. Se questo non è il caso, allora c'è un problema con il nome e la firma della funzione, o la sua documentazione.

Per me, l'obiettivo dell'astrazione è rimuovere i dettagli interni e fornire un'interfaccia più pulita per alcune funzionalità. In futuro, il funzionamento interno della funzione escape_space potrebbe essere modificato o sovrascritto. Non mi interessa che qualche funzione gsub venga chiamata con due argomenti. Mi interessa che i miei spazi siano sfuggiti. Ciò rende l'astrazione utile.

Nella mia lingua preferita, C #, I semper aggiungo documentazione a tutte le mie funzioni (anche private) che descrivono cose come la funzione, l'uso, le unità usate, le eccezioni generate , contratto di input e tipi. Quindi, con IntelliSense di Visual Studio ogni sviluppatore può vedere più chiaramente ciò che la funzione fa senza leggere il codice . Senza uno strumento come IntelliSense, gli sviluppatori dovranno essere più specifici nei loro nomi di funzioni o mantenere la documentazione aggiuntiva da qualche parte facilmente accessibile.

Non credo che le astrazioni debbano mai essere limitate, e non so di una tale fonte.

    
risposta data 09.08.2012 - 15:44
fonte
5

Quando parlo di astrazioni, penso che sia importante definire due termini: complessità intrinseca e incidentale. La complessità intrinseca è la complessità del problema che l'astrazione sta risolvendo, la complessità accessoria è la complessità che l'astrazione si nasconde.

Le buone astrazioni sono quelle che nascondono molte complessità accessorie e cercano di risolvere i giusti problemi, diminuendo la loro complessità intrinseca. Penso che ciò che ti viene frustrato qui sono astrazioni troppo superficiali; non nascondono molta complessità accessoria ed è difficile (se non di più) capire le astrazioni così come comprendere le loro implementazioni.

    
risposta data 09.08.2012 - 15:51
fonte
2

La mia risposta sta andando più lontano dall'esperienza personale, ma una ricerca rapida ha trovato questo blog , che assomiglia a dettagli molto dettagliati su quando l'astrazione si spinge troppo lontano (non solo la voce collegata, ma ce ne sono diversi correlati).

Fondamentalmente, l'astrazione può essere una buona cosa. Tuttavia, come qualsiasi altra cosa, ci si può prendere la mano. I tuoi esempi sono buoni in cui l'astrazione è andata troppo oltre. Stai creando "pass-through" che fondamentalmente non fanno altro che rinominare la funzione in questione.

Ora, nel caso di funzioni personalizzate, questo potrebbe essere necessario, se stai cercando di deprecare un vecchio schema di denominazione a favore di uno nuovo (come cambiare escape_space in escapeSpace o escWhitespace, o qualsiasi altra cosa). Per le funzioni di libreria, però? Eccessivo. Per non parlare di controproducente. Cosa hai guadagnato dal fare quell'astrazione? Se è tutto ciò che sta facendo, tutto ciò che hai ottenuto è il mal di testa di dover ricordare quella funzione e passare quella conoscenza a qualcun altro (mentre, con le funzioni di libreria, gli sviluppatori ne sono già a conoscenza o dovrebbero essere in grado di digitare ruby gsub su Google e scopri cosa significa).

Raccomando di essere più intelligente nel determinare quando estrarre qualcosa. Per esempio, cose che sono più di 2-3 linee e usate almeno due volte (anche se ci sono lievi differenze, molte volte, quelle differenze possono essere parametrizzate), sono generalmente buoni candidati all'astrazione. A volte, considererò grandi blocchi di codice che fanno una cosa particolare all'interno di una funzione più ampia, per motivi di leggibilità e principio del "lavoro unico", e li astraggono in funzioni protette all'interno della stessa classe (spesso nell'immediato vicinanza della funzione che lo utilizza).

Inoltre, non dimenticare YAGNI (non ne avrai bisogno) e l'ottimizzazione prematura. Questo attualmente viene usato più di una volta? No? Quindi non preoccuparti di estrapolarlo in questo momento (a meno che ciò non crei un notevole guadagno in termini di leggibilità). "Ma potremmo averne bisogno in seguito!" Quindi estrai quando ne hai effettivamente bisogno da qualche altra parte. Fino ad allora, finisci con i puntatori ai puntatori ai puntatori senza alcun beneficio reale.

Dall'altro lato di questa moneta, quando estrai qualcosa, fallo non appena il bisogno diventa evidente. È più facile spostare e modificare due o tre blocchi di codice di 15. In linea generale, non appena mi ritrovo a copiare blocchi di codice da qualche altra parte o a scrivere qualcosa che mi sembra molto familiare, comincio a pensare a come estrapolarlo.

    
risposta data 09.08.2012 - 15:50
fonte
2

Per prima cosa, dai un'occhiata a questo esempio di Robert C. Martin:

link

La ragione di questo tipo di refactoring è di conseguenza la costruzione di astrazioni per creare funzioni molto piccole, in modo che ogni funzione faccia solo una cosa . Ciò porta a funzioni come quelle che ci hai mostrato sopra. Credo che sia una buona cosa: ti aiuta a creare codice in cui ogni funzione chiamata all'interno di un'altra funzione è sullo stesso livello di astrazione.

Se ritieni che l'utilizzo di nomi come make_foo_binary o escape_space renda il codice meno leggibile, allora dovresti provare a scegliere nomi migliori (ad esempio escape_space_chars_with_backslash ) invece di abbandonare l'astrazione.

    
risposta data 09.08.2012 - 16:09
fonte

Leggi altre domande sui tag