One-liner vs. leggibilità: quando smettere di ridurre il codice? [chiuso]


14

Contesto

Di recente mi sono interessato a produrre codice meglio formattato. E per meglio intendo "seguire le regole approvate da un numero sufficiente di persone per considerarlo una buona pratica" (dal momento che non ci sarà mai un modo unico "migliore" per codificare, ovviamente).

In questi giorni, scrivo principalmente codice in Ruby, quindi ho iniziato a usare una linter (Rubocop) per fornirmi alcune informazioni sulla "qualità" del mio codice (questa "qualità" è definita dalla guida alla ruby del progetto guidata dalla comunità ).

Si noti che userò la "qualità" come nella "qualità della formulazione", non tanto sull'efficienza del codice, anche se in alcuni casi, l'efficienza del codice è effettivamente influenzata dalla sua scrittura.

Ad ogni modo, facendo tutto ciò, ho realizzato (o almeno ricordato) alcune cose:

  • Alcuni linguaggi (in particolare Python, Ruby e simili) consentono di creare grandi linee di codice
  • Seguire alcune linee guida per il codice può renderlo significativamente più breve e tuttavia ancora molto chiaro
  • Tuttavia, seguire queste linee guida troppo rigorosamente può rendere il codice meno chiaro / facile da leggere
  • Il codice può rispettare alcune linee guida quasi perfettamente ed essere di scarsa qualità
  • La leggibilità del codice è per lo più soggettiva (come in "quello che trovo chiaro potrebbe essere completamente oscuro per un collega sviluppatore")

Quelle sono solo osservazioni, non regole assolute ovviamente. Noterai anche che la leggibilità del codice e le seguenti linee guida potrebbero sembrare non correlate a questo punto, ma qui le linee guida sono un modo per restringere il numero di modi per riscrivere un pezzo di codice.

Ora, alcuni esempi, per rendere tutto più chiaro.

Esempi

Prendiamo un semplice caso d'uso: abbiamo un'applicazione con un " User" modello. Un utente ha un indirizzo opzionale firstnamee surnameobbligatorio email.

Voglio scrivere un metodo " name" che restituirà quindi il nome ( firstname + surname) dell'utente se almeno il suo firstnameo surnameè presente, oppure il suo emailvalore di fallback in caso contrario.

Voglio anche che questo metodo prenda un " use_email" come parametro (booleano), che consenta di utilizzare l'e-mail dell'utente come valore di fallback. Questo use_emailparametro " " dovrebbe essere predefinito (se non passato) come " true".

Il modo più semplice per scrivere che, in Ruby, sarebbe:

def name(use_email = true)
 # If firstname and surname are both blank (empty string or undefined)
 # and we can use the email...
 if (firstname.blank? && surname.blank?) && use_email
  # ... then, return the email
  return email
 else
  # ... else, concatenate the firstname and surname...
  name = "#{firstname} #{surname}"
  # ... and return the result striped from leading and trailing spaces
  return name.strip
 end
end

Questo codice è il modo più semplice e facile da capire per farlo. Anche per qualcuno che non "parla" di Ruby.

Ora proviamo a renderlo più breve:

def name(use_email = true)
 # 'if' condition is used as a guard clause instead of a conditional block
 return email if (firstname.blank? && surname.blank?) && use_email
 # Use of 'return' makes 'else' useless anyway
 name = "#{firstname} #{surname}"
 return name.strip
end

Questo è più breve, ancora facile da capire, se non più semplice (la clausola di guardia è più naturale da leggere rispetto a un blocco condizionale). La clausola di Guard rende anche più conforme alle linee guida che sto usando, quindi win-win qui. Riduciamo anche il livello di rientro.

Ora usiamo un po 'di magia Ruby per renderla ancora più breve:

def name(use_email = true)
 return email if (firstname.blank? && surname.blank?) && use_email
 # Ruby can return the last called value, making 'return' useless
 # and we can apply strip directly to our string, no need to store it
 "#{firstname} #{surname}".strip
end

Ancora più breve e seguendo le linee guida perfettamente ... ma molto meno chiaro poiché la mancanza di dichiarazione di ritorno lo rende un po 'confuso per coloro che non hanno familiarità con questa pratica.

È qui che possiamo iniziare a porre la domanda: ne vale davvero la pena? Dovremmo dire "no, renderlo leggibile e aggiungere ' return'" (sapendo che questo non rispetterà le linee guida). O dovremmo dire "Va bene, è il modo di Ruby, impara la dannata lingua!"?

Se prendiamo l'opzione B, perché non renderla ancora più breve:

def name(use_email = true)
 (email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end

Eccolo qui, il one-liner! Naturalmente è più breve ... qui sfruttiamo il fatto che Ruby restituirà un valore o l'altro a seconda di quale è definito (poiché l'e-mail sarà definita nelle stesse condizioni di prima).

Possiamo anche scriverlo:

def name(use_email = true)
 (email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end

È breve, non così difficile da leggere (voglio dire, tutti abbiamo visto come può essere un brutto one-liner), buon Ruby, è conforme alle linee guida che uso ... Ma comunque, rispetto al primo modo di scrivere è molto meno facile da leggere e capire. Possiamo anche sostenere che questa riga è troppo lunga (più di 80 caratteri).

Domanda

Alcuni esempi di codice possono mostrare che scegliere tra un codice "full-size" e molte delle sue versioni ridotte (fino al famoso one-liner) può essere difficile dal momento che, come possiamo vedere, i one-liner non possono essere così spaventosi ma tuttavia, nulla batterà il codice "full-size" in termini di leggibilità ...

Quindi ecco la vera domanda: dove fermarsi? Quando è breve, abbastanza corto? Come sapere quando il codice diventa "troppo corto" e meno leggibile (tenendo presente che è abbastanza soggettivo)? E ancora di più: come codificare sempre di conseguenza ed evitare di mescolare una riga con blocchi di codice "full-size" quando ne ho voglia?

TL; DR

La domanda principale qui è: quando si tratta di scegliere tra un "pezzo di codice lungo ma chiaro, leggibile e comprensibile" e un "one-liner potente, più breve ma più difficile da leggere / comprendere", sapendo che quei due sono il massimo e il fondo di una scala e non le uniche due opzioni: come definire dove si trova la frontiera tra "abbastanza chiaro" e "non così chiaro come dovrebbe essere"?

La domanda principale non è la classica "One-liner vs readability: quale è meglio?" ma "Come trovare l'equilibrio tra quei due?"

Modifica 1

I commenti negli esempi di codice devono essere "ignorati", sono qui per chiarire cosa sta succedendo, ma non devono essere presi in considerazione quando si valuta la leggibilità del codice.


7
Troppo breve per una risposta: mantieni il refactoring iterativo fino a quando non sei sicuro che sia meglio dell'iterazione precedente, quindi interrompi e inverti l'ultima refactorizzazione.
Dom

8
Preferirei la variante 3 con la returnparola chiave aggiunta . Quei sette personaggi aggiungono un po 'di chiarezza ai miei occhi.
cmaster - ripristina monica il

2
Se ti senti davvero orribile, puoi scrivere tutto come [firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip... perché false.blank?ritorna vero e l'operatore ternario ti salva alcuni caratteri ... ¯ \ _ (ツ) _ / ¯
DaveMongoose,

1
OK, devo chiederti: quale chiarezza deve returnaggiungere la parola chiave ?! Non fornisce alcuna informazione . È puro disordine.
Konrad Rudolph,

2
L'idea che la brevità genera chiarezza non solo soffre della legge dei rendimenti decrescenti, ma si inverte quando viene spinta agli estremi. Se stai riscrivendo per abbreviare una funzione breve, stai perdendo tempo e lo stesso vale per cercare di giustificare la pratica.
sdenham,

Risposte:


26

Indipendentemente dal codice che scrivi, leggibile è la cosa migliore. Short è il secondo migliore. E leggibile di solito significa abbastanza breve da poter dare un senso al codice, agli identificatori ben definiti e aderire ai modi di dire comuni della lingua in cui è scritto il codice.

Se questo fosse un linguaggio agnostico, penso che questo sarebbe sicuramente basato sull'opinione pubblica, ma entro i confini del linguaggio Ruby penso che possiamo rispondere.

Innanzitutto, una caratteristica e un modo idiomatico di scrivere Ruby è omettere la returnparola chiave quando si restituisce un valore, a meno che non si ritorni presto da un metodo.

Un'altra caratteristica e un idioma combinati sta usando le ifistruzioni finali per aumentare la leggibilità del codice. Una delle idee guida in Ruby è scrivere codice che legge come linguaggio naturale. Per questo, andiamo a _why Poignant Guide to Ruby, Chapter 3 .

Leggi ad alta voce quanto segue.

5.times { print "Odelay!" }

Nelle frasi inglesi, la punteggiatura (come punti, esclamazioni, parentesi) è silenziosa. La punteggiatura aggiunge significato alle parole, aiuta a dare spunti su ciò che l'autore intendeva con una frase. Quindi leggiamo quanto sopra come: Cinque volte stampa "Odelay!".

Detto questo, l'esempio di codice n. 3 è il più idiomatico per Ruby:

def name(use_email = true)
  return email if firstname.blank? && surname.blank? && use_email

  "#{firstname} #{surname}".strip
end

Ora quando leggiamo il codice, dice:

Restituisci l'e-mail se il nome è vuoto, il cognome è vuoto e usa l'e-mail

(ritorno) nome e cognome eliminati

Che è dannatamente vicino all'attuale codice Ruby.

Sono solo 2 righe di codice reale, quindi è piuttosto conciso e aderisce ai modi di dire della lingua.


Bel punto. È vero che la domanda non doveva essere incentrata su Ruby, ma concordo sul fatto che non è possibile avere una risposta agnostica linguistica qui.
Sudiukil,

8
Trovo l'idea di far sembrare il codice come un linguaggio naturale molto sopravvalutato (e talvolta anche problematico). Ma anche senza questa motivazione, arrivo alla stessa conclusione di questa risposta.
Konrad Rudolph,

1
C'è un'altra modifica che vorrei prendere in considerazione per fare il codice. Cioè, per mettere use_emaildavanti alle altre condizioni poiché è una variabile anziché una chiamata di funzione. Ma poi di nuovo l'interpolazione di stringhe sommerge comunque la differenza.
John Dvorak,

Strutturare il codice seguendo le strutture del linguaggio naturale può farti cadere nelle trappole del linguaggio. Ad esempio, quando leggi i requisiti successivi do send an email if A, B, C but no D, seguire la tua premessa sarebbe naturale digitare 2 blocchi if / else , quando probabilmente sarebbe più facile codificare if not D, send an email. Fai attenzione al momento di leggere il linguaggio naturale e trasformalo in codice perché può farti scrivere una nuova versione della "Storia infinita" . Con classi, metodi e variabili. Dopo tutto non è un grosso problema.
Laiv

@Laiv: far leggere il codice come il linguaggio naturale non significa letteralmente tradurre i requisiti. Significa scrivere codice in modo che quando letto ad alta voce consenta al lettore di comprendere la logica senza leggere ogni bit di codice, carattere per carattere, costrutto del linguaggio per il costrutto del linguaggio. Se codificare if !Dè meglio, va bene tanto quanto Londra Dha un nome significativo. E se l' !operatore si perde tra l'altro codice, NotDsarebbe appropriato avere un identificatore chiamato .
Greg Burghardt,

15

Non credo che otterrai una risposta migliore di "usa il tuo miglior giudizio". In breve, dovresti cercare la chiarezza piuttosto che la mancanza . Spesso, anche il codice più breve è il più chiaro, ma se ci si concentra solo sul raggiungimento della brevità, la chiarezza potrebbe risentirne. Questo è chiaramente il caso degli ultimi due esempi, che richiede uno sforzo maggiore per comprendere rispetto ai tre esempi precedenti.

Una considerazione importante è il pubblico del codice. La leggibilità ovviamente dipende totalmente dalla persona che legge. Le persone che ti aspetti di leggere il codice (oltre a te stesso) conoscono davvero gli idiomi della lingua Ruby? Bene, questa domanda non è qualcosa a cui le persone casuali su Internet possono rispondere, questa è solo una tua decisione.


Sono d'accordo con il punto del pubblico, ma fa parte della mia lotta: poiché il mio software è spesso open-source, il pubblico potrebbe essere composto da principianti e da "Ruby Gods". Potrei renderlo semplice per renderlo accessibile alla maggior parte delle persone, ma sembra un po 'uno spreco dei vantaggi offerti dalla lingua.
Sudiukil,

1
Come qualcuno che ha dovuto prendere il controllo, estendere e mantenere un codice davvero orribile, la chiarezza deve vincere. Ricorda il vecchio adagio: scrivi il tuo codice come se il manutentore fosse un vendicativo Hell's Angel che sa dove vivi e dove vanno i tuoi figli a scuola.
uɐɪ

2
@Sudiukil: questo è un punto importante. Ti suggerisco di cercare il codice idiomatico in quel caso (cioè assumere una buona conoscenza della lingua), poiché è improbabile che i principianti contribuiranno comunque all'open source. (O se lo fanno, saranno pronti a
impegnarsi

7

Parte del problema qui è "cos'è la leggibilità". Per me, guardo il tuo primo esempio di codice:

def name(use_email = true)
 # If firstname and surname are both blank (empty string or undefined)
 # and we can use the email...
 if (firstname.blank? && surname.blank?) && use_email
  # ... then, return the email
  return email
 else
  # ... else, concatenate the firstname and surname...
  name = "#{firstname} #{surname}"
  # ... and return the result striped from leading and trailing spaces
  return name.strip
 end
end

E trovo difficile leggere poiché è pieno di commenti "rumorosi" che ripetono semplicemente il codice. Eliminali:

def name(use_email = true)
 if (firstname.blank? && surname.blank?) && use_email
  return email
 else
  name = "#{firstname} #{surname}"
  return name.strip
 end
end

ed è ora molto più leggibile. Nel leggerlo, penso "hmm, mi chiedo se Ruby supporta l'operatore ternario? In C #, posso scriverlo come:

string Name(bool useEmail = true) => 
    firstName.Blank() && surname.Blank() && useEmail 
    ? email 
    : $"{firstname} {surname}".Strip();

Qualcosa del genere è possibile nel rubino? Analizzando il tuo post, vedo che c'è:

def name(use_email = true)
 (email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end

Tutte cose buone. Ma questo non è leggibile per me; semplicemente perché devo scorrere per vedere l'intera riga. Quindi sistemiamo questo:

def name(use_email = true)
 (email if (firstname.blank? && surname.blank?) && use_email) 
 || "#{firstname} #{surname}".strip
end

Ora sono felice. Non sono completamente sicuro di come funzioni la sintassi, ma posso capire cosa fa il codice.

Ma sono solo io. Altre persone hanno idee molto diverse su ciò che rende piacevole leggere un pezzo di codice. Quindi devi conoscere il tuo pubblico quando scrivi il codice. Se sei un principiante assoluto dell'insegnamento, ti consigliamo di mantenerlo semplice e possibilmente scriverlo come il tuo primo esempio. Se lavori in un gruppo di sviluppatori professionisti con molti anni di esperienza in ruby, scrivi un codice che sfrutti la lingua e tienilo breve. Se è da qualche parte nel mezzo, quindi mira a un punto nel mezzo.

Una cosa che direi però: attenzione al "codice intelligente", come nel tuo ultimo esempio. Chiediti, [firstname, surname].all?(&:blank?)aggiungi altro oltre a farti sentire intelligente perché mette in mostra le tue abilità, anche se ora è un po 'più difficile da leggere? Direi che questo esempio rientra probabilmente in quella categoria. Se invece confrontassi cinque valori, lo vedrei come un buon codice. Quindi, di nuovo, non esiste una linea assoluta qui, basta essere consapevoli di essere troppo intelligenti.

Quindi, in sintesi: la leggibilità richiede di conoscere il pubblico e di indirizzare il codice di conseguenza e scrivere un codice breve ma chiaro; mai scrivere codice "intelligente". Tienilo breve, ma non troppo corto.


2
Beh, ho dimenticato di menzionarlo, ma i commenti dovevano essere "ignorati", sono qui solo per aiutare coloro che non conoscono bene Ruby. Punto valido però sul pubblico, non ci ho pensato. Per quanto riguarda la versione che ti rende felice: se è la lunghezza della linea che conta, la terza versione del mio codice (quella con una sola dichiarazione di ritorno) lo fa ed è anche un po 'più comprensibile, no?
Sudiukil,

1
@Sudiukil, non essendo uno sviluppatore rubino, ho scoperto che il più difficile da leggere e non si adattava a quello che stavo cercando (dal punto di vista di un'altra lingua) come la soluzione "migliore". Tuttavia, per qualcuno che ha familiarità con il fatto che ruby ​​è una di quelle lingue che restituisce il valore dell'ultima espressione, probabilmente rappresenta la versione più semplice e facile da leggere. Ancora una volta, tutto dipende dal tuo pubblico.
David Arno,

Non sono uno sviluppatore di Ruby ma questo ha molto più senso per me della risposta più votata, che recita come "Ecco cosa ho intenzione di restituire [nota in calce: in una condizione lunga specifica]. Inoltre, ecco una stringa che è arrivata in ritardo alla festa." La logica che è essenzialmente solo un'istruzione case dovrebbe essere scritta come una singola istruzione case uniforme, non distribuita su più istruzioni apparentemente non correlate.
Paul,

Personalmente andrei con il tuo secondo blocco di codice, tranne che unirei le due istruzioni nel tuo ramo else in una:return "#{firstname} #{surname}".strip
Paul,

2

Questa è probabilmente una domanda in cui è difficile non dare una risposta basata sull'opinione, ma, ecco i miei due centesimi.

Se trovi che accorciare il codice non influisce sulla leggibilità o addirittura lo migliora, provaci. Se il codice diventa meno leggibile, allora devi considerare se c'è una ragione abbastanza buona per lasciarlo in quel modo. Farlo solo perché è più corto, o bello, o semplicemente perché puoi, sono esempi di cattive ragioni. Devi anche considerare se rendere il codice più breve lo renderebbe meno comprensibile per le altre persone con cui lavori.

Quindi quale sarebbe una buona ragione? È un appello al giudizio, davvero, ma un esempio potrebbe essere qualcosa di simile a un'ottimizzazione delle prestazioni (dopo il test delle prestazioni, ovviamente, non in anticipo). Qualcosa che ti dà dei benefici che sei disposto a pagare con una leggibilità ridotta. In tal caso, puoi attenuare lo svantaggio fornendo un commento utile (che spiega cosa fa il codice e perché è stato reso un po 'enigmatico). Ancora meglio, puoi estrarre quel codice in una funzione separata con un nome significativo, in modo che sia solo una riga nel sito della chiamata che spiega cosa sta succedendo (tramite il nome della funzione) senza entrare nei dettagli (tuttavia, le persone hanno differenze opinioni su questo, quindi questa è un'altra chiamata di giudizio che devi fare).


1

La risposta è un po 'soggettiva, ma devi chiederti con tutta l'onestà che puoi raccogliere, se sarai in grado di capire quel codice quando torni ad esso in un mese o due.

Ogni modifica dovrebbe migliorare la capacità della persona media di comprendere il codice. Per rendere comprensibile il codice, è utile utilizzare le seguenti linee guida:

  • Rispetta i modi di dire della lingua . C #, Java, Ruby, Python hanno tutti i loro modi preferiti di fare la stessa cosa. I costrutti idiomatici aiutano a comprendere il codice che non conosci.
  • Interrompi quando il codice diventa meno leggibile . Nell'esempio che hai fornito, è successo quando hai colpito le tue ultime coppie di codice riducente. Hai perso il vantaggio idiomatico dell'esempio precedente e hai introdotto molti simboli che richiedono molta riflessione per capire veramente cosa sta succedendo.
  • Usa i commenti solo quando devi giustificare qualcosa di inaspettato . So che i tuoi esempi erano lì per spiegare i costrutti alle persone meno familiari con Ruby, e va bene per una domanda. Preferisco usare i commenti per spiegare regole aziendali inattese ed evitarle se il codice può parlare da solo.

Detto questo, ci sono momenti in cui il codice espanso aiuta a capire cosa sta succedendo meglio. Un esempio di ciò deriva da C # e LINQ. LINQ è un ottimo strumento e può migliorare la leggibilità in alcune situazioni, ma ho anche incontrato una serie di situazioni in cui era molto più confuso. Ho avuto un feedback nella revisione tra pari che ha suggerito di trasformare l'espressione in un ciclo con appropriate istruzioni if ​​in modo che altri potessero mantenerlo meglio. Quando ho obbedito, avevano ragione. Tecnicamente, LINQ è più idiomatico per C #, ma ci sono casi in cui degrada la comprensibilità e una soluzione più dettagliata la migliora.

Dico tutto ciò per dire questo:

Migliora quando puoi migliorare il tuo codice (più comprensibile)

Ricorda, tu o qualcuno come te dovrai mantenere quel codice in seguito. La prossima volta che ti imbatterai, potrebbero passare mesi. Fatti un favore e non inseguire la riduzione del numero di righe al costo di poter capire il tuo codice.


0

La leggibilità è una proprietà che si desidera avere, mentre avere-many-one-liners non lo è. Quindi piuttosto che "one-liners vs readability" la domanda dovrebbe essere:

Quando una linea di linea aumenta la leggibilità e quando la danneggiano?

Credo che le battute singole siano buone per la leggibilità quando soddisfano queste due condizioni:

  1. Sono troppo specifici per essere estratti in una funzione.
  2. Non vuoi interrompere il "flusso" di lettura del codice circostante.

Ad esempio, supponiamo che namenon sia un buon ... nome per il tuo metodo. Che combinare il nome e il cognome, o usare l'e-mail invece del nome, non fossero cose naturali da fare. Quindi, invece della namecosa migliore che potresti trovare, risulta essere lunga e ingombrante:

puts "Name: #{user.email_if_there_is_no_name_otherwise_use_firstname_and_surname(use_email)}"

Un nome così lungo indica che questo è molto specifico - se fosse più generale avresti potuto trovare un nome più generale. Quindi racchiuderlo in un metodo non aiuta né con la leggibilità (è troppo lungo) né con DRYness (troppo specifico per essere usato altrove), quindi è meglio lasciare il codice lì dentro.

Tuttavia - perché renderlo un one-liner? Di solito sono meno leggibili del codice multilinea. È qui che dovremmo verificare la mia seconda condizione: il flusso del codice circostante. E se hai qualcosa del genere:

puts "Group: #{user.group}"
puts "Title: #{user.title}"
if user.firstname.blank? && user.surname.blank?) && use_email
  name = email
else
  name = "#{firstname} #{surname}"
  name.strip
end
puts "Name: #{name}"
puts "Age: #{user.age}"
puts "Address: #{user.address}"

Il codice multilinea stesso è leggibile, ma quando si tenta di leggere il codice circostante (stampando i vari campi) quel costrutto multilinea interrompe il flusso. Questo è più facile da leggere:

puts "Group: #{user.group}"
puts "Title: #{user.title}"
puts "Name: #{(email if (user.firstname.blank? && user.surname.blank?) && use_email) || "#{user.firstname} #{user.surname}".strip}"
puts "Age: #{user.age}"
puts "Address: #{user.address}"

Il flusso non viene interrotto e, se necessario, puoi concentrarti sull'espressione specifica.

È questo il tuo caso? Sicuramente no!

La prima condizione è meno rilevante: l'hai già considerata abbastanza generica da meritare un metodo e hai trovato un nome per quel metodo che è molto più leggibile della sua implementazione. Ovviamente non lo estrarresti più in una funzione.

Per quanto riguarda la seconda condizione, interrompe il flusso del codice circostante? No! Il codice circostante è una dichiarazione di metodo, che la selezione nameè il suo unico scopo. La logica della scelta del nome non sta interrompendo il flusso del codice circostante - è lo scopo stesso del codice circostante!

Conclusione: non trasformare l'intero corpo della funzione in una riga

One-liner sono buoni quando vuoi fare qualcosa di un po 'complesso senza interrompere il flusso. Una dichiarazione di funzione sta già interrompendo il flusso (in modo che non venga interrotta quando si chiama quella funzione), pertanto rendere l'intero corpo della funzione un liner non aiuta la leggibilità.

Nota

Mi riferisco a funzioni e metodi "completi", non a funzioni incorporate o espressioni lambda che di solito fanno parte del codice circostante e devono adattarsi al suo flusso.

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.