KwanIk Devlog
article thumbnail
보다 유연하고 확장성 있는 설계를 하기 위해 나는 과연 객체에 대해서 온전히 이해하고 있을까?

 

사내스터디로 조영호님의 오브젝트라는 책을 공부하게 된 배경이다. Chapter 3를 통해 언급될 역할, 책임, 협력은 실생활에서도 적용된다고 보는데, 내가 회사에서 맡고 있는 역할과 책임을 다하기 위해 그리고 보다 나은 협력을 구축하기 위해 공부하고 있는 내용을 블로그에도 추가적으로 정리하고자 한다.

 

앞으로 해당 카테고리에 기재할 내용들은 모두 앞서 말한대로 조영호님의 오브젝트 책을 보고 공부한 내용을 정리한 게시글들입니다. 반드시 기억해야 할 내용들을 요약함과 동시에, 공부하며 든 생각을 함께 정리합니다.


Chapter 01. 객체, 설계

여느 공학보다 상대적으로 짧은 역사를 가지고 있는 소프트웨어 분야는 이론보다 실무가 앞서 있다. 특히 설계와 유지보수의 경우 소프트웨어의 라이프사이클 중 중요한 비중을 차지함에도 불구하고 이론은 실무를 따라오지 못하고 있는 상황이다. 그렇기 때문에 이론을 중심적으로 보기 보다는 실무에 초점을 두는 것이 바람직하며, 추상적인 개념과 이론은 훌륭한 코드 작성을 위해 필요한 도구일 뿐이다.

 

01) 티켓 판매 애플리케이션 구현하기

작은 소극장에서 추첨을 통해 일부 관람객에게 공연을 무료로 관람할 수 있는 초대장을 발송하였고, 공연 당일이 되어 소극장에서 관람객들을 맞이하는 프로그램을 작성한다.

 

해당 시스템을 구현하기 위해 처음으로 작성한 클래스 구조는 아래와 같다. (코드)

 

 

02) 어떤 문제점들이 있을까?

소프트웨어 모듈이 가져야 하는 세 가지 기능 - 로버트 마틴, 「클린 소프트웨어: 애자일 원칙과 패턴, 그리고 실천 방법」

  • 실행 중에 제대로 동작해야 한다.
  • 모듈은 변경을 위해 존해가므로, 변경에 용이해야 한다.
  • 코드를 이해하기 쉬워야 한다.

 

하지만 현재 작성한 클래스 구조는 뜻하는 대로 동작은 하고 있으나 변경에 용이하지 않고 이해하기 쉬운 코드가 아니다.

 

1. 정말 뜻하는 대로 동작하고 있는가?

// Theater 클래스의 enter 메서드
public void enter(Audience audience) {
    if (audience.getBag().hasInvitation()) {
        Ticket ticket = TicketSeller.getTicketOffice().getTicket();
        audience.getBag().setTicket(ticket);
    } else {
        Ticket ticket = ticketSeller.getTicketOffice().getTicket();
        audience.getBag().minusAmount(ticket.getFee());
        ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
        audience.getBag().setTicket(ticket);
    }
}

 

1.1 관람객(Audience)과 판매원(TicketSeller)이 소극장(Theater)에 종속적인 구조

  • 소극장이 멋대로 관람객의 가방을 뒤지고 있음
  • 소극장이 매표소의 티켓과 현금에 접근하고 있음
  • 이해 가능한 코드란 동작이 우리의 예상에서 크게 벗어나지 않는 코드
  • 상식과 다르게 동작하는 코드는 의사소통 비용을 발생시킴

1.2 코드를 이해하기 위해서 세부적인 내용들을 모두 기억하고 있어야 함

  • enter 메서드를 이해하기 위해서 Audience가 Bag을 가지고 있고, Bag안에는 현금과 티켓이 있고, TicketSeller가 TicketOffice에서 티켓을 판매하고 등과 같은 알아야할 것이 너무 많음

 

2. 변경에 취약한 코드

  • 변경에 취약한 의존적인 구조로 인해, 관람객 및 판매원에 변경점이 발생할 경우 소극장까지 영향을 미치는 구조로 되어 있음
  • 의존성(dependency)은 변경과 관련된 내용이다
    • 특정 객체가 변경될 때, 해당 객체에 의존하는 다른 객체도 함께 변경될 수 있음
    • 객체 사이의 의존성이 높은 경우, 결합도(coupling)가 높다고 함

 

객체지향 설계는 서로 의존하면서 협력하는 객체들의 공동체를 구축하는 것으로, 기능을 구현하는 데 필요한 최소한의 의존성만 유지하고 불필요한 의존성을 제거해야 함

 

03) 설계 개선하기

변경과 의사소통은 서로 엮여 있는 문제로, 소극장이 관람객의 가방과 판매원의 매표소까지 직접 접근함에 따라, 이해하기 어려운 코드가 되었고 이는 곧 결합도로 인한 변경의 어려움이 따르게 된다. 

관람객과 판매원을 자율적인 존재로 만들자!

 

1. 어떻게?

  • Theater에서 TicketOffice에 접근하는 모든 코드를 TicketSeller 내부로 옮기기
  • TicketSeller가 Ticket을 판매할 수 있도록 메서드 추가하기
  • 마찬가지로 Theater가 Audience의 세부 사항에 대해 알지 못하도록 차단하기

 

2. 캡슐화(encapsulation)

  • 개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것
  • 변경하기 쉬운 객체를 만드는 것이 목적
  • 접근 제한을 통해 객체와 객체 사이의 결합도를 낮춰서 변경에 용이하게 할 수 있음

변경된 구조

 

3. 무엇이 어떻게 개선되었는가?

  • Audience와 TicketSeller가 각자 자신의 소지품을 스스로 관리하게 됨
  • 즉, 가독성이 개선됨에 따라 이해하기 쉬운 코드로 변경됨
  • Audience나 TicketSeller가 변경되도 Theater를 수정할 필요가 없어짐
객체 내부의 상태를 캡슐화하고 객체 간에 오직 메시지를 통해서만 상호작용하도록 만들어야 한다.

높은 응집도(cohesion): 밀접하게 연관된 작업만을 수행하고, 연관성 없는 작업은 다른 객체에게 위임하는 것

 

4. 절차지향과 객체지향

4.1 절차지향(Procedural Programming)

  • 처음 구현한 기본 구조처럼, 프로세스와 데이터를 별도의 모듈에 위치시키는 방식
    • 프로세스 : Theater의 enter
    • 데이터 : Audience, TicketSeller, Bag, TicketOffice
  • 단점
    • 직관에 위배됨에 따라, 가독성이 떨어지고 원활한 의사소통이 어려움
    • 데이터의 변경으로 인한 영향을 지역적으로 고립시키기가 어려움
    • 예상 밖의 동작을 수행할 수 있음

 

4.2 객체지향(Object-Oriented Programming)

  • 데이터와 프로세스를 동일한 모듈 내부에 위치시키는 방식
  • 캡슐화를 이용해 의존성을 적절히 관리하여 결합도를 낮출 수 있음
    • 객체의 자율성을 높이고 응집도 높은 객체들의 공동체를 만들어 낼 수 있음
  • 책임의 이동(shift of responsibility)
    • 각 객체가 온전히 스스로에 대해 책임을 지는 것
    • 적절한 책임 할당은 이해하기 쉬운 구조와 읽기 쉬운 코드를 얻게 됨
    • 불필요한 의존성 제거를 통해 객체 사이의 결합도를 낮춰야 함
  • '적절한 객체'에 '적절한 책임'을 할당하는 것이다.

 

04) 결국 트레이드 오프다.

설계는 균형의 예술이다. 리팩토링을 통해 각 객체들의 자율성을 높일 수 있지만 전체 설계관점에서는 특정 객체에 대한 결합도가 증가할 수도 있음.

훌륭한 설계는 적절한 트레이드오프의 결과물이다.

 


정리

요구사항은 항상 변경되기 때문에 변화에 유연하게 대처할 수 있는 코드를 작성해야만 한다. 설계는 이 코드 작성의 일부이며, 좋은 설계란 코드를 작성할 때 어떻게 변화에 유연하게 대처할 수 있는 방향으로 배치할 것이냐를 의미한다. 여러 방법론 중 객체지향 프로그래밍은 의존성을 효율적으로 통제할 수 있는 다양한 방법을 제공하며, 자신의 데이터를 스스로 책임지는 자율적인 존재인 객체들 간의 상호작용으로 구현된다.


'구글 엔지니어는 이렇게 일한다'를 공부하면서도 느꼈지만, 모든 것은 결국 선택의 연속이다 라는 것을 많이 느낀다. 가장 중요한 것은 선택에는 책임이 따르는 것이며 프로그래밍 세계에서는 선택으로 인한 결과가 고스란히 나와 동료에게 돌아오곤 한다.

 

늘 어떻게 하면 최대한 의사소통 비용을 줄일 수 있는 코드를 작성할 수 있을까에 대한 고민을 자주 하는 편이다. 그 첫 단추로는 아무래도 코드 포맷일 것이다. 그 어떤 방법론이든 결국 최소한의 규칙을 지키지 않고 작성된 코드는 일관성을 깨트리기 때문이다. 그 두 번째 단추가 객체지향이라 생각한다. 각각의 객체들이 온전히 자신에 대해서 책임질 수 있도록 설계하는 것이 무엇보다도 중요하다는 것을 다시금 느낀다. 

 

동시에 개발 방법론의 경우 단 하나만이 채택되어 사용된다고 생각하지는 않는다. 복잡한 비즈니스 로직을 구현해내기 위해서는 각각의 객체들이 온전히 데이터와 프로세스를 책임지더라도, 흐름을 제어해주는 부분은 절차지향이든 함수형 프로그래밍이든 다른 방법들이 함께 존재할 수 있기 때문이다. 하지만 책에서 얘기한대로 현실에서 수동적인 존재들을 객체지향 속에서는 능동적이고 자율적인 존재로 바꿔줄 수 있으며, 각자 정확한 역할과 책임을 부여하기에는 더할 나위 없이 좋은 수단이라고 생각한다.

반응형
profile

KwanIk Devlog

@갈닉

노력하는 개발자가 되고자 합니다. GitHub이나 블로그 모두 따끔한 피드백 부탁드립니다 :)