ChatGPT vs. Copilot Chat vs. Bard vs. Bing Chat: Test av AI til koding

Noen av forslagene var så dumme at vi sjelden har sett maken, og vinneren er bare ørlite granne bedre enn konkurrentene.

Kurt Lekanger er journalist i kode24, og bruker ofte AI-chatboter til å få kodehjelp. 📸: Ole Petter Baugerød Stokke
Kurt Lekanger er journalist i kode24, og bruker ofte AI-chatboter til å få kodehjelp. 📸: Ole Petter Baugerød Stokke Vis mer

Mange utviklere bruker AI-chatboter som ChatGPT og Copilot Chat til å få hjelp med kodingen – for eksempel hvis man er usikker på syntaks, sliter med å finne en bug eller bare trenger hjelp til å få skrevet koden litt kjappere enn man klarer på egen hånd.

Men hvor gode er de ulike chatbotene, og spiller det noen rolle hvilken du bruker?

Vi bestemte oss for å sette noen av de mest brukte på prøve ved å gi dem noen enkle og noen litt vanskeligere kodeoppgaver. Dette er AI-chatbotene vi har testet:

Vi har altså prøvd chattefunksjoner, og ikke sett på kodefullføring i kodeeditorer eller det å få skrevet kode ved å starte med en kommentar – noe mange bruker Copilot til.

Vi gjør oppmerksom på at Bing Chat nylig byttet navn til Copilot (!) og er tilgjengelig på copilot.microsoft.com. Denne er imidlertid ikke den samme som Copilot Chat i VS Code – ettersom sistnevnte har koden din som kontekst når den svarer.

Finn bugs og skriv kode

Så hva skal vi spørre AI-chatbotene om? I denne minitesten/stikkprøven bestemte vi oss for å fokusere på to områder:

  • Finne bugs i kode: Til disse plukket vi ut to ulike oppgaver fra nyhetsbrevet Bytes, som hver uke har en "Spot the bug"-oppgave.
  • Få hjelp til å skrive kodesnutter basert på en beskrivelse av hva du vil gjøre: Her fant vi to forskjellige oppgaver på Codewars. Vi prøvde én oppgave med vanskelighetsgrad 5 og én med vanskelighetsgrad 7 av 8 (nest lettest), og vanilla JavaScript som programmeringsspråk.

Vi understreker at dette er en stikkprøve, ettersom slike AI-chatboter er temmelig uforutsigbare. Hadde vi stilt samme spørsmål én gang til, kunne vi fått helt andre svar.

Oppgave #1: Finn feilen

Den første oppgaven var å finne bugen i kodesnutten under, og vi skrev samme prompt til alle chatbotene: "Find the bug in this code:" etterfulgt av koden:

class Person {
  #name;

  constructor(name) {
    this.#name = name;
  }

  introduce() {
    console.log(`Hello, my name is ${this.#name}.`);
  }
}

class Student extends Person {
  #grade;

  constructor(name, grade) {
    super(name);
    this.#grade = grade;
  }

  introduce() {
    console.log(
      `Hello, my name is ${this.#name} and I am in grade ${this.#grade}.`
    );
  }
}

const student = new Student("John", 5);
student.introduce();

Feilen i koden er at name-feltet i Person-klassen har en hashtag (#) foran seg, det vil si at den er merket som et privat felt. Dermed er ikke name tilgjengelig fra subklassen Student.

Slik klarte chatbotene seg:

🤖 Bing: Fant ikke feilen
Bing fant ikke feilen, men påsto skråsikkert at "The code you provided looks correct" etterfulgt av en forklaring av hva koden gjør. Bing påsto også at den hadde kjørt koden og fått ut "Hello, my name is John and I am in grade 5." i konsollen. "Det ser riktig ut, og indikerer ingen bugs i koden", sa Bing. Neppe riktig!

🤖 Bard: Fikset bugen – med dårlig forklaring
Bard fant feilen og ga oss en ny kodesnutt der feilen var rettet opp ved å legge til en getter i Person-klassen og bruke den i Student-subklassen for å hente ut navnet til personen:

class Person {
  #name;

  constructor(name) {
    this.#name = name;
  }

  get name() {
    return this.#name;
  }

  introduce() {
    console.log(`Hello, my name is ${this.name}.`);
  }
}

class Student extends Person {
  #grade;

  constructor(name, grade) {
    super(name);
    this.#grade = grade;
  }

  get grade() {
    return this.#grade;
  }

  introduce() {
    console.log(
      `Hello, my name is ${this.name} and I am in grade ${this.grade}.`
    );
  }
}

Forklaringen på hvorfor det ikke virket haltet imidlertid litt.

🤖 ChatGPT: Foreslo noe som ikke virket
Også ChatGPT klarte i likhet med Bard å skjønne at det hadde å gjøre med at #name i Person-klassen var et privat felt som ikke kunne aksesseres direkte fra en subklasse. Forklaringen var imidlertid ikke helt presis. Og mens Bard foreslo å lage en getter i Person-klassen for å aksessere #name fra andre klasser, foreslo ChatGPT å aksessere #name fra Student-subklassen ved å skrive super.#name. Det fungerer ikke!

Her måtte jeg prøve å hjelpe en stakkars robot. Så jeg skrev inn:

I get the error message "Unexpected private field" when using super.#name in the Student sub class.

Og voilà: Med litt hjelp skjønte ChatGPT hva som var feil, og ga meg en kodesnutt som fungerte – med en getter for #name i Person-klassen, altså det samme som Bard foreslo.

🤖 Copilot Chat: Lett!
Copilot Chat skjønte lett hva som var feil, og ga oss en kortfattet og fin forklaring med en ny kodesnutt der det var lagt til en getter-metode kalt getName() som kunne aksesseres fra subklassen Student.

Oppgave #2: Ikke gjør dette i React!

Som oppgave to i kategorien "finn bugen" skrev vi "Find the bug" og ga alle chatbotene denne kodesnutten:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>Increment</button>
    </>
  )
}

For de som ikke vet det: du kan ikke kjøre setNumber tre ganger etter hverandre slik som i kodeeksempelet, og forvente at number øker med tre. React oppdaterer nemlig ikke state umiddelbart, men bare mellom hver rendring av komponenten. Så hvis number er 0 og du kjører setNumber(number +1) tre ganger etter hverandre i samme rendringssyklus, er det det samme som å skrive setNumber(0 + 1) tre ganger. Number er da fortsatt bare 1.

Det du må gjøre er å sende inn en callbackfunksjon til setNumber, slik: setNumber(n => n + 1). Da vil React hive alle disse funksjonene i en kø, og kjøre dem én etter én neste gang komponenten rendres. Og number blir satt til 3.

La oss se hvordan chatbotene klarte seg:

🤖 Bing: Er det noen sak!
Bing klarte oppgaven med glans, og forklarte at "The bug in this code is that the setNumber function is called three times in a row with the same value of number." og hvorfor det er slik. Så fikk vi denne rettede kodesnutten, som var identisk med "fasiten" på kodeoppgaven til Bytes.dev:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(prevState => prevState + 1);
        setNumber(prevState => prevState + 1);
        setNumber(prevState => prevState + 1);
      }}>Increment</button>
    </>
  )
}

🤖 Bard: Så du tror du er smart..?
Bard trodde den var smart, og kanskje den var det – eller kanskje den ikke skjønte hva utvikleren ville gjøre. Forslaget var nemlig å skrive onClick={() => setNumber((prevNumber) => prevNumber + 1)} , som er riktig – men å gjøre det bare én gang. Men nå legger vi jo bare til 1 – og slik i hvert fall undertegnede tolket oppgaven skulle vi legge til 1 tre ganger.

🤖 ChatGPT: Dette kan vi!
ChatGPT forsto umiddelbart hva problemet var, og forklarte at det har å gjøre med setNumber-funksjonen er asynkron og at ikke state blir oppdatert umiddelbart når du kaller den. Og så fikk vi omtrent samme fungerende kodesnutt som fra Bing.

🤖 Copilot Chat: Riktig på første forsøk
Igjen utmerket Copilot Chat seg med en veldig god forklaring på hvorfor koden ikke virker, og et fint forslag til fungerende kode (omtrent den samme som de andre foreslo).

Oppgave #3: Molekyler til atomer ⚛

Å finne ut av useState i React var tydeligvis for lett for koderobotene – så hva med å prøve å få dem til å skrive litt mer omfattende kode basert på en ganske komplisert beskrivelse?

For å sjekke hvor gode de ulike chatte-robotene var til å løse kodeoppgaver, fant vi et par forskjellige oppgaver på Codewars. Disse er rangert etter vanskelighetsgrad, der 1 er vanskeligst og 8 er enklest. Til første oppgave la vi oss på 5 i vanskelighetsgrad og valgte vanilla JavaScript som programmeringsspråk.

Oppgaven var som følger, og kopiert rett inn fra denne oppgaveteksten hos Codewars:

"Solve this in JavaScript:

For a given chemical formula represented by a string, count the number of atoms of each element contained in the molecule and return an object. For example:

var water = 'H2O';
parseMolecule(water); // return {H: 2, O: 1}

var magnesiumHydroxide = 'Mg(OH)2';
parseMolecule(magnesiumHydroxide); // return {Mg: 1, O: 2, H: 2}

var fremySalt = 'K4[ON(SO3)2]2';
parseMolecule(fremySalt); // return {K: 4, O: 14, N: 2, S: 4}

As you can see, some formulas have brackets in them. The index outside the brackets tells you that you have to multiply count of each atom inside the bracket on this index. For example, in Fe(NO3)2 you have one iron atom, two nitrogen atoms and six oxygen atoms.

Note that brackets may be round, square or curly and can also be nested. Index after the braces is optional."

Dette er i hvert fall en oppgave som jeg syntes var vanskelig – og som jeg sikkert ville brukt flere timer på å løse. La oss se hvordan AI-chatbotene klarte seg.

🤖 Bing: Eneste som klarte oppgaven
Bing løste den på første forsøk, med en temmelig innviklet kodesnutt:

function parseMolecule(formula) {
  const stack = [{}];
  let i = 0;
  while (i < formula.length) {
    if (formula[i] === '(' || formula[i] === '[' || formula[i] === '{') {
      stack.unshift({});
      i++;
    } else if (formula[i] === ')' || formula[i] === ']' || formula[i] === '}') {
      const top = stack.shift();
      i++;
      let j = i;
      while (j < formula.length && /\d/.test(formula[j])) j++;
      const factor = j > i ? parseInt(formula.slice(i, j)) : 1;
      for (const [atom, count] of Object.entries(top)) {
        stack[0][atom] = (stack[0][atom] || 0) + count * factor;
      }
      i = j;
    } else {
      let j = i + 1;
      while (j < formula.length && /[a-z]/.test(formula[j])) j++;
      const atom = formula.slice(i, j);
      i = j;
      while (j < formula.length && /\d/.test(formula[j])) j++;
      const count = j > i ? parseInt(formula.slice(i, j)) : 1;
      stack[0][atom] = (stack[0][atom] || 0) + count;
      i = j;
    }
  }
  return stack.pop();
}

Innviklet, men koden besto alle testcasene til Codewars.

Til sammenligning, så er dette de to forslagene som Codewars-brukerne har stemt frem som de to beste – henholdsvis "Best practice" og "Clever":

// Best practice
function parseMolecule2(formula) {
  var group, tokens, tokenExp = /([{(\[]|[})\]]|[A-Z][a-z]?)(\d*)/g, stack = [[]];
  while (tokens = tokenExp.exec(formula)) {
    tokens[2] = tokens[2] || 1;
    console.log("my tokens: ", tokens)
    if (/^[A-Z]/.test(tokens[1])) {
      while (tokens[2]--) stack.push(stack.pop().concat([tokens[1]]));
    } else if (/[{\(\[]/.test(tokens[1])) {
      stack.push([]);
    } else {
      group = stack.pop();
      while (tokens[2]--) stack.push(stack.pop().concat(group))
    }
  }
  return stack[0].reduce(function (count, x) {
    count[x] = (count[x] || 0) + 1;
    return count;
  }, {});
}

// Clever
function parseMolecule(s) {
  var o = {}
  while (s != (s = s.replace(/[\[\(\{]([a-z0-9]+)[\]\)\}]([0-9]+)/gi, (f,e,n) => repeat(e,n))));
  s.replace(/([A-Z][a-z]?)([0-9]+)?/g, (f,e,n) => (o[e] = (o[e] || 0) + +(n || 1)));
  return o;
}

function repeat(s, n) {
  for (var r = ""; n--; r += s);
  return r;
}

Bing var den eneste som klarte oppgaven. Her er hvordan de andre klarte seg:

🤖 Bard: Bare rør – selv etter tre forsøk
Dette var åpenbart en vanskelig oppgave. For Bard kom først med en kodesnutt som klarte å finne alle grunnstoffene, men ikke riktig antall atomer for hvert grunnstoff. Vi forsøkte å forklare feilen, men selv etter tre forsøk klarte ikke Bard å få det til.

🤖 ChatGPT: Feilmelding
ChatGPT slet også med denne oppgaven, som åpenbart må ha vært veldig vanskelig. Faktisk så vanskelig at mange på Codewars har kommentert at den burde vært merket med en vanskeligere vanskelighetsgrad.

Selv etter tre forsøk på å rettlede chatboten, fikk vi ikke ChatGPT-koden til å fungere.

ChatGPTs kode passerte ingen av testene.
ChatGPTs kode passerte ingen av testene. Vis mer

🤖 Copilot Chat: Klarte 1 av 3 tester
Copilot kom opp med en kodesnutt som tilsynelatende virket ganske mye enklere og mer lettfattelig enn de andre forslagene. Optimismen var derfor stor da den besto den første av tre testcaser – parse den kjemiske formelen for vann (H2O) og returnere et objekt med antall av de ulike atomene ({H: 2, O: 1}).

Men så feilet koden på alle formler som inneholdt parenteser. Selv etter tre forsøk på å peke Copilot i riktig retning, klarte den ikke å løse oppgaven.

Oppgave #4: Vi prøver noe enklere

Det var åpenbart at det å konvertere molekyler til atomer var en oppgave som var altfor vanskelig for en stakkars AI-chatbot. Vi gikk derfor ned på det nest letteste nivået i Codewars og fant en ny og enklere oppgave.

Make a function in JavaScript called getCount(str) that returns the number (count) of vowels in the given string. We will consider a, e, i, o, u as vowels for this Kata (but not y). The input string will only consist of lower case letters and/or spaces.

Dette gikk bedre!

🤖 Bing: Ryddig, men omstendelig
Også denne oppgaven klarte Bing på første forsøk. Dette er koden som ble foreslått:

function getCount(str) {
  const vowels = ['a', 'e', 'i', 'o', 'u'];
  let count = 0;
  for (let i = 0; i < str.length; i++) {
    if (vowels.includes(str[i])) {
      count++;
    }
  }
  return count;
}

Ryddig, fint og lett å skjønne dette, selv om koden var litt omstendelig. Og koden passerte testene til Codewars.

🤖 Bard: for..of
Bard foreslo nesten det samme som Bing – men i stedet for en for-løkke som teller på "gamlemåten" bruker den den litt mer moderne for..of-måten å gjøre ting på. Jeg liker den bedre, siden jeg synes det gjør koden enda mer lettlest.

function getCount(str) {
  const vowels = ['a', 'e', 'i', 'o', 'u'];
  let count = 0;

  for (const char of str) {
    if (vowels.includes(char)) {
      count++;
    }
  }

  return count;
}

Alle testene ble bestått.

🤖 ChatGPT: Regex med forklaringer
ChatGPT foreslo å bruke Regex til å løse oppgaven, og slang til og med på noen kommentarer for å forklare hva som skjer:

function getCount(str) {
  // Define a regular expression to match vowels (a, e, i, o, u)
  const vowelRegex = /[aeiou]/g;

  // Use the match method to find all matches of the regex in the string
  const matches = str.match(vowelRegex);

  // If there are matches, return the count; otherwise, return 0
  return matches ? matches.length : 0;
}

🤖 Copilot Chat:
Copilot ga en fin forklaring og en kodesnutt som fungerte perfekt:

function getCount(str) {
  const matches = str.match(/[aeiou]/g);
  return matches ? matches.length : 0;
}

Dette er definitivt den korteste og enkleste måten å løse oppgaven på av de tre forslagene – og den metoden jeg håper jeg selv ville kommet frem til.

Alle Codewars-testene ble bestått.

Det var spennende å se om noen på Codewars hadde kommet frem til noe enda bedre – og det var det. Løsningen som ble stemt frem som både "best practice" og "clever" er imidlertid den samme som Copilot- og ChatGPT-forslagene, men de kodelinjene er slått sammen til ett enkelt return-statement:

function getCount(str) {
  return (str.match(/[aeiou]/ig)||[]).length;
}

...og vinneren er: Copilot Chat!

Oppsummert er dette hvordan de fire AI-chatbotene klarte seg:

  • Copilot: Klarte 3 av 4 oppgaver. Beste forslag på oppgave 4.
  • Bing: Klarte 3 av 4 oppgaver. Den eneste som klarte den aller vanskeligste molekyl-til-atom-oppgaven.
  • Bard: Klarte 2 av 4 oppgaver. Den ene med litt dårlig forklaring. Pluss for å bruke moderne for..of-syntaks.
  • ChatGPT: Klarte 2 av 4 oppgaver. Fine forklaringer på React-oppgaven.

I denne stikkprøven skilte Bing og Copilot seg ut som de beste. Bing var den eneste som klarte den supervanskelige molekyl-til-atom-oppgaven. Men samtidig klarte den ikke å skjønne hvorfor et privat felt i en klasse ikke var tilgjengelig i en subklasse (det klarte heller ikke ChatGPT).

Totalt sett, hvis vi ser på hvor god kode de ulike AI-chatbotene lager og hvor godt de forklarer koden, lander jeg på Copilot Chat som vinner – hårfint foran Bing.

Copilot Chat koster imidlertid penger – så er du ute etter et gratisalternativ og ikke har forsøkt Bing Chat, ja da vet du hva du må gjøre!

Vi gjør også oppmerksom på at vi som nevnt innledningsvis har brukt gratisversjonen av ChatGPT (GPT 3.5). Betalversjonen bruker GPT 4 og skal være bedre.