How ChatGPT's Codex has transformed the way I code

Security vulnerabilities... code refactoring... readability. Codex is changing the way I code my projects! 🤖

How ChatGPT's Codex has transformed the way I code
Photo by vackground.com / Unsplash

As a solo developer, I'm constantly finding new ways to improve the way I code. Recently, I've started using ChatGPT's Codex to achieve that.

Now, let's not get carried away here, it's not re-writing entire projects for me. I'm not letting it loose on new features or APIs, and the code it generates isn't going into production untested. However, what it is doing is reviewing my code for me. I'm asking it to check specifically for security vulnerabilities, performance issues, typos, and to suggest improvements.

How about I show you an example...

Codex Example

My "lists" app Marvelist relies on a REST API that is currently deployed in Azure, written in Python, and operates on Azure Functions.

Parts of this API server relies on a connection to a backend MySQL database.

I'd noticed recently that when my database server undergoes maintenance or failover, the connection can sometimes fail for a second or two. My current code logic didn't take this into account and just returned an HTTP 500 error when the database connection failed. This felt like a quick and easy task I could delegate to Codex to resolve.

After hooking the GitHub repository for the API server into Codex (and obviously disabling internet access), I fired the following prompt at it.

This repository is for an Azure Functions App, providing REST API services to a mobile app. Currently whenever the database times out (when connecting) an error 500 is produced. The database connection is configured from the [FILE NAME].py file, implement a retry logic.

And away it went...

It reviewed the contents of the database delegate Python file, found the function used to establish a connection to the database server, and then started to implement the retry logic.

Here's what it spat out, and the code I had wrote. (No judgements please).

My Code

@contextmanager
def get_connection():
    conn = None
    cursor = None
    try:
        # Get a connection from the pool
        conn = mysql.connector.connect(**dbconfig)
        cursor = conn.cursor(dictionary=True)
        yield conn, cursor
    except Exception as e:
        logging.error(f"Database connection error: {e}")
        raise
    finally:
        if cursor is not None:
            cursor.close()
        if conn is not None:
            conn.close()  # This returns the connection to the pool

Codex's Refactored Code

@contextmanager
def get_connection(max_retries: int = 3, base_delay: float = 1.0):
    """Context manager that returns a MySQL connection and cursor with retry logic.

    Args:
        max_retries: Number of times to retry the connection on failure.
        base_delay: Initial delay (in seconds) before retrying; doubles on each retry.
    """

    conn = None
    cursor = None
    attempt = 0

    # Attempt to establish the connection with retries
    while True:
        try:
            conn = mysql.connector.connect(**dbconfig)
            cursor = conn.cursor(dictionary=True)
            break
        except mysql.connector.Error as e:
            attempt += 1
            if attempt >= max_retries:
                logging.error(f"Database connection failed after {attempt} attempts: {e}")
                raise
            delay = base_delay * (2 ** (attempt - 1))
            logging.warning(
                f"Database connection failed (attempt {attempt}/{max_retries}). Retrying in {delay} seconds..."
            )
            time.sleep(delay)

    try:
        yield conn, cursor
    finally:
        if cursor is not None:
            cursor.close()
        if conn is not None:
            conn.close()  # This returns the connection to the pool

As you can see, given the right prompt, and the specifics I asked for, it implemented the retry logic I asked for. It didn't scrap and rewrite the entire thing; it took what I already had and found a way to compliment it.

I was satisfied with the code it had written. I reviewed it, sense-checked it, and thought "ok, I can see that working how I want it to work". With that, I requested Codex create a pull request for these changes.

Codex then created a new branch in my repository, and a pull request so that the changes could be merged into my main production branch.

With Codex creating a new branch for me, I could very easily switch over to this branch in my local development environment, spin up the server with the changes, and do some thorough testing of the new code. Verifying what happens in the event of a network outage, DNS resolution failures, etc.

The code worked exactly how I wanted it to. I was impressed.

Once I was satisfied my testing was complete, and I approved of the code changes, I merged and closed the pull request and deleted the Codex-created branch.

This commit to the main production branch triggered my continuous integration flows in GitHub Actions and deployed the latest code changes directly to Azure.

Would I recommend Codex?

Absolutely. But only in certain use-cases. It has its pros and cons, as does every AI agent. My testing has found Codex is very helpful with smaller isolated issues and improvements. Tell it to target several files, a class, a module, and it thrives. Tell it to scan the entire repository, it falls down, things get missed, not everything is accounted for.

I will always recommend sense-checking any code that an AI agent produces, and ensure it works for your specific project and use-case. Don't blindly trust. Test and then test again.

I will definitely be using Codex as an assistant to my development efforts in the future, it's proved itself a capable sidekick.

Until next time 👋