Вход в матрицу: изучение незнакомой кодовой базы
У меня было много отличных дискуссий, связанных с предыдущей записью о чтении кода (которую вы можете найти здесь), и кое-что, о чем меня спросили несколько человек, было особенно практичным: как вы вводите кодовую базу, с которой вы не знакомы ?
Это то, что является серьезным препятствием для многих разработчиков, в том числе и для меня: вводить новую кодовую базу очень сложно, и еще труднее понять, с чего начать. Много времени уходит на то, чтобы возиться с краями, без энтузиазма перебирая фрагменты кода, пока вы не поймете, что происходит (если вы когда-нибудь это сделаете).
Я не могу утверждать, что у меня это хорошо получается, но я часто этим занимаюсь по работе, и часто для меня это большая победа. Удачным побочным эффектом этого является то, что это помогает также развить некоторые хорошие навыки чтения кода (хотя это не все, что касается чтения кода). В результате я опишу свой процесс в этом документе, используя несколько примеров, чтобы проиллюстрировать, что я имею в виду.
Замечание перед тем, как мы начнем: хотя это и не является строго необходимым, 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
но по большей части я ожидал прохождения пары сотен строк кода в день с таким подходом к изучению кода.
Это не самый быстрый способ войти в кодовую базу, но он поможет вам больше всего в будущем! (Для тех из вас, кому нужен чувствительный ко времени способ сделать это, я в настоящее время пишу сообщение с описанием аварийного кода и буду ссылаться на него, когда он будет готов).
Последние мысли
Надеюсь, это помогло некоторым людям лучше понять, как начать свой первый набег на кодовую базу, с которой они не знакомы.
Это, конечно, просто способ, которым я научился это делать, и тот способ, который лучше всего подходит для меня: если у вас есть другие методы / предложения, напишите мне комментарий!