Ruby

루비 스타일(1) – C로부터의 탈출

wallpaper-keep-calm-and-code-in-ruby

* 이곳에 나온 예제 코드들은 RoRLab의 Playground에서 결과물로 나온 코드임을 밝혀둡니다. 매우 짧은 제한 시간에 만들어진 코드이기 때문에 실제 실력에 비해 잘 나오지 못한 코드임을 감안해주시기 바라며, 흔쾌히(?) 코드를 공유해주신 회원님들께 감사드립니다. *

앞으로 세 번에 걸쳐 보게 될 글들은 아래와 같은 간단한 문제에 대한 해결책을 담고 있습니다. 여러분도 한 번 풀어보시길 권해드립니다.

1 과 2로 시작하는 피보나치 수열의 10 번째 까지 숫자는 다음과 같다.
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ......
40만이 넘지 않는 피보나치 수열중 짝수인 숫자의 합을 구하라

이에 대한 해법 중 하나를 봅시다.

class Fibo
  attr_accessor :limit, :even_sum, :array

  def initialize(limit)
    @limit = limit
    @array = Array.new
  end

  def even_sum()
    bb=1
    b=1
    sum=0

    c=0
    begin
      c=bb+b
      if(c < @limit && c.even?)
        @array << c
        sum+=c
      end
      bb=b
      b=c
    end while c < @limit
    sum
  end
end

잘 동작하는 코드이고 C 언어 기준으로 봤을 때 특별히 나쁘지 않은 코드입니다. 불필요한 @array 변수가 있는 것만 빼면 인터프리터 입장에선 군더더기 없는 형태라 실행 속도 측면에서도 훌륭해보입니다…만, 루비 개발자들이 추구하는 코드와는 상당한 차이가 있습니다. 애초에 기계가 좋아할 최적화된 코드를 선호하시는 분이라면 루비를 선택하시면 안..

루비의 코딩 스타일의 핵심은 (루비 고유의 방법대로) 가독성의 극대화를 추구하는 것에 있다고 저는 믿습니다. 잘 만들어진 루비 코드를 보면 공통점이 머리 속으로 컴파일러를 가동(mental compilation)시키지 않아도 이해할 수 있다는 점인데(이에 대해서는 나중에 다른 포스팅에서 Rails 코드를 얘기하면서 다뤄보겠습니다) 이 코드의 경우 기계가 이해하기에는 편하겠지만, 딱 봐서 의미를 알 수 없는 변수와 하나의 메소드에 모든 로직이 전부 구현된 형태이다 보니 머리 속으로 조금 컴파일을 해보지 않으면 이해하기 힘든 부분이 있고, begin/end, if/end 구문이 중첩되어 있어 (많지는 않지만) 가독성을 떨어뜨리고 있습니다.

일단 몸풀기로.. 코딩 스타일을 잠시 짚고 넘어가겠습니다. 자세한 내용은 각 항목의 [링크]를 눌러보세요.

  • 연산자 전후에는 공백을 넣는 것이 좋습니다. [자세한 내용]
  • 파라미터가 없는 메소드에는 호출할 때나 정의할 때나 괄호를 넣지 않는 게 좋습니다. [자세한 내용]
  • begin/end/while은 권장하지 않습니다. 대신 Kernel#loop와 break를 사용하는 것이 좋습니다. [자세한 내용]
  • 해시와 배열 생성은 (생성자에 파라미터를 넣어줘야하는 경우가 아니라면) Array.new 대신 []를, Hash.new 대신 {}를 이용하는 것이 좋습니다. [자세한 내용]
  • 그리고 끝으로 어떤 언어든 마찬가지지만.. 의미를 알 수 없는 변수명은 피하시는 게 좋습니다. ^^;

이에 따라 수정을 하면 아래와 같이 되겠군요.

# 1차 수정
class Fibo
  attr_accessor :limit, :even_sum, :seq

  def initialize(limit)
    @limit = limit
    @seq = [] # 변수명 수정, Array.new -> []
  end

  def even_sum     # 괄호 생략
    first = 1      # 띄어쓰기 추가, 변수명 수정
    second = 1     # 띄어쓰기 추가, 변수명 수정
    sum = 0        # 띄어쓰기 추가

    next_number = 0       # 띄어쓰기 추가
    loop do
      next_number = first + second  # 띄어쓰기 추가
      if next_number < @limit && c.even?  # 띄어쓰기 추가
        @seq << next_number
        sum += next_number
      end
      first = second
      second = next_number
      break if next_number > limit
    end
    sum
  end
end

코딩 스타일을 정리한 것만으로도 좀 더 소스가 이해하기 편해졌죠?

그 다음은 클래스 설계를 볼 필요가 있습니다. 클래스 설계는 클래스가 어떤 책임(responsibility)를 갖고 있냐를 조정하는 예술이라고 할 수 있습니다. 풀어서 얘기하면 클래스가 어떤 역할을 하게 될 것인가, 그리고 그를 뒷받침하기 위한 내부 데이터는 어디까지 공개하고 어디까지 숨길 것인가, 메소드들은 어떻게 나눌 것인가 에 대한 문제를 적절히 해결하는 것입니다.

  • 책임을 적절히 분산시킨다
  • 각 기능이 가장 적절한 클래스에 들어있도록 설계한다
  • 클래스와의 coupling이 일어나지 않도록 한다

대략 이런 원칙들을 통해 클래스를 설계하게 됩니다.(이에 대해서는 다른 포스팅을 통해서 다뤄보겠습니다.)

그럼 다시 예로 든 코드로 돌아와서 Fibo 클래스의 경우 무엇이 들어 있는가 보면..
1) 피보나치 수열의 최대값에 대한 접근자(accessor)
2) 설정된 최대값까지의 짝수 수열의 합을 리턴하는 단일 메소드
3) 2의 메소드가 실행되면서 만들어진 피보나치 배열에 대한 접근자
이렇게 다소 혼란스럽고 일관성이 떨어집니다.

이 클래스로 40만 이하의 짝수 수열 합을 구하는 코드는 아래와 같이 되는데..

fibo = Fibo.new(400_000)
puts fibo.even_sum

이후 다른 limit 값으로 짝수 합을 구하려면 #even_sum을 다시 호출하기 전에 fibo.limit = 100 과 같은 식으로 직접 limit 값을 바꿔줘야 합니다. 그리고 보조 결과물인 seq 배열은 #even_sum의 리턴 형태로 받는 것이 아니기 때문에 값이 채워지는 시점을 따로 알고 있어야 합니다.

위 코드는 이렇게 바뀌어야 하지 않을까요?

seq 필드 제거: 애초에 피보나치 수열 자체는 의미있는 정보가 아니기 때문에 굳이 노출할 필요가 없는 정보입니다. 다만, TDD로 진행을 하면서 unit test로 결과를 검증하기 위해서 필요할 텐데, 이 경우 피보나치 수열만 만들어주는 로직을 별도로 빼는 것이 합리적이지 않을까요?

피보나치 수열을 계산하는 로직을 분리: 위에서 얘기한 테스트를 편리하게 만들기 위한 것도 있지만 무엇보다 모든 로직을 하나의 메소드에 다 구현하는 것은 매우 좋지 못한 방식입니다. 논리적으로 분리되는 내용은 다른 메소드로 추출(extract)하는 것이 좋습니다.

limit 값을 #even_sum 메소드의 파라미터로 이동: limit 값에 영향을 받는 것이 애초에 even_sum 메소드 하나밖에 없다면 이것은 클래스의 소속이 아닌 메소드 안으로 들어가는 것이 맞습니다.

even_sum 접근자 제거: even_sum을 attr_accessor 를 이용해 정의했는데 even_sum 변수에 값을 써도 그것을 쓸 수 있는 코드가 어디에도 없습니다.

결론적으로 모든 accessor를 삭제하고 피보나치 배열을 만드는 서브로직을 별도로 빼면 되겠군요.
그런데 그렇게만 바꾸면 좋은 루비 코드가 나올까요? 사실 여기서 언급된 문제들은 Java 같은 다른 OOP에서도 똑같이 요구되는 것들입니다.

그럼 위의 문제를 모두 해결하면서도 좀 더 루비만의 특징을 이용한 깔끔한 코드는 어떻게 만들 수 있을까요?
다음 포스팅에서 다뤄보겠습니다.

루비 스타일(1) – C로부터의 탈출”에 대한 1개의 생각

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중