Wecode - 6주차 | 1차 Project 마무리 회고

한성봉·2021년 6월 20일
0

1차 프로젝트 회고

회고(Retrospective)란 사전적 의미로 돌아다보는 것, 지나간 일을 돌이켜 생각하는 것이다.
그렇기에 프로젝트에서 느낀점과 생각들이 휘발되기 전에 빠른시간에 기록하는 것이 중요할 것이다.

위코드에서 1차 프로젝트가 끝이났다. 2주간의 프로젝트였는데 시간이 정말 말도 안되게 빨리 갔다.
인생 첫 프로젝트를 하면서 배운점도 많고 아쉬운점도 많았다. 우리팀은 kangol 이라는 유명한 브랜드의 커머스사이트를 클론하는 프로젝트를 진행했다. 저작권 보호를 위해 우리는 캉골사이트의 로고인 캥거루를 대신해 공룡을 사용하여 gonggol이라는 사이트로 바꿨다.

Gonggol 시연 영상 링크

우선 로고를 제작하여주신 프론트의 지민님께 감사의 말씀을 전합니다. 또한 멋있는 페이지를 구현해주신 다른 프론트분들에게도 감사의 말씀을 전합니다.

🕺🏿🦖Team Gonggol

사용 기술

  • backend
    • python
    • django
    • Mysql

협업 tool

프로젝트를 하며 느낀점

공통

  • 잘한점
    우리팀이 잘한점으로 꼽자면 공통의견으로 나온 것이 활발한 소통이었다. 매일 철저히 스탠드업 미팅을 진행하였고 trello를 통해 일정을 공유하였고 무엇이 현재 개발단계에서 막고 있는지 블로커를 말함으로써 원활한 소통이 이루어지게 하였다. 1주단위로 스프린트를 진행했고 매주 월요일에 이번주에 해야할 일을 소통하였다.

  • 아쉬운점
    원활한 소통을 하였지만 아쉬웠던 점은 문서화가 제대로 되지 않아 많은 소통중에 기억이 왜곡되어 반복적으로 물어본적이 많았다. 그렇기에 우리는 1주차의 아쉬운점을 보완하여 2주차 스프린트에서 매일 스탠드업 미팅에서의 회의 내용을 기록하였고 무엇보다 프론트와 백엔드 통신에 있어서 API문서의 필요성을 느끼게 되었다. 구두로 전달할때 오는 막연함을 해소하기 위해 필요한 tool을 찾았고 그것이 gitbook이었다. API 문서를 작성할 수 있게 하는 tool이었고 우리는 제대로 한번 문서화 해놓은 다음 반복적으로 소통하는 일을 줄일 수 있었고 효율적으로 프로젝트를 진행할 수 있었다.

백엔드

  • 잘한점
    백엔드끼리의 잘한점으로 꼽자면 개발단계에서 팀적으로 막히는 부분이 있다면 바로바로 소통하고 같이 해결하였고 자신의 티켓을 다했다면 다른 사람의 티켓을 해결하기 위해 같이 힘썼다는 것이다. 나는 이 과정에서 팀원과 팀워크를 발휘해 문제를 정의하고 해결한다는 것의 의미를 알게되었고 이 과정에서 재미를 느꼈다.

  • 아쉬운점

    • 모델링 : 아쉬운 부분은 모델링 부분이다. 물론 DB모델링이 한번에 완벽하게 되는 것은 있을 수 없는 일이라 생각하고 개발단계에서도 부분 수정이 필요한 경우도 있고 최악의 경우 새로 모델링을 해야할 수도 있다고 생각한다. 하지만 첫 프로젝트에서 실수는 프론트를 고려하지 않은 모델링이었다. 나와 팀원은 모델링은 온전히 백엔드 담당으로만 생각했다. 하지만 후에 기능구현에 있어서 프론트와 소통하다보니 모델링이 잘못되었다는 생각이 들기 시작했다. 애초에 프론트와 같이 충분한 소통 후에 모델링 작업을 했다면 조금 더 좋은 모델링이 나올 수 있었다는 아쉬움이 남았다.
    • 트렐로 : 티켓 기능별로 분배할 때 최대한 자세하게 잘게 쪼개 나누어야 했는데 그렇게 하지 못하고 큰 틀에서 티켓을 분배해한 것이 아쉬움으로 남았다. 그 이유는 너무 큰 단위로 티켓을 분배하니 일단 티켓이 긴 시간동안 done으로 넘어가질 않고 자리에 남아있었고 또 개인한테 부담으로 작용했을 것 같다는 생각이 들었다. 너무 큰 단위로 분배하게 되면 큰 기능을 정해진 기한 동안 해내야한 한다는 부담감이 작용했을 것으로 생각한다. 정작 뭐가 끝났고 어디까지 가야하는 목표가 없는 느낌? 이었다. 다음 프로젝트 부터는 최대한 자세한 티켓 분배를 해야겠다는 생각이 들었다.

개인

  • 잘한점
    개인적으로 잘한점은 최대한 팀에 누가 되지 않게 미팅시간과 시간을 철저히 준수하기 위해 노력하였고 티켓이 할당되었다면 내가 할 수 있는 최대한의 역량을 발휘해 책임감을 가지고 기능 구현을 해낼려고 하였다는 점이다. 또한 팀원이 다른 기능구현으로 바쁘고 내가 조금 여유가 있다면 최대한 프로젝트에 필요한 csv파일 만들기 같은 부수적인 업무를 맡을려고 노력하였다.

  • 아쉬운점
    개인적으로 아쉬운점은 팀 프로젝트이지만 아직도 기본기는 부족한 상황이다. 하지만 개인적으로 공부를 많이 하지 못했다. 이번 프로젝트는 팀과의 소통과 스크럼 방식으로 프로젝트를 진행하는 방법에 몰두했고 그 나머지 제가 부족한 파이썬과 장고 기본기에 대해 조금 소홀했던 것 같다. 팀의 목표와 개인의 목표의 비중을 적절하게 분배하지 못해 내가 원하는 학습에 조금 소홀한 부분이 아쉬웠다고 생각한다.

프로젝트를 하며 얻은 것들

나는 개인적으로 1차 프로젝트를 하며 정말 행복한 2주였다. 물론 체력적으로 너무 힘들었고 심적으로 내 실력부족을 느끼며 힘들었지만 그것을 다 상쇄하고도 남을만큼 재밌는 프로젝트였다. 마음이 잘맞는 팀원과 공동의 목표를 가지고 무엇을 해결한다는 것! 그 재미를 느꼈고 개발자를 하기로 마음먹은게 또 한번 잘했다고 생각했다. 나는 팀원과 소통하는 법을 배웠고 프론트와의 소통도 정말 중요하다는 것과 팀의 목표에 해가 되지않기 위해 개인 개발 실력 향상을 위해 정진해야한다는 것등을 배웠다. 다음에도 같이 하고 싶은 팀을 만나 감사할 뿐이다.

이번 프로젝트에서는 프론트와 소통하는 방법을 배웠다. 백엔드 쪽에서만 기능을 생각하며 기능을 구현할 것이 아니라 항상 프론트쪽도 같이 생각하며 기능구현을 해야한다는 점이었다. 프론트와 중간중간 소통을 하지않고 기능구현을 하다 보니 내가 생각했던 부분이란 프론트에서 생각했던 부분이 달랐던 적도 있었다.

사용한 기술 스택에 대해 느낀점

  • python
    나는 다른 프로그래밍언어를 배운적은 없고 파이썬을 첫 언어로 배웠다. 파이썬은 사용자 친화적인 언어임에는 틀림없다고 생각한다. 가독성이 매우 높은 언어라고 생각한다. 그렇게 만들어진 언어이니 만큼 파이썬을 최대한 효율적으로 활용해야한다고 생각했다. 가독성이 높은 언어임에도 불구하고 아직 프로그래밍적인 사고가 미숙하여 가독성을 해치는 코딩을 하고 있다고 느꼈다. 항상 어떻게 하면 효율적으로 가독성 높게 코드를 짤 수 있는지 고민하며 전체적인 틀부터 잡고 코딩해야겠다고 느꼈다. 그냥 기능구현에만 치중하고 코드를 짜니 나만 알아보는 코드가 구현되었고 결국 효율적이 못한 코드 구성이 되어버렸다.
  • django
    웹 사이트를 만들기 위해 정말 편리한 프레임워크라는 생각이 들었다. 공식문서만 검색해도 원하는 기능을 쉽게 만들 수 있었다. 하지만 아직 나는 편리한 기능을 능숙하게 쓰지는 못했다. 아직도 가야할 길이 먼 것 같다. 하지만 능숙해지면 정말 빠른 시간 내에 웹 사이트를 구축할 수 있을 수도 있겠다는 생각을 했다. 이번에 장고 ORM을 사용해 보며 쿼리문 대신 간접적으로 데이터베이스를 조작해보며 객체지향적 코드가 무엇인지 조금은 감을 잡을 수 있었다. 모든 DB의 테이블을 클래스로 구현하여 참조등을 활용하여 DB자료를 이용할 수 있었다. 그리고 쿼리문은 DB에 접근 할 때 마다 원하는 자료를 이용할 때면 계속 반복적으로 쿼리문을 작성해야하지만 ORM으로 작성해두면 똑같은 조건의 자료를 불러오거나 비슷한 자료를 빌려올 때 작성된 ORM 코드를 활용해 다시 사용하기 편리하였다. 조금만 응용하면 여러 조건을 줘 편리하게 원하는 DB에 접근할 수 있었다.

내가 구현한 기능

  • 상품 리스트 : 카테고리 별 상품 리스트, 카테고리별 정렬기능, 페이징기능

  • 처음 구현한 코드

# main 카테고리 클릭 시  main 카테고리 상품 전체 리스트 호출 로직
class ProductListView(View):
    def get(self, request):
        if request.GET.get('category'):
            main_category_name    = request.GET.get('category', None)
                main                  = MainCategory.objects.get(name=main_category_name) 
                sub_category_name     = main.subcategory_set.filter(maincategory_id=main.id)
                main_image            = Image.objects.filter(is_main=False)
                
                main_product_info = []
                for product in sub_category_name:
                    for main_product in product.product_set.all():
                        tags = ProductTag.objects.filter(product_id=product.id)
                        main_product_info.append(
                            {
                                "product_id"    : main_product.id,
                                "product_name"  : main_product.name,
                                "product_price" : int(main_product.price),
                                "discount_rate" : main_product.discount,
                                "product_image" : [img.url for img in main_image],
                                "product_tag"   : [{"new":tag.tag.new, "sale":tag.tag.sale, "best":tag.tag.best} for tag in tags]
                            }
                        )
                return JsonResponse({"product_list": main_product_info, "product_total" : len(main_product_info)}, status=200)

            # 서브카테고리 클릭시 정렬 로직
            if request.GET.get('name') and request.GET.get('sort-method'):
                sort_method        = request.GET.get('sort-method', None)
                sub_category_name  = request.GET.get('name', None)
                sub_category       = SubCategory.objects.get(name=sub_category_name)
                products           = Product.objects.filter(sub_category_id=sub_category.id)
                main_image         = Image.objects.filter(is_main=True)
                    
                sort_info = []
                for sort_product in products.order_by(sort_method):
                        tags = ProductTag.objects.filter(product_id=sort_product.id)
                        sort_info.append(
                            {
                                "product_id"    : sort_product.id,
                                "product_name"  : sort_product.name,
                                "product_price" : int(sort_product.price),
                                "discount_rate" : sort_product.discount,
                                "product_image" : [img.url for img in main_image],
                                "product_tag"   : [{"new":tag.tag.new, "sale":tag.tag.sale, "best":tag.tag.best} for tag in tags]
                            }
                        )
                return JsonResponse({'product_list': sort_info, "product_total" : len(sort_info)}, status=200)

            ## sub 카테고리 클릭시 sub 카테고리 상품 리스트 호출 로직       
            if request.GET.get('name'):
                sub_category_name  = request.GET.get('name', None)
                sub_category       = SubCategory.objects.get(name=sub_category_name)
                products           = Product.objects.filter(sub_category_id=sub_category.id)
                main_image         = Image.objects.filter(is_main=False)
        
                sub_product_info = []
                for product in products:
                    tags = ProductTag.objects.filter(product_id=product.id)
                    sub_product_info.append(
                        {
                            "product_id"    : product.id,
                            "product_name"  : product.name,
                            "product_price" : int(product.price),
                            "discount_rate" : product.discount,
                            "product_image" : [img.url for img in main_image],
                            "product_tag"   : [{"new":tag.tag.new, "sale":tag.tag.sale, "best":tag.tag.best} for tag in tags]
                        }
                    )
        
                return JsonResponse({"product_list": sub_product_info, "product_total" : len(products),}, status=200)
        except ObjectDoesNotExist:
            return JsonResponse({'MESSAGE': '해당 상품이 존재하지 않습니다.'}, status=404)

위의 코드는 정말 효율이 없는 코드였다. 가독성?이 있는지는 모르겠지만 가독성만 고려했다고 생각했고 중복이 너무 많은 비효율적인 코드였다. 그냥 기능만 중시하여 해당 기능에 맞는 조건만 고려하다 보니 위와 같은 코드가 나왔다고 생각한다. 역참조를 두번에 결쳐해야하는 상황이 있었는데 너무 기본적인 것만 생각하여 코딩을 하다보니 비효율적으로 역참조를 두번할 때 쿼리셋에 담긴 객체들을 for문을 두번이나 사용하여 상품리스트를 호출하는 코드가 완성됐다. 하지만 팀원의 도움과 멘토님의 피드백으로 장고 공식문서를 참조해 더블 언더스코어와 q객체를 활용하여 효율적으로 참조롤 할 수 있었다.

  • 리팩토링 후 코드
class ProductListView(View):
    def get(self, request):
        try:
            # 쿼리 파라미터 
            category_id     = request.GET.get('categoryId', None)
            sub_category_id = request.GET.get('subcategoryId', None)
            sort            = request.GET.get('sort-method', None)
            page            = int(request.GET.get('page', 1)) 
            
            # 페이징 기능
            limit  = page*PAGE_SIZE
            offset = limit-PAGE_SIZE
            
            # q 객체를 활용한 조건문
            q = Q()
            
            # 페이징 기능 / 정렬 기능
            if category_id:
                q &= Q(sub_category__maincategory_id=category_id)
                products = Product.objects.filter(q)[offset:limit]

            if sub_category_id:
                q &= Q(sub_category_id=sub_category_id)
                products = Product.objects.filter(q)[offset:limit]
            
            if category_id and sort:
                q &= Q(sub_category__maincategory_id=category_id)
                products = Product.objects.filter(q).order_by(sort)[offset:limit]

            if sub_category_id and sort:
                q &= Q(sub_category_id=sub_category_id)
                products = Product.objects.filter(q).order_by(sort)[offset:limit]
            
            total = Product.objects.filter(q)

            if not products:
                return JsonResponse({"MESSAGE": "해당 상품이 존재하지 않습니다."}, status=404)
            
            # 상품 리스트 호출 로직
            results = []
            
            for product in products:
                tags       = ProductTag.objects.filter(product_id=product.id)
                main_image = product.image_set.all()
                results.append(
                    {
                        "product_id"    : product.id,
                        "product_name"  : product.name,
                        "product_price" : int(product.price),
                        "discount_rate" : product.discount,
                        "product_image" : [img.url for img in main_image],
                        "product_tag"   : [{
                            "new":tag.tag.new, 
                            "sale":tag.tag.sale, 
                            "best":tag.tag.best} for tag in tags]
                    }
                )
            return JsonResponse({"results": results, "total_counts" : len(total), "page_count": PAGE_SIZE}, status=200) 
        except:
            return JsonResponse({"MESSAGE": "해당 상품이 존재하지 않습니다.", "status": '404'}, status=404)

처음에 1:N 관계인 메인카테고리를 참조하고 있는 서브카테고리를 또 참조하고 있는 제품들만 호출해야하는 상황이 있었는데 역참조 두번을 해야하는 상황이었고 for문을 두번 활용해 쿼리셋을 참조하려했다.

장고 공식문서와 스택오버플로우를 활용해 __q객체 의 사용법을 알아보고 상당량의 중복 제거와 나름 가독성 좋은 조건문이 완성되었다고 생각한다. 물론 훨씬 더 좋은 로직이 있다고 생각하지만 처음 구현한 코드에 비해 훨씬 가독성이 좋아져 만족하는 코드가 되었다. 앞으로도 내가 알고 있는 지식을 활용한 코드가 효율적인지에 대한 고찰을 하며 최대한 프레임워크의 기능을 활용해 문제해결을 하는 개발자가 되기 위해 노력할 것이다.

프로젝트 소감

너무 즐거운 2주였고 기회가 된다면 다시 같이 프로젝트를 해야하고 싶은 팀원들을 만나것에 너무 감사하게 생각한다. 하지만 반대로 생각해보면 다른 팀원들도 나를 같이 일하고 싶은 개발자로 생각할지 궁금했다. 팀으로 하는 일이니 만큼 나 자신이 남이 봤을 때 같이 일하고 싶은 개발자로 성장한다는 것이 무엇인지 항상 생각하며 내 개발 커리어를 쌓아야겠다고 생각한다. 나는 팀 프로젝트를 진행하면 같이 일하고 싶은 개발자가 되기 위한 가치관은 소통할 줄 아는 사람, 팀을 위해 배려할 줄 아는 능력, 물론 개발실력도 중요할 것이다. 독단적으로 개발실력만 좋은 것 보다. 나열한 능력들을 고루 겸비한 개발자가 같이 일하고 싶은 개발자라고 믿어 의심치 않는다. 2주간 함께한 우리팀에게 너무 고생했고 감사합니다.

1개의 댓글

comment-user-thumbnail
2021년 6월 20일

성봉님 행복한 2주였다니 놀라운걸요! 🤭
2차 마무리 시점에는 더 많은 성장 이루시길 바랄게요!
1차 수고 많으셨습니다 👏🏻👏🏻👏🏻

답글 달기