ChatGPT - Making Developers More Productive

May 13, 2023 Written By: Scott Sinclair

The more I use ChatGPT 4.0, the more impressed I become with what it can do. As developers, we should not feel threatened by this technology, but rather should embrace it to help us be more productive and effective. If your job involves sitting in front of a computer monitor for most of the day, chances are that you can increase your productivity by 10 - 30% just by leveraging ChatGPT. This is especially true if are involved in a technical role such as:

Historically, people in technical roles needed to spend quite a lot of time searching for solutions to their problems. Often, this would involve iterating through website after website trying to find a site that fully covers what they are looking for. Now, there is a new kid in town, named ChatGPT, that lets you cut to the chase and get your answer, usually in the first shot.

In this post, we will go through a "Day in the Life of a Software Developer" and identify various problems that a typical programmer might need to solve; how they asked ChatGPT for help and what information ChatGPT provided. Most of the answers are from ChatGPT 4, which is the paid version. These answers tend to be more comprehensive than those provided by ChatGPT 3.5 but this free version also provides great results for most questions. Before we start asking ChatGPT some questions, let's go over some basics of Prompt Engineering.

Prompt Engineering

Just like with Google searches, you will get better results from ChatGPT if you ask it very specific questions that include everything that you want it to consider before answering. One thing that ChatGPT does not do, that I wish it would, is seek clarification. In other words, when you ask it a question that contains some ambiguity, it does not prompt you to seek clarification before answering. Instead, it will just make assumptions to resolve the ambiguity. If these assumptions don't align with what you had in mind you will get subpar answers and it will take longer to get to something that you can use.

One technique that will usually help improve your answers is something called "Role Prompting". This involves telling the AI who it is supposed to be. This may have the effect of positioning the response engine in the general area of where it needs to be to answer your question. For example, if you just ask the AI for information on Java, this could pertain to the Java programming language or the Island in Indonesia. By telling the AI that it is a "Java Programming Expert" it sets the context to ensure your answer will be focused correctly.

"Shot Prompting" is another technique that is used to provide the AI with examples to help it ascertain the types of things you are looking for.

Now let's dive right in and start asking ChatGPT some questions. These will be broken down into various topics.

Leveraging ChatGPT

Shot Prompting

Let's start with a simple example of Shot Prompting by instructing ChatGPT on some items (in this case variable names) that you consider good or bad:

x is bad
familyName is good
last-name is bad
abcdeHijk is bad
street is good
first_name is bad
jobTitle is good
Based on the above sample set, is "postalcode" good or bad?

Here is ChatGPT's reply:

Based on the naming conventions used in your examples, postal_code would be considered bad. It appears that you prefer using camelCase (e.g., familyName, jobTitle) for naming variables, rather than snake_case (e.g., last-name, first_name, postal_code). In this case, a better variable name following camelCase convention would be postalCode.

Let's ask a followup question.

is "sdrefXdyhr" good?
And ChatGPT replies:

Based on the given sample set, it seems that the classification of good or bad is related to the formatting of the variable names. "Good" variables use camelCase, meaning the first letter of each word is capitalized except for the initial word, while "bad" variables use other styles such as snake_case (underscores) or hyphenated words.
Notice that ChatGPT considered the context of my first question when answering the second question. ChatGPT can retain about 3000 words of context. Sometimes, you want to start a new conversation that is not influenced by the previous one. In such cases you can issue the command "reset" to tell the AI to ignore the previous context. As an FYI, I believe sdrefXdyhr is not considered to be camelCase since it is not made up of valid words.

SQL Queries

Many developers dabble in SQL and can craft basic queries and joins but when it comes time to create more complex queries they struggle. This is an area where ChatGPT excels as we will see in the examples in this section.

SQL Query 1 - Prompt

I have a SQL Server table named ADDRESS which contains the non-nullable columns ADDRESS-LINE-1, ADDRESS-LINE-2, ADDRESS-LINE-3 as well as a column named COUNTRY.
Create a query that shows the minimum, maximum and average number of address lines per country, also include the number of countries included in the average.

SQL Query 1 - Answer from ChatGPT 4

WITH CTE_ADDRESS_LINE_COUNTS AS (
    SELECT
        COUNTRY,
        (CASE
            WHEN LEN(ADDRESS_LINE_1) > 0 THEN 1
            ELSE 0
         END +
         CASE
            WHEN LEN(ADDRESS_LINE_2) > 0 THEN 1
            ELSE 0
         END +
         CASE
            WHEN LEN(ADDRESS_LINE_3) > 0 THEN 1
            ELSE 0
         END) AS NUM_ADDRESS_LINES
    FROM
        ADDRESS
)

SELECT
    COUNTRY,
    MIN(NUM_ADDRESS_LINES) AS MIN_ADDRESS_LINES,
    MAX(NUM_ADDRESS_LINES) AS MAX_ADDRESS_LINES,
    AVG(CAST(NUM_ADDRESS_LINES AS FLOAT)) AS AVG_ADDRESS_LINES,
    COUNT(DISTINCT COUNTRY) OVER() AS NUM_COUNTRIES
FROM
    CTE_ADDRESS_LINE_COUNTS
GROUP BY
    COUNTRY
ORDER BY
    COUNTRY;

This query uses a Common Table Expression (CTE) named CTE_ADDRESS_LINE_COUNTS to calculate the number of address lines for each row in the table. It then groups the data by country and calculates the minimum, maximum, and average number of address lines for each country. The COUNT(DISTINCT COUNTRY) OVER() window function is used to count the number of countries included in the average.

SQL Query 1 - Commentary

I was wondering why ChatGPT used the LEN function to check whether the address lines were empty, rather than comparing the addresses with an empty string, so I asked it why it made this choice. Here is the AI's reply.

You're right. Comparing the address lines against an empty string is a more straightforward and efficient approach. The reason I used the LEN function was to determine if the length of the address line is greater than zero, which would indicate that it has content. However, comparing the address lines against an empty string provides the same result and is more efficient.

SQL Query 2 - Prompt

You are SQL programmer. I have a 2012 SQL Server table named HOME_VISIT with columns CASE_NO and PREV_CASE_NO.
Create a query to show all of the CASE_NO values associated with PREV_CASE_NO values but only include rows where there are multiple distinct CASE_NO values for a given PREV_CASE_NO.

SQL Query 2 - Answer from ChatGPT 4

WITH GroupedData AS (
    SELECT
        PREV_CASE_NO,
        COUNT(DISTINCT CASE_NO) AS Distinct_CASE_NO_Count
    FROM
        HOME_VISIT
    GROUP BY
        PREV_CASE_NO
)

SELECT
    hv.CASE_NO,
    hv.PREV_CASE_NO
FROM
    HOME_VISIT hv
JOIN
    GroupedData gd ON hv.PREV_CASE_NO = gd.PREV_CASE_NO
WHERE
    gd.Distinct_CASE_NO_Count > 1
ORDER BY
    hv.PREV_CASE_NO, hv.CASE_NO;
This query first creates a Common Table Expression (CTE) called GroupedData that groups the rows by PREV_CASE_NO and counts the distinct CASE_NO values. Then, it joins the CTE back to the original HOME_VISIT table and filters the results based on the count of distinct CASE_NO values. The results are ordered by PREV_CASE_NO and then by CASE_NO.

PHP Code Generation

Next, let's see how ChatGPT does at generating code. In Canada there are 10 provinces and 3 territories. All of them have postal codes that are formatted using alternating letter and numbers. The first letter must match one of the letters allowed for the given province or territory. Here is our question to ChatGPT:

Now you are an expert PHP 8 developer, can you provide a PHP class to validate a Canadian postal code including making sure the postal code is valid for the supplied province?
Here see the code that ChatGPT 4 came up with.
class CanadianPostalCodeValidator {
    private static array $provinceCodes = [
        'AB' => 'T',
        'BC' => 'V',
        'MB' => 'R',
        'NB' => 'E',
        'NL' => 'A',
        'NS' => 'B',
        'NT' => 'X',
        'NU' => 'X',
        'ON' => 'KLMNP',
        'PE' => 'C',
        'QC' => 'GJH',
        'SK' => 'S',
        'YT' => 'Y'
    ];

    public static function isValidPostalCode(string $postalCode, string $province): bool {
        // Remove any whitespace characters
        $postalCode = preg_replace('/\s+/', '', $postalCode);
        
        // Check if the province code is valid
        if (!array_key_exists($province, self::$provinceCodes)) {
            return false;
        }

        // Check if the postal code format is valid
        if (!preg_match('/^[A-Za-z]\d[A-Za-z]\d[A-Za-z]\d$/', $postalCode)) {
            return false;
        }

        // Check if the first letter of the postal code corresponds to the province
        return strpos(self::$provinceCodes[$province], $postalCode[0]) !== false;
    }
}
You can use this class to validate a Canadian postal code like this:
$postalCode = 'K1A 0B1';
$province = 'ON';

if (CanadianPostalCodeValidator::isValidPostalCode($postalCode, $province)) {
    echo 'The postal code is valid.';
} else {
    echo 'The postal code is invalid.';
}
This class includes a isValidPostalCode() method that takes a postal code and province as input and returns true if the postal code is valid for the given province, or false otherwise. The method first checks if the province code is valid, then checks if the postal code format is valid, and finally verifies if the first letter of the postal code corresponds to the province.
In this case I was not happy with the fact that the code first changes the input postal code by stripping out all white space. The caller would have no way of knowing that the supplied postal code was actually altered before being validated. In such a case, the caller might save the original postal code (containing leading spaces for example) not realizing that it is actually invalid in its unaltered form. Here I ask ChatGPT for an updated class.

I see that your code actually changes the supplied postal code, by stripping out white space, before validating it. This is poor form. Also, can you change the class so that the caller can discern whether the validation failed because the postal code is in the wrong format or rather it is not valid for the supplied province.
And here is the updated response:
I apologize for the oversight. Here's an updated version of the CanadianPostalCodeValidator class that doesn't modify the input postal code and provides different error codes to indicate whether the validation failed due to an incorrect format or an invalid province-postal code combination:
class CanadianPostalCodeValidator {
    const INVALID_FORMAT = 1;
    const INVALID_PROVINCE = 2;
    const VALID = 0;

    private static array $provinceCodes = [
        'AB' => 'T',
        'BC' => 'V',
        'MB' => 'R',
        'NB' => 'E',
        'NL' => 'A',
        'NS' => 'B',
        'NT' => 'X',
        'NU' => 'X',
        'ON' => 'KLMNP',
        'PE' => 'C',
        'QC' => 'GJH',
        'SK' => 'S',
        'YT' => 'Y'
    ];

    public static function validatePostalCode(string $postalCode, string $province): int {
        // Check if the province code is valid
        if (!array_key_exists($province, self::$provinceCodes)) {
            return self::INVALID_PROVINCE;
        }

        // Check if the postal code format is valid
        if (!preg_match('/^[A-Za-z]\d[A-Za-z](\s+)?\d[A-Za-z]\d$/', $postalCode)) {
            return self::INVALID_FORMAT;
        }

        // Check if the first letter of the postal code corresponds to the province
        if (strpos(self::$provinceCodes[$province], $postalCode[0]) !== false) {
            return self::VALID;
        }

        return self::INVALID_PROVINCE;
    }
}
Now, the validatePostalCode() method returns one of three constants: VALID, INVALID_FORMAT, or INVALID_PROVINCE. You can use this class as follows:
$postalCode = 'K1A 0B1';
$province = 'ON';

$validationResult = CanadianPostalCodeValidator::validatePostalCode($postalCode, $province);

switch ($validationResult) {
    case CanadianPostalCodeValidator::VALID:
        echo 'The postal code is valid.';
        break;
    case CanadianPostalCodeValidator::INVALID_FORMAT:
        echo 'The postal code format is invalid.';
        break;
    case CanadianPostalCodeValidator::INVALID_PROVINCE:
        echo 'The postal code is invalid for the supplied province.';
        break;
}

Regular Expressions

Even seasoned programmers struggle with regular expressions. Unless you are an expert in regular expressions you should also seek clarification on the regular expressions that ChatGPT used in its code. Let's learn more about the regular expression the AI used to validate the postal code.

Can you explain what the regular expression does in the latest CanadianPostalCodeValidator class?

Here is ChatGPT's reply:

Certainly! In the latest CanadianPostalCodeValidator class, the regular expression used in the validatePostalCode() method is as follows:
/^[A-Za-z]\d[A-Za-z](\s+)?\d[A-Za-z]\d$/
This regular expression is designed to match valid Canadian postal codes. Let's break it down:
^ - This symbol indicates the start of the string. It ensures that the pattern must start at the beginning of the input string.
[A-Za-z] - This part matches any single uppercase or lowercase letter. In the context of a Canadian postal code, this corresponds to the first letter that represents the province or territory.
\d - This part matches a single digit (0-9). In a Canadian postal code, this digit represents the regional distribution center.
[A-Za-z] - Again, this part matches any single uppercase or lowercase letter. In a Canadian postal code, this letter represents the local delivery unit.
(\s+)? - This part matches one or more whitespace characters (spaces, tabs, etc.) but makes them optional due to the ? following the group. This allows for postal codes written with or without a space in the middle (e.g., "K1A 0B1" or "K1A0B1").
\d - This part matches a single digit (0-9), representing the second group of the local delivery unit in the postal code.
[A-Za-z] - This part matches any single uppercase or lowercase letter, representing the second letter of the local delivery unit in the postal code.
\d - This part matches a single digit (0-9), representing the final digit of the local delivery unit in the postal code.
$ - This symbol indicates the end of the string. It ensures that the pattern must end at the end of the input string.
In summary, the regular expression checks if the input string matches the valid Canadian postal code format, which consists of alternating letters and digits, with an optional space in the middle.

Notice that the regular expression allows the postal code to contain lower case or upper case letters. I really should have stipulated that only upper case characters are allowed. The caller should convert the string to upper case to ensure standard case handling when storing postal codes. Another issue is the fact that the regular expression allows any number of characters in the middle of the postal code. Again, I should have tightened up the specification to ensure all postal codes end up being the same length.

Here is how I should ask for the class to ensure it better meets my requirements:

You are an expert PHP 8 developer, provide a PHP class to validate a Canadian postal code including making sure the postal code is valid for the supplied province? I only want upper case postal codes to be valid, so convert the postal code to upper case. I also want exactly one space in the middle of the postal code so remove any other spaces and introduce one space if necessary. I want to pass the postal code by reference so that corrections to the postal code are returned to the caller.

And here is the new class that ChatGPT generated:

class CanadianPostalCodeValidator {
    // Mapping of valid first characters for postal codes by province.
    private static $provincePostalCodeMap = [
        'AB' => 'T', 
        'BC' => 'V', 
        'MB' => 'R', 
        'NB' => 'E',
        'NL' => 'A',
        'NS' => 'B',
        'ON' => ['K', 'L', 'M', 'N', 'P'],
        'PE' => 'C',
        'QC' => ['G', 'H', 'J'],
        'SK' => 'S',
        'NT' => 'X',
        'NU' => 'X',
        'YT' => 'Y'
    ];

    public static function validatePostalCode(&$postalCode, $province) {
        // Remove all whitespace and convert to uppercase.
        $postalCode = strtoupper(preg_replace('/\s+/', '', $postalCode));
        // Insert a single space in the middle.
        $postalCode = substr($postalCode, 0, 3) . ' ' . substr($postalCode, 3, 3);

        // Check if the postal code format is valid.
        if (!preg_match('/^[A-Z][0-9][A-Z] [0-9][A-Z][0-9]$/', $postalCode)) {
            return [false, 'Invalid postal code format.'];
        }
        // Check if the postal code is valid for the province.
        $firstChar = $postalCode[0];
        $validChars = self::$provincePostalCodeMap[$province] ?? null;
        if (is_array($validChars) ? !in_array($firstChar, $validChars) : $firstChar !== $validChars) {
            return [false, 'Postal code does not match province.'];
        }
        return [true, 'Postal code is valid.'];
    }
}

It should be noted that this new version was generated during a different conversation than the previous program. This may explain when the code was changed quite a bit compared to the earlier version. This lack of repeatability is an interesting, and sometimes annoying, phenomenon about the answers you get from ChatGPT. Sometimes I have asked the same question four different times and have gotten four different answers with varying degrees of "greatness". Let's review some of the changes made in this latest version.

  • I asked for the postalCode to be defined by reference, which it did, good job.
  • In the earlier version of the class the parameters passed were typed, as was the function itself. In the revised version the types were dropped, that's not good.
  • It tightened up the rules on the postal code validation as I asked, good job.
  • The return parameter was changed from an integer constant to an array which changes the interface and is not appropriate for multilingual usage since an English error message is returned.

The bottom line is that you may need to tell the AI exactly how you want your code to behave to get the results you are looking for.

ADO Stories

If you a Product Owner working in an Agile team, you may want to add a story to your ADO board to assign this programming task to someone in your scrum. Let's ask for help writing this story.

You are a Product Owner, can you write a story for your ADO board that can be assigned to the PHP developer tasked with creating the aforementioned postal code validator class?

And here is the story the AI came up with.

Title: Create a Canadian Postal Code Validator Class
As a PHP developer,
I want to create a class for validating Canadian postal codes,
So that I can ensure that the postal codes are correctly formatted and correspond to the supplied province.
Acceptance Criteria:
The class should be named CanadianPostalCodeValidator.
The class should have a method boolean validatePostalCode(String &postalCode, String provinceCode) that takes a postal code string and a province code string as input arguments, and returns an integer indicating whether the postal code is valid or not.
The method should check if the postal code is correctly formatted according to the Canadian postal code format (e.g., "A1A 1A1"). The method should attempt to coerce the supplied postal code into this format.
The method should ensure that the provided postal code is valid for the supplied province (e.g., the postal code "M5V 1M1" should be considered valid for the province "ON").
The method should return 1 if the postal code is not formatted correctly or 2 if it doesn't correspond to the supplied province. It should return 0 if the postal code is valid. These values should be defined as constants.
The class should include unit tests to cover various valid and invalid postal codes and province codes.
Example Scenarios:
The method isValidPostalCode("M5V 1M1", "ON") should return CanadianPostalCodeValidator::VALID.
The method isValidPostalCode("M5V 1M1", "AB") should return CanadianPostalCodeValidator::INVALID_PROVINCE.
The method isValidPostalCode("MXV1M1", "QC") should return CanadianPostalCodeValidator::INVALID_FORMAT.
Notes:
When implementing this class, you may refer to the Canadian postal code format and the first letters for each province's postal codes.
Don't forget to write unit tests to cover various edge cases and ensure the class works correctly.

When asking for a "Definition of Done", ChatGPT returns:

Definition of Done:
The CanadianPostalCodeValidator class has been implemented according to the acceptance criteria outlined in the user story, with the required method and functionality.
The class includes appropriate comments for the class itself and its methods, describing their purpose, input parameters, and return values.
The code is properly formatted, following the team's established coding standards and best practices.
Unit tests have been written to cover various edge cases and scenarios, including valid and invalid postal codes and province codes. The tests must pass with 100% success rate.
The code has been reviewed by at least one other team member, and any feedback or suggestions have been addressed and incorporated into the final implementation.
The class has been successfully integrated into the existing codebase without causing any conflicts or issues.
The class has been tested in a staging environment, and its functionality has been verified to work correctly with the rest of the application.
Any necessary documentation or user guides have been updated to include information about the new CanadianPostalCodeValidator class and its usage.
The code is ready for deployment to the production environment.

HTML Generation

ChatGPT is also very good at generating HTML. Let's ask it to generate a complex select field.

Provide an html page containing a form with a "select" control that includes all of the provinces and territories in Canada. Return the 2-character province code but show the full name. Allow multiple provinces to be selected. Group the list into "Western Provinces", "Central Canada", "Maritime Provinces" and "Territories". Style the results so that each group is in a different color. 

This is another example where I got two different answers to the same question over the course of a couple of days. The first answer stated that browser support for coloring select options was not consistent so it provided the following code which uses javascript to perform the styling.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canadian Provinces and Territories</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f0f0f0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        form {
            background-color: #ffffff;
            border: 1px solid #dddddd;
            border-radius: 5px;
            padding: 20px;
            width: 350px;
        }
        label {
            display: block;
            font-weight: bold;
            margin-bottom: 10px;
        }
        select {
            width: 100%;
            padding: 8px 12px;
            border: 1px solid #dddddd;
            border-radius: 5px;
            font-size: 14px;
        }
        input[type="submit"] {
            display: block;
            background-color: #007bff;
            color: #ffffff;
            border: none;
            border-radius: 5px;
            padding: 8px 12px;
            font-size: 14px;
            cursor: pointer;
            margin-top: 20px;
        }
        input[type="submit"]:hover {
            background-color: #0056b3;
        }
    </style>
    <script>
        function styleOptions() {
            const select = document.getElementById('provinces');
            const groups = select.getElementsByTagName('optgroup');
            
            for (let i = 0; i < groups.length; i++) {
                const options = groups[i].getElementsByTagName('option');
                let color;
                
                switch (groups[i].label) {
                    case 'Western':
                        color = '#f44336';
                        break;
                    case 'Central Canada':
                        color = '#4caf50';
                        break;
                    case 'Maritime':
                        color = '#2196f3';
                        break;
                    case 'Territories':
                        color = '#9c27b0';
                        break;
                    default:
                        color = '#000000';
                        break;
                }
                
                for (let j = 0; j < options.length; j++) {
                    options[j].style.color = color;
                }
            }
        }
        
        window.addEventListener('DOMContentLoaded', styleOptions);
    </script>
</head>
<body>
    <form action="your_php_script.php" method="post">
        <label for="provinces">Select Canadian Provinces and Territories:</label>
        <select name="provinces[]" id="provinces" multiple>
            <optgroup label="Western">
                <option value="BC">British Columbia</option>
                <option value="AB">Alberta</option>
                <option value="SK">Saskatchewan</option>
            </optgroup>
            <optgroup label="Central Canada">
                <option value="MB">Manitoba</option>
                <option value="ON">Ontario</option>
                <option value="QC">Quebec</option>
            </optgroup>
            <optgroup label="Maritime">
                <option value="NB">New Brunswick</option>
                <option value="NS">Nova Scotia</option>
                <option value="PE">Prince Edward Island</option>
                <option value="NL">Newfoundland and Labrador</option>
            </optgroup>
            <optgroup label="Territories">
                <option value="NT">Northwest Territories</option>
                <option value="NU">Nunavut</option>
                <option value="YT">Yukon</option>
            </optgroup>
        </select>
        <input type="submit" value="Submit">
    </form>
</body>
</html>
When I asked the next day, it decided that the browser support was adequate enough to use normal CSS styling as shown below, but it did alert me to the fact that this might not be supported in all browsers.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canadian Provinces and Territories</title>
    <style>
        .western { color: red; }
        .central { color: green; }
        .maritime { color: blue; }
        .territories { color: purple; }
    </style>
</head>
<body>
    <h1>Select Canadian Provinces and Territories</h1>
    <form action="submit.php" method="post">
        <label for="provinces-and-territories">Select Provinces/Territories:</label>
        <select name="provinces-and-territories[]" id="provinces-and-territories" multiple>
            <optgroup label="Western Provinces" class="western">
                <option value="AB">Alberta</option>
                <option value="BC">British Columbia</option>
                <option value="SK">Saskatchewan</option>
                <option value="MB">Manitoba</option>
            </optgroup>
            <optgroup label="Central Canada" class="central">
                <option value="ON">Ontario</option>
                <option value="QC">Quebec</option>
            </optgroup>
            <optgroup label="Maritime Provinces" class="maritime">
                <option value="NB">New Brunswick</option>
                <option value="NS">Nova Scotia</option>
                <option value="PE">Prince Edward Island</option>
            </optgroup>
            <optgroup label="Territories" class="territories">
                <option value="NL">Newfoundland and Labrador</option>
                <option value="NT">Northwest Territories</option>
                <option value="NU">Nunavut</option>
                <option value="YT">Yukon</option>
            </optgroup>
        </select>
        <br>
        <input type="submit" value="Submit">
    </form>
</body>
</html>
This HTML page has a form with a "select" control that groups the provinces and territories as requested. The CSS styles within the <style> tag assign different colors to each group. When the form is submitted, the selected province/territory codes will be sent to a PHP script called "submit.php" on the server.
Please note that the styling of the "select" control might not work consistently across different web browsers, as some browsers have limited support for styling the <optgroup> and <option> elements. To achieve a more consistent appearance, you might consider using a custom dropdown control created with JavaScript and CSS, or using a third-party library that provides such functionality.

PHP Interpretation and Refactoring

We have seen that ChatGPT is pretty good at writing code but I wanted to determine how well ChatGPT understands existing code. For example, if you are asked to work on a new system, can it help to teach you about a program that you are not familiar with? As a test I took an existing GenHelm class and changed the class name so as not to give the AI any hints about what the class does. I also introduced a bug in the code to see if ChatGPT would catch it. Here is the program that I asked ChatGPT to describe.

<?php
class c1
{
protected $href;
protected $other;
protected $rel;
protected $gen_count;
function get_gen_count() {
    return $this->gen_count;
}
function define($rel,$href,$other=false) {
    $this->rel = $rel;
    $this->href = $href;
    if (!empty($other) and is_array($other)) {
        if (isset($other['key'])) {
            $id = $other['key'];
            unset($other['key']);
        }
       $this->other = $other;
    }
    if (isset($id)) {
        return $id;  // User supplied a key
    }
    // Try to derive a key from the data
    if (isset($other['hreflang'])) {
        $suggest[] = $other['hreflang'];
    }
    if (isset($other['type'])) {
        $suggest[] = $other['type'];
    }
    if (isset($other['sizes'])) {
        $suggest[] = $other['sizes'];
    }
    if (isset($suggest)) {
        $append = '-'.implode('-',$suggest);
    }
    else {
        $append = '';
    }
    return $rel.$append;
}
function generate() {
    $this->gen_count = isset($this->gen_count) ? $this->gen_count++ : 1;
    $code = 'rel="'.$this->rel.'" href="'.$this->href.'"';
    if (isset($this->other)) {
        foreach ($this->other as $name => $value) {
            $code .= ' '.$name.'="'.$value.'"';
        }
    }
    return '<link '.$code.' />'.PHP_EOL;
}
}
?>
And here is the reply:
The PHP class `c1` provided in the code snippet is designed to generate HTML link elements. It has properties for the `href`, `rel`, and any other additional attributes that might be required. The class also keeps track of how many times a link has been generated using the `gen_count` property.
Here's a breakdown of the class and its methods:
1. `get_gen_count()`: This method returns the current value of the `gen_count` property, which indicates how many times the `generate()` method has been called.
2. `define($rel, $href, $other = false)`: This method sets the values for the `rel`, `href`, and `other` properties. It also tries to derive a key from the provided data or return a user-supplied key if one is provided in the `$other` array.
3. `generate()`: This method generates the HTML link element using the values of the `rel`, `href`, and `other` properties. It increments the `gen_count` property each time this method is called.
Here's an example of how you could use the `c1` class:
<?php
// Include the c1 class file here
// require_once 'path/to/c1.php';

$link = new c1();
$link->define('stylesheet', 'styles.css', ['type' => 'text/css']);

echo $link->generate(); // Outputs: <link rel="stylesheet" href="styles.css" type="text/css" />

$link->define('alternate', 'example.com/fr', ['hreflang' => 'fr', 'type' => 'text/html']);

echo $link->generate(); // Outputs: <link rel="alternate" href="example.com/fr" hreflang="fr" type="text/html" />

echo 'Links generated: ' . $link->get_gen_count(); // Outputs: Links generated: 2
?>
In this example, we create a new instance of the `c1` class and use the `define()` method to set the values for the `rel`, `href`, and `other` properties. Then, we call the `generate()` method to output the generated HTML link elements. Finally, we use the `get_gen_count()` method to display the total number of generated links.
ChatGPT summarised the code very well however it did not mention that there is a bug in the code. Let's see if it does so if we explicitly ask about this.

Can you identify any problems with this code?
And here is what it said:

Yes, there is a small issue with the code in the `generate()` method when incrementing the `gen_count` property. The increment operation is not being applied correctly. Here's the corrected version of the `generate()` method:
function generate() {
    $this->gen_count = isset($this->gen_count) ? $this->gen_count + 1 : 1;
    $code = 'rel="' . $this->rel . '" href="' . $this->href . '"';
    if (isset($this->other)) {
        foreach ($this->other as $name => $value) {
            $code .= ' ' . $name . '="' . $value . '"';
        }
    }
    return '<link ' . $code . ' />' . PHP_EOL;
}
The issue was with this line:
$this->gen_count = isset($this->gen_count) ? $this->gen_count++ : 1;
The post-increment operation `$this->gen_count++` increments the value of `$this->gen_count` after the expression is evaluated, which means the increment would not take effect as intended. Changing it to `$this->gen_count + 1` resolves this issue.
With this fix, the `generate()` method should correctly increment the `gen_count` property each time it's called.
Good job on finding this bug. I suspect many reasonable programmers would not have caught this. You can start to see that ChatGPT could be a very useful performing code reviews. Next let's see if it can improve the code. It should be noted that this program was originally written in PHP 5.3 which does not support typing of variables. Let's ask the AI to refactor the code and implement its own suggestions.
<?php
class HtmlLinkGenerator
{
    protected string $linkHref;
    protected string $linkRel;
    protected array $linkOther;
    protected int $generateCount;

    public function __construct()
    {
        $this->generateCount = 0;
    }

    public function getGenerateCount(): int
    {
        return $this->generateCount;
    }

    public function define(string $rel, string $href, array $other = []): string
    {
        $this->linkRel = $rel;
        $this->linkHref = $href;
        
        if (!empty($other)) {
            if (isset($other['key'])) {
                $id = $other['key'];
                unset($other['key']);
            }
            $this->linkOther = $other;
        }
        
        if (isset($id)) {
            return $id;
        }

        $suggest = [];

        if (isset($other['hreflang'])) {
            $suggest[] = $other['hreflang'];
        }
        if (isset($other['type'])) {
            $suggest[] = $other['type'];
        }
        if (isset($other['sizes'])) {
            $suggest[] = $other['sizes'];
        }

        $append = !empty($suggest) ? '-' . implode('-', $suggest) : '';

        return $rel . $append;
    }

    public function generate(): string
    {
        $this->generateCount += 1;
        
        $code = 'rel="' . $this->linkRel . '" href="' . $this->linkHref . '"';
        
        if (isset($this->linkOther)) {
            foreach ($this->linkOther as $name => $value) {
                $code .= ' ' . $name . '="' . $value . '"';
            }
        }
        
        return '<link ' . $code . ' />' . PHP_EOL;
    }
}
?>
Notice that, in addition to correcting the bug, ChatGPT also changed the variables to camel case and added types for all of the class variables and function parameters as well as the functions themselves. It also created a constructor to initialise the generateCount variable.

Conclusion

Let's go through some of the key takeaways of this post.

  • ChatGPT has changed the game for developers. If you don't start to take advantage of this technology you will be left behind.
  • ChatGPT will not always give you the same response for a given question. If you are not happy with what it gives you, always challenge it to give you what you want.
  • Just like in old school programming, the results are only as good as the specification. If you don't tell it exactly what features or capabilities to incorporate into the code you ask it for you may not get what you want.
  • There are lots of things that ChatGPT can do to make programmers more productive. Before you start writing your next program you should always ask ChatGPT to come up with something. Even if it is not exactly what you had in mind, you can always tweak the code to your needs, likely in less time that it would take to write the code from scratch.