Java 또는 C #의 예외 관리 모범 사례
내 응용 프로그램에서 예외를 처리하는 방법을 결정하고 있습니다.
예외와 관련된 문제가 1) 원격 서비스를 통해 데이터에 액세스하거나 2) JSON 객체를 직렬화 해제하는 경우가 많습니다. 불행히도 나는이 작업 중 하나 (네트워크 연결 끊기, 잘못된 JSON 객체로 제어 할 수 없음)에 대한 성공을 보장 할 수 없습니다.
결과적으로 예외가 발생하면 단순히 함수 내에서 예외를 포착하고 호출자에게 FALSE를 반환합니다. 내 논리는 모든 발신자가 실제로 관심을 갖는 것은 작업이 성공했는지 여부가 아니라 성공하지 못한 것입니다.
다음은 일반적인 방법의 샘플 코드 (JAVA)입니다.
public boolean doSomething(Object p_somthingToDoOn)
{
boolean result = false;
try{
// if dirty object then clean
doactualStuffOnObject(p_jsonObject);
//assume success (no exception thrown)
result = true;
}
catch(Exception Ex)
{
//don't care about exceptions
Ex.printStackTrace();
}
return result;
}
이 방법은 훌륭하다고 생각하지만 예외 관리에 대한 모범 사례가 무엇인지 알고 싶습니다. (실제로 예외를 호출 스택까지 버블 링해야합니까?)
주요 질문 요약 :
- 예외를 잡을 수는 있지만 예외적으로 거품을 일으키거나 공식적으로 시스템에 알리지 마십시오 (로그 또는 사용자에게 알림을 통해)?
- try / catch 블록이 필요한 모든 것을 초래하지 않는 예외에 대한 모범 사례는 무엇입니까?
후속 / 편집
모든 의견을 보내 주셔서 감사합니다. 온라인 예외 관리에 대한 훌륭한 소스를 발견했습니다.
- 예외 처리 모범 사례 | 오라일리 미디어
- .NET의 예외 처리 모범 사례
- 모범 사례 : 예외 관리 (이 문서는 이제 archive.org 사본을 가리킴)
- 예외 처리 반 패턴
예외 관리는 상황에 따라 다른 것들 중 하나 인 것 같습니다. 그러나 가장 중요한 것은 시스템 내에서 예외를 관리하는 방식이 일관성이 있어야한다는 것입니다.
또한 과도한 시도 / 캐치를 통해 예외를 존중하지 않는 코드 로트 (code-rot)에주의하십시오 (예외는 시스템에 경고를 주며 경고해야 할 사항은 무엇입니까?).
또한 이것은 m3rLinEz의 훌륭한 선택 의견입니다 .
나는 Anders Hejlsberg에 동의하는 경향이 있으며, 대부분의 발신자는 작업이 성공했는지 여부 만 신경 쓰게됩니다.
이 의견에서 예외를 다룰 때 고려해야 할 몇 가지 질문이 있습니다.
- 이 예외가 발생하는 지점은 무엇입니까?
- 그것을 처리하는 것이 어떻게 합리적입니까?
- 발신자가 실제로 예외를 처리합니까? 아니면 호출이 성공했는지 만 신경 쓰나요?
- 호출자가 잠재적 예외를 관리하도록 강요하고 있습니까?
- 당신은 언어의 우상을 존중합니까?
- 부울과 같은 성공 플래그를 반환해야합니까? 부울 (또는 int)을 반환하는 것은 Java (Java에서는 예외를 처리 할 것)보다 C 사고 방식에 가깝습니다.
- 언어와 관련된 오류 관리 구조를 따르십시오 :)!
예외를 잡아서 오류 코드로 바꾸고 싶은 것이 이상하게 보입니다. Java와 C #에서 기본값이 기본값 인 경우 호출자가 예외보다 오류 코드를 선호하는 이유는 무엇입니까?
당신의 질문에 관해서는 :
- 실제로 처리 할 수있는 예외 만 잡아야합니다. 예외를 잡는 것이 대부분의 경우 올바른 일이 아닙니다. 몇 가지 예외 (예 : 스레드 간 로깅 및 마샬링 예외)가 있지만 이러한 경우에도 일반적으로 예외를 다시 발생시켜야합니다.
- 코드에 많은 try / catch 문이 없어야합니다. 다시 한 번, 처리 할 수있는 예외 만 잡는 것이 좋습니다. 처리되지 않은 예외를 최종 사용자에게 다소 유용한 것으로 바꾸는 최상위 예외 처리기를 포함 할 수 있지만 가능한 모든 장소에서 모든 예외를 포착하려고 시도해서는 안됩니다.
응용 프로그램과 상황에 따라 다릅니다. 라이브러리 구성 요소를 빌드하는 경우 예외는 구성 요소와 컨텍스트를 갖도록 랩핑되어야하지만 예외를 버블 링해야합니다. 예를 들어 Xml 데이터베이스를 구축 할 때 파일 시스템을 사용하여 데이터를 저장하고 있고 파일 시스템 권한을 사용하여 데이터를 보호한다고 가정합니다. 구현이 누출되는 FileIOAccessDenied 예외를 버블 링하고 싶지 않을 것입니다. 대신 예외를 래핑하고 AccessDenied 오류가 발생합니다. 구성 요소를 타사에 배포하는 경우 특히 그렇습니다.
예외를 삼키는 것이 괜찮다면. 시스템에 따라 다릅니다. 응용 프로그램이 실패 사례를 처리 할 수 있고 실패한 이유를 사용자에게 알리면 아무런 이점이없는 경우 로그에 실패하는 것이 좋습니다. 나는 항상 문제를 해결하고 예외를 삼키는 것을 발견하기 위해 좌절감을 느꼈습니다 (또는 예외를 바꾸고 내부 예외를 설정하지 않고 대신 새 예외를 던짐).
일반적으로 다음 규칙을 사용합니다.
- 내 구성 요소 및 라이브러리에서 처리하거나 처리하려는 경우에만 예외를 잡습니다. 또는 예외적으로 추가 상황 정보를 제공하려는 경우.
- 응용 프로그램 진입 점 또는 가능한 가장 높은 수준에서 일반적인 시도 캐치를 사용합니다. 예외가 발생하면 그냥 기록하고 실패하게하십시오. 이상적으로는 예외가 발생하지 않아야합니다.
다음 코드가 냄새가 나는 것으로 나타났습니다.
try
{
//do something
}
catch(Exception)
{
throw;
}
이와 같은 코드는 의미가 없으며 포함해서는 안됩니다.
나는 그 주제에 관한 또 다른 좋은 출처를 추천하고 싶습니다. C #과 Java, Anders Hejlsberg, James Gosling의 발명가와 Java의 Checked Exception 주제에 대한 인터뷰입니다.
페이지 하단에는 훌륭한 자료도 있습니다.
나는 Anders Hejlsberg에 동의하는 경향이 있으며, 대부분의 발신자는 작업이 성공했는지 여부 만 신경 쓰게됩니다.
Bill Venners : 확인 된 예외와 관련하여 확장 성과 버전 관리 문제를 언급했습니다. 이 두 가지 문제가 무엇을 의미하는지 명확히 할 수 있습니까?
Anders Hejlsberg : 버전 관리부터 시작해 봅시다. 문제는보기가 쉽기 때문입니다. 예외 A, B 및 C를 발생시키는 것으로 선언하는 foo 메소드를 작성한다고 가정 해 봅시다. foo의 버전 2에서 여러 기능을 추가하고 싶습니다. 이제 foo가 예외 D를 발생시킬 수 있습니다. 해당 메소드의 기존 호출자가 해당 예외를 거의 처리하지 않기 때문에 D를 해당 메소드의 throws 절에 추가하십시오.
새 버전에서 throws 절에 새 예외를 추가하면 클라이언트 코드가 중단됩니다. 인터페이스에 메소드를 추가하는 것과 같습니다. 인터페이스를 게시 한 후에는 인터페이스를 구현할 때 다음 버전에서 추가 할 메소드가있을 수 있으므로 모든 실제적인 목적을 위해 변경할 수 없습니다. 대신 새로운 인터페이스를 만들어야합니다. 예외와 마찬가지로, 더 많은 예외를 발생시키는 foo2라는 완전히 새로운 메소드를 작성하거나 새 foo에서 예외 D를 포착하고 D를 A, B 또는 C로 변환해야합니다.
Bill Venners: But aren't you breaking their code in that case anyway, even in a language without checked exceptions? If the new version of foo is going to throw a new exception that clients should think about handling, isn't their code broken just by the fact that they didn't expect that exception when they wrote the code?
Anders Hejlsberg: No, because in a lot of cases, people don't care. They're not going to handle any of these exceptions. There's a bottom level exception handler around their message loop. That handler is just going to bring up a dialog that says what went wrong and continue. The programmers protect their code by writing try finally's everywhere, so they'll back out correctly if an exception occurs, but they're not actually interested in handling the exceptions.
The throws clause, at least the way it's implemented in Java, doesn't necessarily force you to handle the exceptions, but if you don't handle them, it forces you to acknowledge precisely which exceptions might pass through. It requires you to either catch declared exceptions or put them in your own throws clause. To work around this requirement, people do ridiculous things. For example, they decorate every method with, "throws Exception." That just completely defeats the feature, and you just made the programmer write more gobbledy gunk. That doesn't help anybody.
EDIT: Added more details on the converstaion
Checked exceptions are a controversial issue in general, and in Java in particular (later on I'll try to find some examples for those in favor and opposed to them).
As rules of thumb, exception handling should be something around these guidelines, in no particular order:
- For the sake of maintainability, always log exceptions so that when you start seeing bugs, the log will assist in pointing you to the place your bug has likely started. Never leave
printStackTrace()
or the likes of it, chances are one of your users will get one of those stack traces eventually, and have exactly zero knowledge as to what to do with it. - Catch exceptions you can handle, and only those, and handle them, don't just throw them up the stack.
- Always catch a specific exception class, and generally you should never catch type
Exception
, you are very likely to swallow otherwise important exceptions. - Never (ever) catch
Error
s!!, meaning: Never catchThrowable
s asError
s are subclasses of the latter.Error
s are problems you will most likely never be able to handle (e.g.OutOfMemory
, or other JVM issues)
Regarding your specific case, make sure that any client calling your method will receive the proper return value. If something fails, a boolean-returning method might return false, but make sure the places you call that method are able to handle that.
You should only catch the exceptions you can deal with. For example, if you're dealing with reading over a network and the connection times out and you get an exception you can try again. However if you're reading over a network and get a IndexOutOfBounds exception, you really can't handle that because you don't (well, in this case you wont) know what caused it. If you're going to return false or -1 or null, make sure it's for specific exceptions. I don't want a library I'm using returning a false on a network read when the exception thrown is the heap is out of memory.
Exceptions are errors that are not part of normal program execution. Depending on what your program does and its uses (i.e. a word processor vs. a heart monitor) you will want to do different things when you encounter an exception. I have worked with code that uses exceptions as part of normal execution and it is definitely a code smell.
Ex.
try
{
sendMessage();
if(message == success)
{
doStuff();
}
else if(message == failed)
{
throw;
}
}
catch(Exception)
{
logAndRecover();
}
This code makes me barf. IMO you should not recover from exceptions unless its a critical program. If your throwing exceptions then bad things are happening.
All of the above seems reasonable, and often your workplace may have a policy. At our place we have defined to types of Exception: SystemException
(unchecked) and ApplicationException
(checked).
We have agreed that SystemException
s are unlikely to be recoverable and will bve handled once at the top. To provide further context, our SystemException
s are exteneded to indicate where they occurred, e.g. RepositoryException
, ServiceEception
, etc.
ApplicationException
s could have business meaning like InsufficientFundsException
and should be handled by client code.
Witohut a concrete example, it's difficult to comment on your implementation, but I would never use return codes, they're a maintenance issue. You might swallow an Exception, but you need to decide why, and always log the event and stacktrace. Lastly, as your method has no other processing it's fairly redundant (except for encapsulation?), so doactualStuffOnObject(p_jsonObject);
could return a boolean!
After some thought and looking at your code it seems to me that you are simply rethrowing the exception as a boolean. You could just let the method pass this exception through (you don't even have to catch it) and deal with it in the caller, since that's the place where it matters. If the exception will cause the caller to retry this function, the caller should be the one catching the exception.
It can at times happen that the exception you are encountering will not make sense to the caller (i.e. it's a network exception), in which case you should wrap it in a domain specific exception.
If on the other hand, the exception signals an unrecoverable error in your program (i.e. the eventual result of this exception will be program termination) I personally like to make that explicit by catching it and throwing a runtime exception.
If you are going to use the code pattern in your example, call it TryDoSomething, and catch only specific exceptions.
Also consider using an Exception Filter when logging exceptions for diagnostic purposes. VB has language support for Exception filters. The link to Greggm's blog has an implementation that can be used from C#. Exception filters have better properties for debuggability over catch and rethrow. Specifically you can log the problem in the filter and let the exception continue to propagate. That method allows an attaching a JIT (Just in Time) debugger to have the full original stack. A rethrow cuts the stack off at the point it was rethrown.
The cases where TryXXXX makes sense are when you are wrapping a third party function that throws in cases that are not truly exceptional, or are simple difficult to test without calling the function. An example would be something like:
// throws NumberNotHexidecimalException
int ParseHexidecimal(string numberToParse);
bool TryParseHexidecimal(string numberToParse, out int parsedInt)
{
try
{
parsedInt = ParseHexidecimal(numberToParse);
return true;
}
catch(NumberNotHexidecimalException ex)
{
parsedInt = 0;
return false;
}
catch(Exception ex)
{
// Implement the error policy for unexpected exceptions:
// log a callstack, assert if a debugger is attached etc.
LogRetailAssert(ex);
// rethrow the exception
// The downside is that a JIT debugger will have the next
// line as the place that threw the exception, rather than
// the original location further down the stack.
throw;
// A better practice is to use an exception filter here.
// see the link to Exception Filter Inject above
// http://code.msdn.microsoft.com/ExceptionFilterInjct
}
}
Whether you use a pattern like TryXXX or not is more of a style question. The question of catching all exceptions and swallowing them is not a style issue. Make sure unexpected exceptions are allowed to propagate!
I suggest taking your cues from the standard library for the language you're using. I can't speak for C#, but let's look at Java.
For example java.lang.reflect.Array has a static set
method:
static void set(Object array, int index, Object value);
The C way would be
static int set(Object array, int index, Object value);
... with the return value being a success indicator. But you're not in C world any more.
Once you embrace exceptions, you should find that it makes your code simpler and clearer, by moving your error handling code away from your core logic. Aim to have lots of statements in a single try
block.
As others have noted - you should be as specific as possible in the kind of exception you catch.
If you're going to catch an Exception and return false, it should be a very specific exception. You're not doing that, you're catching all of them and returning false. If I get a MyCarIsOnFireException I want to know about it right away! The rest of the Exceptions I might not care about. So you should have a stack of Exception handlers that say "whoa whoa something is wrong here" for some exceptions (rethrow, or catch and rethrow a new exception that explains better what happened) and just return false for others.
If this is a product that you'll be launching you should be logging those exceptions somewhere, it will help you tune things up in the future.
Edit: As to the question of wrapping everything in a try/catch, I think the answer is yes. Exceptions should be so rare in your code that the code in the catch block executes so rarely that it doesn't hit performance at all. An exception should be a state where your state machine broke and doesn't know what to do. At least rethrow an exception that explains what was happening at the time and has the caught exception inside of it. "Exception in method doSomeStuff()" isn't very helpful for anyone who has to figure out why it broke while you're on vacation (or at a new job).
My strategy:
If the original function returned void I change it to return bool. If exception/error occurred return false, if everything was fine return true.
If the function should return something then when exception/error occurred return null, otherwise the returnable item.
Instead of bool a string could be returned containing the description of the error.
In every case before returning anything log the error.
Some excellent answers here. I would like to add, that if you do end up with something like you posted, at least print more than the stack trace. Say what you were doing at the time, and Ex.getMessage(), to give the developer a fighting chance.
try/catch blocks form a second set of logic embedded over the first (main) set, as such they are a great way to pound out unreadable, hard to debug spaghetti code.
Still, used reasonably they work wonders in readability, but you should just follow two simple rules:
use them (sparingly) at the low-level to catch library handling issues, and stream them back into the main logical flow. Most of the error handling we want, should be coming from the code itself, as part of the data itself. Why make special conditions, if the returning data isn't special?
use one big handler at the higher-level to manage any or all of the weird conditions arising in the code that aren't caught at a low-level. Do something useful with the errors (logs, restarts, recoveries, etc).
Other than these two types of error handling, all of the rest of the code in the middle should be free and clear of try/catch code and error objects. That way, it works simply and as expected no matter where you use it, or what you do with it.
Paul.
I may be a little late with the answer but error handling is something that we can always change and evolve along time. If you want to read something more about this subject I wrote a post in my new blog about it. http://taoofdevelopment.wordpress.com
Happy coding.
'IT story' 카테고리의 다른 글
MongoDB / NoSQL : 문서 변경 기록 유지 (0) | 2020.07.19 |
---|---|
Java에서 메소드 매개 변수를 final로 선언 해야하는 성능상의 이유가 있습니까? (0) | 2020.07.19 |
Git에서 현재 커밋 해시를 같은 커밋의 파일에 쓰는 방법 (0) | 2020.07.19 |
Java에서 널 참조의 정적 필드 (0) | 2020.07.19 |
Cython 코드가 포함 된 Python 패키지를 어떻게 구성해야합니까 (0) | 2020.07.19 |