개발자의 노하우

시니어 개발자로 성장하기 위한 핵심 역량 - '육각형 개발자' 책 리뷰 - 2

륨륨이 2023. 9. 6. 15:06
반응형

설계 역량 : 변화에 대응하기 좋은 구조를 짤 수 있는가

5. 응집도와 결합도

높은 응집도와 낮은 결합도를 추구해야 한다는 것의 구체적인 의미.

응집도 : 한 기능과 관련된 코드는 한곳에 모여 있어야 한다(구분되는 기능은 서로 다른 곳에 분리되어야 한다.)

응집도가 높아야 하는 이유? 수정 비용을 줄이기 위해. 응집도가 높을수록 구성 요소가 각 역할에 따라 분리될 가능성이 크고, 그럴수록 수정해야할때 변경 범위가 좁아진다.

응집도를 높이는 방법 : 캡슐화. 데이터에 대한 직접 접근을 최소화해서 구현을 감추고 외부에 기능을 제공한다.

 

결합도 : 소프트웨어 모듈이 서로 의존하는 정도.

구성 요소 간 상호 작용을 최소화해야 한다. 구체적인 구현에 대한 의존을 줄여야 한다.

결합도를 낮추는 방법 : 추상화 타입(DIP 와 관련 - 추상화 타입에 의존한다.)

결합도를 낮추는 방법 : 이벤트. 이벤트를 통지하고, 이에 대응하는 사건은 이벤트 리스너에 구현한다.

다만 위 두 방법은 코드가 간접적으로 연결되어 분석에는 복잡해지기 때문에 도입시 이점을 잘 따져 봐야 한다.

  • 공통 결합 : 여러 모듈이 동일한 글로벌 데이터에 접근하는 경우
  • 제어 결합 : 한 모듈이 다른 모듈의 논리적 흐름을 제어하는 경우. 파라미터로 전달된 모듈로 흐름을 제어하는데, 내부 동작 방식이 외부에 노출되어서 결합도를 높인다.
  • 하위 클래스 결합 : 하위는 상위에 의존하고, 상위는 하위에 의존하지 않는다. 하지만 상위 클래스 기능을 사용하는 하위 클래스가 많을수록 하위에 많은 영향을 주기 때문에 상위 클래스를 수정하기 어려워진다.
    • cf. 상속보다는 조립(Composition over inheritance) 원칙 - 상속은 상위 클래스와 하위 클래스간 강한 결합을 발생시키지만 컴포지션을 사용하면 상속에 비해 결합도를 낮출 수 있다.
  • 시간적 결합 : 함께 실행해야 하므로 한 모듈에 묶여있는 경우. 혹은 실행 순서가 정해져 있는 경우
  • 논리 결합 : 특정 모듈, 시스템 데이터를 변경할 때 다른 모듈, 시스템 데이터도 함께 변경해야 하는 경우

6. 리팩터링

레거시 : 현재는 사용되지 않는 기술로 만들어진 시스템. 코드를 수정하기 어렵고, 개선하기 좋게 리팩터링이 필요하다.

리팩터링은 외부로 드러나는 동작이나 기능을 변경하지 않고 내부 구조를 변경해서 재구성하는 기법이다. 

  • 미사용 코드 삭제
  • 매직 넘버 : type=42 등. 의미를 숫자값으로 표현한 것. 값이 무엇을 의미하는지 유추하기 어렵다. const 나 enum 으로 대체
  • 이름 변경 : 이름과 실제 의미가 일치하도록, 구체적으로 작성 (ex. selectInput() -> filterAndSaveSuccessInput())
  • 메서드 추출 : 논리적으로 하나의 작업을 수행하는 코드를 메서드로 묶는다
  • 클래스 추출 : 특정 기능을 전담하는 역할의 클래스로 분리
  • 클래스 분리 : 한 클래스에 많은 기능이 모여 있으면 각 기능을 별도 클래스로 분리
  • 메서드 분리 : 서로 다른 기능을 한 메서드에 구현하지 말고 각 기능을 구별해서 구현하는 메서드를 만들어서 기능별로 응집도를 높여야 한다.
  • 파라미터값 정리 : 사용하지 않는 파라미터는 제거해야 한다. 실제로 사용되는지 확인하기가 까다롭다.
  • for 에서 하는 2가지 일 분리 : 서로 다른 목적을 가진 코드가 뒤섞이지 않게 한가지 일만 하도록 수정한다.

항상 리팩터링만이 답이 아니다. 일부 기능을 마이크로서비스로 분리해서 새로 만드는 방법도 있다.

7. 테스트

시스템이 복잡할수록 테스트의 중요성이 증가. 자동화된 테스트가 있다면 수정한 코드가 발생시키는 문제를 빨리 찾을 수 있다. 테스트 커버리지는 70~80% 수준이면 적당

테스트 주도 개발(TDD - Test Driven Development) : 테스트 코드를 먼저 만들고, 테스트 코드를 통과시킬 만큼 구현을 진행한다. 예외적인 상황에서의 테스트를 먼저 작성한다.

TDD는 기능을 설계하는 데에도 도움을 준다. 테스트할 대상의 이름이나 타입, 의존 대상을 테스트 코드를 작성하는 단계에서 설계한다.

 

수동 테스트에 비해 테스트 코드가 갖는 장점

모든 코드를 다 개발하지 않아도 일부만으로 테스트가 가능하다.

수동 테스트는 실제 DB, 외부 API 등에 의존성이 걸려서 원하는 테스트 환경을 만들기 까다롭다.

테스트 코드는 기능을 구현하자마자 테스트를 실행하기 때문에 시점의 차이로 인한 오버헤드가 없다.

테스트 가능성을 높이는 방향으로 구현하는 것이, 개발 생산성과 설계 품질을 높일 수 있다. 외부 환경에 대한 의존이 줄어들고, 테스트 코드를 짜기 위해 역할 별 클래스, 메소드 분리가 잘 되게 설계가 된다.

반응형