본문 바로가기
Swimming/Think

객체에 관한 고민 - 책임과 기능은 어디까지인가.

by JKROH 2023. 2. 25.
반응형

 얼마 전, 디스코드에서 사용하는 별명을 바꿔야 할 일이 있었다. 바꿀 별명을 설정하고 저장 버튼을 누르지 않고 설정 창을 닫으려 하니 시스템에서 '저장되지 않은 변경사항이 있습니다.' 라는 메세지를 보냈다. '아, 내가 저장 버튼을 안 눌렀구나.'별생각 없이 저장 버튼을 눌러 바뀐 별명으로 설정을 변경하고 디스코드를 이용하던 중에 이런 생각이 문득 들었다.

별명을 변경하는건, 내가 하는 일이 맞나?
나는 바꿀 별명을 설정했는데 왜 시스템이 그걸 저장 안 했다고 뭐라 그러는 거지?

 

 생각이  '그러면 이 시스템 내에서 나를 어떻게 생각하길래 이렇게 간섭하는 거지?'로 이어지고 나니, 결과적으로는 '그렇다면 이 디스코드라는 프로그램 안에서 나라는 사람은 어떤 기능과 역할을 하고, 어떤 책임을 가지고 있는 거야?'라는 생각에까지 생각이 꼬리에 꼬리를 물고 이어졌다.

 

 모든 서비스에는 가장 중요하고 핵심적이며 어떻게 보면 서비스 자체가 의존하고 있는 객체가 있다. 바로 "User"다. 세상 어느 누가 어떤 서비스를 제공해도, 그 서비스를 사용하는 유저가 없다면 그 서비스는 존재할 수가 없다. 신이 지구온라인이라는 게임을 만들어서 서비스한다고 해도 그 지구온라인을 플레이하는 유저, 다시 말해 사람이 없으면 지구온라인이라는 게임은 그 어떤 기능도 할 수 없다. 즉, "모든 서비스의 시작과 끝은 그 어떤 도메인도 아닌 바로 유저다."

그럼 내 별명 바꾸는 것 정도는 내가 하고 싶은 대로 해도 되는 것 아냐?
왜 서비스 따위가 유저님께서 하시는 일을 자기들 입맛대로 안 했다고 간섭해?

 

 그래, User 객체를 만드는 것 정도는 UserFactory에게 맡긴다고 치자. 어쨌든 '나'라는 사용자가 해당 시스템을 이용하려면 해당 시스템에 '나'를 대변할 하나의 객체는 만들어야 되니까. 아니 근데, 고객님이 그 정도로 도와드렸으면 됐지, 왜 내가 하려는 일에까지 간섭을 하냐고. 내가 심지어 나라는 껍데기를 벗고 당신들 시스템에 들어가기까지 했다니까? 나한테 이 정도 기능과 책임도 주지 않는다고?

 

 그러니까, 당연히 changeName(String newName) 정도는 User객체에 담겨있어도 되는 거 아니냐 이거다. User 같은 핵심 객체라면 이 정도 기능은 당연히 가지고 있어야 하는 거 아니냐는 거다. 여러 강의를 듣고, 여러 사람들과 이야기를 나눠보고, 직접 간단한 프로그래밍을 해보며 내가 세운 가장 근본적인 객체 설정의 기준은 다음과 같다.

모름지기 객체라면, 자신이 담당하는 기능이 있어야 한다.
객체는 자신이 담당하는 기능으로써 자신을 보여주고 증명한다.
다시 말해, 객체는 기능으로써 정의된다.
기능이 없다면, 그것은 객체가 아니다. 그저 데이터 클래스에 지나지 않는다.

 

 그런데, 비단 디스코드뿐만이 아니라 모든 서비스에서 내가 내 아이디, 닉네임, 이메일과 같은 정보를 바꾸고 싶다면 그 일은 해당 서비스가 맡아서 담당한다. 나는 그냥 바꾸고 싶은 이름의 데이터를 '전달만 한다'. 나는 바꾸고 싶은 이름을 입력하고 저장 버튼을 누르면, 실질적으로 바뀐 이름을 받아서 DB에 '얘 이름 바꾼데'라는 업데이트 쿼리가 담긴 트랜잭션을 날리고 DB를 업데이트하는 일은 서비스가 담당한다.

 

 그러면 서비스 내에 뭐 UserModifier 같은 객체가 있어서, User가 원하는 변경을 대신 수행한다는 건데. 뭐 서비스의 다른 기능을 이용하는 것도 아니고, "자기 이름을 바꾸는 기능조차도 다른 객체에게 위임한다면", 그 객체가 객체로서의 존재의의가 있냐는 말이다. User 객체가 changeName() 조차도 하지 못한다면, "User 객체 내에 담길 수 있는 기능은 도대체 무엇이 있냐는 말이다".

 

 우리는 그러면 모든 서비스의 핵심 객체인 User를 단순히 User의 정보만 저장해 놓는 데이터 클래스로 둬야 하는가? 그렇다면 설계를 마치고 프로그래밍을 시작하는 단계에서 한 번 생각해 보자. 예를 들어서, '우리는 User의 이름을 바꾸는 changeName() 메서드가 있어. 그럼 이걸 테스트하는 테스트 코드를 짜보자.' 라고 하는 상황이 있다고 생각해 보자. 만약 UserModifier 객체가 있다면, User 객체의 인스턴스를 하나 만들고 UserModifier에 다시 그 객체를 넣어서 이름을 바꾸는 메서드를 실행시키는 테스트 코드를 작성해야 한다. "서비스에서 가장 중요한 주체인 User가 User를 위한 서비스를 테스트하기 위해서 그냥 데이터로만 전달된다."

 

 그런데, 그렇다고 User 객체 자체 내부에 User의 초기 설정을 바꾸거나, 비어있는 데이터를 추가할 수 있는 메서드를 만들어놓는다면, 우리는 User같이 중요한 객체 내부에 "무수히 많은 public 메서드를 만들어야 된다". 비단 이름을 바꾸는 것뿐만이 아니다. 우리는 회원가입할 때 수많은 데이터를 입력하고, 추후에 수정하기도 하고, 처음 가입할 때는 입력하지 않았던 데이터를 추가로 입력하기도 한다. 당장 티스토리에 가입한다고 쳐보자. 우리는 아이디, 이메일, 비밀번호, 닉네임, 생일, 전화번호와 같이 뭐 엄청나게 많은 데이터를 넘겨줘야 한다. 그런데, 그 데이터들을 수정하려고 public 메서드를 그렇게 마구마구 만들어버리면, "도대체 이 객체는 누가 지켜주는가?" 다시 말해, 아무나 다 이 객체의 내용물을 수정할 수 있게 된다. "가장 지켜줘야 하는 놈의 캡슐을 깨버리는 꼴이다".

 

 두 가지 방법 모두 각자의 장단이 확실했다. 먼저 'User가 자기 일은 알아서 처리하는 경우'에는 다음과 같은 장점이 있을 것이다.

  • User 객체의 정의가 명확해진다. User 객체는 말 그대로 Playable 한 User 그 자체다.
  • 추가적인 객체를 생성할 필요가 없어 시스템의 구조가 단순해진다.
  • 테스트 코드 작성 시 다뤄야 할 메서드가 하나면 된다. 즉, 리팩토링이 쉬워진다.

단점은 이런 것들이 있을 수 있다.

  • User가 지나치게 많은 책임을 맡는다. 자기 정보는 말 그대로 자기가 전부 처리해야 한다. 그 처리해야 할 데이터가 한두 개가 아니라는 게 문제다.
  • 자신의 정보를 처리하는 기능은 당연하게도 public하게 만들어져야 한다. 즉, 핵심 객체가 보호받지 못하게 된다.

반면에 'User가 해야 할 일을 다른 객체가 맡는 경우'에는 다음과 같은 장/단이 있을 것이다.

  • User 객체는 거의 모든 일을 private하게 할 수 있다. 즉, 핵심 객체가 보호받는다.
  • User 객체의 책임을 나눠줄 수 있다. User 객체에 어떤 문제가 발생했을 때, 해야 할 일이 적어진다.
  • User가 객체보다는 데이터 클래스에 가까워진다. 기본적인 개념 자체가 흔들리게 된다.
  • 객체가 나뉘다 보니 시스템의 구조가 복잡해진다.

 사실 이런 고민을 한 게 이번이 처음은 아니다. 작년에 우아한테크코스 문제들을 풀면서 '입력값을 검증하는' 기능을 구현했어야 했는데, 해당 기능을 어디에 구현하느냐를 가지고 고민한 것이 처음이었다. 로또 문제가 대표적인데, 로또 번호를 입력받고 그 번호가 당첨 번호면 뭐 상금을 출력하는 그런 간단한 프로그램이었다. 문제는 '로또 번호를 검증하는' 부분에서 발생했다. 로또 번호는 당연히 객체로 만들었다. 그 문제의 가장 핵심적인 존재니까. 그런데 문제는, 고작해야 숫자 주제에 자기를 검증하는 게 말이나 되나였다.

 

 로또 번호가 객체라는 거는 알겠어, 얘가 업으면 뭐 아무것도 안되니까. 그런데 그래봐야 숫자 주제에 자기가 뭔지 검증까지 한다고? 이게 뭔 소리야. 라는 생각이 들어 처음에는 NumberValidator 라는 객체를 만들어 숫자의 검증을 맡겼다. 그런데 막상 그러고 나니 로또 번호 객체는 아무것도 하는 일이 없더라. 당시에는 짧은 시간에 문제를 제출해야 하므로 깊이 고민할 여유가 없었고 해당 프로그램이 작동을 잘했으니 일단 넘어가기는 했었다. 

 

 그런데 이번에는 달랐다. 시간도 좀 있고 그래서 깊이 생각해 봤는데, 아무래도 둘 다 자기들 나름대로 자신의 입장을 대변할 수 있으니 더더욱 결론이 나질 않았다. 아무튼 지금 당장의 내 결론은 "그래도 수정해 주는 객체가 있는 게 맞다." 로 굳어지기는 했다. 아니 어쨌든 User를 지키기는 해야 되잖아... 그리고 실생활에서도 내 이름을 뭐 내가 바꾸고 싶다고 막 바꾸나? 개명 신청하고, 그게 수리가 되면 가정법원해서 해주지. 뭐 지금 당장은 내가 생각하지 못하는 기능과 책임들이 실제로 서비스를 운영하다 보면 User에게 넘어가겠지. 물론 안 그럴 수도 있고.

 

 지금은 일개 개꿈이일뿐이지만, 멀지 않은 미래 개발자 타이틀을 달고 설계에 대한 고민을 하다 보면 이 고민은 다시금 내 발목을 잡을 것 같다. 그러나 이 문제는 결국 '객체를 바라보는 관점' 차원의 문제이기 때문에 이 문제만큼 정답이 없는 문제가 없지 않나 싶다. 좀 더 큰 단위의 프로젝트를 진행하면서 해당 문제에 대해 계속 고민하다 보면 그래도 "어떤 때에는 객체 자체에 기능과 책임을 부여해야하고 어떤 때에는 해당 기능과 책임을 분배해줘야 하는지에 대한 기준" 은 잡히겠지 뭐. 결국 또 가장 중요한 건 끊임없이 '왜 그래야 하는데?'를 스스로에게 묻는 것이라는 결론으로 마친다.

반응형

댓글