[Refactoring] 리팩터링 2판 Chapter.08 part 1 (기능 이동)

Date:     Updated:

카테고리:

태그:

코드 리팩터링에서 중요한 요소는 단순히 코드를 개선하는 것뿐만 아니라 기능을 다른 컨텍스트로 옮기는 작업도 포함됩니다. 이번 챕터는 이러한 기능 이동에 초점을 맞추고 있습니다. 이 과정에서 특별한 새로운 기술이 요구되기보다는, 이미 익숙하게 사용 중인 개념들을 다시 점검하는 데 의의가 있습니다. (앞서 소개된 리팩터링 기술들이 일부 중복되어 언급되기도 합니다.) 따라서 이 문서에서는 새롭게 배운 내용이나 중요하다고 생각되는 부분만 간략히 정리하였으며, 예제는 생략한 경우가 많습니다.

함수 옮기기

함수 옮기기의 시작은 다음과 같은 말로 시작합니다.

좋은 소프트웨어 설계의 핵심은 모듈화가 얼마나 잘 되어 있느냐를 뜻하는 모듈성이다.

모듈성이란 프로그램의 어딘가를 수정하려 할 때 해당 기능과 깊이 관련된 작은 일부만 이해해도 가능하게 해주는 능력이다.

제가 객체지향 개념을 어느 정도 이해하게 된 계기는 바로 모듈화의 중요성을 깨달았기 때문입니다. 객체지향을 제대로 이해하지 못했을 당시에는 동일한 기능을 수행하는 함수들이 여러 컨텍스트에 중복으로 배치되곤 했습니다. 필요할 때마다 해당 컨텍스트에 같은 알고리즘을 그대로 넣는 방식이었죠. 이러한 방식은 유지보수에 큰 어려움을 초래했습니다. 요구사항이 변경되면, 이 기능이 들어간 모든 컨텍스트를 확인하고 수정해야 했기 때문입니다. 이 과정을 겪으면서 객체지향 개념을 이해하게 되었고, 모듈화가 얼마나 중요한지 깨닫게 되었습니다.

또한, 라이브러리 코드를 분석하면서 전역 변수가 많지 않다는 점도 눈에 띄었습니다. 처음에는 그 이유를 이해하지 못했지만, 지금 생각해보면 이는 모듈화 덕분에 기능이 높은 응집도를 유지하고 있었기 때문입니다.

예를 들어, 어떤 함수가 자신이 속한 모듈 A의 요소들보다 모듈 B의 요소를 더 많이 참조한다면, 그 함수는 모듈 B로 옮기는 것이 바람직합니다. 이를 통해 캡슐화를 강화할 수 있으며, 소프트웨어의 다른 부분이 모듈 B의 내부 구현에 덜 의존하게 되어 유지보수성과 확장성이 크게 향상됩니다.

혹시나, 옮긴 위치가 적합한가에 대한 고민이 생긴다면, 시간이 해결해 줄것입니다. 그곳이 얼마나 적합한지는 차차 사용하다보면 깨달아갈것이고, 언제든지 옮길 수 있습니다.

절차

  1. 선택한 함수가 현재 컨텍스트에서 사용 중인 모든 프로그램 요소를 살펴본다. 이 요소들 중에도 함께 옮겨야 할 게 있는지 고민해본다.
  2. 선택한 함수가 다형 메서드인지 확인한다.
  3. 선택한 함수를 타깃 컨텍스트로 복사한다. 타깃 함수가 새로운 터전에 잘 자리 잡도록 다듬는다.
  4. 정적분석을 수행한다.
  5. 소스 컨텍스트에서 타깃 함수를 참조할 방법을 찾아 반영한다.
  6. 소스 함수를 타깃 함수의 위임 함수가 되도록 수정한다.
  7. 테스트한다.
  8. 소스 함수를 인라인 할지 고민해 본다.

필드 옮기기

프로그램의 상당 부분이 동작을 구현하는 코드로 이뤄지지만, 프로그램의 진짜 힘은 데이터 구조에서 나옵니다. 주어진 문제에 적합한 데이터 구조를 활용하면, 동작 코드는 자연스럽게 단순하고 직관적으로 짜여질 것입니다.

저자는 이러한 구조를 알기 때문에, 프로그램을 설계할때, 가장 적합한 데이터 구조를 만드려고 노력하지만 빈번히 실패한다고 합니다. 요구사항이 지속적으로 바뀌기 때문입니다. 그렇기에 한번에 완벽한 구조를 짜기 보다는 프로젝트를 하면서 바꿔가는게 합리적이라고 합니다.

인라인 코드를 함수 호출로 바꾸기

함수는 여러 동작을 하나로 묶어줍니다. 그리고 함수의 이름이 코드의 동작 방식보다는 목적을 말해주기 때문에 함수를 활용하면 코드를 이해하기 쉬워집니다. 또한, 함수는 중복을 없애는데 효과적입니다.

만약, 어떤 코드를 짰는데 그 코드를 이해하기 어렵거나 또는, 사용하고 있는 라이브러리에서 해당 함수의 기능을 구현해 놓았다면 그 함수로 대체하도록 합니다.

뒤에 나오는 반복문을 파이프라인으로 바꾸기도 동일한 매커니즘 입니다. 복잡한 알고리즘을 이해하기 쉬운 함수로 대체하면, 그 코드를 읽기 쉬워질 뿐 아니라 어떤 기능을 하는지 단번에 알 수 있는 장점이 있습니다. (물론, 이름을 잘 지어야 한다.)

절차

  1. 인라인 코드를 함수 호출로 대체한다.
  2. 테스트한다.

예제

여기, People를 담고 있는 List 에서 age 가 19 이상인 사람을 뽑는 코드가 있습니다.

private List<People> GetAdults(List<People> _peopleList)
{
	List<People> result = new List<People>();

	foreach(var person in _peopleList)
	{
		if(person.age >= 19)
		{
			result.Add(person);
		}
	}
	return result;
}

지금은 단순한 코드라서 이걸 보고 바로 이해가 된다는 사람도 있겠습니다만, 코드가 조금만 복잡해도 이해하기 힘든 코드가 될것입니다.

이것을 C# 에서 지원하는 LINQ 문으로 바꾸면 다음과 같습니다.

private List<People> GetAdults(List<People> _peopleList)
{
	return _peopleList.Where(person => person.age >=19).ToList();
}

이런식으로 간단하게 나타내면 이 코드가 어떤 코드인지 훨씬 이해하기가 쉽습니다.

처음 코딩을 배울 때는 라이브러리의 코드는 수정하기 어렵기 때문에 가능한 한 사용을 자제해야 한다고 생각했습니다. 하지만 프로젝트를 진행하면서 몇 가지 중요한 점을 깨달았습니다. 첫째, 내가 작성한 코드에도 실수가 들어갈 수 있다는 사실을 인지하게 되었고, 둘째, 직접 작성한 코드가 항상 의도를 명확히 드러내지는 않는다는 문제를 마주하게 되었습니다. 이러한 경험을 통해 라이브러리의 사용을 피하는 것이 반드시 좋은 접근법은 아니라는 것을 깨달았습니다.

문장 슬라이드 하기

관련된 코드들이 가까이에 있으면 이해하기가 더 쉽습니다. 예컨대 하나의 데이터 구조를 이용하는 문장들은 한데 모여 있어야 좋습니다.

저는 이 책을 읽기 전까지, 변수들을 한데 모아놓고 작업하였는데 이게 문장 구조적으로 깔끔해 보였기 때문입니다. 이제는 관련된 코드들끼리 모아놓고, 함수를 추출할 각을 계속 보고 있습니다.

혹여나, 부수효과가 일어나는 코드나 함수일 경우, 문장 슬라이드하기가 제한될 수 있습니다. 부수효과란 어떠한 함수 내부에서의 로직이 다른 함수나 변수에 영향을 미치는 경우를 말합니다. 이러한 부수효과를 최대한 줄이기 위해서, 명령-질의 분리 원칙을 지켜야 합니다.

명령-질의 분리 원칙이란 메서드는 명령만을 실행하거나, 어떠한 질의에 대해 반환하거나 둘 중 하나의 동작만을 수행해야함을 말합니다.

절차

  1. 코드 조각을 이동할 목표 위치를 찾는다. 코드 조각의 원래 위치와 목표 위치 사이의 코드를 훑어보면서, 조각을 모으고 나면 동작이 달라지는 코드가 있는지 살핀다. 다음과 같은 간섭이 있다면, 리팩터링을 포기한다.
    • 코드 조각에서 참조하는 요소를 선언하는 문장 앞으로는 이동할 수 없다.
    • 코드 조각을 참조하는 요소의 뒤로는 이동할 수 없다.
    • 코드 조각에서 참조하는 요소를 수정하는 문장을 건너뛰어 이동할 수 없다.
    • 코드 조각이 수정하는 요소를 참조하는 요소를 건너뛰어 이동할 수 없다.
  2. 코드 조각을 원래 위치에서 잘라내어 목표 위치에 붙여 넣는다.
  3. 테스트한다.

죽은 코드 제거하기

소프트웨어를 납품할 때, 심지어 모바일 기기용 소프트웨어라도 코드의 양에는 따로 비용을 매기지 않습니다. 쓰이지 않는 코드가 몇 줄 있다고 해서 시스템이 느려지는 것도 아니고 메모리를 많이 잡아먹지도 않습니다. 그렇더라도 사용되지 않는 코드가 있다면, 그 코드를 해석할 때, 어려움이 있을 수 있습니다. 시간을 들여 호출부를 추적했는데, 결국 쓰이지 않는 함수라는것을 알게되면 그 어이없음은 말로 표현할 수 없죠.

그래서 죽은 코드는 지워야 합니다. 혹시 다시 필요하진 않을까? 라고 생각할 수 있지만, 우리는 이미 버전관리 시스템을 사용하고 있기 때문에, 롤백해서 가져오면 됩니다.

죽은 코드를 주석처리해서 처리하는 방법이 있었지만, 버전관리 시스템이 보급화 되지 않았던 시대의 잔재라고 여기고, 죽은코드는 바로 처리해야 합니다.

절차

  1. 죽은 코드를 외부에서 참조하는지 확인한다.
  2. 참조가 없으면 지운다.
  3. 테스트한다.

Refactoring 카테고리 내 다른 글 보러가기

댓글 남기기