Is GitHub Copilot worth the money?
Some months ago I tried out GitHub Copilot for free. At this time I started with Go and I was too lazy to read a book. I am a software engineer and normally use C# for programming. Copilot helped me to get started with the basics of Go. There are some stumbling blocks when you come from C#.
The initial euphoria quickly soon began to fade. The code examples did not always compile and were full of errors. My corrections were always accepted. But more than once I found myself going around in circles because the next time I suggested fixing something, previously fixed errors would get re-included.
Did I waste money by buying GitHub Copilot?
Does GitHub Copilot make me smarter?
I am writing a microservice for storing files. I have decided to use MongoDB to persist the file metadata. I have no experience with this specific database and I have never used a database with Golang. In my opinion, this is a perfect spotlight for the use of AI.
Task 1: Create a docker-compose file
First of all, I need a test database. I use docker-compose for that. Let’s ask the AI for help.
version: '3.8'
services:
mongodb:
image: mongo:latest
container_name: mongodb_container
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
volumes:
- mongodb_data:/data/db
ports:
- 27017:27017
volumes:
mongodb_data:
The AI answers with an example and a lot of text about how docker-compose works. Apart from the fact that the newest version is 3.9 ( the source is probably the outdated docker documentation ), this is very useful. But one explanation makes me suspicious:
Previously I only used PostgreSQL and I have never declared a volume for the database in the docker-compose file. For use as a test system, I want all data to be removed when I delete the Docker container. Let’s ask the AI how to achieve this instead of using Google:
I didn’t know that but I expected this behaviour. Let’s teach the AI exactly what I want:
mongodb:
image: mongo:latest
container_name: mongodb_container
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
volumes:
- /data/db
ports:
- 27017:27017
Nice one. My existing docker-compose file uses a network to allow communication between the database and the service. GitHub Copilot did not detect this. The container name does not follow my naming scheme. I fixed both problems manually. Overall good work.
Task 2: Create a repository
The use of a repository to encapsulate the database is quite common. In my experience, Go is a bit different and I prefer to follow standards. So let’s ask GitHub Copilot if I have the right design:
Never trust just one source is a good principle on the Internet. So ask about the sources the AI used:
GitHub Copilot’s last sentence sounds like he is talking to a child. But I am not a child anymore. I am an experienced software developer. Two of the links are broken because the authors have changed the names of the Git repositories on GitHub. But as a human, I can find the correct links.
GitHub Copilot accepts my corrections without objection. Who is the child now? 🤪 As a good practice, I have to ask the child if it understood what I explained. Listen and repeat:
The third source was removed. I have provided the corrected link and invested my time in correcting this. Let’s remind the AI.
Let’s try to convince them to add the link again. The link was quite useful and I do not want to make GitHub Copilot any dumber.
This time, I give it a thumbs up (highlighted in yellow) to increase the chances that the answer will be used later. At least I hope it works that way.
Now that I know I want a repository, let’s create one. Of course, I use AI to do this, because programming is a lazy job.
package repository
import (
"context"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Repository struct {
client *mongo.Client
db *mongo.Database
}
func NewMongoDBRepository(uri string, dbName string) (*Repository, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
if err != nil {
return nil, err
}
db := client.Database(dbName)
return &Repository{
client: client,
db: db,
}, nil
}
func (r *Repository) Close() error {
return r.client.Disconnect(context.Background())
}
In general, that is ok. The code compiles, which was not the case in some other examples. Small modifications are needed. I prefer to use the context cancellation support. Oops… I missed that there is a WithTimeout function on the context. I always used WithDeadline. I have learned something from the AI. Today is a good day.
So I can create the repository once, store it in a package-level variable, and initialize it from the main.go file. I manually add an initialization function for this.
var File file.FileRepository // package-level variable
func InitializeMongoDBRepository(uri string, databaseName string) error {
var err error
File, err = NewMongoDBRepository(uri, databaseName)
return err
}
Phew… that one’s tough. I was typing code by myself. I want to rest now, so the AI should take over.
package main
import (
"log"
"github.com/kinneko-de/restaurant-file-store-svc/internal/app/repository"
)
func main() {
// The URI should be in the format: mongodb://<username>:<password>@<host>:<port>
uri := "mongodb://root:example@localhost:27017"
databaseName := "mydatabase"
err := repository.InitializeMongoDBRepository(uri, databaseName)
if err != nil {
log.Fatalf("Failed to initialize MongoDB repository: %v", err)
}
// The rest of your main function...
}
Stop… these are not the variables I used in my docker-compose file. Here is my final docker-compose file:
mongodb:
image: mongo:latest
networks:
- project-network
container_name: restaurant-file-store-db
environment:
MONGO_INITDB_ROOT_USERNAME: rootuser
MONGO_INITDB_ROOT_PASSWORD: rootpassword
volumes:
- /data/db
ports:
- 27017:27017
I did not show the docker-compose file to GitHub Copilot as a reference. So I show it the file so it can fix the variables itself.
package main
import (
"log"
"github.com/kinneko-de/restaurant-file-store-svc/internal/app/repository"
)
func main() {
// The URI should be in the format: mongodb://<username>:<password>@<host>:<port>
uri := "mongodb://rootuser:rootpassword@mongodb:27017"
databaseName := "mydatabase"
err := repository.InitializeMongoDBRepository(uri, databaseName)
if err != nil {
log.Fatalf("Failed to initialize MongoDB repository: %v", err)
}
// The rest of your main function...
}
Now the user and password are correct. But I think ‘mongodb’ is the wrong value for the database host. I use ‘restaurant-file-store-db’ as the container name in my docker-compose declaration. But humans are fallible, so let’s ask GitHub Copilot:
package main
import (
"log"
"github.com/kinneko-de/restaurant-file-store-svc/internal/app/repository"
)
func main() {
// The URI should be in the format: mongodb://<username>:<password>@<host>:<port>
uri := "mongodb://rootuser:rootpassword@restaurant-file-store-db:27017"
databaseName := "mydatabase"
err := repository.InitializeMongoDBRepository(uri, databaseName)
if err != nil {
log.Fatalf("Failed to initialize MongoDB repository: %v", err)
}
// The rest of your main function...
}
Now I am a little bit confused. In its answer, the AI told me to use the ‘service name’ which is ‘mongodb’ and not ‘restaurant-file-store-db’. But it still replaced my suggestion in the example. Does it just want to follow what I say even if it is wrong? I need to test this.
Testing shows that both versions work. Using Stackoverflow as a resource for human knowledge, it turns out that using the service name is preferable. It works even if you have multiple instances. Not that it matters for my tests here, but I change the host back to the service name ‘mongodb’.
Overall, GitHub Copilot gives me a lot of good suggestions. The quality of the examples varies. It also cannot provide code examples for more advanced uses that a software engineer would normally have to take care of.
For example, I added health checks to the docker-compose file to ensure that the systems start in the right order and don’t crash non-deterministically. It also doesn’t realize that I need support for Kubernetes health checks and pod shutdown in the main.go file. The client, the database, and the collection of ‘mongodb’ are thread-safe and should not be recreated for each use. The examples are for demonstration and explanation only and are not production-ready.
Conclusion
GitHub Copilot (and AI in general) is not (yet?) ready to replace me as a software engineer. It saves me some mindless typing and time-consuming internet research. It allows me to learn new things faster. I love this ❤.
At this stage, the AI is still a kid who needs my help to cross the street.
Foto von Ben White auf Unsplash
I’m excited to see the kid grow up.
Top comments (0)