일상

오브젝트 세미나 정리 및 후기(객체지향 vs 절차지향)

ri5 2022. 9. 1. 01:33

오브젝트 세미나 참여 이유?

요즘 클린 코드책과 소프트웨어 장인을 읽으면서 비록 개발팀 규모는 작지만 우리도 이러한 좋은 품질의 코드를 추구하고 변경되기 전보다 변경된 후가 더 깔끔해진 코드를 보고 작은 성공을 경험하고 성장한다는 것을 전파하고 싶었다. 그렇기에 내가 먼저 바뀌어야 했고 어제보다 더 발전해야했다. 그러다 우연히 개발자 커뮤니티에서 객체지향의 사실과 오해, 오브젝트를 집필하신 조영호 개발자님이 디어코퍼레이션에서 세미나를 여신다는 것을 보게 되었고 참여하게 되었습니다.

 

도메인이란?

입사를 하고 인수인계를 하게 되면 가장 먼저하는 것이 도메인에 대한 이해이다. 개발지식 만큼 가장 중요하게 생각하는 것이 도메인에 대한 지식이다. 조영호님은 이것을 실무적인 관점으로 간단하게 요약했는데 오프라인에서 시스템적으로 자동화하고 싶은 부분 예를 들어 배달앱을 한다고 하면 배달은 오프라인에서 해야하지만 거리안내나 손님과의 매칭 등은 자동화할 수 있다. 이처럼 우리가 실생활에서 시스템적으로 구현하고 싶은 범위를 도메인이라고 한다.

https://dev.to/microtica/the-concept-of-domain-driven-design-explained-1ccn 펌

영화 예매 시스템을 통한 도메인 설계

영화에 대한 도메인

  • 영화: 러닝타임, 제목, 상영 시간
  • 상영: 스크린, 예매 대상
  • 할인 정책: 가격을 계산
    • 절대 금액 빼는 방식
    • 비유해서 금액을 빼는 방식
  • 할인 조건
    • 조건 할인: 특정 조건에 충족하는 경우(조조할인, 10회 상영)
    • 날짜 할인(월요일 10시, 목요일 10시)

위와 같은 도메인이 있고 우리는 시스템적으로 구현을 한다고 하면 어떻게 접근하는 방법으로 접근해야 좋을까? 
도메인을 설계하는 방법은 두가지가 존재합니다. 바로 절차적인 설계와 객체지향설계로 나뉘는데 각각 설계를 설명하면서 어떤 점이 다르고 유지보수하는 관점으로는 어떤 것이 유리한지 확인해볼 것이다. 

 

절차적인 예매시스템 설계

우리는 위와같은 도메인에 대한 프로세스를 설계할 때 서로 다른 시점으로 독립적으로 보게 된다면 그냥 절차적인 코드로 구현을 하게 된다고 합니다. 이것은 프론트, 백엔드 할 것 없이 공통 사항 입니다. 

서버 관점으로 설계를 한다고 하면 우리는 일단 데이터부터 설계를 하게 된다. 우리는 데이터베이스를 그리는데 pk, fk를 그리고 정규화를 그린다. 

public class Movie {
	private Long id;

	private String title;

	private LocalTime runningTime;
    
    public Long getId() {
    	return this.id;
    }
    
    public String getTitile() {
    	return this.title;
    }
    
    public LocalTime getRunningTime() {
    	return this.runningTime;
    }
    
    public void setId(Long id) {
    	this.id = id;
    }
    
    public void setTitile(String titile) {
		this.title = title;
    }
    
    public void setRunningTime(LocalTime runningTime) {
    	this.runningTime = runningTime;
    }
}

우리는 이처럼 객체가 무엇을 행동하고 어떠한 문맥에 사용되는지 전혀 생각하지 않았다. 엄청 기계적인 코드를 작성하고 있는 것이다. 이러한 어디에 쓰일지 모르는 메서드를 만들고 시스템은 다만들어져 있으니 이걸 어떻게 처리할지 고민을 한다.

public class ReservationService {

	public void reservation(Movie movie, Order order) {
    	List<DiscountpPolicy> discountPolicies = discountpPolicyRepsitory.findAll();
        
        for(DiscountpPolicy discountpPolicy : discountPolicies) {
        	if(order.getPaymentDate.isEqual(discountpPolicy.getDiscountDate())) {
            	order.setAmount(order.getAmount() - discountpPolicy.getDiscountedAmount)
            }
            
            ...
        }
    }
}

우리는 결국 이처럼 절차적인 코드를 짜다보면 객체가 아닌 데이터가 되어버린다. 데이터가 된다는 것은 우리는 하나하나 전부 밖에서 제어하게 되고 포문과 이프문이 중복이 되게 된다. 이러한 코드방식은 코드가 무엇을 한다기 보다 데이터를 다루는데 코드를 사용하게 된다. 그리고 항상 타입을 체크하기 때문에 해당 객체 밖에서 값을 체크하게 된다.

 

이러한 코드가 나쁜 방식일까? 결론은 나쁠 수도 있고 나쁘지 않을수도 있다. 이렇게 짜는 것이 좋을 수 있는데 트레이드 오프라고 하는데 환경에 따라 나쁘지 않은 결과를 가져올 수 있을 수 있다는 것이다. 하지만 요구사항이 자주 바뀌는 코드를 이렇게 구현하다보면 한곳에 제어가 몰리게 되고 결국 응집도가 떨어지는 코드가 될 수 밖에 없다. 

 

객체지향적인 예매시스템 설계

우리는 보통 데이터 부터 설계를 하고 그에 맞는 객체를 끼워 맞추는 방식으로 설계를 한다. 하지만 이렇게 설계를 하다보면 객체는 데이터를 통해 상태를 확인할 수 밖에 없는 로직이 설계가 되고 하나의 메서드나 객체에서 제어를 다 하게 된다.. 흔히들 말하는 SOLID, OOP등에 대한 디자인 패턴을 사용하기 어려운 구조로 설계가 되는 것이다. 객체 지향 설계는 객체가 무엇을 할지 어떤 행동을 취할지 부터 설계를 해야한다.

 

TDD가 객체지향적으로 설계하는데 대표적인 예로 들 수 있는데 TDD를 간단하게 설명하자면 실패하는 테스트를 만들고 그다음 어떻게든 성공하게 코드를 짠다음 테스트가 성공하면 리팩토링하고 다시 테스트를 동작시키는 방법론이다. 이런 부분을 객체 지향 설계 관점으로 보게되면 TDD로 객체가 어떤 행동을 할지 설계하고 결과를 Assert문으로 확인을하는 것이다. 

이러한 방법론은 객체지향 설계와 동일합니다. 코드의 관점으로 보자면 우리는 컴파일러의 이러이러한 클래스가 필요한 것이 아닌 우리는 런타임 환경에서 이러한 객체들이 어떻게 행동할꺼야를 중점으로 봐야한다. 결국 중요한 것은 객체부터 행동을 설계를 하는 것이다.

절차적인 서비스

절차적인 설계는 문맥을 보지않고 클래스를 만들고 데이터가 필요해서 행동을 예측한 설계를 하게 된다. 결국 의미없는 필드와 메서드들이 나열이 되고 이런 메서드가 필요하겠지 하고 데이터 주도 설계를 들어가게 된다.(ex: 무분별한 Getter와 Setter를 사용) 그렇다면 행동부터 설계하면 어떤 것이 달라질까? 

 

객체 지향 적인 예매 시스템 설계 접근 방법

예매할 때 예매를 해야되는 행동이 있고 그 행동을 해야하는 객체가 필요하다. 그러한 객체를 찾을 때 어디서 찾아야 할까? 바로 도메인에서 찾으면 된다. 그 이유는 연상을 하고 접근하기 쉽기 때문이다. 

  • 예매라는 도메인이 필요하다.

  • 영화관은 결국 자리를 선택하고 예매해야되기 때문에 자리라는 도메인이 필요하고 자리를 선택하는 메시지(객체)를 주고 받아야한다.

  • 결제 하기전 할인조건에 충족하면 할인을 해준다. 

  • 여태까지 받을 정보를 통해 결제를 해야한다.

  • 이제 필요한 정보들을 채우면 된다. 

객체 지향은 보는 것처럼 설계하는 것에 규칙이 없고 답이 없다. 그렇기 때문에 서로의 공감대를 가질 수 있는 설계를 하는 것이 중요하다. 예를 들어 영화관 자리라는 객체가 있고 해당 객체는 자리 위치를 알고 있고 자리 금액을 물었을 때 얼마인지 대답할 수 있다. 이처럼 객체는 동사만 가지고 있는 절차지향과 달리 동사와 명사를 함께 가지고 있다. 좋은 객체는 동사와 명사를 보고도 해당 객체가 무엇을 의미하는지 연상하기 쉬운 것들로 이루어져 있다. 이것이 흔히들 말하는 응집성이 높고 결합성이 떨어지는 코드 설계이다.

 

다형성의 중요성

실무를 하다보면 생각 보다 코드를 재사용하는 경우는 적다. 하지만 같은 행위를 다르게 행동하는 경우는 많이 있다. 쉽게 예를 들자면 결제라는 도메인은 결국 결제하는 행위와 결제를 취소하는 행위는 같지만 카드 결제의 결제와 모바일 결제의 행동은 다르다. 이처럼 하나의 추상적인 객체를 두고 다형성있는 객체를 만들게 되면 기존에 결합된 객체를 건들일 필요없이 확장하기 좀 더 편리하게 구현할 수 있을 것이다. 다형성을 설계하는데 있어서 중요한 점은 추상된 객체를 먼저 설계하는 것이 아닌 구현된 객체를 전부 설계하고 구현한 다음 추상된 객체를 설계해야된다는 점이다.

1부 정리 후기

1부는 말그대로 객체지향에 대해 맛보기인 시간을 정리한 글이다. 1시간 가량인데도 너무 많은 내용을 담고 있어 생략된 부분이 많았다. 그중 기억에 남는 에 좋은 구절들이 몇가지를 알려드리자면  "모든 디자인 패턴과 방법론은 결국 소프트웨어의 변화에 대해 대응하기 위해 만들어진 이론이다", "수정하기 쉽고 유용한 코드를 만드는 것이 중요하다.", "객체 지향적 코드는 읽기는 어렵지만 이해하기 쉽고 절차지향인 읽기는 쉽지만 이해하기는 어렵다." 등이 있었다.

세미나를 참석한지는 몇칠이  지났지만 복기하면 복기할수록 생각을 많이하게 되었고 전에는 객체 지향에 대해 망망대해였다면 이제는 아주 얕은 길이 드러나 걸어갈 수 있는 느낌이다.