Отладка принтами

Друг, если ты умеешь настраивать ротируемый лог в файл, знаешь все аналоги pdb, можешь перенаправить syslog в Kibana и знаешь, как хранятся данные в Elasticsearch, эта статья не для тебя. Посмотри лучше гифки с котиками.

Сегодня мы поговорим о том, чем все занимаются и редко говорят об этом: об отладке программ с помощью дебажных принтов. Это не самый крутой, но очень практичный и простой метод разобраться, что не так.

Вот есть код:

def get_context_data(self, **kwargs):
    search_query = kwargs.get('search_query')
    context = super().get_context_data(**kwargs)
    context['qna_menu_item_active'] = True
    if search_query:
        models.QnaSearchQuery(
            query=search_query,
            user=self.request.user if self.request.user.is_authenticated() else None
        ).save()
        context['object_list'] = context['object_list'].filter(
            Q(title__icontains=search_query) |
            Q(text_md__icontains=search_query) |
            Q(category__icontains=search_query) |
            Q(answers__text_md__icontains=search_query)
        )
        context['search_query'] = search_query
    return context

Этот код должен правильно готовить данные для страницы вопросов и ответов, но он не работает. Можешь не пытаться разобраться в самом коде, он специально взят такой.

Итак, предположим, что тестов нет, автора нет, комментариев нет. Надо разобраться, в чём дело. Тесты, логирование, отладчик и специализированные библиотеки запрещено использовать из-за трудового договора.

В самом начале ещё можно не думать. Нужна переменная context– давайте выведем её в консоль:

def get_context_data(self, **kwargs):
    search_query = kwargs.get('search_query')
    context = super().get_context_data(**kwargs)
    context['qna_menu_item_active'] = True
    if search_query:
        models.QnaSearchQuery(
            query=search_query,
            user=self.request.user if self.request.user.is_authenticated() else None
        ).save()
        context['object_list'] = context['object_list'].filter(
            Q(title__icontains=search_query) |
            Q(text_md__icontains=search_query) |
            Q(category__icontains=search_query) |
            Q(answers__text_md__icontains=search_query)
        )
        context['search_query'] = search_query
        print(context)
    return context

Теперь выполним код, посмотрим на результат в консоли. Кажется, многое зависит от переменной search_query: есть ли она в kwargs, что в ней и во что она превращается при конвертации в bool. Смотрим:

def get_context_data(self, **kwargs):
    print('search_query' in kwargs)
    search_query = kwargs.get('search_query')
    print(search_query)
    print(bool(search_query))
    context = super().get_context_data(**kwargs)
    context['qna_menu_item_active'] = True
    if search_query:
        models.QnaSearchQuery(
            query=search_query,
            user=self.request.user if self.request.user.is_authenticated() else None
        ).save()
        context['object_list'] = context['object_list'].filter(
            Q(title__icontains=search_query) |
            Q(text_md__icontains=search_query) |
            Q(category__icontains=search_query) |
            Q(answers__text_md__icontains=search_query)
        )
        context['search_query'] = search_query
        print(context)
    return context

Снова смотрим на данные в консоли. Окей, с этим понятно. Теперь посмотрим, что у нас в context['object_list'] изначально и что мы туда кладём. Чтобы не захламлять консоль, посмотрим на количество элементов в этом списке. Сделаем отдельный запрос, чтобы его потом можно было легко удалить:

def get_context_data(self, **kwargs):
    print('search_query' in kwargs)
    search_query = kwargs.get('search_query')
    print(search_query)
    print(bool(search_query))
    context = super().get_context_data(**kwargs)
    context['qna_menu_item_active'] = True
    if search_query:
        models.QnaSearchQuery(
            query=search_query,
            user=self.request.user if self.request.user.is_authenticated() else None
        ).save()
        print(context['object_list'].count())
        print(context['object_list'].filter(
            Q(title__icontains=search_query) |
            Q(text_md__icontains=search_query) |
            Q(category__icontains=search_query) |
            Q(answers__text_md__icontains=search_query)
        ).count())
        context['object_list'] = context['object_list'].filter(
            Q(title__icontains=search_query) |
            Q(text_md__icontains=search_query) |
            Q(category__icontains=search_query) |
            Q(answers__text_md__icontains=search_query)
        )
        context['search_query'] = search_query
        print(context)
    return context

Снова запускаем. Данные в консоли навели на ошибку? Отлично, ради этого всё и задумывалось. Осталось удалить принты. Это просто, ведь основной код мы не трогали.

Данные в консоли не навели на ошибку? Значит, с теми переменными, на которые мы смотрели порядок. Можно удалить неактуальные принты и таким же образом проверить другие переменные. Вот, например, что-то с QnaSearchQuery происходит.

Главное – не забыть поудалять отладочные принты перед коммитом. А то все узнают, что ты занимаешься ЭТИМ. :)