////
Search
Duplicate
🥋

Chapter 8. 어떻게 기능을 추가할까?

문제 영역에 따라 상황에 따라 자신만의 답을 가질 수 있는 답이 없는 질문이다.
그러나 설계 방식이나 제약 조건 등과 무관하게 기능을 추가하는 과정에 도움이 되는 몇 가지 기법들이 존재한다.
책 제목에서 알 수 있듯 레거시 코드를 다룰 때의 상황으로 한정해보자.
레거시 코드를 다룰 때 가장 중요한 고려 사항은 대부분의 코드가 테스트 코드를 가지고 있지 않다는 점이다. 심지어는 테스트 코드를 추가하기가 어려운 상황도 존재한다.
이런 경우, 정상적으로 동작하는 레거시 코드를 내버려 두고 6장의 기법들을 사용해 새로운 기능을 추가하고 싶은 유혹에 빠질 것이다. 하지만 그런 기법들은 다음과 같은 단점을 가진다.
해당 기법들은 기존 코드의 수정이 적기 때문에 코드 개선의 효과를 보기 어렵다.
기존 코드를 놔두고 새로운 코드를 작성하기 때문에 중복이 발생할 가능성이 높다.
마지막 위험 요소는 두려움과 체념이다. 많은 비용을 들여 수정해도 전체적인 개선 효과가 없을 것이라는 체념, 두려움들은 우리의 의사 결정을 한정적으로 제한한다.
일반적으로 맹수를 만났을 때는 피하기보다 맞서는 편이 낫다.
⇒ 앞선 언급들에서 짐작컨대 기능을 추가하는 과정에 도움이 되는 기법은 테스트같다.

테스트 주도 개발

TDD는 저자가 아는 한 가장 강력한 기능 추가 기법이다.
테스트 주도 개발은 문제를 해결할 수 있는 메소드를 상상하고 이에 대해 실패하는 케이스를 작성한다.
가장 중요한 아이디어다. 테스트 루틴을 먼저 작성함으로써 앞으로 작성할 코드가 무엇을 구현할지 명쾌히 이해할 수 있게 된다.
테스트 주도 개발은 다음과 같은 알고리즘으로 구현된다.
1.
실패하는 테스트 케이스를 작성한다.
2.
컴파일한다. (테스트가 빌드가 되게 한다)
3.
테스트를 통과한다. (테스트를 통과하게 한다)
4.
중복을 제거한다.
5.
반복한다. (입력값이나 다른 동작이 기대되는 경우들을 테스트 케이스로 작성하여 이 과정을 반복한다)
레거시 코드에서 TDD 알고리즘은 다음과 같이 확장될 수 있다.
0.
변경 대상 클래스를 테스트 루틴으로 보호한다.
1.
실패 테스트 케이스를 작성한다.
2.
컴파일한다.
3.
테스트를 통과한다.
4.
중복을 제거한다.
5.
반복한다.

차이에 의한 프로그래밍

객체 지향 프로그래밍인 경우 사용해볼 수 있는 방법으로 상속을 이용함으로써 클래스를 직접 수정하지 않고 기능을 추가하는 것이다.
기능을 일단 추가한 후에 그 기능을 어떻게 통합할지 파악해도 된다. 이렇게 하기 위한 핵심적 기법을 차이에 의한 프로그래밍(Programming By Difference)이라고 부른다.
다소 오래된 기법으로 1980년대에 폭넓게 사용되었으나 상속 남용의 문제들이 수면 위로 떠오르면서 인기가 많이 줄었다.
처음에 상속을 사용했다고 해서 계속 상속을 해야하는 것은 아니며 상속이 문제가 된다면 테스트 루틴을 활용해 다른 구조로 코드를 쉽게 변경할 수 있다.
테스트 루틴으로 보호받는 MailForwarder라는 클래스가 있다고 하자. 이 클래스는 메일링 리스트를 관리하는 자바 프로그램의 일부분이다.
이 클래스의 getFromAddress 메소드는 다음과 같다.
private InternetAddress getFromAddress(Message message) throws MessagingException { Address[] from = message.getFrom(); if (from != null && from.length > 0) return new InternetAddress(from[0].toString()); return new InternetAddress(getDefaultFrom()); }
Java
복사
이 메소드는 수신 메일에서 보낸 사람의 이메일 주소를 추출해 반환한다. 이렇게 반환된 주소는 메일링 리스트의 등록자들에게 포워딩되는 메시지의 보낸 사람 주소로서 사용될 수 있다.
이 상황에서 새로운 요구 사항이 발생했다고 하자. 메일링 리스트에 익명 수신자 기능을 추가하려면 어떻게 해야 할까?
코드를 추가하는 대신 MailForwarder를 상속하면서 getFromAddress를 재정의하여 문제를 해결할 수 있다.
이러한 작업 방식은 기존 코드를 건드리지 않으면서도 매우 간단하게 문제를 해결한다. 문제는 없을까?
설계에 충분히 주의를 기울이지 않은 채로 이 기법을 반복하다 보면, 코드의 설계 품질이 급격하게 저하된다.
요구사항이 변경될 지점인, 속성을 인지하고 해당 부분을 클래스를 이용해 구현해도 좋다.
이처럼 재정의를 통해 새로운 기능을 추가하게 되는 경우, 리스코프 치환 원칙을 위배할 수 있다.
일반적으로 구체 메소드를 자주 재정의하면 코드가 혼란스러워진다.
상속 관계를 잘 쓰고 싶다면 부모 클래스를 추상 클래스로 만들고 서브 클래스에서 구체 메소드로 구현되도록 설계를 바꿀 필요가 있다.
저자는 이러한 구조를 정규화된 계층 구조라고 부른다.

요약

이번 장의 기법을 사용할 때, 어떤 코드든 테스트 루틴으로 보호받기만 하면 신규 기능을 추가할 수 있다.