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

견고성 원칙으로 루비 코드 단순화하기

이 글에서 소개하는 아이디어 대부분은 제가 가장 좋아하는 루비 책 중 하나인 『Confident Ruby』에서 배운 것입니다. 이 글이 마음에 드셨다면 직접 구매해서 전체를 읽어보시길 권합니다. 정말 유용한 내용이 가득합니다.

current_user 메서드는 User 객체를 반환하지만, 로그인한 사용자가 없으면 nil을 반환합니다. search 메서드는 결과를 담은 Array를 반환하지만, 결과가 하나뿐일 때는 배열이 아닌 그 객체 자체를 그대로 돌려주죠. 얼핏 보면 합리적이고, 어쩌면 편리해 보일 수도 있습니다.

하지만 곧 이런 결정들이 여러분의 코드를 if문의 산더미 속에 파묻고 말 겁니다. 곳곳에 흩어진 kind_of? 타입 검사들이 생겨나거나, 모든 곳에서 nil 체크를 해야 하는 상황이 벌어질 수 있습니다. 더 심해지면 새 기능을 배포할 때마다 NoMethodError가 튀어나와 또다시 급하게 핫픽스를 해야 하겠죠.

다행히도 이런 문제를 예방할 방법이 있습니다. 필요한 것은 약간의 신경 쓰임뿐입니다.

견고성 원칙(The Robustness Principle)

컴퓨팅 분야에는 다음과 같은 원칙이 있습니다.

"스스로 하는 일은 보수적으로, 남에게 받아들이는 것은 관대하게."

이 원칙은 루비 메서드 작성에도 그대로 적용할 수 있습니다. 여러분이 작성하는 메서드는 합리적인 입력을 받아들이고, 일관된 출력을 반환해야 합니다.

특히 마지막 부분에 주목하세요. 누군가 여러분이 작성한 메서드를 호출할 때, 그 메서드가 무엇을 반환할지 정확히 알 수 있어야 합니다.

반환값에 신중을 기하세요

Rails 2.1의 ActiveRecord::Errors#on 구현을 살펴보겠습니다.

# File activerecord/lib/active_record/validations.rb, line 212
def on(attribute)
  attribute = attribute.to_s
  return nil unless @errors.has_key?(attribute)
  errors = @errors[attribute].map(&:to_s)
  errors.size == 1 ? errors.first : errors
end

이 메서드를 호출하면 String 배열, 단일 String, 아니면 nil 중 하나가 반환됩니다. 어떤 타입의 객체를 다루게 되는지 판단하는 책임은 전부 호출자에게 넘어갑니다. 이것은 나쁜 설계입니다.

  • 호출자는 자신의 코드를 번거로운 타입 검사로 지저분하게 만들어야 합니다.

  • 호출자는 호출하는 메서드에 대해 많은 것을 알아야 합니다. 최소한 해당 메서드가 반환할 수 있는 모든 타입과, 각 타입이 반환되는 조건까지 파악해야 하죠.

  • 테스트해야 할 엣지 케이스가 늘어납니다. 코드가 의도대로 동작한다고 확신하려면 세 가지 시나리오를 모두 검증해야 합니다.

메서드는 반환값에 대해 일관성을 유지해야 합니다. 보통 Array를 반환하는 메서드라면, 필요한 조치를 취해 항상 Array를 반환하도록 만드세요. 보통 User를 반환하지만 가끔 nil을 반환하는 메서드라면, 널(Null) User 객체를 만들어 nil 대신 그 객체를 반환하는 방법도 있습니다.

조금 더 느슨한 약속도 가능합니다. "Taggable 모듈을 포함하는 무언가를 반환한다"라고 할 수도 있고, 더 일반적으로 "idname 속성을 가진 무언가를 반환한다"라고 할 수도 있습니다. 중요한 것은 일관성을 유지하고, 호출자가 무엇을 기대해야 하는지 명확히 하는 것입니다.

jQuery가 흥미로운 예시입니다. 대부분의 jQuery 메서드는 같은 종류의 Array 유사 객체를 반환합니다. 덕분에 jQuery 메서드는 놀랍도록 잘 조합되며, 한 줄의 코드로 매우 많은 작업을 처리할 수 있습니다.

참고로 Rails는 이후 버전에서 해당 메서드를 수정했습니다.

# File activemodel/lib/active_model/errors.rb, line 133
def [](attribute)
  get(attribute.to_sym) || set(attribute.to_sym, [])
end

이제 이 메서드는 항상 Array를 반환합니다. Rails 개발자에게도, 이 API를 쓰는 우리에게도 훨씬 단순해졌습니다.

불일관성을 없애세요

다음에 "배열 또는 nil"을 반환하려는 순간이 온다면, 그냥 Array만 반환하세요. 코드베이스를 훑어보면서 kind_of?respond_to?를 사용하고 있는 곳을 찾아보세요. 그 코드가 호출하는 메서드들을 리팩토링해서 단일 타입을 반환하도록 만들 수 있는지 확인해 보세요.

그리고 반환값에 대해 안심하고 가정할 수 있게 되면서, 그 혜택이 프로젝트 전반으로 퍼져나가 주변 코드가 모두 단순해지는 과정을 지켜보세요.