Десктопное приложение с нуля на .Net и C# с помощью ChatGPT

Говорят, что нейросети скоро заменят программистов, можно будет просто ставить им задачи и они будут сами писать код. Мне захотелось проверить, насколько это возможно уже сейчас. В своей работе я довольно часто использую ChatGPT (далее в тексте буду называть его «чгпт») и по опыту знаю, что его ответы далеко не всегда адекватные, особенно если задача нетривиальная. Но поскольку я задаю ему вопросы, касающиеся моей профессиональной области (я фронтендер), то ещё на этапе чтения его ответа я могу понять, где он прогнал и уточнить какие-то моменты. Порой даже я могу просто уловить его идею и дальше не мучать расспросами, а докрутить его решение самостоятельно.

Мне стало интересно, каково всё-таки писать приложение с помощью нейросети с нуля (и возможно ли это вообще, при условии, что ты не программист и не сможешь увидеть даже самые простые ошибки в коде), поэтому для максимальной чистоты эксперимента я выбрал область, в которой совершенно не шарю — попробую написать десктопное приложение для макбука на .Net и C#. Чтобы было понятно, насколько я не шарю — я до этого думал, что .Net — это язык программирования. =)

В эксперименте я преследую 2 цели:

  1. Проверяю гипотезу, что не программист может полностью написать приложение с нуля (тут, конечно, держим в голове, что я всё-таки программист, поэтому эксперимент не очень чистый, но всё же я выбрал максимально далёкую от себя область и неизвестный мне язык программирования).

  2. Мне всё же интересно заодно разобраться, как пишутся десктопные приложения, плюс интересно потрогать язык C#.

Важно: В процессе написания статьи она сильно разрослась, поэтому я принял решение в текущей части довести дело до запуска приложения, а финальную отладку, наведение красоты и формирование полноценной программы оставить на вторую часть.

Штош, приступим-с.

Дисклеймер

  • Конечно же, я не претендую на истину и делаю это исключительно по фану.

  • Языком C# я не владею и никогда на нём не писал, но всё же стоит учесть, что JavaScript, на котором я пишу, является Си-подобным языком и какие-то конструкции мне знакомы. Поэтому я старался вообще не вчитываться в код, копировал всё, что чгпт мне предлагает, в спорных ситуациях скидывал ему текущий код и просил самого вставить обновление туда, куда нужно.

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

Исходные данные

  • Я использовал ChatGPT 3.5.

  • VS Code (программа, в которой пишу код) у меня уже была установлена, поэтому шаг с его установкой я пропускаю.

  • Для того, чтобы вам было удобнее проследить все шаги, я создал гит-репозиторий и каждый шаг оформил в виде отдельного реквеста. Про то, как работать с гитом, я у чгпт не спрашивал и привнёс сюда свой опыт, потому что сделал это исключительно для вашего удобства и исходил из того, что если я не программист и пытаюсь создать проект с помощью нейросети, то вряд ли я работаю в команде и вряд ли буду заморачиваться такими вопросами, как репозиторий, коммиты и реквесты.

Шаг 0. Общие вопросы

Для начала я просто спросил, что такое .Net. Ответ меня вполне устроил и как раз на этом этапе я понял, что .Net — это не язык, а платформа, которая поддерживает несколько языков (из списка я выбрал C#):

0ac2a04c6e34a0e5758d2847960182bc.png

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

a665e3908e9436847fcf8715d236a399.pngВот какой план он мне расписал

Шаг 1: Установка инструментов разработки

  1. Установка .NET SDK: Скачай и установи .NET SDK для мака с официального сайта Microsoft.

  2. Выбор IDE: Рекомендую установить Visual Studio Code и расширение для разработки на C#.

Шаг 2: Создание проекта

  1. Открой терминал и создай новую папку для проекта.

  2. В командной строке выполните dotnet new console -n TicTacToe для создания нового проекта.

Шаг 3: Написание игры в крестики-нолики

  1. Определи структуру проекта: создай классы для игрового поля, игроков и логики игры.

  2. Создай классы GameBoard, Player, GameLogic.

  3. Реализуй логику проверки выигрыша, ходов игроков и отображения игрового поля.

  4. Напиши метод для вывода игрового поля в консоль.

Шаг 4: Тестирование

  1. Напиши тесты для проверки корректности работы методов.

  2. Запусти тесты и убедись, что все работает правильно.

Шаг 5: Опционально — добавление интерфейса

  1. Изучи возможности для создания GUI приложения на C# для мака.

  2. Используй WPF или AvaloniaUI для создания пользовательского интерфейса.

  3. Имплементируй GUI для игры в крестики-нолики.

Шаг 6: Запуск проекта

  1. Проверь, что проект компилируется без ошибок.

  2. Запусти игру и проверь её работоспособность.

Шаг 7: Упаковка и распространение

  1. Упакуй приложение в исполняемый файл для macOS.

  2. Протестируй упакованное приложение на других машинах под управлением macOS.

  3. Подумай о возможности упаковки для других операционных систем.

Что ж, пока звучит адекватно, хотя я и вижу кучу непонятных слов. Полагаю, что дальше шаг за шагом он мне всё объяснит. Сразу скажу, что писать тесты в планах нет. =)

Шаг 1. Установка инструментов

Далее переходим к конкретным шагам.

557d02e2e2b3865f9ded7698543a932b.png

Он мне предложил скачать на компьютер .NET SDK (собсна, сама платформа .Net, как я понимаю) и IDE (то есть, программу, в которой будем писать код).

SDK я установил без проблем, а VS Code, как я говорил выше, у меня уже был, поэтому этот шаг я пропустил, но полагаю, что с этим не должно быть проблем.

Что касается расширения, о котором он говорит — думаю, что у новичка бы возникли здесь сложности с тем, что это и где вообще искать, но я решил сразу пойти, куда нужно. И всё же я маленько погонял его, чтобы посмотреть, что он порекомендует:

d453e0f53615cc31c075f4e03659a740.png

Как можем видеть, его ответ не очень адекватен, он просто повторил то же название, что писал до этого, хотя я прямо сказал, что такого нет.

В итоге я открыл расширение C# и увидел в описании, что это именно то, о чём он говорил, и установил его.

bedc9791e5159fc5bd23cf2f889a382e.png

Шаг 2. Создание проекта

Далее создаём проект. Он предлагает открыть терминал и выполнить команду dotnet new console -n TicTacToe.

be48bcc6306b323cfd3c39429d3a71b1.png

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

Но здесь важно другое. Он написал команду для установки и прокомментировал, что она «создаст новый проект типа консольное приложение». Консольное приложение — это приложение без интерфейса, оно работает так: пишешь в консоль какую-то команду текстом, программа отвечает тоже текстом. Это не то, что я хочу и не то, что я изначально у него просил (хотя, возможно, тут мой косяк и на самом деле десктопное приложение!= приложение с интерфейсом). Поэтому если на этом этапе прощёлкать и не ткнуть его в это носом, то не понятно, на каком этапе потом эта ошибка вскроется и насколько муторно будет переделывать.

730a0bcb426eb79e4341ec6ae710a502.png

Уже веселее, но всё же я решил уточнить ещё раз:

734f90af1ccdcd962bcd81e35a3ce192.png

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

В конце он меня спрашивает, что будем использовать, но я понятия не имею, поэтому приходится спрашивать дальше:

a77abfb10a3b58ca8f2a813aad5d23e1.png

Дальше идёт довольно длинный список отличий, но уже из первого пункта понятно, что Xamarin.Forms нам не подходит совершенно (зачем тогда вообще предлагал?) и нужно выбирать AvaloniaUI.

В общем, после всех этих уточнений он наконец-то пишет итоговую инструкцию, по которой я развернул проект:

b305c94d910d50348554e88119f63960.png

На этом дело не заканчивается. После открытия проекта в VS Code у меня вылетели две ошибки и я начал с ними разбираться:

8644aafacba10276d838ac5c2060522e.png

В первом пункте он предлагает установить .NET Core SDK и мне не понятно, нужно ли его устанавливать, если в самом начале я установил .NET SDK. Снова спрашиваем:

18dda02e1086377414b786064f8a816a.png

Получается, не нужен, окей. Устанавливаю переменные из инструкции выше — перезагружаю VS Code — не помогает, ошибка всё равно появляется. Я задал ему ещё несколько вопросов на эту тему, которые не буду тут приводить ради сокращения статьи, в целом эта проблема решилась перезагрузкой компа. Справедливости ради, я закрывал только текущее окно VS Code (которых у меня открыто миллион), поэтому возможно, если бы я закрыл все и заново открыл, это бы помогло и без перезагрузки.

После открытия проекта ошибок не было, но было предложение установить ресурсы для сборки. Я задал ему нубский вопрос, нажал «Да» и всё получилось.

b6c5b81bf754ca97fc7a4cffa0cb2b65.png

Кстати, изначально я говорил, что у меня было две ошибки — так вот вторая ошибка как раз на эту тему была, что он не мог что-то там найти, но после перезагрузки ошибка сама ушла.

В итоге получили вот такую структуру:

14c729852e76f9a54d3e87fe21d00b82.png

Шаг 3. Написание логики игры.

2876ed41b7a10fb9aa11ef96a304190a.png

Получилось дохрена делов, поэтому начинаем поэтапно:

1feaad6f94f668f508466ab243becf50.png

В целом, ответ хороший, я его загоню под кат, если кому интересно.

Туть

Хорошо, давай начнем с определения структуры проекта и создания необходимых классов для игры в крестики-нолики.

1. Определение структуры проекта

Классы, которые нам понадобятся:

  1. GameBoard (Игровое поле): Этот класс будет представлять игровое поле для крестиков-ноликов. Он будет хранить текущее состояние игры и предоставлять методы для установки символов (крестиков и ноликов) на поле, проверки наличия победителя и т. д.

  2. Player (Игрок): Этот класс будет представлять игрока. Он может иметь атрибуты, такие как имя игрока и символ (X или O).

  3. GameLogic (Логика игры): Этот класс будет отвечать за логику игры, такую как проверка наличия победителя, определение следующего хода и т. д.

Где создать классы:

В проекте AvaloniaUI создаются два основных каталога для размещения кода:

  • ViewModels: Здесь располагаются классы-представления, которые управляют бизнес-логикой приложения и предоставляют данные для отображения в пользовательском интерфейсе.

  • Views: Здесь располагаются файлы XAML, описывающие пользовательский интерфейс приложения.

Так как классы, которые мы хотим создать, относятся к бизнес-логике игры, мы создадим их в каталоге ViewModels.

Создание классов в проекте AvaloniaUI

  1. Откройте Solution Explorer в Visual Studio Code: Это окно показывает структуру проекта.

  2. Перейдите в каталог «ViewModels»: Обычно его можно найти в корне проекта.

  3. Создайте новые классы:

    • Создайте новый файл с расширением .cs в каталоге ViewModels для каждого из вышеупомянутых классов.

    • Назовите файлы соответственно: GameBoard.cs, Player.cs и GameLogic.cs.

  4. Реализуйте логику в классах: В каждом из этих классов напишите необходимый код для представления игрового поля, игроков и логики игры.

Пока создавал файлы, VS Code предложил поставить ещё одно расширение, задал вопрос про него:

df3e12b19a9d6dfe4e3cbf24e598fefe.png

Ответ меня устраивает, расширение я не ставил.

Далее задаю закономерный вопрос:

b647c787cbbc5e4be79badfcadad2ce8.png

И в целом получаю нормальный ответ:

7826c69d00ad7344d6e3e91c964c42d7.png

Он мне написал код класса GameBoard, потом я попросил написать код для остальных двух классов, что он и сделал. Благодаря его комментариям в целом понятно, что делает код, но сам синтаксис мне не совсем понятен, так что на этом этапе я ещё не понимаю, рабочий это код или нет, дальше будет видно.

Кстати, только сейчас поймал себя на мысли, что чгпт использует термин «Класс (Class)», который я, как программист, знаю, но вряд ли его поймёт не программист, поэтому, возможно, потребуется немного больше уточняющих вопросов (если коротко, то это такой объект, который хранит в себе набор данных и методов для работы с ними).

Как программисту, мне стало интересно чуть-чуть погрузиться в синтаксис, так что я попросил его построчно разобрать код и сказать, чё тут к чему, но этот шаг не программисту можно пропустить, ибо он вряд ли даст что-то полезное:

fe32fd7c1b6e4ac6b01a5219713c8d83.png

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

Туть

Прекрасно! Давай разберем класс GameBoard по строкам и рассмотрим, что каждая из них означает:

  • using System; — это директива using, которая используется для включения пространства имен System в код. Пространство имен System содержит базовые классы и типы данных, такие как Console, String, Int32 и многое другое. В этом конкретном классе GameBoard мы используем System для работы с базовыми типами данных и для вывода информации в консоль.

  • namespace TicTacToe.ViewModels — это ключевое слово namespace, которое используется для создания области имен (namespace) с именем TicTacToe.ViewModels. Область имен — это контейнер, который содержит другие классы, интерфейсы и прочие типы. Мы используем область имен для организации нашего кода и предотвращения конфликтов имен. Здесь класс GameBoard принадлежит области имен TicTacToe.ViewModels.

  • public class GameBoard — это объявление класса GameBoard. Ключевое слово public означает, что класс GameBoard доступен извне текущего проекта. class указывает, что мы создаем новый класс. GameBoard — это имя класса.

  • private char[,] board; и private int size; — это объявления приватных (private) полей класса GameBoard. board — это двумерный массив типа char, который будет использоваться для представления игрового поля. size — это целочисленное поле, которое будет хранить размер игрового поля (количество строк и столбцов).

  • public GameBoard(int size) — это конструктор класса GameBoard, который принимает один аргумент типа int с именем size. Конструкторы используются для создания новых экземпляров класса. В этом конструкторе мы инициализируем игровое поле заданного размера.

  • private void InitializeBoard() — это приватный метод InitializeBoard(). Методы представляют действия или операции, которые можно выполнить с объектами класса. В данном случае, метод InitializeBoard() используется для инициализации игрового поля, заполняя его пустыми символами.

  • public bool SetSymbol(int row, int column, char symbol) — это публичный метод SetSymbol(), который принимает три аргумента: row (строка), column (столбец) и symbol (символ). Метод используется для установки символа (крестика или нолика) на указанную позицию игрового поля.

  • public bool CheckWin(char symbol) — это публичный метод CheckWin(), который принимает один аргумент типа char с именем symbol. Метод используется для проверки наличия победителя в игре для указанного символа (крестика или нолика).

  • public void DisplayBoard() — это публичный метод DisplayBoard(), который используется для отображения текущего состояния игрового поля в консоли.

Это основные элементы класса GameBoard. Каждая строка в этом классе выполняет определенную функцию, и вместе они обеспечивают функциональность игрового поля для крестиков-ноликов. Если у вас есть какие-либо дополнительные вопросы по синтаксису или по этому классу, не стесняйтесь спрашивать!

Продолжаем…

aec9106277952a5873228ba9081e086a.png

В ответ он снова просто накидывает план (видимо, надеется, что мы напишем всё вместо него, но не тут-то было).

5a679cee3819f85fd699ab23c6494aec.png

И опять ни строчки кода. Продолжаем:

5e7f2eba1e518e8b7d97c232f743be5f.png

Так, отлично, уже что-то. Добавляем файл по инструкции.

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

58207a577771c69d7748ef70c1d90258.png

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

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

f9ebb315c336b36fa955bfa4a39cf539.png51f9c4a1017ffa5f75b4246846dcc707.png70afbca0865f55eb5e3266365855e58e.png

Дальше он говорит «Ну просто используйте теперь этот компонент», но я не понимаю, как использовать, поэтому спрашиваю:

6cc42820127ad8c1fffbb467be359a5c.png

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

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

5df1c313b524594db4b8fa71f7d215cc.png

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

Полная переписка туть

На самом деле, проблема возникла из-за того, что он изначально написал говнокод и криво разделил логику между классами GameBoard и GameLogic, из-за этого получилось так, что одна нужная нам переменная оказалась в одном классе, а другая нужная переменная — в другом. Но если я не программист, то я, разумеется, никогда этого не пойму и с этой точки зрения я продолжал его допинывать в сторону того, чтобы он как-то решил проблему, сохранив реализацию.

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

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

Шаг 3.1. Выясняем, что делать дальше.

Дальше произошёл какой-то странный этап общения. Я спросил его, что делаем дальше, он мне ответил вот так:

91066121a8a61cf8d558e6f67df59e74.png

Как позже выяснилось, то, что он предлагает, мы уже реализовали. Такое ощущение, что он потерял контекст общения (что вряд ли, чат тот же), либо это его нормальное поведение и он в принципе может «потеряться в пространстве». Для чистоты эксперимента я загоню нашу переписку под кат, но в целом тут важно понять, что такое может случиться и нужно быть внимательным, и можно смело переходить к следующему шагу.

Туть

Шаг 4. Запуск проекта

После предыдущего бесполезного общения напрямую предложил запускать проект:

b87c63c865c271e4dcce8639963041ef.png

Вроде всё логично пишет, но вот 3-й пункт вызывает вопросы:

864ed770fe5d8890abca2f402a2c9436.png

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

fd01b34efef65229a6cfb09bbd058b4a.png

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

5a1b4fccf4749d683e86dd99a1a17dee.png

Инструкция совершенно не понятная. Нам что-то нужно проверить, в чём-то убедиться, но никакой конкретики.

fc61482adca23283b792d8624062626b.png

Дальше идёт довольно длинная инструкция про то, что надо пройтись по коду и убедиться, что всё правильно, но проблема в том, что на данном этапе причина вообще не в этом, а мы просто тупо не подключили наши файлы к проекту, их как будто там нет. И я не смог добиться от него этого ответа. Я скинул ему свой текущий код:

b0d7b95c1eeb70f4668a0ee8ccb3475e.pnga2d7f9c0e1e528dc763750b1388dd912.png

И в ответ он мне говорит вот это:

dd8f90c7327b179d8b81d9e86e425b2a.png

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

deb4544ffc6baf447800ad30e3d3145c.png

«Вы правильно поняли» — спасибо большое!

Это было сложно. Идём дальше, пытаюсь снова запустить проект:

f59f05b2b17019f23547531c7c880725.png2bba5763b634c4512208fe1b4ce0f669.png

В общем, тут была ошибка в том, что несмотря на использование моего компонента в главном окне, он почему-то не подтягивался. Я унесу под кат дальнейшее общение по этому поводу, где он старательно подсказывал мне неправильные варианты, а кроме того, в какой-то момент перепутал программы Visual Studio и Visual Studio Code и дал рекомендацию, которую в VS Code в принципе нельзя выполнить.

Туть

В общем, после продолжительного общения ни о чём он наконец-то говорит что-то дельное:

892491f64142f8a5b48e4d1f633f977d.png

Во 2-м пунке, ВНЕЗАПНО оказывается, что мой файл всё-таки нужно импортировать, правда в другом месте.
Скидываю ему текущий код, чтобы он помог прям в него встроить нужную строчку:

631e7d736731374eff4c23494e12c694.png

Ответ адекватный:

54e90d625b2eff4b3d5b9073b1225d34.png

Прописал, запускаю дальше, следующая ошибка:

825e3d144aca33f9edbfd328b25d2fc2.png15d6b6431f6494feb2f2585e04191236.pngc4961c332b6dbf9cfa7e3e9045aab558.png

Для тех, кто не уловил юмора: он говорит, что ошибка в том, что мой компонент GameBoardView имеет тип Window, то есть, это верхнеуровневое окно, его нельзя помещать внутрь другого окна, оно может существовать только само по себе. А потом в качестве решения предлагает просто обернуть мой компонент в .)))

b8cc2222ad7eac85294ddbf689f863d0.png5688c6e3d17dfdbaad901aa545e1134a.png

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

a2c3f6f490d358b08c5dcb01b355f52e.png0f98a41a0ed6a71eae928c40d17c7180.pngd65988fc2459726c2ee8de0396ddda8f.pngb211ccef7dfc18fadad2132701f56040.png

Почему-то во втором случае он написал this.VisualRoot as Window;, а не (Window)this.VisualRoot;, и вот так стало получше, все варнинги исчезли.

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

9ab9aef499d529b7a932cb9bcdc9ef93.png

Запускаю ещё раз, иииии…

2ce293e84a8e9dd582454f6da5cb7756.png

Та-дам! Проект запустился и даже какая-то вьюха отрисовалась (реквест). Правда пока что ничего не работает и никакие нажатия на кнопки не обрабатываются, а в одном случае даже всё приложение падает (наш код всё-таки кривой).

Однако поскольку статья уже порядком подзатянулась, я решил оставить отладку приложения на вторую часть.

Промежуточные выводы.

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

З.Ы. Если кто-то из вас решит повторить эксперимент (особенно, если вы не программист), пожалуйста, поделитесь результатами, очень интересно.

© Habrahabr.ru