Czasami if
instrukcja może być dość skomplikowana lub długa, dlatego ze względu na czytelność lepiej jest wyodrębnić skomplikowane wywołania przed rozszerzeniem if
.
np. to:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall())
{
// do stuff
}
zaangażowany w to
bool b1 = SomeComplicatedFunctionCall();
bool b2 = OtherComplicatedFunctionCall();
if (b1 || b2)
{
//do stuff
}
(podany przykład nie jest taki zły, to tylko dla ilustracji ... wyobraź sobie inne wywołania z wieloma argumentami itp.)
Ale z tym wydobyciem straciłem ocenę zwarcia (SCE).
- Czy naprawdę tracę SCE za każdym razem? Czy istnieje scenariusz, w którym kompilator może go „zoptymalizować” i nadal zapewniać SCE?
- Czy istnieją sposoby na utrzymanie lepszej czytelności drugiego fragmentu bez utraty SCE?
Odpowiedzi:
Jedno naturalne rozwiązanie wyglądałoby tak:
bool b1 = SomeCondition(); bool b2 = b1 || SomeOtherCondition(); bool b3 = b2 || SomeThirdCondition(); // any other condition bool bn = bn_1 || SomeFinalCondition(); if (bn) { // do stuff }
Ma to tę zaletę, że jest łatwy do zrozumienia, ma zastosowanie we wszystkich przypadkach i wykazuje właściwości zwarciowe.
To było moje początkowe rozwiązanie: dobry wzorzec w wywołaniach metod i treściach pętli for jest następujący:
if (!SomeComplicatedFunctionCall()) return; // or continue if (!SomeOtherComplicatedFunctionCall()) return; // or continue // do stuff
Otrzymuje się te same niezłe korzyści wydajnościowe wynikające z oceny zwarć, ale kod wygląda na bardziej czytelny.
źródło
if
” to także znak, że twoja funkcja lub metoda jest za duża i powinna zostać podzielona na mniejsze. Nie zawsze jest to najlepszy sposób, ale bardzo często jest!b2
poprawnie, a otrzymaszsomeConditionAndSomeotherConditionIsTrue
, nie super znaczące. Poza tym podczas tego ćwiczenia muszę utrzymywać kilka zmiennych na swoim mentalnym stosie (i do czasu aż przestanę pracować w tym zakresie). WybrałbymSJuan76
rozwiązanie numer 2 lub po prostu umieściłbym całość w funkcji.Mam tendencję do rozkładania warunków na wiele linii, tj .:
if( SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall() ) {
Nawet jeśli masz do czynienia z wieloma operatorami (&&), musisz po prostu przesuwać wcięcia z każdą parą nawiasów. SCE nadal działa - nie ma potrzeby używania zmiennych. Pisanie kodu w ten sposób już od lat sprawiło, że stał się on dla mnie dużo bardziej czytelny. Bardziej złożony przykład:
if( one() ||( two()> 1337 &&( three()== 'foo' || four() ) ) || five()!= 3.1415 ) {
źródło
Jeśli masz długie łańcuchy warunków i chcesz zachować niektóre z nich, możesz użyć zmiennych tymczasowych, aby połączyć wiele warunków. Biorąc twój przykład, można by zrobić np
bool b = SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall(); if (b && some_other_expression) { ... }
Jeśli masz kompilator obsługujący C ++ 11, możesz użyć wyrażeń lambda do łączenia wyrażeń w funkcje, podobnie jak powyżej:
auto e = []() { return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall(); }; if (e() && some_other_expression) { ... }
źródło
1) Tak, nie masz już SCE. W przeciwnym razie miałbyś to
bool b1 = SomeComplicatedFunctionCall(); bool b2 = OtherComplicatedFunctionCall();
działa w jedną lub drugą stronę, w zależności od tego, czy
if
później pojawi się instrukcja. Zbyt skomplikowane.2) Jest to oparte na opiniach, ale w przypadku dość złożonych wyrażeń możesz zrobić:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall()) {
Jeśli jest to zbyt skomplikowane, oczywistym rozwiązaniem jest utworzenie funkcji, która ocenia wyrażenie i wywołuje ją.
źródło
Możesz także użyć:
bool b = someComplicatedStuff(); b = b || otherComplicatedStuff(); // it has to be: b = b || ...; b |= ...; is bitwise OR and SCE is not working then
i SCE będzie działać.
Ale nie jest dużo bardziej czytelny niż na przykład:
if ( someComplicatedStuff() || otherComplicatedStuff() )
źródło
b = b || otherComplicatedStuff();
i @SargeBorsch dokonał edycji, aby usunąć SCE. Dziękuję za zwrócenie uwagi na tę zmianę @Ant.Myślę, że taka optymalizacja nie jest dozwolona; zwłaszcza
OtherComplicatedFunctionCall()
może mieć pewne skutki uboczne.Wolę zamienić to na jedną funkcję lub jedną zmienną o opisowej nazwie; co pozwoli zachować zarówno ocenę zwarć, jak i czytelność:
bool getSomeResult() { return SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall(); } ... if (getSomeResult()) { //do stuff }
A ponieważ implementujemy w
getSomeResult()
oparciu oSomeComplicatedFunctionCall()
iOtherComplicatedFunctionCall()
, możemy je dekomponować rekurencyjnie, jeśli nadal są skomplikowane.źródło
Nie, nie masz, ale jest to stosowane inaczej:
if (SomeComplicatedFunctionCall() || OtherComplicatedFunctionCall()) { // do stuff }
Tutaj kompilator nie będzie nawet działał,
OtherComplicatedFunctionCall()
jeśliSomeComplicatedFunctionCall()
zwróci true.bool b1 = SomeComplicatedFunctionCall(); bool b2 = OtherComplicatedFunctionCall(); if (b1 || b2) { //do stuff }
Tutaj obie funkcje będą działać, ponieważ muszą być zapisane w
b1
ib2
. Ff nie będzieb1 == true
wtedyb2
oceniana (SCE). AleOtherComplicatedFunctionCall()
już został uruchomiony.Jeśli nie
b2
jest używane nigdzie indziej, kompilator może być wystarczająco inteligentny, aby wbudować wywołanie funkcji wewnątrz if, jeśli funkcja nie ma obserwowalnych skutków ubocznych.To zależy. Jeśli musisz
OtherComplicatedFunctionCall()
uruchomić z powodu skutków ubocznych lub wydajność funkcji jest minimalna, powinieneś użyć drugiego podejścia dla czytelności. W przeciwnym razie trzymaj się SCE przy pierwszym podejściu.źródło
Inna możliwość zwarcia i ma warunki w jednym miejscu:
bool (* conditions [])()= {&a, &b, ...}; // list of conditions bool conditionsHold = true; for(int i= 0; i < sizeOf(conditions); i ++){ if (!conditions[i]()){; conditionsHold = false; break; } } //conditionsHold is true if all conditions were met, otherwise false
Możesz umieścić pętlę w funkcji i pozwolić funkcji zaakceptować listę warunków i wyprowadzić wartość logiczną.
źródło
Bardzo dziwne: mówisz o czytelności, gdy nikt nie wspomina o użyciu komentarza w kodzie:
if (somecomplicated_function() || // let me explain what this function does someother_function()) // this function does something else ...
Poza tym zawsze poprzedzam swoje funkcje komentarzami dotyczącymi samej funkcji, jej danych wejściowych i wyjściowych, a czasami umieszczam przykład, jak widać tutaj:
/*---------------------------*/ /*! interpolates between values * @param[in] X_axis : contains X-values * @param[in] Y_axis : contains Y-values * @param[in] value : X-value, input to the interpolation process * @return[out] : the interpolated value * @example : interpolate([2,0],[3,2],2.4) -> 0.8 */ int interpolate(std::vector<int>& X_axis, std::vector<int>& Y_axis, int value)
Oczywiście formatowanie komentarzy może zależeć od środowiska programistycznego (Visual studio, JavaDoc w Eclipse, ...)
Jeśli chodzi o SCE, zakładam, że masz na myśli co następuje:
bool b1; b1 = somecomplicated_function(); // let me explain what this function does bool b2 = false; if (!b1) { // SCE : if first function call is already true, // no need to spend resources executing second function. b2 = someother_function(); // this function does something else } if (b1 || b2) { ... }
źródło
Czytelność jest konieczna, jeśli pracujesz w firmie, a Twój kod będzie czytany przez kogoś innego. Jeśli piszesz program dla siebie, to od Ciebie zależy, czy chcesz poświęcić wydajność na rzecz zrozumiałego kodu.
źródło