Czy powinienem wcześniej wrócić z funkcji, czy użyć instrukcji if? [Zamknięte]


304

Często pisałem tego rodzaju funkcje w obu formatach i zastanawiałem się, czy jeden format jest lepszy od drugiego i dlaczego.

public void SomeFunction(bool someCondition)
{
    if (someCondition)
    {
        // Do Something
    }
}

lub

public void SomeFunction(bool someCondition)
{
    if (!someCondition)
        return;

    // Do Something
}

Zwykle koduję za pomocą pierwszego, ponieważ w ten sposób działa mój mózg podczas kodowania, chociaż myślę, że wolę drugi, ponieważ od razu zajmuje się obsługą błędów i łatwiej mi go czytać


9
Jestem trochę spóźniony na tę dyskusję, więc nie dam odpowiedzi; Pomyślałem również o tym dwa lata temu: lecterror.com/articles/view/code-formatting-and-readability . Drugie jest łatwiejsze do odczytania, modyfikacji, utrzymania i debugowania. Ale może to tylko ja :)
dr Hannibal Lecter


4
teraz to pytanie jest dobrym przykładem pytania opartego na opiniach
Rudolf Olah

2
a co jeśli nie ma absolutnego dowodu w tym czy innym kierunku? Gdy wystarczająca argumentacja jest przekazywana w jednym i drugim kierunku, a głosowanie jest prawidłowe, jeśli odpowiedź jest prawidłowa - czyni ją bardzo przydatną. Uważam, że takie pytania zamykające są szkodliwe dla wartości tej witryny.
gsf

9
I lubię oparte na opiniach pytania i odpowiedzi. Mówią mi, co większość woli, a to pozwala mi napisać kod, który inni mogą przeczytać.
Zygimantas

Odpowiedzi:


402

Wolę drugi styl. Najpierw usuń niepoprawne przypadki, po prostu wychodząc lub podnosząc wyjątki, wstaw odpowiednią pustą linię, a następnie dodaj „rzeczywistą” treść metody. Łatwiej mi to czytać.


7
Smalltalk nazywa te „klauzule ochronne”. Przynajmniej tak je nazywa Kent Beck we wzorcach najlepszych praktyk Smalltalk; Nie wiem, czy to zwykły język.
Frank Shearar,

154
Wychodzenie wcześnie pozwala zrzucić rzeczy z ograniczonego stosu mentalnego. :)
Joren,

50
Wcześniej słyszałem, że jest to określane jako „Wzór Bouncer” - pozbądź się złych skrzyń, zanim wejdą do drzwi.
RevBingo,

14
Posunąłbym się nawet tak daleko i powiedziałbym, że to zdecydowanie jedyna słuszna rzecz do zrobienia.
Oliver Weiler,

38
Ponadto nie musisz dodawać wcięć, jeśli na początku pozbędziesz się skrzynek granicznych.
doppelgreener

170

Zdecydowanie to drugie. Ten pierwszy nie wygląda teraz źle, ale kiedy otrzymujesz bardziej złożony kod, nie wyobrażam sobie, aby ktokolwiek pomyślałby tak:

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    int retval = SUCCESS;
    if (someCondition)
    {
        if (name != null && name != "")
        {
            if (value != 0)
            {
                if (perms.allow(name)
                {
                    // Do Something
                }
                else
                {
                    reval = PERM_DENY;
                }
            }
            else
            {
                retval = BAD_VALUE;
            }
        }
        else
        {
            retval = BAD_NAME;
        }
    }
    else
    {
        retval = BAD_COND;
    }
    return retval;
}

jest bardziej czytelny niż

public int SomeFunction(bool cond1, string name, int value, AuthInfo perms)
{
    if (!someCondition)
        return BAD_COND;

    if (name == null || name == "")
        return BAD_NAME;

    if (value == 0)
        return BAD_VALUE;

    if (!perms.allow(name))
        return PERM_DENY;

    // Do something
    return SUCCESS;
}

W pełni przyznaję, że nigdy nie zrozumiałem zalet pojedynczych punktów wyjścia.


20
Zaletą pojedynczego punktu wyjścia jest to, że ... jest jeden punkt wyjścia! Na twoim przykładzie jest kilka punktów, które mogą wrócić. Z bardziej złożoną funkcją, która może zmienić się w punkt wyjścia, gdy zmienia się format wartości zwracanej. Oczywiście zdarza się, że wymuszanie pojedynczego punktu wyjścia nie ma sensu.
JohnL,

71
Problem stanowią duże funkcje @JohnL, a nie wiele punktów wyjścia. Chyba że pracujesz w kontekście, w którym dodatkowe wywołanie funkcji ogromnie spowolni Twój kod, oczywiście ...
Dan Rosenstark

4
@Yar: To prawda, ale sedno sprawy. Nie zamierzam próbować nawracać nikogo, a ja właśnie wskazywałam na korzyść wynikającą z uzyskania jak najmniejszej liczby punktów wyjścia (a przykład Jasona i tak był typem słomianego człowieka). Następnym razem, gdy wyciągniesz włosy, próbując dowiedzieć się, dlaczego SomeFunction czasami zwraca nieparzyste wartości, być może będziesz chciał dodać rejestrację tuż przed powrotem. Znacznie łatwiej jest debugować, jeśli jest tylko jeden robal! :)
JohnL

76
@Jason Viers Jakby singiel return value;pomaga!?! Następnie trzeba polować na pół tuzina value = ..., z tą wadą, że nigdy nie masz pewności, że wartość nie zostanie zmieniona między tym zadaniem a ostatecznym zwrotem. Przynajmniej natychmiastowy powrót jest jasny, że nic już nie zmieni wyniku.
Sjoerd

5
@Sjoed: Tutaj drugi Sjoerd. Jeśli chcesz zapisać wynik, możesz zalogować się na stronie dzwoniącego. Jeśli chcesz poznać przyczynę, musisz zalogować się w każdym punkcie wyjścia / przypisaniu, aby oba były identyczne pod tym względem.
Matthieu M.

32

To zależy - ogólnie rzecz biorąc, nie będę się starał przesuwać wielu kodów, aby wcześnie wyjść z funkcji - kompilator ogólnie się tym zajmie. To powiedziawszy jednak, jeśli są jakieś podstawowe parametry na górze, których potrzebuję i nie mogę kontynuować w inny sposób, wybuchnę wcześnie. Podobnie, jeśli warunek generuje gigantyczny ifblok w działaniu, to również wcześniej go wyłamię.

To powiedziawszy jednak, jeśli funkcja wymaga pewnych danych podczas jej wywoływania, zwykle rzucę wyjątek (patrz przykład), a nie tylko powracam.

public int myFunction(string parameterOne, string parameterTwo) {
  // Can't work without a value
  if (string.IsNullOrEmpty(parameterOne)) {
    throw new ArgumentNullException("parameterOne");
  } 
  if (string.IsNullOrEmpty(parameterTwo)) {
    throw new ArgumentNullException("parameterTwo");
  }

  // ...      
  // Do some work
  // ...

  return value;
}

9
A jeśli efekt końcowy jest możliwy do utrzymania, kogo obchodzi, który styl został wybrany?
Jeff Siver

3
@Jeff Siver - Dlaczego więc jest to pytanie w stylu „świętej wojny”, w końcu sprowadza się to do osobistych preferencji i wszystkiego, co mówi wewnętrzny przewodnik po stylu.
rjzii,

1
Kluczową sprawą jest to, że rzuca wyjątek zamiast wracać wcześniej. Zwracana wartość nie powinna być ponownie wykorzystywana do sprawdzania ważności. Co jeśli miałeś różne warunki i chciałbyś, aby kod za pomocą metody wiedział, dlaczego się nie udało? Nagle możesz zwrócić rzeczywiste dane biznesowe, nic (pusty wynik) lub wiele różnych ciągów, kodów, liczb ... za pomocą jednej metody. Wystarczy opisać, dlaczego się nie udało. Nie, dziękuję.
DanMan

a co ze złożonością cykliczną, jak sugeruje mój kompilator? czy nie lepiej nie zagnieżdżać kodu, jeśli można mu pomóc?
l - '' '' '----------' '' '' ''

24

Wolę wczesny powrót.

Jeśli masz jeden punkt wejścia i jeden punkt wyjścia, zawsze musisz śledzić cały kod w głowie aż do punktu wyjścia (nigdy nie wiesz, czy jakiś inny kod kodu robi coś innego, więc możesz muszę to wyśledzić, aż będą istnieć). Robisz to bez względu na to, która gałąź określa ostateczny wynik. To jest trudne do naśladowania.

Z jednym wpisem i wielokrotnością istnieje, wracasz, gdy masz wynik i nie zawracasz sobie głowy śledzeniem go do końca, aby zobaczyć, że nikt nie robi nic innego (ponieważ nie będzie nic innego, odkąd wróciłeś). To tak, jakby ciało metody podzieliło się na więcej kroków, które każdy krok z możliwością zwrócenia wyniku lub wypróbowania szczęścia w następnym kroku.


13

W programowaniu C, w którym trzeba ręcznie wyczyścić, wiele można powiedzieć o jednopunktowym zwrocie. Nawet jeśli nie ma teraz potrzeby, aby coś wyczyścić, ktoś może edytować twoją funkcję, przydzielić coś i musi to wyczyścić przed powrotem. Jeśli tak się stanie, będzie to koszmarna praca przeglądająca wszystkie zwroty.

W programowaniu w C ++ masz destruktory, a nawet teraz osłony wyjścia z zakresu. Wszystko to musi być tutaj, aby upewnić się, że kod jest wyjątkowo bezpieczny, więc kod jest dobrze chroniony przed wczesnym wyjściem, a zatem nie ma logicznego minusu i jest wyłącznie kwestią stylu.

Nie mam wystarczającej wiedzy na temat Javy, czy „w końcu” zostanie wywołany kod blokowy i czy finaliści mogą poradzić sobie z sytuacją konieczności zapewnienia, że ​​coś się stanie.

C # Na pewno nie mogę odpowiedzieć.

Język D zapewnia odpowiednie wbudowane osłony wyjścia zakresu i dlatego jest dobrze przygotowany do wcześniejszego wyjścia, dlatego nie powinien przedstawiać innego problemu niż styl.

Funkcje nie powinny oczywiście być tak długie, a jeśli masz dużą instrukcję switch, twój kod jest prawdopodobnie również źle uwzględniony.


1
C ++ pozwala na tak zwaną optymalizację wartości zwracanej, która pozwala kompilatorowi zasadniczo pominąć operację kopiowania, która normalnie miałaby miejsce po zwróceniu wartości. Jednak w różnych warunkach, które są trudne do wykonania, a wielokrotność zwracanych wartości jest jednym z takich przypadków. Innymi słowy, użycie wielu wartości zwracanych w C ++ może faktycznie spowolnić kod. To z pewnością trzyma MSC, a biorąc pod uwagę, że wdrożenie RVO z wieloma możliwymi wartościami zwrotnymi byłoby dość trudne (jeśli nie niemożliwe), prawdopodobnie jest to problem we wszystkich kompilatorach.
Eamon Nerbonne

1
W C wystarczy użyć gotoi ewentualnie dwukropka. Przykład (formatowanie kodu niemożliwe w komentarzach):foo() { init(); if (bad) goto err; bar(); if (bad) goto err; baz(); return 0; err: cleanup(); return 1; }
mirabilos

1
Zamiast goto wolę „Extract Method”. Kiedy myślisz, że musisz zaimplementować zmienną wartości zwracanej lub goto, aby zapewnić, że Twój kod czyszczenia jest zawsze wywoływany, to jest Zapach, który musisz podzielić na wiele funkcji. Pozwala to uprościć złożony kod warunkowy za pomocą klauzul ochronnych i innych technik wczesnego powrotu, przy jednoczesnym zapewnieniu, że kod czyszczenia zawsze działa.
BrandonLWhite

„ktoś może edytować twoją funkcję” - to takie absurdalne wyjaśnienie przy podejmowaniu decyzji. Każdy może zrobić wszystko w przyszłości. Nie oznacza to, że powinieneś dziś zrobić coś konkretnego, aby uniemożliwić komuś zepsucie się w przyszłości.
Victor Yarema

Nazywany, co umożliwia utrzymanie kodu. W prawdziwym świecie kod jest pisany dla celów biznesowych, a czasami programista musi go później zmienić. W tym czasie napisałem tę odpowiedź, chociaż załatałem CURL, aby wprowadzić buforowanie i przypomnieć bałagan spaghetti, że to naprawdę wymagało odpowiedniego przepisania we współczesnym C ++
CashCow

9

Wczesne zwroty dla wygranej. Mogą wydawać się brzydkie, ale znacznie mniej brzydkie niż duże ifopakowania, szczególnie jeśli istnieje wiele warunków do sprawdzenia.


9

Używam obu.

Jeśli DoSomethingjest 3-5 wierszy kodu, kod wygląda pięknie przy użyciu pierwszej metody formatowania.

Ale jeśli ma o wiele więcej wierszy, wolę drugi format. Nie podoba mi się, gdy nawiasy otwierające i zamykające nie są na tym samym ekranie.


2
Piękne nie ma mowy! Za dużo wcięć!
JimmyKane 24.04.16

@ JimmyKane Jest tylko tyle wcięć, które mogą się zdarzyć w 3-5 liniach, szczególnie, że potrzebujesz 2 (?) Linii na poziom, reszta jest wcięta: jedna dla struktury kontroli i początku bloku, jedna dla końca bloku.
Deduplicator

8

Klasycznym powodem pojedynczego wejścia-pojedynczego wyjścia jest to, że inaczej formalna semantyka stałaby się niewymownie brzydka inaczej (ten sam powód GOTO został uznany za szkodliwy).

Innymi słowy, łatwiej jest uzasadnić, kiedy oprogramowanie opuści procedurę, jeśli masz tylko 1 zwrot. Co jest również argumentem przeciwko wyjątkom.

Zazwyczaj minimalizuję podejście do wczesnego powrotu.


ale w przypadku narzędzia do analizy formalnej można zsyntetyzować funkcję zewnętrzną za pomocą semantyki, której potrzebuje narzędzie, i zachować czytelność kodu.
Tim Williscroft,

@Tim. To zależy od tego, ile chcesz zrobić i co analizujesz. Uważam, że SESE jest dość czytelny, jeśli programista był rozsądny.
Paul Nathan

Moje podejście do analizy kształtowane jest przez optymalizacje z projektu Self. 99% dynamicznych wywołań metod można rozwiązać statycznie i wyeliminować. więc możesz przeanalizować całkiem ładny kod linii prostej. Jako pracujący programista mogę wiarygodnie założyć, że większość kodu, nad którym pracuję, została napisana przez przeciętnych programistów. Więc nie będą pisać bardzo dobrego kodu.
Tim Williscroft,

@Tim: Wystarczająco uczciwy. Chociaż czuję się zmuszony do wskazania, że ​​języki statyczne wciąż mają dużo zabawy.
Paul Nathan

Powód, który nie stoi w obliczu wyjątków. Dlatego single-exit jest świetny w C, ale nic nie kupuje w C ++.
peterchen

7

Osobiście wolę na początku sprawdzać warunki zaliczenia / niepowodzenia. To pozwala mi pogrupować większość najczęstszych awarii na górze funkcji z resztą logiki do naśladowania.


6

To zależy.

Wcześniejszy powrót, jeśli istnieje jakiś oczywisty stan ślepej uliczki, który należy natychmiast sprawdzić, co sprawiłoby, że uruchomienie pozostałej funkcji byłoby bezcelowe. *

Ustaw Retval + pojedynczy powrót, jeśli funkcja jest bardziej złożona i w przeciwnym razie może mieć wiele punktów wyjścia (problem z czytelnością).

* Może to często wskazywać na problem z projektem. Jeśli okaże się, że wiele metod wymaga sprawdzenia stanu zewnętrznego / parametrów przed uruchomieniem pozostałej części kodu, prawdopodobnie to powinno być zajęte przez program wywołujący.


6
Kiedy piszę kod, który można udostępniać, moja mantra brzmi: „Nie zakładaj niczego. Nie ufaj nikomu”. Zawsze powinieneś sprawdzać poprawność danych wejściowych i każdego zewnętrznego stanu, od którego zależysz. Lepiej rzucić wyjątek niż potencjalnie uszkodzić coś, ponieważ ktoś dał ci złe dane.
TMN

@TMN: Dobra uwaga.
Tabele Bobby

2
Kluczową kwestią jest to, że w OO rzucasz wyjątek , a nie wracasz . Wielokrotne zwroty mogą być złe, wielokrotne generowanie wyjątków niekoniecznie musi mieć zapach kodu.
Michael K

2
@MichaelK: Metoda powinna stanowić wyjątek, jeśli nie można spełnić warunku końcowego. W niektórych przypadkach metoda powinna wyjść wcześniej, ponieważ warunek końcowy został osiągnięty jeszcze przed uruchomieniem funkcji. Na przykład, jeśli ktoś wywoła metodę „Ustaw etykietę kontrolną”, aby zmienić etykietę kontrolki, etykieta Fredokna jest już Fredustawiona, a ustawienie nazwy kontrolki na jej obecny stan wymusiłoby przerysowanie (co, choć potencjalnie przydatne, w niektórych przypadkach, być denerwujące w tym, który jest pod ręką), byłoby całkowicie rozsądne, aby metoda set-name wcześnie wychodziła, jeśli stare i nowe nazwy pasują do siebie.
supercat

3

Użyj If

W książce Dona Knutha o GOTO czytałem, że podaje powód, dla którego zawsze najbardziej prawdopodobny warunek jest najważniejszy w stwierdzeniu if. Przy założeniu, że jest to nadal rozsądny pomysł (i nie tylko z uwagi na szybkość epoki). Powiedziałbym, że wczesne zwroty nie są dobrą praktyką programistyczną, szczególnie biorąc pod uwagę fakt, że częściej niż nie są używane do obsługi błędów, chyba że Twój kod jest bardziej podatny na awarię niż na :-)

Jeśli zastosujesz się do powyższej porady, musisz umieścić ten zwrot na dole funkcji, a wtedy równie dobrze możesz nie nazwać go return, po prostu ustaw kod błędu i zwróć dwa wiersze. W ten sposób osiągnięcie idealnego wyjścia 1 wejście 1.

Specyficzne dla Delphi ...

Myślę, że jest to dobra praktyka programistyczna dla programistów Delphi, chociaż nie mam żadnego dowodu. Przed D2009 nie mamy atomowego sposobu na zwrócenie wartości, mamy exit;i result := foo;moglibyśmy po prostu rzucić wyjątki.

Gdybyś musiał zastąpić

if (true) {
 return foo;
} 

dla

if true then 
begin
  result := foo; 
  exit; 
end;

możesz mieć dość tego, że widzisz to u góry każdej z twoich funkcji i wolisz

if false then 
begin
  result := bar;

   ... 
end
else
   result := foo;

i po prostu exitcałkowicie unikajcie .


2
Z nowszych Delphis, może być skrócona do if true then Exit(foo);używam często technikę najpierw zainicjować resultdo nillub FALSEodpowiednio, a następnie sprawdzając wszystkie warunki błędach i tylko Exit;jeśli jest spełniony. Przypadek sukcesu resultjest następnie (zwykle) gdzieś na końcu metody.
JensG

tak, podoba mi się ta nowa funkcja, chociaż wydaje się, że to po prostu cukierek, aby uspokoić programistów Java, następną rzeczą, o której wiesz, że pozwolą nam zdefiniować nasze zmienne w ramach procedur.
Peter Turner

I programiści C #. Tak, szczerze mówiąc, uznałbym to za przydatne, ponieważ zmniejsza liczbę wierszy między deklaracją a użyciem (IIRC ma nawet pewną miarę, zapomniałem nazwy).
JensG

2

Zgadzam się z następującym oświadczeniem:

Osobiście jestem fanem klauzul ochronnych (drugi przykład), ponieważ zmniejsza wcięcie funkcji. Niektórzy ludzie ich nie lubią, ponieważ powoduje to wiele punktów zwrotnych z funkcji, ale myślę, że jest to dla nich bardziej zrozumiałe.

Zaczerpnięte z tego pytania w przepełnieniu stosu .


+1 za klauzule ochronne. Wolę również pozytywnie nastawionego strażnika, np .: powrót if (warunek = fałsz) w przeciwieństwie do powrotu if (! Warunek)
JustinC

1

Korzystam z wczesnych zwrotów prawie wyłącznie w dzisiejszych czasach, do skrajności. Piszę to

self = [super init];

if (self != nil)
{
    // your code here
}

return self;

tak jak

self = [super init];
if (!self)
    return;

// your code here

return self;

ale to naprawdę nie ma znaczenia. Jeśli masz więcej niż jeden lub dwa poziomy zagnieżdżania w swoich funkcjach, należy je wyeliminować.


Zgadzam się. Wcięcie powoduje problemy z czytaniem. Im mniej wcięcia, tym lepiej. Twój prosty przykład ma ten sam poziom wcięcia, ale ten pierwszy przykład z pewnością przerośnie w więcej wcięć, które wymagają większej mocy mózgu.
user441521

1

Wolałbym pisać:

if(someCondition)
{
    SomeFunction();
}

2
eww. Czy to wstępne sprawdzenie poprawności? A może jest to mehtod poświęcony walidacji DoSomeFunctionIfSomeCondition?
STW

Jak to rozdziela twoje obawy?
justkt

2
Naruszasz enkapsulację, czyniąc implementację funkcji (jej logikę zależności) zewnętrzną.
TMN

1
Jeśli jest to uruchamiane w metodzie publicznej, a SomeFunction () jest metodą prywatną w tej samej klasie, może być w porządku. Ale jeśli ktoś inny wywołuje SomeFunction (), musiałbyś zduplikować kontrole. Uważam, że lepiej jest, aby każda metoda zadbała o to, czego potrzebuje, aby wykonać swoją pracę, nikt inny nie powinien tego wiedzieć.
Per Wiklander,

2
Jest to zdecydowanie styl, który Robert C. Martin proponuje w „Clean Code”. Funkcja powinna robić tylko jedną rzecz. Jednak w „Wzorach wdrażania” Kent Beck sugeruje, że z dwóch opcji zaproponowanych w PO druga jest lepsza.
Scott Whitlock,

1

Tak jak ty, zwykle piszę pierwszy, ale wolę ostatni. Jeśli mam dużo zagnieżdżonych czeków, zwykle refaktoryzuję się do drugiej metody.

Nie podoba mi się, jak obsługa błędów jest odsuwana od kontroli.

if not error A
  if not error B
    if not error C
      // do something
    else handle error C
  else handle error B
else handle error A

Wolę to:

if error A
  handle error A; return
if error B
  handle error B; return
if error C
  handle error C; return

// do something

0

Warunki na górze nazywane są „warunkami wstępnymi”. Stawiając if(!precond) return;, wizualnie wymieniasz wszystkie warunki wstępne.

Użycie dużego bloku „jeśli-inaczej” może zwiększyć obciążenie wcięcia (zapomniałem cytatu o wcięciach 3-poziomowych).


2
Co? Nie możesz zrobić wczesnego powrotu w Javie? C #, VB (.NET i 6) i najwyraźniej Java (co, jak zakładałem, zrobiły, ale musiały spojrzeć w górę, ponieważ nie używałem tego języka od 15 lat) wszystkie pozwalają na wczesne powroty. więc nie oskarżaj „silnie pisanych języków” o brak tej funkcji. stackoverflow.com/questions/884429/…
ps2goat

-1

Wolę zachować, jeśli stwierdzenia są małe.

Wybierając między:

if condition:
   line1
   line2
    ...
   line-n

i

if not condition: return

line1
line2
 ...
line-n

Wybrałbym to, co opisałeś jako „wczesny powrót”.

Pamiętaj, że nie dbam o wczesne zwroty lub cokolwiek innego, po prostu bardzo lubię uprościć kod, skrócić treść instrukcji if itp.

Zagnieżdżone jeśli i dla i podczas gdy są okropne , unikaj ich za wszelką cenę.


-2

Jak mówią inni, to zależy. W przypadku małych funkcji, które zwracają wartości, mogę kodować wcześniejsze zwroty. Ale w przypadku funkcji o dużych rozmiarach zawsze lubię mieć miejsce w kodzie, w którym wiem, że mogę umieścić coś, co zostanie wykonane przed jego zwróceniem.


-2

Szybko ćwiczę na poziomie funkcji. Utrzymuje spójny i czysty kod (dla mnie i dla tych, z którymi pracowałem). Z tego powodu zawsze wracam wcześnie.

W przypadku niektórych często sprawdzanych warunków możesz zaimplementować aspekty tych kontroli, jeśli używasz AOP.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.