9. 오류 수정과 기능 개선하기

등록일 : 2020.12.15. 19:30



오류 수정하기

강좌의 순서대로 잘 따라왔지만 분명 한 번 이상 오류 화면이나 에러 메시지를 마주하여 당황했을 것이다. 단순한 타이핑 오류가 있을 수도 있고, 저자와 환경이나 버전이 달라서 발생할 수도 있다. 또한, 본 온라인 북의 타이핑 오류등에 의해서도 발생할 수 있다.

작성한 모든 코드는 완벽하지 않다. 장고 또한 오류를 가지고 있고 버전이 개선되면서 수정된다. 사용 중 오류가 발생하는 것을 보통 '버그'가 발생했다고 부른다. 최초의 버그란 말은 컴퓨터에 벌레가 들러붙어 오작동을 일으킨 것, 이 벌레(오류)를 제거한다는 의미로 'debugging' 이라고 부르게 되었다.

소프트웨어의 특성상 모든 오류를 모두 제거하기란 매우 어렵고, 내가 만든 프로그램이 잘 돌아간다고 해도 버그는 존재한다.

아무리 똑똑한 개발자라고 하더라도 버그 없는 소프트웨어 출시는 불가능하다. 소프트웨어 개발로 유명한 애플이나 마이크로소프트의 소프트웨어나 운영체제도 출시 후 대부분 오류를 수정하는 업데이트를 내놓는다.

여러분이 만든 투표 앱도 수많은 오류를 가지고 있다. 프로그램이 동작중 먹통이 되거나, 기능이 제대로 동작하지 않는 등 제작자가 의도한 결과와 다른 결과가 발생하는 것을 오류(버그)라고 생각하면 된다. 다음의 예시를 보자.

기획 요구사항
1.  polls 홈 화면에 접근 시 가장 최근에 배포된 투표제목 부터 5개의 투표 리스트를 보여준다.

위의 요구사항을 토대로 다음과 같이 코드를 작성했다. 여기서는 제너릭 뷰를 사용한 것을 참고하자.

class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    model = Question
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        # """Return the last five published questions."""
        return Question.objects.order_by('pub_date')[:5] # 실수로 -를 붙이지 않음

개발자인 당신은 위와 같이 코드를 구현했다. 그리고 웹 브라우저에서 구현한 내용을 확인했다. 오류를 발견했는가? 내림차순이 아닌 오름차순으로 나타나고 있는 것을 알 수 있다. 코드상으로는 단순히 - 하나 있고 없고의 차이지만 결과는 달라진다. 사소한 실수가 매우 큰 파장을 불러일으키는 경우도 많다. 심각한 경우 서비스 자체가 먹통이 될 수도 있다.

디버깅을 시작해보자. 코드를 다음과 같이 수정한다.

class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    model = Question
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        # """Return the last five published questions."""
        return Question.objects.order_by('--pub_date')[:5]  # --실수로 2개를 붙임

하지만 의도치 않은 실수로 -를 두 개를 붙이고 말았다. 여러분들은 문서를 작성하면서 오타를 그대로 중요한 메일이나 첨부파일을 제출했던 아찔한 경험은 누구나 있을 것이다. 프로그램 제작과 배포도 이를 피할 수 없다.

다행히 여러분이 쓰고 있는 파이썬과 장고는 자체적인 문법 오류가 있는 경우 알려주고, Pycharm 같은 개발 툴에서도 자체적으로 체크할 수 있는 간단한 타이핑이나 문법오류를 발견하면 알려준다. 또한, 이런 오류들을 찾을 수 있도록 테스트 도구도 많이 존재한다. 하지만 이런 툴들이 모두 내가 투표 앱을 어떻게 모델을 설계하고 리스트 목록을 오름차순으로 보여줄지 내림차순으로 보여줄지까지 알 수 없을뿐더러 모든 부분을 완벽히 체크해 줄 수 없다.

이렇게 변경하고 배포해도 문법 자체의 오류는 없으므로 서버는 나의 변경사항을 의심 없이 적용하게 된다. 웹사이트를 접속하고 투표 목록페이지에 접속하여 오류메시지를 확인해보자.

오류 메시지는 웹 페이지에 나오지 않는 경우도 있다. 이때는 장고 개발서버 실행 중인 콘솔을 같이 확인하는 것이 좋다.

오류가 생긴 경우는 오류 메시지를 확인하는 것이 중요하다.

장고 개발 서버 콘솔 메세지

...중략...
django.core.exceptions.FieldError: Cannot resolve keyword '-pub_date' into field. Choices are: choice, id, pub_date, question_text
...중략...

실제로 'pub_date' 라는 필드를 찾아야 하지만 오타로 인해 '-pub_date' 라는 필드를 찾게 되고 이 필드는 만든적이 없으므로 이를 찾을 수 없다. 이런 경우 결과를 처리해 줄 수 없는 심각한 상황에 이른다. 이번 오류는 장고 터미널을 보면 그 원인을 알 수 있는 오류이다. 하지만 이마저도 나오지 않는 경우도 있다.

서버에서 처리할 수 없는 오류 발생시 상황에 따라 전혀 서비스를 제공할 수 없을지도 모른다. 그래서 코드를 변경 후 실제 서버에 적용하기 전에 다양한 테스트를 진행하는 것이 중요하다. 다행이 우리는 아직 서비스를 내 PC에서만 사용 중이기 때문에 여기서는 얼마든지 테스트해도 된다. 이제 원래의 의도대로 --pub_date를 수정하여 원래도록 동작하게 한다. 물론 지금도 이야기하지 않은 오류들이 굉장히 많이 숨어있다.

버그 발견과 수정은 소프트웨어 개발의 한 부분이라고 생각하는 것이 좋다. 전통적인 개발 프로세스는 간단하게 기획 → 설계 → 개발 → 테스트 → 배포 및 운영으로 볼 수 있다. 그리고 추가/변경 사항에 대한 기획 → 설계변경 → 개발(수정) → 테스트 → 배포 및 운영활동이 반복 작용한다.

버그가 있는 것에 대해 완벽하게 대응하기보다는 자연스러운 현상으로 생각하는 것이 좋고 정해진 시간 안에서 해결할 수 있는 우선순위가 높은 버그들 위주로 먼저 처리하는 것이 유익하다. 시간이 없다고 버그를 아예 수정하지 않는 것은 아니다. 이를 그냥 두게 된다면 사용자들은 당신이 만든 서비스를 사용하지 않고 떠나버릴 것이다. 평균 이상의 제품 퀄리티를 위해서는 개발자 스스로 느끼기에 충분한 테스트와 다양한 사용자 동작을 고려하고 일반적인 기본 시나리오 외에 예외케이스까지 고려하여 고품질의 서비스를 제공하는 것이 일반적이다. 또는 본인이 아닌 주변의 다른 사람들에게 사용해보고 피드백을 받아보는 것이 중요하다.

내가 만든 제품에 대해 나쁜 평가가 올 수도 있고 의도치 않게 마음의 상처를 입을 수도 있다. 하지만 감정적인 부분을 배제하고 실제 그런 쓴소리는 제품을 좋게 만들어주는 아주 좋은 자양분이다. 물론 주변의 평가와 실제 사용자의 반응이 다른 경우도 존재한다. 조언과 수정요청을 100% 수용할 필요는 없고, 확신이 있다면 본인의 생각을 믿고 추진하자. 어쨌든 좋은 피드백과 나쁜 피드백을 하는 주변 지인이 사실 개발에 있어 가장 고마운 사람임을 잊지 말자.

알파 테스트와 베타 테스트

회사의 소속이라면 다양한 내부 직원들에게 테스트를 요청해 볼 수 있다. 출시 전에 내부 직원들이 해보는 테스트를 보통 알파 테스트라고 한다. 흔히 개밥 먹기(dogfooding)라고도 한다. 강아지 사료를 만드는 회사에서 이를 내부적으로 검증하는 부분에서 이름이 쓰였다고 한다. 이 부분은 굉장히 중요한 부분이다.

회사 소속이 아니라면 주변 지인이나 불특정 다수(커뮤니티 등)에게 서비스를 한 번 써보도록 요청할 수 있다. 이는 실제 릴리즈 전에 한번씩 꼭 해보길 추천하며 게임 업계에서 매우 잘 활용하고 있다. 게임을 실제 출시 되기 전 '베타 테스트'라는 이름으로 불리며, 일부 사용자들에게만 미리 출시전 게임을 해보고 그에 대한 피드백과 오류를 받고 개선한다. 게임 회사뿐만아니라 일반적인 소프트웨어 개발 회사들은 모두 내부 테스트(알파 테스트) 및 베타 테스트를 진행하는 것이 일반적이다.

아니면 시장에 빠르게 출시 먼저하고 사용자 반응을 살피고 그에 맞게 전략을 수정하는 경우도 있다. 회사 이미지나 평판 관리가 중요한 경우 이런 배포 전략은 조금 위험할 수도 있는데, 신규 서비스가 시장에서 반응이 어떤지 보고 전략이 바뀌는 경우에 사용된다. 보통 스타트업이나 소규모 프로젝트에 사용되는 경우가 많고 오히려 처음에는 별로였다가 나중에 점차 개선해서 획기적으로 서비스가 좋아진다면 반전 효과(?)를 누릴 수도 있겠다. 정답은 없다만 다양한 테스트는 가능하면 많이 하는 것이 좋다는 것은 사실이다.

오류 수정을 돕는 장고의 디버그 모드

이전 강좌에서 http://127.0.0.1:8000/polls/아무거나 접속하여 404 페이지를 볼 수 있었다. 하단의 메세지를 한번 살펴보자.

> You're seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard 404 page.

장고는 오류 수정을 돕기 위해 기본적으로 버그 수정에 필요한 디버그 모드로 작동한다. 이를 참고하여 버그 수정에 필요한 정보를 찾아볼 수 있으니 개발환경에서는 그대로 잘 사용하면 되겠다. 버그를 수정하기 위해서는 오류 원인과 어느 지점에서 오류가 발생했는지, 이때 사용된 데이터들이 어떻게 되어있는지 확보하는 것은 매우 중요하다.

다만 실제 이런 화면은 사용자에게 보여주게 되면 친절하지도 않을뿐더러 중요한 보안 정보가 노출될수도 있으므로 실제로 서비스 하는 경우는 끄도록 한다.

settings.py 파일을 열어 DEBUG 항목을 찾아보자. DEBUG = True 항목을 False 로 꾼다. 파이썬에서는 True / False를 사용할 때 첫 글자만 대문자로 사용하는 것이 원칙이다.

settings.py 수정

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False  # True에서 False로 변경한다.
ALLOWED_HOSTS = ['127.0.0.1'] # []안에 '127.0.0.1' 를 추가한다.

그러자 서버가 동작을 멈출 것이다. 터미널로 가서 확인한다.

CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
(venv) $ python manage.py runserver


디버그 모드를 종료하는 경우는 이제 운영 환경을 위한 셋업이라고 보기 때문에 ALLOWED_HOSTS를 등록해야 사용 가능하다. 기존 서버 실행은 디버그 모드였고 ALLOWED_HOSTS에 정의된 값이 없었다. 실제 서비스를 위해 사용할 값을 명시하는 것이다. 일단 실제 배포가 아닌 debug모드를 끈 상태를 우리는 로컬에서 확인만 할 것이므로 ALLOWED_HOSTS = ['127.0.0.1']로 변경했다. 값을 수정했으면 이제 개발 서버를 다시 시작해보자.

이제 다시 http://127.0.0.1:8000/polls/아무거나 접속하여 404 페이지를 보자.

메세지가 뜬 것을 볼 수 있다. 디버그 모드가 True일 때 처럼 디버깅 메시지를 표시하지 않고 요청을 찾을 수 없다는 메세지를 띄워 줄 수 있다. 운영 환경에서는 장고 디버깅 화면을 이렇게 숨겨 처리하는 것이다. 이 화면의 내용과 디자인 또한 변경 할 수 있지만 여기서는 다루지 않는다. 우리는 아직 개발 중이므로 다시 원래대로 돌려놓겠다.

settings.py 수정

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []

로 변경한다. 다시 디버그 모드로 동작할 것이다.

test.py를 활용한 자동화 테스트

파일 구조를 보면 test.py 파일이 있다. 여러분이 작성한 코드에 대한 테스트를 도와주는 부분이다. 사람이 아닌 컴퓨터가 자동으로 여러분이 작성한 테스트를 검증해 줄 수 있도록 하는 코드도 작성할 수 있다. 물론 작성은 사람이 하게 되지만 말이다. 장고 튜토리얼에 포함된 매우 중요한 파트이지만, 초급자 레벨에 벗어나는 부분이 많아 이 강좌에서는 다루지 않는다.

투표 앱 기능 개선하기

여러분이 만든 뷰를 좀 더 개선해보도록 해 보자. 코드는 오류 수정 뿐만이 아니라 새로운 기능이나 더 좋은 기능과 서비스를 위해 개선된다. 오류 수정과는 또 다른 큰 축이다.

요구사항은 다음과 같다.

Question에 투표 등록 시 pub_date에 입력한 날짜가 되면 노출하고 싶다. 
예를 들어 12월 24일 00:00:00시에 맞게 투표를 미리 등록해놓고 12월 24일이 되면 해당 시작하고자 한다.

이를 만들기 위해 admin에서 하나의 투표를 수정해보자. Question 모델에 보면 Date published: 값을 변경할 수 있다. 이를 현재보다 미래 시간으로 변경한다. 이 온라인 북은 아직 2020년 크리스마스가 되기 이전이다. 2020-12-24, 00:00:00으로 변경하고 저장한다. 이후에 접속했다면 더 미래의 시간으로 하자.

그리고 127.0.0.1:8000/polls/의 index페이지에 접속해서 보자. 가장 미래의 투표가 가장 위에 출력되었을 것이다. 이제 수정해보겠다.

polls/views.py 수정

from django.utils import timezone # 추가하기

# ...중략...

class IndexView(generic.ListView): # 수정
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """
        Return the last five published questions (not including those set to be
        published in the future).
        """
        return Question.objects.filter(
            pub_date__lte=timezone.now()
        ).order_by('-pub_date')[:5]

기존의 Question.objects.order_by('-pub_date')[:5]를 다음과 같이 수정한다.

Question.objects.filter(
            pub_date__lte=timezone.now()
        ).order_by('-pub_date')[:5]

Question.objects에 filter가 사용되었다. filter는 말 그대로 특정 조건에 해당하는 데이터만 걸러 내겠다는 뜻이다. pub_date__lte = timezone.now()는는 현재 날짜/시간이 DB의 pub_date가 작거나 같은 Question을 포함하는 쿼리 셋을 반환한다. 정렬 부분과 슬라이스 부분은 이전과 동일하게 최신 5개만 가져온다. 단 필터 조건에 의해 현재 시간보다 이전의 투표들만 가져오게 될 것이다.

장고에서 '~보다 작은'을 표현하고 싶으면 필드에 __lte를 사용한다. '~보다 큰'을 표현하려면 __gt를, 미만은 __le를 사용한다.

수정 후 127.0.0.1:8000/polls/ 를 접속해보자. 현재 시간을 기준으로 미래의 투표는 표시되지 않았을 것이다. 이렇게 요구사항에 맞춰 기능을 개선했다.

이제 다음 실습을 위해 미래 시간의 투표는 과거시간으로 다시 변경 해 두자.

여러분이 개선해보고 싶은 부분이 있거나, 오류를 발견했다면 사소한 부분 하나라도 주저하지 말고 개선해보자. 그러면서 실력도 많이 늘 것이다.