Вход в матрицу: изучение незнакомой кодовой базы

У меня было много отличных дискуссий, связанных с предыдущей записью о чтении кода (которую вы можете найти здесь), и кое-что, о чем меня спросили несколько человек, было особенно практичным: как вы вводите кодовую базу, с которой вы не знакомы ?

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

Я не могу утверждать, что у меня это хорошо получается, но я часто этим занимаюсь по работе, и часто для меня это большая победа. Удачным побочным эффектом этого является то, что это помогает также развить некоторые хорошие навыки чтения кода (хотя это не все, что касается чтения кода). В результате я опишу свой процесс в этом документе, используя несколько примеров, чтобы проиллюстрировать, что я имею в виду.

Замечание перед тем, как мы начнем: хотя это и не является строго необходимым, IDE, позволяющая переходить к объявлениям и реализациям функций / классов, чрезвычайно полезна. Многие бесплатные IDE делают это: я большую часть времени работаю на Python, так что это будет пример языка, который я использую для этого поста. (для питонистов vim + jedi отлично подходит в качестве бесплатной версии, как и PyCharm’s Community Edition). Я большой поклонник PyCharm и использую его ежедневно, но в большинстве IDE есть некоторые варианты этой функции, и в этом процессе очень помогает быстрый переход к объявлениям функций.

Используйте код так, как это сделал бы пользователь

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

Цель здесь - сразу перейти к высокоуровневой функции, которая выполняет окончательную обработку и выводит функцию, которую видит пользователь. Или, другими словами, вы нашли суть того, что делает эту библиотеку удобной для вас: функцию, ввод и преобразованный вывод которой вам знаком. Все остальное будет выходить изнутри этой функции, что сделает ее идеальной отправной точкой для входа в кодовую базу: это фундаментальный шаг в битве за понимание кодовой базы.

Что, если это огромная обширная кодовая база с множеством входов?

Предположим, вы используете библиотеку widgetmanager, которая выполняет ряд административных задач, связанных с виджетами. В основном вы используете функцию count_widgets:

from widgetmanager import count_widgets
count_widgets(factory)

count_widgets по-прежнему будет вашим основным входом.

Есть ли у каркаса множество других частей? Конечно. Может быть, есть count_widgets, distribute_widgets, return_widgets, make_widgets. Может быть, есть масса вспомогательных функций, на которые они полагаются.

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

Начните выделять эту функцию отдельно

Как только вы найдете эту начальную функцию, которая обрабатывает окончательный результат, начните разбирать, что заставляет ее работать.

Важная часть здесь - терпение. Не пренебрегайте функцией: в конце концов, это ваша свободная нить. Начните читать функцию построчно, понимая, что на самом деле делает код.

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

(Пример кода здесь взят из библиотеки Serpextract от Parsely)

def extract(serp_url, parser=None, lower_case=True, trimmed=True,
            collapse_whitespace=True, use_naive_method=False):
    """
Docstrings omitted for brevity
    """
  # Software should only work with Unicode strings internally, converting
    # to a particular encoding on output.
url_parts = _unicode_urlparse(serp_url)
    if url_parts is None:
        return None
result = None
    if parser is None:
        parser = get_parser(url_parts)
if parser is None:
        if not use_naive_method:
            return None  # Tried to get keyword from non SERP URL

Вы еще не знаете, что делают _unicode_urlparse, get_parser или use_naive_method. В конце концов вам нужно будет понять, что делает функция; пока, однако, прочтите и разберитесь в потоке функции верхнего уровня. Иногда помогает озвучить то, что вы читаете, чтобы убедиться, что вы действительно это понимаете:

«Присвоить результаты _unicode_urlparse (serp_url) url_parts. Если значение равно None, верните None. Инициализировать результат переменной как Нет. Если в качестве аргумента не был передан анализатор, присвойте результаты get_parser (url_parts) синтаксическому анализатору ».

И так далее. Это кажется элементарным (и действительно, вероятно, излишним для простого потока, подобного приведенному выше), но когда вы дойдете до сложных лямбда-выражений и тернистых циклов, иногда полезно пригнуться, чтобы убедиться, что вы действительно понимаете код, который читаете.

Копать медленно…

допустим, вы перешли в конец функции. Вы по-прежнему не знаете, что делают _unicode_urlparse, get_parser или use_naive_method. Что значит…

Вы узнаете!

Здесь пригодятся функции «перехода к объявлению» IDE (это также то, что делает это на Github или веб-интерфейсе гигантским PITA, поскольку сделать это быстро в интерфейсе Github непросто, и поэтому я рекомендую какая то IDE, ну или хотя бы grep и текстовый редактор).

Итак, мы собираемся перейти к определению _unicode_urlparse, посмотреть, что там наверху, поздороваться, вы знаете, как это бывает.

Итак, вот _unicode_urlparse:

def _unicode_urlparse(url, encoding='utf-8', errors='ignore'):
    """
    Docstrings omitted for brevity
    """
    if isinstance(url, bytes):
        url = url.decode(encoding, errors)
    elif isinstance(url, ParseResult):
        # Ensure every part is unicode because we can't rely on clients to do so
        parts = list(url)
        for i in range(len(parts)):
            if isinstance(parts[i], bytes):
                parts[i] = parts[i].decode(encoding, errors)
        return ParseResult(*parts)
try:
        return urlparse(url)
    except ValueError:
        msg = 'Malformed URL "{}" could not parse'.format(url)
        log.debug(msg, exc_info=True)
        return None

Красиво, коротко, мило (здесь, кстати, поможет резиновое утканье: этот маленький цикл for немного сложен, а оператор разброса - это приятный кусочек сахара).

Итак, теперь у нас есть _unicode_urlparse. Обратите внимание, что это немного упрощает понимание функции извлечения:

«Присвоить результаты _unicode_urlparse (serp_url) url_parts. Если значение равно None, вернуть None »

становится

«Присвоить url_parts возвращаемое значение ParseResult _unicode_urlparse (serp_url). Если значение равно None, вернуть None »

Итак, теперь, в остальной части функции извлечения, вы знаете, что url_parts - это ParseResult, и можете соответствующим образом с ним взаимодействовать.

Выполнив это с каждой из функций, которые использует extract (), вы начнете раскрывать всю библиотеку и лучше понять, что и как она делает.

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

… Но не копайте слишком глубоко

Однако проницательный читатель заметит, что в моей методологии есть потенциально разрушительный недостаток: нет предела тому, насколько глубоко вы можете пойти. В _unicode_urlparse у нас есть ParseResult и urlparse - что это? Мы должны пойти туда (они часть шести, библиотеки переходов 2/3). А как насчет .decode? Что насчет isinstance?

О боже, я тону в стандартной библиотеке

К сожалению, нет простого способа ответить на этот вопрос (по крайней мере, я не встречал такого способа). С академической точки зрения, конечно, невероятно полезно знать больше: вам никогда не повредит копать глубже, и это только даст вам больше знаний.

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

Если вызываемая функция является частью достаточно хорошо документированной библиотеки (например, стандартной библиотеки, шести, запросов или чего-то подобного), я думаю, что нормально просто прочитать документацию по этой библиотеке, когда дело доходит до понимания того, что за конкретная функция или класс делает.

Мошенничество? Возможно, немного, и будут времена, когда доверительная документация укусит вас (редко для хорошо документированных библиотек, но бывает), но по большей части я думаю, что это разумное руководство, которому нужно следовать изо дня в день в своих приключениях в программировании. .

Документ

Вы, наверное, уже забыли фрагмент серпэкстракта, который мы только что прочитали.

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

(Кстати: если вы делаете это для библиотеки, в которой мало / нет строк документации или комментариев, подумайте о том, чтобы спросить сопровождающего репозитория, будут ли они открыты для запроса на вытягивание, состоящего только из строк документации и комментариев. Если да, то это отличный способ как документировать ваш прогресс в кодовой базе, так и внести свой вклад в помощь другим в будущем!)

В конце концов, поработав какое-то время с определенной кодовой базой, вы начнете узнавать ее больше с головы до ног, особенно части, с которыми вы работаете больше всего, - но для этого важно, чтобы вы документировали, даже временно (я я невероятно виноват в документировании блокнота, и мне действительно нужно поработать над тем, чтобы сделать свой код более постоянным для тех, кто придет после).

Потерпи

Наконец, один из самых важных моментов для меня - быть терпеливым, в чем я тоже не очень силен (хотя я над этим работаю!)

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

[url.strip() for url_block in url_blocks for url in url_block]

потребуется немного больше времени на синтаксический анализ, чем:

x = 1

но по большей части я ожидал прохождения пары сотен строк кода в день с таким подходом к изучению кода.

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

Последние мысли

Надеюсь, это помогло некоторым людям лучше понять, как начать свой первый набег на кодовую базу, с которой они не знакомы.

Это, конечно, просто способ, которым я научился это делать, и тот способ, который лучше всего подходит для меня: если у вас есть другие методы / предложения, напишите мне комментарий!