Use o ChatGPT como desenvolvedor
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:
- Projetar uma API
- Corrija o Chat GPT para obter uma melhor saída
- Deixe o Chat GPT explicar suas decisões
- Pergunte ao Chat GPT sobre tecnologias
- Peça conselhos sobre qual tecnologia usar
- Implemente o código da especificação
- Explique como o código funciona
- Escrever scripts de shell
- Dockerize seu aplicativo
- Escrever coleções de carteiro para teste
- Escrever testes
- 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 entradas.Namee a fatia do aluno for uma fatia de ponteiros para estruturas, a alteração feita pelaupdateNamefunçã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.





































![O que é uma lista vinculada, afinal? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)