OOP(객체지향 프로그래밍)을 나에게 묻다

내가 알던 OOP가 아니야

글을 써내려 가기전에 이 글은 “OOP에 대해 알아보자!” 하는 글이 아닌 과연 “OOP에 대해서 잘 알고 있나?” 본인에게 질문을 던지면서 쓴 글입니다.

OOP를 알고 있냐?

OOP의 개념에 대해서는 컴공학부생 4년 동안 끊임 없이 들어왔던 개념중에 하나 입니다. 개념또한 공채를 준비하면서 CS전반적인 개념에 대해 공부를 하면서 이론은 확실하게 익히고 있었습니다. 실제로 면접에서.. 질문을 받았던것중에 하나였습니다.

image

개념은 잘 알고 있는데 실상 코드에 적용시켜본적은 아마 본인도 이게 OOP의 어떤 원칙을 사용해서 코딩을 한건지 인지를 못할때가 많았던거 같습니다. 결론은 이론은 어느정도 확립이 되어있는데 이를 어떻게 적용시켜야할지 또 그게 맞는지 틀린지 피드백 해줄 기회가 없었습니다.

아마 모든 컴학도생들이 공감하실 내용이라 생각이 듭니다. “과연 내가 짠 코드가 OOP를 잘 따르는 가?.. 원칙에 어긋나는 코드인가?” 이런 부분에 대해 관심을 가지면 다행이지만 설령 갖는다 해도 코드리뷰를 통한 피드백을 받기는 어려운 실상입니다.

OOP를 잘하는 사람? 그게뭐지?

자 그럼 OOP를 잘한다? 라는 말은 무엇일까요? 저는 “잘 짜면 그게 OOP지~” 라는 생각도 했습니다. 학부생때 3번의 인턴 경험을 하면서 코드리뷰라는 것을 통해 제 코드를 되돌아 처음 되돌아 보았던거 같습니다. 내가 코딩한 시스템이 잘짠건지? 아닌지에 대한 피드백을 받지 못한다면 영원히 그 코드는 자기만의 세상에 빠진 코드가 될것입니다. 그래서 코드리뷰의 경험은 정말 중요한 것 같습니다.

코드리뷰를 받은 내코드는 한마디로 “개판이구나..” 라는 생각이 가장들었던거 같습니다. 우선 여기에는 저의 몇가지 핑계아닌 핑계를 늘어 놓아보자면..

  • 제한된 기한에 기능을 최대한 구현했어.. 그래야 점수를 주거든 -> 스파게티 코드
  • 딱히 짜놓은 코드에 대해 코드리뷰를 안해줬어 -> 앞만보고 코딩
  • 객체지향?패턴공부? 중요하지 일단 외워! 면접에서 물어볼꺼니깐 외워외워 -> 암기식 공부

이런저런 핑계로 내코드는 OOP를 잘 적용한 사례야! 라고 자신있게 말 할 수 있는 학부생은 그리 많지 않을 것 같습니다. 저를 포함해서 말입니다.

“OOP잘할려면 경력좀 쌓고 코드 좀 많이 보고 하면 돼~” 라고 말을 할 수 없는게 코드의 이런 원칙적인 부분은 시간이 해결해주는게 아닌거 같습니다.

그럼 니가 알던 OOP랑은 어때?

이론적으로봤을때 OOP의 목적은 코드의 재사용성과 중복제거 가 가장 궁극적인 목적이라고 할 수 있습니다.

그럼 세부적으로 생각했을때는 OOP의 다양한 특징이 있겠지만 그중에서 상속(Inheritance)에 대해서 저는 가장 강력하다고 믿고 있었습니다. 학교에서 또 그렇게 배웠기도 했습니다. 실제로 상속을 통해서 코드의 재사용성이라던지 복잡한 구조에서 상당히 중복이 많이 발생할 수 있는 것을 super class와 sub class 사이의 관계를 명확하게 하니깐요.

image

하지만 상속은 고강도의 Coupling(결합력) 을 갖고 있기 때문에 이로 인해 발생하는 문제가 많습니다. 물론 재사용성을 통해 프로젝트 전반적인 중복을 줄일 수 있는 것은 사실입니다. 하지만 코드가 점점 쌓이고 상속의 부모-자식 관계의 클래스가 많이 발생 할 수록 코드는 더 개판이 된다고 합니다.

아직은 대형 프로젝트를 진행 해본적이 없지만 일전에 좋은 소프트웨어 디자인은 이렇게 들었던 적 있습니다.

“좋은 소프트웨어 디자인은 높은 응집력(Cohesion)과 낮은 결합력(Coupling)을 가지고 있다”

코드에서 OOP를 찾아봐

우선 상속이 고강도의 coupling을 갖는다를 확인하기 전에 위의 말을 제가 짠 코드를 보면서 생각해보겠습니다.

protocol APIService {
    func directions(input: DirectionInput) -> Observable<Result<Directions, NetworkError>>
    func searchPlaces(input: PlaceInput) -> Observable<Result<[PlaceInfo], NetworkError>>
}

class APIServiceImpl: APIService {
   private let session: URLSession
  
   func directions(input: DirectionInput) -> Observable<Result<Directions, NetworkError>> {
     ...
   }
   func searchPlaces(input: PlaceInput) -> Observable<Result<[PlaceInfo], NetworkError>> {
     ...
   }
  
  // URL Component를 생성해주는 메서드
  private func createDrivingComponents(input: DirectionInput) -> URLComponents {
  }
  private func createSearchComponents(input: PlaceInput) -> URLComponents {
  }
}
  

자 API를 요청하는 코드입니다. 프로토콜도 선언했고 이를 채택해서 Naver API중 네비게이션 API(directions)와 위치검색 API(searchPlace) 를 요청해주는 함수를 구현해주고 있습니다.

말그대로 API를 service해주는 클래스입니다. 자 과연 이 클래스는 높은 응집력(Cohesion)을 가지고 있을까요?

image

혼좀 나야 합니다. 우선 고강도의 응집력을 가질려면 “클래스의 의도와 관련된 메서드에만 집중하도록 해야한다.” 입니다. 위의 APIService는 말 그대로 API를 Service하기 위한 메서드만 가지고 있으면 되었죠. URLComponent를 생성하고 API요청시에 필요한 파라미터를 가공하고 URL scheme를 가공하는 클래스를 따로 둬서 APIService는 해당 객체를 갖고 있게 변경을 시키는 것이 조금 더 응집력을 높일 수 있는 방법이 되지 않나 생각이 듭니다.

자 상속에서의 그럼 결합력(Coupling)에 관해서는 어떨까요? 상속은 부모클래스의 기능과 속성을 모두 가져오기 때문에 super-sub 클래스 사이에는 고강도의 결합력이 있습니다.

아래의 코드는 Naver 지도를 보여줄 View Controller 입니다.

// super class
class MyMapViewController: UIViewController {
    @IBOutlet weak var naverMapView: NMFNaverMapView?
    weak var mapView: NMFMapView?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        setUpNaverMapView()
        mapView = naverMapView?.mapView
    }
    
    deinit {
        mapView = nil
    }
    // 지도 옵션 설정
    private func setUpNaverMapView() {
        naverMapView?.positionMode = .disabled
        naverMapView?.mapView.zoomLevel = 15
        naverMapView?.showLocationButton = false
        naverMapView?.showIndoorLevelPicker = true
    }
    // 레이아웃 설정
    private func setUpLayout() {
        guard let mapView = naverMapView else { return }
        mapView.frame = self.view.bounds
        // self.view.addSubview(mapView)
    }
}

// sub class
class CustomMapViewController: MapViewController {
  
}

저는 기본적으로 지도를 보여줄 View가 몇군데 보이기 때문에 super 클래스로 MyMapViewController를 만들어 놓고 제가 사용할 CustomMapViewController에서 상속해서 사용하기로 했습니다. 여기서 이미 강한 Coupling 이 발생한 것이죠.

만약 여기서 새로운 기능이 추가가 된다면 어떨까요?

class FirstMapViewController: MyMapViewController {
  // 길찾기를 위한 MapView 그러므로 길찾기 기능에 맞는 지도를 원한다.
}

class SecondMapViewController: MyMapViewController {
  // 즐겨찾기 표시를 위한 MapView 그러므로 즐겨찾기 기능에 맞는 지도를 원한다.
}

class ThirdMapViewController: MyMapViewController {
  // 나는 둘다
}

위와 같이 상속받는 자식이 늘어나고 저마다 구현해야할 기능이 다르게 되면 부모의 입장에서는 자식의 공통적인 부분만 제공을 해야하는가 등에 대한 이슈로 골머리를 썩게 됩니다. 아니면 부모를 늘려야하는 상황이 발생할 수 있죠

이렇게 할 경우 문제가 되는 경우는 쉽게 말해 바꿀려면 큰 비용이 든다 가 될 것 같습니다. 예시와 같이 기능이 추가가 되면 불필요한 상속을 또 해야할 것이고 이는 계속해서 부모 자식, 부모 자식, 부모 자식,, 관계가 늘어나서 이 모든 것을 결국 신경을 써가면서 코딩을 해야하는 상황이 벌어집니다.

상속 대신 구성(Composition)

그런 대안으로 베어 코드 - OOP에 대해서 고민해보기 영상에서는 상속대신에 구성(Composition)을 통해 결합력을 낮추라고 언급하셨습니다.

상위클래스에 너무 의존적인 하위클래스, 이 문제점을 해결 하기 위해서는 is-a 의 상속 관계가 아닌 그냥 has-a의 구성 관계를 적용하라는 말입니다. (어렴풋이 .. 학부생때 “상속은 단일 패키지 안에서만 해야 안전함” 이라는 말이 떠오르네요..)

계승은 캡슐화 원칙을 깨 IS-A 관계가 확실할 때만 사용해야 한다. IS-A 관계가 성립하더라도 하위 클래스가 상위 클래스와 다른 패키지에 있거나 상위 클래스가 상속을 고려해 만든 클래스가 아니라면 구성을 사용하는 게 좋다. 또한 포장 클래스 구현에 인터페이스까지 있다면 더 좋다.

Abstraction! 우리는 Protocol을 사용하자

상속을 통한 문제점은 앞에서 확인을 했습니다. super 클래스에서 그 기능과 속성 모두를 계승하지말고 추상적인 부모로 남겨 두면 어떨까 합니다.

위의 예제의 경우 추상화를 통해 MapViewViewController 의 경우 단순하게 추상적인 채로 나두고 그걸 구현하는 측에 기회를 남겨두는 거죠. 이부분은 나중에 POP(Protocol Oriented Programming)을 통해 조금더 자세하게 정리를 하도록 하겠습니다.

Design Pattern 이걸 다외워야 할까?

사실 이론적인 부분인 경우는 어느정도 확립이 서야하기 때문에 공부를 하는건 나쁘다고 생각이 들지 않다고 생각합니다. 이제 문제는 여기에 너무 굳어져버려서는 안된다 인 것 같습니다.

한창 인턴 프로젝트를 진행할때 프로젝트의 아키텍쳐 패턴이니 그 내부에 디자인 패턴등에 대해서 상당히 신경을 썼었던 것 같습니다. “어떻게 하면 MVVM을 조금더 깔끔하게 적용하지?” “이부분에서는 델리게이트? 옵저버?” 등 혼자 고민이 많았었습니다.

다른 선배 개발자 분들한테 많은 자문을 얻었고 결론은 하나였습니다.

“내 프로젝트에 다양한 패턴들을 맞춰야지 거기에 내 프로젝트가 맞춰지면 안된다.”

네 그랬던것 같습니다. 그 패턴에 너무 제 프로젝트를 맞추다 보니 기존에 기획했던 부분과 차질도 있었고, 또 원하는 방향으로 개발이 안되었던 것 같습니다.

패턴은 패턴일 뿐 그 개념을 익혀두는 것은 중요하지만 문제는 얼마나 내 프로젝트에 맞게 그것을 유도해 내고, 또 그것을 응용하느냐 가 중요한게 아닌가 생각이 듭니다.

결국 Object-Oriented Design을 잘하기 위해서는 다양한 경험속에 좋은 코드, 그리고 탄탄한 이론을 바탕으로 한 다양한 트레이닝 등,, 앞으로 힘든 여정이 될 것 같습니다.

개발자는 정글에 던져진 탐험가

image

뜬금없이 이 글의 마지막에 본인 생각을 좀 쓰겠습니닼ㅋ..

개발공부라는 건 결국 코드로 짜보기 전까지는 어떤게 정답인지 모르는게 사실입니다. 설령 그게 잘못되어도 피드백을 받지 않는다면 옳은 코드인가를 분간하기란 어렵죠. 정말 다양한 팀에 소속되어서 많은 코드를 보고 또 그것을 내것으로 만들면서 내가 몰랐던 부분 그리고 놓쳤던 부분 하나하나를 기록하고 공유를 해야겠다고 생각합니다.

내가 던져진 저 정글을 꽃이 핀 화원으로 만들 수 있는 그런 정원사가 되도록 노력하고자 합니다!

image

Reference





© 2020. by Gaki

Powered by gaki