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

Ruby에서 case 문 사용을 멈춰야 하는 이유: 다형성으로 더 나은 코드 작성하기

OOP(객체 지향 프로그래밍)의 진정한 힘을 제대로 활용하고 계신가요, 아니면 그저 지나치고 있으신가요?

객체의 타입에 따라 분기 처리를 하고 있다면 중요한 OOP 기능인 다형성(polymorphism)을 놓치고 있는 것입니다.

타입 분기는 보통 case 문 안에서 이루어지는데, 이는 객체 지향 설계에 적합하지 않습니다. 이 글에서는 case 문을 제거하여 더 나은 코드를 작성하는 방법을 알아보겠습니다.

타입 검사하기

다형성을 활용하지 않는 예제부터 살펴보겠습니다.

"가위바위보" 게임을 구현한다고 가정해 봅시다. 하나의 Game 클래스와 각 선택지(바위, 보, 가위)마다 하나의 클래스를 만들기로 했습니다.

승자를 판별하기 위해 Game 클래스에 play 메서드를 구현합니다:

class Game
  def self.play(move1, move2)
    return :tie if move1 == move2

    move1.wins_against?(move2)
  end
end

그리고 선택지 중 하나입니다(나머지도 같은 패턴을 따릅니다):

class Rock
  def wins_against?(other_move)
    case other_move
    when Paper then false
    when Scissors then true
    end
  end
end

이제 두 개의 선택지로 play 메서드를 호출하면 첫 번째 선택이 이기는지 알 수 있습니다.

p Game.play(Rock.new, Paper.new)
# false

동작은 하지만, 저 지저분한 case 문을 없앨 수는 없을까요?

타입 검사 대신 다형성 활용하기

네! OOP의 기본 원리를 활용하면 타입 검사용 case 문을 제거할 수 있습니다.

핵심 아이디어는 "현재 자신의 클래스를 알고 있다"는 점을 활용해, 상대 선택 객체에게 "당신이 우리를 이길 수 있는가?"라고 물어보는 것입니다.

그리고 클래스에 특화된 메서드 이름을 사용합니다. Rock이라면 메서드 이름이 do_you_beat_rock?처럼 되는 식입니다.

Ruby에서 다형성이란 객체의 클래스를 확인하지 않고도 어떤 메서드 호출(OOP 용어로 '메시지')이든 모든 객체에 보낼 수 있는 능력을 의미합니다. 흔히 '덕 타이핑(duck typing)'이라고도 불리지만, 저는 그 용어를 별로 좋아하지 않습니다 🙂

Java(잠시 참아주세요...)에는 '인터페이스'라는 것이 있어, 컴파일러 수준에서 특정 클래스가 반드시 구현해야 할 메서드 집합을 강제할 수 있습니다.

Ruby에는 그런 기능이 없습니다(아마 그 편이 더 좋겠죠). 대신 테스트에 의존해야 합니다.

새로운 구현이 어떤 모습인지 코드 예제로 살펴보겠습니다:

class Rock
  def wins_against?(other_move)
    other_move.do_you_beat_rock?
  end

  def do_you_beat_paper?
    false
  end

  def do_you_beat_scissors?
    true
  end
end

case 문이 사라진 것에 주목하세요. 단 하나의 메서드 호출과 두 개의 메서드 정의로 대체되었습니다.

업데이트: 일부 독자들이 지적했듯이, 이 패턴을 구현하는 과정에서 로직이 뒤집혔다는 점을 미처 인지하지 못했습니다. Daniel P. Clark가 제안한 해결책은 play 메서드의 순서를 move2.wins_against?(move1)로 바꾸는 것입니다.

훨씬 깔끔해지지 않았나요? 어떻게 생각하시나요?

선택지 하나 더 추가하기!

이제 새로운 선택지를 추가하고 싶다고 가정해 봅시다. 무엇을 변경해야 할까요?

잠시 생각해 보세요...

case 문 방식에서는 모든 선택지마다 새로운 분기를 추가해야 합니다. 이미 잘 동작하는 메서드를 '강제로' 수정해야만 새 기능을 도입할 수 있다는 점에 주목하세요.

하지만 OOP 지향 버전에서는 그런 문제가 없습니다. 새로운 메서드만 추가하면 됩니다. 이것이 바로 개방-폐쇄 원칙(OCP), 즉 SOLID의 'O'가 실제로 작동하는 모습입니다.

또 다른 방법으로는 메타프로그래밍(method_missing 또는 define_method)을 사용하는 것도 있습니다.

그렇다면 실제로 여기서 메타프로그래밍을 사용할까요?

아마 아닐 겁니다. 선택지가 끊임없이 변하거나 그 수가 매우 많은 경우가 아니라면요.

메타프로그래밍에는 비용이 따르며, 일부 사람들이 믿는 것처럼 만능 해결책이 아닙니다. 약간의 유연성을 얻는 대신 성능과 가독성을 희생하게 됩니다.

결론

이 글에서 배운 핵심은 클래스 타입 검사를 위한 case 문 사용을 피해야 한다는 것, 그리고 대신 다형성을 활용해야 한다는 점입니다.

이를 통해 기존 코드를 수정하는 대신 새로운 코드를 추가하는 방식으로 확장할 수 있는, 더 나은 코드를 만들 수 있습니다. 이제 직접 리팩토링을 시도해 볼 차례입니다 🙂

참고로, 이것이 도구 상자에서 case 문을 완전히 없애자는 주장은 아닙니다. 다만 case 문을 작성하고 있다는 사실을 인식했다면 항상 경계하고, 그것이 해당 문제에 대한 최선의 해결책인지 반드시 확인하세요.

이런 글이 계속 쓰여지기를 원하신다면 글 공유를 잊지 마세요!