리펙터링(Refactoring)
리팩터링(refactoring)은 소프트웨어 공학에서 '결과의 변경 없이 코드의 구조를 재조정함'을 뜻한다
주로 가독성을 높이고 유지보수를 편하게 하는 것으로 버그를 없애거나 새로운 기능을 추가하는 행위는 아니다.
- Wikipedia -
리펙터링은 기존의 코드를 외부 동작을 바꾸지 않으면서 내부 구조를 개선하는 일련의 과정으로, 코드의 가독성을 높이고, 버그를 줄이며, 프로그램을 보다 효율적으로 만들기 위해 수행하게 된다.
간단히 이야기하면, 코드 스멜이라 불리는 코드들의 문제점들을 찾아 개선하여 소프트웨어를 보다 이해하기 쉽고, 수정하기 쉽게 만드는 것이 주요 목표이며, 성능 향상이 목표가 되기도 한다.
코드 스멜 (Code Smell)
코드 스멜(Code Smell)이란 프로그래밍 소스 코드에서 발생할 수 있는 문제를 가리키는 말이며, 버그를 직접적으로 발생시키는 것은 아니지만, 버그가 발생할 가능성을 높이는 코드들을 의미한다.
일반적인 코드 스멜로는 중복 코드, 긴 메서드의 길이, 많은 양의 매개변수 등이 있다.
3가지 원칙
아래 3가지 원칙은 소프트웨어 개발의 3원칙으로 리펙터링의 3원칙으로도 통용된다.
1. KISS 원칙
“Keep it small and simple.”, “Keep it short and simple.”, “Keep it simple, stupid.”의 약자로 쉽게 말해 간단하고 알기 쉽게 작성하라는 원리이다.
2. YAGNI 원칙
“You aren't gonna need it” 의 약자로 정말 필요할때가 아니면 기능을 추가하지 말라는 원칙이다.
풀어서 이야기하면, 필요할 것이라 예상될때는 절대로 기능을 구현하지 말고, 실제로 필요하다면 그 때 기능을 구현하도록한다.라고 풀이 된다. 필자는 기능이 “may be”와 “must be”의 차이로 이헤헸다.
3. DRY 원칙
“Don’t Repeat Yourself”의 약자로 “모든 지식 조각은 시스템 내에서 하나의 모호하지 않고 권위 있는 표현을 가져야 합니다”의 의미로 The Pragmatic Programmer 책에서 해석된다고 한다. 즉, 반복되는 코드를 피하고, 코드와 로직을 재사용 가능하도록 프로그래밍을 하라는 의미로 필자는 이해했다.
6하원칙 기반 리펙터링
1. When
Don Roberts가 위 책에서 말하는 리펙터링의 때는 3의 법칙을 따른 후라고 한다.
It states that two instances of similar code do not require refactoring,
but when similar code is used three times, it should be extracted into a new procedure
- Don Roberts-
3의 법칙이란 비슷한 코드의 중복이 2번 발생 했을 때까지는 리펙터링이 필요 없다. 하지만 코드 중복이 3번 발생하면 리펙터링을 진행해보라는 원칙이다.
정답이라고는 볼 수 없으며, 각 프로젝트의 특성에 맞게, 개인의 특성에 맞게 리펙터링을 진행해도 된다!
2. Who
Kent Beck의 Two Hats 모델에서 리펙터링 모자를 쓴 사람이 리펙터링을 진행해야 한다고 한다.
이는, 리펙토링을 하는 사람은 오직 기존의 코드를 재구성하는 것에만 초점을 두고 진행해야 한다는 의미로 작업 방식의 구분을 두자는 이야기이다.
Kent Beck의 Two Hats
개발할 때의 목적을 명확히 정해두고 진행하자는 것으로 두개의 모자로 비유를 하였다.
1.
기존 코드를 변경하지 않고 새로운 기능(테스트 포함)을 추가하는 모자
2.
동작을 변경하지 않고 코드를 재구성하는 모자
소프트웨어는 behavioral and structural change로 개발되기 때문에 동일한 시간에 모두 진행할 수 없도록 명확한 목적을 기반으로 작업을 나누는 방식이라 한다.
3. Where
위 책에서 3의 법칙에 의해 리펙터링을 결정한다면, 리펙터링의 종류들을 선택하라고 한다.
•
PREPARATORY REFACTORING (준비를 위한 리펙터링)
리펙터링의 가장 좋은 시점은 새로운 기능을 추가하기 직전이라고 말한다. 왜냐하면 이 작업을 수행하면서 기존 코드를 조금만 다르게 변경한다면 작업이 더욱 쉬워진다는 것을 알 수 있기 때문이다.
즉, 새로운 기능을 추가하기 전 관련된 기존 기능들을 한번씩 점검할 필요성이 있다고 이야기하는 것이라 이해했다.
•
COMPREHENSION REFACTORING (이해를 위한 리펙터링)
코드를 변경하기 전에 그 코드가 어떠한 기능을 수행하는지 이해할 필요가 있습니다.
보통 프로젝트를 하게 된다면, 내가 작성한 코드 외에 다른 사람들의 코드를 볼 일이 많아지기 때문에, 코드를 이해하는데 많은 시간이 걸리게 된다. 그렇기에 리펙토링을 진행해 보며 코드에 대한 이해를 높일 수 있다.
나의 생각
코드를 이해하기 위해 리펙토링하는 것은 좋은 방법이라 생각한다. 하지만, 다른 사람이 짠 코드를 나의 주관으로 코드를 바꾸는 것이 과연 프로젝트 성격상 맞는지, 그리고 팀에 맞는지 한번 생각해 볼 필요가 있을 것 같다.
•
LITTER-PICKUP REFACTORING (쓰레기 줍기 리펙터링)
쓰레기 줍기 레펙터링은 코드의 동작을 이해하였지만 잘못 동작되고 있을 때, 혹은 비효율 적일 때 시간이 오래 걸리지 않는다면 리펙터링을 진행하자는 의미이다.
책에서는 금방 끝낼 수 있는 리펙터링이라면 우선적으로 진행하고 만약, 몇시간이 걸리는 작업이고 더 급한 작업이 있더라도 가끔씩은 리펙터링을 우선적으로 적용하는 것이 더 좋을 때가 있다고 말한다.
•
PLANNED AND OPPORTUNISTIC REFACTORING (수시로 하는 리펙터링)
리펙토링은 프로그래밍과 분리된 작업이 아니며, 따로 계획을 잡아두고 하는 리펙터링이 아닌, 수시로 진행하는 것이 좋다는 의미이다.
개발을 하며 쉽게 고칠 수 있는 것 혹은, 전날에 작성한 코드가 오늘은 더 좋은 방법이 생각나게 된다면 바로 리펙터링을 수행하라는 뜻으로 이해했다. 이러한 수시로 하는 리펙터링은 향후에 반드시 도움이 될 것이라고 한다.
•
LONG-TERM REFACTORING (오래 걸리는 리펙터링)
보통의 리펙터링은 몇 분 혹은 몇 시간 내에 이루어지는 경우가 많다. 하지만 라이브러리를 변경하는 등의 큰 규모의 리펙터링을 진행하게 되는데, 이 때 조급해하지 않고 천천히 작은 부분부터 변경해 나가라는 것이다.
4. What
리펙터링은 결국 Code Smell을 찾고 개선하는 일이다. 아래는 Code Smell의 대표적인 종류들이다.
Refactoring Point | Description |
Mysterious Name | 함수, 모듈, 변수, 클래스 등 코드에서 사용되는 모든 이름은 명료해야 한다. |
Dupplicated Code | 똑같은 코드 구조가 반복될때 하나로 통합하여 더 나은 프로그램을 만들 수 있다. |
Long Function | 코드를 이해하고, 공유하고, 선택하기 쉬워진다는 장점은 짧은 코드에서 나온다. |
Long Parameter List | 매개변수 목록이 길어지면 그자체로 이해하기 어려울때가 많다. |
Global Data | 전역 데이터는 누가 어디서 바궜는지 찾아 낼 수 없어 버그가 발생 할 수 있다. |
Mutable Data | 가변 데이터의 유효범위가 넓어질수록 문제를 일으킬 가능성이 높아진다. |
Divergent Change | 하나의 코드가 여러가지 이유들에 의해 변경되는 경우 문제가 발생한다. |
Shotgun Surgery | 코드를 수정할때마다 다른 여러가지 코드들을 같이 수정해야하는 경우 문제가 있다. |
Feature Envy | 다른 모듈의 함수나 데이터와 상호작용을 더 많이 하고 있다면 문제가 있다. |
Data Clumps | 자주 같이 사용되는 데이터들은 별도의 의미가 있을 수 있다. |
Primitive Obsession | 객체를 만드는것이 귀찮아 기본형만 사용하는것을 피해야한다. |
Repeated Swwitches | 중복된 스위치문을 사용하는경우 조건문 추가시 다른 케이스에서도 조건을 모두 추가해주어야 한다. |
Loops | 가능하다면 파이프라인으로 변경 |
Lazy Element | 쓸모없는 요소들 (클래스, 함수, 변수 등)은 제거 |
Speculative Generality | 나중의 작업을 위해 작업해둔, 현재는 사용되지 않는코드들은 제거 |
Temporary Field | 특정상황에서만 값이 설정되는 필드가 있다면 해당 필드의 존재이유를 파악하기 쉽지 않다. |
Message Chains | 객체요청이 꼬리를 물고 이어지는 코드는 객체네비게이션에 종속되었음을 의미 |
Insider Trading | 모듈간 데이터 조회가 많으면 결합도가 높다는 의미 |
Large Class | 클래스에 필드가 많으면 중복코드가 생기기 쉽다. |
Alternative Classes with Different Interfaces | 교체가 가능하도록 인터페이스를 잘 선언해줘야한다. |
Data Class | 데이터 클래스(필드와 getter/setter로 구성된 클래스)는 캡슐화를 통해 필드를 잘 숨겨야한다. |
Refused Bequest | 부모클래스의 메서드 혹은 데이터가 필요없을 수 있다. |
Comments | 주석대신 함수로 코드를 명확하게 표현 |
5. How
•
테스트
•
기본 방법
•
캡슐화
•
기능 이동
•
데이터 조직화
•
조건부 로직 간소화
•
API
•
상속
6. WHY
리펙터링은 Magic이 아닌 Medicine이다.
•
리펙터링을 통해 중복코드가 줄어들게 된다면, 모든 코드가 단일 책임 원칙을 지키며 고유의 기능을 수행할 수 있게 되기 때문에 소프트웨어의 설계가 좋아진다.
•
또한, 코드의 목적이 잘 드러나기 때문에 소프트웨어가 이해하기 쉬워진다.
•
소프트웨어의 설계가 좋아지고 이해가 쉬워짐으로써 버그를 쉽게 찾을 수 있고 박멸이 수월해진다.
•
위와 같은 효과를 통해 프로그래밍 속도가 빨라진다.
주의할 점
내부 동작을 이해하지 못한다면 함부로 리펙터링을 하지 말라고 말하며, 기존 코드를 리펙터링하는 것보다 새로 만드는 것이 더 쉬울 때는 리펙토링을 하지말라고 이야기한다. 즉, 리펙터링을 하귀 위해선 뛰어난 판단력과 경험, 그리고 논리가 뒷받침해주어야 할 것이다.
또한, 리펙터링의 주요 목적중 하나는 개발 속도의 향상이다. 하지만 많은 사람들이 리펙토링으로 인해 새로운 기능을 개발함에 있어 속도가 저하된다고 한다. 이는, 리펙토링이 필요하지만, 추가해야 하는 기능이 작을 경우와 같이 리펙터링으로 인해 새로운 기능의 개발이 늦춰질 때가 있다는 것으로 일종의 Trade Off이다. 위 책의 저자는 이에 대한 균형점을 정해주기도, 정량적 혹은 정성적으로 판단해주기도 어렵다고 한다. 오로지 경험에 의해 진행되어야 하고 팀별로 많은 대화를 나누어야 한다고 한다.
또한, 코드의 소유권과 브랜치의 문제가 함께 발생할 수 있다고 책의 저자는 소개한다. 이는, 위에서 본 아티클의 필자인 내가 가진 생각과 비슷한 맥락이다. 이에 대해서는 CI가 주기적으로 그리고 수시로 이루어져야 하며, 코드 수정에 대한 팀원들의 동의와 규칙이 필요할 것이다.