레거시 코드 활용 전략 | 마이클 C. 페더스 - 교보문고
레거시 코드 활용 전략 | ▶ 레거시 코드 활용 전략에 관한 내용을 담은 전문서적입니다.
product.kyobobook.co.kr
챕터
- 코드 변경의 메커니즘
- 소프트웨어 변경
- 의존 관계 제거 기법
메모
서문
더보기
레거시 코드란 무엇일까?
…
엄밀히 말하면, 레거시 코드는 다른 누군가로부터 이어받은 코드라고 정의할 수 있다.
회사 차원에서 다른 회사로부터 구입한 코드일 수도 있고, 부서 이동으로 인해 다른 팀원에게 인계된 코드일 수도 있다.
이처럼 레거시 코드는 다른 누군가의 코드를 의미하지만, 프로그래머들에게 이 단어는 그 이상의 의미를 갖고 있다.
…
레거시 코드라는 말을 들으면 여러분은 어떤 생각이 드는가?
나와 비슷한 생각이 든다면, 여기저기 얽힌 난해한 구조 때문에 제대로 이해하지 못하면서도 수정해야만 하는 코드가 떠오를 것이다.
겉보기에는 쉬워 보였던 기능을 추가하느라 며칠 밤을 지새운 추억이 떠오를 수도 있고, 더 이상 어떻게 할 수 없는 코드에 질려서 무력감에 빠진 모습이 떠오를 수도 있다.
이런 상황에서 당신의 노력은 무가치한 일처럼 느껴진다.
레거시 코드의 정의는 사실 누가 그 코드를 작성했는가와 무관하다.
…
소프트웨어 업계에서 레거시 코드란 이해할 수 없고 수정하기도 힘든 코드를 지칭하는 속어처럼 사용될 때가 많다.
…
내게 레거시 코드란, 단순히 테스트 루틴이 없는 코드다.
-p12
테스트 루틴이 없는 코드는 나쁜 코드다.
코드가 얼마나 훌륭하게 작성돼 있는지 여부와는 상관없다.
아무리 깔끔하고 객체 지향적이며 캡슐화가 잘돼 있어도 소용없다.
테스트 루틴이 있으면, 코드의 동작을 빠르게 검증하며 수정할 수 있다.
테스트 루틴이 없으면 우리가 작성하고 있는 코드가 좋아지고 있는지, 나빠지고 있는지 제대로 알 수 없다.
테스트 루틴 없이 대규모의 수정 작업을 시도하면 커다란 어려움에 직면하게 된다.
이는 마치 안전그물망 없이 공중 곡예를 하는 것과 비슷하다.
…
변수 몇 개를 수정했을 때 어떤 일이 일어날지 예측하는 것은 마치 공중에서 재주를 넘었을 때 다른 동료가 팔을 잡아줄지 말지 미리 아는 것과 같다.
-p13
코드 변경의 메커니즘
더보기
소프트웨어 코드를 변경하는 네 가지 이유
1. 새로운 기능의 추가
2. 버그 수정
3. 설계 개선
4. 자원 이용의 최적화
-p37
새로운 동작을 추가하는 것과 기존의 동작을 변경하는 것 간에는 커다란 차이가 있다.
…
프로그래머의 관점에서는 이 문제를 분명하게 구별할 수 잇는 유용한 방법이 있다.
기존 코드를 변경해야 한다면 동작 변경으로 간주하고, 새로운 코드를 추가하고 이를 호출할 뿐이라면 동작 추가로 간주하는 것이다.
-p38 ~ p39
변경을 회피하는 것은 또 다른 나쁜 결과도 초래한다.
변경을 자주 하지 않으면 개발자의 실력이 녹슬기 쉽다.
거대한 클래스를 분할하는 작업을 일주일에 두세 번 정도 꾸준히 하지 않으면, 나중에는 감당하기 힘든 수준이 된다.
반면에 꾸준히 수행하면 습관이 된다.
무엇을 나누고 무엇을 나누면 안 되는지를 점점 더 잘 판단할 수 있게 된다.
-p44
시스템을 변경하는 방법은 크게 두 가지로 나눌 수 있다.
나는 ‘편집 후 기도하기’와 ‘보호 후 수정하기’라고 각각 부르는데, 유감스럽게도 ‘편집 후 기도하기’ 방식이 업계 표준에 가깝다.
‘편집 후 수정하기’ 방식은 코드 변경 계획을 신중하게 세우고, 변경 대상 코드를 이해했는지 확인한 후 비로소 변경 작업에 들어간다.
변경 완료 후에는 시스템을 실행해서 변경 사항이 제대로 동작하고 무언가 손상된 동작이 없는지 자세히 조사한다.
의도하지 않은 영향이 발생했는지 확인하는 것은 매우 중요하다.
변경 작업이 제대로 완료되길 희망하는 기도를 하고, 작업 결과를 확인하기 위해 추가로 시간을 들이는 것이다.
’ 편집 후 기도하기’ 방식은 겉보기에는 ‘신중하고’ 매우 전문적인 방식처럼 보인다.
…
그러나 아무리 신중하게 주의를 기울여도 그에 비례해서 안전성이 높아진다는 보장은 없다.
’ 보호 후 수정하기’는 좀 다른 방식으로 코드를 변경한다.
…
작업 대상 코드 위에 망토를 덮어놓음으로써 변경에 따른 문제가 발생해도 이로 인해 나머지 코드에 미치는 영향을 최소화하는 것을 의미한다.
소프트웨어를 망토로 덮는다는 것은 테스트 루틴으로 코드를 덮는 것과 같다.
코드의 곳곳에 테스트 루틴이 배치돼 있으면 변경 작업을 수행한 후에 결과가 올바른지 쉽게 확인할 수 있을 뿐 아니라 문제가 발생할 때는 잘못된 부분을 빠르게 찾아낼 수 있다.
물론 ‘보호 후 수정하기’ 방식도 신중한 작업을 필요로 하지만, 테스트 루틴으로부터 피드백을 받을 수 있기 때문에 더욱 신중하게 변경을 수행할 수 있다.
-p45 ~ p46
회귀 테스트란 주기적으로 테스트를 실행해 정상적인 동작 여부를 확인하고 지금까지와 마찬가지로 소프트웨어가 동작하는지 조사하는 것을 의미한다.
-p46
레거시 코드를 이용해 작업할 때 단위 테스트는 가장 중요한 요소 중 하나다.
시스템 수준의 회귀 테스트는 물론 중요하다.
하지만 소규모 수준의 부분별 테스트도 매우 중요하다.
개발 과정에서 빠른 피드백을 제공함으로써 훨씬 더 안전한 리팩토링이 가능하기 때문이다.
-p48
단위 테스트의 기본 개념은 독립된 개별 소프트웨어 컴포넌트를 테스트하는 것이다.
그럼 컴포넌트란 무엇일까?
…
절차적 프로그래밍에서 단위는 보통 함수를 의미하며, 객체 지향 프로그래밍에서는 클래스를 의미한다.
…
절차적 시스템에서 특정 함수를 분리해 테스트하는 것은 매우 어렵다.
최상위 함수는 다른 함수들을 호출하고, 이 함수들은 또 다른 함수들을 호출하고, 이런 식으로 호출이 계속 이어지기 때문이다.
객체 지향 시스템에서 클래스를 분리해 테스트하는 것은 좀 더 쉽지만, 실질적으로 클래스도 독자적으로 존재하는 경우가 거의 없다.
…
함수나 클래스의 분리 테스트는 단위 테스트의 의미상 매우 중요하다.
…
물론 대규모 테스트는 중요하며, 그 사실을 부인할 수는 없다.
하지만 대규모 테스트는 몇 가지 문제가 있다.
1. 오류 위치 파악
2. 실행 시간
3. 커버리지
-p49
단위 테스트는 대규모 테스트의 단점을 보완할 수 있다.
코드 조각들을 독립적으로 테스트할 수 있으며, 테스트들을 그룹별로 묶어 서로 다른 조건하에서 테스트를 실행할 수 있다.
이는 테스트 실패가 발생한 위치를 좀 더 빠르게 파악하는 데 많은 도움이 된다.
-p50
좋은 단위 테스트
1. 조건 실행 속도가 빠르다.
2. 오류 위치 파악에 도움이 된다.
실행에 0.1초가 걸리는 단위 테스트는 속도가 느린 단위 테스트다.
실행 속도가 빠르지 않다면 단위 테스트가 아니다.
다음과 같은 테스트는 단위 테스트가 아니다.
1. 데이터베이스와 연동한다.
2. 네트워크를 통해 통신한다.
3. 파일시스템을 건드린다.
4. 테스트 실행을 위해 (설정 파일 편집과 같이) 특별한 작업을 해야 한다
코드를 변경할 때는 오류가 생기기 마련이다.
결국은 사람이 하는 일이기 때문이다.
하지만 변경을 가하기 전에 테스트 루틴으로 코드를 보호한다면, 발생한 오류를 좀 더 쉽게 잡아낼 수 있다.
-p52
레거시 코드의 딜레마 코드 변경을 하려면 테스트 코드를 배치해야 한다.
그런데 테스트 코드를 배치하려면 코드 변경이 필요할 때가 많다.
레거시 코드를 다룰 때의 가장 큰 문제 중 하나가 의존 관계다.
특정 코드만 독립적으로 실행해 어떻게 동작하는지 테스트하려면, 대체로 다른 코드에 대한 의존 관계를 제거할 필요가 있다.
하지만 이것은 그리 간단한 일이 아니다.
그 다른 코드가 우리가 수행하는 작업의 영향을 감지할 수 있는 유일한 위치일 때가 많기 때문이다.
따라서 그 다른 코드를 별도의 코드로 대체할 수만 있다면, 변경 대상을 테스트하는 루틴을 작성할 수 있을 것이다.
객체 지향 프로그래밍에서는 이 별도의 코드를 가리켜서 가짜 객체 혹은 위장 객체라고 부른다.
-p63
우리는 재사용이 가능하도록 프로그램을 작은 조각들로 쪼개서 작성하는 것이 좋다는 말을 자주 듣는다.
하지만 이렇게 작게 쪼개진 조각들은 얼마나 자주독립적으로 재사용될까?
실제로는 그리 자주 재사용되지 않는다.
재사용 자체가 어렵기 때문이다.
소프트웨어의 각 부분들은 독립적인 것처럼 보이지만, 실제로는 서로 미묘하게 얽혀 있는 경우가 대부분이다.
-p72 ~ p73
코드 변경이 리팩토링으로서 인정받으려면, 기존 동작이 달라지지 않아야 한다.
따라서 리팩토링 도구는 코드 변경이 동작 변경으로 이어지지 않음을 검증해야 하며, 실제로 많은 도구들이 이러한 검증 작업을 수행한다.
-p94
레거시 코드를 다룰 때 가장 큰 문제점 중 하나는 의존 관계다.
…
다른 코드를 제거한 상태로 특정 코드를 제대로 테스트하려면 그 다른 코드를 대신해서 올바른 값을 제공하는 또 다른 코드가 필요하다.
객체 지향에서는 이것을 일반적으로 모조 객체라고 부른다.
-p96
소프트웨어 변경
더보기
시간 압박이 주어진 상황에서 테스트 루틴 작성 여부를 판단하는 데 가장 문제가 되는 것은 기능 구현에 걸리는 시간을 알 수 없다는 점이다.
…
기능 추가에 시간이 얼마나 걸릴지 모르거나 주어진 시간이 많지 않을 때, 가급적 가장 빠른 방법을 사용해 일단 해당 기능을 구현하려는 유혹에 빠지기 쉽다.
하지만 문제는 실제로 과거 작업으로 돌아가서 테스트와 리팩토링을 하지 않는 경우가 많다는 점이다.
-p109
레거시 시스템의 경우 무엇을 해야 할지 이해하는 데도 오랜 시간이 걸리고 변경 작업 자체도 힘들다.
또는 변경 작업을 위해 이해해야 할 범위의 일부분만 이해하고 끝나는 경우가 많다.
최악의 경우에는 아무리 많은 시간을 들여도 변경 작업에 필요한 것을 이해하기에 부족하다고 느끼는 나머지, 무작정 코드 내부를 방황하며 작업을 시작하고 작업 과정에서 맞닥뜨릴 문제들에 대처할 수 있도록 하늘에 기도할 뿐이다.
-p134
의존 관계를 제거하기 위해 시스템 내의 인터페이스 및 패키지 수가 늘어나면, 전체 시스템의 재빌드에 걸리는 시간이 조금 늘어나고 컴파일 대상 파일 수도 증가한다.
하지만 필요한 것만 재컴파일할 때의 빌드 시간은 크게 단축된다.
-p141
테스트 주도 개발 (레거시 코드) 변경 대상 클래스를 테스트 루틴으로 보호한다.
1. 실패하는 테트 케이스를 작성한다.
2. 컴파일한다.
3. 테스트를 통과한다.
4. 중복을 제거한다.
5. 반복한다.
거의 모든 레거시 시스템에서는 시스템이 ‘어떻게 동작해야 하는지’보다 ‘실제로 어떻게 동작하고 있는지’가 더 중요하다.
시스템이 해야 하는 일을 바탕으로 테스트 루틴을 작성하는 것은 버그 찾기로 돌아가는 것과 다를 바 없다.
버그 찾기도 중요하지만, 현재 우리의 목표는 변경 작업을 명확히 하기 위한 테스트 루틴을 정확한 위치에 작성하는 것이다.
-p262
기존 동작 유지에 필요한 테스트를 ‘문서화 테스트’라고 부른다.
문서화 테스트는 코드 실제 동작의 특징을 나타내는 테스트다.
‘시스템이 이 작업을 수행해야 한다.’거나 ‘이 작업을 수행하는 것 같다.’라고 확인하는 테스트가 아니라, 시스템의 현재 동작을 그대로 문서화하는 테스트다.
문서화 테스트를 작성하는 순서
1. 테스트 하네스 내에서 대상 코드를 호출한다.
2. 실패할 것임을 알고 있는 확증문(assertion)을 작성한다.
3. 실패 결과로부터 실제 동작을 확인한다.
4. 코드가 구현할 동작을 기대할 수 있도록 테스트 루틴을 변경한다.
5. 위 과정을 반복한다.
-p262
개발 팀이 아키텍처를 이해하고 있지 않다면 아키텍처는 계속 퇴보한다.
아키텍처 이해를 가로막는 요인
1. 시스템이 너무 복잡해서 전체 그림을 이해하는 데 오랜 시간이 걸린다.
2. 시스템이 너무 복잡해서 전체 그림이 아예 없다.
3. 전체 그림이 없다 보니 긴급 상황이 끊임없이 발생한다.
그리고 이를 처리하느라 땜질 처방에 급급하게 된다.
-p298
아키텍트가 있는 것은 좋지만, 아키텍처를 유지하려면 팀원 전체가 아키텍처가 무엇인지 알고 관심을 기울여야 한다.
코드와 관련 있는 사람은 아키텍처를 알아야 하고, 아키텍처를 잘 아는 사람으로부터 학습을 받아야 한다.
팀원 전체가 동일한 개념을 갖고 일하면, 시스템 전반에 대한 지식이 확장될 것이다.
-p298
일반적으로, 각 클래스마다 적어도 한 개의 단위 테스트 클래스를 작성한다.
-p309
클래스가 커지면 어떤 문제가 생길까?
첫 번째 문제는 혼란이다.
50 ~ 60 개의 메소드를 갖는 클래스라면 무엇을 변경해야 할지, 그리고 다른 클래스에 영향을 미치는지 파악하기 어려울 때가 많다.
최악의 경우 너무나 많은 인스턴스 변수 때문에 한 개의 변수 변경으로 인한 영향이 어떨지 알 수 없다.
두 번째 문제는 작업 계획의 조정이다.
한 개의 클래스가 20개 정도의 책임을 갖고 있으면, 그 클래스를 변경해야 하는 이유도 매우 많아진다.
…
세 번째 문제, 즉 크기가 거대한 클래스는 테스트 코드 작성이 어렵다는 문제가 더해지면 혼란은 더욱 심각해진다.
…
너무 커져버린 클래스는 너무 많은 것을 감추기도 한다.
-p332
클래스와 메소드에 약어(준말)를 사용하면 나중에 골칫거리가 될 수 있다.
약어가 일관되게 사용되면 문제없을 수도 있지만, 그래도 나는 약어 사용을 꺼린다.
-p378
클래스 간의 중복을 제거하고 나면, 메서드는 매우 작고 집약된 형태가 된다.
각 메소드는 다른 메서드들이 하지 않는 처리를 하기 때문에 직교성이라는 매우 큰 효과가 얻어진다.
…
기존 동작들을 변경하고 싶을 때, 변경할 곳이 정확히 한 곳이라면 직교성이 있는 것이다.
-p379
대규모 메소드는메서드는 다루기 힘든 수준이라면, 괴물 메서드는 재앙이라고 부를 만하다.
괴물 메소드는 너무나 길고 복잡해서 손대고 싶지 않은 메서드를 의미한다.
-p383
의존 관계 제거 기법
더보기
- 매개변수 적응
- 메소드 객체 추출
- 정의 완성
- 정적 메소드 드러내기
- 호출 추출과 재정의
- 팩토리 메소드 추출과 재정의
- get 메소드 추출과 재정의
- 구현체 추출
- 인터페이스 추출
- 인스턴스 위임 도입
- 정적 set 메소드 도입
- 연결 대체
- 생성자 매개변수화
- 메소드 매개변수화
- 매개변수 원시화
- 특징 끌어올리기
- 의존 관계 밀어 내리기
- 함수를 함수 포인터로 대체
- 전역 참조를 get 메소드로 대체
- 서브클래스화와 메소드 재정의
- 인스턴스 변수 대체
- 템플릿 재정의
- 텍스트 재정의
'Book' 카테고리의 다른 글
실전 카프카 개발부터 운영까지 (0) | 2023.05.06 |
---|---|
HTTP 완벽 가이드 (1) | 2023.05.05 |
소프트 스킬 (0) | 2023.04.22 |
개발자 원칙 (0) | 2023.04.21 |
개발자의 글쓰기 (0) | 2023.04.21 |
댓글