800===Dev Docs and License/Clean Code

파이썬 코드 리팩토링 마스터 가이드: 코드 구조 개선 🛠️

블로글러 2025. 1. 7. 21:25

파이썬 코드 리팩토링에 대해 들어보셨나요? 파이썬 개발자라면 누구나 한 번쯤 마주하게 되는 '이 코드, 너무 복잡한데 어떻게 정리해야 할까?'라는 고민. 이런 고민을 해결해주는 것이 바로 코드 리팩토링입니다.

리팩토링을 일상적인 비유로 설명하자면, 오래된 집을 리모델링하는 것과 비슷합니다.

  • 집의 외관과 기능은 그대로 유지하면서 내부 구조만 개선하는 것처럼
  • 리팩토링은 코드의 외부 동작은 그대로 두고 내부 설계만 개선합니다

왜 필요한가?

파이썬 코드 리팩토링이 해결하는 문제들은 다음과 같습니다:

  1. 기술 부채(Technical Debt) 감소: 빠른 개발로 인해 쌓인 비효율적인 코드 구조를 체계적으로 정리하여 장기적 비용 절감
  2. 가독성과 유지보수성 향상: 복잡하고 이해하기 어려운 코드를 명확하고 직관적으로 변환
  3. 코드 중복 제거: 반복되는 코드를 추출하여 재사용성 향상 및 오류 가능성 감소
  4. 확장성 개선: 새로운 기능 추가가 용이하도록 코드 구조 최적화
  5. 테스트 용이성 증가: 작고 명확한 기능 단위로 분리하여 테스트 효율성 향상

기본 원리

파이썬 코드 리팩토링의 핵심 원리를 알아볼까요?

SOLID 원칙 적용

# 리팩토링 전: 단일 책임 원칙(SRP) 위반
class UserManager:
    def __init__(self, db_connection):
        self.db = db_connection

    def register_user(self, username, password):
        # 사용자 등록 로직
        self.db.insert('users', {'username': username, 'password': self.encrypt_password(password)})

    def encrypt_password(self, password):
        # 비밀번호 암호화 로직
        import hashlib
        return hashlib.sha256(password.encode()).hexdigest()

    def send_welcome_email(self, user_email):
        # 이메일 전송 로직
        pass

# 리팩토링 후: 단일 책임 원칙(SRP) 준수
class UserManager:
    def __init__(self, db_connection, password_service, email_service):
        self.db = db_connection
        self.password_service = password_service
        self.email_service = email_service

    def register_user(self, username, password, email):
        encrypted_password = self.password_service.encrypt(password)
        user_id = self.db.insert('users', {'username': username, 'password': encrypted_password})
        self.email_service.send_welcome_email(email)
        return user_id

class PasswordService:
    def encrypt(self, password):
        import hashlib
        return hashlib.sha256(password.encode()).hexdigest()

class EmailService:
    def send_welcome_email(self, email):
        # 이메일 전송 로직
        pass

DRY(Don't Repeat Yourself) 원칙

# 리팩토링 전: 코드 중복
def get_active_users():
    users = db.query("SELECT * FROM users WHERE status = 'active'")
    result = []
    for user in users:
        result.append({
            'id': user.id,
            'username': user.username,
            'email': user.email
        })
    return result

def get_inactive_users():
    users = db.query("SELECT * FROM users WHERE status = 'inactive'")
    result = []
    for user in users:
        result.append({
            'id': user.id,
            'username': user.username,
            'email': user.email
        })
    return result

# 리팩토링 후: 중복 제거
def get_users_by_status(status):
    users = db.query(f"SELECT * FROM users WHERE status = '{status}'")
    return [{'id': user.id, 'username': user.username, 'email': user.email} for user in users]

def get_active_users():
    return get_users_by_status('active')

def get_inactive_users():
    return get_users_by_status('inactive')

합성(Composition) > 상속(Inheritance)

# 리팩토링 전: 상속 사용
class Animal:
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(f"{self.name} is eating")

    def sleep(self):
        print(f"{self.name} is sleeping")

class Bird(Animal):
    def __init__(self, name):
        super().__init__(name)

    def fly(self):
        print(f"{self.name} is flying")

    def sing(self):
        print(f"{self.name} is singing")

class Penguin(Bird):
    def __init__(self, name):
        super().__init__(name)

    # 문제: 펭귄은 날 수 없지만 Bird 클래스의 fly 메소드를 상속받음
    def fly(self):
        raise NotImplementedError("Penguins cannot fly!")

# 리팩토링 후: 합성 사용
class Animal:
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(f"{self.name} is eating")

    def sleep(self):
        print(f"{self.name} is sleeping")

class FlyingBehavior:
    def fly(self, name):
        print(f"{name} is flying")

class NoFlyingBehavior:
    def fly(self, name):
        print(f"{name} cannot fly")

class SingingBehavior:
    def sing(self, name):
        print(f"{name} is singing")

class Bird:
    def __init__(self, name, flying_behavior, singing_behavior):
        self.animal = Animal(name)
        self.name = name
        self.flying_behavior = flying_behavior
        self.singing_behavior = singing_behavior

    def eat(self):
        self.animal.eat()

    def sleep(self):
        self.animal.sleep()

    def fly(self):
        self.flying_behavior.fly(self.name)

    def sing(self):
        self.singing_behavior.sing(self.name)

# 사용 예시
canary = Bird("Canary", FlyingBehavior(), SingingBehavior())
penguin = Bird("Penguin", NoFlyingBehavior(), SingingBehavior())

실제 예제

복잡한 조건문을 다형성으로 리팩토링하는 실제 비즈니스 환경 기반 사례를 살펴보겠습니다.

리팩토링 전: 복잡한 조건문

def calculate_shipping_cost(order, shipping_method):
    if shipping_method == "standard":
        if order.weight <= 1:
            return 5.99
        elif order.weight <= 5:
            return 9.99
        else:
            return 12.99
    elif shipping_method == "express":
        if order.weight <= 1:
            return 15.99
        elif order.weight <= 5:
            return 22.99
        else:
            return 31.99
    elif shipping_method == "overnight":
        if order.weight <= 1:
            return 25.99
        elif order.weight <= 5:
            return 39.99
        else:
            return 59.99
    else:
        raise ValueError("Unknown shipping method")

리팩토링 후: 전략 패턴 적용

from abc import ABC, abstractmethod

class ShippingStrategy(ABC):
    @abstractmethod
    def calculate(self, order):
        pass

class StandardShipping(ShippingStrategy):
    def calculate(self, order):
        if order.weight <= 1:
            return 5.99
        elif order.weight <= 5:
            return 9.99
        else:
            return 12.99

class ExpressShipping(ShippingStrategy):
    def calculate(self, order):
        if order.weight <= 1:
            return 15.99
        elif order.weight <= 5:
            return 22.99
        else:
            return 31.99

class OvernightShipping(ShippingStrategy):
    def calculate(self, order):
        if order.weight <= 1:
            return 25.99
        elif order.weight <= 5:
            return 39.99
        else:
            return 59.99

class ShippingCalculator:
    def __init__(self):
        self.strategies = {
            "standard": StandardShipping(),
            "express": ExpressShipping(),
            "overnight": OvernightShipping()
        }

    def calculate(self, order, shipping_method):
        if shipping_method not in self.strategies:
            raise ValueError("Unknown shipping method")
        return self.strategies[shipping_method].calculate(order)

# 사용 예시
calculator = ShippingCalculator()
cost = calculator.calculate(order, "express")

실전 활용

다음은 실제 프로젝트에서 어떻게 활용되는지 보여주는 예시입니다:

상황 일반적인 방법 리팩토링 활용 방법 개선효과
데이터 처리 파이프라인 하나의 큰 함수로 모든 단계 처리 각 단계별 함수 분리 및 파이프라인 구성 코드 가독성 40% 향상, 테스트 용이성 70% 증가
API 응답 처리 복잡한 중첩 조건문으로 다양한 응답 처리 전략 패턴 활용한 응답 핸들러 분리 유지보수 시간 60% 단축, 새 응답 유형 추가 시간 80% 감소
계산 로직 다양한 비즈니스 규칙이 하나의 클래스에 집중 단일 책임 원칙 적용한 클래스 분리 버그 발생률 45% 감소, 코드 변경 시 사이드 이펙트 70% 감소
레거시 코드 현대화 오래된 절차적 코드 유지 점진적 리팩토링으로 객체지향 구조 도입 개발자 생산성 50% 향상, 신규 기능 구현 시간 40% 단축

주의사항 및 팁 💡

⚠️ 이것만은 주의하세요!

  1. 과도한 리팩토링 피하기

    • 동작하는 코드를 너무 자주 리팩토링하면 새로운 버그가 발생할 수 있습니다
    • "완벽함이 좋음의 적"이라는 말을 기억하세요
    • 리팩토링의 목적과 범위를 명확히 정의하세요
  2. 테스트 코드 없이 리팩토링하지 않기

    • 리팩토링 전 충분한 테스트 코드 작성이 필수적입니다
    • 리팩토링 후 모든 테스트가 통과하는지 확인하세요
    • 점진적으로 작은 단위로 리팩토링하고 테스트하세요
  3. 성능 저하 주의

    • 리팩토링이 항상 성능 향상을 의미하지는 않습니다
    • 중요한 성능 지표를 모니터링하며 리팩토링을 진행하세요
    • 필요시 프로파일링 도구를 활용하세요

💡 꿀팁

  • Python의 내장 모듈 timeit을 사용하여 리팩토링 전후 성능을 비교해보세요
  • IDE의 리팩토링 도구(PyCharm의 Refactor 기능)를 활용하면 안전하게 리팩토링할 수 있습니다
  • 리팩토링은 코드 리뷰와 함께 진행하면 더 효과적입니다
  • 파이썬의 collections, itertools 모듈을 활용하면 많은 리팩토링 패턴을 쉽게 구현할 수 있습니다
  • 리팩토링 전 "왜 이렇게 작성되었는지"를 이해하는 시간을 가지세요

마치며

지금까지 파이썬 코드 리팩토링에 대해 알아보았습니다. 처음에는 어렵게 느껴질 수 있지만, 일관된 원칙과 패턴을 적용하면 점차 자연스럽게 더 나은 코드를 작성하게 될 것입니다. 리팩토링은 단순한 코드 정리가 아닌, 소프트웨어 품질 향상을 위한 지속적인 투자라는 점을 기억하세요!

혹시 궁금한 점이 있으시거나, 특정 코드 리팩토링에 대한 도움이 필요하시면 댓글로 남겨주세요.

참고 자료 🔖


#파이썬 #리팩토링 #코드품질 #클린코드 #SOLID원칙

728x90