Wszystkie wpisy

GitHub Copilot technical preview - pierwsze wrażenia w VS Code
GitHub Copilot technical preview - pierwsze wrażenia w VS Code

English

GitHub Copilot technical preview - pierwsze wrażenia w VS Code

Luty 2022: Copilot nadal jest technical preview (waitlista, za darmo). Ghost text w VS Code, Tab i Esc, recenzja sugestii. Nie czat i nie GA.

Szary tekst, którego wcześniej nie było

Od końca czerwca zeszłego roku krąży w mojej bańce ta sama historia: GitHub pokazał narzędzie, które nazwał “AI pair programmer”, część znajomych dostała dostęp, reszta nadal siedzi na liście oczekujących. Wpisałem się na tę listę w tygodniu ogłoszenia i po swoim czekaniu dostałem zaproszenie. Od kilku tygodni mam więc w VS Code coś, czego wcześniej tam nie było: szary tekst, który pojawia się przede mną w trakcie pisania i proponuje resztę linii albo całą funkcję.

Piszę to wprost na starcie, bo w lutym 2022 łatwo o nieporozumienie. Copilot nie wystartował w tym miesiącu. Ogłoszenie technical preview jest z 29 czerwca 2021 i od tamtej pory to jest ta sama rzecz: ograniczony preview, waitlista, darmowy dostęp dla tych, którzy się przez nią przebiją. Nie ma premiery, nie ma cennika, nie ma pudełka. Jest narzędzie w fazie testów, do którego dostaje się część chętnych.

Ten wpis jest o tym, jak to postawiłem u siebie i co z tego wynikło po kilku tygodniach codziennego klepania kodu. Nie jest o tym, że zaraz przestanę pisać testy. Trochę jak w podsumowaniu zeszłego roku, gdzie modele językowe zamknąłem w kategorii “eksperyment przy biurku”, tak i tutaj: opisuję narzędzie, którego używam, a nie zmianę procesu, którą właśnie wprowadziłem.

Czym jest Copilot w lutym 2022

Najkrócej: to rozszerzenie do edytora, które podpowiada kod w miejscu, w którym stoi kursor. Nie ma okna rozmowy. Nie zadaje się mu pytań i nie dostaje się odpowiedzi w akapitach. Kontekst bierze z tego, co ma pod ręką, czyli z bieżącego pliku: z importów, z nazw klas, z sygnatury metody, którą właśnie zacząłem, i z komentarza, który napisałem linijkę wyżej. Na tej podstawie proponuje ciąg dalszy, który widzę jako wyszarzony tekst wpleciony w edytor.

Pod spodem pracuje model OpenAI o nazwie Codex. GitHub i OpenAI mówią o tym od czerwca, a w sierpniu 2021 OpenAI opisało Codex osobno; jest też publikacja opisująca ewaluację tej rodziny modeli na zadaniach programistycznych. Dla mnie jako użytkownika to jest jednak szczegół techniczny. W praktyce nie widzę modelu ani jego parametrów. Widzę szary tekst i decyduję, czy go chcę.

Na stronie preview jest lista języków, w których to działa szczególnie dobrze: Python, JavaScript, TypeScript, Ruby, Java i Go. C#, którym płacę rachunki, na tej liście nie stoi na pierwszym miejscu, ale od aktualizacji z października 2021 dostaje wieloliniowe podpowiedzi na równi z Javą i C++. Krótko mówiąc: dema marketingowe nie są kręcone na NUnicie, a mimo to w moim kodzie testowym coś się dzieje. Jak dużo, o tym dalej.

Warto od razu odsunąć dwa nieporozumienia, które słyszę najczęściej. Copilot nie jest wyszukiwarką po Stack Overflow, która wkleja znaleziony wynik. Nie jest też linterem ani generatorem z szablonu, bo szablon jest deterministyczny, a to nie. To jest model, który przewiduje ciąg dalszy tekstu, tyle że tekstem jest tu kod.

Dostęp i cena, czyli waitlista i telemetria

Pierwszy krok nie jest techniczny. Pierwszym krokiem jest formularz na copilot.github.com i czekanie. Liczba miejsc w preview jest ograniczona i GitHub wpuszcza ludzi partiami. Nie umiem powiedzieć, ile się czeka, bo nie ma na to opublikowanej reguły; ja czekałem swoje. Piszę o tym w drugiej sekcji, a nie w przypisie, bo widziałem już kilka poradników, które zaczynają od “zainstaluj rozszerzenie”, a instalacja bez dostępu daje tylko prośbę o zalogowanie się i komunikat, że konto nie ma włączonego preview.

Cena na dziś: preview jest darmowy dla osób z dostępem. Nie ma abonamentu, nie ma faktury, nie ma karty do wpisania. GitHub mówi wprost, że to faza testów, a nie produkt komercyjny, i że model biznesowy dopiero powstanie. To jest plan na przyszłość, nie fakt lutego, więc jeżeli ktoś planuje budżet zespołu na ten rok, nie ma dziś czego wpisać w tabelkę.

Trzecia rzecz jest ważniejsza od dwóch poprzednich i to ona decyduje, czy w ogóle wolno tego włączyć w pracy. Copilot działa w chmurze. Żeby dostać podpowiedź, fragment mojego pliku wychodzi z edytora do usługi GitHuba. Do tego dochodzi telemetria: usługa zapisuje, które sugestie akceptuję, a które odrzucam, żeby model i produkt dało się ulepszać. Warunki telemetrii akceptuje się przy włączeniu rozszerzenia i nie ma tam trybu “działa lokalnie, nic nie wychodzi”.

W dokumentacji preview GitHub odpowiada na dwa pytania, które ludzie zadają od razu. Czy mój prywatny kod stanie się czyjąś sugestią? FAQ mówi, że nie. Czyj jest kod, który zaakceptowałem? Mój, tak jak każdy kod, który wpisałem do swojego pliku. Ja te odpowiedzi przyjmuję do wiadomości i mimo to trzymam się prostszej zasady, bo ona nie zależy od czyjegoś FAQ: Copilota włączam na swoim, publicznym scratchu i tam, gdzie mam na to jasną zgodę. Nie włączam go z rozpędu w repozytorium klienta, bo to nie jest moja decyzja do podjęcia w pojedynkę.

Setup w VS Code krok po kroku

Cała reszta wpisu dzieje się w VS Code, w wersji 1.64.2 z 10 lutego. Wybieram VS Code, bo to jest środowisko z ogłoszenia i nadal najlepiej ograne. Oficjalnie działa też plugin do Neovima (github/copilot.vim), rodzina JetBrains (IntelliJ, PyCharm, Android Studio) i Codespaces. Rozszerzenia do Visual Studio, tego dużego IDE od Microsoftu, na dziś nie ma; jest zapowiedź w publicznym roadmapie GitHuba i to jest zapowiedź, a nie coś, co dzisiaj zainstaluję. Jeżeli ktoś siedzi w Visual Studio 2022, to na razie zostaje przy przeglądaniu cudzych zrzutów ekranu.

Krok pierwszy: konto GitHub z włączonym dostępem do preview. Sprawdzić można w ustawieniach konta, w sekcji Copilot, która pojawia się dopiero po zaproszeniu.

Krok drugi: rozszerzenie. Z palety w edytorze albo z linii poleceń:

$ code --install-extension GitHub.copilot

Krok trzeci: logowanie. Po pierwszym uruchomieniu rozszerzenie prosi o autoryzację przez OAuth, otwiera przeglądarkę i wraca z tokenem. Na pasku stanu pojawia się ikona Copilota. Ikona przekreślona oznacza, że sugestie są wyłączone dla tego pliku albo globalnie, i to jest ten sam przełącznik, którego używam, gdy nagrywam ekran albo prowadzę warsztat.

Krok czwarty, mój własny i nie ma go w żadnej instrukcji GitHuba: osobne, puste repozytorium do zabawy. Zwykły katalog z jednym projektem konsolowym i jednym plikiem testowym. Wszystko, co niżej opisuję, robiłem tam, na wymyślonych danych. Ma to dwie zalety. Nic prawdziwego nie wychodzi do zewnętrznej usługi, a ja widzę sugestie w czystym kontekście, bez wpływu dziesięciu tysięcy linii cudzego kodu.

Warto uprzedzić jedno rozczarowanie. Copilot w pustym pliku, bez importów i bez nazw, jest znacznie słabszy niż w pliku, który już coś zawiera. Kiedy otwieram klasę testową w suite, o której pisałem przy refaktorze do Page Object Model, podpowiedzi trafiają dużo lepiej, bo model widzi konwencje nazw i to, jak wyglądają sąsiednie metody. Kontekst jest paliwem tego narzędzia.

Pętla accept i reject

Cała obsługa sprowadza się do kilku klawiszy i to jest chyba najlepsza rzecz w tym produkcie.

Piszę kod normalnie. W momencie, w którym model ma propozycję, pojawia się szary tekst za kursorem. Tab akceptuje go w całości. Esc odrzuca i szary tekst znika bez śladu. Jeżeli propozycja mi się nie podoba, ale kierunek jest dobry, przechodzę do kolejnych wariantów przez Alt+] i Alt+[ (na macOS Option). Jest też panel z listą alternatyw pod Ctrl+Enter, który otwiera osobną kartę z kilkoma propozycjami naraz. Używam go rzadko, bo wybija mnie z rytmu pisania, ale przy dłuższej funkcji potrafi pokazać, że model ma dwa zupełnie różne pomysły na to samo.

Kontekst podaję na dwa sposoby. Pierwszy to sygnatura, czyli po prostu zaczynam pisać metodę i czekam:

public class OrderTotalTests
{
    [Test]
    public void Total_WithSingleItem_ReturnsItemPrice()
    {

W tym miejscu Copilot najczęściej dopisuje treść testu w układzie arrange, act, assert, zgodnym z tym, co widzi w sąsiednich metodach. Nazwa testu jest tu instrukcją, więc im bardziej opisowa nazwa, tym mniej zgaduje.

Drugi sposób to komentarz, czyli piszę zdaniem, czego chcę:

// zbuduj koszyk z trzema pozycjami po 19.99 i policz sumę z rabatem 10 procent

Po zejściu do nowej linii dostaję propozycję kodu. Tu zaczyna się część, o której trzeba mówić głośno: propozycja bywa dobra i bywa wymyślona. Model zaproponował mi kiedyś w tym miejscu metodę ApplyDiscount, której w moim projekcie nie ma, bo nazwaliśmy ją inaczej. Kod wyglądał wiarygodnie i nie kompilował się.

Stąd jedyna zasada, którą trzymam bez wyjątków. Każda zaakceptowana sugestia jest cudzym diffem i przechodzi tę samą recenzję, co diff od człowieka. Czytam ją, zanim wcisnę Tab, i czytam jeszcze raz, zanim zrobię commit. Tab nie oznacza “zgadzam się”, tylko “wklej mi to do pliku, przyjrzę się”.

Co działa, a co jest śmieciem

Zacznę od tego, co wychodzi dobrze, bo wychodzi.

Boilerplate. Konstruktor przypisujący pola, mapowanie DTO na model, zestaw using, budowanie obiektu testowego z kilkoma polami, kolejny przypadek w TestCase różniący się jedną wartością. Wszędzie tam, gdzie kod jest powtarzalny i przewidywalny, model trafia i oszczędza mi realne minuty. Najlepiej widać to, gdy w pliku istnieje już jeden przykład danego wzorca. Wtedy drugi i trzeci powstają prawie same.

Nazwy i drobne uzupełnienia. Copilot dobrze zgaduje, jak nazwać zmienną i jak dokończyć rozpoczętą linię. Traktuję to jak lepsze uzupełnianie kodu, a nie jak nową kategorię narzędzia, i w tym trybie działa bardzo przyzwoicie.

Teraz lista rzeczy, które psują dzień, jeżeli się ich nie pilnuje.

Kod, który się nie kompiluje. Wspomniana metoda, której nie ma. Przeciążenie z inną liczbą argumentów. Zwrot innego typu niż deklarowany. To wychodzi natychmiast i jest najmniej groźne z całej listy, bo kompilator krzyczy.

Stare API. Model widział dużo kodu i część z tego jest sprzed lat. Dostaję propozycje z wzorcami, których w .NET 6 już się nie używa, albo z wywołaniami synchronicznymi tam, gdzie mam całą ścieżkę asynchroniczną. To się kompiluje, więc trzeba to zauważyć okiem.

Dane wyglądające jak prawdziwe. Przy generowaniu przykładowych rekordów potrafią wypaść adresy e-mail w prawdziwie wyglądających domenach i numery, które wyglądają jak dane osobowe. Nie wkładam takich rzeczy do repozytorium. W fixtures trzymam się domen dokumentacyjnych, dokładnie tak samo jak przy eksperymencie z generowaniem danych.

Bezpieczeństwo. W sierpniu 2021 badacze z NYU opublikowali pracę, w której przepuścili sugestie Copilota przez zestaw scenariuszy zbudowanych wokół typowych klas podatności. Sporo wygenerowanych programów zawierało lukę: brak walidacji wejścia, zła obsługa danych z zewnątrz, słabe użycie kryptografii. To nie jest zarzut wobec narzędzia, tylko opis tego, czym ono jest. Model proponuje kod podobny do tego, który widział, a kod, który widział, bywa dziurawy.

I rzecz, o której warto wiedzieć zawczasu. GitHub opublikował 30 czerwca 2021 własne badanie recytacji, czyli tego, jak często Copilot wypluwa fragment powtarzający istniejący kod dosłownie. Wyszło im rzędu 0.1 procent przypadków, przy czym rozkład nie jest równomierny: recytacja zdarza się częściej, gdy plik jest pusty i model nie ma się czego złapać, a prawie nie zdarza się w środku prawdziwego kodu z kontekstem. Ważne dla mnie jest zdanie o filtrze. W tym badaniu GitHub pisze, że mechanizm wykrywania takich powtórzeń nie jest jeszcze wpięty w technical preview, a w dokumentacji z tego roku nadal czytam, że filtr powstaje. Czyli: dziś to jest moja odpowiedzialność, nie funkcja produktu.

Ostatnia rzecz z FAQ, którą powtarzam na każdym spotkaniu: Copilot nie uruchamia ani nie testuje kodu, który proponuje. Nie ma pojęcia, czy to działa. To jest maszyna do pisania prawdopodobnego tekstu, a nie do sprawdzania, czy tekst jest prawdziwy.

To nie jest to samo co Completions

W czerwcu opisywałem eksperyment z generowaniem danych testowych przez API Completions i widzę, że ludzie wrzucają obie rzeczy do jednego worka pod hasłem “modele językowe”. Warto je rozdzielić, bo to są dwa różne narzędzia do dwóch różnych zadań.

Tamto było wywołaniem HTTP. Pisałem POST na endpoint z davinci w adresie, budowałem prompt few-shot z kilkoma przykładowymi obiektami JSON, ustawiałem max_tokens, temperature i stop, dostawałem tekst w odpowiedzi, walidowałem go w Pythonie i zatwierdzony wynik lądował w pliku w repozytorium. Kontrolowałem każdy parametr, płaciłem za tokeny i sam decydowałem, kiedy wywołanie się dzieje. Efektem były dane, nie kod. Nawiasem: waitlista na to API została zdjęta w listopadzie, więc akurat tam bariera wejścia zniknęła. W Copilocie została.

Copilot jest po drugiej stronie. Nie ma promptu, który sam piszę i wersjonuję, bo promptem jest mój otwarty plik. Nie ma parametrów do ustawienia. Nie ma odpowiedzi w JSON-ie, którą mogę zwalidować schematem, bo odpowiedzią jest tekst wstawiony wprost do edytora. Nie ma Completion.create w moim kodzie i nie ma klucza API w zmiennej środowiskowej. Jest szary tekst i dwa klawisze.

Konsekwencja praktyczna jest taka, że te dwa narzędzia mają zupełnie inne miejsce w procesie. Wynik z Completions jest artefaktem: zapisuję go, wersjonuję, ktoś go recenzuje w pull requeście i test go czyta z dysku. Wynik z Copilota nie jest artefaktem, tylko sposobem, w jaki powstał mój kod. Po zaakceptowaniu jest nie do odróżnienia od tego, co napisałem palcami, i właśnie dlatego recenzja przed Tab jest tak ważna. Nie ma tu drugiego miejsca, w którym błąd się zatrzyma.

Podsumowanie

Po kilku tygodniach mam prosty wniosek. To jest narzędzie do szybszego pisania kodu, który i tak przechodzi recenzję. Nie jest źródłem prawdy o tym, czy kod działa, i nie zastępuje żadnego kroku, który mam w procesie. Kompilator, testy, przegląd kodu i ktoś drugi, kto na to patrzy, zostają dokładnie tam, gdzie były.

Realnie zyskuję na powtarzalnym boilerplacie i na dokańczaniu linii w pliku, który ma już kontekst. Realnie tracę czas wtedy, gdy uwierzę szaremu tekstowi bez czytania, bo debugowanie wymyślonej metody trwa dłużej niż napisanie jej samemu. Bilans po kilku tygodniach mam na plus, ale nie jest to plus, który zmienia sposób pracy zespołu. Jest to plus, który zmienia tempo pisania jednej osoby.

Trzy rzeczy powtórzę na koniec, bo są najczęściej mylone. Copilot w lutym 2022 jest technical preview za waitlistą, ogłoszonym w czerwcu 2021, i nie jest to premiera tego miesiąca. Jest darmowy dla osób z dostępem, a cennik to plan na przyszłość, nie fakt. I jest to podpowiadanie w edytorze, a nie rozmowa: nie ma okna, w którym pytam narzędzie o cokolwiek.

Zostaje mi za to pytanie, które chodzi za mną od pierwszego dnia z tym rozszerzeniem: czy da się tym sensownie przyspieszyć szkielety testów. Nie treść asercji, bo tę muszę znać sam, ale całą powtarzalną otoczkę wokół niej - klasę testową, przypadki parametryzowane, konfigurację. Chcę to sprawdzić porządnie, na jednej konkretnej suite, i opisać osobno.

Zanim jednak zacznę cokolwiek przestawiać, wracam do pytania, które zadaję przy każdym nowym narzędziu: gdzie to się mieści w całym procesie testowym. Szybsze pisanie kodu nie naprawia procesu. Sprawia najwyżej, że szybciej dochodzę do miejsca, w którym widać, co w nim nie działa.