일상

오브젝트 세미나(응집도가 높은 코드란?)

ri5 2022. 9. 7. 00:26

객체지향 설계를 접하다보면 흔히들 하는 이야기가 결합도가 낮고 응집성이 높고 캡슐화가 잘되어 있어야 한다고 한다 하지만 이런 전통적인 관점에서 응집도, 결합도, 캡슐화와 실무적인 응집도, 결합도, 캡슐화는 매우 다르다고 한다. 

 

전통적인 관점

응집도

  • 모듈 내부 요소들이 서로 관련이 있는 정도
  • 모듈 내부의 기능적인 집중도
  • 응집도가 높거나 낮다로 표현하며 좋은 설계는 응집도가 높다

결합도

  • 한 모듈이 다른 모듈에 의존하는 정도
  • 한 모듈이 다른 모듈에 대해 알고 있는 지식의 양
  • 결합도가 강하다/느슨하다고 표현하며 좋은 설계는 결합도가 느슨하다.

캡슐화

  • 상태와 행동을 함께 모음
  • 데이터를 감추고 공용 메서드를 외부의 공개

이러한 전통적인 관점에서 응집도, 결합도, 캡슐화는 설계하는데 있어서 도움이 되긴 어렵다. 코드를 봤을 때 응집도가 높은 것인지 낮은 것인지 전통적인 관점으로 판단을 할 때 무엇이 좋은 설계이고 안좋은 설계인지 판별할 수 있는 확실한 기준이 되지 못하기 때문이다.

 

이전 글에서는 코드를 한군데 몰아넣는 절차지향 방식과 다른 하나는 코드를 다 찢어서 배치 시키는 객체 지향 방식을 정리했다. 소프트웨어에서의 설계는 이러한 코드 조각들을 사용하기 좋게 배치하는 것이 설계이다. 우리는 흔히들 말하는게 설계를 다하고 개발을 해야된다고 생각하지만 좋은 설계를 하기 위해서는 위해서는 전혀 도움이 되지 않는 방법이다. 내가 천재가 아닌 이상 코드를 짜지 않았는데 나오지 않은 코드들을 어떻게 배치하고 작성할지 설계할 것 인가? 왠만하면 할 수 없을 것이다.

 

소프트 웨어의 철학이 다른 이유

소프트 웨어는 다른 학문에 비해서 정해진 정답을 찾을 수 없다. 왜냐하면 건축/기계에 비해서 몇십년밖에 되지 않았고 소프트웨어에서의 설계는 다른 기계/건축과 달리 물리법칙에 구애받지 않고 개발할 수 있기 때문이다. 물리법칙이 따로 적용되지 않기 때문에 코드가 바뀔 때 마다 설계도 계속 바뀌고 개발자에 따라서 같은 시스템인데도 전혀 다른 구조로 될수도 있다. 소프트웨어에서의 설계는 결국 코드의 배치하는 방식이라고 생각하면 된다.

 

좋은 설계는 변경하기 좋은 코드

코드를 잘 배치하려면 무엇을 신경을 써야할까? 바로 변경하기 좋게 구현을 하는 것이 중요하다. 여기서 변경하기 좋다는 것은 유연하거나 복잡하다는 것과  다르다. 개발자는 보통 코드를 짜는데 시간을 많이 소모하지 않는다. 커뮤니케이션을 하고 내가 짠 코드를 보면서 어디를 고쳐야 되는지 고민하는 시간과 그걸 또 어떻게 고칠지 고민하는 시간이 필요하다.

인터페이스를 쓰고 유연하게 짜야하는 경우가 있고 그냥 구현 클래스를 사용하여 프라이버트한 메서드에 구현해야 될 때도 있다. 이러한 방법은 정해진 조건에서 사용하는 것이 아니라 남이 읽었을 때 이해하기 쉽고 변경하기 좋은 코드를 짜야한다는 것이다. 때로는 코드를 복잡하게 짜야할 때도 있고 단순하게 짜야할 때도 있다. 

 

변경 관점에서의 SOLID

  • SRP: 모듈에 변경할 때에 이유는 하나만 있어야한다. 기능 변경의 이유를 제외하고는 해당 모듈은 바뀌면 안된다. "한 클래스는 하나의 책임만 가져야 한다."  
  • OCP: 확장해서 추가는 되는데 변경으로 인한 기능수정은 안된다. 
    “소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”
  • LSP: 서브타입을 슈퍼 타입으로 변경할 수 있다. 
    "프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.”
  • ISP: 기능이 변경되었을 때 불필요한 쪽에 영향을 주지 않도록 인터페이스로 분리해야 한다.
    “특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다."
  • DIP: 변경하기 좋은 코드는 다 추상화에 의존해야 한다.
     “추상화에 의존해야지, 구체화에 의존하면 안된다.”

객체지향에서 중요하게 여기는 SOLID 원칙은 결국 변경 관점에 대해서 이해를 하고 있어야 상황이 닥쳤을 때 필요한 다지인 패턴인지 적절하게 사용할 수 있다. 그렇지 않고 사용하게 되면 결국 발생하지 않은 변경에 복잡한 구조가 들어가서 이해하기 어려운 코드가 나온다. 항상 변경이 핵심이다.

 

변경 관점에서 바라본 응집도 VS 전통적인 관점에서 바라본 응집도

public class ReservationService() {



    public reservation(User user, Movie movie, Payment payment) {
    	
        if(payment.getType() == Payment.CARD) {
        	payment = sequenceDiscountPolicy(payment);
        } else if(payment.getType() == Payment.POINT) {
        	payment = percentDiscountPolicy(payment);
        }
        
    }
    
    private sequenceDiscountPolicy(Payment payment) {
    	...
    }
    
    private percentDiscountPolicy(Payment payment) {
  		...
    }
}

위의 코드는 전통적인 관점에서 높은 응집도를 통해 비교하자고 하면 예매와 관련된 기능들만 있기 때문에 응집도가 높다고 할 수 있다. 하지만 변경 관점에서 보게 되면 이러한 코드들은 응집도가 낮다고 할 수 있다. 변경 관점에서 응집도가 높은 코드는 모듈 전체가 동일한 이유로 변경이 되고 다같이 바뀌면 응집도가 높은 코드인 것이다.

 

응집도가 높아야 하는 것이 중요한 이유

왜 서로 다른 코드를 안에 넣고 싶지 않을까? 쉽게 생각해보면 깃을 사용해보면 알 수 있다. 다른 개발자와 협업을 할 때 서로 다른 이유로 코드를 작업을 하고 커밋 후 푸시를 한다. 응집도가 낮은 코드는 서로 다른 이유로 코드를 작업을 할 때 동시에 수정할 확률이 굉장히 높다. 이러한 코드들은 서로 협업을 하는데 있어서 큰 스트레스를 주고 버그를 생성한다.

결론적으로는 내가 수정한 코드가 다른 사람이 수정안했으면 좋겠다는 것이다. 컨플릭트가 안나고 서로 작업을 원활하게 하고 싶기 때문에 응집도가 높은 코드를 짜야하는 것이다.

 

절차적인 설계에서 생길 수 있는 문제점

실무에서 개발하면서 가장 살 떨리는 것이 엄청 큰 메서드 안에서 한줄만 수정했을 때 제일 살이 떨린다. 다 바뀐다면 지워버리고 새로 개발하면 되지만 이 메서드가 여러쪽에서 뭔가 쓰고 있다면 진짜 골치가 아파진다. 응집도가 낮은 이유로 인해 한 줄짜리 코드를 수정하는데 몇 백줄짜리 코드를 읽고 분석하고 있다면 진짜 현자타임이 올 것이다.

이처럼 서로 다른 이유로 코드가 변경되는 경우 응집도가 낮은 코드 인 것이다. 하지만 이렇게 응집도가 낮은 코드들을 응집도가 높도록 짜는 것은 리소스가 많이 드는 작업이다. 실무에서는 리소스가 한정되어 있기 때문에 모든 코드를 응집도가 높게 짜는 것은 매우 힘이드는 일이다. 그렇기에 응집도가 중요하지 않은 잘 바뀌지 않는 코드에 시간을 쏟는 것보다 자주 바뀌는 코드를 위주로 응집도가 높게 설계하는 것이 좋다. 그렇다고 해서 변경이 일어나지도 않았는데 미리 짤 필요는 없다. 리소스 낭비다.

 

※ 메서드 분리 TIP: 메소드 안에서 거의 같은 데이터들을 같이 사용해야한다. 하지만 그안에서 받은 데이터의 30%, 40%를 부분적으로 사용하고 있다면 메소드를 찢어서 사용하는 것이 좋다.