Ruby에서`Exception => e`를 구제하는 것이 왜 나쁜 스타일입니까?


894

Ryan Davis의 Ruby QuickRef 는 다음과 같이 말합니다.

예외를 구출하지 마십시오. 이제까지. 아니면 널 찌를거야

왜 안돼? 옳은 일이 무엇입니까?


35
그럼 당신은 아마 자신을 쓸 수 있습니까? :)
Sergio Tulentsev

65
나는 폭력에 대한 부름이 매우 불편하다. 그것은 단지 프로그래밍입니다.
Darth

1
멋진 Ruby Exception Hierarchy 와 함께 Ruby Exception 에서이 기사 를 살펴보십시오 .
Atul Khanduri

2
라이언 데이비스가 널 찌르니까 그래서 아이들. 예외를 구출하지 마십시오.
Mugen

7
@DarthEgregious 나는 당신이 농담인지 아닌지 알 수 없습니다. 그러나 나는 그것이 재미 있다고 생각합니다. (그리고 그것은 심각한 위협이 아닙니다). 이제 예외를 잡을 때마다 인터넷의 임의의 사람이 찔릴 가치가 있는지 고려합니다.
Steve Sether

답변:


1375

TL; DR : StandardError일반적인 예외 포착에 대신 사용하십시오 . 원래 예외가 다시 발생하면 (예 : 예외 만 기록하도록 구조 할 때) 구조 Exception는 괜찮습니다.


Exception의 루트입니다 루비의 예외 계층 구조 , 그래서 rescue Exception당신이에서 구출 모두 같은 서브 클래스를 포함, SyntaxError, LoadError, 그리고 Interrupt.

구조 Interrupt는 사용자 CTRLC가 프로그램을 종료하는 데 사용하지 못하게합니다 .

구조 SignalException는 프로그램이 신호에 올바르게 응답하지 못하게합니다. 를 제외하고는 죽을 수 없습니다 kill -9.

구조 SyntaxErroreval실패한 것이 자동으로 수행됨을 의미합니다 .

이들 모두는이 프로그램을 실행하고 시도하여 표시 할 수 있습니다 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. 일반적으로 기본값보다 더 구체적인 것을 지정해야 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 # not enough lifeboats ;)
end

129
그래서 잡기처럼 Throwable자바
래칫 괴물

53
이 조언은 깨끗한 Ruby 환경에 좋습니다. 그러나 불행히도 많은 보석들이 예외에서 직접 내려 오는 예외를 만들었습니다. 우리 환경에는 OpenID :: Server :: EncodingError, OAuth :: InvalidRequest, HTMLTokenizerSample과 같은 30 가지가 있습니다. 이것들은 표준 구조 블록에서 매우 많이 잡아야 할 예외입니다. 불행히도 루비의 어떤 것도 보석이 예외에서 직접 상속되는 것을 막거나 방해하지 않습니다. 심지어 이름조차도 직관적이지 않습니다.
Jonathan Swartz

20
@JonathanSwartz 그런 다음 예외가 아닌 특정 하위 클래스에서 구출하십시오. 보다 구체적으로는 거의 항상 더 좋고 명확합니다.
Andrew Marshall

22
@JonathanSwartz-보석 제작자들에게 예외가 상속되는 것을 변경하도록 버그를 줄 것입니다. 개인적으로, 나는 모든 보석이 MyGemException에서 내려 오는 것을 원하므로 원한다면 구제 할 수 있습니다.
Nathan Long

12
당신은 또한 수있는 ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]다음rescue *ADAPTER_ERRORS => e
j_mcnally

83

실제 규칙은 다음과 같습니다 예외를 버리지 마십시오. 당신의 인용문 저자의 객관성은 의심의 여지가 있습니다.

아니면 널 찌를거야

물론 신호 (기본적으로)에서 예외가 발생하고 일반적으로 장기 실행 프로세스가 신호를 통해 종료되므로 예외를 포착하고 신호 예외를 종료하지 않으면 프로그램을 중지하기가 매우 어려워집니다. 따라서 이것을하지 마십시오 :

#! /usr/bin/ruby

while true do
  begin
    line = STDIN.gets
    # heavy processing
  rescue Exception => e
    puts "caught exception #{e}! ohnoes!"
  end
end

아뇨, 정말로하지 마십시오. 작동하는지 확인하기 위해 실행하지 마십시오.

그러나 스레드 서버가 있고 모든 예외를 원하지 않는다고 가정하십시오.

  1. 무시 됨 (기본값)
  2. 서버를 중지하십시오 (라고 말하면 발생합니다 thread.abort_on_exception = true).

그런 다음 연결 처리 스레드에서 완벽하게 허용됩니다.

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

위의 내용은 Ruby의 기본 예외 처리기의 변형을 해결하며 프로그램을 죽이지 않는다는 이점이 있습니다. Rails는 요청 핸들러에서이를 수행합니다.

메인 스레드에서 신호 예외가 발생합니다. 백그라운드 스레드는 그들을 얻지 못하므로 거기에서 잡으려고 시도 할 필요가 없습니다.

이것은 무언가 잘못 될 때마다 프로그램이 단순히 중지되는 것을 원하지 않는 프로덕션 환경에서 특히 유용합니다 . 그런 다음 로그에서 스택 덤프를 가져 와서 코드에 추가하여 특정 예외를 처리하고 콜 체인에서보다 우아한 방식으로 처리 할 수 ​​있습니다.

또한 동일한 효과를 갖는 또 다른 Ruby 관용구가 있습니다.

a = do_something rescue "something else"

이 줄에서 do_something예외가 발생하면 Ruby에서 예외를 잡아서 버리고 a할당됩니다 "something else".

일반적으로, 당신은 특별한 경우를 제외하고, 그렇게하지 않습니다 알고 당신이 걱정할 필요가 없습니다. 한 가지 예 :

debugger rescue nil

debugger함수는 코드에서 중단 점을 설정하는 좋은 방법이지만 디버거 및 Rails 외부에서 실행하면 예외가 발생합니다. 이제 이론적으로는 디버그 코드를 프로그램에 두지 말아야하지만 (pff! 아무도 그렇게하지 않습니다!) 어떤 이유로 코드를 잠시 동안 유지하고 싶지만 디버거를 계속 실행하지는 않을 것입니다.

노트 :

  1. 신호 예외를 포착하고 무시하는 다른 사람의 프로그램을 실행 한 경우 (위의 코드와 같이) 다음을 수행하십시오.

    • Linux의 경우 쉘에서 pgrep ruby, 또는을 입력 ps | grep ruby하여 문제가되는 프로그램의 PID를 찾은 다음를 실행하십시오 kill -9 <PID>.
    • (윈도우에서 작업 관리자를 사용하여 CTRL- SHIFT- ESC) 마우스 오른쪽을 클릭, 프로세스를 찾습니다 "프로세스"탭으로 이동하여 "프로세스 끝내기"를 선택합니다.
  2. 어떤 이유로 든 이러한 무시 예외 블록을 사용하는 다른 사람의 프로그램으로 작업하는 경우 이것을 메인 라인의 맨 위에 두는 것이 하나의 가능한 실패입니다.

    %W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }

    이로 인해 프로그램은 정리없이 예외 처리기를 무시하고 즉시 종료하여 일반 종료 신호에 응답합니다 . 따라서 데이터 손실 또는 이와 유사한 원인이 될 수 있습니다. 조심해!

  3. 이 작업을 수행해야하는 경우 :

    begin
      do_something
    rescue Exception => e
      critical_cleanup
      raise
    end
    

    당신은 실제로 이것을 할 수 있습니다 :

    begin
      do_something
    ensure
      critical_cleanup
    end
    

    두 번째 경우 critical cleanup에는 예외 발생 여부에 관계없이 매번 호출됩니다.


21
죄송합니다. 잘못되었습니다. 서버는 예외를 구출 해서는 안되며 로그 만 기록하면됩니다. 에 의해 제외하고는 그것을 죽일 수 없게 만들 것 kill -9입니다.
John

8
참고 3의 예제는 동등하지 않으며 ensure예외가 발생했는지 여부에 관계없이 rescue실행되며 예외가 발생한 경우에만 실행됩니다.
Andrew Marshall

1
그것들은 / 정확하게 / 동등하지는 않지만 추악하지 않은 방식으로 동등성을 간결하게 표현하는 방법을 알 수는 없습니다.
Michael Slade

3
첫 번째 예에서 begin / rescue 블록 뒤에 다른 critical_cleanup 호출을 추가하십시오. 가장 우아한 코드는 아니지만 동의하지만 분명히 두 번째 예제는 우아한 방법이므로 약간의 우아함이 예제의 일부입니다.
gtd

3
"작동하는지 확인하기 위해 실행하지 마십시오." 코딩에 대한 나쁜 조언 인 것 같습니다 ... 반면에, 나는 그것을 실행하고, 실패를보고, 누군가를 맹목적으로 믿는 대신 실패가 어떻게되는지 스스로 이해하는 것이 좋습니다. 어쨌든 큰 대답 :)
huelbois

69

TL; DR

rescue Exception => e예외를 다시 발생 시키지 마십시오. 또는 다리에서 벗어날 수 있습니다 .


당신이 차 안에 있다고 가정 해 봅시다 (루비를 실행). 최근에 무선 업그레이드 시스템 (을 사용하는 eval) 으로 새 스티어링 휠을 설치 했지만 구문에 엉망인 프로그래머 중 한 명을 몰랐습니다.

당신은 다리에 있고, 당신이 난간을 향해 조금 가고 있다는 것을 알고 있습니다. 그래서 당신은 좌회전합니다.

def turn_left
  self.turn left:
end

죄송합니다! 그건 아마 좋은하지 다행히, 루비가 제기 ™ SyntaxError.

차는 즉시 멈춰야 해요.

아니.

begin
  #...
  eval self.steering_wheel
  #...
rescue Exception => e
  self.beep
  self.log "Caught #{e}.", :warn
  self.log "Logged Error - Continuing Process.", :info
end

삐 삐

경고 : SyntaxError Exception이 발생했습니다.

정보 : 기록 된 오류-계속되는 프로세스.

당신은 뭔가 잘못 통지, 당신은 비상 브레이크에 슬램 ( ^C: Interrupt)

삐 삐

경고 : 인터럽트 예외가 발생했습니다.

정보 : 기록 된 오류-계속되는 프로세스.

네, 그다지 도움이되지 않았습니다. 레일과 매우 가까우므로 차를 공원에 두십시오 ( killing :) SignalException.

삐 삐

경고 : SignalException 예외가 발생했습니다.

정보 : 기록 된 오류-계속되는 프로세스.

마지막 2 초 동안 키 ( kill -9) 를 당기면 차가 멈추고 스티어링 휠로 앞으로 밀립니다 (프로그램이 정상적으로 멈추지 않아 에어백이 팽창 할 수 없습니다-종료했습니다). 차 뒷좌석에서 앞 좌석으로 내려갑니다. 코카콜라 절반이 종이 위에 쏟아져 나옵니다. 뒷면의 식료품은 분쇄되어 있으며 대부분 달걀 노른자와 우유로 덮여 있습니다. 차는 심각한 수리와 청소가 필요합니다. (데이터 손실)

바라건대 당신은 보험 (백업)이 있습니다. 네, 에어백이 팽창하지 않았기 때문에 다칠 수 있습니다 (해고 등).


하지만 기다려! 있다왜 당신이 사용하고 싶을까요 rescue Exception => e!

당신이 그 차라고 말하고 자동차가 안전한 정지 운동량을 초과하면 에어백이 팽창하는지 확인하고 싶다고 가정 해 봅시다.

 begin 
    # do driving stuff
 rescue Exception => e
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
    raise
 end

규칙에 대한 예외는 다음과 같습니다. 예외를 Exception 다시 제기 한 경우에만 잡을 수 있습니다 . 따라서 더 나은 규칙은 절대 삼키지 않고 Exception항상 오류를 다시 발생시키는 것입니다.

그러나 구조를 추가하는 것은 Ruby와 같은 언어에서 잊어 버리기 쉽고 문제를 제기하기 직전에 구조 진술을하는 것은 약간 비 건조한 느낌입니다. 그리고 당신 raise진술 을 잊고 싶지 않습니다 . 그리고 그렇게하면, 그 오류를 찾으려고 행운을 빕니다.

고맙게도 루비는 훌륭합니다. ensure키워드를 사용하면 코드가 실행됩니다. ensure키워드는 상관없이 코드를 실행하지 않습니다 - 예외가 발생하면 하나가 아닌 경우, 유일한 예외가 있다면되는 세계의 끝 (또는 다른 가능성 이벤트).

 begin 
    # do driving stuff
 ensure
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
 end

팔! 그리고 그 코드는 어쨌든 실행되어야합니다. 사용해야하는 유일한 이유 rescue Exception => e는 예외에 액세스해야하거나 예외에서만 코드를 실행하려는 경우입니다. 그리고 오류를 다시 발생시키는 것을 잊지 마십시오. 매번

참고 : @Niall이 지적했듯이 항상 실행하십시오. 때로는 문제가 발생하더라도 프로그램이 사용자에게 거짓말을하고 예외를 throw하지 않기 때문에 좋습니다. 에어백 팽창과 같은 중요한 작업을 수행하면 에어백이 어떻게 발생하는지 확인해야합니다. 이 때문에 차가 멈출 때마다 예외가 발생했는지 여부를 확인하는 것이 좋습니다. 에어백을 부 풀리는 것이 대부분의 프로그래밍 상황에서 흔하지 않은 작업이지만 실제로는 대부분의 정리 작업에서 일반적입니다.


12
하 하하하! 이것은 좋은 대답입니다. 나는 아무도 언급하지 않은 것에 충격을 받았다. 모든 것을 실제로 이해할 수있는 명확한 시나리오를 제공합니다. 건배! :-)
James Milani

@JamesMilani 감사합니다!
Ben Aubin

3
이 답변에 대한 + 💯. 한 번 이상 공표 할 수 있기를 바랍니다! 😂
engineerDave

1
당신의 대답을 즐겼습니다!
Atul Vaibhav

3
이 답변은 완벽하게 이해 가능하고 정답을 얻은 지 4 년 만에 이루어졌으며 현실보다 재미 있도록 설계된 터무니없는 시나리오로 다시 설명했습니다. 버즈 킬이되어서 죄송하지만 이건 레딧이 아닙니다. 답변이 재미 있고 간결하고 정확해야합니다. 또한, ensure대안으로 rescue Exception오해의 소지가있는 부분 은 오해의 소지가 있습니다.이 예는 그것들이 동등하다는 것을 암시하지만 ensure예외가 있는지 여부에 관계없이 언급 된 바와 같이 이제 아무 문제가 없어도 5mph를 넘었으므로 에어백이 팽창합니다.
Niall

47

이것은 모든 예외를 포착하기 때문입니다. 그것은 당신의 프로그램이 복구 할 수있는 확률이 낮다 있는 그들.

복구 방법을 알고있는 예외 만 처리해야합니다. 특정 종류의 예외가 예상되지 않으면 예외를 처리하지 말고 크게 충돌 (로그에 세부 사항 작성) 한 다음 로그를 진단하고 코드를 수정하십시오.

예외를 삼키는 것은 좋지 않습니다. 이렇게하지 마십시오.


10

그것은 당신이 잡을 안된다는 규칙의 특정 사건의 어떤 당신이 처리하는 방법을 모르는 예외. 처리 방법을 모르는 경우 시스템의 다른 부분을 잡고 처리하는 것이 좋습니다.


0

방금 honeybadger.io 에서 훌륭한 블로그 게시물을 읽었습니다 .

루비의 예외 대 StandardError : 차이점은 무엇입니까?

예외를 구출해서는 안되는 이유

Exception 구출의 문제점은 Exception에서 상속 된 모든 Exception을 실제로 구출한다는 것입니다. .. 그들 모두입니다!

Ruby에서 내부적으로 사용하는 예외가 있기 때문에 문제가됩니다. 앱과 관련이 없으며 삼키면 나쁜 일이 발생합니다.

다음은 몇 가지 큰 것들입니다.

  • SignalException :: Interrupt-이것을 구출하면 control-c를 눌러 앱을 종료 할 수 없습니다.

  • ScriptError :: SyntaxError-구문 오류를 삼키면 puts ( "Forgot 무언가)와 같은 것들이 자동으로 실패합니다.

  • NoMemoryError-모든 RAM을 사용한 후에 프로그램이 계속 실행될 때 어떤 일이 발생하는지 알고 싶습니까? 나도

begin
  do_something()
rescue Exception => e
  # Don't do this. This will swallow every single exception. Nothing gets past it. 
end

나는 당신이 정말로 이러한 시스템 수준 예외를 삼키고 싶지 않다고 추측합니다. 모든 응용 프로그램 수준 오류 만 포착하려고합니다. 예외로 인해 코드가 발생했습니다.

운 좋게도 이것에 대한 쉬운 방법이 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.