효과 구조를 단순하게 (side effect 제한)

아래 내용은 레거시 코드 활용 전략 11장 (Michael C. Feathers, p.209-231) 을 읽고 정리한 내용입니다.

상황

동작 변경을 해야 하는데 이를 안전하게 수행하기 위해 테스트 코드를 작성하려 한다. 그런데 기존 코드의 어느 부분들을 테스트로 커버해야 할지 잘 모르겠다.

제안하는 방법

  1. 동작을 변경하기 위해 구체적으로 어느 코드 라인을 수정해야 하는지 찾아낸다.
  2. 해당 코드를 변경함으로써 어떤 다른 것들이 영향받는지 분석한다.
    • 예: 변경해야 할 메서드를 호출하는 곳, 변경해야 할 메서드가 반환하거나 다른 메서드에 매개변수로 넘기는 객체, 변경이 필요한 메서드를 사용하는 상위/하위 클래스.
  3. 영향받는 것들을 충분히 커버할 수 있도록 테스트를 작성한다.
    • 아래 소개된 effect sketch 의 endpoint 들을 테스트하는 것도 방법이다.

효과 구조 분석 방법: 효과 스케치 (effect sketch)

  • 코드 중 한 부분의 변경이 어떤 다른 동작(메서드)이나 상태(변수)에 영향을 미치는지 분석한 관계도.
  • 변경하려는 메서드가 어느 메서드들을 호출하고 반환값을 누구와 공유하는지 등을 화살표로 표현.
  • 효과 스케치의 종단점을 endpoint 라고 한다. 직접적으로 변경하는 메서드와 그 효과구조의 endpoint 를 커버하는 테스트를 작성하면, 변경작업으로 인한 부작용 (side effect) 을 검토하는데 도움이 된다.
  • 재활용을 통해 코드의 중복도를 낮추면 endpoint 가 줄어드는 효과를 가질 수 있다.
  • 효과 스케치의 예:
    Capture

교훈

  1. 메서드 구현시 원하는 동작을 최대한 단순한 효과구조로  구현하는 것이 바람직하다 (i.e., restrict/localize effects). 메서드 바깥으로의 효과 전파가 적을수록 프로그램 흐름의 이해 및 디버깅이 용이하기 때문이다.
    • 로컬변수 사용, Immutable object 사용, Singleton state 지양 등이 그러한 노력의 일환이라 할 수 있다.
  2. 사용하는 프로그래밍 언어를 잘 이해하고 활용할 수 있어야 한다.
    • 예를들어 JAVA 클래스의 경우 인스턴스 변수나 메서드의 access modifier 에 따라 해당 인스턴스 필드에 접근 가능한 것들의 범위가 달라진다. 이를 이해해야 인스턴스 필드를 변경했을 때 이를 사용하는 어떤 것들이 영향받을 수 있는지 추론 가능하다.
  3. 캡슐화(encapsulation)는 코드의 이해를 위한 하나의 수단이다. 캡슐화 자체가 목적이 아니다.
    • 효과 전파를 제한하는데 캡슐화는 효과적이긴 하다.
    • 그러나, 완벽하게 캡슐화되어 있지만 테스트하기 어려운 설계라면 바람직하다고만 할 수 있을까.

책에서 발췌 (번역 및 일부 내용 수정)

코드 내에서의 효과 전파는 다음의 기본적인 세 가지 행위로 인해 일어난다.

  1. 호출자가 사용하는 반환값이 기존과 다르게 계산되게끔 로직을 수정함으로써.
  2. 다른 메서드의 매개변수로 전달되어 나중에 다른곳에서도 사용될 객체를 수정함으로써.
  3. static 또는 global 상태를 수정함으로써.

[중간 내용 생략…]

다음은 효과 구조 분석시 사용하는 heuristic 이다.

  1. 구현을 변경할 메서드를 식별한다 (“타겟 메서드”라고 하자)
  2. 타겟 메서드가 반환값을 가진다면, 타겟 메서드의 호출자를 점검한다.
  3. 타겟 메서드가 내부 로직으로 인해 어떤 값들을 변경시키는지 살펴본다. 그 값을 사용하는 다른 메서드들과 그들의 호출자를 점검한다.
    • 인스턴스 변수에 영향을 미친다면 이를 공유하는 것들을 점검한다.
    • 로컬 변수라도 Object 타입이면 조심하자. 타겟 메서드 내에서 호출한 다른 메서드의 매개변수로 객체를 넘겼는데, 이 객체를 타겟메서드에서 또 수정하면 다른 메서드에 영향을 미치게 된다.
  4. 타겟 메서드 또는 타겟 메서드가 영향을 미치는 인스턴스 필드를 사용하는 상위/하위 클래스도 점검한다.
  5. [못이해함…]
  6. 위 과정 중 식별해낸 모든 메서드들 안에서 global 또는 static 상태값을 수정시키는 부분이 있는지 점검한다.