Blog

Akava is a technology transformation consultancy delivering

delightful digital native, cloud, devops, web and mobile products that massively scale.

We write about
Current & Emergent Trends,
Tools, Frameworks
and Best Practices for
technology enthusiasts!

AI Powered Software Development

AI Powered Software Development

Subir Chowdhuri Subir Chowdhuri
16 minute read

Artificial Intelligence (AI) is all the hype right now. Large language models (LLM) like OpenAI’s ChatGPT are used for everything from learning to speak a foreign language to creating delicious cake recipes. The general applicability of these systems lends itself to many use cases, so why not software development?

If you’ve searched for AI and programming on YouTube lately, you’ve noticed that many people already use AI for software development. Version 4 of GPT is well suited to write code and help explain it, and then there is GitHub Copilot, which uses OpenAI’s Codex, a GPT version fine-tuned for writing code. 

So, in this article, we will check out what these AI tools offer for software development, what problems you should look out for, and how you can get the most out of them. So, let’s dive in!

What is Artificial Intelligence?

Recently, the hyped AI flavor is large language models (LLMs), specifically LLMs implemented as generative pre-trained transformers (GPTs). The software looks at vast amounts of data, for example, texts on the Web, and then uses this data to create a model. The model is, in turn, also a software we can feed text, which it will then complete according to the data that trained the model. So, you could think of an LLM as your smartphone autocomplete on steroids.

OpenAI even went a step further, trained a system to answer questions, and called it ChatGPT. Which moves the whole interaction from the autocomplete paradigm to conversations.

Why are LLMs Useful for Programming?

LLMs can complete text, and programming is usually done by writing text. This means an LLM can write code if it is trained on source code. If it was trained on source code and human language, it could even write both code and text.

How to Speed Up Software Development with ChatGPT, GitHub Copilot

Prominent LLMs, like GitHub Copilot and GPT4, were trained on both, so they can do just that, complete your code and even answer questions about your code or write code according to the guidelines you give them.

According to GitHub research, developers completed coding tasks about 50% quicker when using Copilot compared to those who didn’t use it. So it might be worth checking out how to use these shiny new tools for your everyday coding work.

Using ChatGPT Effectively for Coding

Let’s start with ChatGPT, the most prominent LLM on the AI market. It’s like a text chat with another person. Using ChatGPT as a coding companion means you discuss your code with it. You write questions and paste your code into the input field, and ChatGPT will give you answers. 

The GPT4 version is exceptionally well suited for programming tasks but requires a ChatGPT Pro subscription and (at the time this article was written) only allows 25 questions per 3 hours, which means a bit more than one question every 10 minutes.

Prompting

Prompting is supplying AI with information related to the task at hand. 

ChatGPT is a general LLM that can handle all kinds of tasks, not just programming-related ones. It was created to talk with the average human on a daily basis, which might be different from what you want when programming. ChatGPT remembers information given in one session, so you can instruct it with your first messages before starting the development.

A prompt for a coding companion could look like this:

You are a senior JavaScript developer with over ten years of experience. You care about clean coding practices and always comment code in meaningful ways. Your answers are concise and terse, using bullet points when possible. You mark answers you’re not sure about with an asterisk, and when asking for code, you answer with the source code and only add a minimal explanation if not explicitly told otherwise.

ChatGPT Reply:

Understood. I'm ready to assist with your JavaScript inquiries. Please go ahead with your questions.

This prompt may seem strange. After all, how can you tell someone who they are? But it’s like effective “googling”. Google expects some input to give you the best answers; the same goes for ChatGPT.

This is just an example to get you started. You can modify that prompt to your needs, change languages, tell the system to use functional programming, or explain every answer step-by-step.

How ChatGPT Can Help You Programming

After you primed ChatGPT, you can ask questions or give it programming tasks.

Generating Code

ChatGPT, especially the GPT4 version, is reasonably good at writing code. But remember that GPT uses sampling to generate slightly different answers for the same question. This means the code can have bugs or security vulnerabilities or perform poorly. 

It’s well suited to generate code you could write yourself but don’t want to because it’s cumbersome. Let it implement sorting algorithms, boilerplate code, or design patterns. It writes faster than you, but not necessarily better code, so if you let it write too much code that is hard to reason about, you might lose the speed gain when debugging later.

Example Input:

Write an Article class. It should use private fields and an asynchronous factory function to load multiple articles at once.

ChatGPT Reply:

Here's a basic structure for the Article class, utilizing private fields and an asynchronous factory function. This example simulates loading data, rather than using real data:

class Article {

  // Private fields

  #id;

  #title;

  #content;

  constructor(id, title, content) {

    this.#id = id;

    this.#title = title;

    this.#content = content;

  }

 

  // Getters for private fields

  get id() {

    return this.#id;

  }

  get title() {

    return this.#title;

  }

  get content() {

    return this.#content;

  }

 

  // Async factory function to simulate loading articles

  static async loadArticles(count) {

    const articles = await new Promise((resolve, reject) => {

      setTimeout(() => {

        const articles = [];

        for (let i = 0; i < count; i++) {

          articles.push(new Article(i, Article ${i}, Content of article ${i}));

        }

        resolve(articles);

      }, 1000);

    });

    return articles;

  }

}

 

// Usage

Article.loadArticles(5)

  .then(articles => {

    for(let article of articles) {

      console.log(article.id, article.title, article.content);

    }

  })

  .catch(error => {

    console.log('Error:', error);

  });

In this example, each Article instance has private fields #id, #title, and #content, with public getters to access these. The static async factory function loadArticles(count) simulates loading a specified number of Article instances from a source, in this case a timeout function creating mock data. Each Article is created with a unique ID, title, and content.

Optimizing Existing Code

AI can read your code and optimize it. For example, when you work with a new programming language and don’t know how to write idiomatic code. Just paste the code you want it to optimize after briefly explaining what to do. It always helps to be a bit more explicit about what you want it to change. The less wiggle-room ChatGPT has for its answers, the better.

Example Input:

Optimize the following functions' time complexity: 

function swap(a, i, j) {

  let temp = a[i]

  a[i] = a[j]

  a[j] = temp

}

function bubbleSortBasic(a) {

  for(let i = 0; i < a.length; i += 1) 

    for(let j = 1; j < a.length; j += 1) 

      if(a[j - 1] > arr[j]) swap(arr, j - 1, j) 

  return a

}

ChatGPT Output:

The Bubble Sort algorithm is not an efficient sorting algorithm. Its average and worst-case time complexity is O(n^2), which is less efficient for larger lists than other algorithms like QuickSort, MergeSort, or HeapSort.

However, there is a slight optimization we can do to improve Bubble Sort's best-case time complexity. If during a pass through the array no swaps are made, the array is already sorted and there's no need to continue. We can use a flag to check this condition.

function bubbleSortOptimized(a) {

  let swapped

  for(let i = 0; i < a.length; i += 1) {

    swapped = false

    for(let j = 1; j < a.length - i; j += 1) {

// Ignore 'i' elements at end of array as they're already sorted

      if(a[j - 1] > a[j]) {

        [a[j - 1], a[j]] = [a[j], a[j - 1]]

// Swapping elements using destructuring

        swapped = true

      }

    }

    // If no elements were swapped in a pass, the array is sorted

    if(!swapped) break

  }

  return a

}

Now, the optimized bubble sort has a best-case time complexity of O(n) (when the input is already sorted) but maintains the average and worst-case time complexity of O(n^2). For better average and worst-case time complexity, consider using a different sorting algorithm that is more suited to your specific needs or constraints.

Explaining Code

You can paste the code and ask for an explanation. A typical example is regular expressions, which are pretty terse. But you can use this for all code in your system if you give ChatGPT enough context. 

Example Input:

Explain the following function:

function hashCode(str) {

    let hash = 0

    for (let i = 0, len = str.length; i < len; i++) {

        let chr = str.charCodeAt(i)

        hash = (hash << 5) - hash + chr

        hash |= 0 // Convert to 32bit integer

    }

    return hash

}

ChatGPT Output:

This is a simple hash function that is used to convert a string into an integer. It's a simplified version of the DJB2 hash algorithm, which is a well-known non-cryptographic hashing function. Here's how it works:

  • `let hash = 0`: This initializes the hash to 0

  • `for (let i = 0, len = str.length; i < len; i++)`: This loop goes through each character of the input string.

  • `let chr = str.charCodeAt(i)`: `charCodeAt(i)` gives the Unicode of the character at position i. The value of the Unicode character is stored in the variable chr.

  • `hash = (hash << 5) - hash + chr`: The `<<` is the bitwise left shift operator. It shifts the bits of hash 5 places to the left, adding zeros at the right. This operation effectively multiplies the hash by 32. Then the original hash is subtracted, and chr is added. This new value is assigned to hash

  • `hash |= 0`: The `|=` operator is a bitwise OR assignment. Here, it is used to convert hash to a 32-bit integer. In JavaScript, bitwise operators treat their operands as a sequence of 32 bits. When you use a bitwise operation, it will operate on the full 32 bits and return a 32-bit integer, effectively discarding any excess bits if the number was more than 32 bits.

  • `return hash`: Finally, the hash value is returned.

It's worth noting that while this function is quick and generally distributes different strings across different hash values well, it's not suitable for cryptographic purposes. It can also generate the same hash for different strings (a collision), which may or may not be acceptable depending on the use case.

Converting Algorithms

If you only know one programming language but need an implementation in one you don’t know, paste it and tell ChatGPT to convert it. This also works for conversion from imperative to functional programming. Just be reasonably explicit about your goals; otherwise, ChatGPT will use the next best approximation, which might be slow or buggy.

Example Input:

Rewrite the following JavaScript function to OCaml:

function hashCode(str) {

    let hash = 0;

    for (let i = 0, len = str.length; i < len; i++) {

        let chr = str.charCodeAt(i);

        hash = (hash << 5) - hash + chr;

        hash |= 0; // Convert to 32bit integer

    }

    return hash;

}

ChatGPT Output:

Here is the function hashCode translated to OCaml:

let hash_code str =

  let len = String.length str in

  let rec helper hash i =

    if i >= len then hash

    else 

      let chr = Char.code (String.get str i) in

      let hash = (hash lsl 5) - hash + chr in

      helper hash (i + 1)

  in helper 0 0;;

This OCaml function hash_code mimics the behavior of your JavaScript function hashCode. The helper function helper uses recursion to iterate through the characters of the string (since OCaml doesn't typically use for-loops like JavaScript). 

The function Char.code is used to get the ASCII value of a character (similar to charCodeAt(i) in JavaScript), and lsl is the left shift operator equivalent to << in JavaScript. 

Also, note that OCaml's integer type is already a fixed size (typically 31 or 63 bits, including the sign bit), so we don't need to do a bitwise OR with 0 to ensure a 32-bit result like in the JavaScript function.

Generating Unit Tests

Testing is vital and automated tests are even more so. But who likes to write unit tests? ChatGPT AI doesn’t have an opinion on any topic, so paste the code you want tests for and tell it to implement them. It helps to explicitly mention the testing framework you’re using and the use cases that need to be covered.

Example Input:

Write unit tests for the following function:

function hashCode(str) {

    let hash = 0;

    for (let i = 0, len = str.length; i < len; i++) {

        let chr = str.charCodeAt(i);

        hash = (hash << 5) - hash + chr;

        hash |= 0; // Convert to 32bit integer

    }

    return hash;

}

ChatGPT Output:

The following unit tests are written for the hashCode function using the Mocha testing framework and the Chai assertion library:

const chai = require('chai');

const expect = chai.expect;

function hashCode(str) {

    let hash = 0;

    for (let i = 0, len = str.length; i < len; i++) {

        let chr = str.charCodeAt(i);

        hash = (hash << 5) - hash + chr;

        hash |= 0; // Convert to 32bit integer

    }

    return hash;

}

 

describe('hashCode', function() {

  it('should return 0 for an empty string', function() {

    expect(hashCode('')).to.equal(0);

  });

  it('should return the same hash code for identical strings', function() {

    expect(hashCode('Hello, World!')).to.equal(hashCode('Hello, World!'));

  });

 

  it('should return different hash codes for different strings', function() {

    expect(hashCode('Hello, World!')).not.to.equal(hashCode('Goodbye, World!'));

  });

 

  it('should return a number', function() {

    expect(hashCode('Hello, World!')).to.be.a('number');

  });

});

These tests cover the following cases:

  1. The function should return 0 for an empty string.

  2. The function should return the same hash code for two identical strings.

  3. The function should return different hash codes for two different strings.

  4. The function should always return a number.

Using GitHub Copilot Effectively

Copilot is a solution tailored to the needs of programmers. It integrates with Visual Studio and VSCode, acting as an autocomplete similar to IntelliSense. Copilot uses OpenAI in the background, a fine-tuned version for writing source code, previously known as OpenAI Codex.

Avoiding Common Copilot Pitfalls

Let’s start with usage patterns that might lead to suboptimal outcomes. 

Generating Complex Code

As with ChatGPT, don’t let Copilot create code you couldn’t write yourself. It might seem tempting at first, implementing things in seconds that would take you days of research, but Copilot isn’t reliable enough for such tasks.

Comment Based Programming

Writing a comment that explains what you need and then letting Copilot write the actual code might seem like a good idea and is similar to how you would use ChatGPT for programming.

The problem is that the generated solutions suffer from the same issues as when using ChatGPT. You must be very explicit in your comments or already have quite some code in a file (for context) to let Copilot write something worthwhile. If you waste time writing short stories about code you might not understand later, you don’t gain much development velocity.

Good Use Cases for Copilot

While Copilot AI isn’t well suited to generate a secure hashing algorithm on the fly, it can still perform practical tasks for you.

Generate Small Code Snippets

Name your functions and variables deliberately and create appropriate function signatures, and Copilot will generate valuable implementations. The more explicit you can be, the better. This also means that explicit types go a long way. If the type system doesn’t allow you to represent something (e.g., units), add it to the variable name. 

In contrast to comment-based coding, this is more explicit because you show Copilot what the surrounding code looks like and write the code you actually use.

Generate Boilerplate from Templates

When writing multiple slightly different implementations of the same function, Copilot will complete every new function with code that follows the style of the previous functions. It also uses type definitions to complete object creations, which are repetitive by nature.

Generate Transformation Code

Another repetitive task Copilot is good at is transforming data. Often you have a database and an API, but one gives you data in a different format than the other expects. Cumbersome work, but creating a variable with a name indicating a transformation hints at Copilot what to do. 

Summary

OpenAI ChatGPT and GitHub Copilot are powerful tools but have some pitfalls. Their simple interfaces might suggest that they’re plug-and-play, but they need context as a primer, or their results will fall short of your expectations.

Remember that these systems sample their responses; they focus more on variety than on the accuracy of their outputs. So never generate code you couldn’t write yourself; generate code you’re just too lazy to write yourself.

Akava would love to help your organization adapt, evolve and innovate your modernization initiatives. If you’re looking to discuss, strategize or implement any of these processes, reach out to [email protected] and reference this post.

« Back to Blog