Navigating Github Copilot: Part 1

Navigating Github Copilot: Part 1

Practical Use Cases and Potential Pitfalls

Copilot is an AI-powered virtual code assistant created by GitHub. It helps us as programmers write professional quality code faster and easier.

Becoming a Copilot Convert

I've been using Github Copilot for over a year now - since March 2022, to be exact. At first, I was wary of it and didn't use it very effectively, but over time it has become an integral part of my workflow. In that time, I've found that, when used correctly, Copilot can be an incredible time saver and unleash creative thinking. I can still code without Copilot, but I find it an immense waste of time. It's like driving through an unfamiliar city without navigation. Sure, you can rely on the signs and street names. But you may have to stop now and then and look up the route on your paper map before you can continue your journey. And there is (most likely) no way back for me. I got used to Copilot the same way I got used to Waze.

The First 'Tragic' Date

A programmer friend of mine has yet to use Copilot, despite my persistent recommendations. He recently wrote me to say that he had finally installed it on a trial basis, but described the result as "tragic". I was not that surprised: Copilot doesn't have to be love at first sight (it took me about a month to learn how to use it so that we didn't get on each other's nerves).

For many people, getting started with Copilot can be difficult. Some may be bothered by the frequent suggestions, others by the error-prone nature of the resulting code, or by a certain unpredictability. It is also important not to expect miracles from Copilot right away, as these will appear after a while of use. Copilot is a tool like any other, and as such it is good to know exactly what it can and cannot be used for.

For this purpose, I have written down some of the most common use cases that I encounter daily and that make the most of Copilot's power.

For the sake of simplicity, I will write most of the following snippets in Javascript.

Imitation

A natural feature of AI models is the ability to mimic faithfully, and therein lies the power of Copilot. If we provide it with an understandable blueprint, we can get it to mimic it any way we want.

Let's imagine that we have an array of animals and one of the animals selected (currentAnimal - it can be different each time).

const animals = ['badger', 'otter', 'bear', 'racoon'];

let currentAnimal = 'bear';

Now we will write a moveRight() function that will be called every time the right arrow is clicked. If there is no element left, we will start with the first one:

function moveRight() {
    let index = animals.indexOf(currentAnimal) + 1;

    if (index > animals.length - 1) {
        index = 0;
    }

    currentAnimal = animals[index];
}

It probably won't even be necessary to write the function declaration for Copilot to understand what's going on: I just added two newlines after the closing brace of the moveRight() function, and Copilot already suggests the following:

function moveLeft() {

This is probably not a big surprise. But if I accept, Copilot will propose the whole function body:

function moveLeft() {
    let index = animals.indexOf(currentAnimal) - 1;

    if (index < 0) {
        index = animals.length - 1;
    }

    currentAnimal = animals[index];
}

Ok, so what is the big deal here? Let's break down what has Copilot done.

  1. Pair completion: Copilot tends toward completeness. If I have something that describes one half of the problem, it likes to suggest the other half. This can be annoying in the rare case that it wasn't my goal, but very useful when I simply forget to finish it and am about to start writing some other code. In that case, this serves as a friendly reminder that I might be about to accidentally leave out an important part of the desired result.

  2. Maintaining code consistency: This function can be written in many ways, many of which are probably even better than our solution. Copilot doesn't try to outsmart us and respects our code style.

  3. Identification of the divergence: These two functions are similar, but there are some key differences. Copilot is very good at deciding what is similar and what is different about these functions. It correctly reverses the endless circle logic for the opposite direction.

On the other hand, you should not expect Copilot to fix your bad code if not asked to. For example, if I had written the first function like this:

function moveRight() {
    let index = animals.indexOf(currentAnimal);

    if (index >= animals.length - 1) {
        index = 0;
    }

    currentAnimal = animals[index + 1];
}

it would have given me the following moveLeft() without hesitation:

function moveLeft() {
    let index = animals.indexOf(currentAnimal);

    if (index <= 0) {
        index = animals.length - 1;
    }

    currentAnimal = animals[index - 1];
}

What is the problem here? It may not be clear at first, but if you go right from the last one back to the first one, the first one is skipped. Likewise, if you go back to the last one from the first one to the left, it will also skip it.

Don't blame Copilot for your own mistakes. If you are not sure about your code, you can always ask Copilot to refactor it.

Refactoring

I don't use Copilot to refactor my code very often, relying instead on my intuition and experience. However, in some cases, you might find it very useful. Nevertheless, you should always be suspicious of any code that Copilot generates, and double-check that it meets your expectations. Be critical and optionally ask it to improve until you are satisfied with the result.

You shouldn't just accept Copilot's output as is; always explore its essence to empower your future coding

Let's rewrite the moveRight() function first. Start by writing a comment with the request.

// rewrite moveRight better

It offers us this simple solution

// rewrite moveRight better
function moveRight() {
    let index = animals.indexOf(currentAnimal);

    index = (index + 1) % animals.length;

    currentAnimal = animals[index];
}

However, we might want to have the function as a one-liner. So we will specify it:

// rewrite moveRight better as a one-liner
function moveRight() {
    currentAnimal = animals[(animals.indexOf(currentAnimal) + 1) % animals.length];
}

This seems fine, but it is starting to look pretty unreadable. It is our job to keep things simple, so we should decide which solution to choose.

A good example of why we shouldn't always rely on Copilot uncritically is this:

// rewrite moveRight better, but without using indexOf

Let's not be mistaken, this is completely possible. But the result does not reflect that:

// rewrite moveRight better, but without using indexOf
function moveRight() {
    let index = animals.indexOf(currentAnimal);

    if (index >= animals.length - 1) {
        index = 0;
    }

    currentAnimal = animals[index + 1];
}

This proves that you need to know, at least approximately, what you are doing when you use Copilot. Let's fix the "prompt":

// rewrite moveRight better, but without using indexOf and using a loop

The funny thing is (and this happens to me quite often) that when Copilot has to enter unknown territory, it tries to show some resistance, similar to a person who is forced to jump into unknown water. When I wrote the first comment and pressed Enter, it did not give me a function but added a similar comment for moveLeft and then started repeating it indefinitely.

// rewrite moveRight better, but without using indexOf but using a loop
// rewrite moveLeft better, but without using indexOf but using a loop
// rewrite moveRight better, but without using indexOf but using a loop
// rewrite moveLeft better, but without using indexOf but using a loop
// rewrite moveRight better, but without using indexOf but using a loop
// rewrite moveLeft better, but without using indexOf but using a loop

So I had to force the desired result by writing function just below my initial comment. Here it had no turning point, so it finally committed itself to this task.

// rewrite moveRight better, but without using indexOf but using a loop
function moveRight() {
    let index = 0;

    for (let i = 0; i < animals.length; i++) {
        if (animals[i] === currentAnimal) {
            index = i;
        }
    }

    if (index >= animals.length - 1) {
        index = 0;
    }

    currentAnimal = animals[index + 1];
}

Is this solution the best? If you don't know, don't rely on Copilot to know any better.