[번역] Code I like (II): Fractal journeys

구경회·2022년 10월 26일
1
post-thumbnail

원 저자인 Jorge Manrubia 씨의 허락을 얻어 번역합니다. 원문은 https://world.hey.com/jorge/code-i-like-ii-fractal-journeys-b7688f93 에서 확인하실 수 있습니다.


프랙탈은 점진적으로 더 작은 규모로 반복되는 유사한 패턴에 관한 것이다. 나에게 좋은 코드는 프랙탈이다. 좋은 코드에서는 마치 프랙탈처럼 다른 수준의 추상화에서 반복되는 동일한 특성을 관찰할 수 있다.

그닥 놀랍지는 않다. 좋은 코드는 이해하기 쉬운 코드고, 복잡성을 다루는 가장 좋은 방법은 추상화기 때문이다. 이를 통해 우리는 복잡성을 알기 쉬운 인터페이스로 바꿀 수 있다. 하지만, 이제 우린 또 다시 우리가 밀어낸 복잡성과 싸워야 한다. 그러기 위해 아까의 동작을 다시 반복한다. 새로운 추상화를 통해 속내용을 가리고 이 복잡한 동작의 조감도를 제공한다.

나는 추상화를 모든 것을 언급하기 위해 사용하고 있다. 큰 하위 시스템에서부터 일부 내부 클래스의 마지막 Private method에 이르기까지. 하지만 어떻게 이런 추상화를 만들 수 있을까? 굉장히 중요하나 대답하기는 어렵다. 수없이 많은 책들이 이를 다루고 있음이 방증한다. 이 글에서는 이해하기 쉬운 코드를 짜는데 필수적이라고 생각하는 네 가지 자질에 초점을 맞추도록 하겠다.

  • 도메인 주도(Domain-Driven): 도메인에 대해 다루어라.
  • 캡슐화(Encapsulation): 매우 명확한 인터페이스를 제공하고 세부 구현을 감춰라.
  • 응집성(Cohesiveness): 호출자의 입장에서 한 가지 일만 해라.
  • 대칭성(Symmetry): 비슷한 수준으로 추상화하라.

글이 지나치게 추상적인 방향으로 흘러가는 것 같다. Basecamp의 코드를 보며 현실에 대해 좀 얘기를 해보자. 제품의 여러 부분에서 활동 타임라인을 제공한다. 이 타임라인은 알아서 새로고침이 된다. 사용자가 이걸 보고 있을 때 다른이가 활동을 하면 실시간으로 업데이트된다는 뜻이다.

도메인 수준에서 이야기를 해보자. 누군가 Basecamp에서 todo를 완료한다든지 문서를 만든다든지 댓글을 다는 등의 활도을 하면, 시스템은 이벤트를 만들고, 이 이벤트는 여러 목적지로 실어날라진다. 목적지는 활동 타임라인이나 webhook 등이다. 코드를 들여다보자.

가장 먼저 Event 모델이 있다. 모델은 Relaying concern을 include 한다. (관련한 부분만 제시)

class Event < ApplicationRecord
  include Relaying
end

이 concern은 relays와 관계를 만들고 생성 후에 비동기 relay event를 만든다.

module Event::Relaying
  extend ActiveSupport::Concern

  included do
    after_create_commit :relay_later, if: :relaying?
    has_many :relays
  end

  def relay_later
    Event::RelayJob.perform_later(self)
  end

  def relay_now
    ...
  end
end

class Event::RelayJob < ApplicationJob
  def perform(event)
    event.relay_now
  end
end

따라서 Event#relay_now가 우리가 관심있는 메서드이다. 도메인 언어로 쓰여있다. 호출하는 관점에서 하나의 일만을 하며 이벤트 릴레잉과 관련한 모든 것을 숨기고 있다. 좀 더 파고들어가자.

module Event::Relaying
  def relay_now
    relay_to_or_revoke_from_timeline

    relay_to_webhooks_later
    relay_to_customer_tracking_later

    if recording
      relay_to_readers
      relay_to_appearants
      relay_to_recipients
      relay_to_schedule
    end
  end
end

이 메서드는 더 낮은 추상화 수준을 가진 메서드들의 호출을 다시 부른다. 모두가 Relaying에 관한 것으로 응집성을 위배하지 않는다. 도메인에 기반하여 목적지에 대해 명확한 이름을 갖고 잇다. 역시 세부사항은 숨겨져 있고 여기서 또 다시 프랙탈의 한 조각을 발견할 수 있다. 이 메서드가 무엇을 하는지 알기 위해 추상화 수준을 뛰어넘을 이유는 없다.

#relay_to_or_revoke_from_timeline 메서드가 우리가 찾는 것으로 보인다.

module Event::Relaying
  private
    def relay_to_or_revoke_from_timeline
      if bucket.timelined?
        ::Timeline::Relayer.new(self).relay
        ::Timeline::Revoker.new(self).revoke
      end
    end
end

도메인에 뿌리를 둔 좋은 이름들을 다시 볼 수 있다. bucket이 timelined인지 확인한 후, Timeline::Relayed 객체를 만들어 타임라인에 이벤트를 전달한다. 대칭성에 주목하라. 이벤트를 취소하는, 대칭쌍을 이루는 클래스가 있다. relaytimeline에 대해 초점을 맞추고 있고 세부적인 구현은 숨겨져 있다. 이 클래스를 좀 더 파보도록 하자.

class Timeline::Relayer
  def initialize(event)
    @event = event
  end

  def relay
    if relaying?
      record
      broadcast
    end
  end

  private
    attr_reader :event
    delegate :bucket, to: :event

    def record
      bucket.record Relay.new(event: event), parent: timeline_recording, visible_to_clients: visible_to_clients?
    end

    def broadcast
      TimelineChannel.broadcast_event(event, to: recipients)
    end
end

이번에는 메서드가 아니고 순수한 루비 클래스를 이용해 추상화를 했지만, 같은 특질들을 볼 수 있다. 밖으로는 메서드 #relay만을 노출하며 구현의 세부사항들을 숨기고 있다. 안을 들여다보면, 두 개의 연산을 확인할 수 있다. relay를 데이터베이스에 저장하고 Action Cable을 통해 broadcast하는 것이다. (이 코드는 Hotwire 이전에 작성한 것이다.) 다시 대칭성에 줌족하라. 두 작업 모두 한 줄짜리 코드로 표현할 수 있지만, 더 높은 수준의 메서드로 추출했다.

마침내 저수준의 구현까지 도달했다. #record 메서드는 relay를 DB에 저장한다. relay는 recordable이고, Rails의 delegated type을 사용한 예시이다. #broadcast는 event를 시작할 때 관심을 가졌던 수신자에게 broadcast하는 방식이다.

이 예에서는 이벤트가 생성되는 순간부터 액션 케이블 채널을 통해 푸시될 때까지 릴레이 논리를 쉽게 이해할 수 있었다. 각 단계에서 관심사가 하나뿐이었기 때문이다. 하나의 책임과 단일한 수준의 추상화, 그리고 우리의 도메인을 반영하는 이름들. 물론 좋은 코드를 구성하는 것은 주관적이고 더 많은 개념을 포함한다. 하지만 사소하지 않은 규모의 시스템에서 이런 여행을 할 수 있는 능력은 내가 좋아하는 코드의 첫번쨰 자질이다.


이 시리즈의 다른 글:

수년 전에 작성한 composed method implementation pattern에서도 이러한 취지를 확인할 수 있다. 가장 좋은 점은 저기 참조된 두 권의 책인데, 이 주제에 관심이 있다면 꼭 읽어보길 권한다.


Photo by Martin Rancourt on Unsplash

profile
즐기는 거야

2개의 댓글

comment-user-thumbnail
2022년 12월 1일

많은 도움 되었습니다. 감사합니다.

1개의 답글