Stylizacja złożonego AND / OR instrukcji if

13

Jak stylizujesz złożone złożone ORAZ / LUB jeśli instrukcje dla maksymalnej czytelności? Jak wcinasz i gdzie umieszczasz łamanie linii? Moja szczególna sytuacja wygląda następująco. To zdecydowanie lepsze niż rozbijanie wszystkiego w jednym wierszu, ale nadal wygląda na bałagan.

if (
    (
        x == y
        && a != b
        && p.isGood() 
        && (
            i + u == b
            || q >= a
        )
    )
    || k.isSomething()
    || m > n
) {
    doSomething();
}
JoJo
źródło
1
Czy głupie wcięcia i nawiasy / struktura nawiasów są zamierzone czy stanowią część tego stylu?
Ed S.
Zabawny. Zadałem to samo pytanie na SO tydzień temu i zostało zamknięte. Cieszę się, że gdzieś to pytanie żyje!
Eric Belair,

Odpowiedzi:

6

Twórz zmienne boolowskie dla każdego małego kroku:

bool step1 = i + u == b || q >= a;
bool step2 = a != b && p.isGood() && group1;
bool step3 = group2 || k.isSomething() || m > n;
if (step3) { doSomething(); }

Jest to oczywiście podobne do odpowiedzi Lacrymology, z wyjątkiem różnych nazw dla każdego kroku.

Jeśli nazwa step1, step2i step3w taki sposób, że tworzą dobry koncepcyjne sens, powinno to być zdecydowanie najbardziej czytelne. p.isGood()i k.isSomething()czasami może zostać wywołany w sytuacjach, w których nie byłoby go w oryginalnym kodzie, więc nie byłaby to opcja, jeśli te funkcje są drogie lub jeśli uruchamiasz ten kod w bardzo ciasnej pętli.

Z drugiej strony nie musisz się martwić spadkiem wydajności, jaki może ponieść tworzenie nowych zmiennych; dobry kompilator zoptymalizuje je.

Przykład z wykrywaniem kolizji prostokąta (którego prawdopodobnie nie użyłbyś ze względu na wspomniane wcześniej pogorszenie wydajności):

if((a.x + a.width >= b.x || b.x + b.width >= a.x)
 && (a.y + a.height >= b.y || b.y + b.width >= a.y)
)
{ collision(); }

Może stać się:

bool horizMatch = a.x + a.width >= b.x || b.x + b.width >= a.x;
bool vertMatch = a.y + a.height >= b.y || b.y + b.width >= a.y;
if(horizMatch && vertMatch) { collision(); }

Ponadto, jeśli chcesz zostawić swój kod bez zmian, myślę, że byłoby to również w porządku. Myślę, że twój kod jest dość czytelny. Oczywiście nie wiem, czym dokładnie a b x y i u p k m nsą, ale jeśli chodzi o strukturę, wygląda mi to dobrze.

Rei Miyasaka
źródło
8

Zazwyczaj zmieniam swój kod na bardziej modułowy, jeśli moje warunki warunkowe komplikują się.

JohnFx
źródło
W tej sytuacji unikałbym refrakcji. Głupio byłoby testować takie małe funkcje w izolacji, a posiadanie definicji wiszących poza funkcją sprawia, że ​​kod jest mniej oczywisty.
Rei Miyasaka,
To zależy od tego, jak dobrze refaktoryzujesz. Twierdzę, że to sprawia, że ​​twój kod jest WIĘCEJ oczywisty, że ma sensowne nazwy funkcji zamiast ciągu znaków warunkowych, które wyglądają jak podręcznik algebry, który jest wyrzucony w twoim kodzie.
JohnFx,
Jednak wizualnie miałbyś swoje funkcje wiszące na zewnątrz i nie byłoby od razu oczywiste, co dokładnie robi ta funkcja. Przez chwilę jest to czarna skrzynka, dopóki nie przewiniesz w górę. Chyba że jesteś w języku, który pozwala na funkcje w funkcjach, nie sądzę, że byłoby to w ogóle wygodne dla czytelnika lub pisarza, niezależnie od tego, jak dobrze refraktor. A jeśli jesteś w języku, który pozwala na funkcje w funkcjach, istnieje prawdopodobieństwo, że składnia nie różni się niczym od zadeklarowania wiązania lub zmiennej zamiast, np . let x = a > bLub let f a b = a > b.
Rei Miyasaka,
Zmienne działałyby równie dobrze. Rozważyłbym to również refaktoryzację.
JohnFx,
Ah, dobrze.
Rei Miyasaka,
8

Na tym poziomie złożoności zrobiłbym coś więcej

bool doIt = x == y && a != b && p.isGood();
doIt &= ( i + u == b || q >= a);
doIt |= k.isSomething() || m > n;

if(doIt)
{
    doSomething();
}

jest brzydki, ale czytelny i jestem pewien, że kompilator będzie wiedział, jak go refaktoryzować.

Z drugiej strony, jeśli kiedykolwiek zobaczę siebie w sytuacji, gdy piszę takie oświadczenie IF, ponownie zastanawiam się nad rozwiązaniem, ponieważ JESTEM PEWNY, że istnieje sposób na uproszczenie go lub przynajmniej wyodrębnienie niektórych z tych warunków (np. Może x == y && a != b && p.isGood()naprawdę po prostu znaczy this->isPolygon()i mogę zrobić tę metodę;

Lacrymology
źródło
4

Z czasem coraz mniej mam obsesję na punkcie wyrównania w pionie, ale moja ogólna postać z wyrażeniami wieloliniowymi to ...

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6)
       )
    && (   (expr7 == expr8)
        || (expr9 == expra)
       )
   )
{
  blah;
}

Kluczowe punkty...

  • Zamknięte pareny wyrównują się pionowo z otwartymi parens, tak jak z aparatami ortodontycznymi.
  • Podwyrażenia pasujące do jednej linii znajdują się w jednej linii i są wyrównane w pionie po lewej stronie. Tam, gdzie poprawia to czytelność, operatory infix w tych częściach jednowierszowych są również wyrównane w pionie.
  • Zamykające szelki naturalnie tworzą prawie puste linie, pomagając wizualnie grupować rzeczy.

Czasami sformatuję +i / *lub niektórzy inni operatorzy też to lubią. Całkiem kilka złożonych wyrażeń przyjmuje postać sumy produktu lub iloczynu sumy (która może odnosić się do boolowskich „sum” i „produktów”), więc prawdopodobnie jest na tyle powszechna, że ​​warto stosować spójny styl.

Uważaj jednak na to. Często lepiej jest zrefaktoryzować (przenieść części wyrażenia do funkcji lub obliczyć i zapisać części pośrednie w zmiennej), zamiast używać wcięcia, aby uczynić nadmiernie złożone wyrażenie bardziej czytelnym.

Jeśli wolisz układać swoje ścisłe pareny po prawej stronie, nie nienawidzę tego, ale myślę, że nie jest tak źle. Mówiąc zbyt daleko, ryzykujesz, że błąd może sprawić, że wcięcie będzie mylnie interpretować to, co robią nawiasy.

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6))

    && (   (expr7 == expr8)
        || (expr9 == expra)))
{
  blah;
}
Steve314
źródło
+1 Podoba mi się twoja stylizacja. Odpowiadało bezpośrednio na moje pytanie, ale myślę, że Rei Miyasaka przybił sedno problemu. Jeśli kiedykolwiek będę leniwy, by zastosować metodę Rei, użyję twojej stylizacji.
JoJo
Wow, to naprawdę miłe.
Rei Miyasaka,
1

http://www.codinghorror.com/blog/2006/01/flattening-arrow-code.html

Zgadzam się z odpowiedzią JohnFx, a także odpowiedzią Lacrymology. Zbudowałbym zestaw funkcji (najlepiej statycznych), które osiągają małe cele, a następnie rozbudowałem je w inteligentny sposób.

A co powiesz na coś takiego? Uwaga: nie jest to idealne rozwiązanie, ale działa. Istnieją sposoby na dalsze oczyszczenie tego, ale potrzebne są bardziej szczegółowe informacje. Uwaga: ten kod powinien działać równie szybko, ponieważ kompilator jest inteligentny.

// Currently based on members or global vars
// (which is often a bad idea too)
function doSomethingCondirionally()
{
  if (k.isSomething() || m > n)
  {
    doSomething();
    return;
  }

  // Else ... 
  if (x != y) return;
  if (a == b) return;
  if (!p.isGood()) return;

  // Final, positive check
  if (i + u == b || q >= a)
  {
    doSomething();
  }
}
Praca
źródło
Jeśli posiadanie tylko jednego punktu wyjścia z funkcji jest czymś, co cenisz (np. Jeśli używasz języka funkcjonalnego), może to nie być najlepsza opcja, a nawet dostępna. To powiedziawszy, tak, często to robię.
Rei Miyasaka,
Co jeśli jest to język interpretowany jak Javascript?
JoJo
@ Rei Miyasaka, jak bardzo to cenię, zależy od języka. Chociaż lubię rodzinę języków LISP, nie musiałem używać żadnego z nich w pracy. Jeśli będę musiał ponownie uwzględnić czyjąś funkcję, ale nie dotknę innego kodu (często rzeczywistości), zrobiłbym coś takiego jak wyżej. Jeśli mogę napisać / przepisać tę logikę od podstaw, moje podejście będzie inne, ale nie mogę napisać takiego kodu bez konkretnego przykładu tego, co autor próbuje tutaj zrobić.
Job
1
@Rei Miyasaka, ta osoba może być geniuszem lub być gównem. Nie wiem wszystkiego, ale byłbym ciekawy, jak ta osoba broni punktu pojedynczego wyjścia. Dyskutuje się o tym tutaj i na SO, i mam wrażenie, że takie podejście było popularne wśród naukowców w latach 80., ale już nie ma znaczenia i może utrudniać czytelność. Oczywiście, jeśli robisz wszystko w stylu funkcjonalnym LINQ, ten problem nawet by się nie pojawił.
Job
2
@ Job @Steve Myślę, że jest to ważniejsza wskazówka w językach, które wymagają wyraźnego zwolnienia. Oczywiście nie dla każdej funkcji, ale prawdopodobnie jest to nawyk, że początkujący programiści byli zachęcani do trzymania się, aby uniknąć zapominania o wolnych zasobach.
Rei Miyasaka,
1

Z powodu tego, co jest warte, zdziwiłem się, widząc, że twój przykład przypomina skomplikowane predykaty, które napisałem. Zgadzam się z innymi, że skomplikowany predykat nie jest najlepszy ze względu na łatwość utrzymania lub czytelność, ale czasami pojawiają się.

Chciałbym podkreślić, że poprawiłeś tę część: && a != b NIGDY nie umieszczaj złącza logicznego na końcu linii, zbyt łatwo jest przeoczyć wizualnie. Innym miejscem, w którym NIGDY nie należy umieszczać operatora na końcu wiersza, jest konkatenacja łańcuchów, w językach z takim operatorem.

Zrób to:

String a = b
   + "something"
   + c
   ;

Nie rób tego:

String a = b +
   "something" +
   c;
Bruce Ediger
źródło
Czy masz jakieś logiki lub badania potwierdzające twoje twierdzenie o logicznych łącznikach, czy jest to tylko twoja preferencja określona jako fakt? Słyszałem to wiele w różnych miejscach i nigdy tego nie rozumiałem (w przeciwieństwie do warunkowych yoda, które mają ważny [jeśli wprowadzono w błąd] powód).
Caleb Huitt - cjhuitt
@Caleb - Matematyka była składana w ten sposób od stuleci. Podczas przeglądania kodu skupiamy się na lewej stronie każdej linii. Linie zaczynające się od operatora są oczywiście kontynuacją poprzedniej linii, a nie niepoprawnie wciętą nową instrukcją.
kevin cline
Lubię też prefiks operatorów matematycznych. Uświadomiłem sobie, że to za późno w mojej karierze programistycznej :)
JoJo,
0

Jeśli warunek jest tak skomplikowany, zwykle oznacza to, że należy go podzielić na części. Być może jedna klauzula może być przypisana do zmiennej pośredniej. Być może jedna klauzula może zostać przekształcona w metodę pomocniczą. Zasadniczo wolę nie mieć tak wielu znaków „i” w jednej linii.

Zhehao Mao
źródło
0

Możesz podzielić kod na wiele instrukcji, co ułatwi zrozumienie. Ale prawdziwy ninja zrobiłby coś takiego. :-)

if
(
    (
        x == y
    &&
        a != b
    &&
        p.isGood()
    &&
        (
            i + u == b
        ||
            q >= a
        )
    )
||
    k.isSomething()
||
    m > n
)
{
    doSomething();
}
Daniel Lubarov
źródło
5
Jestem fanem białych znaków, ale jak na mój gust jest to nadmiernie wypełnione prawie pustymi liniami.
Steve314,