Ruby에서`rescue Exception => e`가 왜 나쁜 스타일입니까?
Ryan Davis의 Ruby QuickRef 는 다음과 같이 말합니다 (설명없이).
예외를 구하지 마십시오. 이제까지. 아니면 내가 당신을 찌를 것입니다.
왜 안돼? 옳은 일은 무엇입니까?
TL; DR : StandardError
일반적인 예외 포착에 대신 사용 합니다. 원래 예외가 다시 발생하면 (예 : 예외를 기록하기 위해 구조하는 경우에만) 구조 Exception
가 괜찮을 것입니다.
Exception
의 루트입니다 루비의 예외 계층 구조 , 그래서 rescue Exception
당신이에서 구출 모두 같은 서브 클래스를 포함, SyntaxError
, LoadError
, 그리고 Interrupt
.
구조 Interrupt
는 사용자 CTRLC가 프로그램을 종료하는 데 사용 하는 것을 방지합니다 .
구조 SignalException
는 프로그램이 신호에 올바르게 응답하지 못하게합니다. 를 제외하고는 죽일 수 없습니다 kill -9
.
구출 SyntaxError
은 eval
실패한 사람들이 조용히 그렇게 할 것임을 의미합니다 .
이들 모두는이 프로그램을 실행하고 시도하여 표시 할 수 있습니다 CTRLC또는 kill
그것을 :
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
구조 Exception
는 기본값이 아닙니다. 하기
begin
# iceberg!
rescue
# lifeboats
end
에서 구출하지 않고에서 Exception
구출 StandardError
합니다. 일반적으로 default보다 더 구체적인 것을 지정해야 StandardError
하지만, 구출 하면 범위를 좁히기보다는 범위가 Exception
넓혀 지고 치명적인 결과를 낳고 버그 찾기가 매우 어려워 질 수 있습니다.
구출하려는 상황 StandardError
이 있고 예외가있는 변수가 필요한 경우 다음 양식을 사용할 수 있습니다.
begin
# iceberg!
rescue => e
# lifeboats
end
이는 다음과 같습니다.
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
구조가 정상인 몇 가지 일반적인 경우 중 하나는 Exception
로깅 /보고 목적으로,이 경우 예외를 즉시 다시 발생시켜야합니다.
begin
# iceberg?
rescue Exception => e
# do some logging
raise e # not enough lifeboats ;)
end
실제 규칙은 다음과 같습니다 예외를 버리지 마십시오. 인용문 저자의 객관성은 의심 스럽습니다.
아니면 내가 너를 찌를거야
물론 신호 (기본적으로)는 예외를 발생시키고 일반적으로 장기 실행 프로세스는 신호를 통해 종료되므로 예외를 포착하고 신호 예외를 종료하지 않으면 프로그램을 중지하기가 매우 어려워집니다. 그러니 이렇게하지 마세요 :
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
아니, 정말 하지마. 작동하는지 확인하기 위해 실행하지 마십시오.
그러나 스레드 서버가 있고 모든 예외가 다음과 같이하지 않기를 원한다고 가정합니다.
- 무시 됨 (기본값)
- stop the server (which happens if you say
thread.abort_on_exception = true
).
Then this is perfectly acceptable in your connection handling thread:
begin
# do stuff
rescue Exception => e
myLogger.error("uncaught #{e} exception while handling connection: #{e.message}")
myLogger.error("Stack trace: #{backtrace.map {|l| " #{l}\n"}.join}")
end
The above works out to a variation of Ruby's default exception handler, with the advantage that it doesn't also kill your program. Rails does this in its request handler.
Signal exceptions are raised in the main thread. Background threads won't get them, so there is no point in trying to catch them there.
This is particularly useful in a production environment, where you do not want your program to simply stop whenever something goes wrong. Then you can take the stack dumps in your logs and add to your code to deal with specific exception further down the call chain and in a more graceful manner.
Note also that there is another Ruby idiom which has much the same effect:
a = do_something rescue "something else"
In this line, if do_something
raises an exception, it is caught by Ruby, thrown away, and a
is assigned "something else"
.
Generally, don't do that, except in special cases where you know you don't need to worry. One example:
debugger rescue nil
The debugger
function is a rather nice way to set a breakpoint in your code, but if running outside a debugger, and Rails, it raises an exception. Now theoretically you shouldn't be leaving debug code lying around in your program (pff! nobody does that!) but you might want to keep it there for a while for some reason, but not continually run your debugger.
Note:
If you've run someone else's program that catches signal exceptions and ignores them, (say the code above) then:
- in Linux, in a shell, type
pgrep ruby
, orps | grep ruby
, look for your offending program's PID, and then runkill -9 <PID>
. - in Windows, use the Task Manager (CTRL-SHIFT-ESC), go to the "processes" tab, find your process, right click it and select "End process".
- in Linux, in a shell, type
If you are working with someone else's program which is, for whatever reason, peppered with these ignore-exception blocks, then putting this at the top of the mainline is one possible cop-out:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
This causes the program to respond to the normal termination signals by immediately terminating, bypassing exception handlers, with no cleanup. So it could cause data loss or similar. Be careful!
If you need to do this:
begin do_something rescue Exception => e critical_cleanup raise end
you can actually do this:
begin do_something ensure critical_cleanup end
In the second case,
critical cleanup
will be called every time, whether or not an exception is thrown.
Let's say you are in a car (running Ruby). You recently installed a new steering wheel with the over-the-air upgrade system (which uses eval
), but you didn't know one of the programmers messed up on syntax.
You are on a bridge, and realize you are going a bit towards the railing, so you turn left.
def turn_left
self.turn left:
end
oops! That's probably Not Good™, luckily, Ruby raises a SyntaxError
.
The car should stop immediately - right?
Nope.
begin
#...
eval self.steering_wheel
#...
rescue Exception => e
self.beep
self.log "Caught #{e}.", :warn
self.log "Logged Error - Continuing Process.", :info
end
beep beep
Warning: Caught SyntaxError Exception.
Info: Logged Error - Continuing Process.
You notice something is wrong, and you slam on the emergency breaks (^C
: Interrupt
)
beep beep
Warning: Caught Interrupt Exception.
Info: Logged Error - Continuing Process.
Yeah - that didn't help much. You're pretty close to the rail, so you put the car in park (kill
ing: SignalException
).
beep beep
Warning: Caught SignalException Exception.
Info: Logged Error - Continuing Process.
At the last second, you pull out the keys (kill -9
), and the car stops, you slam forward into the steering wheel (the airbag can't inflate because you didn't gracefully stop the program - you terminated it), and the computer in the back of your car slams into the seat in front of it. A half-full can of Coke spills over the papers. The groceries in the back are crushed, and most are covered in egg yolk and milk. The car needs serious repair and cleaning. (Data Loss)
Hopefully you have insurance (Backups). Oh yeah - because the airbag didn't inflate, you're probably hurt (getting fired, etc).
But wait! There's more reasons why you might want to use rescue Exception => e
!
Let's say you're that car, and you want to make sure the airbag inflates if the car is exceeding its safe stopping momentum.
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Here's the exception to the rule: You can catch Exception
only if you re-raise the exception. So, a better rule is to never swallow Exception
, and always re-raise the error.
But adding rescue is both easy to forget in a language like Ruby, and putting a rescue statement right before re-raising an issue feels a little non-DRY. And you do not want to forget the raise
statement. And if you do, good luck trying to find that error.
Thankfully, Ruby is awesome, you can just use the ensure
keyword, which makes sure the code runs. The ensure
keyword will run the code no matter what - if an exception is thrown, if one isn't, the only exception being if the world ends (or other unlikely events).
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Boom! And that code should run anyways. The only reason you should use rescue Exception => e
is if you need access to the exception, or if you only want code to run on an exception. And remember to re-raise the error. Every time.
Note: As @Niall pointed out, ensure always runs. This is good because sometimes your program can lie to you and not throw exceptions, even when issues occur. With critical tasks, like inflating airbags, you need to make sure it happens no matter what. Because of this, checking every time the car stops, whether an exception is thrown or not, is a good idea. Even though inflating airbags is a bit of an uncommon task in most programming contexts, this is actually pretty common with most cleanup tasks.
TL;DR
Don't rescue Exception => e
(and not re-raise the exception) - or you might drive off a bridge.
Because this captures all exceptions. It's unlikely that your program can recover from any of them.
You should handle only exceptions that you know how to recover from. If you don't anticipate a certain kind of exception, don't handle it, crash loudly (write details to the log), then diagnose logs and fix code.
Swallowing exceptions is bad, don't do this.
That's a specific case of the rule that you shouldn't catch any exception you don't know how to handle. If you don't know how to handle it, it's always better to let some other part of the system catch and handle it.
참고URL : https://stackoverflow.com/questions/10048173/why-is-it-bad-style-to-rescue-exception-e-in-ruby
'development' 카테고리의 다른 글
노드 js에서 npm 모듈을 제거하는 방법은 무엇입니까? (0) | 2020.09.28 |
---|---|
Bash 함수에 매개 변수 전달 (0) | 2020.09.28 |
JavaScript에서 올해 가져 오기 (0) | 2020.09.28 |
PHP에서 요청 유형 감지 (GET, POST, PUT 또는 DELETE) (0) | 2020.09.28 |
정적으로 입력되는 언어와 동적으로 입력되는 언어의 차이점은 무엇입니까? (0) | 2020.09.28 |