클린 소프트웨어 - 리팩토링

리팩토링

리팩토링은 프로그램이 그냥 동작하기보다는 올바르게 동작하게 하며, 개발자가 코드의 구조에 더 많은 가치를 부여하는 것이다.

마틴 파울러는 리팩토링을 ‘외부 행위를 바꾸지 않으면서 내부 구조를 개선하는 방법으로, 소프트웨어 시스템을 변경하는 프로세스’라고 정의하였다.

모든 소프트웨어 모듈에는 세 가지 기능이 있다.

  • 실행 중에 동작하는 기능

이는 모듈의 존재 이유가 된다.

  • 변경 기능

모듈은 언제든지 변경 가능해야 한다. 변경하기 힘든 모듈은 잘못된 것이며 고쳐져야 한다.

  • 모듈을 읽는 사람과 의사소통하는 기능

모듈은 특별한 훈련 없이도 개발자가 쉽게 읽고 이해할 수 있어야 한다. 그렇지 않다면 고쳐져야 한다.

읽기 쉽고 변경하기 쉬운 모듈을 만들기 위해서는 원칙과 패턴이 아닌, 주의력과 훈련, 미를 창조하기 위한 열정이 필요하다.

소수 생성기: 리팩토링의 간단한 예

책은 에라토스테네스의 체 알고리즘을 사용하여, 주어진 숫자까지 존재하는 소수를 구하는 프로그램을 제시하고 있다. 하나의 메소드에 모든 구현 코드가 위치해 있고, 변수의 의미를 파악하기 힘들고, 주석을 사용하여 로직을 설명하고 있다.

이 코드는 또한 책이 제시한 테스트 코드를 통과하므로 잘 동작하는 프로그램이다. 하지만 이 프로그램을 작성한 개발자가 아닌 다른 사람이 코드를 읽기에는 쉽지 않다.

책은 차례대로 다음의 기법을 사용하여 코드를 리팩토링하고 있다.

함수를 여러 개의 독립된 함수로 나누기

기존에 프로그램은 하나의 함수에 모든 코드가 위치해 있었으므로, 여러 개의 함수로 나누어 로직을 위치시킨다. 에라토스테네스의 체 알고리즘을 사용하므로 1. 모든 변수를 초기화하고 체를 기본 상태로 설정하며, 2. 체로 걸러내는 동작을 실제로 실행하고, 3. 체로 걸러낸 결과를 정수 배열에 넣는 각각의 함수를 만들어 로직을 분리하였다.

var primes: [Int] = []

func generatePrimes(upTo maxValue: Int) -> [Int] {
if maxValue < 2 {
return [0]
} else {
initializeSieve(maxValue)
sieve()
loadPrimes()
return primes
}
}

함수 및 변수의 이름 변경하기

함수 및 변수가 사용되는 위치에서 그 의미를 드러낼 수 있도록 이름을 다시 짓는다.

ex 1)

if isCrossed[i] == false {
// ...
}

위의 코드도 다음과 같이 다시 작성될 수 있다.

if notCrossed(i) {
// ...
}

func notCrossed(i: Int) {
return isCrossed[i] == false
}

ex 2)

unCrossed[i] = false

위의 코드는 이중 부정으로 인한 혼란을 불러일으킬 수 있다. 변수의 이름은 부정보다는 긍정의 의미를 갖는 것이 좋다.

결론

이러한 과정을 통해 최종 코드는 처음에 작성된 코드와 비교하여 훨씬 읽기 쉽고 프로그램 자체도 좀 더 정확하게 동작하게 된다. 이해하기도, 변경하기도 훨씬 쉬워진다. 서로 분리된 프로그램 구조가 만들어진 것이 프로그램을 변경하기 쉽게 만들어준다.

한 번만 호출되는 로직을 함수로 분리하고 호출하는 것에서 오는 오버헤드보다, 이를 통해 가독성을 얻는 것이 더욱 가치가 있을 수 있다.

작성한 모든 모듈과 유지보수하는 모든 모듈에 대해 항상 이러한 리팩토링 과정을 거치는 것이 중요하다. 이것에 투자하는 시간은 쓸 데 없는 것이 아니다. 향후 이 프로그램을 작성한 사람과 다른 사람들이 들여야 할 시간과 수고에 비하면 극히 적은 시간을 투자하게 될 것이다.

리팩토링의 목표는 매일 코드를 청소하는 것이다. 코드를 항상 깔끔하게 유지하여 시스템을 확장하고 변경하기 쉽게 해야 한다.

원칙과 패턴을 적용하기 전에, 간결하고 분명한 코드를 만드는 것에 신경을 써야 한다.