Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

루비(Ruby)는 오류를 숨기고 있습니다! 숨겨진 예외 파헤치기

루비(Ruby)는 의도적으로 일부 오류와 예외를 개발자에게서 숨기곤 합니다.

때로는 이런 동작이 유용할 수 있습니다.

예를 들어 블록과 함께 Kernel#loop 메서드를 사용하면, StopIteration 예외가 발생했을 때 loop가 자동으로 종료됩니다.

하지만 다른 경우에는 디버깅 작업을 훨씬 더 어렵게 만들 수 있습니다.

구체적인 예시를 통해 살펴보겠습니다!

숨겨진 예외 #1: Comparable 모듈과 <=> 메서드

첫 번째 예시는 Comparable 모듈과 <=> 메서드와 관련이 있습니다.

예시 코드입니다:

class MyObject
  attr_accessor :value

  include Comparable

  def initialize(value)
    @value = value
  end

  def <=>(other)
    raise ArgumentError, "can't compare #{other.class} with #{self.class}" unless other.is_a?(MyObject)

    value <=> other.valuee
  end
end

mo1 = MyObject.new(10)
mo2 = MyObject.new(10)

p mo1 == mo2

이 예시를 단계별로 분석해 보겠습니다.

먼저:

MyObjectvalue라는 attr_accessor가 하나 있으며, Comparable 모듈을 포함하고 있습니다. 이 모듈은 비교 메서드(==, <, > 등)를 클래스에 추가해 줍니다.

이러한 비교 메서드들은 모두 <=> 메서드를 기반으로 동작합니다.

Enumerable의 메서드들이 each 메서드를 기반으로 하는 것과 같은 원리입니다.

그다음:

같은 값(10)을 가진 두 객체(MyObject.new)를 생성합니다.

값이 같더라도 두 객체는 서로 다른 객체라는 점에 주목하세요. 이 부분이 중요합니다.

이제 이 두 객체 mo1mo2를 비교하면 false가 반환됩니다...

왜 그럴까요?

<=> 메서드에 오류가 있는데, 루비가 그 오류를 숨기고 있기 때문입니다!

코드를 잘 살펴보세요...

오류를 찾을 수 있나요?

찾았다면 훌륭합니다! 못 찾았어도 괜찮습니다 🙂

바로 여기 있습니다:

value <=> other.valuee

valuee가 보이시나요?

사실 오타가 있었던 것입니다!

원래대로라면 NoMethodError 예외가 발생해서 문제를 금방 알아챘을 것입니다. 하지만 이 예시에서는 그렇지 않습니다.

다행히도 Ruby 2.3부터는 이 동작이 변경되었습니다. 이제 오류가 더 이상 숨겨지지 않으므로 직접 확인할 수 있습니다.

아직 구버전 루비를 사용하고 있다면 업그레이드해야 할 또 하나의 이유가 되겠네요.

숨겨진 예외 #2: Numeric 객체와 coerce 메서드

숨겨진 예외의 또 다른 예시는 Numeric 객체(Float, Integer)와 coerce 메서드의 조합에서 나타납니다.

예시 코드입니다:

class MyObject
  attr_accessor :value

  def initialize(value)
    @value = value
  end

  def +(other)
    other = MyObject.new(other) if other.kind_of?(Numeric)

    value + other.value
  end

  def coerce(other)
    mo = MyObject.new
    mo.valuee = other

    [mo, self]
  end
end

mo1 = MyObject.new 10
mo2 = MyObject.new 10

p mo1 + mo2
# 20

p mo1 + 20
# 30

이번에도 MyObject 클래스지만, 새로운 메서드인 +coerce가 추가되었습니다.

coerce 메서드는 호환되지 않는 두 타입을 같은 타입으로 변환하여 함께 사용할 수 있도록 도와줍니다.

이 경우 우리 클래스는 초 단위 시간, 가격 등 어떤 숫자 값을 나타낸다고 가정해 봅시다...

그리고 우리는 다음과 같은 연산이 가능하기를 원합니다:

mo1 + 20
20 + mo1

첫 번째(mo1 + 20)는 간단합니다. 우리가 클래스의 + 메서드를 직접 제어할 수 있기 때문입니다.

하지만 Integer 클래스는 어떨까요?

Integer의 + 메서드를 몽키 패치로 수정해서 구현할 수도 있지만, 그건 좋은 생각이 아닙니다 🙂

해결책은 무엇일까요?

바로 자신의 클래스에 coerce 메서드를 구현하는 것입니다.

Integer+ 메서드는 연산 대상 객체가 이 메서드를 구현하고 있는지 확인하고, 구현되어 있다면 해당 메서드를 호출합니다.

이제 이 섹션 처음의 코드 예시를 기억하시나요?

다음 코드를 실행해 보면:

20 + mo1

mo1의 값이 10이므로 30이 출력되기를 기대합니다. 하지만 실제로 화면에 나타나는 것은:

MyObject can't be coerced into Fixnum

앞서 본 것과 같은 문제입니다!

coerce 메서드 내부에서 오류가 숨겨지고 있는 것입니다.

바로 이 부분입니다: mo.valuee = other

여기서도 오타가 있지만, 오류 메시지는 전혀 다른 내용을 알려줍니다!

MyObject can't be coerced into Fixnum

좋은 소식이 있습니다. 이 동작 역시 Ruby 2.5에서 변경되어, 숨겨진 오류가 하나 더 사라지게 됩니다. 따라서 더 이상 이런 문제로 고민할 필요가 없습니다.

마무리

이처럼 예외를 함부로 숨기면 안 되는 이유를 잘 보여주는 좋은 예시들이었습니다. 예외 처리에 대해 더 깊이 알고 싶다면 제 책 'Ruby Deep Dive'를 참고해 보세요.

이 글이 도움이 되었다면 주변 지인들과 공유해 주시면 감사하겠습니다. 더 많은 사람들이 유용한 정보를 얻을 수 있을 것입니다.

읽어주셔서 감사합니다!