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

왜 클래스를 만들까요? 추상화의 개념부터 좋은 클래스 설계까지

지난 글에서 nil을 왜 사용하는지 다룬 데 이어, 이번에는 개발자들이 당연하게 여기는 다른 것들에 대해서도 같은 질문을 던져볼 때라고 생각했습니다.

예를 들면 이렇습니다.

지저분한 거대 코드 덩어리 하나로 두지 않고, 왜 굳이 클래스를 사용할까요?

근본적으로 클래스는 코드와 데이터를 논리적인 단위로 정리하기 위해 사용합니다.

하지만 그 이상의 의미가 담겨 있습니다.

클래스를 활용하면 추상화(abstraction)를 만들 수 있기 때문입니다.

추상화란 무엇일까요?

사실 우리는 이미 매일, 매 순간 추상화를 사용하고 있습니다.

제가 지금 이 글을 입력하고 있는 키보드를 예로 들어볼까요? 키보드는 다음 요소들로 구성되어 있습니다.

  • 케이블
  • Cherry MX 블랙 스위치
  • 모든 부품을 하나로 묶어주는 플라스틱 케이스
  • 전기 신호를 컴퓨터로 전달하는 회로 기판

즉, 키보드란 이 부품들을 올바른 방식으로 조합했을 때 비로소 성립하는 하나의 개념입니다.

이것이 추상화인 이유는, 내부 작동 원리를 몰라도 사용할 수 있기 때문입니다. 구성 부품이 무엇인지조차 알 필요가 없죠.

그저 연결하고 타이핑을 시작하면 됩니다.

클래스가 해주는 일이 바로 이것입니다.

추상화는 왜 중요할까요?

추상화는 복잡성(complexity)을 관리하는 데 큰 도움을 줍니다.

『코드 컴플리트(Code Complete)』의 저자 스티브 맥코넬(Steve McConnell)은 이렇게 말했습니다.

"클래스를 만드는 단연 가장 중요한 이유는 프로그램의 복잡도를 줄이는 것이다."

그렇다면 추상화는 어떻게 복잡도를 낮출 수 있을까요?

비밀은 세부 사항을 숨기고, 공개 인터페이스(public interface)를 통해서만 서비스를 노출하는 데 있습니다.

루비(Ruby) 클래스에서 인터페이스는 public 메서드로 구성되고, 세부 사항은 private 메서드로 감춰집니다.

왜 클래스를 만들까요? 추상화의 개념부터 좋은 클래스 설계까지

여기서 말하는 '세부 사항'이란 다음과 같은 것들입니다.

  • 인스턴스 변수(instance variables)
  • 내부 메서드
  • 알고리즘과 자료구조

이런 것들이 잘 숨겨져 있다면, 클래스를 사용하는 외부 코드에 영향을 주지 않고 언제든 자유롭게 변경할 수 있습니다.

예를 들어 데이터를 배열에 저장하는 GPS 클래스가 있다고 합시다. 그런데 "배열의 0번 인덱스에는 위도가, 1번 인덱스에는 경도가 들어 있다"는 사실을 반드시 알아야 한다면 어떨까요? 이런 정보는 클래스 사용자가 알 필요가 없는 세부 사항입니다.

좋은 클래스를 작성하는 방법

자, 이제부터는 꽤 방대한 주제입니다.

SOLID 원칙을 따르는 것처럼, 좋은 클래스를 설계하는 데 고려해야 할 요소는 많습니다.

하지만 가장 중요한 것은, 클래스가 반드시 명확한 역할(role)을 가져야 한다는 점입니다. 용도를 알 수 없는 메서드가 무작위로 나열된 형태가 되어서는 안 됩니다.

예를 들어 비즈니스 로직(계산, 규칙 처리)과 프레젠테이션 로직(출력 포맷팅)은 서로 다른 역할이므로 분리하는 것이 좋습니다.

아래 코드를 살펴보세요.

require 'rest-client'
require 'nokogiri'

class ImageCounter
  def initialize(url)
    @url = url
  end

  def find_images_and_report
    data   = RestClient.get(@url).body
    html   = Nokogiri::HTML.parse(data)
    images = html.css("img")

    report = ""
    report << "=" * 40 << "\n"
    report << "Found #{images.size} images on #{@url} \n"
    report << "=" * 40 << "\n"
  end
end

reporter = ImageCounter.new("rubyguides.com")
report   = reporter.find_images_and_report

이 클래스가 이미지 개수를 세는 계산과 리포트 출력이라는 두 가지 일에 동시에 관여하고 있음을 알 수 있습니다. 간단한 임시 스크립트 수준이라면 괜찮지만, 좀 더 규모 있는 애플리케이션에서는 반드시 분리하는 편이 좋습니다.

책임을 분리해두면 나중에 프레젠테이션 계층만 다른 것으로 교체하거나, 비즈니스 규칙을 바꾸면서도 동일한 출력 로직을 재사용할 수 있습니다.

이처럼 클래스가 제공하는 코드 재사용(code reuse) 능력은 더 많은 클래스를 작성해야 하는 또 다른 강력한 이유입니다.

더 나은 클래스를 작성하는 데 도움이 되는 개념은 이 밖에도 많습니다. 심화 학습에 도움이 될 만한 주제들을 소개합니다.

  • 응집도(Cohesion)와 결합도(Coupling)
  • 묻지 말고 시켜라(Tell, Don't Ask)
  • 데메테르의 법칙(Law of Demeter)

무엇보다 '왜 클래스를 작성하는가'를 먼저 이해하는 것이 좋은 출발점입니다. 끝까지 읽어주셨다면 정말 잘하신 겁니다!

마치며

이번 글에서는 클래스를 작성하는 이유, 추상화가 무엇인지 그리고 왜 중요한지 알아보았습니다. 좋은 클래스를 설계하는 기본 원칙과 추가로 공부할 만한 주제들도 함께 정리했습니다.

코드가 '진흙 덩어리(Big Ball of Mud)'로 변해버리는 게 싫다면, 오늘 당장 이 개념들을 실천하며 클래스 설계를 개선해 보세요 🙂

이 글이 유용했다면 SNS에 공유하는 것도 잊지 마세요!