Как инженер-программист, я всегда ищу инструменты и технологии, которые помогут мне работать более эффективно. Недавно у меня была возможность попробовать GitHub Copilot X, помощник по программированию на основе искусственного интеллекта, который использует машинное обучение, чтобы предлагать фрагменты кода и предложения автозаполнения при написании кода. Я был взволнован, увидев, на что способен этот новый инструмент, и мне было любопытно посмотреть, как он интегрируется в мой рабочий процесс. В этой статье я поделюсь своим опытом использования GitHub Copilot X в первый раз и предложу свои мысли о том, стоит ли добавлять его в свой собственный набор инструментов.
Я должен отметить, что для целей этой статьи я решил использовать только те функции, которые в настоящее время доступны в общедоступной предварительной версии GitHub Copilot X. Кроме того, я использовал Visual Code в качестве своей среды кодирования и Go в качестве предпочтительного языка. Ваш опыт работы с Copilot X может различаться в зависимости от вашей IDE и языка, так как некоторые функции могут не полностью поддерживаться или интегрироваться с некоторыми IDE. Хотя есть и другие экспериментальные или ночные функции сборки, которые я мог бы протестировать, я хотел предоставить более доступный и реалистичный отчет о том, каково это использовать этот инструмент в его текущем состоянии с широко используемой IDE. Тем не менее, я изучил весь спектр функций, доступных в общедоступной предварительной версии, и смог получить хорошее представление о том, как они работают и как они могут быть полезны в реальных сценариях кодирования.
Интеграция в мою IDE
В целом, интеграция GitHub Copilot X в мою IDE была довольно простым процессом. Установка расширения была простой, но процесс аутентификации немного раздражал, потому что Visual Code не делал очевидным, где мне нужно проходить аутентификацию с помощью GitHub. Однако как только я это понял, процесс аутентификации оказался относительно безболезненным. Я должен отметить, что вам нужно перезапустить Visual Code, прежде чем вы сможете использовать обновленные функции, но это самое незначительное неудобство. В целом, процесс оказался не таким сложным, как я ожидал, и я довольно быстро смог начать использовать Copilot X в своем рабочем процессе кодирования.
Написание нового кода
Первой и наиболее очевидной особенностью GitHub Copilot X является его способность помогать в написании нового шаблонного кода. В этом разделе мы рассмотрим, как Copilot X может помочь вам создавать новые функции, классы и другие структуры кода с нуля. Мы также рассмотрим, насколько хорошо Copilot X может адаптироваться к вашей существующей кодовой базе и насколько он может улучшить ваш общий рабочий процесс кодирования.
Чтобы проверить способность Copilot генерировать новый код, соответствующий существующему стилю, я решил попросить его написать новую функцию для структуры, которую я часто использую в своей работе по программированию, под названием Snowplow
, которую я использую для выполнения запросов к хранилищу данных Snowflake. В качестве теста я решил попросить Copilot написать новую функцию, которая будет извлекать финансовые активы, связанные с датой. Я начал с ввода сигнатуры функции, включая только получателя и имя: GetAssetsForDate
. К моему удивлению, GitHub Copilot X сразу выдал следующий код:
func (client *Snowplow) GetAssetsForDate(ctx context.Context, provider gopb.Provider, date *time.Time) ([]*snowflake.Asset, error) { client.logger.Log("Getting assets data for %s provider...", provider) // First, create the query from our table name and select clause query := orm.NewQuery().Select(assetColumns...).From(client.fullName(Assets)). Where(orm.And, orm.Equals("provider_id", provider, false), orm.Equals("last_updated_utc", date, false)) // Next, run the query and attempt to collect our results into a list of assets assets, err := orm.RunQuery[snowflake.Asset](ctx, query, client.db, client.logger) if err != nil { return nil, err } // Finally, return the assets we retrieved client.logger.Log("Retrieved %d assets for %s provider", len(assets), provider) return assets, nil }
Что меня действительно впечатлило, так это то, что Copilot смог реализовать полностью инструментированный код, аналогичный тому, что я уже написал в том же файле. Комментарии, связанные с кодом, также были на языке, который я бы использовал.
Кроме того, Copilot X использовал те же пакеты, которые я использовал для выполнения аналогичных задач в другом месте этого файла. Код синтаксически правильный и делает то, что я ожидал бы от функции с таким именем. Он принимает правильные типы данных и производит правильный вывод. Изучив код, я не обнаружил в нем явных логических проблем.
Более того, Copilot смог внедрить тип из стандартной библиотеки, включенной в Go, который я не импортировал явно, а это означает, что он имеет представление за пределами конкретного файла, с которым я работаю. В целом, я был доволен качеством кода, который GitHub Copilot X смог сгенерировать на основе моей первоначальной подсказки.
Чтобы показать, насколько этот предлагаемый вывод близок к тому, что я обычно пишу, я включил код из того же файла в качестве ссылки:
func (client *Snowplow) GetAssetsForProvider(ctx context.Context, provider gopb.Provider, enabled bool) ([]*snowflake.Asset, error) { client.logger.Log("Getting assets data for %s provider...", provider) // First, create the query from our table name and select clause query := orm.NewQuery().Select(assetColumns...).From(client.fullName(Assets)). Where(orm.And, orm.Equals("provider_id", provider, false), orm.Equals("download_enabled", enabled, false)) // Next, run the query and attempt to collect our results into a list of assets assets, err := orm.RunQuery[snowflake.Asset](ctx, query, client.db, client.logger) if err != nil { return nil, err } // Finally, return the assets we retrieved client.logger.Log("Retrieved %d assets for %s provider", len(assets), provider) return assets, nil }
При этом этот вывод не идеален. Например, третий параметр, date
, является указателем на time.Time
. Это было бы полезно, если бы я хотел сделать этот параметр необязательным. Однако код, созданный Copilot, с учетом используемого мной пакета ORM не обеспечивает такой возможности.
Таким образом, пользователь потенциально может передать этой функции аргумент nil, что неизбежно приведет к неверным результатам. Еще одна потенциальная проблема, которую я заметил, заключалась в том, что сигнатура функции не была прокомментирована. Но просто набрав //
в строке над подписью, Copilot побудил создать комментарии для функции, и они также были высокого качества.
Рефакторинг существующего кода
Рефакторинг существующего кода является неотъемлемой частью процесса разработки программного обеспечения. Будь то повышение производительности, расширение функциональности или просто упрощение понимания кода, рефакторинг может оказать значительное влияние на качество и эффективность вашего кода. В этом разделе мы рассмотрим, может ли Copilot X помочь вам реорганизовать существующий код быстрее и точнее, чем ваш текущий процесс. Мы также рассмотрим, насколько хорошо Copilot X может адаптироваться к вашему существующему стилю кодирования и насколько он может улучшить ваш общий рабочий процесс кодирования.
Чтобы проверить способность Copilot X рефакторить существующий код, я экспериментировал с несколькими вариантами использования, включая преобразование данных, содержащихся в существующей переменной, из одного типа в другой, внесение некритических изменений, требующих значительных изменений в существующем коде, и исправление критического изменения. в результате обновления пакета.
Преобразование типов данных
В том же файле у меня есть список имен столбцов, которые должны возвращаться из запросов, связанных с активами:
var assetColumns = []string{"symbol", "provider_id", "asset_name", "asset_type", "market", "locale", "currency_symbol", "currency_name", "base_currency_symbol", "base_currency_name", "delisted_utc", "primary_exchange", "cik", "composite_figi", "share_class_figi", "last_updated_utc", "download_enabled"}
Теперь предположим, что я хочу преобразовать это в одну строку с разделителями-запятыми. Может ли Copilot X сделать это? Да, оно может. Ниже этой строки я начал с ввода var assetColums = “”
. В этот момент второй пилот выдал следующий автозаполненный вывод:
var assetColumns = "symbol, provider_id, asset_name, asset_type, market, locale, currency_symbol, currency_name, base_currency_symbol, base_currency_name, delisted_utc, primary_exchange, cik, composite_figi, share_class_figi, last_updated_utc, download_enabled"
В целом Copilot смог выполнить эту задачу с минимальными подсказками, быстро сгенерировав решение, которое было синтаксически правильным и представляло те же данные. Однако я заметил, что в некоторых случаях, когда длина результирующей строки превышала нормы, установленные в другом месте файла, Copilot не включал символы новой строки.
Хотя это может не быть серьезной проблемой для небольших фрагментов кода, это потенциально может привести к проблемам с читаемостью в более крупных проектах, поэтому важно помнить об этом при использовании Copilot X для рефакторинга существующего кода.
Обновление существующего кода
Чтобы проверить способность Copilot X внедрять обновления пакетов, я попытался заменить необработанные строковые SQL-запросы запросами с использованием импортированного мной пакета ORM.
Во-первых, я вручную написал пример запроса, используя пакет ORM, чтобы дать Copilot X представление о том, что я пытался сделать.
Затем я попросил Copilot X преобразовать необработанный строковый SQL-запрос в код ORM. Это код, с которого я начал.
querier := func(offset int, size int) *Query { return NewQuery(fmt.Sprintf("SELECT %s FROM %s WHERE provider_id = ? AND symbol = ? AND sip_timestamp >= ? "+ "AND sip_timestamp < ? ORDER BY sip_timestamp LIMIT ? OFFSET ?", quoteColumns, client.fullName(Quotes)), provider, symbol, startTick, endTick, size, offset) }
После ввода querier := func(o
второй пилот X выдал следующий вывод с пятью вкладками и вводами.
querier := func(offset int, size int) *orm.Query { return orm.NewQuery().Select(quoteColumns...).From(client.fullName(Quotes)). Where(orm.And, orm.Equals("provider_id", provider), orm.Equals("symbol", symbol), orm.Equals("sip_timestamp", startTick), orm.Equals("sip_timestamp", endTick)). OrderBy("sip_timestamp").Limit(size).Offset(offset) }
Хотя вывод был частично правильным, было несколько ошибок компилятора и логических ошибок, которые необходимо было исправить перед запуском кода. Для справки, правильный код выглядел так:
querier := func(offset int, size int) *orm.Query { return orm.NewQuery().Select(quoteColumns...).From(client.fullName(Quotes)). Where(orm.And, orm.Equals("provider_id", provider, false), orm.Equals("symbol", symbol, false), orm.GreaterThanOrEqualTo("sip_timestamp", startTick, false), orm.LessThan("sip_timestamp", endTick, false)). OrderBy("sip_timestamp").Limit(size, false).Offset(offset, false) }
Устранив эти проблемы, я повторил попытку с другим запросом, и на этот раз Copilot X смог без проблем сгенерировать правильный код ORM.
Этот опыт показал мне, что Copilot X не полностью осведомлен о внешних пакетах или о том, как их можно использовать, но способен быстро изучить синтаксис и использование и даже сделать вывод о значении отдельных функций и переменных.
Фактически, позже я попытался преобразовать запрос, который включал вызов функции Having
в том же пакете ORM, и Copilot смог предвидеть существование этой функции и какие аргументы она будет принимать без моего участия.
Исправление критических изменений
Как разработчику, мне интересно посмотреть, может ли Copilot X автоматически исправлять ошибки компиляции или логические ошибки, вызванные нарушением изменений в обновлении пакета, и может ли он систематизировать исправление в нескольких файлах. Хотя Copilot — мощный инструмент для создания нового кода, неясно, распространяются ли эти возможности на исправление критических изменений в пакетах.
В этом разделе я проверю эту гипотезу, преднамеренно внеся критические изменения в пакет и посмотрев, сможет ли Copilot идентифицировать и предложить исправления для затронутого кода.
Это даст ценную информацию о возможностях и ограничениях инструмента и может помочь другим разработчикам принять обоснованное решение о том, как интегрировать его в свои рабочие процессы.
Чтобы проверить это, я добавил аргумент к интерфейсу и одну функцию, а затем импортировал обновленный пакет в свой основной модуль. При попытке предоставить постоянную строку для отсутствующего аргумента Copilot не смог предвидеть значение строки или предоставить какие-либо предложения после двух последовательных попыток в разных файлах.
Хотя размер выборки для этой операции невелик, он аналогичен скорости обучения, демонстрируемой вторым пилотом в других случаях. Это наводит меня на мысль, что либо второй пилот не способен предвидеть небольшие изменения такого рода без дополнительных примеров, либо сам тест мог быть слишком ограниченным, чтобы второй пилот мог показать свой истинный потенциал. При этом, вместо того, чтобы предлагать заведомо неправильные варианты, второй пилот вообще воздержался от каких-либо предложений. Хотя меня бы раздражало такое отсутствие подсказок от чего-то вроде Intellisense, я уважаю способность Copilot избегать предоставления ненужного вывода, когда ему не хватает уверенности в своих подсказках. Мне придется продолжить тестирование в этой конкретной области, чтобы точно определить, где он терпит неудачу и где дает полезные предложения.
Генерация тестов
В этом разделе я исследую способность Copilot генерировать тестовый код. В частности, я хочу определить, может ли он генерировать полные наборы тестов, делать выводы о том, как тесты должны быть структурированы на основе тестовых библиотек или фреймворков, и генерировать достоверные, реалистичные тестовые данные.
Кроме того, я хочу посмотреть, сможет ли Copilot найти крайние случаи или общие режимы отказа в зависимости от типа теста и определить ожидаемые ответы или значения сгенерированных данных. Изучая эти аспекты функциональности Copilot, я надеюсь лучше понять, как он может помочь в тестировании программного обеспечения и где могут лежать его ограничения.
С этой целью я попросил Copilot X попытаться сгенерировать модульные тесты для функции, которую мы сгенерировали ранее: GetAssetsForDate
. Чтобы избежать повторного использования кода и упростить тестирование, наши тесты полагаются на пакеты gomega и ginkgo для утверждений и структуры теста, и мы используем go-sqlmock, в частности, для тестирования кода SQL. Более того, набор этих тестов, к которому будут добавлены, содержит два типа: тесты для функций постраничного запроса и тесты для функций одиночного запроса.
Поскольку эта функция содержит одноэлементный SQL-запрос, тест должен иметь структуру, похожую на первую, а не на вторую. Кроме того, тесты включают несколько различных условий, включая условия неверных данных и условия ошибок SQL. Наконец, когда возникают ошибки, мы полагаемся на внутренний пакет для форматирования ошибки и отображения дополнительной информации, такой как среда, пакет, класс (если он доступен), функция, внутренняя ошибка и сообщение. Сможет ли Copilot сгенерировать тестовый код, соответствующий всем этим требованиям?
Чтобы сгенерировать тестовый код, я сначала написал комментарий, описывающий желаемое поведение. Затем второй пилот начал генерировать тестовый код, строка за строкой. Первоначально он пытался сгенерировать тест для функции постраничного запроса, вероятно, потому, что я работал над этим типом теста до написания этого поста. Тем не менее, он по-прежнему мог генерировать большую часть стандартного кода, который я в противном случае скопировал бы, и он создавал тестовые данные, которые соответствовали тому, что я использую в других тестах в том же файле. Хотя изначально он генерировал некоторые разные тестовые данные, он, возможно, узнал, что я склонен повторно использовать одни и те же данные в нескольких тестах для повышения эффективности.
Однако Copilot не удалось сгенерировать точный SQL-запрос, который функция отправила бы на сервер, но эти различия было легко исправить. При создании вызова функции Copilot пытался предоставить строковый аргумент для time.Time
вместо создания вызова time.Parse
или time.Date
. Кроме того, ему требовалась помощь в создании соответствующего кода проверки ошибок, но он смог узнать мои ожидания после нескольких итераций. Он даже генерировал правильные ошибки типа Go без моего участия, что было полезно. Внеся некоторые коррективы, я получил следующий код:
// Tests the conditions determining which errors are returned from the GetAssetsForDate function when it fails DescribeTable("GetAssetsForDate - Failures", func(queryFails bool, rowErr bool, scanFails bool, verifier func(*utils.GError)) { // First, setup the mock to return the rows svc, mock := createServiceMock("TEST_DATABASE", "TEST_SCHEMA") // Next, create the rows that will be returned by the query rows := mock.NewRows([]string{"symbol", "provider_id", "asset_name", "asset_type", "market", "locale", "currency_symbol", "currency_name", "base_currency_symbol", "base_currency_name", "delisted_utc", "primary_exchange", "cik", "composite_figi", "share_class_figi", "last_updated_utc", "download_enabled"}). AddRow("AAPL", 1, "Apple Inc.", 1, 4, 0, "USD", "", "", "", "", "XNAS", "0000320193", "BBG000B9XRY4", "BBG001S5N8V8", "2022-06-20T00:00:00Z", false). AddRow("AAP", 1, "ADVANCE AUTO PARTS INC", 1, 4, 0, "USD", "", "", "", "", "XNYS", "0001158449", "BBG000F7RCJ1", "BBG001SD2SB2", "2022-06-20T00:00:00Z", false). AddRow("AAUKF", 1, "ANGLO AMER PLC ORD.", 2, 5, 0, "USD", "", "", "", "", "", "", "", "", "2022-03-08T07:44:15.171Z", false). AddRow("BA", 1, "Boeing Company", 1, 4, 0, "USD", "", "", "", "", "XNYS", "0000012927", "BBG000BCSST7", "BBG001S5P0V3", "2022-06-20T00:00:00Z", false). AddRow("BABAF", 1, "ALIBABA GROUP HOLDING LTD", 2, 5, 0, "USD", "", "", "", "", "", "", "", "", "2021-02-11T06:00:50.792Z", false). AddRow("BAC", 1, "Bank of America Corporation", 0, 0, 0, "USD", "", "", "", "", "XNYS", "0000070858", "BBG000BCTLF6", "BBG001S5P0Y0", "2022-06-20T00:00:00Z", false) if rowErr { rows.RowError(0, fmt.Errorf("row error")) } else if scanFails { rows.AddRow("BAC", "derp", "Bank of America Corporation", 0, 0, 0, "USD", "", "", "", "", "XNYS", "0000070858", "BBG000BCTLF6", "BBG001S5P0Y0", "2022-06-20T00:00:00Z", true) } // Setup the mock so we can verify the SQL query when it is submitted to the server queryStmt := mock.ExpectQuery(regexp.QuoteMeta("SELECT symbol, provider_id, asset_name, "+ "asset_type, market, locale, currency_symbol, currency_name, base_currency_symbol, "+ "base_currency_name, delisted_utc, primary_exchange, cik, composite_figi, share_class_figi, "+ "last_updated_utc, download_enabled FROM TEST_SCHEMA.assets WHERE provider_id = ? AND "+ "last_updated_utc = ?")).WithArgs(gopb.Provider_Polygon, time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)) if queryFails { queryStmt.WillReturnError(fmt.Errorf("query error")) } else { queryStmt.WillReturnRows(rows) } // Finally, call the function and verify the error assets, err := svc.GetAssetsForDate(context.Background(), gopb.Provider_Polygon, time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC)) // Verify the error and the returned assets verifier(err.(*utils.GError)) Expect(assets).Should(BeEmpty()) Expect(mock.ExpectationsWereMet()).ShouldNot(HaveOccurred()) }, Entry("Query fails", true, false, false, testutils.ErrorVerifier("test", "snowplow", "/xefino/quantum-core/pkg/snowplow/assets.go", "Snowplow", "GetAssetsForDate", 86, testutils.InnerErrorVerifier("Failed to query data from "+ "the TEST_DATABASE.TEST_SCHEMA.assets table"), "query error", "[test] snowplow.Snowplow.GetAssetsForDate "+ " (/xefino/quantum-core/pkg/snowplow/assets.go 86): Failed to query data from the "+ "TEST_DATABASE.TEST_SCHEMA.assets table: query error")), Entry("rows.Err fails", false, true, false, testutils.ErrorVerifier("test", "snowplow", "/xefino/quantum-core/pkg/snowplow/assets.go", "Snowplow", "GetAssetsForDate", 86, testutils.InnerErrorVerifier("Failed to query data from "+ "the TEST_DATABASE.TEST_SCHEMA.assets table"), "row error", "[test] snowplow.Snowplow.GetAssetsForDate "+ " (/xefino/quantum-core/pkg/snowplow/assets.go 86): Failed to query data from the "+ "TEST_DATABASE.TEST_SCHEMA.assets table: row error")), Entry("rows.Scan fails", false, false, true, testutils.ErrorVerifier("test", "snowplow", "/xefino/quantum-core/pkg/snowplow/assets.go", "Snowplow", "GetAssetsForDate", 86, testutils.InnerErrorVerifier("Failed to query data from "+ "the TEST_DATABASE.TEST_SCHEMA.assets table, error: sql: Scan error on column index 1, name "+ "\"provider_id\": converting driver.Value type string (\"derp\") to a int64: invalid syntax"), "[test] snowplow.Snowplow.GetAssetsForDate (/xefino/quantum-core/pkg/snowplow/assets.go 86): "+ "Failed to query data from the TEST_DATABASE.TEST_SCHEMA.assets table, error: sql: Scan error "+ "on column index 1, name \"provider_id\": converting driver.Value type string (\"derp\") to a "+ "int64: invalid syntax")))
Хотя Copilot смог сгенерировать некоторые шаблоны для тестовой функции, он изо всех сил пытался вывести структуру теста на основе используемых библиотек и сред тестирования. Кроме того, у него были трудности с созданием реалистичных тестовых данных и поиском крайних случаев или общих режимов отказа.
Однако он смог извлечь уроки из предыдущих итераций и генерировать допустимые ошибки типа Go без ввода. В целом, несмотря на то, что Copilot показывает многообещающие результаты в создании тестового кода, он еще не является комплексным решением для создания тестов. Разработчики по-прежнему должны быть готовы вручную просмотреть и изменить сгенерированный код, чтобы обеспечить полное покрытие тестами.
Заключение
Copilot — это мощный инструмент, который может улучшить рабочий процесс кодирования опытных разработчиков путем создания нового кода или копирования шаблонов из существующего кода. Это особенно полезно, когда существует согласованный шаблон для существующего кода в одном и том же файле. Однако ему требуется несколько примеров, прежде чем он сможет сгенерировать что-либо, что должно интегрироваться с чем-либо еще. Copilot генерирует синтаксически правильный код примерно в 95% случаев, но у него все еще есть проблемы со ссылками на типы в разных файлах или в разных пакетах.
API по-прежнему довольно медленный (~ 500 мс на строку) и не особенно хорош для генерации тестов, которые будут проходить, хотя это может быть полезно с точки зрения TDD. Несмотря на свои ограничения, Copilot — полезный инструмент для опытных разработчиков, которые хотят работать более эффективно. Однако младшим разработчикам и новичкам в области компьютерных наук не следует полагаться на этот инструмент, поскольку он не может привить лучшие практики, характерные для языка, на котором он работает. В целом, я бы сказал, что тарификация Copilot как программиста пары ИИ примерно наполовину верна: он может генерировать код, пока вы рецензируете, но не способен выполнять роль рецензента.
Если вы старший разработчик и хотите оптимизировать свой рабочий процесс, я настоятельно рекомендую попробовать Copilot. Это может помочь вам быстро и эффективно создавать новый код и шаблоны. Однако имейте в виду, что это не замена хорошим практикам кодирования и не подходит для начинающих или младших разработчиков. По мере того, как ИИ и машинное обучение продолжают развиваться, мы обязательно увидим еще больше захватывающих разработок в этой области. Итак, оставайтесь любопытными, продолжайте учиться, и давайте посмотрим, что ждет в будущем ИИ и программирование!