Cursor - moje pierwsze wrażenia

Cursor – moje pierwsze wrażenia z programowania z asystentem AI

Opublikowano Kategorie AICzas czytania 11min

Materiały dotyczące Cursora od jakiegoś czasu wyskakują mi wręcz z lodówki. W obliczu jego popularności, jak również obecnych trendów, grzechem byłoby go nie przetestować. W tym artykule przedstawię Ci jak pracuję z Cursorem. Podzielę się z Tobą moimi spostrzeżeniami i wnioskami.

Jeśli jeszcze nie wiesz, czym jest Cursor, to spieszę z odpowiedzią. Cursor to środowisko programistyczne zintegrowane z możliwościami, jakie daje wykorzystanie AI. Cursor jest forkiem Visual Studio Code. Oznacza to, że jeśli do tej pory korzystasz z VSCode, to możesz w prosty sposób przenieść swoją konfigurację edytora do Cursora.

Jedną z najważniejszych funkcji Cursora jest uzupełnianie kodu. Polega na tym, że edytor wyświetla nam sugestię dokończenia kodu, który obecnie piszemy. By zaakceptować sugestię, wystarczy nacisnąć Tab. Zasada działania jest analogiczna do działania np. GitHub Copilot.

Druga z kluczowych funkcji to czat. Zasada działania jest analogiczna do działania innych „inteligentnych” czatów pokroju Chatu GPT. Dodatkowo mamy możliwość dołączenia plików, ich fragmentów oraz dodatkowych pytań lub instrukcji. Interakcja z czatem pozwala również na wprowadzanie bezpośrednich sugestii w kodzie.

Jak korzystam z Cursora?

Absolutnie nie uważam się za power usera Cursora. Możliwe (i całkiem prawdopodobne), że idzie to robić lepiej, wydajniej. Sam wciąż się tego uczę. Jednak opisany sposób pracy pozwala mi osiągnąć zadowalające efekty.

Jeśli po przeczytaniu artykułu uznasz, że coś mógłbym robić lepiej, to zachęcam by dać o tym znać w komentarzu. Będzie to z korzyścią dla mnie i pozostałych czytelników 😉

Całość komunikacji z asystentem przeprowadzam w języku angielskim. Dla wygody czytania artykułu polecenia czy konfiguracje przedstawione w artykule są w języku polskim.

Do pracy z Cursorem w momencie tworzenia tego artykułu wykorzystywałem głównie modele Claude 3.5 Sonnet oraz Claude 3.7 Sonnet. To właśnie z nimi otrzymuję najlepsze rezultaty. Testowałem również kilka innych modeli, ale rezultaty były w moim odczuciu nieco gorsze. Cursor pozwala na zdefiniowanie reguł, które mają zostać wykorzystane przy generowaniu kodu.

Co działa dobrze a co niekoniecznie?

Moja główna obserwacja jest taka, że im mniej zmuszamy asystenta do myślenia, tym lepiej. Z moich obserwacji wynika, że LLM-y lubią proste i jednoznaczne instrukcje. Na przykład:

  • „Bazując na kodzie z pliku UserAddedEventHandler, w osobnym pliku zaimplementuj klasę UserRemovedEventHandler. Wykorzystaj do implementacji UserRemovedEvent.”
  • „Stwórz klasę InMemoryUsersRepository implementującą interfejs IUsersRepository. Obiekty klasy User przechowaj w mapie.”
  • „Na podstawie podanego przykładu testów jednostkowych zaimplementuj testy dla klasy InMemoryUsersRepository.”
  • „Przygotuj funkcję groupByCustomerId, która pogrupuje obiekty AbandonedCarts po customerId.”

Do podanych poleceń dołączam wspomniane pliki i fragmenty kodu. Podane zadania są oczywiście proste. Jednak jest to oszczędność czasu, który można spożytkować w lepszy sposób.

Moje obserwacje potwierdzają, że dołączenie przykładowego kodu lub plików jest kluczowe. Bez podawania przykładów i dołączania plików obserwowałem dużo gorsze rezultaty. Instrukcje bez przykładów i ze zbyt ogólnymi wytycznymi prowadziły do niezadowalających rezultatów lub halucynacji.

Warto również rozbijać polecenia na mniejsze i używać prostych zdań. Moje obserwacje wskazują, że polecenie „Przygotuj funkcję groupByCustomerId, która pogrupuje obiekty AbandonedCarts, posortuje po id Customera i odfitruje puste koszyki” może dać gorszy rezultat niż rozbicie tego na kilka osobnych. Na przykład:

  • „Przygotuj funkcję groupByCustomerId, która pogrupuje obiekty AbandonedCarts po customerId.”;
  • „Odfiltruj obiekty, gdzie items.length wynosi 0″;
  • „Posortuj klucze obiektu wynikowego rosnąco.”.

Podobną analogię dostrzegam w funkcji czatu. Zaobserwowałem, że krótko żyjące okna czatu działają efektywniej niż korzystanie cały czas z jednej konwersacji. Zauważyłem, że długie konwersacje lub takie, gdzie próbuję rozwiązać więcej niż jeden problem, nie działają dobrze. AI ma wtedy skłonność do powielania błędów, halucynacji lub zwracania odpowiedzi, które nie odpowiadają oczekiwaniom.

Podobną strategię jak przy pisaniu poleceń, przyjąłbym w definiowaniu reguł w konfiguracji Cursora. Celowałbym raczej w specyficzne reguły, które mają spełniać kod. Na przykład zamiast „twórz funkcje zgodne z SOLID, KISS i DRY” dodałbym bardziej precyzyjne instrukcje odpowiadające moim preferencjom:

  • „Funkcje nie powinny przyjmować więcej niż 4 parametry”;
  • „Preferuj obiekty z parametrami funkcji nad listą parametrów”;
  • „Unikaj funkcji modyfikujących dane wejściowe”.

Warto jednak mieć na uwadze, że przy zbyt dużej liczbie instrukcji możemy dostać efekt odwrotny od zamierzonego, wynikający z wysycenia okna kontekstowego.

We wszystkich przedstawionych przykładach zmiany dotyczyły małych fragmentów kodu tzn. funkcji, klasy a maksymalnie pliku. Zaobserwowałem, że im mniejszego fragmentu dotyczy zmiana, tym lepsze rezultaty otrzymywałem. Prowadząc asystenta za rączkę, zwykle robił to, co chciałem.

Zaskakująco dobrze AI sobie radzi z jednym z najtrudniejszych zadań programistycznych… czyli nazewnictwem. Często posiłkuję się AI, jeśli nie mam pomysłu na nazwanie czegoś w kodzie. Zwykle proszę o wygenerowanie kilku propozycji i wśród nich znajduję jakąś, która mnie zadowala. Sporadycznie potrzebuję kilku kolejnych propozycji.

Dobre efekty uzyskiwałem również po napisaniu logiki dla wzorca i rozpoczęciu kodowania dla kolejnych komponentów polegających na tych samych schematach. Przykładowo, po implementacji UserAddedEventHandler zabrałem się do implementacji UserRemovedEventHandler. Jej implementacja sprowadzała się do „Tab Tab Tab” i drobnych poprawek stylistycznych. Podobnie dobrze zadziałało przepisywanie testów na inny framework – „Tab Tab Tab” i dodanie drobnych poprawek. Przy zadaniach przypisywania kodu z podejścia X na Y zalecam, by upewnić się, że asystent nic nie zgubił po drodze. W moim przypadku przy przepisywaniu testów potrafił „zgubić” kilka dość istotnych asercji 😉

Dokumentacja

Tworzenie dokumentacji czy README z wykorzystaniem Cursora też daje radę! Asystent „wypluwa” poprawny tekst zarówno pod kątem merytorycznym, jak i językowym. Zwykle przy tworzeniu takich dokumentów najpierw tworzę inicjalną wersję dokumentacji, a następnie dołączam plik lub fragment tekstu i proszę model o przeredagowanie go zgodnie z podanymi wytycznymi.

Debuggowanie

Zaskakująco dobrze radzi sobie również przy debuggowaniu prostych błędów. Z moich obserwacji wynika, że kluczowe jest podanie stanu obecnego lub treści błędu. Równie ważne jest podanie oczekiwanego rezultatu.

Przykładowo niedawno miałem sytuację, gdzie aplikacja nie łączyła się poprawnie do lokalnej instancji Redisa. Całość działała na kontenerach i jedyne co dostawałem to błąd error: connect ECONNREFUSED 127.0.0.1:6380. Cursor najpierw przeprowadził mnie za rączkę przez diagnostykę czy kontener z serwerem Redisa istnieje, czy odpowiada. Następnie przeanalizował konfigurację aplikacji. Praktycznie od razu wykrył błąd konfiguracji klienta Redisa oraz sieci, w jakiej działały kontenery.

Równie dobrze Cursor poradził sobie w innych sytuacjach, gdzie rozwiązanie było proste do wdrożenia jednak niełatwe do znalezienia:

  • brakująca właściwość obiektu wynikająca z braku jej przypisania w kodzie;
  • błąd w funkcji sortującej;
  • poprawa błędnych asercji i opisów w testach jednostkowych;
  • (zasłyszany przykład) – optymalizacja zapytania bazując na kodzie migracji bazy danych, zapytaniu i rezultacie polecenia EXPLAIN.

Prototypowanie

Cursor to dla mnie gamechanger, jeśli chodzi o prototypowanie. Jeśli zależy nam na walidacji pomysłu czy napisaniu kodu, który ma działać, a niekoniecznie wyglądać, Cursor sprawdzi się świetnie. Potrzebowałem napisać prostą aplikację do weryfikacji pewnych założeń, która po spełnieniu swojego zadania miała polecieć do kosza. Zwykle zajmowało mi to jakieś 20-30 minut, z czego połowa to szukanie po code snippetach brakujących klocków. Cursor zrobił to samo w jakieś 20-30…sekund 🤯. Oczywiście jakość kodu była dyskusyjna, ale w tym przypadku mi na tym nie zależało.

We trained copilot on your code - that's why it sucks. sama prawda

Co według mnie nie działa najlepiej?

Znacznie gorzej Cursor poradził sobie z debuggowaniem bardziej złożonych problemów. Całkowicie wyłożył się na błędzie będącym wynikiem wykorzystania zależności wykorzystywanej w kodzie. Po wstępnej próbie zrozumienia problemu asystent zaczął halucynować i produkować, mówiąc wprost, bzdury. Podobnie nie poradził sobie w debuggowaniu problemów związanymi z wyciekami pamięci. Tu również skończyło się na halucynacjach.

W pracy z Cursorem jest to widoczne, że asystentowi brakuje rzeczywistego zrozumienia problemu i polega na poznanych schematach i wzorcach. Jest to dość oczywiste, jeśli wiemy czym są i jak działają LLM-y. Moja efektywność pracy z Cursorem drastycznie spadła, gdy zacząłem od niego wymagać wnioskowania. Podobnie nie dał sobie rady, gdy w grę wchodziło wykorzystanie metod i klas z zależności, których Cursor nie miał jeszcze okazji użyć. Było widać, że podejmuje próby przewidzenia tego, co robię. Jednak całkowicie zgadywał np. nazwy metod, typów czy klas. Na szczęście okazało się, że wystarczy dołączyć pliki zależności do czatu, co rozwiązuje problem.

Podobnie źle poradził sobie, gdy wrzuciłem go na żywioł i poprosiłem o napisanie zestawu testów, bez podania mu przykładów. Wyprodukowany rezultat nie pasował do żadnego znanego mi frameworka do testów w JavaScript. Same przypadki testowe również nie były zbyt sensowne. Wyglądało to, jakby asystentowi zależało wyłącznie na pokryciu wszystkich możliwych ścieżek testowych. Wskazywały na to zarówno opisy, jak i sama konstrukcja testów.

Są również chwile, gdzie Cursor wręcz przeszkadza. Są to momenty, gdy „wiem co robię”. Przeprowadzałem refaktoryzację dość skomplikowanego kawałka kodu. Liczba reguł biznesowych i zależności między poszczególnymi składowymi kodu była ogromna. Moim celem było zredukowanie couplingu, zwiększenie czytelności, reużycie pewnych fragmentów kodu oraz dodanie prostej funkcjonalności. Jakieś 80% sugestii Cursora próbowało władować mnie na minę. Fakt polegania na schematach i brak wiedzy o tym, co biznesowo robi refaktoryzowany kod, kończył się tym, że większość sugestii tylko zaśmiecała mi obszar roboczy edytora. Bardzo widoczne było podejście „wstaw w to miejsce kod, który ma największe prawdopodobieństwo bycia poprawnym w tej sytuacji”. Opisaną sytuację idealnie opisuje poniższy mem.

Mem o działaniu Excela - niezrozumienie koncepcji miesięcy
Cursor też jest do tego zdolny

Podsumowanie

Moja pierwsza styczność z AI w kontekście asystenta programowania sprowadzała się do wykorzystania ChatGPT oraz GitHub Copilot w formie rozszerzenia do VSCode. Wykorzystując te dwa narzędzia, nie widziałem sporego wzrostu mojej wydajności. Oczywiście było widać różnice, ale nie na tyle duże, by jakoś szczególnie się tym zachwycać.

Cursor w mojej opinii to inna liga. Tutaj widzę zauważalną różnicę na plus. Czy to jest już ten moment, gdzie „AI ZASTĄPI PROGRAMISTÓW”? Moim zdaniem zdecydowanie nie. Cursor zdecydowanie przyspiesza i ułatwia moją pracę. Jednak by efektywnie z niego korzystać nadal trzeba mieć wiedzę i umiejętności. Trzeba również przećwiczyć samą pracę z Cursorem. Narzędzie ma również duży potencjał do rozleniwiania. Jeśli już programujemy w „Tab Tab Tab Driven Development”, to sprawdzajmy i starajmy się zrozumieć, co zostało wygenerowane.

Szukając metafory, porównałbym programowanie w Cursorze i tradycyjnym IDE do wyznaczania trasy z nawigacją GPS i papierową mapą. Nawigacja jest dużo wygodniejsza i ma dużo większe możliwości. Jednak nie sprawia ona magicznie, że nie potrzebujemy umiejętności prowadzenia pojazdów, zasad ruchu drogowego itp. Nieumiejętnie wykorzystana nawigacja również może wyprowadzić nas w pole. Dosłownie i w przenośni 😉

Zachęcam do podzielenia się w komentarzu, jakie są Twoje wrażenia z wykorzystania Cursora? Chętnie dowiem się jakie techniki pracy z Cursorem wykorzystujesz. Jeśli coś Cię blokuje przed jego przetestowaniem, to również jestem ciekaw powodów.

Materiały dodatkowe

Dominik Szczepaniak

Zawodowo Senior Software Engineer w CKSource. Prywatnie bloger, fan włoskiej kuchni, miłośnik jazdy na rowerze i treningu siłowego.

Inne wpisy, które mogą Cię zainteresować

Przygotuj się lepiej do rozmowy o pracę!

Odbierz darmowy egzemplarz e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer i realnie zwiększ swoje szanse na rozmowie rekrutacyjnej! Będziesz też otrzymywać wartościowe treści i powiadomienia o nowych wpisach na skrzynkę e-mail.

Dlaczego warto?

  • 👉 Ponad 1000 pobrań e-booka!
  • 👉 60 stron pełnych pytań i zadań praktycznych. Pytania i zadania pochodzą z faktycznych procesów rekrutacyjnych.

E-booka odbierzesz korzystając z formularza poniżej 👇

Okładka e-booka - Kolejna książka o Gicie

Subscribe
Powiadom o
guest

2 komentarzy
oceniany
najnowszy najstarszy
Inline Feedbacks
View all comments
Wojtek
12 godzin temu

Mam bardzo podobne doświadczenia oraz przemyślenia. Jeszcze jesteśmy bardzo daleko od całkowitego zastąpienia pracy programisty przez AI. Również stworzenie względnie prostej aplikacji za pomocą Cursora, czy innego narzędzia nie jest takie szybkie i proste, jak niektórzy piszą, że 15 minut, dwa prompty, tab, tab, tab i gotowe 😉

Ostatnio w ramach testów i zabawy tworzę dla siebie dosyć proste narzędzie tylko przy pomocy AI. Okazuje się, że wymaga to całkiem sporo pracy z mojej strony i nie jest takie kolorowe 😉 Polecam regularne testowanie tego, co wypluwa AI, bo często na pierwszy rzut oka wszystko wygląda OK, ale gdzieś tam siedzi jakiś drobny błąd. Najlepiej pracować z AI tak, jak piszesz, przez korzystanie z pseudo kodu i wydawanie bardzo dokładnych poleceń. Wtedy naprawdę AI działa super.
Ostatnio miałem taką sytuację, że chciałem wyciągnąć z (trochę bardziej zagnieżdżonego) JSONa kilka danych. Bardzo precyzyjnie opisałem o co mi chodzi, jaki jest cel, czego potrzebuję, dałem przykładowy plik i żaden model nie był w stanie tego zrobić nawet po kilku iteracjach. Dopiero sam musiałem rozwikłać wszystkie pola (nie było do tego żadnej dokumentacji), gdzie znajdują konkretne wartości, podać dokładną lokalizację, gdzie znajdują się dane, które chce i dopiero wtedy uzyskałem poprawny wynik. Myślałem, że najnowsze modele ogarniają już takie stosunkowo proste rzeczy, ale jak widać, jest jeszcze miejsce na wkład ze strony człowiek :p