Jaki jest cel używania nawiasów klamrowych (tj. {}) Dla pojedynczej linii if lub loop?

321

Czytam notatki z wykładu mojego wykładowcy C ++, który napisał:

  1. Użyj wcięcia // OK
  2. Nigdy nie polegaj na pierwszeństwie operatora - zawsze używaj nawiasów // OK
  3. Zawsze używaj bloku {} - nawet dla pojedynczej linii // nie OK , dlaczego ???
  4. Stały obiekt po lewej stronie porównania // OK
  5. Użyj bez znaku dla zmiennych, które są> = 0 // fajna sztuczka
  6. Ustaw wskaźnik na NULL po usunięciu - Podwójna ochrona przed usunięciem // nieźle

Trzecia technika nie jest dla mnie jasna: co zyskałbym, umieszczając jedną linię w { ... }?

Weźmy na przykład ten dziwny kod:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

i zamień na:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;

Jakie są zalety korzystania z 1. wersji?

JAN
źródło
256
Czytelność i łatwość konserwacji. Nie jest od razu oczywiste, do którego bloku instrukcji należy „j ++”, a dodawanie kodu po nim nie będzie powiązane z instrukcją if.
Las i drzewa
27
Zawsze kazano mi używać nawiasów klamrowych {} do tych linii z kilku powodów. Sprawia, że ​​kod jest bardziej czytelny. Również ktoś inny w ciągu sześciu miesięcy może potrzebować edycji kodu, więc jasność jest ważna, a przy nawiasach klamrowych prawdopodobieństwo wystąpienia błędu jest mniejsze. Nie ma w tym nic bardziej poprawnego technicznie, to tylko kwestia dobrej praktyki. Pamiętaj, że projekt może zawierać tysiące wierszy kodu dla jakiegoś nowego faceta!
RossC
30
Nie zgadzam się z 6, ponieważ ukryje to podwójne usunięcie i potencjalnie ukryje błędy logiczne.
Luchian Grigore
28
# 5 może być trudne - rozważenie tej pętli: for (unsigned i = 100; i >= 0; --i).
Archie
36
Btw, (i % 2 == 0)zaprzecza (2). Polegasz na pierwszeństwie operatora, a znaczenie jest oczywiście ((i % 2) == 0)raczej niż (i % (2 == 0)). Sklasyfikowałbym zasadę 2 jako „prawidłowy sentyment, ale„ zawsze ”jest zły”.
Steve Jessop,

Odpowiedzi:

509

Spróbujmy również zmodyfikować, igdy będziemy zwiększać j:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
        i++;

O nie! Pochodzący z Pythona wygląda to dobrze, ale w rzeczywistości tak nie jest, ponieważ jest równoważne z:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;

Oczywiście jest to głupi błąd, ale może popełnić go nawet doświadczony programista.

Kolejny bardzo dobry powód jest wskazany w odpowiedzi ta.speot.is .

Trzeci , o którym myślę, to zagnieżdżony if:

if (cond1)
   if (cond2) 
      doSomething();

Załóżmy, że chcesz teraz, doSomethingElse()kiedy cond1nie jest spełniony (nowa funkcja). Więc:

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();

co jest oczywiście błędne, ponieważ elsewiąże się z wewnętrznym if.


Edycja: Ponieważ przyciąga to trochę uwagi, wyjaśnię swój pogląd. Pytanie, na które odpowiadałem to:

Jakie są zalety korzystania z 1. wersji?

Które opisałem. Istnieje kilka korzyści. Ale IMO zasady „zawsze” nie zawsze mają zastosowanie. Więc nie w pełni popieram

Zawsze używaj bloku {} - nawet dla pojedynczej linii // nie OK, dlaczego ???

Nie mówię, że zawsze używaj {}bloku. Jeśli jest to dość prosty warunek i zachowanie, nie rób tego. Jeśli podejrzewasz, że ktoś może przyjść później i zmienić kod, aby dodać funkcjonalność, zrób to.

Luchian Grigore
źródło
5
@ Science_Fiction: Prawda, ale jeśli dodasz i++ wcześniej j++ , obie zmienne będą nadal w zasięgu, gdy zostaną użyte.
Mike Seymour
26
Brzmi to bardzo rozsądnie, ale pomija fakt, że redaktor wykonuje wcięcie, a nie ty, i wcina i++;w sposób, który natychmiast pokazuje, że nie jest on częścią pętli. (W przeszłości mógł to być rozsądny argument i widziałem takie problemy. Około 20 lat temu. Od tamtej pory.)
James Kanze
43
@James: to nie jest „fakt”, to jest twój przepływ pracy. I przepływ pracy wielu ludzi, ale nie wszystkich. Nie sądzę, że to koniecznie błędem traktować C ++ źródło jako plik tekstowy, zamiast wyjście z edytora WYSIWYG (vi / emacs / Visual Studio), który wymusza reguły formatowania. Więc ta reguła jest niezależna od edytora ponad to, czego potrzebujesz, ale nie wykracza poza to, czego ludzie używają do edycji C ++. Stąd „defensywny”.
Steve Jessop,
31
@JamesKanze Czy naprawdę polegasz na założeniu, że wszyscy zawsze pracują w potężnych IDE? Ostatni napisany przez CI napisano w Nano. Nawet biorąc pod uwagę, że jedną z pierwszych rzeczy, które zwykle wyłączam w IDE, jest automatyczne wcięcie - ponieważ IDE zwykle przeszkadza w moim nieliniowym przepływie pracy, próbując naprawić moje „błędy” na podstawie niepełnych informacji . IDE nie są zbyt dobre w automatycznym wcinaniu naturalnego przepływu każdego programisty. Ci programiści, którzy używają takich funkcji, łączą swój styl z IDE, co jest w porządku, jeśli używasz tylko jednego IDE, ale nie tak bardzo, jeśli pracujesz na wielu.
Rushyo,
10
„To głupi błąd, ale nawet doświadczony programista mógłby go popełnić.” - Tak jak powiedziałem w mojej odpowiedzi, nie wierzę w to. Myślę, że to całkowicie przemyślany przypadek, który w rzeczywistości nie stanowi problemu.
Konrad Rudolph
324

Bardzo łatwo jest przypadkowo zmienić przepływ kontroli z komentarzami, jeśli nie używasz {i }. Na przykład:

if (condition)
  do_something();
else
  do_something_else();

must_always_do_this();

Jeśli skomentujesz komentarz do_something_else()z jednym wierszem, skończysz na tym:

if (condition)
  do_something();
else
  //do_something_else();

must_always_do_this();

Kompiluje się, ale must_always_do_this()nie zawsze jest wywoływane.

Mamy ten problem w naszej bazie kodu, gdzie ktoś wszedł, aby bardzo szybko wyłączyć niektóre funkcje przed wydaniem. Na szczęście złapaliśmy go podczas przeglądu kodu.

ta.speot.is
źródło
3
Och chłopcze !! jest to zdefiniowane zachowanie, które must_always_do_this();zostanie wykonane, jeśli skomentujesz // do_something_else ();
Mr.Anubis
1
@Supr, jak napisano po raz pierwszy, mówi, że trudno jest przerwać prawidłowy przepływ, jeśli używasz nawiasów klamrowych, a następnie podaje przykład, jak łatwo jest przerwać bez odpowiedniego nawiasowania kodu
SeanC
20
Natknąłem się na to innego dnia. if(debug) \n //print(info);. Zasadniczo wyjęłam całą bibliotekę.
Kevin
4
Fortunately we caught it in code review.Auć! To brzmi tak źle. Fortunately we caught it in unit tests.byłoby znacznie lepiej!
BЈовић
4
@ BЈовић Ale co, jeśli kod był w teście jednostkowym? Umysł drży. (Żartowałem, to starsza aplikacja. Nie ma testów jednostkowych.)
ta.speot.is
59

Mam wątpliwości co do kompetencji wykładowcy. Biorąc pod uwagę jego punkty:

  1. ok
  2. Czy ktoś naprawdę napisałby (lub chciałby przeczytać) (b*b) - ((4*a)*c) ? Niektóre pierwszeństwa są oczywiste (lub powinny być), a dodatkowe nawiasy tylko powodują zamieszanie. (Z drugiej strony, powinieneś używać nawiasów w mniej oczywistych przypadkach, nawet jeśli wiesz, że nie są one potrzebne).
  3. Raczej. Istnieją dwie szeroko rozpowszechnione konwencje dotyczące formatowania warunków i pętli:
    if (cond) {
        kod;
    }
    
    i:
    if (cond)
    {
        kod;
    }
    
    Na początku zgodziłbym się z nim. Otwór {nie jest aż tak widoczny, więc najlepiej założyć, że zawsze tam jest. W drugim przypadku ja (i większość osób, z którymi pracowałem) nie mam problemu z pominięciem nawiasów klamrowych dla pojedynczej instrukcji. (Pod warunkiem, oczywiście, że wcięcie jest systematyczne i że konsekwentnie używasz tego stylu. (I wielu bardzo dobrych programistów, piszących bardzo czytelny kod, pomija nawiasy klamrowe nawet przy pierwszym formatowaniu).
  4. NO . Takie rzeczy if ( NULL == ptr )są wystarczająco brzydkie, aby utrudnić czytelność. Pisz porównania intuicyjnie. (Co w wielu przypadkach powoduje stałą po prawej.) Jego 4 to zła rada; wszystko, co czyni kod nienaturalnym, czyni go mniej czytelnym.
  5. NO . Wszystko, ale intjest zastrzeżone dla szczególnych przypadków. Dla doświadczonych programistów C i C ++ użycie unsignedoperatorów bitów sygnałów. C ++ nie ma prawdziwego typu kardynalnego (ani żadnego innego efektywnego typu podzakresu); unsignednie działa dla wartości liczbowych z powodu reguł promocji. Prawdopodobnie mogłyby to być wartości liczbowe, dla których żadne operacje arytmetyczne nie miałyby sensu, takie jak numery seryjne unsigned. Byłbym jednak przeciw temu, ponieważ wysyła niewłaściwy komunikat: operacje bitowe również nie mają sensu. Podstawową zasadą jest to, że typy całkowe są int, o ile nie ma istotnego powodu, aby używać innego typu.
  6. NO . Robienie tego systematycznie jest mylące i właściwie nie chroni przed niczym. W ścisłym kodu OO, delete this;często jest najczęstszy przypadek (i nie można ustawić thisdo NULL), a inaczej, najbardziej deletesą destruktory, więc nie można uzyskać dostępu wskaźnik później tak. Ustawienie tej opcji NULLnie ma wpływu na żadne inne wskaźniki unoszące się wokół. Systematyczne ustawianie wskaźnika NULLdaje fałszywe poczucie bezpieczeństwa i tak naprawdę nic nie kupuje.

Spójrz na kod w jednym z typowych odniesień. Stroustrup narusza każdą podaną przez ciebie regułę, z wyjątkiem pierwszej, na przykład.

Proponuję znaleźć innego wykładowcę. Ten, który tak naprawdę wie o czym mówi.

James Kanze
źródło
13
Liczba 4 może być brzydka, jednak ma na to cel. Stara się zapobiec if (ptr = NULL). Nie sądzę, żebym kiedykolwiek używał delete this, czy to jest bardziej powszechne niż widziałem? Nie wydaje mi się, aby ustawianie wskaźnika na NULL po użyciu jest tak kiepską rzeczą, jak YMMV. Może to tylko ja, ale większość jego wskazówek nie wydaje się taka zła.
Firedragon,
16
@Firedragon: Większość kompilatorów będzie ostrzegać, if (ptr = NULL)chyba że napiszesz to jako if ((ptr = NULL)). Muszę zgodzić się z Jamesem Kanze, że brzydota, jaką jest posiadanie, NULLsprawia, że ​​jest to zdecydowanie NIE dla mnie.
Leo
34
@JamesKanze: Muszę powiedzieć, że nie zgadzam się z większością tego, co tu powiedziałeś - chociaż doceniam i szanuję twoje argumenty za dotarciem do nich. Dla doświadczonych programistów C i C ++ użycie operatorów bitów sygnałów niepodpisanych. - W ogóle się nie zgadzam: użycie operatorów bitowych sygnalizuje użycie operatorów bitowych. Dla mnie używanie unsignedwskazuje na dążenie ze strony programisty, że zmienna powinna reprezentować tylko liczby dodatnie. Mieszanie z podpisanymi liczbami zwykle powoduje ostrzeżenie kompilatora, które prawdopodobnie było tym, czego zamierzał wykładowca.
Komponent 10
18
Dla doświadczonych programistów C i C ++ użycie operatorów bitów niepodpisanych sygnałów Lub nie. size_tktoś?
ta.speot.is
16
@James Kanze, rozważ cel. Porównujesz kod wytworzony przez doświadczonego programistę z przykładami instruktażowymi. Reguły te zapewnia wykładowca, ponieważ są to rodzaje błędów, które popełniają jego uczniowie. Z doświadczeniem uczniowie mogą zrelaksować się lub zignorować te absoluty.
Joshua Shane Liberman
46

Wszystkie pozostałe odpowiedzi bronią zasady twojego wykładowcy 3.

Powiem, że się z tobą zgadzam: zasada jest zbędna i nie radziłbym jej. To prawda, że teoretycznie zapobiega błędom, jeśli zawsze dodajesz nawiasy klamrowe. Z drugiej strony nigdy nie spotkałem tego problemu w prawdziwym życiu : wbrew temu, co sugerują inne odpowiedzi, nie zapomniałem dodać nawiasów klamrowych, gdy stały się konieczne. Jeśli użyjesz odpowiedniego wcięcia, natychmiast stanie się oczywiste, że musisz dodać nawiasy klamrowe po wcięciu więcej niż jednego wyrażenia.

Odpowiedź „komponentu 10” w rzeczywistości podkreśla jedyny możliwy przypadek, w którym może to naprawdę prowadzić do błędu. Ale z drugiej strony zastąpienie kodu wyrażeniem regularnym zawsze wymaga ogromnej uwagi.

Spójrzmy teraz na drugą stronę medalu: czy jest wada, aby zawsze używać nawiasów klamrowych? Inne odpowiedzi po prostu ignorują ten punkt. Ale jest to wadą: to zajmuje dużo przestrzeni pionowej ekranu, a to z kolei może spowodować, że kod nieczytelny, ponieważ oznacza to, że trzeba przejść więcej niż to konieczne.

Rozważ funkcję z wieloma klauzulami ochronnymi na początku (i tak, poniższy kod jest zły C ++, ale w innych językach byłaby to dość powszechna sytuacja):

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
    {
        throw null_ptr_error("a");
    }
    if (b == nullptr)
    {
        throw null_ptr_error("b");
    }
    if (a == b)
    {
        throw logic_error("Cannot do method on identical objects");
    }
    if (not a->precondition_met())
    {
        throw logic_error("Precondition for a not met");
    }

    a->do_something_with(b);
}

To okropny kod i zdecydowanie argumentuję, że następujące elementy są znacznie bardziej czytelne:

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
        throw null_ptr_error("a");
    if (b == nullptr)
        throw null_ptr_error("b");
    if (a == b)
        throw logic_error("Cannot do method on identical objects");
    if (not a->precondition_met())
        throw logic_error("Precondition for a not met");

    a->do_something_with(b);
}

Podobnie krótkie zagnieżdżone pętle korzystają z pominięcia nawiasów klamrowych:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
        for (auto j = 0; j < a.h(); ++j)
            c(i, j) = a(i, j) + b(i, j);

    return c;
}

Porównać z:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
    {
        for (auto j = 0; j < a.h(); ++j)
        {
            c(i, j) = a(i, j) + b(i, j);
        }
    }

    return c;
}

Pierwszy kod jest zwięzły; drugi kod jest wzdęty.

I tak, można to w pewnym stopniu złagodzić, umieszczając nawias otwierający w poprzedniej linii. Ale tak by było było byłoby mniej czytelne niż kod bez nawiasów klamrowych.

W skrócie: nie pisz niepotrzebnego kodu, który zajmuje miejsce na ekranie.

Konrad Rudolph
źródło
26
Jeśli nie wierzysz w pisanie kodu, który niepotrzebnie zajmuje miejsce na ekranie, to nie masz nic przeciwko umieszczaniu nawiasów otwierających we własnej linii. Prawdopodobnie teraz będę musiał uchylać się i uciekać przed świętą zemstą GNU, ale poważnie - albo chcesz, aby Twój kod był pionowo zwarty, albo nie. A jeśli tak, nie rób rzeczy zaprojektowanych wyłącznie po to, aby twój kod był mniej zwarty. Ale jak mówisz, że ustalone, że nadal chcesz również usunąć zbędne szelki. A może po prostu napisz if (a == nullptr) { throw null_ptr_error("a"); }w jednym wierszu.
Steve Jessop,
6
@Steve W rzeczywistości, ja nie umieścić nawias otwierający na poprzedniej linii, dla samego powodu zamieszczonego. Użyłem tutaj innego stylu, aby pokazać, jak ogromna może być różnica.
Konrad Rudolph
9
+1 Całkowicie zgadzam się, że twój pierwszy przykład jest znacznie łatwiejszy do odczytania bez nawiasów klamrowych. W drugim przykładzie moim osobistym stylem kodowania jest stosowanie nawiasów klamrowych na zewnętrznej pętli for, a nie na wewnętrznej. Nie zgadzam się z @SteveJessop, że muszę być jednym krańcem lub drugim w kwestii pionowo zwartego kodu. Pomijam dodatkowe nawiasy klamrowe z jednowarstwowymi, aby zmniejszyć przestrzeń pionową, ale umieszczam otwierające nawiasy na nowej linii, ponieważ łatwiej mi zobaczyć zakres, gdy nawiasy są ustawione w jednej linii. Celem jest czytelność, a czasem oznacza to użycie większej ilości przestrzeni pionowej, innym razem oznacza użycie mniejszej.
Travesty3
22
„Nigdy nie spotkałem tego problemu w prawdziwym życiu”: na szczęście. Takie rzeczy nie tylko cię palą, ale powodują 90% oparzenia trzeciego stopnia (a to tylko kilka warstw zarządzania, które wymagają naprawy późnym wieczorem).
Richard
9
@Richard Po prostu tego nie kupuję. Jak wyjaśniłem na czacie, nawet jeśli ten błąd powinien kiedykolwiek wystąpić (co uważam za mało prawdopodobne), to jest trywialne, aby naprawić, gdy spojrzysz na ślad stosu, ponieważ jest oczywiste, gdzie jest błąd, patrząc tylko na kod. Twoje przesadzone roszczenie jest całkowicie bezpodstawne.
Konrad Rudolph
40

Baza kodu, nad którym pracuję, jest rozproszona z kodem przez ludzi z patologiczną niechęcią do nawiasów klamrowych, a dla ludzi, którzy przyjdą później, naprawdę może to mieć wpływ na łatwość konserwacji.

Najczęstszym problematycznym przykładem, z jakim się spotkałem, jest:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
    this_looks_like_a_then-statement_but_isn't;

Więc kiedy przychodzę i chcę dodać oświadczenie wtedy, mogę z łatwością skończyć z tym, jeśli nie jestem ostrożny:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
    this_looks_like_a_then-statement_but_isn't;
    i_want_this_to_be_a_then-statement_but_it's_not;
}

Biorąc pod uwagę, że dodanie nawiasów zajmuje ~ 1 sekundę i może zaoszczędzić co najmniej kilka zdezorientowanych minut debugowania, dlaczego nigdy nie miałbyś skorzystać z opcji zmniejszonej dwuznaczności? Wydaje mi się to fałszywą ekonomią.

dżem
źródło
16
Czy problem w tym przykładzie nie polega na niewłaściwym wcięciu i zbyt długich liniach, a nie na nawiasach klamrowych?
Honza Brabec
7
Tak, ale przestrzeganie wytycznych dotyczących projektowania / kodowania, które są tylko „bezpieczne”, zakładając, że ludzie przestrzegają również innych wytycznych (takich jak brak długich linii), wydaje się prosić o problemy. Gdyby nawiasy klamrowe były od samego początku, nie byłoby w tej sytuacji niepoprawnego bloku if-block.
jam
16
Jak dodawanie nawiasów klamrowych (dzięki if(really long...editor){ do_foo;}czemu unikniesz tego przypadku? Wydaje się, że problem nadal będzie taki sam. Osobiście wolę unikać nawiasów klamrowych, gdy nie jest to konieczne, ale nie ma to nic wspólnego z czasem potrzebnym na ich napisanie, ale zmniejszoną czytelnością z powodu dwóch dodatkowych wierszy w kodzie
Grizzly,
1
Dobra uwaga - zakładałem, że wymuszenie użycia aparatów ortodontycznych spowodowałoby również umieszczenie ich w rozsądnym miejscu, ale oczywiście ktoś zdeterminowany, aby utrudnić, mógłby umieścić je w szeregu, tak jak w twoim przykładzie. Wyobrażam sobie jednak, że większość ludzi tego nie zrobi.
jam
2
Pierwszą i ostatnią rzeczą, którą robię po dotknięciu pliku, jest przycisk automatycznego formatowania. Eliminuje większość tych problemów.
Jonathan Allen,
20

Mój 2c:

Użyj wcięcia

Oczywiście

Nigdy nie polegaj na pierwszeństwie operatora - zawsze używaj nawiasów

Nie używałbym słów „nigdy” i „zawsze”, ale ogólnie rzecz biorąc widzę, że ta zasada jest przydatna. W niektórych językach (Lisp, Smalltalk) nie jest to problemem.

Zawsze używaj bloku {} - nawet dla pojedynczej linii

Nigdy tego nie robię i nigdy nie miałem ani jednego problemu, ale widzę, jak to może być dobre dla studentów, szczególnie. jeśli wcześniej studiowali Python.

Stały obiekt po lewej stronie porównania

Warunki Yoda? Nie prosze Utrudnia to czytelność. Wystarczy użyć maksymalnego poziomu ostrzeżenia podczas kompilacji kodu.

Użyj bez znaku dla zmiennych, które są> = 0

OK. Zabawne, słyszałem, że Stroustrup się nie zgadza.

Ustaw wskaźnik na NULL po usunięciu - ochrona przed podwójnym usunięciem

Zła rada! Nigdy nie używaj wskaźnika, który wskazuje na usunięty lub nieistniejący obiekt.

Nemanja Trifunovic
źródło
5
+1 tylko za ostatni punkt sam. Wskaźnik surowy nie ma zresztą żadnej własności biznesowej.
Konrad Rudolph
2
Jeśli chodzi o używanie niepodpisanych: nie tylko Stroustrup, ale K&R (w C), Herb Sutter i (myślę) Scott Meyers. W rzeczywistości nigdy nie słyszałem, aby ktoś, kto naprawdę rozumiał zasady C ++, argumentowałby za użyciem niepodpisanego.
James Kanze
2
@JamesKanze W rzeczywistości, przy tej samej okazji, kiedy usłyszałem opinię Stroustrupa (konferencja w Bostonie w 2008 r.), Herb Sutter był tam i nie zgodził się z Bjarne na miejscu.
Nemanja Trifunovic
3
Aby zakończyć „ unsignedzepsuty”, jednym z problemów jest to, że gdy C ++ porównuje typy podpisane i niepodpisane o podobnej wielkości, przed dokonaniem porównania konwertuje się na niepodpisane . Co powoduje zmianę wartości. Konwersja na podpisane niekoniecznie byłaby lepsza; porównanie powinno naprawdę odbywać się „tak jakby” obie wartości zostały przekonwertowane na większy typ, który mógłby reprezentować wszystkie wartości w każdym typie.
James Kanze
1
@ SteveJessop Myślę, że musisz wziąć to w kontekście zwracanej funkcji unsigned. Jestem pewien, że nie ma problemu ze exp(double)zwrotem wartości większej niż MAX_INT:-). Ale po raz kolejny prawdziwym problemem są niejawne konwersje. int i = exp( 1e6 );jest całkowicie poprawnym C ++. W pewnym momencie Stroustrup zaproponował wycofanie stratnych, ukrytych konwersji, ale komitet nie był zainteresowany. (Interesującym pytanie: czy unsigned-> intuznać stratny Pomyślę obu. unsigned-> inta int-> unsignedstratne Które przejść długą drogę do podejmowania. unsignedOK
James Kanze
18

jest bardziej intuicyjny i łatwo zrozumiały. Wyjaśnia to intencję.

I zapewnia, że ​​kod nie zostanie złamany, gdy nowy użytkownik może nieświadomie przegapić {, }dodając nową instrukcję kodu.

Alok Save
źródło
Makes the intent clear+1, to prawdopodobnie najbardziej zwięzły i dokładny powód.
Qix - MONICA MISTREATED
13

Aby dodać do bardzo rozsądnych sugestii powyżej, jeden przykład, który napotkałem podczas refaktoryzacji kodu, w którym staje się on krytyczny, był następujący: Zmieniłem bardzo dużą bazę kodu, aby przełączyć się z jednego interfejsu API na inny. Pierwszy interfejs API miał wywołanie w celu ustawienia identyfikatora firmy w następujący sposób:

setCompIds( const std::string& compId, const std::string& compSubId );

mając na uwadze, że wymiana wymagała dwóch połączeń:

setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );

Zacząłem to zmieniać za pomocą wyrażeń regularnych, co było bardzo udane. Przekazaliśmy również kod przez astyle , co naprawdę uczyniło go o wiele bardziej czytelnym. Następnie, w połowie procesu recenzji, odkryłem, że w pewnych warunkowych okolicznościach zmienia to:

if ( condition )
   setCompIds( compId, compSubId );

Do tego:

if ( condition )
   setCompId( compId );
setCompSubId( compSubId );

co wyraźnie nie jest tym, co było wymagane. Musiałem wrócić do początku, zrób to jeszcze raz, traktując zamiennik jako całkowicie w bloku, a następnie ręcznie zmieniając wszystko, co skończyło się na głupkowatym (przynajmniej nie byłoby to nieprawidłowe).

Zauważam, że astyle ma teraz opcję, --add-bracketsktóra pozwala dodawać nawiasy kwadratowe tam, gdzie ich nie ma, i zdecydowanie polecam to, jeśli kiedykolwiek znajdziesz się w tej samej pozycji, co ja.

Komponent 10
źródło
5
Kiedyś widziałem dokumentację, która miała cudowną monetę „Microsoftligent”. Tak, możliwe jest popełnienie znaczących błędów przy wyszukiwaniu globalnym i zamianie. Oznacza to po prostu, że globalne wyszukiwanie i zamiana muszą być używane w sposób inteligentny, a nie w sposób mikro.
Pete Becker
4
Wiem, że to nie jest moja sekcja pośmiertna, ale jeśli zamierzasz zamienić tekst na kodzie źródłowym, powinieneś to zrobić zgodnie z tymi samymi zasadami, których używałbyś do tego rodzaju zamiany tekstu, który jest dobrze ustalony w języku: makra. Nie powinieneś pisać makra #define FOO() func1(); \ func2(); (z podziałem wiersza po odwrotnym ukośniku), to samo dotyczy wyszukiwania i zamiany. To powiedziawszy, widziałem „zawsze używaj nawiasów klamrowych” jako regułę stylu właśnie dlatego, że nie pozwala ci to na zawijanie wszystkich makr z wieloma wyrażeniami do .. while(0). Ale nie zgadzam się.
Steve Jessop
2
Btw, to „ugruntowane” w tym sensie, że japoński rdest jest dobrze ugruntowany: nie mówię, że powinniśmy robić wszystko, aby używać makr i zastępowania tekstu, ale mówię to, kiedy robimy takie rzeczą, powinniśmy to zrobić w sposób, który działa, zamiast robić coś, co działa tylko wtedy, gdy dana reguła stylu została z powodzeniem nałożona na całą bazę kodu :-)
Steve Jessop
1
@ SteveJessop Można również argumentować za szelkami i paskiem. Jeśli musisz używać takich makr (a my tak robiliśmy przed C ++ i inline), prawdopodobnie powinieneś dążyć do tego, aby działały jak najbardziej jak to możliwe, używając do { ... } while(0)sztuczki, jeśli to konieczne (i wiele dodatkowych nawiasów. Ale to nadal by nie nie powstrzymam cię od używania aparatów ortodontycznych wszędzie, jeśli taki jest styl domu. (FWIW: Pracowałem w miejscach o różnych stylach domów, obejmujących wszystkie omówione tutaj style. Nigdy nie uważałem, że to poważny problem).
James Kanze
1
I sądzę, że im więcej stylów pracujesz, tym bardziej uważnie czytasz i edytujesz kod. Więc nawet jeśli preferujesz najłatwiejszy do odczytania, nadal z powodzeniem przeczytasz inne. Pracowałem w firmie, w której różne zespoły napisały różne komponenty w różnych „stylach domowych”, a poprawnym rozwiązaniem jest narzekać na nie w sali obiadowej bez większego efektu, nie próbować stworzyć globalnego stylu :-)
Steve Jessop,
8

Używam {}wszędzie, z wyjątkiem kilku przypadków, w których jest to oczywiste. Jedna linia to jeden z przypadków:

if(condition) return; // OK

if(condition) // 
   return;    // and this is not a one-liner 

To może cię zranić, jeśli dodasz jakąś metodę przed powrotem. Wcięcie wskazuje, że powrót jest wykonywany, gdy warunek jest spełniony, ale zawsze będzie zwracany.

Inny przykład w C # z użyciem statment

using (D d = new D())  // OK
using (C c = new C(d))
{
    c.UseLimitedResource();
}

co jest równoważne z

using (D d = new D())
{
    using (C c = new C(d))
    {
        c.UseLimitedResource();
    }
}
Łukasz Madon
źródło
1
Po prostu użyj przecinków w usingwyciągu i nie musisz :)
Ry-
1
@minitech To po prostu nie działa - przecinka można używać tylko wtedy, gdy typy są równe, a nie dla typów nierównych. Sposób Lukasa na zrobienie tego jest kanoniczny, IDE nawet formatuje to inaczej (zauważ brak automatycznego wcięcia drugiego using).
Konrad Rudolph
8

Najbardziej trafny przykład, jaki mogę wymyślić:

if(someCondition)
   if(someOtherCondition)
      DoSomething();
else
   DoSomethingElse();

Z ifczym elsezostanie sparowany? Wcięcie oznacza, że ​​zewnętrzny ifpobiera else, ale tak naprawdę kompilator tego nie zobaczy; wewnętrzny if dostanie else, a zewnętrzna ifnie. Musisz wiedzieć, że (lub zobaczyć, jak zachowuje się w ten sposób w trybie debugowania), aby dowiedzieć się, sprawdzając, dlaczego ten kod może nie spełniać twoich oczekiwań. Staje się bardziej mylące, jeśli znasz Python; w takim przypadku wiesz, że wcięcie definiuje bloki kodu, więc można oczekiwać, że będzie ono obliczane zgodnie z wcięciem. C # nie daje jednak latającego przewrotu na temat białych znaków.

To powiedziawszy, nie zgadzam się szczególnie z tą zasadą „zawsze używaj nawiasów” na jej twarzy. Powoduje to, że kod jest bardzo głośny w pionie, co zmniejsza jego zdolność do szybkiego czytania. Jeśli oświadczenie brzmi:

if(someCondition)
   DoSomething();

... to powinno być tak napisane. Stwierdzenie „zawsze używaj nawiasów” brzmi jak „zawsze otaczaj operacje matematyczne nawiasami”. To przekształciłoby bardzo proste stwierdzenie a * b + c / dw ((a * b) + (c / d)), wprowadzając możliwość pominięcia ścisłego paren (zmorę wielu koderów) i po co? Kolejność operacji jest dobrze znana i egzekwowana, dlatego nawiasy są zbędne. Używałbyś nawiasów tylko w celu wymuszenia innej kolejności operacji niż normalnie stosowane: a * (b+c) / dna przykład. Blokowe nawiasy klamrowe są podobne; użyj ich, aby zdefiniować, co chcesz robić w przypadkach, w których różni się od domyślnego i nie jest „oczywisty” (subiektywny, ale zazwyczaj zdrowy rozsądek).

KeithS
źródło
3
@AlexBrown ... co dokładnie o to mi chodziło. Zasada określona w PO brzmi: „zawsze używaj nawiasów, nawet dla pojedynczych linii”, z czym się nie zgadzam z podanego przeze mnie powodu. Wsporniki by pomóc w pierwszym przykładzie kodu, ponieważ kod nie będzie się zachowywał tak, to jest wcięte; trzeba by użyć nawiasów, aby sparować elsez pierwszym ifzamiast drugiego. Proszę usunąć głosowanie negatywne.
KeithS
5

Przeglądając odpowiedzi, których nikt nie określił, jakiego rodzaju praktykę używam, opowiadając historię twojego kodu:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

Staje się:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0) j++;
}

Kładzenie j++się na tej samej linii jak gdyby sygnał nikomu innemu, „Chcę tylko tego bloku kiedykolwiek przyrostu j . Z coursethis ma sens tylko wtedy, gdy linia jest tak proste, jak to możliwe, bo oddanie przerwania tutaj, jak peri wspomina, nie będzie bardzo przydatna.

W rzeczywistości właśnie natknąłem się na część interfejsu API Twitter Storm, która zawiera ten „rodzaj” kodu w java, oto fragment relatywny z kodu wykonawczego, na stronie 43 tego pokazu slajdów :

...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
...

Blok pętli for ma w sobie dwie rzeczy, więc nie wstawiłbym tego kodu. Tj. Nigdy :

int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;

To okropne i nawet nie wiem, czy to działa (zgodnie z przeznaczeniem); nie rób tego . Nowe linie i nawiasy klamrowe pomagają rozróżnić oddzielne, ale powiązane fragmenty kodu, w taki sam sposób, jak przecinek lub średnik w prozie. Powyższy blok jest równie kiepski, naprawdę długie zdanie z kilkoma klauzulami i kilkoma innymi stwierdzeniami, które nigdy nie łamią się ani nie przerywają, aby rozróżnić poszczególne części.

Jeśli naprawdę chcesz telegrafować do kogoś innego, jest to praca tylko w jednym wierszu, użyj operatora trójskładnikowego lub ?:formularza:

for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;

Ale to jest na granicy golfa kodowego i myślę, że nie jest to świetna praktyka (nie jest dla mnie jasne, czy powinienem umieścić j ++ po jednej stronie, :czy nie). NB: Nie korzystałem wcześniej z trójkowego operatora w C ++, nie wiem czy to działa, ale tak jest .

W skrócie:

Wyobraź sobie, jak twój czytelnik (tj. Osoba obsługująca kod) interpretuje twoją historię (kod). Wyjaśnij im, jak to możliwe. Jeśli wiesz, że początkujący programista / uczeń utrzymuje to, być może nawet zostaw tak wiele, {}jak to możliwe, tylko po to, aby się nie pomylić.

Pureferret
źródło
1
(1) Umieszczenie instrukcji w tym samym wierszu sprawia, że ​​jest mniej czytelna, a nie bardziej. Szczególnie proste myśli, takie jak przyrost, można łatwo przeoczyć. Do umieścić je w nowej linii. (2) Oczywiście możesz umieścić swoją forpętlę na jednej linii, dlaczego to nie powinno działać? Działa z tego samego powodu, dla którego można pominąć nawiasy klamrowe; nowa linia po prostu nie ma znaczenia w C ++. (3) Twój przykład operatora warunkowego, oprócz tego, że jest okropny, to niepoprawny C ++.
Konrad Rudolph
@KonradRudolph dzięki, jestem trochę zardzewiały w C ++. Nigdy nie powiedziałem, że (1) jest bardziej czytelny, ale oznaczałoby to, że ten fragment kodu miał być online w jednym wierszu. (2) Mój komentarz polegał na tym, że nie byłbym w stanie go przeczytać i wiedzieć, że zadziałał, czy to w ogóle, czy zgodnie z przeznaczeniem; to przykład tego, czego nie należy robić z tego powodu. (3) Dzięki, nie pisałem C ++ od dawna. Naprawię to teraz.
Pureferret
2
Również umieszczenie więcej niż jednego wyrażenia w jednym wierszu utrudnia debugowanie kodu. Jak umieścić punkt przerwania na 2. wyrażeniu w tym wierszu?
Piotr Perak,
5

Ponieważ bez dwóch stwierdzeń {}łatwo jest przeoczyć problem. Załóżmy, że kod wygląda tak.

int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();

if ((err = prepare_hash(hash, &hash_result))) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
    goto fail;
    goto fail;
if ((err = hash_finish(hash)) != 0)
    goto fail;

error = do_important_stuff_with(hash);

fail:
hash_free(hash);
return error;

To wygląda dobrze. Problem z tym jest naprawdę łatwy do przeoczenia, szczególnie gdy funkcja zawierająca kod jest znacznie większa. Problem polega na tym, że goto failjest prowadzony bezwarunkowo. Możesz łatwo wyobrazić sobie, jak to jest frustrujące (co powoduje, że pytasz, dlaczego ostatni hash_updatezawsze zawodzi, w końcu wszystko hash_updatedziała dobrze ).

Nie oznacza to jednak, że jestem za dodawaniem {}wszędzie (moim zdaniem widzenie {}wszędzie jest denerwujące). Chociaż może powodować problemy, nigdy nie robił tego w przypadku moich własnych projektów, ponieważ mój osobisty styl kodowania zabrania warunkowych bez, {}gdy nie są na tej samej linii (tak, zgadzam się, że mój styl kodowania jest niekonwencjonalny, ale podoba mi się i używaj stylu kodu projektu, gdy współtworzysz inne projekty). To sprawia, że ​​następujący kod jest w porządku.

if (something) goto fail;

Ale nie następna.

if (something)
    goto fail;
Konrad Borowski
źródło
Dokładnie. Po prostu nie umieszczaj (całkowicie niepotrzebnego) wcięcia + wiersza, a całkowicie omijasz tę kwestię, którą wszyscy tak szybko poruszają.
Alexander - Przywróć Monikę
4

Sprawia, że ​​kod jest bardziej czytelny, dzięki wyraźnemu zdefiniowaniu zakresu pętli i bloków warunkowych. Oszczędza to również przed przypadkowymi błędami.

Drona
źródło
4

wrt 6: Jest to bezpieczniejsze, ponieważ usunięcie wskaźnika zerowego nie jest możliwe. Jeśli przypadkowo przypadkowo przejdziesz tę ścieżkę dwa razy, nie spowoduje to, że uszkodzenie pamięci spowoduje zwolnienie pamięci, która jest wolna lub została przydzielona na coś innego.

Jest to większość problemu ze statycznymi obiektami zakresu plików i singletonami, które nie mają bardzo jasnego czasu życia i które zostały odtworzone po ich zniszczeniu.

W większości przypadków możesz tego uniknąć, używając auto_ptrs

Tom Tanner
źródło
6
Jeśli zdarzy ci się przejść tę ścieżkę dwa razy, wystąpił błąd programowania. Ustawienie wskaźnika na wartość NULL, aby ten błąd był mniej szkodliwy, nie rozwiązuje podstawowego problemu.
Pete Becker
1
Zgadzam się, ale widziałem to wcześniej i uważam, że jest to zgodne z niektórymi standardami programowania. Komentowałem bardziej, dlaczego wymyślił to profesor plakatu, niż kiedy było to dobre
Tom Tanner
2
Zgodnie z tym, co powiedział Pete Becker: nie rozwiązuje podstawowego problemu, ale może go maskować. (Są przypadki, w których należy ustawić wskaźnik NULLpo usunięciu. Jeśli NULLw takich okolicznościach wskaźnik ma prawidłową wartość, np. Wskaźnik wskazuje na buforowaną wartość i NULLwskazuje niepoprawną pamięć podręczną. Ale gdy zobaczysz, że ktoś ustawia wskaźnik do NULLostatniej linii w destruktorze, zastanawiasz się, czy on zna C ++.)
James Kanze
4

Podoba mi się zaakceptowana odpowiedź Luchiana, w rzeczywistości nauczyłem się na własnej skórze, że ma rację, więc zawsze używam nawiasów klamrowych, nawet dla bloków jednowierszowych. Jednak osobiście robię wyjątek, pisząc filtr, tak jak w twoim przykładzie. To:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}

wygląda na zagracony dla mnie. Oddziela pętlę for i instrukcję if na osobne akcje, podczas gdy tak naprawdę twoja intencja jest pojedynczą akcją: policzyć wszystkie liczby całkowite podzielne przez 2. W bardziej wyrazistym języku można to napisać w następujący sposób:

j = [1..100].filter(_%2 == 0).Count

W językach, w których brakuje zamknięć, filtr nie może być wyrażony w pojedynczej instrukcji, ale musi być pętlą for, po której następuje instrukcja if. Jest to jednak jedno działanie w umyśle programisty i uważam, że powinno to zostać odzwierciedlone w kodzie, tak jak poniżej:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
  if (i % 2 == 0)
{
    j++;
}
MikeFHay
źródło
7
Podoba mi się, jak wszyscy potrafią zignorować for (int i = 0; i < 100; i += 2);, w celu kontynuowania sporu o wcięcie ;-) Prawdopodobnie moglibyśmy mieć całą oddzielną walkę, jak „najlepiej” wyrazić logikę „dla każdego iw pewnym zakresie z pewną właściwością” w C ++ bez pętli, używając koszmaru kombinacji standardowych algorytmów filter_iteratori / lub counting_iterator.
Steve Jessop,
1
Ponadto, gdybyśmy to mieli, moglibyśmy nie zgodzić się co do tego, jak wciąć powstałe ogromne pojedyncze zdanie.
Steve Jessop,
2
@ Steve, to tylko przykład. Istnieje wiele uzasadnionych zastosowań tego wzoru. Oczywiście, jeśli chcesz policzyć liczby od 1 do 100, które są podzielne przez 2, wszystko co musisz zrobić, to 100/2.
MikeFHay
2
Jasne, wiem, dlatego streściłem na „dla każdego iz określonego zakresu z pewną właściwością”. Tyle, że zwykle w przypadku SO ludzie bardzo szybko ignorują aktualne pytanie na rzecz zupełnie innego podejścia do podanego przykładu. Ale wcięcie jest ważne , więc nie ;-)
Steve Jessop
4

Jedną z opcji zapobiegania błędom opisanym powyżej jest podkreślenie tego, co chcesz się wydarzyć, gdy nie używasz nawiasów klamrowych. Znacznie trudniej jest nie zauważyć błędów podczas próby modyfikacji kodu.

if (condition) doSomething();
else doSomethingElse();

if (condition) doSomething();
    doSomething2(); // Looks pretty obviously wrong
else // doSomethingElse(); also looks pretty obviously wrong
Czarny tygrys
źródło
5
Druga opcja spowodowałaby błąd kompilacji, ponieważ elsenie jest powiązany z if.
Luchian Grigore
Jednym z nie tak widocznych problemów z wbudowanym jest to, że większość IDE domyślnie zmienia styl wcięcia podczas korzystania z narzędzia do automatycznego formatowania.
Honza Brabec
@Honza: to jednak bardzo naładowana kwestia polityczna. Jeśli współpracujemy w oparciu o kod, musimy albo stosować ten sam styl wcięcia w każdym szczególe, albo oboje musimy zgodzić się na automatyczne formatowanie istniejącego kodu „tylko dlatego, że”. Jeśli to pierwsze, to uzgodniony styl może nadal to obejmować, ale będziesz musiał albo skonfigurować IDE, aby go przestrzegać, albo nie używać automatycznego formatowania. Zgadzanie się, że wspólnym formatem jest „bez względu na moje autoformatowania IDE”, wszystko jest bardzo dobrze, jeśli wszyscy używamy tego samego IDE na zawsze, w przeciwnym razie nie tak dobrze.
Steve Jessop,
3

Jeśli jesteś kompilatorem, nie robi to żadnej różnicy. Oba są takie same.

Ale dla programistów pierwszy jest bardziej przejrzysty, czytelny i mniej podatny na błędy.

PP
źródło
2
{Zresztą poza otwarciem na własnej linii.
Rob
3

Kolejny przykład dodawania nawiasów klamrowych. Kiedyś szukałem błędu i znalazłem taki kod:

void SomeSimpleEventHandler()
{
    SomeStatementAtTheBeginningNumber1;
    if (conditionX) SomeRegularStatement;
    SomeStatementAtTheBeginningNumber2;
    SomeStatementAtTheBeginningNumber3;
    if (!SomeConditionIsMet()) return;
    OtherwiseSomeAdditionalStatement1;
    OtherwiseSomeAdditionalStatement2;
    OtherwiseSomeAdditionalStatement3;
}

Jeśli czytasz metodę wiersz po wierszu, zauważysz, że w metodzie występuje warunek, który zwraca wartość, jeśli nie jest to prawda. Ale tak naprawdę wygląda to na 100 innych prostych procedur obsługi zdarzeń, które ustawiają niektóre zmienne na podstawie pewnych warunków. I pewnego dnia pojawia się Fast Coder i dodaje dodatkowe oświadczenie o zmiennej wartości na końcu metody:

{
    ...
    OtherwiseSomeAdditionalStatement3;
    SetAnotherVariableUnconditionnaly;
}

W rezultacie SetAnotherVariableUnconditionnaly jest wykonywana, gdy SomeConditionIsMet (), ale szybki facet tego nie zauważył, ponieważ wszystkie linie mają prawie podobny rozmiar, a nawet gdy warunek powrotu jest wcięty pionowo, nie jest to tak zauważalne.

Jeśli warunkowy zwrot jest sformatowany w następujący sposób:

if (!SomeConditionIsMet())
{
    return;
}

jest to zauważalne, a szybki koder znajdzie to na pierwszy rzut oka.

Artemix
źródło
Jeśli szybki koder nie może mieć problemu z wykryciem wyróżnionej składni returninstrukcji w treści funkcji przed dodaniem czegoś do niej, nie należy pozwolić, aby szybki koder zbliżył się do kodu. Nie powstrzymasz takiego faceta od trollowania twojego kodu, włączając nawiasy klamrowe.
cmaster
@cmaster On już z nami nie pracuje. W każdym razie podświetlanie składni jest dobre, ale pamiętaj, że są osoby, które nie widzą wyraźnie (widziałem nawet post niewidomego programisty w zeszłym roku).
Artemix
2

Pierwszy uważam za jasny, a następnie drugi. Daje to wrażenie zamykania instrukcji, ponieważ mały kod jest w porządku, gdy kod staje się skomplikowany, bardzo {...}pomaga, nawet jeśli jest endiflubbegin...end

//first
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}


//second
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;
RollRoll
źródło
1

Po zakończeniu najlepiej ustawić wskaźnik na NULL.

Oto przykład, dlaczego:

Klasa A wykonuje następujące czynności:

  1. Przydziela blok pamięci
  2. Potem jakiś czas usuwa ten blok pamięci, ale nie ustawia wskaźnika na NULL

Klasa B wykonuje następujące czynności

  1. Przydziela pamięć (w tym przypadku zdarza się, że otrzymuje ten sam blok pamięci, który został usunięty przez klasę A.)

W tym momencie zarówno klasa A, jak i klasa B mają wskaźniki wskazujące na ten sam blok pamięci, jeśli chodzi o klasę A, ten blok pamięci nie istnieje, ponieważ jest z nim zakończony.

Rozważ następujący problem:

Co jeśli wystąpił błąd logiczny w klasie A, który spowodował zapisanie go w pamięci, która teraz należy do klasy B?

W tym konkretnym przypadku nie wystąpi błąd wyjątku złego dostępu, ponieważ adres pamięci jest legalny, a klasa A skutecznie uszkadza dane klasy B.

Klasa B może ostatecznie upaść, jeśli napotka nieoczekiwane wartości, a kiedy się zawiesi, są szanse, że spędzisz dość dużo czasu na polowaniu na tego błędu w klasie B, gdy problem występuje w klasie A.

Jeśli ustawisz wskaźnik usuniętej pamięci na NULL, otrzymasz błąd wyjątku, gdy tylko jakiekolwiek błędy logiczne w klasie A spróbują zapisać do wskaźnika NULL.

Jeśli martwisz się błędem logicznym podwójnego usuwania, gdy wskaźniki po raz drugi mają wartość NULL, dodaj dla tego potwierdzenie.

Ponadto: jeśli zamierzasz obniżyć głosowanie, wyjaśnij to.

Jan
źródło
2
Jeśli wystąpił błąd logiczny, należy go naprawić, a nie maskować.
uınbɐɥs
@Barmar, OP mówi ... 6. Ustaw wskaźnik na NULL po usunięciu - Podwójna ochrona przed usunięciem // niezła. Niektóre osoby odpowiedziały, że nie ustawiły wartości NULL, a ja mówię, dlaczego należy ustawić wartość NULL, która część 6. mojej odpowiedzi na ustawienie NULL nie mieści się w 6?
John
1
@Shaquin, a jak proponujesz znaleźć te błędy logiczne? Po ustawieniu zmiennej wskaźnika na NULL po usunięciu pamięci. Każda próba odwołania się do wskaźnika NULL spowoduje awarię debuggera w linii, w której dokonano nielegalnej próby. Możesz prześledzić wstecz i zobaczyć, gdzie był błąd logiczny, i naprawić problem. Jeśli po usunięciu pamięci nie ustawisz zmiennej wskaźnika na NULL, nielegalna próba zapisania tej usuniętej pamięci z powodu błędów logicznych UNAWARE może się powieść i dlatego nie zawiedzie się w tym momencie. To nie maskuje.
Jan
1

Zawsze posiadanie nawiasów klamrowych jest bardzo prostą i niezawodną zasadą. Jednak kod może wyglądać nieelegancko, gdy jest wiele nawiasów klamrowych. Jeśli reguły pozwalają pominąć nawiasy klamrowe, powinny istnieć bardziej szczegółowe reguły stylu i bardziej wyrafinowane narzędzia. W przeciwnym razie może łatwo powstać chaotyczny i mylący (nie elegancki) kod. Dlatego szukanie reguły pojedynczego stylu oddzielnie od pozostałych przewodników stylu i używanych narzędzi jest prawdopodobnie bezowocne. Przedstawię tylko kilka ważnych szczegółów dotyczących tej zasady nr 3, o których nawet nie wspomniano w innych odpowiedziach.

Pierwszym interesującym szczegółem jest to, że większość zwolenników tej zasady zgadza się na jej naruszenie w przypadku else. Innymi słowy, nie wymagają przeglądu takiego kodu:

// pedantic rule #3
if ( command == Eat )
{
    eat();
}
else
{
    if ( command == Sleep )
    {
        sleep();
    }
    else
    {
        if ( command == Drink )
        {
            drink();
        }
        else
        {
            complain_about_unknown_command();
        }
    }
}

Zamiast tego, jeśli go zobaczą, mogą nawet zasugerować, aby napisać w ten sposób:

// not fully conforming to rule #3
if ( command == Eat )
{
    eat();
}
else if ( command == Sleep )
{
    sleep();
}
else if ( command == Drink )
{
    drink();
}
else
{
   complain_about_unknown_command();
}

Jest to technicznie naruszenie tej zasady, ponieważ nie ma nawiasów klamrowych między elsei if. Taka dwoistość reguły ujawnia się, kiedy należy zastosować ją automatycznie do bazy kodu za pomocą bezmyślnego narzędzia. Rzeczywiście, po co się kłócić, po prostu pozwól narzędziu automatycznie zastosować styl.

Drugi szczegół (który jest również często zapominany przez zwolenników tej reguły) jest taki, że błędy, które mogą się zdarzyć, nigdy nie są spowodowane jedynie naruszeniem tej zasady # 3. W rzeczywistości prawie zawsze wiążą się z naruszeniem reguły nr 1 (z którą nikt się nie kłóci). Ponownie, z punktu widzenia automatycznych narzędzi, nie jest trudno stworzyć narzędzie, które natychmiast narzeka, gdy naruszona zostanie reguła nr 1, dzięki czemu większość błędów można wychwycić na czas.

Trzecim szczegółem (często zapominanym przez przeciwników tej reguły) jest myląca natura pustego wyrażenia reprezentowanego przez pojedynczy średnik. Większość programistów z pewnym doświadczeniem prędzej czy później pomyliło się z powodu jednoznacznie umieszczonego średnika lub pustej instrukcji napisanej przy użyciu jedynego średnika. Dwa nawiasy klamrowe zamiast pojedynczego średnika są znacznie łatwiejsze do zauważenia.

Öö Tiib
źródło
1

Muszę przyznać, że nie zawsze używa się go {}na pojedynczej linii, ale to dobra praktyka.

  • Powiedzmy, że piszesz kod bez nawiasów, który wygląda następująco:

    for (int i = 0; i <100; ++ i) for (int j = 0; j <100; ++ j) DoSingleStuff ();

A po pewnym czasie chcesz dodać w jpętli inne rzeczy i po prostu to zrobić, wyrównując i zapomnieć o dodawaniu nawiasów.

  • Przydział pamięci jest szybszy. Powiedzmy, że masz duży zakres i tworzysz duże tablice wewnątrz (bez, newwięc są one w stosie). Te tablice usuwają się z pamięci tuż po opuszczeniu zakresu. Ale możliwe jest, że użyjesz tej tablicy w jednym miejscu i będzie ona przez pewien czas w stosie i będzie czymś w rodzaju śmieci. Ponieważ stos ma ograniczony i dość mały rozmiar, można go przekroczyć. Dlatego w niektórych przypadkach lepiej jest pisać, {}aby temu zapobiec. UWAGA nie dotyczy pojedynczej linii, ale takich sytuacji:

    if (...) {// SomeStuff ... {// nie mamy, jeśli, podczas, itp. // SomeOtherStuff} // SomeMoreStuff}

  • Trzeci sposób użycia jest podobny do drugiego. To po prostu nie ma na celu uczynienia stosu czystszym, ale otwarcie niektórych funkcji. Jeśli używasz mutexdługich funkcji, zwykle lepiej jest zablokować i odblokować tuż przed uzyskaniem dostępu do danych i zaraz po zakończeniu ich odczytu / zapisu. UWAGA ten sposób jest używany, jeśli masz własne classlub structz constructori destructordo blokowania pamięci.

  • Co więcej:

    if (...) if (...) SomeStuff (); else SomeOtherStuff (); // przechodzi do drugiego jeśli, ale przydział pokazuje, że jest on pierwszy ...

Podsumowując, nie mogę powiedzieć, jaki jest najlepszy sposób, aby zawsze używać {}pojedynczej linii, ale nie jest to nic złego.

WAŻNA EDYCJA Jeśli napiszesz nawiasy kompilujące kod dla pojedynczej linii, nic nie robi, ale jeśli twój kod zostanie zinterpretowany, bardzo nieznacznie spowalnia kod. Bardzo nieznacznie.

ST3
źródło
1

Istnieje wiele możliwych sposobów pisania instrukcji kontrolnych; niektóre ich kombinacje mogą współistnieć bez pogorszenia czytelności, ale inne kombinacje powodują problemy. Styl

if (condition)
  statement;

będzie współistnieć wygodnie z niektórymi innymi sposobami pisania instrukcji kontrolnych, ale nie tak dobrze z innymi. Jeśli instrukcje sterowane wieloliniowo są zapisywane jako:

if (condition)
{
  statement;
  statement;
}

wtedy wizualnie będzie oczywiste, które ifinstrukcje kontrolują jedną linię, a które kontrolują wiele linii. Jeśli jednak ifwyrażenia wieloliniowe są zapisywane jako:

if (condition) {
  statement;
  statement;
}

wtedy prawdopodobieństwo, że ktoś spróbuje rozszerzyć konstrukcję pojedynczej instrukcji ifbez dodania niezbędnych nawiasów klamrowych, może być znacznie wyższe.

Instrukcja pojedynczego wyrażenia w następnym wierszu ifmoże być również problematyczna, jeśli baza kodów w znacznym stopniu korzysta z formularza

if (condition) statement;

Osobiście wolę, aby posiadanie instrukcji we własnej linii ogólnie poprawiało czytelność, z wyjątkiem przypadków, gdy istnieje wiele ifinstrukcji z podobnymi blokami kontrolnymi, np.

if (x1 > xmax) x1 = xmax;
if (x1 < xmin) x1 = xmin;
if (x2 > xmax) x2 = xmax;
if (x2 < xmin) x2 = xmin;
etc.

w takim przypadku zwykle poprzedzam i podążam za takimi grupami ifinstrukcji pustą linią, aby wizualnie oddzielić je od innych kodów. Posiadanie szeregu stwierdzeń rozpoczynających się ifod tego samego wcięcia zapewni wyraźne wizualne wskazanie, że istnieje coś niezwykłego.

supercat
źródło