Abstraction: The War tra risolvere il problema e una soluzione generale [chiuso]


33

Come programmatore, mi trovo nel dilemma in cui voglio rendere il mio programma il più astratto e il più generale possibile.

Farlo di solito mi consentirebbe di riutilizzare il mio codice e di avere una soluzione più generale per un problema che potrebbe (o non potrebbe) ripresentarsi.

Quindi questa voce nella mia testa dice: risolvi il problema manichino è così facile! Perché passare più tempo di quanto devi?

Tutti abbiamo effettivamente affrontato questa domanda in cui l'astrazione è sulla tua spalla destra e Solve-it-stupid siede sulla sinistra.

Quale ascoltare e con quale frequenza? Qual è la tua strategia per questo? Dovresti astrarre tutto?


1
"Come astratto e generale" è generalmente noto come arroganza del programmatore :) Tuttavia, a causa della legge di Murphy, l'unico grado di adattamento di cui avrai bisogno quando lavori sul prossimo compito sarà quello che non hai fornito.
Matthieu M.

1
Una delle migliori domande di sempre!
esplorazione

1
Hai sempre indovinato, quindi vedi semplice. Quando hai ampliato i casi semplici "abbastanza volte" inizi a vedere uno schema. Fino ad allora, non farlo.

Risposte:


27

Quale ascoltare e con quale frequenza?

Mai astratto fino a quando non è necessario.

In Java, ad esempio, è necessario utilizzare le interfacce. Sono un'astrazione.

In Python non hai interfacce, hai Duck Typing e non hai bisogno di altissimi livelli di astrazione. Quindi non lo fai.

Qual è la tua strategia per questo?

Non astrarre fino a quando non lo hai scritto tre volte.

Una volta è - beh - una volta. Basta risolverlo e andare avanti.

Il doppio indica che potrebbe esserci uno schema qui. O potrebbe non esserlo. Può essere solo una coincidenza.

Tre volte è l'inizio di un modello. Ora trascende la coincidenza. Ora puoi astrarre con successo.

Dovresti astrarre tutto?

No.

In effetti, non dovresti mai astrarre nulla fino a quando non avrai la prova assoluta che stai facendo il giusto tipo di astrazione. Senza la "Regola delle tre ripetizioni", scriverai cose inutilmente astratte in un modo inutile.

Farlo di solito mi consentirebbe di riutilizzare il mio codice

Questo è un presupposto che è spesso falso. L'astrazione potrebbe non aiutare affatto. Può essere fatto male. Pertanto, non farlo fino a quando non è necessario.


1
"Mai astratto fino a quando non è necessario." - Immagino che eviti l'uso di classi, metodi, cicli o istruzioni? Queste cose sono tutte astrazioni. Se ci pensate, il codice scritto in linguaggi di alto livello è molto astratto, che vi piaccia o no. La domanda non è se astrarre. È quali astrazioni usare.
Jason Baker,

9
@Jason Baker: "Presumo che tu eviti l'uso di classi, metodi, loop o istruzioni if". Non sembra essere questa la domanda. Perché l'assurda affermazione che tutta la programmazione è impossibile se evitiamo di sovra-astrarre i nostri progetti?
S.Lott

1
Mi viene in mente la vecchia battuta in cui un uomo chiede a una donna se dormirà con lui per un milione di dollari. Quando dice di sì, le chiede se dormirà con lui per cinque dollari. Quando dice "Per quale tipo di donna mi prendi?" la risposta è "Bene, abbiamo già stabilito che dormirai con qualcuno per fare sesso. Ora stiamo solo negoziando sul prezzo." Il mio punto è che non si tratta mai di una decisione di astrarre o meno. Si tratta di quanto astrarre e quali astrazioni scegliere. Non puoi scegliere di resistere all'astrazione se non stai scrivendo puro assemblaggio.
Jason Baker

9
@Jason Baker: "Non puoi scegliere di resistere all'astrazione se non stai scrivendo puro assemblaggio" È banalmente falso. L'assemblaggio è un'astrazione sul linguaggio macchina. Che è un'astrazione sui circuiti hardware. Per favore, smetti di leggere troppo nella risposta che non era presente in questione in primo luogo. La domanda non riguardava "Astrazione" come concetto o strumento intellettuale. Si trattava di astrazione come una tecnica di progettazione OO - erroneamente applicata - troppo spesso. La mia risposta non è stata che l'astrazione è impossibile. Ma quella "sovraastrazione" è negativa.
S.Lott

1
@JasonBaker non è un motivo inaudito per dormire con qualcuno. Forse stavi pensando ai "soldi"?

19

Ah, YAGNI. Il concetto di programmazione più abusato.

C'è una differenza tra rendere generico il tuo codice e fare un lavoro extra. Dovresti dedicare tempo extra per rendere il tuo codice liberamente accoppiato e facilmente adattato per fare altre cose? Assolutamente. Dovresti dedicare tempo all'implementazione di funzionalità non necessarie? No. Dovresti perdere tempo a far funzionare il tuo codice con altri codici che non sono ancora stati impiantati? No.

Come dice il proverbio "Fai la cosa più semplice che potrebbe funzionare". Il fatto è che le persone confondono sempre semplice con facile . La semplicità richiede lavoro. Ma ne vale la pena.

Puoi andare in mare? Ovviamente. Ma la mia esperienza è che poche aziende hanno bisogno di un atteggiamento "get it now now" più di quello che hanno già. La maggior parte delle aziende ha bisogno di più persone che penseranno in anticipo. Altrimenti, finiscono sempre per avere un problema autosufficiente in cui nessuno ha mai tempo per niente, tutti hanno sempre fretta e nessuno ottiene nulla.

Pensaci in questo modo: è probabile che il tuo codice venga riutilizzato in qualche modo. Ma non hai intenzione di prevedere correttamente in quel modo. Quindi rendi il tuo codice pulito, astratto e liberamente accoppiato. Ma non cercare di far funzionare il tuo codice con funzionalità specifiche che non esistono ancora. Anche se sai che esisterà in futuro.


Bella distinzione tra simplee easy!
Matthieu M.


"Ma la mia esperienza è che poche aziende" - interessante, il contrario potrebbe essere vero nel mondo accademico.
Steve Bennett,

12

Un sillogismo:

  1. La generalità è costosa.

  2. Stai spendendo i soldi degli altri.

  3. Pertanto, le spese di generalità devono essere giustificate per le parti interessate.

Chiediti se stai davvero risolvendo il problema più generale al fine di risparmiare denaro degli stakeholder in seguito, o se trovi semplicemente una sfida intellettuale per fare una soluzione inutilmente generale.

Se si ritiene che la generalità sia desiderabile, allora dovrebbe essere progettata e testata come qualsiasi altra caratteristica . Se non riesci a scrivere test che dimostrano come la generalità che hai implementato risolva un problema richiesto dalla specifica, non preoccuparti di farlo! Una funzionalità che non soddisfa criteri di progettazione e che non può essere testata è una funzionalità su cui nessuno può fare affidamento.

E infine, non esiste qualcosa come "il più generale possibile". Supponiamo di scrivere software in C #, solo per ragioni di discussione. Farai implementare un'interfaccia a ogni classe? Ogni classe una classe base astratta con ogni metodo un metodo astratto? È piuttosto generale, ma non è affatto "il più generale possibile". Ciò consente alle persone di modificare l'implementazione di qualsiasi metodo tramite la sottoclasse. E se vogliono cambiare l'implementazione di un metodo senza subclassare? Puoi rendere ogni metodo effettivamente una proprietà di tipo delegato, con un setter in modo che le persone possano cambiare ogni metodo in qualcos'altro. E se qualcuno volesse aggiungere altri metodi? Ora ogni oggetto deve essere espandibile.

A questo punto dovresti abbandonare C # e abbracciare JavaScript. Ma non sei ancora arrivato abbastanza vicino al generale; cosa succede se qualcuno vuole cambiare il modo in cui la ricerca dei membri funziona per questo particolare oggetto? Forse dovresti invece scrivere tutto in Python.

Aumentare la generalità spesso significa abbandonare la prevedibilità e aumentare notevolmente i costi dei test per garantire che la generalità implementata riesca effettivamente a soddisfare un bisogno reale dell'utente . Tali costi sono giustificati dai loro benefici per le parti interessate che li pagano? Forse lo sono; dipende da te discutere con le parti interessate. I miei stakeholder non sono affatto disposti ad abbandonare la tipizzazione statica alla ricerca di un livello di generalità completamente inutile ed estremamente costoso, ma forse lo sono anche i tuoi.


2
+1 per principi utili, -1 per fare tutto con i soldi.
Mason Wheeler,

4
@Mason: non si tratta di soldi , si tratta di sforzo . Il denaro è una misura pratica di sforzo . L'efficienza è i benefici maturati per sforzo prodotto; ancora una volta, il profitto in denaro è una misura pratica dei benefici maturati . È semplicemente più facile discutere dell'astrazione del denaro che trovare un modo per misurare lo sforzo, i costi e i benefici. Preferiresti misurare sforzi, costi e benefici in qualche altro modo? Sentiti libero di fare una proposta di un modo migliore.
Eric Lippert,

2
Sono d'accordo con il punto di @Mason Wheeler. Trovo che la soluzione qui sia quella di non coinvolgere le parti interessate a quel livello di un progetto. Dipende ovviamente molto dal settore, ma i clienti di solito non vedono il codice. Inoltre, tutte queste risposte sembrano essere anti-sforzo, sembrano pigre.
Orbling

2
@Orbling: sono fortemente contrario a sforzi inutili, costosi e dispendiosi che tolgono risorse a progetti meritevoli e importanti. Se stai suggerendo che questo mi rende "pigro", sostengo che stai usando la parola "pigro" in un modo insolito.
Eric Lippert,

1
Nella mia esperienza, altri progetti non tendono ad andare via, quindi di solito vale la pena investire tutto il tempo necessario nel progetto corrente prima di andare avanti.
Orbling

5

Giusto per essere chiari, fare qualcosa di generalizzato e attuare un'astrazione sono due cose completamente diverse.

Ad esempio, si consideri una funzione che copia la memoria.

La funzione è un'astrazione che nasconde il modo in cui i 4 byte vengono copiati.

int copy4Bytes (char * pSrc, char * pDest)

Una generalizzazione farebbe copiare questa funzione a qualsiasi numero di byte.

int copyBytes (char * pSrc, char * pDest, int numBytesToCopy)

L'astrazione si presta al riutilizzo, mentre la generalizzazione rende l'astrazione utile in più casi.

Più specificamente correlato alla tua domanda, l'astrazione non è utile solo dal punto di vista del riutilizzo del codice. Spesso se eseguito correttamente renderà il tuo codice più leggibile e gestibile. Usando l'esempio sopra, cosa è più facile da leggere e capire se si sta scorrendo il codice copyBytes () o un ciclo for iterating attraverso un array che sposta i dati di un indice alla volta? L'astrazione può fornire una sorta di auto-documentazione che a mio avviso rende il codice più facile da lavorare.

Come mia regola empirica, se riesco a trovare un buon nome di funzione che descrive esattamente cosa intendo fare un pezzo di codice, allora scrivo una funzione per esso indipendentemente dal fatto che penso che lo userò di nuovo.


+1 per fare la differenza tra astrazione e generalizzazione.
Joris Meys,

4

Una buona regola generale per questo tipo di cose è Zero, One, Infinity. Cioè, se hai bisogno di quello che stai scrivendo più di una volta, puoi presumere che ne avrai bisogno ancora più volte e generalizzare. Questa regola implica che non ti preoccupi dell'astrazione la prima volta che scrivi qualcosa.

Un altro buon motivo per questa regola è che la prima volta che scrivi il codice, non saprai necessariamente cosa astrarre perché hai solo un esempio. La legge di Murphy implica che se scrivi il codice astratto per la prima volta, il secondo esempio avrà differenze che non avevi previsto.


1
Sembra molto simile alla mia versione della Regola di refactoring: colpisci due! refactoring!
Frank Shearar,

@Frank: probabilmente è la tua regola di refactoring, ma con il secondo paragrafo aggiunto dall'esperienza personale.
Larry Coleman,

+1: Credo che ciò sia stato notato abbastanza presto anche in The Pragmatic Programmer quasi alla lettera IIRC.
Steven Evers,

oh sicuramente. Non c'è niente di astratto con un solo esempio: non appena hai un secondo esempio, puoi vedere cosa è comune (condiviso) e cosa no, e puoi sottrarre!
Frank Shearar,

Un campione di due è secondo me troppo piccolo per essere generalizzato per l'infinito. In altre parole, troppo presto.

2

Eric Lippert sottolinea tre cose che penso possano essere applicate nel suo articolo a prova di futuro di un design . Penso che se lo seguirai sarai in buona forma.

Primo: la generalità prematura è costosa.

Secondo: rappresentare nel modello solo quelle cose che sono sempre nel dominio del problema e le cui relazioni di classe sono immutabili.

Terzo: mantieni le tue politiche lontane dai tuoi meccanismi.


1

Dipende dal motivo per cui stai codificando, dallo scopo del tuo progetto. Se il valore del tuo codice è che risolve un problema concreto, allora vuoi farlo e passare al problema successivo. Se ci sono cose facili e veloci che puoi fare per rendere le cose più facili per i futuri utenti del codice (incluso te stesso), allora, prendi tutte le ragionevoli soluzioni.

D'altra parte, ci sono casi in cui il codice che stai scrivendo ha uno scopo più generico. Ad esempio, quando si scrive una libreria per altri programmatori, che la utilizzerà in un'ampia varietà di applicazioni. I potenziali utenti sono sconosciuti e non puoi chiedere loro esattamente quello che vogliono. Ma vuoi rendere la tua biblioteca ampiamente utile. In questi casi passerei più tempo a cercare di supportare la soluzione generale.


0

Sono un grande fan del principio KISS.

Mi concentro sul fornire ciò che mi viene chiesto di fare e non su quale sia la soluzione migliore. Ho dovuto abbandonare il perfezionismo (e il DOC), perché mi rendeva infelice.


Perché l'antipatia del perfezionismo? (A proposito, non ti ho votato)
Orbling

Non puntare alla perfezione funziona per me. Perché? Perché so che i miei programmi non saranno mai perfetti. Non esiste una definizione standard di perfezione, quindi scelgo di fornire qualcosa che funzioni bene.
Pablo,

-1

dove Astrazione è sulla tua spalla destra e Solve-it-stupid siede sulla sinistra.

Non sono d'accordo con "risolverlo stupido" , penso che possa essere più "risolverlo intelligente" .

Cosa c'è di più intelligente:

  • Scrivere una soluzione generalizzata complessa che potrebbe supportare più casi
  • Scrivere codice breve ed efficace che risolva il problema a portata di mano ed è facile da mantenere e che può essere esteso in futuro SE è necessario.

La scelta predefinita dovrebbe essere la seconda. A meno che tu non possa dimostrare la necessità di un generazionale.

La soluzione generalizzata dovrebbe essere solo quando sapevi che è qualcosa che verrà utilizzato in più progetti / casi diversi.

Di solito trovo che le soluzioni generalizzate siano meglio servite come "Codice della biblioteca principale"


Se potessi fare tutte le ipotesi qui, non avresti un problema. Brevi e facili da mantenere si escludono a vicenda. Se sai che qualcosa verrà riutilizzato, la soluzione generale potrebbe risolverlo.
JeffO,

@Jeff Non sono del tutto sicuro di aver capito il tuo commento ..
Darknight

Hai preso "Risolvi stupido" dal contesto.
Bryan Harrington,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.