Quando una funzione è troppo lunga? [chiuso]


130

35 linee, 55 linee, 100 linee, 300 linee? Quando dovresti iniziare a romperlo? Lo sto chiedendo perché ho una funzione con 60 righe (inclusi i commenti) e stavo pensando di separarla.

long_function(){ ... }

in:

small_function_1(){...}
small_function_2(){...}
small_function_3(){...}

Le funzioni non verranno utilizzate al di fuori di long_function, fare funzioni più piccole significa più chiamate di funzione, ecc.

Quando divideresti una funzione in una più piccola? Perché?

  1. I metodi dovrebbero fare solo una cosa logica (pensa alla funzionalità)
  2. Dovresti essere in grado di spiegare il metodo in una sola frase
  3. Dovrebbe adattarsi all'altezza del display
  4. Evita spese generali non necessarie (commenti che evidenziano l'ovvio ...)
  5. Il test unitario è più semplice per le piccole funzioni logiche
  6. Controlla se parte della funzione può essere riutilizzata da altre classi o metodi
  7. Evitare un eccessivo accoppiamento interclasse
  8. Evita strutture di controllo profondamente annidate

Grazie a tutti per le risposte , modifica l'elenco e vota per la risposta corretta, sceglierò quella;)

Sto refactoring ora con queste idee in mente :)


C'è un refuso nella tua domanda, penso che volevi dire "Quando una funzione è troppo lunga?".
Tom,

1
Stai fraintendendo la domanda ponendola in termini di righe di codice. I fattori determinanti non sono misurati in righe di codice.
dkretz,

questa domanda può diventare complicata a seconda del codice e della lingua. forse puoi pubblicarlo.
Ray Tayek,

Se è rispettato il principio della responsabilità singola, basta farlo. Di solito sento il bisogno di creare un'intestazione o per ogni 20 righe di codice, il che mi sta segnalando come astratto e nominare questo frammento una funzione con un nome significativo invece di creare un'intestazione di capitolo.
Yevgeniy Afanasyev,

Risposte:


75

Non ci sono regole vere e proprie per questo. In genere mi piace che i miei metodi "facciano solo una cosa". Quindi se si tratta di afferrare dati, quindi fare qualcosa con quei dati, quindi scriverli su disco, allora dividerei afferrando e scrivendo in metodi separati, quindi il mio metodo "principale" contiene semplicemente il "fare qualcosa".

Quel "fare qualcosa" potrebbe comunque essere un bel po 'di righe, quindi non sono sicuro che un certo numero di righe sia la metrica giusta da usare :)

Modifica: questa è una singola riga di codice che ho inviato in giro per lavoro la scorsa settimana (per dimostrare un punto .. non è qualcosa di cui mi sono abituato :)) - Certamente non vorrei 50-60 di questi cattivi ragazzi nel mio metodo : D

return level4 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3) && (r.Level4 == (int)level4)).ToList() : level3 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3)).ToList() : level2 != null ? GetResources().Where(r => (r.Level2 == (int)level2)).ToList() : GetAllResourceList();

1
LOL Beh, potrei rimuovere tutto lo spazio bianco nel mio metodo e sarebbe solo una riga molto lunga e non una funzione lunga. Facendo una cosa, questa è probabilmente la risposta che, grazie

@Movaxes Quel frammento di codice che ho pubblicato è una singola affermazione, non solo molte righe su una riga .. non ci sono punti e virgola lì dentro :) Avrei potuto espandere GetResources () ogni volta per renderlo ancora più malvagio: P
Steven Robbins,

Sì, ha senso. Perché non prendere semplicemente l'intero file sorgente e inserirlo in una riga. Voglio dire, allora
diventerai

Ricordo che nelle vecchie riviste (sto parlando di BBC Micro old) avevano "programmi a 10 righe" che avevano solo diverse dichiarazioni su ogni riga, fino alla lunghezza massima che la BBC poteva gestire .. erano sempre un dolore giusto digitare: D
Steven Robbins,

6
Mi piace il concetto della funzione che fa solo una cosa, .... ma. Se hai una funzione che fa 10 cose e sposti 9 di queste cose in funzioni separate, che sono ancora chiamate dalla funzione rimanente, quella funzione rimanente non sta ancora facendo essenzialmente 10 cose! Penso che interrompere la funzione in questo modo renda molto più facile il test.
mtnpaul

214

Ecco un elenco di bandiere rosse (in nessun ordine particolare) che potrebbero indicare che una funzione è troppo lunga:

  1. Strutture di controllo profondamente annidate : ad esempio for-loop 3 livelli di profondità o anche solo 2 livelli di profondità con istruzioni if ​​annidate che presentano condizioni complesse.

  2. Troppi parametri che definiscono lo stato : Per parametro che definisce lo stato , intendo un parametro di funzione che garantisce un particolare percorso di esecuzione attraverso la funzione. Prendi troppi di questi tipi di parametri e hai un'esplosione combinatoria di percorsi di esecuzione (questo di solito accade in tandem con il n. 1).

  3. Logica duplicata in altri metodi : il riutilizzo di codice scadente contribuisce enormemente al codice procedurale monolitico. Molte di queste duplicazioni logiche possono essere molto sottili, ma una volta ripensate, il risultato finale può essere un design molto più elegante.

  4. Eccessivo accoppiamento tra le classi : questa mancanza di incapsulamento adeguato provoca funzioni che si occupano delle caratteristiche intime di altre classi, allungandole quindi.

  5. Sovraccarico non necessario : i commenti che evidenziano le classi ovvie, profondamente annidate, i getter e setter superflui per le variabili di classe annidate private e nomi di funzioni / variabili insolitamente lunghi possono creare rumore sintattico all'interno di funzioni correlate che ne aumenteranno la lunghezza.

  6. Il tuo enorme display di livello per sviluppatori non è abbastanza grande per visualizzarlo : in realtà, i display di oggi sono abbastanza grandi che una funzione che è vicino alla sua altezza è probabilmente troppo lunga. Ma, se è più grande , questa è una pistola fumante che qualcosa non va.

  7. Non è possibile determinare immediatamente scopo della funzione : Inoltre, una volta che effettivamente fai determinare il suo scopo, se non è possibile riassumere questo scopo in una sola frase o capita di avere un tremendo mal di testa, questo dovrebbe essere un indizio.

In conclusione, le funzioni monolitiche possono avere conseguenze di vasta portata e sono spesso un sintomo di gravi carenze progettuali. Ogni volta che incontro codice che è una gioia assoluta da leggere, la sua eleganza è immediatamente evidente. E indovina un po ': le funzioni sono spesso di lunghezza molto breve.


1
Bel post! Molto deterministico
Chuck Conway,

2
@PedroMorteRolo Exactly. L'API standard non è sempre un modello di eleganza. Inoltre, gran parte dell'API Java è stata sviluppata con una profonda conoscenza di Java Compiler e JVM, quindi hai considerazioni sulle prestazioni che potrebbero spiegarlo. Concedo che sezioni critiche di codice che non possono sprecare un singolo millisecondo potrebbero dover infrangere alcune di queste regole, ma ciò dovrebbe sempre essere considerato un caso speciale. Trascorrere anticipatamente un ulteriore tempo di sviluppo è un investimento iniziale che può evitare il debito tecnologico futuro (potenzialmente paralizzante).
Ryan Delucchi,

2
A proposito ... Sono dell'opinione che i metodi lunghi non sono euristici e valgono anche per le classi. IMHO, le classi lunghe sono cattive, perché tendono a violare il principio della responsabilità singola. Sarebbe divertente avere compilatori che emettessero avvisi sia per le classi lunghe che per i metodi ....
Pedro Rolo,

3
@PedroMorteRolo Sono assolutamente d'accordo su questo. Inoltre, è probabile che le grandi classi abbiano uno stato più mutevole: il che porta a un codice che è molto difficile da mantenere.
Ryan Delucchi,

1
Migliore risposta. Un altro buon indizio è: come sono i commenti nel codice? Il numero di volte che ho inciampato attraverso il codice di qualcuno con una linea come: // fetch Foo's credentials where Bar is "uncomplete". Questo è quasi sicuramente un nome di funzione proprio lì e dovrebbe essere disaccoppiato. Probabilmente vuole essere trasformato in qualcosa del tipo: Foo.fetchCredentialWhereBarUncomplete()
Jay Edwards,

28

Penso che ci sia un enorme avvertimento nel mantra "fai solo una cosa" in questa pagina. A volte fare una cosa destreggia molte variabili. Non suddividere una funzione lunga in un gruppo di funzioni più piccole se le funzioni più piccole finiscono per avere lunghi elenchi di parametri. In questo modo si trasforma una singola funzione in un insieme di funzioni altamente accoppiate senza alcun valore individuale reale.


18

Una funzione dovrebbe fare solo una cosa. Se stai facendo molte piccole cose in una funzione, trasforma ogni piccola cosa in una funzione e chiama quelle funzioni dalla funzione lunga.

Quello che davvero non vuoi fare è copiare e incollare ogni 10 righe della tua funzione lunga in funzioni brevi (come suggerisce l'esempio).


Sì, fare molte piccole funzioni con il modello copia e incolla non è una grande idea, sono d'accordo che una funzione dovrebbe cercare di fare sempre solo una cosa

"fare una cosa" può o non può essere corretto, a seconda della granularità. Se una funzione moltiplica una matrice, va bene. Se una funzione crea un'auto virtuale, questa è "una cosa" ma è anche una cosa molto grande. È possibile utilizzare più funzioni per costruire un'auto, componente per componente.
void.pointer

16

Sono d'accordo che una funzione dovrebbe fare solo una cosa, ma a che livello è quella cosa.

Se le tue 60 linee stanno realizzando una cosa (dal punto di vista dei programmi) e i pezzi che compongono quelle 60 linee non verranno utilizzati da nessun altro, allora 60 linee vanno bene.

Non vi è alcun reale vantaggio nel romperlo, a meno che tu non possa scomporlo in pezzi concreti che si reggono da soli. La metrica da utilizzare è la funzionalità e non le righe di codice.

Ho lavorato su molti programmi in cui gli autori hanno portato l'unica cosa a un livello estremo e tutto ciò che è finito è stato far sembrare che qualcuno abbia preso una granata per una funzione / metodo e l'ha fatta esplodere in dozzine di pezzi non collegati che sono Difficile da seguire.

Quando si estraggono parti di tale funzione, è necessario considerare anche se si aggiungeranno spese generali non necessarie ed evitare di trasmettere grandi quantità di dati.

Credo che il punto chiave sia cercare la nausea in quella lunga funzione ed estrarre quelle parti. Ciò che ti rimane è la funzione, che sia lunga 10, 20 o 60 righe.


2
+1 "La metrica da utilizzare è la funzionalità e non le righe di codice"
Cody Piersall,

Un'altra metrica importante è il numero di livelli di annidamento dei blocchi. Riduci al minimo. Rompere una funzione in parti più piccole spesso aiuta. Anche altre cose possono aiutare, come i ritorni multipli.
user2367418

10

60 linee sono grandi ma non troppo lunghe per una funzione. Se si adatta a una schermata di un editor, puoi vederlo tutto in una volta. Dipende davvero da cosa stanno facendo le funzioni.

Perché posso interrompere una funzione:

  • È troppo lungo
  • Rende il codice più gestibile suddividendolo e usando nomi significativi per la nuova funzione
  • La funzione non è coerente
  • Parti della funzione sono utili in se stesse.
  • Quando è difficile trovare un nome significativo per la funzione (probabilmente sta facendo troppo)

3
Aspetti positivi, sono d'accordo, anche se devi nominare la funzione DoThisAndThisAndAlso Questo probabilmente fa troppo. grazie :)

2
Sei semplicemente fuori servizio con questo amico. 60 righe saranno sempre troppe. Direi che se ti stai avvicinando su 10 linee probabilmente sei vicino al limite.
Willcodejavaforfood,

Ma un'altra funzione sta ancora chiamando queste funzioni ed è essenzialmente la stessa DoThisAndThisAndAlsoThisfunzione ma con molta astrazione che devi ancora nominare in qualche modo
Timo Huovinen,

6

La mia euristica personale è che è troppo lungo se non riesco a vedere tutto senza scorrere.


4
... mentre hai impostato la dimensione del carattere su 5?
EricSchaefer,

5

Dimensione approssimativa della dimensione dello schermo (quindi procurati un grande pivot widescreen e ruotalo) :-)

Scherzo a parte, una cosa logica per funzione.

E la cosa positiva è che il test unitario è davvero molto più facile da fare con piccole funzioni logiche che fanno 1 cosa. Le grandi funzioni che fanno molte cose sono più difficili da verificare!

/ Johan


Un buon punto sul test dell'unità :)

5

Regola empirica: se una funzione contiene blocchi di codice che fanno qualcosa, che è in qualche modo staccato dal resto del codice, mettilo in una funzione separata. Esempio:

function build_address_list_for_zip($zip) {

    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

molto più bello:

function fetch_addresses_for_zip($zip) {
    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }
    return $addresses;
}

function build_address_list_for_zip($zip) {

    $addresses = fetch_addresses_for_zip($zip);

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

Questo approccio presenta due vantaggi:

  1. Ogni volta che è necessario recuperare gli indirizzi per un determinato codice postale, è possibile utilizzare la funzione immediatamente disponibile.

  2. Quando hai mai bisogno di leggere di build_address_list_for_zip()nuovo la funzione sai cosa sta per fare il primo blocco di codice (recupera gli indirizzi per un determinato codice postale, almeno è quello che puoi derivare dal nome della funzione). Se avessi lasciato in linea il codice della query, dovrai prima analizzare quel codice.

[D'altra parte (negherò di averti detto questo, anche sotto tortura): se leggi molto sull'ottimizzazione di PHP, potresti avere l'idea di mantenere il numero di funzioni il più piccolo possibile, perché le chiamate di funzione sono molto, molto costoso in PHP. Non lo so da quando non ho mai fatto benchmark. In tal caso, probabilmente sarebbe meglio non seguire nessuna delle risposte alla tua domanda se la tua applicazione è molto "sensibile alle prestazioni" ;-)]


grazie bell'esempio :)

5

Dai un'occhiata al ciclomatico di McCabe, in cui suddivide il suo codice in un grafico in cui "Ogni nodo nel grafico corrisponde a un blocco di codice nel programma in cui il flusso è sequenziale e gli archi corrispondono ai rami presi nel programma. "

Ora immagina che il tuo codice non abbia funzioni / metodi; è solo un'enorme quantità di codice sotto forma di un grafico.

Vuoi dividere questo sprawl in metodi. Considera che, quando lo fai, ci sarà un certo numero di blocchi in ciascun metodo. Solo un blocco di ciascun metodo sarà visibile a tutti gli altri metodi: il primo blocco (presumiamo che sarai in grado di saltare in un metodo in un solo punto: il primo blocco). Tutti gli altri blocchi in ciascun metodo saranno informazioni nascoste all'interno di quel metodo, ma ogni blocco all'interno di un metodo può potenzialmente saltare a qualsiasi altro blocco all'interno di quel metodo.

Per determinare quale dimensione dovrebbero essere i tuoi metodi in termini di numero di blocchi per metodo, una domanda che potresti porsi è: quanti metodi dovrei avere per ridurre al minimo il numero massimo potenziale di dipendenze (MPE) tra tutti i blocchi?

Questa risposta è data da un'equazione. Se r è il numero di metodi che minimizza l'errore massimo tollerato del sistema e n è il numero di blocchi nel sistema, l'equazione è: r = sqrt (n)

E si può dimostrare che ciò fornisce anche il numero di blocchi per metodo, anche sqrt (n).


4

Tieni presente che puoi finire il factoring solo per motivi di factoring, rendendo potenzialmente il codice più illeggibile di quanto non fosse in primo luogo.

Un mio ex collega aveva una bizzarra regola secondo cui una funzione / metodo deve contenere solo 4 righe di codice! Ha cercato di attenersi a questo in modo così rigido che i nomi dei suoi metodi spesso sono diventati ripetitivi e privi di significato, più le chiamate sono diventate profondamente annidate e confuse.

Quindi il mio mantra è diventato: se non riesci a pensare a una decente funzione / nome del metodo per il pezzo di codice che stai ricostituendo, non preoccuparti.


2

Il motivo principale per cui di solito interrompo una funzione è perché alcuni pezzi sono anche ingredienti di un'altra funzione vicina che sto scrivendo, quindi le parti comuni vengono prese in considerazione. Inoltre, se utilizza molti campi o proprietà di qualche altra classe, ci sono buone probabilità che il blocco rilevante possa essere estratto all'ingrosso e, se possibile, spostato nell'altra classe.

Se hai un blocco di codice con un commento in alto, considera di estrarlo in una funzione, con i nomi delle funzioni e degli argomenti che ne illustrano lo scopo e riservando il commento per la logica del codice.

Sei sicuro che non ci siano pezzi che potrebbero essere utili altrove? Che tipo di funzione è?


La funzione crea un file cache da un modello, basato sull'URL, come post_2009_01_01.html dall'URL / post /

2

Secondo me la risposta è: quando fa troppe cose. La funzione dovrebbe eseguire solo le azioni previste dal nome della funzione stessa. Un'altra cosa da considerare è se vuoi riutilizzare alcune parti delle tue funzioni in altre; in questo caso potrebbe essere utile dividerlo.


2

Di solito interrompo le funzioni dalla necessità di inserire commenti che descrivono il prossimo blocco di codice. Ciò che in precedenza è andato nei commenti ora va nel nuovo nome della funzione. Questa non è una regola difficile, ma (per me) una bella regola empirica. Mi piace parlare da solo meglio di uno che ha bisogno di commenti (poiché ho imparato che i commenti di solito mentono)


Mi piace commentare il mio codice, principalmente non per me ma per gli altri, che elimina molte domande su dove è stata definita $ variabile, ma mi piace anche che il codice sia autoesplicativo. I commenti mentono?

sì, perché spesso non vengono mantenute. Al momento della stesura potrebbero essere corretti, ma una volta introdotti un bugfix o una nuova funzionalità, nessuno impone che i commenti vengano modificati in base alla nuova situazione. I nomi dei metodi tendono a mentire molto meno spesso dei commenti IMHO
Olaf Kock,

Ho appena trovato questa risposta: stackoverflow.com/questions/406760/… affermando che "La maggior parte dei commenti nel codice sono in realtà una forma perniciosa di duplicazione del codice". Inoltre - Lunga fila di commenti lì.
Olaf Kock

1

Questo è in parte una questione di gusti, ma il modo in cui lo determino è che cerco di mantenere le mie funzioni all'incirca solo finché si adatteranno allo schermo contemporaneamente (al massimo). Il motivo è che è più facile capire cosa sta succedendo se riesci a vedere tutto in una volta.

Quando codifico, è un mix di scrivere funzioni lunghe, quindi rifattorizzare per estrarre bit che potrebbero essere riutilizzati da altre funzioni e scrivere piccole funzioni che svolgono compiti discreti mentre procedo.

Non so che ci sia una risposta giusta o sbagliata a questo (ad esempio, puoi stabilirti su 67 righe come il tuo massimo, ma ci possono essere momenti in cui ha senso aggiungerne altre).


Beh, mi piace anche vedere la mia funzione completa sullo schermo :) a volte ciò significa un carattere Monospace 9 e una grande risoluzione su uno sfondo nero, sono d'accordo che è più facile capirlo in quel modo.

1

Sono state fatte alcune ricerche approfondite su questo argomento, se vuoi il minor numero di bug, il tuo codice non dovrebbe essere troppo lungo. Ma non dovrebbe essere troppo corto.

Non sono d'accordo sul fatto che un metodo dovrebbe adattarsi al tuo display in uno, ma se stai scorrendo verso il basso di più di una pagina, il metodo è troppo lungo.

Vedere Dimensione ottimale della classe per software orientato agli oggetti per ulteriori discussioni.


grazie per il link, leggendo :)

1

Ho già scritto 500 funzioni di linea, tuttavia queste erano solo grandi istruzioni switch per la decodifica e la risposta ai messaggi. Quando il codice per un singolo messaggio è diventato più complesso di un singolo if-then-else, l'ho estratto.

In sostanza, sebbene la funzione fosse di 500 linee, le regioni mantenute in modo indipendente avevano una media di 5 linee.


1

Normalmente utilizzo un approccio test-driven per scrivere codice. In questo approccio la dimensione della funzione è spesso correlata alla granularità dei test.

Se il tuo test è sufficientemente focalizzato, ti porterà a scrivere una piccola funzione focalizzata per far passare il test.

Questo funziona anche nell'altra direzione. Le funzioni devono essere sufficientemente piccole per essere testate in modo efficace. Quindi, quando lavoro con un codice legacy, trovo spesso che scompongo funzioni più grandi per testare le diverse parti di esse.

Di solito mi chiedo "qual è la responsabilità di questa funzione" e se non riesco a dichiarare la responsabilità in una frase chiara e concisa, per poi tradurla in un piccolo test mirato, mi chiedo se la funzione sia troppo grande.


1

Se ha più di tre rami, in genere ciò significa che una funzione o un metodo dovrebbe essere suddiviso, per incapsulare la logica di ramificazione in diversi metodi.

Ciascuno per ciclo, istruzione if, ecc. Non viene quindi visto come un ramo nel metodo chiamante.

Il codice Cobertura per Java (e sono sicuro che ci sono altri strumenti per altre lingue) calcola il numero di if, ecc. In una funzione per ciascuna funzione e lo somma per la "complessità ciclomatica media".

Se una funzione / metodo ha solo tre rami, otterrà tre su quella metrica, il che è molto buono.

A volte è difficile seguire questa linea guida, in particolare per la convalida dell'input dell'utente. Tuttavia, mettere i rami in metodi diversi aiuta non solo lo sviluppo e la manutenzione, ma anche i test, poiché gli input ai metodi che eseguono il branching possono essere facilmente analizzati per vedere quali input devono essere aggiunti ai casi di test al fine di coprire i rami che non erano coperti.

Se tutti i rami fossero all'interno di un singolo metodo, gli input dovrebbero essere tracciati dall'inizio del metodo, il che ostacola la testabilità.


0

Sospetto che troverai molte risposte su questo.

Probabilmente lo spezzerei in base alle attività logiche che venivano eseguite all'interno della funzione. Se ti sembra che il tuo racconto si stia trasformando in un romanzo, suggerirei di trovare ed estrarre passi distinti.

Ad esempio, se si dispone di una funzione che gestisce un tipo di input di stringa e restituisce un risultato di stringa, è possibile interrompere la funzione in base alla logica per dividere la stringa in parti, alla logica per aggiungere caratteri extra e alla logica per inserirlo tutti di nuovo insieme come risultato formattato.

In breve, qualunque cosa renda il tuo codice pulito e di facile lettura (sia semplicemente assicurando che la tua funzione abbia un buon commento o scomponendolo) è l'approccio migliore.


0

supponendo che stai facendo una cosa, la lunghezza dipenderà da:

  • cosa stai facendo
  • quale lingua stai usando
  • quanti livelli di astrazione devi affrontare nel codice

60 righe potrebbero essere troppo lunghe o potrebbe essere giusto. sospetto che potrebbe essere troppo lungo però.


Sto facendo un po 'di cache in PHP, sì, probabilmente 60 righe sono troppe, refactoring ...

0

Una cosa (e quella cosa dovrebbe essere ovvia dal nome della funzione), ma non più di una schermata di codice, a prescindere. E sentiti libero di aumentare la dimensione del carattere. E in caso di dubbio, trasformalo in due o più funzioni.


0

Estendendo lo spirito di un tweet di Zio Bob qualche tempo fa, sai che una funzione sta diventando troppo lunga quando senti la necessità di mettere una riga vuota tra due righe di codice. L'idea è che se hai bisogno di una riga vuota per separare il codice, la sua responsabilità e ambito si stanno separando in quel punto.


0

La mia idea è che se devo chiedermi se è troppo lungo, probabilmente è troppo lungo. Aiuta a rendere più piccole le funzioni, in quest'area, perché potrebbe aiutare più avanti nel ciclo di vita dell'applicazione.

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.