Use o ChatGPT como desenvolvedor

Jan 23 2023
Termine o trabalho que leva dias em 30 minutos Introdução Depois de ler este ótimo tópico sobre o Chat GPT, decidi tentar usar o Chat GPT em meu trabalho. Mostrarei como você, como desenvolvedor, pode usar o Chat GPT para realizar tarefas demoradas e melhorar a qualidade do seu código.

Termine o trabalho que leva dias em 30 minutos

Introdução

Depois de ler este ótimo tópico sobre o Chat GPT , decidi tentar usar o Chat GPT no meu trabalho. Mostrarei como você, como desenvolvedor, pode usar o Chat GPT para realizar tarefas demoradas e melhorar a qualidade do seu código. Como exemplo, vamos criar uma API Go do zero.

Criei este projeto com e sem a ajuda do Chat GPT e comparei os resultados. Com o Chat GPT consegui concluir o projeto cerca de 10 vezes mais rápido. O bot descobriu em segundos um bug no qual perdi uma noite inteira, sugeriu bibliotecas melhores para trabalhar e tomou decisões de design no nível de um desenvolvedor sólido de nível médio.

O GPT de bate-papo funcionará melhor se você delegar parte das tarefas que executa, usá-lo para aprendizado e como uma ferramenta de revisão de código. Ele não irá substituí-lo como desenvolvedor, mas pode aumentar muito sua produtividade.

Abaixo, você pode encontrar um índice para as etapas pelas quais passei durante a construção do projeto. Cada um deles é um exemplo independente de como você pode usar o Chat GPT em seu trabalho.

Conteúdo:

  1. Projetar uma API
  2. Corrija o Chat GPT para obter uma melhor saída
  3. Deixe o Chat GPT explicar suas decisões
  4. Pergunte ao Chat GPT sobre tecnologias
  5. Peça conselhos sobre qual tecnologia usar
  6. Implemente o código da especificação
  7. Explique como o código funciona
  8. Escrever scripts de shell
  9. Dockerize seu aplicativo
  10. Escrever coleções de carteiro para teste
  11. Escrever testes
  12. Descubra bugs
  • se o Chat GPT parar de gerar a saída, apenas diga para continuar e ele terminará de escrever uma resposta separada;
  • Peça ao Chat GPT para “escrever código como um júnior/sênior”, você ficará surpreso com o resultado.

Vamos construir uma API simples de gerenciamento de registros de alunos. Também escreveremos uma especificação OpenAPI para a API e encaixaremos o servidor.

Construindo o projeto

1. Projete uma API

Entramos no prompt e obtemos a saída do ChatGPT. Além disso, apresentarei os prompts e a saída em formato textual.

Incitar:

Escreva uma especificação Open API v3 para uma API de gerenciamento de alunos RESTful. Deve ser possível criar um registro de aluno, adicionar um aluno a um curso e vincular um aluno a um professor supervisor.

Saída:

openapi: 3.0.0
info:
  title: Student Management API
  version: 1.0.0
  description: A RESTful API for managing student records, courses, and professors

paths:
  /students:
    post:
      summary: Create a new student record
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                  format: email
                program:
                  type: string
              required:
                - name
                - email
                - program
      responses:
        201:
          description: Student record created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  email:
                    type: string
                    format: email
                  program:
                    type: string
        400:
          description: Bad request
  /courses/{courseId}/students/{studentId}:
    put:
      summary: Add a student to a course
      parameters:
        - name: courseId
          in: path
          required: true
          description: ID of the course to add the student to
          schema:
            type: string
        - name: studentId
          in: path
          required: true
          description: ID of the student to add to the course
          schema:
            type: string
      responses:
        204:
          description: Student added to course
        400:
          description: Bad request
  /professors/{professorId}/supervise/{studentId}:
    put:
      summary: Attach a student to a supervising professor
      parameters:
        - name: professorId
          in: path
          required: true
          description: ID of the professor to supervise the student
          schema:
            type: string
        - name: studentId
          in: path
          required: true
          description: ID of the student to attach to the professor
          schema:
            type: string
      responses:
        204:
          description: Student attached to professor
        400:
          description: Bad request

A especificação que ele gerou está ok, mas gostaria que o cliente gerasse o ID do aluno.

Incitar:

Reescreva a especificação, de modo que, ao criar um novo registro, o usuário tenha que enviar o ID como um parâmetro de caminho de url

Saída:

openapi: 3.0.0
info:
  title: Student Management API
  version: 1.0.0
  description: A RESTful API for managing student records, courses, and professors

paths:
  /students/{id}:
    post:
      summary: Create a new student record
      parameters:
        - name: id
          in: path
          required: true
          description: ID of the student to create
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                  format: email
                program:
                  type: string
              required:
                - name
                - email
                - program
      responses:
        201:
          description: Student record created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  email:
                    type: string
                    format: email
                  program:
                    type: string
        400:
          description: Bad request
  /courses/{courseId}/students/{studentId}:
    put:
      summary: Add a student to a course
      parameters:
        - name: courseId
          in: path
          required: true
          description: ID of the course to add the student to
          schema:
            type: string
        - name: studentId
          in: path
          required: true
          description: ID of the student to add to the course
          schema:
            type: string
      responses:
        204:
          description: Student added to course
        400:
          description: Bad request
  /professors/{professorId}/supervise/{studentId}:
    put:
      summary: Attach a student to a supervising professor
      parameters:
        - name: professorId
          in: path
          required: true
          description: ID of the professor to supervise the student
          schema:
            type: string
        - name: studentId
          in: path
          required: true
          description: ID of the student to attach to the professor
          schema:
            type: string
      responses:
        204:
          description: Student attached to professor
        400:
          description: Bad request

Quero comparar as duas especificações e ver qual é mais RESTful.

Incitar:

Qual especificação adere melhor às convenções repousantes?

Saída:

Ambas as versões da especificação que forneci aderem às convenções RESTful de maneiras diferentes.

Na primeira versão, ao criar um novo cadastro de aluno, o usuário envia os dados como um payload JSON no corpo da solicitação, e a API gera o ID do aluno. Isso segue as convenções RESTful porque segue o padrão de criação de recursos usando o método HTTP POST, e a API é responsável por gerar o identificador exclusivo do recurso.

Na segunda versão, ao criar um novo cadastro de aluno, o usuário envia o ID do aluno como parâmetro de caminho e o restante dos dados como payload JSON no corpo da solicitação. Isso também segue as convenções RESTful porque segue o padrão de criação de recursos usando o método HTTP POST, mas, neste caso, o usuário é responsável por gerar o identificador exclusivo do recurso.

Ambas as versões da especificação fornecem uma maneira de criar registros do aluno, mas a segunda versão é mais RESTful no sentido de seguir os princípios do estilo arquitetônico REST, que é usar uma URL para identificar o recurso e os métodos HTTP para interagir com isso.

Em geral, é importante seguir as convenções e princípios RESTful ao projetar uma API, mas, em última análise, a escolha de qual abordagem usar depende dos requisitos e restrições de seu caso de uso específico.

— — —

Legal, parece que tomamos uma decisão certa. Vamos implementar nosso servidor Go API.

4. Pergunte ao ChatGPT sobre tecnologias

Antes de implementar a especificação, quero ver quais frameworks web e ORMs estão disponíveis para Go.

Incitar:

Quais são as ferramentas de servidor da web minimalistas mais populares e construtores de ORM/query para Go?

Saída:

Go, também conhecido como Golang, é uma linguagem de programação popular conhecida por sua simplicidade e minimalismo. Algumas das ferramentas de servidor web minimalistas mais populares e construtores ORM/query para Go incluem:

  • Gin: Gin é um framework web popular para Go, conhecido por seu alto desempenho e design minimalista. Ele é construído sobre a popular biblioteca httprouter e fornece uma API simples e flexível para criar aplicativos da web.
  • Echo: Echo é outro framework web popular para Go, conhecido por sua simplicidade e facilidade de uso. Ele é construído sobre a biblioteca http padrão e fornece vários recursos prontos para uso, como suporte a middleware, roteamento de solicitação e renderização de modelo.
  • Gorilla Mux: Gorilla Mux é um roteador de solicitação minimalista e despachante para Go que é construído sobre a biblioteca http padrão. Ele fornece uma API simples e flexível para corresponder solicitações recebidas a manipuladores específicos e geralmente é usado como uma alternativa leve a estruturas da Web com mais recursos.
  • SQLBoiler: SQLBoiler é uma biblioteca ORM (Object-Relational Mapping) minimalista para Go que fornece uma API simples e elegante para trabalhar com bancos de dados. Ele oferece suporte a uma ampla variedade de bancos de dados e fornece recursos como criação automática de tabelas, manipulação de associações e gerenciamento de transações. Destina-se a ser usado como um gerador de código para gerar o código Go que interage com o banco de dados.
  • SQLx: SQLx é um popular construtor de consultas e biblioteca SQL para Go que fornece uma API simples e flexível para trabalhar com bancos de dados. Ele é construído sobre a biblioteca de banco de dados/sql padrão e fornece vários recursos adicionais, como marcação de struct, ligação de parâmetro nomeado e fácil gerenciamento de conexão.
  • go-sql-driver: go-sql-driver é uma biblioteca de driver SQL minimalista para Go que fornece uma API simples e flexível para trabalhar com bancos de dados. Ele é construído sobre a biblioteca de banco de dados/sql padrão e fornece vários recursos adicionais, como marcação de struct, ligação de parâmetro nomeado e fácil gerenciamento de conexão.

5. Peça conselhos sobre qual tecnologia usar

Não tenho certeza de qual combinação de servidor/ORM se adapta melhor ao meu projeto.

Incitar:

Qual seria uma boa combinação de ORM/servidor para nossa API?

Saída:

A escolha de qual ORM e servidor web usar para sua API dependerá dos requisitos e restrições específicos de seu projeto. No entanto, posso fornecer algumas sugestões de combinações populares e conceituadas que você pode considerar:

  • Gin com Gorm: Gin é uma estrutura da Web popular e de alto desempenho para Go, e Gorm é uma biblioteca ORM popular para Go que fornece uma API simples e elegante para trabalhar com bancos de dados. Juntos, eles fornecem uma combinação poderosa para construir uma API RESTful que pode lidar com um alto volume de solicitações e interagir com eficiência com um banco de dados.
  • Echo com SQLx: Echo é outro framework web popular para Go que é conhecido por sua simplicidade e facilidade de uso. SQLx é um popular construtor de consultas e biblioteca SQL para Go que fornece uma API simples e flexível para trabalhar com bancos de dados. Juntos, eles fornecem uma combinação leve e fácil de usar para criar uma API RESTful que pode lidar com um volume moderado de solicitações e interagir com eficiência com um banco de dados.
  • Gorilla Mux com SQLBoiler: Gorilla Mux é um roteador de solicitação minimalista e despachante para Go que é construído sobre a biblioteca http padrão. SQLBoiler é uma biblioteca ORM (Object-Relational Mapping) minimalista para Go que fornece uma API simples e elegante para trabalhar com bancos de dados. Juntos, eles fornecem uma combinação minimalista e flexível para construir uma API RESTful que pode lidar com um volume moderado de solicitações e interagir eficientemente com um banco de dados.
  • net/http com go-sql-driver: net/http é a biblioteca Go integrada para construir servidores web e go-sql-driver é uma biblioteca de driver SQL minimalista para Go que fornece uma API simples e flexível para trabalhar com bancos de dados . Juntos, eles fornecem uma combinação leve e minimalista para construir uma API RESTful que pode lidar com um volume moderado de solicitações e interagir com eficiência com um banco de dados.

6. Implemente o código da especificação

Echo/SQLx soa bem. Nossa API não será grande e queremos manter as coisas simples. Vamos em frente e implementar algumas rotas. E inclua a validação da API aberta para obter integridade.

Incitar:

Ok, agora implemente as rotas create and get do aluno com Echo/SQLx. Adicione também validação de API aberta.

Saída:

package main

import (
 "database/sql"
 "encoding/json"
 "fmt"
 "net/http"

 "github.com/go-sql-driver/mysql"
 "github.com/labstack/echo/v4"
 "github.com/labstack/echo/v4/middleware"
 "github.com/labstack/gommon/log"
 "github.com/stripe/stripe-go/v71/form"
 "gopkg.in/go-playground/validator.v9"
  _ "github.com/go-sql-driver/mysql"
)

type Student struct {
 ID       int    `json:"id" validate:"required"`
 Name     string `json:"name" validate:"required"`
 Email    string `json:"email" validate:"required,email"`
 Program  string `json:"program" validate:"required"`
 Professor int    `json:"professor"`
}

var db *sql.DB
var validate *validator.Validate

func init() {
 var err error
 db, err = sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/student_db")
 if err != nil {
  log.Fatal(err)
 }

 validate = validator.New()
}

func main() {
 e := echo.New()
 e.Validator = &CustomValidator{validator: validate}
 e.Use(middleware.Logger())
 e.Use(middleware.Recover())
 e.Use(middleware.RequestID())
 e.POST("/students/:id", createStudent)
 e.GET("/students/:id", getStudent)
 e.Logger.Fatal(e.Start(":1323"))
}

func createStudent(c echo.Context) error {
 student := new(Student)
 if err := c.Bind(student); err != nil {
  return c.JSON(http.StatusBadRequest, err)
 }
 if err := c.Validate(student); err != nil {
  return c.JSON(http.StatusBadRequest, err)
 }

 query := "INSERT INTO students (id, name, email, program, professor) VALUES (?, ?, ?, ?, ?)"
 _, err := db.Exec(query, student.ID, student.Name, student.Email, student.Program, student.Professor)
 if err != nil {
  return c.JSON(http.StatusInternalServerError, err)
 }

 return c.JSON(http.StatusCreated, student)
}

func getStudent(c echo.Context) error {
 id := c.Param("id")

 var student Student
 err := db.QueryRow("SELECT id, name, email, program, professor FROM students WHERE id = ?", id).Scan(
  &student.ID, 
  &student.Name, 
  &student.Email, 
  &student.Program, 
  &student.Professor,
)
 if err != nil {
  if err == sql.ErrNoRows {
   return c.JSON(http.StatusNotFound, map[string]string{"message": "Student not found"})
  }
  return c.JSON(http.StatusInternalServerError, err)
 }

 return c.JSON(http.StatusOK, student)
}

Parece bom para mim. Mas toda essa coisa de QueryRow e Scan parece mágica para mim.

Vamos pedir ao bot para nos explicar.

Incitar:

Explique como esse código funciona:

var student Student
err := db.QueryRow("SELECT id, name, email, program, professor FROM students WHERE id = ?", id)
.Scan(
  &student.ID, 
  &student.Name, 
  &student.Email, 
  &student.Program, 
  &student.Professor,
)

No código que você forneceu, db.QueryRow()é usado para executar uma consulta SQL que recupera uma única linha da studentstabela com base no ID passado na URL. A consulta é parametrizada para evitar ataques de injeção SQL e a idvariável é passada como um argumento para a consulta.

A QueryRow()função retorna um sql.Rowstruct, que é um wrapper leve em torno do conjunto de resultados do banco de dados que permite extrair facilmente valores de uma única linha. O Scan()método é chamado na sql.Rowstruct e recebe o endereço das variáveis ​​onde será armazenado o resultado da consulta. A ordem dos argumentos passados ​​para o Scan()método deve corresponder à ordem das colunas selecionadas na consulta.

O Scan()método atribui os valores da coluna da linha às variáveis ​​passadas. Os valores são atribuídos aos campos do studentstruct, que é definido como var student Student. Cada campo da struct é passado como um endereço, que é denotado pelo &símbolo, para que o método saiba onde armazenar o valor de cada coluna.

Neste caso, a consulta está selecionando todas as colunas da tabela alunos onde o id é igual ao id passado na url. O resultado da consulta é então armazenado na estrutura do aluno, onde os campos da estrutura são mapeados para as colunas do conjunto de resultados.

Dessa forma, facilita o manuseio dos dados recuperados do banco de dados em um formato struct e também facilita a validação dos dados e o retorno deles em uma resposta.

7. Gere scripts de shell

OK, mas como colocamos esse código em nossa máquina?

Incitar:

Gere um script de shell para configurar este projeto do zero

Saída:

#!/bin/bash

# Create a new directory for the project
mkdir student_api
cd student_api

# Initialize a new Go module
go mod init github.com/myusername/student_api

# Install the required dependencies
go get -u github.com/go-sql-driver/mysql
go get -u github.com/labstack/echo/v4
go get -u github.com/labstack/echo/v4/middleware
go get -u gopkg.in/go-playground/validator.v9

# Create the main.go file
touch main.go

# Create a new MySQL database and user for the project
mysql -u root -p -e "CREATE DATABASE student_db;"
mysql -u root -p -e "CREATE USER 'student_user'@'localhost' IDENTIFIED BY 'password';"
mysql -u root -p -e "GRANT ALL PRIVILEGES ON student_db.* TO 'student_user'@'localhost';"

# Create the students table in the database
mysql -u student_user -pstudent_db < students.sql

# Start the development server
go run main.go

Eu não sei, mas eu odeio escrever arquivos docker, continuo esquecendo a sintaxe. Deixe o ChatGPT fazer isso por nós.

Incitar:

Escreva um docker e arquivos docker-compose para o servidor

Saída:

Dockerfile

FROM golang:latest

WORKDIR /app

COPY . .

RUN go build -o main .

EXPOSE 1323

CMD ["./main"]

version: '3'
services:
  server:
    build: .
    ports:
      - "1323:1323"
    depends_on:
      - db
  db:
    image: mysql:latest
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: student_db
    ports:
      - "3306:3306"

Como podemos testar nossa API? Vamos pedir uma coleção do Postman para um rápido teste manual.

Incitar:

Escreva JSON para uma coleção do carteiro para que eu possa importá-lo para o carteiro e usá-lo para testar a API

Saída:

{
   "info":{
      "name":"Student Management API",
      "_postman_id":"1234abcd-5678-efgh-90ij-klmnopqrstuv",
      "description":"A collection of requests to test the Student Management API",
      "schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
   },
   "item":[
      {
         "name":"Create Student",
         "request":{
            "method":"POST",
            "header":[
               {
                  "key":"Content-Type",
                  "value":"application/json"
               }
            ],
            "body":{
               "mode":"raw",
               "raw":"{\"name\":\"John Doe\",\"email\":\"[email protected]\",\"program\":\"Computer Science\",\"professor\":1}"
            },
            "url":{
               "raw":"http://localhost:1323/students/1",
               "protocol":"http",
               "host":[
                  "localhost"
               ],
               "port":"1323",
               "path":[
                  "students",
                  "1"
               ]
            }
         },
         "response":[
            
         ]
      },
      {
         "name":"Get Student",
         "request":{
            "method":"GET",
            "header":[
               
            ],
            "url":{
               "raw":"http://localhost:1323/students/1",
               "protocol":"http",
               "host":[
                  "localhost"
               ],
               "port":"1323",
               "path":[
                  "students",
                  "1"
               ]
            }
         },
         "response":[
            
         ]
      }
   ]
}

Nenhum projeto está completo sem testes.

Incitar:

Escrever testes de unidade para o projeto

Saída:

package main

import (
    "database/sql"
    "github.com/stretchr/testify/assert"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestCreateStudent(t *testing.T) {
    // Setup
    e := echo.New()
    req := httptest.NewRequest(http.MethodPost, "/students/1", strings.NewReader(`{"name":"John Doe","email":"[email protected]","program":"Computer Science","professor":1}`))
    req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)

    // Assertions
    if assert.NoError(t, createStudent(c)) {
        assert.Equal(t, http.StatusCreated, rec.Code)
        assert.Equal(t, `{"id":1,"name":"John Doe","email":"[email protected]","program":"Computer Science","professor":1}`, rec.Body.String())
    }
}

func TestGetStudent(t *testing.T) {
    // Setup
    e := echo.New()
    req := httptest.NewRequest(http.MethodGet, "/students/1", nil)
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)

    // Assertions
    if assert.NoError(t, getStudent(c)) {
        assert.Equal(t, http.StatusOK, rec.Code)
        assert.Equal(t, `{"id":1,"name":"John Doe","email":"[email protected]","program":"Computer Science","professor":1}`, rec.Body.String())
    }
}

O código abaixo mostra uma versão simplificada de um bug no qual gastei algumas horas. O bate-papo GPT o quebrou em 3 segundos.

Incitar:

Encontre um bug neste código:

for _, s := range students{ s.Name = updateName(s.Name) }

Pode haver vários bugs com base no contexto deste código, mas um bug possível é:

  • Se a updateNamefunção modificar a entrada s.Namee a fatia do aluno for uma fatia de ponteiros para estruturas, a alteração feita pela updateNamefunção não será refletida na estrutura original. Isso porque no Go, ao passar uma struct para uma função ela é passada por valor, e as alterações feitas dentro da função não serão refletidas na struct original. Para corrigir esse bug, você pode passar o ponteiro para a estrutura da função e alterar a implementação da função para aceitar um ponteiro.