Operator trójskładnikowy uważany za szkodliwy? [Zamknięte]


79

Na przykład, wolisz ten jednowarstwowy

int median(int a, int b, int c) {
    return (a<b) ? (b<c) ? b : (a<c) ? c : a : (a<c) ? a : (b<c) ? c : b;
}

lub rozwiązanie if / else obejmujące wiele instrukcji zwrotu?

Kiedy jest to ?:właściwe, a kiedy nie? Czy należy go uczyć czy ukrywać przed początkującymi?


221
To szczególne zastosowanie: :)
karmajunkie,

6
Kto to kodował i jak wygląda ich wersja mediany dla czterech liczb? A może pięć?
Mason Wheeler,

3
Bardziej poprawna nazwa to „operator warunkowy”. Zdarza się, że jest to najczęściej używany operator trójskładnikowy.
Alan Pearce

1
To pytanie zostało zadane ponad dwa lata temu przy przepełnieniu stosu. Czy teraz zapytamy o wszystko tutaj? stackoverflow.com/questions/160218/to-ternary-or-not-to-ternary
webbiedave

3
Jestem zaskoczony, dlaczego takie pytania wciąż się pojawiają. Odpowiedź zawsze brzmi „cokolwiek działa i jest czytelne”. - ostatni jest równie ważny.
Apoorv Khurasia

Odpowiedzi:


234

Czy operator trójskładnikowy jest zły?

Nie, to błogosławieństwo.

Kiedy jest: odpowiednie?

Gdy jest to coś tak prostego, że nie chcesz marnować wielu linii.

a kiedy nie jest?

Gdy cierpi czytelność i przejrzystość kodu, a prawdopodobieństwo błędu z powodu niewystarczającej uwagi wzrasta, na przykład, w przypadku wielu operatorów łańcuchowych, tak jak w twoim przykładzie.


Test lakmusowy ma miejsce, gdy zaczynasz wątpić, czy Twój kod jest łatwy do odczytania i utrzymania na dłuższą metę. Więc nie rób tego.


23
+1 dla Kiedy cierpi czytelność i przejrzystość kodu. Z wieloma powiązanymi operatorami, tak jak w twoim przykładzie. Przykład zajmuje więcej czasu niż zrozumienie, jeśli jest to równoważne.
zmiany

23
+1 brawo za świetne wyjaśnienie! Programiści nie zdają sobie sprawy z tego, że niektóre rzeczy są wezwaniami do oceny, chcą, aby wszystko było czarno-białe. Doprowadza mnie to do szału. Wiele osób spotkałem się z opinią „X jest złe, nigdy nie używajmy go”. Wolę „X jest świetny, jeśli używasz go do tego, w czym jest dobry”.
Doktor Jones

54
Jest to również zło, jeśli jest używany: myVar = (someExpression)? prawda fałsz; Aaaaarrgh!
adamk

20
@adamk: Spróbuj tego ze złem:myVar = someExpression ? false : true;
Dean Harding

8
Co powiesz na (someExpression ? var1 : var2)++:-)
fredoverflow

50

Myślę, że nieskrócony operator trójskładnikowy (tj. Instrukcja, w której jest używany tylko raz) jest w porządku, ale jeśli zagnieżdżasz więcej niż jeden, staje się on nieco trudny do odczytania.


3
Może to być uważane za nadmierne uproszczenie, ale jest to bardzo łatwa wytyczna i w większości przypadków działa.
Alan Pearce,

2
To właściwie moja podstawowa zasada: nigdy nie powinieneś ich zagnieżdżać, w przeciwnym razie zastąp je słowami if / else, będzie to w ten sposób jaśniejsze.
Piovezan

Jeśli użyjesz ich i zagnieżdżymy, to dla miłości ludzkości użyj nawiasów i białych znaków, aby były czytelne. Jeśli-można zrobić tak samo brzydko. Oprzyj się chęci pokazania, jak jesteś „mądry”, pisząc rzeczy, które kompilatory potrafią czytać, a ludzie nie. Pewnego dnia będziesz człowiekiem, który nie może.
candied_orange

24

Kiedy jest?: Odpowiednie

  • Kiedy sprawia, że ​​kod jest bardziej zwięzły i czytelny.

a kiedy nie jest?

  • Kiedy sprawia, że ​​twój kod jest nieczytelny.
  • Jeśli robisz to tylko po to, by zadowolić narzędzie refaktoryzacji, takie jak ReSharper, a nie osobę, która musi utrzymywać kod

Jeśli masz jakieś wywołania logiczne lub funkcyjne w wyrażeniu potrójnym, to sprawia, że ​​patrzenie na nie jest okropne.


Ten zasługuje na wiele pochwał!
Piovezan

22

Jedną z różnic, których (jak sądzę) nikt nie zauważył, jest to, że jeśli-inaczej nie może zwrócić wartości, podczas gdy operator potrójny może.

Pochodząc z F #, czasami lubię używać trójskładnikowego operatora do naśladowania dopasowania wzorca.

match val with
| A -> 1
| B -> 3
| _ -> 0

vs

return val == A ? 1 : 
       val == B ? 3 : 
       0;

To fajnie. Nigdy o tym nie myślałem.
Rei Miyasaka,

+1 @Benjol: Chciałem zwrócić uwagę na to samo (że w F # wszystko jest wyrażeniem, w tym if / elif / else). Używam trójskładników jak w twoim przykładzie, jak dotąd również nieodkrytych :). Kolejną rzeczą, którą robiłem w Javascript, być może ponad, jest: var res = function() {switch(input) {case 1: return "1"; case 2: return "2"; ...}}()emulowanie przełączników jako wyrażeń.
Stephen Swensen

@Stephen, zamierzałem użyć tych słów expressioni statementzawsze się martwię, że źle je zrozumiem i zrobię z siebie głupka :)
Benjol,

@Benjol: Wiem, co masz na myśli!
Stephen Swensen

6
Przydatny także w C i C ++ do inicjowania constzmiennych, których później nie można zmienić.
David Thornley,

13

Przykład (IMHO) prawidłowego użycia:

printf("Success in %d %s\n", nr_of_tries, (nr_of_tries == 1 ? "try" : "tries"));

Powoduje to, że kod jest bardziej czytelny niż posiadanie 2 różnych instrukcji drukowania. Zagnieżdżone przykłady zależą: (zrozumiałe? Tak: nie)


11
Pamiętaj, że jeśli to zrobisz, to piekło dla każdego, kto musi zlokalizować twoją aplikację. Oczywiście, jeśli to nie jest problem, śmiało.
Anon.

1
To moja podstawowa zasada: 1 poziom zagnieżdżenia (w niektórych okolicznościach).
Oliver Weiler,

7

Absolutnie nie zło. W rzeczywistości jest czysty , a jeśli tak, to nie jest.

W językach funkcjonalnych, takich jak Haskell, F #, ML itp., To stwierdzenia typu „jeśli to inaczej” są uważane za złe.

Powodem tego jest to, że każda „akcja”, taka jak bezwzględna instrukcja if-then-else, wymaga oddzielenia deklaracji zmiennej od jej definicji i wprowadza stan do funkcji.

Na przykład w następującym kodzie:

const var x = n % 3 == 1
    ? Parity.Even
    : Parity.Odd;

vs.

Parity x;
if (n % 3 == 1)
    x = Parity.Even;
else
    x = Parity.Odd;

Pierwszy ma dwie zalety oprócz tego, że jest krótszy:

  1. x jest stały, a zatem oferuje znacznie mniejszą szansę na wprowadzenie błędów i może zostać zoptymalizowany w sposób, w jaki drugi nigdy nie mógłby być.
  2. Wyrażenie wyjaśnia ten typ, więc kompilator może bez trudu wywnioskować, że xmusi on być typu Parity.

Mylące jest to, że w językach funkcjonalnych operator trójskładnikowy jest często nazywany „jeśli-to”. W Haskell można powiedzieć x = if n mod 3 == 1 then Odd else Even.


tak, to jest również punkt @Benjol. Zobacz mój komentarz do jego odpowiedzi na fajny sposób emulacji instrukcji switch jako wyrażeń w JavaScript.
Stephen Swensen

7

Ten szczególny wyraz sprawia, że ​​bolą mnie oczy; Zwalczę dowolnego programistę w moim zespole, który go użył, ponieważ jest nie do utrzymania.

Operatory trójskładnikowe nie są złe, gdy są dobrze stosowane. Nie muszą nawet być pojedynczymi liniami; Długi, dobrze sformatowany, może być bardzo jasny i łatwy do zrozumienia:

return
      ( 'a' == $s ) ? 1
    : ( 'b' == $s ) ? 2
    : ( 'c' == $s ) ? 3
    :                 4;

Podoba mi się to bardziej niż ekwiwalent łańcucha if / then / else:

if ( 'a' == $s ) {
    $retval = 1;
}
elsif ( 'b' == $s ) {
    $retval = 2;
}
elsif ( 'c' == $s ) {
    $retval = 3;
}
else {
    $retval = 4;
}

return $retval;

Przeformatuję je w:

if    ( 'a' == $s ) { $retval = 1; }
elsif ( 'b' == $s ) { $retval = 2; }
elsif ( 'c' == $s ) { $retval = 3; }
else                { $retval = 4; }

return $retval;

jeśli warunki i zadania pozwalają na łatwe wyrównanie. Nadal wolę wersję trójskładnikową, ponieważ jest krótsza i nie ma tak dużo hałasu wokół warunków i zadań.


Dlaczego nie mogę wstawiać podziałów wierszy w komentarzach? Arrgghh!
Christopher Mahan

3

ReSharper w VS.NET czasami sugeruje zastąpienie if...elsego ?:operatorem.

Wydaje się, że ReSharper sugeruje tylko wtedy, gdy warunki / bloki są poniżej pewnego poziomu złożoności, w przeciwnym razie będzie się trzymać if...else.


4
kolejna świetna funkcja, którą uwielbiam w ReSharper
Anonimowy typ

2

Można to przeformatować, aby wyglądało tak ładnie, jak kombinacja if / else:

int median(int a, int b, int c)
{
    return
        (a<b)
        ?
            (b<c)
            ? b
            :
                (a<c)
                ? c
                : a
        :
            (a<c)
            ? a
            :
                (b<c)
                ? c
                : b;
}

Problem polega jednak na tym, że nie jestem do końca pewien, czy dostałem prawo do wgłębienia do przedstawienia tego, co się naprawdę wydarzy. :-)


3
+1 Myślę, że jest to znacznie lepsze niż równoważna if-elsestruktura. Kluczem jest formatowanie.
Orbling

13
Gdybym zobaczył to w bazie kodu, poważnie rozważyłbym poszukiwanie nowej pracy.
Nick Larsen,

+1 Nie dla tego rzeczywistego wcięcia, ale jest to najlepsze rozwiązanie dla tego przykładu.
Mark Hurd

2
Tylko że inny przypadkowy automatyczne formatowanie by wymazać wszystkie te wcięcia i czas na kolejną rundę restrukturyzacji - :) niezwykle produktywny
Nawfal

2

Potrójny operator nie jest złem, ale jest darem niebios.

  • Jest to najbardziej przydatne, gdy chcesz podjąć decyzję w wyrażeniu zagnieżdżonym . Klasycznym przykładem jest wywołanie funkcji:

    printf("I see %d evil construct%s in this program\n", n, n == 1 ? "" : "s");
    
  • W twoim konkretnym przykładzie trójka jest prawie nieuzasadniona, ponieważ jest wyrażeniem najwyższego poziomu w return. Możesz podnieść warunek do poziomu instrukcji, nie powielając niczego innego niż returnsłowo kluczowe.

Uwaga: Nic nigdy nie sprawi, że ten konkretny algorytm mediany będzie łatwy do odczytania.


Trudno być tak czytelnym, że nie można tego poprawić. printf("I see %d evil construct%s in this program\n", n, "s" unless (n == 1) "s");
Pacerier

2
  1. Pomijając argumenty „zła”, z mojego doświadczenia wynika, że ​​istnieje duża korelacja między użytkowaniem przez programistę operatora trójskładnikowego a prawdopodobieństwem, że cała jego baza kodowa będzie trudna do odczytania, śledzenia i utrzymywania (jeśli nie wręcz nieudokumentowana). Jeśli programista jest bardziej zainteresowany zapisaniem kilku linii 1-2 znaków niż ktoś, kto jest w stanie zrozumieć jego kod, wówczas wszelkie drobne nieporozumienia związane ze zrozumieniem trójki są zazwyczaj wierzchołkiem góry lodowej.

  2. Trójskładnikowi przyciągają magiczne liczby, takie jak s ** t przyciąga muchy.

Gdybym szukał biblioteki Open Source, aby rozwiązać konkretny problem, i zobaczyłem kod, taki jak trójskładnikowi oryginalnego plakatu, kandydata na tę bibliotekę, dzwonki ostrzegawcze zaczęłyby bić mi w głowie i zastanowić się nad przejściem do jakiegoś innego projektu, z którego można pożyczyć.


2

Oto przykład, kiedy jest zły:

oldValue = newValue >= 0 ? newValue : oldValue;

Jest to mylące i marnotrawstwo. Kompilator może zoptymalizować drugie wyrażenie (oldValue = oldValue), ale dlaczego programista zrobił to w pierwszej kolejności?

Kolejny doozy:

thingie = otherThingie != null ? otherThingie : null;

Niektórzy ludzie nie są po prostu programistami ...

Greg twierdzi, że odpowiednik instrukcji if jest „głośny”. Jest tak, jeśli piszesz to głośno. Ale to samo, jeśli można zapisać jako:

if ('a' == $s) return 1;
if ('b' == $s) return 2;
if ('c' == $s) return 3;
return 4;

Który nie jest głośniejszy niż trójskładnikowy. Zastanawiam się, czy trójskładnikowe skróty; czy wszystkie wyrażenia są oceniane?


Twój drugi przykład przypomina mi if (x != 0) x = 0;...
fredoverflow

2

Zło? Spójrz, są po prostu inne.

ifjest stwierdzeniem. (test ? a : b)jest wyrażeniem. To nie to samo.

Istnieją wyrażenia, które wyrażają wartości. Istnieją instrukcje do wykonywania działań. Wyrażenia mogą pojawiać się w instrukcjach, ale nie odwrotnie. Możesz więc używać wyrażeń potrójnych w innych wyrażeniach, takich jak terminy w podsumowaniu lub argumenty metody, i tak dalej. Nie musisz , ale możesz, jeśli chcesz . Nie ma w tym nic złego. Niektórzy ludzie mogą powiedzieć, że to zło, ale taka jest ich opinia.

Jedną z wartości wyrażenia potrójnego jest to, że pozwala ci obsługiwać zarówno przypadki prawdziwe, jak i fałszywe. ifoświadczenia nie.

Jeśli martwisz się o czytelność, możesz sformatować je w czytelny sposób.

Jakoś „zło” wkradło się do słownictwa programistycznego. Chciałbym wiedzieć, kto pierwszy to upuścił. (Właściwie mam podejrzanego - on jest w MIT.) Wolałbym mieć obiektywne powody, by oceniać wartości w tej dziedzinie, a nie tylko gust i imię ludzi.


1
Czy mogę podpowiedzieć, kim jest podejrzany? Żeby trochę poprawić moją wiedzę w tej dziedzinie.
mlvljr

1
@mlvljr: Cóż, mogę się mylić, więc lepiej tego nie robić.
Mike Dunlavey

1

Ma miejsce. Pracowałem w wielu firmach, w których poziomy umiejętności programistów wahają się od strasznych do czarodziei. Ponieważ kod musi być utrzymany, a ja nie będę tam na zawsze, staram się pisać rzeczy tak, aby wyglądały, jakby do nich należały (bez patrzenia na komentarze z moimi inicjałami, jest niezwykle rzadkie, abyś mógł spójrz na kod, nad którym pracowałem, aby zobaczyć, gdzie wprowadziłem zmiany) i że ktoś z mniejszymi umiejętnościami niż ja może go utrzymać.

Podczas gdy operator trójskładnikowy wygląda niesamowicie i jest całkiem fajny, z mojego doświadczenia wynika, że ​​linia kodu będzie niemożliwa do utrzymania. U mojego obecnego pracodawcy mamy produkty, które są wysyłane od prawie 20 lat. Nigdzie nie użyłbym tego przykładu.


1

Nie sądzę, aby operator potrójny był zły.

Oto gotcha, która mnie zaskoczyła. Byłem programistą C dla wielu (10+) i pod koniec lat 90. przeszedłem do programowania aplikacji internetowych. Jako programista internetowy wkrótce natknąłem się na PHP, który ma również trójskładnikowego operatora. Miałem błąd w programie PHP, który w końcu prześledziłem do linii kodu za pomocą zagnieżdżonego operatora trójskładnikowego. Okazuje się, że operator trójskładnikowy PHP jest skojarzony od lewej do prawej, ale operator trójskładnikowy C (do którego byłem przyzwyczajony) kojarzy od prawej do lewej.


1

Wszystko, co czyni brzydszy kod, jest złe.

Jeśli używasz trójskładnikowego, aby twój kod był czystszy, z pewnością go użyj. Czasami w php świetnie jest robić wbudowane podstawienia, np

"Hello ".($Male?"Mr":"Ms")." $Name

To oszczędza kilka linii i jest całkiem jasne, ale twój przykład wymaga co najmniej lepszego formatowania, aby był wyraźny, a trójskładnik nie jest tak naprawdę dobry dla wielu linii, to równie dobrze możesz użyć if / else.


1

Czy mogę to powiedzieć? Nie mogę znaleźć tego konkretnego zastosowania potrójnej operacji zła :

  1. operacja, którą wykonuje, jest dość trywialna, a kiedy przejdziesz przez nią, gdy jest to prawie niemożliwe, pojawią się błędy;
  2. to, co robi, jest wyraźnie określone w nazwie funkcji;
  3. przyjmowanie> 1 linii dla czegoś tak oczywistego i tak wyraźnie, że nie zostanie poprawione w przyszłości (chyba że algorytm magicznej mediany żył do tej pory niewykryty).

Proszę, zmiłuj się, moja reputacja jest już dość żałosna.


1

Największa wygrana: pokazanie, że istnieje jeden cel działania.

if ( $is_whatever )
    $foo = 'A';
else
    $foo = 'B';

Istnieją dwie ścieżki kodu, którymi można podążać, a czytelnik musi uważnie przeczytać, aby sprawdzić, które dwie zmienne są ustawiane. W tym przypadku jest to tylko jedna zmienna, ale czytnik musi przeczytać więcej, aby to zrozumieć. W końcu mogło być tak:

if ( $is_whatever )
    $foo = 'A';
else
    $bar = 'B';

Dzięki operatorowi trójskładnikowemu jest jasne, że ustawiana jest tylko jedna zmienna.

$foo = $is_whatever ? 'A' : 'B';

Na najniższym poziomie zasada DRY (Don't Repeat Yourself) jest najbardziej podstawowa. Jeśli możesz podać $footylko raz, zrób to.


0

Jeśli ... to ... inaczej kładzie nacisk na warunek i dlatego nie uwypukla operacji wykonywanych warunkowo.

operator trójskładnikowy jest przeciwny, ma tendencję do ukrywania warunku i dlatego jest użyteczny, gdy wykonywana operacja jest ważniejsza niż sam warunek.

W niektórych językach istnieje niewielki problem techniczny, polegający na tym, że nie są one do końca wymienne, ponieważ jedno jest instrukcją, a drugie wyrażeniem, np. Warunkowo inicjuje const w C ++


0

Kiedy jest to właściwe, a kiedy nie?

Myślę, że przy opracowywaniu dla jednorodnej grupy ludzi nie ma problemu, ale kiedy masz do czynienia z osobami, które obsługują różne poziomy, ten rodzaj onelinerów wprowadza tylko dodatkowy poziom złożoności do kodu. Tak więc moja polityka w tej sprawie jest następująca: kod wyczyść i nie wyjaśniaj zamiast kodu krótkiego i wyjaśnij 123123 razy.

Czy należy go uczyć czy ukrywać przed początkującymi?

Nie powinienem być uczony początkującym, raczej wybieraj go, aby zrozumiał, kiedy pojawi się potrzeba, więc będzie używany tylko wtedy, gdy będzie to konieczne, a nie za każdym razem, gdy będziesz potrzebować „if”.


0

IMO, sam operator nie jest zły, ale składnia zastosowana w C (i C ++) jest zbyt zwięzła. IMO, Algol 60 zrobił to lepiej, więc coś takiego:

A = x == y ? B : C;

wyglądałby mniej więcej tak (ale ogólnie trzymając się składni podobnej do C):

A = if (x==y) B else C;

Nawet przy tym nadmiernie głębokie zagnieżdżanie może prowadzić do problemów z czytelnością, ale przynajmniej A) każdy, kto w ogóle wykonał programowanie, może rozwiązać ten problem, a B) ludzie, którzy go rozumieją, radzą sobie z znacznie głębszym zagnieżdżaniem. OTOH, chciałbym również zauważyć, że w LISP (na przykład) a condjest prawie jak instrukcja trójskładnikowa - nie zestaw instrukcji, ale pojedyncze wyrażenie daje wartość (wtedy znowu większość LISP jest taka ... .)


Dlaczego nie zrobić tego po prostu dla czytelności? A = (x==y) ? B : C
Jeremy Heiler

@Jeremy: chociaż niektórzy uważają, że pareny są przydatne, nawet w najlepszym wypadku nie pomagają zbytnio . Zagnieżdż więcej niż parę i nadal potrzebujesz dokładnego wcięcia (jako absolutnego minimum), aby wszystko uporządkować. Niewątpliwie to samo stanie się w końcu w Algolu, ale nigdy nie mówię, że pojawia się w nim problem, jak to często robię w C ...
Jerry Coffin

Po prostu założyłem, że wszyscy byli zgodni, że zagnieżdżenie operatora trójskładnikowego jest złe. Mówiłem konkretnie o podanych przez ciebie przykładach. W szczególności, jak pierwszy może być bardziej podobny do drugiego w większości języków.
Jeremy Heiler

0

Sklep, który regularnie pisze metody o numerach 600-1200, nie powinien mi mówić, że trójka jest „trudna do zrozumienia”. Żaden sklep, który regularnie dopuszcza pięć warunków do oceny gałęzi kodu, nie powinien mi powiedzieć, że konkretnie podsumowane warunki w trójce są „trudne do odczytania”.


0

Kiedy jest: odpowiednie, a kiedy nie?

  • Jeśli nie uzyskasz wzrostu wydajności, nie używaj go; wpływa na czytelność twojego kodu.
  • Użyj go raz i nie zagnieżdżaj go.
  • Trudniej jest debugować.

Czy należy go uczyć czy ukrywać przed początkującymi?

To nie ma znaczenia, ale nie powinno być celowo ukryte, ponieważ nie jest zbyt skomplikowane, aby „początkujący” mógł się uczyć.


-2

W twoim przykładzie:

def median(a, b, c):
    if a < b < c: return b
    if a < c < b: return c
    if b < a < c: return a
    if b < c < a: return c
    if c < a < b: return a
    if c < b < a: return b

jest bardzo prosty do odczytania i oczywisty. Zmienna pomiędzy <<to wartość zwracana.

Aktualizacja

to samo, ale mniej wierszy kodu. Myślę, że nadal jest prosty.

def median(a, b, c):
    if b<a<c or c<a<b: return a
    if a<b<c or c<b<a: return b
    if a<c<b or b<c<a: return c

Wymaga to 12 porównań w najgorszym przypadku ...
fredoverflow

1
Być może, ale jest to czytelne.
Christopher Mahan

-2

Jest to również konieczne dla const

const int nLegs  = isChicken ? 2: 4 ;

Dziwne. Myślę, że to C ++ czy coś takiego. Myślałem, że const zawsze był stałą czasową kompilacji (jak w C #)
nawfal

@nawfal - jeśli nie wiesz, to Kurczak do czasu wykonania
Martin Beckett

tak, to co. consttak myślę w niektórych językach. W C # constzawsze powinna być znana wartość czasu kompilacji. co oznacza, że const int nLegs = isChicken ? 2: 4 ;nie zadziała, ale const int nLegs = true ? 2: 4 ;będzie
nawfal 24.04.13
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.