Notice
Recent Posts
Recent Comments
Link
«   2025/10   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

최용우

Django 프로젝트 회고. 나의 부족한 추상화 능력 본문

장고

Django 프로젝트 회고. 나의 부족한 추상화 능력

용우쨩 2025. 4. 12. 08:19

작년에 작업했던 현장관리프로그램을 개조하고 있다.

너무나 처참한 내부 퀄리티에 경악을 금치 못했다.

중반부부터 고객 요구사항이 변하고 임기응변으로 구현해 놓은 것이 가장 큰 문제였다.

또한 Django Rest Framework에서 기본적으로 제공하는 필터링/페이지네이션 기능을 적극적으로 활용하지 못했다.

잘 활용했더라면 view class 나 serializer class에 반복적인 코딩 작업이 줄고 높은 순도의 코드만 남길 수 있었을 것이다.

 

추상화 능력이 부족했던 것이다.

 

프로그래밍의 꽃은 추상화라고 하던가.

근데 도대체 추상화가 뭐야? 사물을 보이는 그대로 그린 정물화의 반댓말 아닌가.

농담이다. 추상화는 소프트웨어를 공부하다 보면 자주 듣는다.

위키백과에서는 추상화를 아래와 같이 기술한다.

컴퓨터 과학에서 추상화(abstraction)는 복잡한 자료, 모듈, 시스템 등으로부터 핵심을 간추려 내는 것을 말한다.

 

여기서 중요한 표현은 핵심을 간추려 내는 것이다.

핵심만 남기는 행동을 추상화라고 한다.

이게 왜 중요한지는 인터넷에 조금만 찾으면 차고 넘친다.

전형적인 답변을 원한다면 고퀄의 다른 글이나 강의를 듣길 바란다.

 

내가 생각하는 추상화 능력이란 (예제와 함께)

1. 가능한 코딩을 적게해야한다. 이미 만들어진 클래스를 최대한 활용하자.

 

안좋은 예시는 아래와 같다.

class ProductViewSet(viewsets.ModelViewSet):
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated, ExcludeLeader]
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    
    #1. 리스트 뷰 필터링 로직
    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset()

        is_active = self.request.query_params.get('is_active')
        if is_active:
            queryset = queryset.filter(is_active=is_active)

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

 

위 코드는 장고에서 자주 사용하는 model viewset 클래스 중 일부이다.

list라는 함수는 특정 모델의 여러 객체를 불러올 때 사용한다.

나는 이 list 함수를 오버라이딩해서 is_active 필터링과 페이지네이션을 수행했다.

 

지금이야 필터링 대상의 필드가 하나이고 적용된 viewset이 하나라서 페이지네이션이 간단하다고 하지만 이는 매우 비효율적이다.

왜냐하면 모든 view에다 저런식으로 직접 구현하게 되면 나중에 로직 변경이 생기거나 문제가 발생하여 코드를 수정할 때 모든 view에 대해서 수동으로 수정해줘야 하는 상황이 발생하기 때문이다.

규모가 작은 시스템은 수정할 것이 많지 않으므로 와닿지 않겠지만 만약 수백개 수천개의 api가 있는 중대형 시스템이라면 상상하기도 싫다.

실제로 나는 모든 토이 프로젝트에 저런 임기응변식의 코드를 사용했고 지나고 보니 매우 엉성하고 유지보수가 어렵다는 점을 발견하였다.

 

2. 이제 간결하게 수정해 볼 차례이다.

 

from django_filters import rest_framework as filters
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.filters import OrderingFilter
from rest_framework.response import Response

# 1. django‑filter FilterSet 정의 (필터링 조건 추상화)
class ProductFilter(filters.FilterSet):
    is_active = filters.BooleanFilter()  # 쿼리파라미터가 Boolean 값을 올바르게 처리
    name = filters.CharFilter(field_name='name', lookup_expr='icontains')
    # category는 콤마(,)로 구분된 여러 값으로 필터링
    category = filters.CharFilter(method='filter_category')

    def filter_category(self, queryset, name, value):
        category_list = value.split(',')
        return queryset.filter(category__in=category_list)

    class Meta:
        model = Product
        fields = ['is_active', 'name', 'category']


# 2. 개선된 ProductViewSet (필터와 정렬 백엔드를 이용)
class ProductViewSet(viewsets.ModelViewSet):
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated, ExcludeLeader]
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    # 전역 설정 또는 뷰셋 단위로 페이지네이션 클래스를 지정 가능 (전역 설정 시 생략 가능)
    pagination_class = CustomPageNumberPagination  # 필요 시 커스텀 페이지네이터 설정

    # django-filter 및 OrderingFilter 백엔드 적용
    filter_backends = [filters.DjangoFilterBackend, OrderingFilter]
    filterset_class = ProductFilter
    
    # ordering 필드 지정 (정렬 가능한 필드; 필요에 따라 '__all__'을 사용할 수도 있음)
    ordering_fields = ['id', 'name', 'category', 'is_active']
    ordering = ['-id']  # 기본 정렬

 

def list를 다시 불러와서 작성하는게 없어졌다.

또한 django-filter와 ordering을 활용해서 변경에 유연하게 대응할 수 있다.

공통 모듈을 한 곳에 모아 관리하면 유지보수가 쉽고 확장이 유연하다는 이유 때문이다.

Django Rest Framework는 이미 우리가 생각하는 거의 모든 동작을 가지고 있고 나는 잘 사용하기만 하면된다.

 

나는 이미 고도화로 추상화되어 있는 DRF를 내가 임의로 오버라이딩해서 사용하는 실수를 범했다.

당연히 코딩은 많아지고 수정도 어려워지게 된다. 

Ctrl + Shift + F 를 눌러서 같은 변수를 여러번 일일이 수정한 경험 있지 않은가?

 

아래는 GPT가 정리해준 추상화의 장점이다. 파란색 글씨는 나의 코멘트다.

추상화가 가져다주는 유연성

  1. 중앙 집중식 관리
    • 전역 설정에서 페이지네이터를 정의하면, 모든 리스트 뷰에 동일한 페이지네이션 로직이 자동으로 적용된다.
      그런데 만약 나처럼 def list를 직접 오버라이딩 한다면 전역 설정한 페이지네이터가 작동하지 않아서 직접 적용해줘야 한다!
    • 페이지네이션 로직이나 기본 페이지 크기를 변경해야 한다면, 모든 뷰셋 대신 settings.py에서 한 번만 수정하면 된다.
    • 이런 중앙 집중식 관리 방식은 소프트웨어 공학의 추상화 원칙(즉, 세부 구현을 감추고 인터페이스만 노출하는 방식)과 맞닿아 있어, 코드의 응집도를 높이고 결합도를 낮춘다.
  2. 유지보수와 확장성
    • 특정 뷰셋에 직접 페이지네이션 로직을 구현하면, 각 뷰셋마다 유사한 코드가 중복되고, 추후 수정할 때 수정 포인트가 많아진다.
    • 전역 설정과 같은 추상화를 사용하면 코드 중복을 줄여 유지보수가 간편해지고, 새로운 API를 추가할 때 기본 로직을 그대로 재사용할 수 있다.
    • 특정 API에서 특별한 페이지네이션 로직이 필요할 때만 해당 뷰셋에 pagination_class를 재정의하면 되므로, 범용적인 로직과 특정 요구사항을 별도로 관리할 수 있다.
  3. 재사용성과 모듈화
    • django-filter의 FilterSet을 사용하면 필터링 관련 로직을 뷰셋 코드에서 분리해 별도의 클래스로 정의 가능. 한 번 작성해두면 여러 뷰셋에서 재사용할 수 있어, 유사한 필터링 기능을 구현할 때 중복 코드를 줄일 수 있다.
      진짜 비슷하게 필터링을 사용해야하는 경우가 많이 있는데 따로 분리해서 관리하면 코드 중복을 방지한다.
    • 전역 설정은 모든 뷰셋에서 재사용 가능한 모듈 역할을 한다. 코드를 모듈화하여 동일한 기능(예: 페이지네이션)을 여러 곳에서 공유하는 것은 추상화의 핵심 개념.
    • 추상화 수준이 높아지면, 개별 뷰셋은 자신이 해결해야 하는 핵심 비즈니스 로직에 집중할 수 있으며, 공통 기능은 이미 추상화된 컴포넌트를 그대로 활용할 수 있다.

나의 섣부른 판단이지만 초급과 고급 엔지니어를 구분하는 가장 중요한 요소는 "설계"다.

아름다운 설계가 반드시 비즈니스 성공을 보장하진 않지만 비즈니스의 성공의 필수 요소임은 분명하다.

서비스가 커지고 고객들의 수많은 요청과 변경사항에 적은 비용으로 대응하려면 설계가 잘 되어 있어야 하기 때문이다.

그러므로 계속 공부하고 고객이 원하는 것이 무엇인지 찾아보자.

 

 

하찮은 나의 vs code 화면...