8 лет назад я исправил опечатку в чужом репозитории, а сейчас регулярно делаю коммиты в проекты, которые использую, и даже вошел в core team библиотеки с 27000 звёзд на GitHub

В этой статье покажу, что участие в Open Source проще, чем кажется. Расскажу, как регулярная работа с чужим кодом помогает быстрее разбираться в незнакомых кодовых базах, писать тесты и лучше документировать решения. А также объясню, почему публичная активность на GitHub выгодно отличает вас от других разработчиков, особенно в эпоху повсеместного использования ИИ.

Почему стоит участвовать в Open Source проектах

Понимание инструментов изнутри

Мне нравится возможность открыть исходники библиотеки и разобраться, как она устроена. Это меняет отношение к библиотекам - перестаёшь их использовать как чёрный ящик, чувствуешь контроль в разработке. А когда натываешься на баг, то можешь сам найти причину, а не ждать исправления месяцами. Например на одном из проектов нужно было интегрировать интерфейс ИИ чата. Такой интерфейс требует много времени на самостоятельную разработку: чат, скролл, поддержка таблиц и заголовков в ответах от ИИ, возможность прикреплять файлы. Воспользовался готовой библиотекой, которая предоставляет нужный интерфейс. Интегрировать удалось быстро, но почему-то ИИ переставал отвечать спустя минуту после общения в чате. После чтения исходников оказалось что библиотека запоминает переданные мной HTTP заголовки и использует их для всех последующих запросов в мой бекенд. Код использования библиотеки:

const runtime = useCustomEdgeRuntime({ 
  headers: { Authorization: `Bearer ${token}` }, 
});

Старые заголовки спустя время становились не актуальными и авторизация ломалась. Проблему удалось исправить лишь внутри библиотеки:

-const headers = new Headers(this.options.headers);
+const headersValue = typeof this.options.headers === 'function' 
+  ? await this.options.headers() 
+  : this.options.headers;

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

const runtime = useCustomEdgeRuntime({ 
  headers: async () => {
    const token = await getToken();
    return { Authorization: `Bearer ${token}` }, 
  }
});

Получился простой пулл реквест, который автор библиотеки одобрил.

Освоение новых подходов

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

const tests = [
  { input: "2 + 3 * 4", expected: 14 },
  { input: "(2 + 3) * 4", expected: 20 },
  { input: "10 / 0", expected: "error" },
  { input: "2 + ", expected: "error" },
  { input: "2.5 + 1.3", expected: 3.8 },
  { input: "sqrt(16)", expected: 4 },
  { input: "pow(2, 3)", expected: 8 },
];

tests.forEach(({ input, expected }) => {
  const result = evaluate(input);
  expect(result).toBe(expected);
});

Такой подход применяется в статическом анализаторе типов PHPStan, который имеет 8 млн загрузок в месяц. Именно табличное тестирование потребовалось применить в этом пулл реквесте, чтобы за один раз покрыть большое количество сценариев.

Карьерные преимущества

Сейчас когда ИИ генерирует резюме, код портфолио и массово откликается на вакансии в автоматическом режиме, разработчикам выделяться стало сложно. Я считаю что публичная активность в популярных проектах помогает выделиться. Например, мне писали CTO американских стартапов с предложением о трудоустройстве с релокацией. Эти компании активно используют MobX, увидели меня в верхнем списке контрибьюторов и написали на email. А на текущую валютную удалёнку устроился благодаря узким знаниям API Телеграм и Open Source библиотеки Gram.js - работодатель искал именно эту экспертизу. Получается есть ситуации когда работодатели сами находят кандидатов, а не наоборот, и ваши коммиты будут работать на вас.

Отправляем пулл реквест

С чего начать

Документация или исправление опечаток - идеальный старт. Для таких случаев GitHub даже предоставляет удобный интерфейс в одну кнопку: у каждого файла в репозитории есть иконка редактирования. Если на неё нажать и отредактировать файл - GitHub за вас сделает форк репозитория, запушит коммит и предложит создать pull request. Не нужно самому делать форк и открывать редактор. Примеры просты пулл реквестов:

Изменения кода

Изменения в коде требуют более основательного подхода - нужно форкнуть библиотеку, настроить и запустить проект локально.

Бывает, что в проекте нет Docker и не описано, как его запустить локально. В таком случае могут помочь файлы CI (Continuous Integration), если они есть. На GitHub они находятся в папке .github/workflows. Обычно там запускается проект и выполняются тесты с линтингом кода. Если код запустился в CI, то эти же инструкции помогут запустить его локально.

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

  • Золотое правило: сначала создаём issue, потом пишем код. Не стоит предлагать изменения без предварительного обсуждения. Автор библиотеки может посчитать ваше изменение ненужным, и тогда вы просто потратите время впустую.

  • При создании issue можно сначала поблагодарить автора за библиотеку. Скорее всего, он работает над ней бесплатно в свободное время. Если есть возможность - предоставляем способ воспроизведения в виде ссылки на код или репозиторий. Чем легче автору запустить и проверить, тем быстрее он ответит.

  • Если проблема обсуждена и автор библиотеки дал зелёный свет, рекомендую воспроизвести проблему в тестах. Такая возможность есть не всегда, но вас скорее всего и так попросят добавить тесты, как меня попросили тут и тут.

  • Если пулл реквест готов, тесты есть, а автор библиотеки не отвечает неделями, можно написать follow-up сообщение. Пример на английском: "Hi, just wanted to follow up on this PR, would love to get it merged. Let me know if there's anything else you need from me. Thanks!". На моей практике это ускоряет принятие пулл реквестов

  • Не ломаем обратную совместимость, если есть возможность. Библиотеки, как правило, стараются следовать семантическому версионированию, чтобы пользователи могли предсказуемо обновляться. Если мы добавляем новый параметр в функцию, то старый код должен продолжать работать.

Например, я пользовался библиотекой wavesurfer.js (9000 звёзд), которая анализирует аудио и рисует столбцы на HTML Canvas. Столбцы не имели закруглений, а на работе по дизайну требовались закругления. Код, отвечающий за рисование столбца в библиотеке, выглядел так:

fillRectToContext(ctx, x, y, width, height) {
    if (!ctx) {
        return;
    }
    ctx.fillRect(x, y, width, height);
}

Для закругления пришлось переработать так:

fillRectToContext(ctx, x, y, width, height, radius) {
    if (!ctx) {
        return;
    }
    if (radius) {
        this.drawRoundedRect(ctx, x, y, width, height, radius);
    } else {
        ctx.fillRect(x, y, width, height);
    }
}

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

От участника к мейнтейнеру

Я много лет пользуюсь библиотекой MobX для управления состоянием на фронтенде. Библиотека имеет порядка ~2 млн загрузок в неделю и 27000 звёзд на GitHub. В какой-то момент в коде библиотеки я обнаружил, что описание TypeScript типов для одной из функций выглядит очень громоздко:

export interface IActionFactory {
   <A1, R, T extends (a1: A1) => R>(fn: T): T & IAction
    <A1, A2, R, T extends (a1: A1, a2: A2) => R>(fn: T): T & IAction
    <A1, A2, A3, R, T extends (a1: A1, a2: A2, a3: A3) => R>(fn: T): T & IAction
    <A1, A2, A3, A4, R, T extends (a1: A1, a2: A2, a3: A3, a4: A4) => R>(fn: T): T & IAction
    <A1, A2, A3, A4, A5, R, T extends (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => R>(fn: T): T &
        IAction
    <A1, A2, A3, A4, A5, A6, R, T extends (a1: A1, a2: A2, a3: A3, a4: A4, a6: A6) => R>(fn: T): T &
        IAction
    <A1, R, T extends (a1: A1) => R>(name: string, fn: T): T & IAction
    <A1, A2, R, T extends (a1: A1, a2: A2) => R>(name: string, fn: T): T & IAction
    <A1, A2, A3, R, T extends (a1: A1, a2: A2, a3: A3) => R>(name: string, fn: T): T & IAction
    <A1, A2, A3, A4, R, T extends (a1: A1, a2: A2, a3: A3, a4: A4) => R>(name: string, fn: T): T &
        IAction
    <A1, A2, A3, A4, A5, R, T extends (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => R>(
        name: string,
        fn: T
    ): T & IAction
    <A1, A2, A3, A4, A5, A6, R, T extends (a1: A1, a2: A2, a3: A3, a4: A4, a6: A6) => R>(
        name: string,
        fn: T
    ): T & IAction
}

Мне показалось, что это можно оптимизировать, в результате получилось:

export interface IActionFactory {
    <T extends Function | null | undefined>(fn: T): T & IAction
    <T extends Function | null | undefined>(name: string, fn: T): T & IAction
}

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

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

Главным уроком для меня стало то, что не нужно просить разрешения быть полезным. Также оказалось, что не обязательно быть суперэкспертом, достаточно быть активным и последовательным.

Главные выводы

Участие в open source может оказаться одним из лучших решений в вашей карьере. Это увлекательно - в каждом проекте можно чему-то научиться, а начать можно буквально с исправления опечатки. Понимание инструментов изнутри прокачивает технические навыки быстрее любых туториалов. А публичная активность - это отличная инвестиция в карьеру.

Комментарии (4)


  1. pnmv
    22.05.2025 19:19

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

    это не означает, что "там, у них" всё плохо, просто настроения у меня такие.


  1. Kuzmin_Vyacheslav
    22.05.2025 19:19

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


  1. kompilainenn2
    22.05.2025 19:19

    Очень многие разработчики этого не понимают. Я часто слышу, что вклад в опенсорц это просто бесплатный труд или ещё мулька "этот ваш опенсорц очень сложный и вообще легаси"


  1. trauus
    22.05.2025 19:19

    Всегда хотел попробовать участие в опенсорс, однако после 8-часового рабочего дня уже нет ни сил, ни времени. Как вам это удается?