The speed of software development can be as important as the quality of your code. By releasing great products faster than competitors, businesses have a better chance to overcome the competition and attract users.
GitHub Copilot is an AI-based tool that helps developers automate coding tasks and improve code quality. By analyzing entire projects, it suggests intelligent completions and generates functions, documentation, and even tests, helping your team save valuable time.
According to GitHub, Copilot can accelerate coding by up to 55%. However, Copilotโs effectiveness depends on various factors, including your teamโs skill level and your projectโs limitations. We decided to test Copilotโs efficiency and discovered that for our (usually complex) projects, GitHub Copilot shortens development cycles by approximately 20%.
In this article, our development specialists provide a detailed GitHub Copilot review that covers key use cases and practical examples. This article will be useful for product managers and development leaders seeking ways to deliver high-quality products faster while maintaining control and security.
Contents:
What is GitHub Copilot?
GitHub Copilot is an AI-based development tool that assists software developers with coding tasks. Your development team can use it to do the following:
- Generate code
- Answer code-related questions
- Create explanations of commands
- Write pull request descriptions
- And more
GitHub Copilot is becoming increasingly popular in the development community due to its convenience and functionality. However, by helping developers, this tool brings value to businesses too.
Business benefits of GitHub Copilot
GitHub Copilot has many technical benefits, and your business can leverage it to help your business succeed.
- Accelerates time to market. GitHub Copilot shortens development cycles, helping businesses get their products to market faster. By streamlining coding, debugging, and testing, it enables quicker launches without compromising on quality.
- Reduces development costs. By speeding up coding, testing, and troubleshooting, GitHub Copilot lowers the overall cost of software development. It automates routine tasks, allowing developers to focus on higher-value work, resulting in more efficient resource use and a faster return on investment.
- Enhances productivity. Copilot enables faster task completion by providing automated code suggestions, documentation, and test creation. Additionally, it integrates with popular code editors like Visual Studio Code, Neovim, and JetBrains Fleet. This allows businesses to enhance their existing development environments without needing to switch tools or workflows.
- Improves code quality. By generating clean, reliable code with comprehensive documentation, GitHub Copilot reduces the risk of errors and makes the codebase easier to maintain. This leads to higher-quality products that require fewer fixes and are easier to scale.
- Improves security. GitHub Copilot helps developers follow secure coding practices by suggesting code that incorporates proper input validation, secure authentication mechanisms, and defenses against vulnerabilities like SQL and path injections. It may also recommend updated cryptographic algorithms, ensuring the use of modern and secure standards. Furthermore, Copilot can help prevent common security issues such as hardcoding credentials or mishandling sensitive data, reducing the likelihood of introducing vulnerabilities.
- Minimizes external dependencies. Developers can resolve issues internally using the integrated chat feature within IDEs without relying on external resources like Google or Stack Overflow. This saves time, reduces distractions, and keeps projects moving smoothly.
- Simplifies onboarding. Copilot can assist developers in quickly understanding legacy code, reducing the time and effort required to onboard new team members. This is especially valuable in large and complex projects where getting up to speed can take considerable time for new developers.
Note: Despite the numerous advantages of GitHub Copilot for software development, we respect our clientsโ concerns and policies and only use it when explicitly approved by a client. Information covered by an NDA is never shared with GitHub Copilot.
Now, letโs explore the main use cases of GitHub Copilot.
Speed up your time to market without sacrificing quality
Hire an experienced team of developers who use AI responsibly and deliver high-quality products fast.
Top GitHub Copilot use cases
GitHub Copilot is a versatile tool that can be used for a range of software development-related tasks. Although itโs not omnipotent and still requires the eye of an experienced developer, it can help your team with:
- Generating or completing code. GitHub Copilot can suggest whole functions, methods, or code snippets based on the context of your project. It helps developers write code faster by completing partial lines or generating entire blocks of code that fit within the structure of the existing codebase.
- Debugging. Copilot can help your team identify bugs by providing suggestions for fixing errors or improving problematic code.
- Writing documentation. Copilot automates the generation of code comments and documentation, helping developers maintain clarity and readability. This is especially useful for creating consistent, professional documentation while minimizing manual effort.
- Creating tests. GitHub Copilot helps to write unit tests by suggesting relevant test cases for your code, ensuring higher test coverage and improved software reliability.
- Legacy code support. When working with legacy code, Copilot can provide your team with explanations and insights into how the code works. This reduces the ramp-up time for new developers and simplifies ongoing maintenance.
Now, letโs demonstrate how to use GitHub Copilot effectively using practical examples from our development experience.
How to use Copilot effectively: practical examples
At Apriorit, we use an internally trained implementation of GitHub Copilot with our clientsโ approval. In this section, weโll show the most common ways we leverage this tool to deliver better code faster.
Automating repetitive tasks
One of the most useful advantages of GitHub Copilot for developers is its ability to handle small and simple yet time-consuming tasks. Your development team can delegate tasks related to testing and prototyping and save time for solving more complex issues.
Interface mocking
Mock interfaces are simplified software components that simulate the behavior of real systems. Theyโre important for testing because they allow developers to verify interactions between components without fully developing them with real dependencies. However, creating mock interfaces can be annoying and time-consuming.
GitHub Copilot can speed up this process and allow developers to focus on implementing core functionality rather than generating repetitive mock data. For instance, here is the interface of ICalculator in C++.
class ICalculator
{
public:
virtual ~ICalculator() = default;
virtual double Add(double a, double b) const = 0;
virtual double Subtract(double a, double b) const = 0;
virtual double Multiply(double a, double b) const = 0;
virtual double Divide(double a, double b) const = 0;
};
Letโs give Copilot this prompt:
> generate mock using old style of gmock for ICalculator
The result is the following:
class MockCalculator : public ICalculator
{
public:
MOCK_CONST_METHOD2(Add, double(double a, double b));
MOCK_CONST_METHOD2(Subtract, double(double a, double b));
MOCK_CONST_METHOD2(Multiply, double(double a, double b));
MOCK_CONST_METHOD2(Divide, double(double a, double b));
};
Done! A task that would take a developer several minutes took Copilot only one second.
Generating unit tests
Your team can use Copilot to generate unit tests for code, as it:
- Saves a lot of time
- Reduces errors in test writing
- Provides a starting point for complex testing scenarios
Letโs look at an example using our implementation of Calculator.
class Calculator : public ICalculator {
public:
double Add(double a, double b) const override {
return a + b;
}
double Subtract(double a, double b) const override {
return a - b;
}
double Multiply(double a, double b) const override {
return a * b;
}
double Divide(double a, double b) const override {
if (b == 0) {
throw std::invalid_argument("Division by zero");
}
return a / b;
}
};
Now, we will try this prompt in Copilot:
> generate unit tests for Calculator
Hereโs the result we receive:
#include <gtest/gtest.h>
#include "Calculator.h"
class CalculatorTest : public ::testing::Test {
protected:
Calculator calculator;
};
TEST_F(CalculatorTest, Add) {
EXPECT_DOUBLE_EQ(calculator.Add(1.0, 2.0), 3.0);
EXPECT_DOUBLE_EQ(calculator.Add(-1.0, -2.0), -3.0);
}
TEST_F(CalculatorTest, Subtract) {
EXPECT_DOUBLE_EQ(calculator.Subtract(5.0, 3.0), 2.0);
EXPECT_DOUBLE_EQ(calculator.Subtract(-5.0, -3.0), -2.0);
}
TEST_F(CalculatorTest, Multiply) {
EXPECT_DOUBLE_EQ(calculator.Multiply(2.0, 3.0), 6.0);
EXPECT_DOUBLE_EQ(calculator.Multiply(-2.0, -3.0), 6.0);
}
TEST_F(CalculatorTest, Divide) {
EXPECT_DOUBLE_EQ(calculator.Divide(6.0, 3.0), 2.0);
EXPECT_THROW(calculator.Divide(1.0, 0.0), std::invalid_argument);
}
Again, it takes about five seconds to prepare, type the prompt, and generate the result, as opposed to minutes spent coming up with the tests and typing them out.
Read also
Benefits and Risks of Outsourcing Engineering Services
Optimize costs, enhance expertise, and accelerate project timelines. Make the most of IT outsourcing by knowing what advantages to expect and how to address common concerns.
Generating commit messages
Often, developers use generic commit messages like โfixโ or โupdate.โ This is problematic because they lack context about the changes made, making it difficult for team members to quickly understand the purpose of a commit. Without clear commit messages, it becomes harder to track the history of changes, especially when reviewing a project or debugging issues later.
Letโs use the prompt โgenerate commit message for the patch belowโ and insert the patch. As a result, GitHub Copilot generates the following commit message:
add unit tests for Calculator class
- Added unit tests for all methods using Google Test framework
- Included edge case handling for division by zero
Copilot generated a clear commit message based on the changes developers made. This is valuable, as it helps to clearly document the projectโs historical evolution and allows team members to quickly understand each commitโs purpose.
Enhancing code quality and efficiency
Copilot can play the role of code reviewer and help your team quickly check their code or figure out what went wrong. Letโs see how it achieves this using the examples below.
Optimizing code
Sometimes, our developers work with legacy projects where existing code is inefficient. For example, the code might rely on loops or custom functions instead of built-in library functions. These older coding practices can make the project hard to maintain, slow to execute, and difficult to scale. GitHub Copilot can help to improve the code by suggesting algorithms from standard libraries. This allows developers to mitigate potential performance issues and enhance code readability.
For instance, hereโs a piece of legacy code that could use some improvement as it contains a loop.
std::vector<std::string> data_str = {...};
std::vector<int32_t> data_int = {...};
for (size_t i = 0; i < data_int.size(); ++i)
data_str.push_back(std::to_string(data_int[i]));
While functional, this loop can be replaced with a more concise and efficient solution from the standard library. Using standard library algorithms often provides better performance and clarity, as they are specifically designed for container processing.
Letโs use the prompt โimprove the codeโ to get the following optimized solution:
// Assuming data_str and data_int are initialized with some values
std::vector<std::string> data_str = {...};
std::vector<int32_t> data_int = {...};
// Reserve space to avoid multiple reallocations
data_str.reserve(data_str.size() + data_int.size());
// Use std::transform to convert and append integers to strings
std::transform(data_int.begin(), data_int.end(),
std::back_inserter(data_str), [](const auto& item) {
return std::to_string(item);
});
In this improved version, we:
- Reserve space in
data_str
to avoid multiple reallocations. - Use
std::transform
to convert and append integers to strings in a single, more efficient operation.
Note that GitHub Copilot automatically suggests and explains these improvements for better readability and performance.
Handling code and compilation errors
When developers encounter a compilation error or a function that doesnโt work as expected, they typically turn to resources like Google or Stack Overflow to find solutions. However, with GitHub Copilot, this isnโt necessary.
GitHub Copilot serves a similar purpose as those resources but has the advantage of being fully integrated into the project. Thanks to this immersion in the project context, it can offer precise and effective solutions to errors.
Querying unknown concepts
Developers frequently encounter non-standard programming concepts and syntax. Copilot can quickly explain a concept, allowing developers to spend more time coding and discussing architecture and less time searching for answers.
However, Copilot canโt replace learning the art of programming; responses can contain inaccuracies, so itโs important to verify them. Besides this, the more accurate and detailed the prompt, the higher the chances of receiving a working result.
Read also
Writing Clear Functional and Non-functional Requirements: Tips and Best Practices from Apriorit Experts
Ensure alignment and clarity throughout the development process by articulating both functional and non-functional requirements. Read our guide for useful tips on writing them.
Generating code using requirements
GitHub Copilot can process detailed requirements provided by a client or project manager. If these requirements involve changes or additions to the code, Copilot can implement them, saving developers a significant amount of time.
However, if the requirements refer to an external project, the generated code may be inaccurate or incomplete.
To test this ability, we sent 10 prompts with โrequirements as isโ to Copilot. Nine were successful, while one failed due to dependencies on multiple projects that GitHub Copilot couldnโt access.
For example, consider the ICalculator interface, which includes four standard operations (addition, subtraction, multiplication, and division). This interface is part of a library used by the client. Imagine the client sends additional requirements, such as:
- Square operation. Implement a method to calculate the square of a double value.
Method signature: double square(double a) const - Sine operation. Implement a method to calculate the sine of a double value (in radians).
Method signature: double sin(double a) const - Cosine operation
Implement a method to calculate the cosine of a double value (in radians).
Method signature: double cos(double a) const
Letโs use a detailed prompt asking Copilot to update the ICalculator interface and the Calculator implementation.
First, Copilot updates the interface and implementation:
class ICalculator {
public:
// ... previous code is hidden
virtual double square(double a) const = 0;
virtual double sin(double a) const = 0;
virtual double cos(double a) const = 0;
};
#include <cmath>
class Calculator : public ICalculator {
public:
// ... previous code is hidden
double Square(double a) const override {
return a * a;
}
double Sin(double a) const override {
return std::sin(a);
}
double Cos(double a) const override {
return std::cos(a);
}
};
Then, Copilot generates unit tests to verify the functionality of the Calculator class. Each test ensures that the new methods (Square, Sin, and Cos) perform as expected:
// previous tests are hidden
TEST(CalculatorTest, Square) {
EXPECT_DOUBLE_EQ(calculator.Square(5), 25);
EXPECT_DOUBLE_EQ(calculator.Square(-4), 16);
}
TEST(CalculatorTest, Sin) {
EXPECT_NEAR(calculator.Sin(3.14159 / 2), 1.0, 1e-5); // Approx. ฯ/2 radians
EXPECT_NEAR(calculator.Sin(0), 0.0, 1e-5);
}
TEST(CalculatorTest, Cos) {
EXPECT_NEAR(calculator.Cos(0), 1.0, 1e-5);
EXPECT_NEAR(calculator.Cos(3.14159), -1.0, 1e-5); // Approx. ฯ radians
}
As you can see, Copilot can easily generate these additions, saving developers time and effort. However, this example is rather simple; for more complex tasks, you need an expert team that can verify the Copilot output.
Related project
Building an AI-based Healthcare Solution
Discover how Apriorit accelerated and simplified doctorsโ routine tasks by building an AI-based system. Our client got an efficient solution that can track, detect, and measure ovarian follicles in video footage with astonishing accuracy.
What to consider when using Copilot for software development
Despite its advantages, Copilot isnโt perfect โ it still requires attention and oversight from experienced developers. When you decide to use Copilot in your project, your team should be aware of the following limitations:
- Limited work with complex code. GitHub Copilot struggles to generate accurate tests for complex code, particularly when multiple classes interact with a main class. As a result, generated tests may not work correctly, requiring developers to manually adjust the test implementation. However, Copilot is useful for naming unit tests and creating a foundation for reusable tests.
- No access to external libraries or projects. Copilot can analyze internal project code but lacks access to external libraries or projects. This limitation can affect the accuracy and completeness of generated code or tests.
- Limited quality of generated code. The quality of generated code doesnโt always meet expectations, and code may occasionally fail to function. Developers often need to refine prompts or regenerate code for better results.
Letโs demonstrate how Copilot can make mistakes in code. Consider a case where a developer has two vectors:
std::vector<std::string> data_str = {...};
std::vector<int32_t> data_int = {...};
We ask Copilot to create a map pairing strings with integers using std::transform
in C++17, and hereโs the result we receive:
std::vector<std::string> data_str = {"key0", "key1", "key2"};
std::vector<int32_t> data_int = {10, 20, 30};
std::map<std::string, int32_t> data_map;
std::transform(data_str.begin(), data_str.end(), data_int.begin(),
std::inserter(data_map, data_map.end()),
{
return std::make_pair(key, value);
});
// Print the map to verify
for (const auto& pair : data_map) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
The problem is that the generated code is incorrect because of an issue in the final parameter of std::transform
. The corrected code should use a lambda with explicit parameters:
std::transform(data_str.begin(), data_str.end(), data_int.begin(),
std::inserter(data_map, data_map.end()), [](const auto& key, const auto& value)
{
return std::make_pair(key, value);
});
Alternatively, std::pair
can replace std::make_pair
for improved clarity. While Copilot provides a starting point, developers must often debug and refine the generated code to ensure it works as intended.
Conclusion
Accelerating development without sacrificing quality has always been a holy grail for businesses and software developers. With GitHub Copilot, it has finally become possible.
While Copilot canโt replace a team of professional developers, it helps engineers bypass routine coding tasks and focus on complex, value-driven solutions for clients. By using Copilotโs automation capabilities, our team improves efficiency and delivers high-quality results in a shorter time.
If youโd like to complete your projects faster, hire a dedicated Apriorit team for expert software development services. With your consent, we will use the latest tools to push your product to the market faster and help you get ahead of your competition.
Looking for a team of developers with rare skills?
We do what AI isnโt capable of. From reverse engineering to kernel development, we are ready to tackle any task regardless of its complexity.