ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [객체지향 프로그래밍] 「객체지향의 사실과 오해」를 읽고 업무 프로세스 개선하기
    Design Pattern 2023. 7. 2. 02:52

     개발을 하며 가장 어려운 부분은 기능 구현보다도 단순하고 유지보수가 쉬운 코드를 만드는 것 같습니다. 처음 일을 시작했을 때에는 하나의 모듈이 클래스들과 그 클래스를 구성하고 있는 함수들로만 보였습니다. 단편적인 시각에서 코드 한줄 한줄을 바라봤고 기능 구현에 초점이 맞춰져 있었기 때문에 하나의 엔진을 '유기체'로 보는 것이 너무나도 어려웠습니다. 그래서 6개월 전 쯤 「객체지향의 사실과 오해」라는 책을 사서 읽기 시작했습니다. 

     

     이 책을 관통하고 있는 "객체지향은 내부와 외부를 명확하게 구분하는 객체들로 구성된 협력 공동체다. 메시지를 수신한 객체는 자율성을 가지고 자신의 역할을 수행할 책임을 가지고 있다."라는 개념이 너무 추상적이고 멀게만 느껴졌습니다. '객체가 사람도 아니고 어떻게 자율성을 가진다는거지? 내가 서로 협력하는 객체를 만들었는지도 모르겠는데 그 객체의 내부와 외부를 구분해...? 엔진이 돌아가게는 만들었는데..' 분명 글을 이해하기는 쉬운데 도대체 코드 작성에 어떻게 적용을 시켜야 할지 전혀 감이 오지 않았습니다. 결국 1/3정도 읽다가 손을 떼었습니다. 그러다 저번주 업무 중 코드가 너무 복잡하다는 피드백을 듣고 리팩토링 작업에 들어가기로 했고, 다시 이 책이 불현듯 떠올랐습니다. 

     

     현재 유지보수하고 있는 엔진의 역할에 대해서 간단히 말씀드리자면 SNMP 프로토콜을 사용해서 특정 장비의 이벤트를 알리는 Trap 메세지를 분석·처리합니다. Trap 메세지를 송신하는 Trap Sender의 종류에는 General, Woorinet, Telefield 세가지가 있습니다. Trap을 수신하는 클래스 안에서 Trap Sender의 종류에 따라 Trap 메세지를 처리하는 로직은 수많은 조건의 나열로 이루어진 복잡한 구조를 띠고 있었고 추후 Trap Sender가 더 추가될 가능성이 있어 추상화가 꼭 필요한 상태입니다. 따라서 이번주 포스팅은 「객체지향의 사실과 오해」를 읽고 이해한 내용을 코드에 어떻게 적용시켰는지 클래스 다이어그램을 통해 설명해보도록 하겠습니다. 아래는 책의 핵심 메세지입니다. 

     

    클래스의 구조와 메서드가 아니라 객체의 역할, 책임, 협력에 집중하라.
    💡 객체지향은 상호작용하는 객체들의 공동체
    - 객체지향이란 시스템을 상호작용하는 자율적인 객체들의 공동체로 바라보고 객체를 이용해 시스템을 분할하는 방법이다. 외부의 도움을 무시한 채 모든 것을 스스로 처리하려고 하는 전지전능한 객체는 내부적인 복잡도에 의해 자멸하고 만다.

    💡객체는 각자의 역할을 수행하며 서로 협력하는 자율적인 존재
    - 자율적인 객체란 상태와 행동을 함께 지니며 자기 자신을 책임지는 객체를 의미한다. 행동이란 외부의 요청 또는 수신된 메세지에 응답하기 위해 동작하고 반응하는 활동이다. 행동의 결과로 객체는 자신의 상태를 변경하거나 다른 객체에게 메세지를 전달할 수 있다. 객체는 시스템의 행위를 구현하기 위해 다른 객체와 협력한다. 각 객체는 협력 내에서 정해진 역할을 수행하며 역할은 관련된 책임의 집합이다. 역할의 가장 큰 가치는 하나의 협력 안에 여러 종류의 객체가 참여할 수 있게 함으로써 협력을 추상화할 수 있다는 것이다. 

    💡 추상적인 인터페이스를 제공할 것 
    - 객체는 다른 객체와 협력하기 위해 메세지를 전송하고 메세지를 수신한 객체는 메세지를 처리하는 데 적합한 메서드를 자율적으로 선택한다. 지나치게 상세한 메세지를 보내는 것은 객체의 자율성을 저해한다. 대신 좀 더 추상적인 수준의 메세지를 수신할 수 있는 인터페이스를 제공하면 수신자의 자율성을 보장할 수 있다.

       출처: 객체지향의 사실과 오해

     

     기존의 Trap Sender별 Trap 메세지 처리 로직은 아래 도식과 같습니다. Trap을 수신하면 콜백함수 recv_trap_msg()가 호출되고 Trap Sender가 General인지, Woorinet인지, Telefield인지 판단해 General이면 Trap 클래스 내부에서 check_event()로 이벤트 체크 후 rtn_result()로 결과를 리턴하고, General이 아니면 Woorinet이나 Telefield의 객체를 생성해 해당 클래스 안에서 이벤트 체크 후 결과를 업데이트합니다. 일반적인 Trap Sender가 아닌 Woorinet과 Telefield의 Trap 메세지를 수신한 경우만 별도의 클래스로 만들어 처리하고 있었습니다. 이러한 로직을 책의 내용을 적용시켜 다음과 같이 바꾸었습니다.

     

    변경 전 클래스 다이어그램

     

    객체지향은 상호작용하는 객체들의 공동체

     변경 전에는 기능 구현에 초점이 맞춰져 있었다보니 Trap 클래스에 여러가지 기능을 추가하게 되면서 해당 객체의 역할이 너무 많았습니다. 크게 1) Trap 메세지 수신, 2) Trap Sender 판단, 3) Trap Sender별 Trap 메세지 처리, 4) 결과 전송의 역할이 있었고 3) Trap Sender별 Trap 메세지 처리 로직이 특히 복잡했습니다. if-else 조건절을 통해 처리 방식을 선택하는 구조로 추후 Trap Sender가 더 추가된다면 더 많은 조건절이 무한정 늘어날 예정이었습니다. 바로 이 상태가 내부적인 복잡도에 의해 자멸하고 있는 상태라는 생각이 들었습니다. 현실세계에서도 외부의 도움을 무시한 채 모든 것을 스스로 처리하려고 하는 사람은 결국 고립되듯 이 안타까운 클래스의 고민을 대신해줄 HandlerFactory 클래스를 만들어줘야겠다고 판단했습니다. HandlerFactory.get_handler()라는 함수에 Tran Sender 타입만 파라미터로 제공하면 handler 객체를 반환해줍니다.

     

    객체는 각자의 역할을 수행하며 서로 협력하는 자율적인 존재

     TrapComm은 Trap 메세지 처리 기능을 가진 클래스로 Trap 클래스가 import해서 사용하고 있었습니다. Trap 클래스와 협력하는 객체로서 동작하기보다는 그저 parse_trap_msg() 메서드를 제공하기 위한 클래스일 뿐이었습니다. 세 가지 핸들러 General, Woorinet, Telefield 역시 Trap 메세지를 처리하기 위한 클래스이므로 TrapComm에 메세지 처리를 위한 모든 메소드들을 정의하고 General, Woorinet, Telefield이 TrapComm을 상속받도록 했습니다. Trap클래스가 가지고 있던 check_event(), rtn_result()함수도 분리해서 General클래스로 옮겼습니다. 

     

     세 가지 Trap 메세지 처리 객체 General, Woorinet, Telefield 핸들러는 HandlerFactory 객체의 메세지에 응답하여 Trap 메세지를 처리하는 역할을 부여받습니다. Trap 객체는 HandlerFactory 객체에게 Trap 메세지 처리 객체를 반환해 줄 것을 요청하고 HandlerFactory 객체에 의해 트랩 메세지 처리를 요청받은 Trap 메세지 처리 객체는 Trap 객체가 수신한 Trap 메세지를 파싱하고 이벤트 발생 여부를 도출해냅니다. "Trap 메세지 처리"로 추상화된 역할을 위해서는 General, Woorinet, Telefield 핸들러가 참여하고 있습니다.

     

    추상적인 인터페이스를 제공할 것 

     마지막으로 TrapComm.run()함수는 인터페이스로 작용하고 있습니다. Trap.recv_trap_msg()에서 SenderFactory.get_sender()를 통해 트랩 메세지 처리 객체를 리턴받고 트랩 메세지 처리 객체의 인터페이스인 run 함수를 실행시켜 결과를 만들어냅니다. Trap 메세지 처리 객체 내부의 동작을 모르더라도 run()을 실행하는 단순한 인터페이스만으로도 결과를 도출하도록 했습니다. 결과적으로 Trap 클래스 객체의 역할을 1) Trap 메세지 수신, 2) Trap Sender 판단, 3) SenderFactory 객체에게 Trap Sender를 전달해 Trap 메세지 처리 결과 "요청", 4) 트랩 메세지 처리 객체에게 "응답"받은 결과 전송으로 변경했습니다. 

     

    변경 후 클래스 다이어그램

     

     많은 기능을 가진 클래스를 단순하게 표현하기 위해 최소한의 기능만 노출시키는 일은 쉽지 않았습니다. 종종 프로그래밍이 실제 세계와 많이 닮아있다는 생각을 하곤 합니다. 일상생활에서도 단순함에는 힘이 있습니다. 상대가 이해하기 쉽게 정보를 요약해서 전달하는 과정에는 배려가 들어가있기 때문인 것 같습니다. 프로그래밍도 혼자 하는 것이 아니기에 실력도 배려의 일종이 아닐까 하는 생각이 듭니다. 배려가 부족한 저는... 열심히 실력을 쌓아야겠습니다.

     

    참고:

    객체지향의 사실과 오해(2015) - 조영호

    이미지 출처: https://www.yes24.com/Product/Goods/18249021

     

    댓글

Designed by Tistory.