Search
📙

레거시 코드 활용 전략

레거시 코드 활용 전략 책을 읽고 정리한 글 입니다. 예제 코드는 아래 레파지토리에서 볼 수 있습니다. 책을 보고 이해한대로 예제 코드를 작성한 것이라 맞지 않는 부분이 있을 수 있습니다. 자유롭게 피드백 부탁드립니다.
working-effectively-with-legacy-code
jeff-seyong
목차

CHAPTER 1 소프트웨어 변경

소프트웨어 코드를 변경하는 네 가지 이유

1.
새로운 기능의 추가
2.
버그 수정
3.
설계 개선
4.
자원 이용의 최적화
소프트웨어 변경이 일어날 때, 어떻게 기존의 동작에 영향을 미치지 않고 유지할 수 있으지 고민해야 한다.
하지만 문제는 코드를 변경할 때 어떤 동작에 영향을 미칠지 파악하기가 매우 어렵다는 점이다.
안전한 변경을 수행하기 위해서는 영향이 미치는 범위를 정확히 이해하는 것이 가장 중요하다.

위험한 변경

기존 동작을 그대로 유지하는것은 쉽지 않은 일이고 상당한 위험이 수반된다.
위험을 최소화하기 위해서는 다음의 세 가지 질문을 해야한다.
1.
어떤 변경을 해야 하는가?
2.
변경이 정확하게 이뤄졌는지 어떻게 확인할 수 있는가?
3.
무언가를 손상시키지 않았는지 어떻게 확인할 수 있는가?

CHAPTER 2 피드백 활용

시스템을 변경하는 방법은 크게 두 가지로 나눌 수 있다
편집 후 기도하기(Edit and Pray)
보호 후 수정하기(Cover and Modify)
편집 후 기도하기 방식이 업계 표준에 가깝다. 방식은 이러하다
1.
코드 변경 계획을 ‘신중하게’ 세운다
2.
변경 대상 코드를 확인하여 이해한다
3.
코드 변경을 한다
4.
시스템을 실행해서 변경 사항이 제대로 동작하는지, 사이드 이펙트는 없는지 ‘자세히’ 조사한다
‘신중함’을 내세워 매우 전문적인 방식처럼 보인다.
하지만 아무리 신중하고 주의를 기울여도 그에 비례해서 안전성이 높아진다는 보장은 없다.
보호 후 수정하기는 좀 다른 방식으로 코드를 변경한다.
코드의 곳곳에 적절한 테스트 루틴을 배치하여 변경에 따른 문제가 발생해도 이로 인해 발생하는 영향을 쵯화하자는 주의이다.
이렇게되면 작업 수행 후 결과가 올바른지 빠르게 확인할 수 있고, 문제가 발생했을 때 잘못된 부분을 빠르게 확인할 수 있다.
물론 ‘보호 후 수정하기’ 방식도 신중한 작업을 필요로 하지만, 테스트 루틴이라는 안전망이 있기 때문에 피드백을 받으며 더욱 신중하게 작업을 할 수 있다.

단위 테스트

대규모 테스트는 아래 문제점이 있다.
1.
오류 위치 파악
a.
테스트가 커지게되면 테스트가 실패한 원인을 알기 힘들어진다
2.
실행 시간
a.
지나치게 오래 걸리는 테스트는 결국 실행하지 않게 된다.
3.
커버리지
a.
새로운 코드를 추가하는 경우, 그 코드를 실행하기 위한 상위 단계의 테스트 루틴을 작성하기 위해 상당양의 코딩이 요구되기도 한다.
좋은 단위 테스트는
1.
실행속도가 빠르다.
a.
실행시간이 0.1초가 넘는 테스트는 느린 단위 테스트이다.
2.
오류 위치 파악에 도움이 된다.
다음과 같은 테스트는 의미가 있는 테스트이지만 단위 테스트라 할 수 없다.
1.
데이터베이스와 연동한다.
2.
네트워크와 통신한다.
3.
파일 시스템을 건드린다.
4.
테스트 실행을 위해 (설정 파일 편집과 같이) 특별한 작업을 해야한다.

상위 수준의 테스트

어플리케이션 내의 시나리오나 상호작용을 테스트하는 상위 수준의 테스트 역시 필요할 때가 있다.
상위 수준의 테스트를 통해 다수 클래스의 동작을 한번에 확인할 수 있다.

테스트를 통한 코드 보호

변경을 가할 코드 주위에 테스트 루틴으로 코드를 보호한다면, 코드를 변경할 때 발생한 오류를 좀 더 쉽게 잡아낼 수 있다.
InvoiceUpdateResponder 클래스의 getResponseText() 와 Invoice 클래스의 getValue()를 변경을 해야된다고 하자.
테스트 루틴을 작성하고 실행하려면, 각 클래스의 인스턴스를 생성할 수 있어야한다.
하지만, 약간의 제약이 있다.
Invoice는 생성자에서 아무 인수도 받지 않기 때문에 수월할 수 있어도, InvoiceUpdateResponser 클래스는 DBConnection 인스턴스와 InvoiceUpdateServlet 인스턴스를 인수로 받아야한다.
DBConnection은 실제 DB에 붙는다고 하더라도 (테스트가 느리더라도) 비교적 쉽게 인스턴스를 주입할 수 있다.
문제는 InvoiceUpdateServlet 이다. 이 서블릿 객체를 쉽게 생성할 수 있을까? 쉽지 않을 것이다.
서블릿 객체를 생성하는 대신에 아예 인수로서 받지 않도록 코드를 수정하는 방법도 생각할 수 있다.
InvoiceUpdateResponder 객체가 InvoiceUpdateServlet 객체로부터 받아야할 정보가 많지 않다면, 서블릿 전체를 인수로 넘기지 말고 해당 정보만 전달하는 것이다.
근데, 코드 변경이 올바른지 확인하는 테스트를 하고 있는데 이렇게 나머지 코드를 함부로 변경해도 되는것 일까?
레거시 코드의 딜레마 코드 변경을 하려면 테스트 코드를 배치해야 한다. 그런데 테스트 코드를 배치하려면 코드 변경이 필요할 때가 많다.
위 예제의 경우 InvoiceUpdateResponder가 정말로 필요로 하는 값을 전달함으로써 InvoiceUpdateServlet에 대한 의존 관계를 끊을 수 있다.
여기서 필요로 하는 값은 InvoiceUpdateServlet가 갖고있는 InvoiceID다.
또한, DBConnection 클래스에대한 의존 관계는 IDBConnection 인터페이스를 새로 작성하여 이를 사용하도록 변경함으로써 끊을 수 있다.
이와 같은 리펙토링을 각각 기본 타입 매개변수(primitive parameter)와 인터페이스 추출(extract interface)라 부른다. (자세한 설명은 25장 참조)
오류를 일으킬 가능성이 있을 때는 보수적으로 리팩토링을 진행하는 것이 올바른 접근법이다.
하지만 코드를 보호하기 위해 테스트 코드를 배치하기 위해선 의존 관계를 제거하여한다.
이때 불필요한(=테스트를 위한) 소스 추가로 인해 코드 품질이 악화된 것처럼 보일 수 있지만, 오류가 커다란 문제로 이어지기 쉽다면 이렇게 보수적인 관점을 유지하는 편이 나을 것이다.
레거시 코드에서 의존 관계를 제거할 때는 미적인 감각을 내려놓아야 한다. 깨끗하게 제거되는 의존 관계도 있지만, 디자인 관점에서 바람직해 보이지 않는 결과물이 나올 수 있다. 이는 외과 수술에서 절개 지점과 비슷하다. 의존 관계 제거 작업 후에 코드에 흉터가 남을 수도 있지만, 그 흉터 다음의 코드들은 안전하게 동작할 것이다. 의존 관계를 제거한 지점 주위에 나중에 코드를 보호하는 테스트를 작성함으로써 이 흉터도 제거할 수 있게 된다.

레거시 코드를 변경하는 순서

1. 변경 지점을 식별한다.

코드 변경을 수행할 지점은 소프트웨어 아키텍처와 밀접히 연관돼 있다.
어느 부분을 변경하는 것이 좋을지 확신이 들 만큼 설계를 잘 모른다면 16장, 17장을 참조하자.

2. 테스트 루틴을 작성할 위치를 찾는다.

특정 변경에 대한 테스트 루틴을 어디에 작성하면 좋을지 판단하는 데 도움이 되는 기법을 알고 싶다면 11장과 12장을 참조하자

3. 의존 관계를 제거한다.

테스트를 실행할 때 의존 관계는 분명히 가장 큰 장애물이다.
테스트를 통해 시스템을 보호할 때 좀 더 안전하게 수행하는 실용적인 기법에 대해서는 23장을 참조하자.
이 단계가 끝나면 그 다음에는 9장과 10장에서 일반적인 의존 관계 문제를 해결하는 시나리오들을 볼 수 있다.
의존 관계 제거 기법에 대한 더 많은 지식을 원한다면 시간들여서 25장의 기법 목록을 읽어볼 것을 권장한다.
테스트의 개념은 잡혔지만, 대규모 메소드 내에 존재하는 의존 관계들 때문에 테스트 루틴을 작성하기 어렵다면 22장을 참조하자.
의존 관계 제거가 가능하다는 것은 알아냈지만, 테스트 루틴을 작성하는 데 시간이 너무 오래걸린다면 7장을 참조하자

4. 테스트 루틴을 작성한다.

레거시 코드에서 작성하는 테스트 루틴은 신규 코드에 대한 테스트 루틴과는 다른 점이 있다.
레거시 코드에서 테스트가 맡은 역할에 대해서는 13장을 참조하자

5. 변경 및 리팩토링을 수행한다.

레거시 코드에 기능을 추가할 때 TDD(Test-Driven-Development, 테스트 주도 개발) 방법론을 권장한다.
TDD를 비롯한 몇 가지 기법들이 8장에 소개돼 있다.

CHAPTER 3 감지와 분리