중첩을 줄이기 위해“if”문을 반전
예를 들어 코드에서 ReSharper 를 실행했을 때 :
if (some condition)
{
Some code...
}
ReSharper는 위의 경고 (네 스팅을 줄이기 위해 "if"문을 반전시킵니다)를 알려주고 다음 수정 사항을 제안했습니다.
if (!some condition) return;
Some code...
왜 더 나은지 이해하고 싶습니다. 저는 항상 "goto"와 같은 문제가있는 방법의 중간에 "return"을 사용한다고 생각했습니다.
메소드 중간의 리턴이 반드시 나쁘지는 않습니다. 코드의 의도가 더 명확 해지면 즉시 반환하는 것이 좋습니다. 예를 들면 다음과 같습니다.
double getPayAmount() {
double result;
if (_isDead) result = deadAmount();
else {
if (_isSeparated) result = separatedAmount();
else {
if (_isRetired) result = retiredAmount();
else result = normalPayAmount();
};
}
return result;
};
이 경우 _isDead
사실이면 즉시 메소드에서 벗어날 수 있습니다. 대신 이런 식으로 구성하는 것이 좋습니다.
double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
};
리팩토링 카탈로그 에서이 코드를 선택했습니다 . 이 특정 리팩토링을 다음과 같이 호출합니다. 중첩 조건부를 가드 절로 바꿉니다.
미적 일뿐만 아니라 분석법 내부의 최대 중첩 수준을 줄 입니다. 이는 일반적으로 분석법을 이해하기 쉽기 때문에 플러스로 간주됩니다 (실제로 많은 정적 분석 도구 는이를 코드 품질의 지표 중 하나로 측정합니다).
다른 한편으로, 그것은 또한 당신의 분석법이 여러 출구 점을 가지도록합니다. 다른 사람들의 그룹은 그렇지 않다고 생각합니다.
개인적으로 저는 ReSharper와 첫 번째 그룹에 동의합니다 (예외가있는 언어에서는 "다중 출구 점"에 대해 설명하기 어리석은 것으로 생각됩니다. 거의 모든 것이 던져 질 수 있으므로 모든 방법에 수많은 잠재적 출구 점이 있습니다).
성능과 관련하여 두 언어는 모든 언어에서 동일 해야 합니다 (IL 수준이 아닌 경우 지터가 코드를 통과 한 후). 이론적으로 이것은 컴파일러에 따라 다르지만 실제로 널리 사용되는 오늘날의 컴파일러는 이보다 훨씬 고급 코드 최적화 사례를 처리 할 수 있습니다.
이것은 약간의 종교적인 주장이지만, ReSharper에 동의하면 중첩이 적어야한다는 데 동의합니다. 나는 이것이 함수에서 여러 리턴 경로를 갖는 것의 단점보다 중요하다고 생각합니다.
중첩이 적은 주요 이유는 코드 가독성과 유지 관리 성 을 향상시키기위한 것 입니다. 앞으로 많은 다른 개발자들이 코드를 읽을 필요가 있으며 들여 쓰기가 적은 코드는 일반적으로 읽기가 훨씬 쉽습니다.
전제 조건 은 함수 시작시 일찍 리턴하는 것이 좋은 예입니다. 전제 조건 점검의 존재로 나머지 기능의 가독성에 영향을 미치는 이유는 무엇입니까?
메소드에서 여러 번 리턴하는 것에 대한 단점은 디버거가 이제는 매우 강력하며 특정 함수가 언제 어디서 언제 반환되는지를 쉽게 찾을 수 있습니다.
함수에 여러 개의 리턴이 있어도 유지 보수 프로그래머의 작업에는 영향을 미치지 않습니다.
코드 가독성이 좋지 않습니다.
다른 사람들이 언급했듯이 성능 저하는 없어야하지만 다른 고려 사항이 있습니다. 이러한 유효한 우려 외에도 일부 상황에서는 문제가 발생할 수 있습니다. 당신이 double
대신 처리하고 있다고 가정 해보십시오 .
public void myfunction(double exampleParam){
if(exampleParam > 0){
//Body will *not* be executed if Double.IsNan(exampleParam)
}
}
외관상 동등한 반전 과는 대조적입니다 .
public void myfunction(double exampleParam){
if(exampleParam <= 0)
return;
//Body *will* be executed if Double.IsNan(exampleParam)
}
따라서 어떤 상황에서는 올바르게 뒤집힌 것으로 보이는 것이 아닐 if
수도 있습니다.
언어가 예외를 지원하기 전날부터 함수가 끝날 때만 복귀한다는 아이디어가 나왔습니다. 그것은 프로그램이 메소드의 끝에 클린업 코드를 넣을 수 있고, 그것이 호출되고 다른 프로그래머가 클린업 코드를 건너 뛰게하는 메소드의 리턴을 숨기지 않을 것을 확신 할 수있게했습니다. . 정리 코드를 건너 뛰면 메모리 또는 리소스 누수가 발생할 수 있습니다.
그러나 예외를 지원하는 언어에서는 그러한 보장을 제공하지 않습니다. 예외를 지원하는 언어에서 명령문 또는 표현식을 실행하면 제어 플로우가 발생하여 메소드가 종료 될 수 있습니다. 즉, finally 또는 키워드를 사용하여 정리를 수행해야합니다.
어쨌든, 나는 많은 사람들이 왜 방법이 좋은지 이해하지 못하고 '방법의 끝에서만 반환'가이드 라인을 인용한다고 생각합니다. 가독성을 높이기 위해 중첩을 줄이는 것이 더 나은 목표 일 것입니다.
나는 가드 조항 인 경우 거꾸로 된 사람들의 이름이 있다고 덧붙이고 싶습니다. 가능할 때마다 사용합니다.
처음에는 두 개의 코드 화면이 있고 다른 곳은없는 곳에서 코드를 읽는 것이 싫습니다. 그냥 뒤집어 놓고 돌아옵니다. 그렇게하면 아무도 스크롤 시간을 낭비하지 않을 것입니다.
http://c2.com/cgi/wiki?GuardClause
그것은 미학에 영향을 줄뿐만 아니라 코드 중첩을 방지합니다.
실제로 데이터가 유효한지 확인하기위한 전제 조건으로 기능 할 수 있습니다.
이것은 물론 주관적이지만 두 가지 측면에서 크게 향상된다고 생각합니다.
- 함수가
condition
보유하고 있는 경우 수행 할 작업이 없음을 즉시 알 수 있습니다. - 중첩 수준을 낮 춥니 다. 중첩은 생각보다 가독성을 떨어 뜨립니다.
여러 반환 지점은 C에서 문제가되었으며 C ++에서는 각 반환 지점 전에 정리 코드를 복제해야했기 때문에 문제가있었습니다. 가비지 콜렉션으로 try
| finally
건설 및 using
블록, 정말 당신이 그들을 두려워해야 할 이유가 없습니다.
궁극적으로 그것은 당신과 당신의 동료들이 더 쉽게 읽을 수있는 것으로 귀착됩니다.
성능 측면에서 두 접근 방식간에 눈에 띄는 차이는 없습니다.
그러나 코딩은 성능 이상의 것입니다. 명확성과 유지 관리도 매우 중요합니다. 성능에 영향을 미치지 않는 이와 같은 경우에는 이것이 유일합니다.
어떤 접근 방식이 바람직한 지에 대한 경쟁 학교가 있습니다.
한 가지 견해는 다른 사람들이 언급 한 견해입니다. 두 번째 접근법은 중첩 수준을 줄여 코드 선명도를 향상시킵니다. 이것은 꼭 필요한 스타일입니다.해야 할 일이 없으면 일찍 돌아올 수도 있습니다.
보다 기능적인 스타일의 관점에서 볼 때 메소드에는 종료 점이 하나만 있어야한다는 것이 또 다른 견해입니다. 기능적 언어의 모든 것은 표현입니다. 따라서 if 문에는 항상 else 절이 있어야합니다. 그렇지 않으면 if 표현식에 항상 값이있는 것은 아닙니다. 따라서 기능적 스타일에서 첫 번째 접근법은 더 자연 스럽습니다.
가드 절 또는 사전 조건 (아마도 알 수 있듯이)은 특정 조건이 충족되는지 확인한 다음 프로그램의 흐름을 중단시킵니다. if
성명서의 한 결과에만 관심이있는 장소에 유용 합니다. 따라서 말하기보다는
if (something) {
// a lot of indented code
}
그 조건이 충족되면 조건을 되돌리고 중단합니다
if (!something) return false; // or another value to show your other code the function did not execute
// all the code from before, save a lot of tabs
return
더럽지 않은 곳은 없습니다 goto
. 함수가 실행할 수 없었던 나머지 코드를 보여주기 위해 값을 전달할 수 있습니다.
중첩 된 조건에서이를 적용 할 수있는 가장 좋은 예를 볼 수 있습니다.
if (something) {
do-something();
if (something-else) {
do-another-thing();
} else {
do-something-else();
}
}
vs :
if (!something) return;
do-something();
if (!something-else) return do-something-else();
do-another-thing();
첫 번째 사람이 더 깨끗하다고 주장하는 사람은 거의 없지만 물론 주관적입니다. 일부 프로그래머는 들여 쓰기로 어떤 조건이 작동하는지 알고 싶어하지만 메서드 흐름을 선형으로 유지하는 것이 좋습니다.
I won't suggest for one moment that precons will change your life or get you laid but you might find your code just that little bit easier to read.
There are several good points made here, but multiple return points can be unreadable as well, if the method is very lengthy. That being said, if you're going to use multiple return points just make sure that your method is short, otherwise the readability bonus of multiple return points may be lost.
Performance is in two parts. You have performance when the software is in production, but you also want to have performance while developing and debugging. The last thing a developer wants is to "wait" for something trivial. In the end, compiling this with optimization enabled will result in similar code. So it's good to know these little tricks that pay off in both scenarios.
The case in the question is clear, ReSharper is correct. Rather than nesting if
statements, and creating new scope in code, you're setting a clear rule at the start of your method. It increases readability, it will be easier to maintain, and it reduces the amount of rules one has to sift through to find where they want to go.
Personally I prefer only 1 exit point. It's easy to accomplish if you keep your methods short and to the point, and it provides a predictable pattern for the next person who works on your code.
eg.
bool PerformDefaultOperation()
{
bool succeeded = false;
DataStructure defaultParameters;
if ((defaultParameters = this.GetApplicationDefaults()) != null)
{
succeeded = this.DoSomething(defaultParameters);
}
return succeeded;
}
This is also very useful if you just want to check the values of certain local variables within a function before it exits. All you need to do is place a breakpoint on the final return and you are guaranteed to hit it (unless an exception is thrown).
Many good reasons about how the code looks like. But what about results?
Let's take a look to some C# code and its IL compiled form:
using System;
public class Test {
public static void Main(string[] args) {
if (args.Length == 0) return;
if ((args.Length+2)/3 == 5) return;
Console.WriteLine("hey!!!");
}
}
This simple snippet can be compiled. You can open the generated .exe file with ildasm and check what is the result. I won't post all the assembler thing but I'll describe the results.
The generated IL code does the following:
- If the first condition is false, jumps to the code where the second is.
- If it's true jumps to the last instruction. (Note: the last instruction is a return).
- In the second condition the same happens after the result is calculated. Compare and: got to the Console.WriteLine if false or to the end if this is true.
- Print the message and return.
So it seems that the code will jump to the end. What if we do a normal if with nested code?
using System;
public class Test {
public static void Main(string[] args) {
if (args.Length != 0 && (args.Length+2)/3 != 5)
{
Console.WriteLine("hey!!!");
}
}
}
The results are quite similar in IL instructions. The difference is that before there were to jumps per condition: if false go to next piece of code, if true go to then end. And now the IL code flows better and has 3 jumps (the compiler optimized this a bit): 1. First jump: when Length is 0 to a part where the code jumps again (Third jump) to the end. 2. Second: in the middle of the second condition to avoid one instruction. 3. Third: if the second condition is false, jump to the end.
Anyway, the program counter will always jump.
That is simply controversial. There is no "agreement among programmers" on the question of early return. It's always subjective, as far as I know.
It's possible to make a performance argument, since it's better to have conditions that are written so they are most often true; it can also be argued that it is clearer. It does, on the other hand, create nested tests.
I don't think you will get a conclusive answer to this question.
In theory, inverting if
could lead to better performance if it increases branch prediction hit rate. In practice, I think it is very hard to know exactly how branch prediction will behave, especially after compiling, so I would not do it in my day-to-day development, except if I am writing assembly code.
More on branch prediction here.
There are a lot of insightful answers there already, but still, I would to direct to a slightly different situation: Instead of precondition, that should be put on top of a function indeed, think of a step-by-step initialization, where you have to check for each step to succeed and then continue with the next. In this case, you cannot check everything at the top.
I found my code really unreadable when writing an ASIO host application with Steinberg's ASIOSDK, as I followed the nesting paradigm. It went like eight levels deep, and I cannot see a design flaw there, as mentioned by Andrew Bullock above. Of course, I could have packed some inner code to another function, and then nested the remaining levels there to make it more readable, but this seems rather random to me.
By replacing nesting with guard clauses, I even discovered a misconception of mine regarding a portion of cleanup-code that should have occurred much earlier within the function instead of at the end. With nested branches, I would never have seen that, you could even say they led to my misconception.
So this might be another situation where inverted ifs can contribute to a clearer code.
Avoiding multiple exit points can lead to performance gains. I am not sure about C# but in C++ the Named Return Value Optimization (Copy Elision, ISO C++ '03 12.8/15) depends on having a single exit point. This optimization avoids copy constructing your return value (in your specific example it doesn't matter). This could lead to considerable gains in performance in tight loops, as you are saving a constructor and a destructor each time the function is invoked.
But for 99% of the cases saving the additional constructor and destructor calls is not worth the loss of readability nested if
blocks introduce (as others have pointed out).
It's a matter of opinion.
My normal approach would be to avoid single line ifs, and returns in the middle of a method.
You wouldn't want lines like it suggests everywhere in your method but there is something to be said for checking a bunch of assumptions at the top of your method, and only doing your actual work if they all pass.
In my opinion early return is fine if you are just returning void (or some useless return code you're never gonna check) and it might improve readability because you avoid nesting and at the same time you make explicit that your function is done.
If you are actually returning a returnValue - nesting is usually a better way to go cause you return your returnValue just in one place (at the end - duh), and it might make your code more maintainable in a whole lot of cases.
I think it depends on what you prefer, as mentioned, theres no general agreement afaik. To reduce annoyment, you may reduce this kind of warning to "Hint"
My idea is that the return "in the middle of a function" shouldn't be so "subjective". The reason is quite simple, take this code:
function do_something( data ){ if (!is_valid_data( data )) return false; do_something_that_take_an_hour( data ); istance = new object_with_very_painful_constructor( data ); if ( istance is not valid ) { error_message( ); return ; } connect_to_database ( ); get_some_other_data( ); return; }
Maybe the first "return" it's not SO intuitive, but that's really saving. There are too many "ideas" about clean codes, that simply need more practise to lose their "subjective" bad ideas.
There are several advantages to this sort of coding but for me the big win is, if you can return quick you can improve the speed of your application. IE I know that because of Precondition X that I can return quickly with an error. This gets rid of the error cases first and reduces the complexity of your code. In a lot of cases because the cpu pipeline can be now be cleaner it can stop pipeline crashes or switches. Secondly if you are in a loop, breaking or returning out quickly can save you a lots of cpu. Some programmers use loop invariants to do this sort of quick exit but in this you can broke your cpu pipeline and even create memory seek problem and mean the the cpu needs to load from outside cache. But basically I think you should do what you intended, that is end the loop or function not create a complex code path just to implement some abstract notion of correct code. If the only tool you have is a hammer then everything looks like a nail.
I'm not sure, but I think, that R# tries to avoid far jumps. When You have IF-ELSE, compiler does something like this:
Condition false -> far jump to false_condition_label
true_condition_label: instruction1 ... instruction_n
false_condition_label: instruction1 ... instruction_n
end block
If condition is true there is no jump and no rollout L1 cache, but jump to false_condition_label can be very far and processor must rollout his own cache. Synchronising cache is expensive. R# tries replace far jumps into short jumps and in this case there is bigger probability, that all instructions are already in cache.
참고URL : https://stackoverflow.com/questions/8437307/does-inverting-the-if-improve-performance
'IT' 카테고리의 다른 글
C ++ rand ()가 같은 크기의 숫자 만 생성하는 이유는 무엇입니까? (0) | 2020.06.14 |
---|---|
당신이 본 가장 우스운 비관은 무엇입니까? (0) | 2020.06.14 |
Firemonkey에서 "No Activate"양식을 만드는 방법 (0) | 2020.06.14 |
언제 std :: forward를 사용하여 인수를 전달합니까? (0) | 2020.06.14 |
Git 소스 제어 하에서 IntelliJ IDEA 프로젝트 파일을 처리하는 방법은 끊임없이 변경됩니까? (0) | 2020.06.14 |