어설 션 또는 예외를 사용하여 계약에 따라 디자인 하시겠습니까? [닫은]
계약으로 프로그래밍 할 때 함수 나 메소드는 먼저 책임을 수행하기 전에 사전 조건이 충족되는지 확인합니다. 이러한 검사를 수행하는 가장 눈에 띄는 두 가지 방법 assert
은 exception
입니다.
- assert는 디버그 모드에서만 실패합니다. 모든 개별 계약 사전 조건을 (단위) 테스트하여 실제로 실패하는지 확인하는 것이 중요합니다.
- 디버그 및 릴리스 모드에서 예외가 실패합니다. 테스트 된 디버그 동작이 릴리스 동작과 동일하지만 런타임 성능 저하가 발생한다는 이점이 있습니다.
어느 쪽이 바람직하다고 생각합니까?
관련 질문을 여기에서보십시오
릴리스 빌드에서 assert를 비활성화하는 것은 "릴리스 빌드에서 어떤 문제도 발생하지 않을 것"이라고 말하는 것과 같으며 종종 그렇지 않습니다. 따라서 릴리스 빌드에서 assert를 비활성화해서는 안됩니다. 그러나 오류가 발생할 때마다 릴리스 빌드가 중단되는 것을 원하지 않습니까?
따라서 예외를 사용하고 잘 사용하십시오. 양호하고 견고한 예외 계층 구조를 사용하고 디버거에서 예외를 throw하여 포착 할 수 있도록하고, 릴리스 모드에서는 일직선 충돌이 아닌 오류를 보상 할 수 있습니다. 더 안전한 방법입니다.
경험상 자신의 오류를 잡으려고 할 때는 어설 션을 사용해야하고 다른 사람의 오류를 잡으려고 할 때는 예외를 사용해야합니다. 다시 말해, 예외를 사용하여 공개 API 함수의 전제 조건을 확인하고 시스템 외부의 데이터를 얻을 때마다 예외를 사용해야합니다. 시스템 내부의 기능 또는 데이터에 대해 어설 션을 사용해야합니다.
내가 따르는 원칙은 이것입니다. 코딩으로 상황을 현실적으로 피할 수 있다면 어설 션을 사용하십시오. 그렇지 않으면 예외를 사용하십시오.
어설 션은 계약이 준수되도록하기위한 것입니다. 고객은 계약을 준수 할 수 있도록 계약이 공정해야합니다. 예를 들어, 계약에서 유효한 URL에 대한 규칙이 알려져 있고 일관성이 없기 때문에 URL이 유효해야한다고 명시 할 수 있습니다.
클라이언트와 서버가 모두 제어 할 수없는 상황은 예외입니다. 예외는 무언가 잘못되었다는 것을 의미하며,이를 피하기 위해 할 수있는 일은 없습니다. 예를 들어 네트워크 연결이 응용 프로그램 제어 외부에 있으므로 네트워크 오류를 피하기 위해 수행 할 수있는 작업이 없습니다.
Assertion / Exception 구별이 실제로 그것에 대해 생각하는 가장 좋은 방법은 아니라고 덧붙이고 싶습니다. 당신이 정말로 생각하고 싶은 것은 계약과 시행 방법입니다. 위의 URL 예제에서 가장 좋은 방법은 URL을 캡슐화하고 Null 또는 유효한 URL 인 클래스를 사용하는 것입니다. 계약을 시행하는 URL로 문자열을 변환하는 것이며 유효하지 않은 경우 예외가 발생합니다. URL 매개 변수가있는 메서드는 String 매개 변수가있는 메서드와 URL을 지정하는 어설 션보다 훨씬 명확합니다.
Asserts는 개발자가 자신이 아니라 팀의 다른 개발자도 잘못한 것을 파악하기위한 것입니다. 사용자 실수로이 조건을 생성 할 수있는 것이 합리적이라면 예외입니다.
마찬가지로 결과에 대해 생각하십시오. 어설 션은 일반적으로 앱을 종료합니다. 조건을 복구 할 수있는 현실적인 기대가있는 경우 예외를 사용해야합니다.
반면, 프로그래머 오류 로만 문제가 발생할 수있는 경우 가능한 빨리 문제를 알고 싶기 때문에 어설트를 사용하십시오. 예외가 발생하여 처리 될 수 있으며 예외를 찾지 못할 것입니다. 그리고 네, 가능성이 가장 적은 경우 앱을 복구하기를 원하므로 릴리스 코드에서 어설 션을 비활성화해야합니다. 프로그램 상태가 심각하게 손상 되더라도 사용자는 작업을 저장할 수 있습니다.
"어설 션은 디버그 모드에서만 실패합니다"라는 것은 사실이 아닙니다.
Bertrand Meyer의 2nd Edition 인 객체 지향 소프트웨어 구성 에서 저자는 릴리스 모드에서 사전 조건을 확인할 수있는 문을 열어 둡니다. 이 경우 어설 션이 실패하면 어설 션 위반 예외가 발생합니다! 이 경우 상황에서 복구 할 수 없습니다. 유용한 작업을 수행 할 수 있지만 오류 보고서를 자동으로 생성하고 경우에 따라 응용 프로그램을 다시 시작해야합니다.
이에 대한 동기는 전제 조건이 일반적으로 변하지 않는 조건과 사후 조건보다 테스트 비용이 저렴하고, 릴리스 빌드의 정확성과 "안전성"이 속도보다 더 중요하다는 것입니다. 즉, 많은 응용 프로그램의 경우 속도는 문제가 아니지만 견고성 (프로그램의 동작이 올바르지 않은 경우, 즉 계약이 깨졌을 때 프로그램이 안전하게 작동하는 능력)입니다.
전제 조건 검사를 항상 활성화 상태로 두어야합니까? 때에 따라 다르지. 그것은 당신에게 달려 있습니다. 보편적 인 대답은 없습니다. 은행 용 소프트웨어를 만드는 경우 $ 1,000 대신 $ 1,000,000를 이체하는 것보다 경고 메시지로 실행을 중단하는 것이 좋습니다. 그러나 게임을 프로그래밍하고 있다면 어떨까요? 어쩌면 당신은 당신이 얻을 수있는 모든 속도가 필요할 수 있으며 누군가가 전제 조건이 잡히지 않은 버그로 인해 10 점 대신 1000 점을 얻는다면, 운이 좋지 않습니다.
두 경우 모두 테스트하는 동안 해당 버그를 발견 한 것이 이상적이며 어설 션을 사용하여 테스트의 상당 부분을 수행해야합니다. 여기서 논의되는 것은 불완전한 테스트로 인해 조기에 발견되지 않은 시나리오에서 프로덕션 코드에서 사전 조건이 실패하는 드문 경우에 가장 적합한 정책입니다.
요약하면, 적어도 에펠 에서는 예외를 사용하도록 설정 한 경우 어설 션을 사용하고 여전히 자동으로 예외를 얻을 수 있습니다 . C ++에서 직접 입력해야한다고 생각합니다.
참조 : 어설 션은 언제 프로덕션 코드에 있어야합니까?
comp.lang.c ++. moderated의 릴리스 빌드에서 어설 션 활성화 / 비활성화와 관련하여 큰 스레드 가있었습니다 . 몇 주가 지나면 이에 대한 의견이 얼마나 다양한 지 알 수 있습니다. :)
coppro 와는 달리 릴리스 빌드에서 어설 션을 비활성화 할 수 있는지 확실하지 않은 경우 어설 션이 아니어야합니다. 주장은 프로그램 불변이 깨지지 않도록 보호하는 것입니다. 이 경우 코드의 클라이언트에 관한 한 두 가지 가능한 결과 중 하나가 있습니다.
- 일종의 OS 유형 오류로 인해 중단되어 중단 호출이 발생합니다. (어설 션없이)
- 직통 전화로 중단하십시오. (어설 션 포함)
그러나 사용자에게는 차이가 없지만 어설 션이 코드가 실패하지 않는 대부분의 실행에 존재하는 코드에서 불필요한 성능 비용을 추가 할 수 있습니다.
질문에 대한 대답은 실제로 API의 클라이언트가 누구인지에 달려 있습니다. API를 제공하는 라이브러리를 작성하는 경우 고객에게 API를 잘못 사용했음을 알리는 메커니즘이 필요합니다. 라이브러리의 두 가지 버전을 제공하지 않는 한 (어설 션이있는 버전,없는 버전이있는 경우) 어설 션은 적절한 선택이 아닙니다.
그러나 개인적으로, 나는이 경우에 대해서도 예외로 갈 것이라고 확신하지 않습니다. 적절한 복구 형태가 발생할 수있는 곳에는 예외가 더 적합합니다. 예를 들어 메모리를 할당하려고 한 것일 수 있습니다. 'std :: bad_alloc'예외를 발견하면 메모리를 확보하고 다시 시도 할 수 있습니다.
I outlined my view on the state of the matter here: How do you validate an object's internal state? . Generally, assert your claims and throw for violation by others. For disabling asserts in release builds, you can do:
- Disable asserts for expensive checks (like checking whether a range is ordered)
- Keep trivial checks enabled (like checking for a null pointer or a boolean value)
Of course, in release builds, failed assertions and uncaught exceptions should be handled another way than in debug builds (where it could just call std::abort). Write a log of the error somewhere (possibly into a file), tell the customer that an internal error occurred. The customer will be able to send you the log-file.
you're asking about the difference between design-time and run-time errors.
asserts are 'hey programmer, this is broken' notifications, they're there to remind you of bugs you wouldn't have noticed when they happened.
exceptions are 'hey user, somethings gone wrong' notifications (obviously you can code to catch them so the user never gets told) but these are designed to occur at run time when Joe user is using the app.
So, if you think you can get all your bugs out, use exceptions only. If you think you can't..... use exceptions. You can still use debug asserts to make the number of exceptions less of course.
Don't forget that many of the preconditions will be user-supplied data, so you will need a good way of informing the user his data was no good. To do that, you'll often need to return error data down the call stack to the bits he is interacting with. Asserts will not be useful then - doubly so if your app is n-tier.
Lastly, I'd use neither - error codes are far superior for errors you think will occur regularly. :)
I prefer the second one. While your tests may have run fine, Murphy says that something unexpected will go wrong. So, instead of getting an exception at the actual erroneous method call, you end up tracing out a NullPointerException (or equivalent) 10 stack frames deeper.
The previous answers are correct: use exceptions for public API functions. The only time you might wish to bend this rule is when the check is computationally expensive. In that case, you can put it in an assert.
If you think violation of that precondition is likely, keep it as an exception, or refactor the precondition away.
You should use both. Asserts are for your convenience as a developer. Exceptions catch things you missed or didn't expect during runtime.
I've grown fond of glib's error reporting functions instead of plain old asserts. They behave like assert statements but instead of halting the program, they just return a value and let the program continue. It works surprisingly well, and as a bonus you get to see what happens to the rest of your program when a function doesn't return "what it's supposed to". If it crashes, you know that your error checking is lax somewhere else down the road.
In my last project, I used these style of functions to implement precondition checking, and if one of them failed, I would print a stack trace to the log file but keep on running. Saved me tons of debugging time when other people would encounter a problem when running my debug build.
#ifdef DEBUG
#define RETURN_IF_FAIL(expr) do { \
if (!(expr)) \
{ \
fprintf(stderr, \
"file %s: line %d (%s): precondition `%s' failed.", \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__, \
#expr); \
::print_stack_trace(2); \
return; \
}; } while(0)
#define RETURN_VAL_IF_FAIL(expr, val) do { \
if (!(expr)) \
{ \
fprintf(stderr, \
"file %s: line %d (%s): precondition `%s' failed.", \
__FILE__, \
__LINE__, \
__PRETTY_FUNCTION__, \
#expr); \
::print_stack_trace(2); \
return val; \
}; } while(0)
#else
#define RETURN_IF_FAIL(expr)
#define RETURN_VAL_IF_FAIL(expr, val)
#endif
If I needed runtime checking of arguments, I'd do this:
char *doSomething(char *ptr)
{
RETURN_VAL_IF_FAIL(ptr != NULL, NULL); // same as assert(ptr != NULL), but returns NULL if it fails.
// Goes away when debug off.
if( ptr != NULL )
{
...
}
return ptr;
}
I tried synthesising several of the other answers here with my own views.
Use assertions for cases where you want to disable it in production, erring toward leaving them in. The only real reason to disable in production, but not in development, is to speed up the program. In most cases, this speed up won't be significant, but sometimes code is time critical or the test is computationally expensive. If code is mission critical, then exceptions may be best despite the slow down.
If there is any real chance of recovery, use an exception as assertions aren't designed to be recovered from. For example, code is rarely designed to recover from programming errors, but it is designed to recover from factors such as network failures or locked files. Errors should not be handled as exceptions simply for being outside the control of the programmer. Rather, the predictability of these errors, compared to coding mistakes, makes them more amiable to recovery.
Re argument that it is easier to debug assertions: The stack trace from a properly named exception is as easy to read as an assertion. Good code should only catch specific types of exceptions, so exceptions should not go unnoticed due to being caught. However, I think Java sometimes forces you to catch all exceptions.
See also this question:
I some cases, asserts are disabled when building for release. You may not have control over this (otherwise, you could build with asserts on), so it might be a good idea to do it like this.
The problem with "correcting" the input values is that the caller will not get what they expect, and this can lead to problems or even crashes in wholly different parts of the program, making debugging a nightmare.
I usually throw an exception in the if-statement to take over the role of the assert in case they are disabled
assert(value>0); if(value<=0) throw new ArgumentOutOfRangeException("value"); //do stuff
The rule of thumb, to me, is that use assert expressions to find internal errors and exceptions for external errors. You can benefit much from the following discussion by Greg from here.
Assert expressions are used to find programming errors: either errors in the program's logic itself or in errors in its corresponding implementation. An assert condition verifies that the program remains in a defined state. A "defined state" is basically one that agrees with the program's assumptions. Note that a "defined state" for a program need not be an "ideal state" or even "a usual state", or even a "useful state" but more on that important point later.
To understand how assertions fit into a program, consider a routine in a C++ program that is about to dereference a pointer. Now should the routine test whether the pointer is NULL before the dereferencing, or should it assert that the pointer is not NULL and then go ahead and dereference it regardless?
I imagine that most developers would want to do both, add the assert, but also check the pointer for a NULL value, in order not to crash should the asserted condition fail. On the surface, performing both the test and the check may seem the wisest decision
Unlike its asserted conditions, a program's error handling (exceptions) refers not to errors in the program, but to inputs the program obtains from its environment. These are often "errors" on someone's part, such as a user attempting to login to an account without typing in a password. And even though the error may prevent a successful completion of program's task, there is no program failure. The program fails to login the user without a password due to an external error - an error on the user's part. If the circumstances were different, and the user typed in the correct password and the program failed to recognize it; then although the outcome would still be the same, the failure would now belong to the program.
The purpose of error handling (exceptions) is two fold. The first is to communicate to the user (or some other client) that an error in program's input has been detected and what it means. The second aim is to restore the application after the error is detected, to a well-defined state. Note that the program itself is not in error in this situation. Granted, the program may be in a non-ideal state, or even a state in which can do nothing useful, but there is no programming errorl. On the contrary, since the error recovery state is one anticipated by the program's design, it iss one that the program can handle.
PS: you may want to check out the similar question: Exception Vs Assertion.
참고URL : https://stackoverflow.com/questions/117171/design-by-contract-using-assertions-or-exceptions
'Programing' 카테고리의 다른 글
HTML 테이블의 세로 (회전) 텍스트 (0) | 2020.07.16 |
---|---|
HTML 문자 코드 8203은 무엇입니까? (0) | 2020.07.16 |
Eclipse 용 JavaScript 편집기 플러그인 (0) | 2020.07.15 |
심볼릭 링크가 작성된 후 가리키는 것을 변경할 수 있습니까? (0) | 2020.07.15 |
STDIN에서 데이터를 읽는 동안 파일 압축 (0) | 2020.07.15 |