여러분은 오래된 집을 리모델링하는 과정을 생각해 본 적이 있으신가요? 🏠
- 집의 기본 구조와 외관은 그대로 유지하면서 내부를 개선하고 현대화하는 작업입니다.
- 코드 리팩토링도 이와 매우 유사합니다. 프로그램의 외부 동작은 변경하지 않으면서 내부 구조를 개선하는 과정이죠.
왜 필요한가?
파이썬 코드 리팩토링이 해결하는 문제들은 다음과 같습니다:
기술 부채(Technical Debt) 관리: 🧾 개발 과정에서 시간 압박이나 자원 제약으로 인해 발생한 임시방편적 코드 솔루션은 시간이 지날수록 유지보수 비용을 증가시킵니다.
코드 스멜(Code Smell) 제거: 🦨 코드 스멜은 더 깊은 문제를 나타내는 코드의 특성으로, 중복된 코드, 지나치게 긴 함수, 복잡한 조건문 등이 여기에 해당합니다.
유지보수성 향상: 🔧 시간이 지남에 따라 새로운 기능이 추가되고 버그가 수정되면서 코드는 점점 이해하고 수정하기 어려워질 수 있습니다.
테스트 용이성 개선: 🧪 잘 구조화되지 않은 코드는 테스트하기 어려우며, 이는 불충분한 테스트 커버리지로 이어질 수 있습니다.
기본 원리
파이썬 리팩토링의 핵심 원리를 알아볼까요?
테스트 주도 개발(TDD) 접근법
1. Red: 실패하는 테스트 작성
2. Green: 테스트를 통과하도록 최소한의 코드 작성
3. Refactor: 코드를 개선하면서 테스트가 계속 통과하는지 확인
점진적 변경
1. 작은 단위로 리팩토링 진행
2. 각 변경 후 테스트 수행
3. 변경사항 커밋 및 문서화
4. 다음 단계로 진행
실제 예제
파이썬 코드 리팩토링의 일반적인 기법과 그 효과를 살펴보겠습니다.
함수 추출
리팩토링 전:
def process_data(data):
# 데이터 검증
if not isinstance(data, list):
raise TypeError("데이터는 리스트여야 합니다")
if len(data) == 0:
raise ValueError("데이터는 비어있을 수 없습니다")
# 데이터 처리
result = []
for item in data:
if isinstance(item, (int, float)):
result.append(item * 2)
else:
result.append(0)
# 결과 포맷팅
formatted_result = []
for item in result:
formatted_result.append(f"값: {item}")
return formatted_result
리팩토링 후:
def validate_data(data):
if not isinstance(data, list):
raise TypeError("데이터는 리스트여야 합니다")
if len(data) == 0:
raise ValueError("데이터는 비어있을 수 없습니다")
def process_item(item):
if isinstance(item, (int, float)):
return item * 2
return 0
def format_item(item):
return f"값: {item}"
def process_data(data):
validate_data(data)
processed = [process_item(item) for item in data]
return [format_item(item) for item in processed]
복잡한 조건문 단순화
리팩토링 전:
def calculate_discount(customer, order_total):
discount = 0
if customer.type == "premium":
if order_total >= 1000:
discount = 0.15
elif order_total >= 500:
discount = 0.10
else:
discount = 0.05
elif customer.type == "regular":
if customer.years_active > 5:
if order_total >= 1000:
discount = 0.10
elif order_total >= 500:
discount = 0.05
else:
discount = 0.02
else:
if order_total >= 1000:
discount = 0.05
elif order_total >= 500:
discount = 0.02
else:
discount = 0
return order_total * (1 - discount)
리팩토링 후:
def get_premium_discount(order_total):
if order_total >= 1000:
return 0.15
elif order_total >= 500:
return 0.10
return 0.05
def get_regular_discount(order_total, years_active):
if years_active <= 5:
return get_regular_new_discount(order_total)
return get_regular_loyal_discount(order_total)
def get_regular_new_discount(order_total):
if order_total >= 1000:
return 0.05
elif order_total >= 500:
return 0.02
return 0
def get_regular_loyal_discount(order_total):
if order_total >= 1000:
return 0.10
elif order_total >= 500:
return 0.05
return 0.02
def calculate_discount(customer, order_total):
if customer.type == "premium":
discount = get_premium_discount(order_total)
else: # regular customer
discount = get_regular_discount(order_total, customer.years_active)
return order_total * (1 - discount)
의존성 주입 적용
리팩토링 전:
class DataAnalyzer:
def analyze(self, data_id):
# 하드코딩된 의존성
fetcher = DataFetcher()
data = fetcher.fetch(data_id)
# 분석 로직
return {'result': sum(data), 'count': len(data)}
리팩토링 후:
class DataAnalyzer:
def __init__(self, data_fetcher):
# 생성자를 통한 의존성 주입
self.data_fetcher = data_fetcher
def analyze(self, data_id):
data = self.data_fetcher.fetch(data_id)
# 분석 로직
return {'result': sum(data), 'count': len(data)}
# 프로덕션 환경 사용
analyzer = DataAnalyzer(DataFetcher())
result = analyzer.analyze(123)
# 테스트 환경 사용
from unittest.mock import Mock
mock_fetcher = Mock()
mock_fetcher.fetch.return_value = [1, 2, 3]
analyzer = DataAnalyzer(mock_fetcher)
result = analyzer.analyze(123)
assert result == {'result': 6, 'count': 3}
테스트 전략 구현
# pytest를 사용한 리팩토링된 코드 테스트 예시
import pytest
from customer import Customer
from discount_calculator import calculate_discount
# 테스트용 픽스처
@pytest.fixture
def premium_customer():
return Customer(type="premium", years_active=3)
@pytest.fixture
def regular_new_customer():
return Customer(type="regular", years_active=2)
@pytest.fixture
def regular_loyal_customer():
return Customer(type="regular", years_active=7)
# 테스트 케이스
def test_premium_high_order(premium_customer):
result = calculate_discount(premium_customer, 1200)
expected = 1200 * 0.85 # 15% 할인
assert result == expected
def test_premium_medium_order(premium_customer):
result = calculate_discount(premium_customer, 600)
expected = 600 * 0.90 # 10% 할인
assert result == expected
def test_premium_small_order(premium_customer):
result = calculate_discount(premium_customer, 300)
expected = 300 * 0.95 # 5% 할인
assert result == expected
Mock을 활용한 테스트
# Mock을 사용한 테스트 예시
import pytest
from unittest.mock import Mock, patch
from data_processor import process_data
def test_process_data_calls_api():
# API에 대한 Mock 생성
mock_api = Mock()
mock_api.get_data.return_value = [1, 2, 3]
# 모듈의 API 의존성 패치
with patch('data_processor.api', mock_api):
result = process_data('test_param')
# API가 올바른 파라미터로 호출되었는지 확인
mock_api.get_data.assert_called_once_with('test_param')
# 결과가 예상대로인지 확인
assert result == [2, 4, 6] # 각 항목이 2배로 증가
다음은 표로 정리한 리팩토링 기법과 그 효과입니다:
리팩토링 기법 | 해결하는 문제 | 기대 효과 |
---|---|---|
함수 추출 | 긴 함수, 코드 중복 | 가독성 향상, 재사용성 증가 |
조건문 단순화 | 복잡한 조건 로직 | 이해하기 쉬운 코드, 유지보수 용이 |
의존성 주입 | 강한 결합, 테스트 어려움 | 테스트 용이성 향상, 모듈화 개선 |
클래스 분리 | 과도한 책임을 가진 클래스 | 단일 책임 원칙 준수, 모듈성 개선 |
메서드 이동 | 잘못된 책임 할당 | 응집도 향상, 관련 기능 그룹화 |
주의사항 및 팁 💡
⚠️ 이것만은 주의하세요!
테스트 없이 리팩토링하지 마세요
- 항상 충분한 테스트 케이스를 작성한 후 리팩토링을 시작하세요.
- 각 변경 후에 테스트를 실행하여 기능이 그대로 유지되는지 확인하세요.
한 번에 너무 많은 변경을 시도하지 마세요
- 리팩토링은 점진적으로 진행해야 합니다.
- 작은 변경을 하고 테스트한 후 커밋하는 방식으로 진행하세요.
리팩토링과 새 기능 추가를 동시에 하지 마세요
- 리팩토링과 기능 추가는 별도의 커밋으로 분리하세요.
- 두 작업을 동시에 수행하면 문제가 발생했을 때 원인을 찾기 어렵습니다.
💡 꿀팁
- 코드 분석 도구를 활용하세요 - pylint, flake8, SonarQube 등의 도구로 자동화된 코드 품질 검사를 수행하세요.
- 보이스카우트 규칙을 적용하세요 - 코드를 처음 발견했을 때보다 더 깨끗하게 만들고 떠나세요.
- 설계 패턴을 학습하세요 - 일반적인 문제에 대한 검증된 해결책을 적용하면 코드 품질이 향상됩니다.
- 페어 프로그래밍을 시도하세요 - 동료와 함께 리팩토링하면 더 나은 결정을 내릴 수 있습니다.
- 코드 리뷰를 활성화하세요 - 다른 사람의 관점에서 코드를 바라보면 놓친 부분을 발견할 수 있습니다.
마치며
지금까지 파이썬 코드 리팩토링에 대해 알아보았습니다. 코드 리팩토링은 시간이 지날수록 더욱 중요해지는 작업입니다. 처음에는 약간의 추가 노력이 필요하지만, 장기적으로는 유지보수성 향상과 버그 감소로 이어져 많은 시간과 자원을 절약할 수 있습니다. 🚀
효과적인
리팩토링을 위해 테스트 주도 개발(TDD)과 지속적인 통합(CI)을 일상적인 개발 프로세스에 통합하는 것을 권장합니다. 작은 변화부터 시작하여 점진적으로 코드베이스를 개선해 나가세요!
혹시 궁금한 점이 있으시거나, 더 알고 싶은 내용이 있으시면 댓글로 남겨주세요. 😊
참고 자료 🔖
- Real Python: Refactoring Python Applications for Simplicity
- Python Design Patterns
- Clean Code in Python
- Test-Driven Development with Python
- ArjanCodes: Optimize Python Code for Better Maintenance
#파이썬 #리팩토링 #코드품질 #TDD #유지보수성
'800===Dev Docs and License > Clean Code' 카테고리의 다른 글
코드 스멜: Change Preventers 코드 변경을 방해하는 요소들 🔒 (1) | 2025.03.22 |
---|---|
코드 스멜: Bloaters - 비대해진 코드 다이어트하기 🍔➡️🥗 (0) | 2025.03.22 |
파이썬 코드 리팩토링 마스터 가이드 - 성능 최적화 핵심 가이드 🚀 (0) | 2025.01.07 |
파이썬 코드 리팩토링 마스터 가이드: 코드 구조 개선 🛠️ (0) | 2025.01.07 |
파이썬 코드 리팩토링의 핵심 가이드 🎯 (0) | 2025.01.07 |