td204

Web development, one post per year since 2002

2022 in review: the year AI changed my world

On paper this is my emptiest year. Same bundler, same CSS approach, same framework versions. One new project, and it started from the same skeleton as the previous one. It shows up in the numbers too: roughly 680 commits, my quietest year on GitHub, against 1,150 the year before and 1,700 in 2019.

And then there is 7 November, 11:12, when I installed GitHub Copilot. I was sold the same afternoon.

Installing Copilot

Code completion that actually completes. Not the identifier you were halfway through typing, which is what my IDE has done for years, but the thing you were about to write.

Start a method, and the body appears in the shape you were going to give it. Name a class and the properties follow. PHPDoc blocks arrive fully formed, correctly typed, including the @return I would have been too lazy to write. That last one alone has improved my codebase more than any resolution I ever made about documentation.

The part I did not see coming is the comment as an instruction. Describe what you want in plain English above an empty line, and you get the function:

// return all active users, sorted by surname, with their most recent order
public function activeUsersWithLastOrder(): Collection
{
    ...
}

You type the comment. It writes the rest. The first time that worked I sat back in my chair.

Is it right every time? No. It is confidently wrong often enough that you read every line, and it has no idea what the rest of my project looks like, so anything that depends on knowing my domain comes out generic. It suggests, I accept or I reject, and the unit of work is a line or a block rather than a task.

It also has a lot of growing to do. But seven weeks in, it is noticeably better than on the day I installed it, and I have stopped thinking of it as a plugin I am evaluating. It is just how I write PHP now.

Which is the strange thing about the chart above. My quietest year by commits is the year I changed how I type, and the two facts sit in the same twelve months without touching each other.

The four tests I now write before any feature work

Nobody has ever approved a budget line called "unit tests". So I stopped asking, and instead write four tests on every project in the first hour. About twenty minutes total, and they have caught more production bugs than everything else I do.

1. Every public route returns something that is not a 500. Cheap, broad, catches the "I renamed a view" class of bug across the whole app.

it('renders every public page', function (string $uri) {
    $this->get($uri)->assertSuccessful();
})->with([
    '/',
    '/contact',
    '/nieuws',
    '/over-ons',
]);

Add a URI every time you add a page. It is a checklist that runs itself.

2. The thing behind the login is actually behind the login. Whatever is protected is what ends up unprotected after a refactor.

it('keeps the dashboard behind a login', function () {
    $this->get('/dashboard')->assertRedirect('/login');
});

it('lets a logged-in user in', function () {
    $this->actingAs(User::factory()->create())
        ->get('/dashboard')
        ->assertSuccessful();
});

3. The one form the business runs on. Every project has one: the quote request, the booking, the order. Test that it validates and that it does the thing.

it('stores a booking and mails the office', function () {
    Mail::fake();

    $this->post('/booking', [
        'name'  => 'Jan',
        'email' => 'jan@example.com',
        'date'  => '2022-10-01',
    ])->assertRedirect('/bedankt');

    expect(Booking::count())->toBe(1);
    Mail::assertSent(BookingReceived::class);
});

Mail::fake() is the line people forget. Without it, the first person to run the suite mails the client's office.

4. The calculation nobody can check by eye. VAT, proration, shipping bands, a discount that stacks. Boundaries only.

it('applies the volume discount at exactly 10 items', function (int $qty, string $expected) {
    expect(Cart::for($qty)->total()->toString())->toBe($expected);
})->with([
    [9,  '€ 90,00'],
    [10, '€ 85,00'],
    [11, '€ 93,50'],
]);

Nine, ten, eleven. That is where the off-by-one lives.

Four tests will not give you a coverage badge. They give you the thing you actually want at 17:00 on a Friday: permission to deploy.

The other thing that changed: how designs arrived

Leaving Copilot aside, what actually varied between 2020 and 2022 is not my stack. It is the file the designer sends. Layered PSDs, then a Figma link, then an XD file, then a PSD again from a designer who never moved.

For a while I treated that as a nuisance. It is worth saying plainly what it actually is: the handoff format is the designer's tool choice, not mine, and building anything that depends on it is building on somebody else's decision. What I need out of a design is the same in every format, and it is not the file: it is the spacing scale, the type scale, the colour values and the breakpoints.

I now ask for those four things explicitly, in text, whatever the file is. That question has saved more time than any tool I adopted in the three years around it.

In short

My quietest year by commit count. Standardised the four tests I write on every project, watched the design handoff format change three times, and on 7 November installed GitHub Copilot, which changed how I write PHP within an afternoon.

The year in commits

Commits by month in 2022: 682 in total, peaking at 92 in Feb.
Jan 2022: 90 commits J Feb 2022: 92 commits 92 F Mar 2022: 73 commits M Apr 2022: 76 commits A May 2022: 40 commits M Jun 2022: 63 commits J Jul 2022: 19 commits J Aug 2022: 54 commits A Sep 2022: 39 commits S Oct 2022: 62 commits O Nov 2022: 56 commits N Dec 2022: 18 commits D