Reading List
The most recent articles from a list of feeds I subscribe to.
Explore the other side & learn a new language
If you want to become a better programmer, my number one advice is to learn another programming language. The further away from your comfort zone the better.
The first language I professionally programmed in was PHP. Building web applications, I occasionally wrote JavaScript, which wasn’t to hard of a transition coming from PHP as they have similar syntaxes.
After a few years, I considered myself a proficient PHP programmer. There was more to learn, but diving deeper into the possibilities and quirks of the language didn’t turn me into a better developer.
I wanted to learn another language. I could have picked up C# or Python, but I’d be looking at code from the same angle as PHP. Instead, I decided to experiment with Elixir. Elixir—and the constraints imposed by functional programming—were different enough to unlock another part of my brain.
Now, a lot of my PHP an JavaScript programming is inspired by functional programming paradigms. I grew more from a basic understanding of a foreign language than I would have from a strong understanding of another dialect.
This doesn’t only apply to languages, but frameworks and paradigms too. If you’ve been using React, try Livewire or HTMX. If you’ve been using ActiveRecord, try a data mapper. You don’t need to end up using it, but you’d be surprised how much you can learn on the other side.
Explore the other side & learn a new language
If you want to become a better programmer, my number one advice is to learn another programming language. The further away from your comfort zone the better.
The first language I professionally programmed in was PHP. Building web applications, I occasionally wrote JavaScript, which wasn't to hard of a transition coming from PHP as they have similar syntaxes.
After a few years, I considered myself a proficient PHP programmer. There was more to learn, but diving deeper into the possibilities and quirks of the language didn't turn me into a better developer.
I wanted to learn another language. I could have picked up C# or Python, but I'd be looking at code from the same angle as PHP. Instead, I decided to experiment with Elixir. Elixirâand the constraints imposed by functional programmingâwere different enough to unlock another part of my brain.
Now, a lot of my PHP an JavaScript programming is inspired by functional programming paradigms. I grew more from a basic understanding of a foreign language than I would have from a strong understanding of another dialect.
This doesn't only apply to languages, but frameworks and paradigms too. If you've been using React, try Livewire or HTMX. If you've been using ActiveRecord, try a data mapper. You don't need to end up using it, but you'd be surprised how much you can learn on the other side.
Beware of PHPUnit data providers with heavy setup methods
Data providers can be a perfect fit to assert a lot of expectations without writing a full test for each.
This makes it cheap and easy to add more test cases. For an in-depth introduction to data providers, I recommend this excellent article on the Tighten blog.
class AddTest extends TestCase
{
/** @dataProvider values */
public function it_adds_values(int $a, int $b, int $result): void
{
$this->assertEquals($result, add($a, $b));
}
public function values(): array
{
return [
[1, 1, 2],
[1, 2, 3],
[5, 5, 10],
// …
];
}
}
Data providers run setUp for each value. If you need a clean slate for every test, there’s no way around this. If you don’t, data providers make your tests slower than they need to be.
Consider a heavier integration test that migrate & seeds the database in setUp().
class MyTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
// Migrate and seed database values…
}
/** @dataProvider values */
public function it_does_something(string $value): void
{
// …
}
public function values(): array
{
return [ /* … */ ];
}
}
Each case in values() will re-seed the database. If this isn’t needed, you’re better off looping over the values yourself.
class MyTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
// Do some heavy lifting…
}
public function it_does_something(): void
{
$values = [ /* … */ ];
foreach ($this->value() as $value) {
// …
}
}
public function values(): array
{
return [ /* … */ ];
}
}
We recently updated a few tests in a project we’re working on, and it almost doubled the speed!
With a data provider:
Time: 00:08.517, Memory: 92.50 MB
OK (43 tests, 43 assertions)
In a loop:
Time: 00:04.448, Memory: 70.14 MB
OK (1 test, 43 assertions)
Beware of PHPUnit data providers with heavy setup methods
Data providers can be a perfect fit to assert a lot of expectations without writing a full test for each.
This makes it cheap and easy to add more test cases. For an in-depth introduction to data providers, I recommend this excellent article on the Tighten blog.
class AddTest extends TestCase{ /** @dataProvider values */ public function it_adds_values(int $a, int $b, int $result): void { $this->assertEquals($result, add($a, $b)); } public function values(): array { return [ [1, 1, 2], [1, 2, 3], [5, 5, 10], // ⦠]; }}
Data providers run setUp for each value. If you need a clean slate for every test, there's no way around this. If you don't, data providers make your tests slower than they need to be.
Consider a heavier integration test that migrate & seeds the database in setUp().
class MyTest extends TestCase{ public function setUp(): void { parent::setUp(); // Migrate and seed database values⦠} /** @dataProvider values */ public function it_does_something(string $value): void { // ⦠} public function values(): array { return [ /* ⦠*/ ]; }}
Each case in values() will re-seed the database. If this isn't needed, you're better off looping over the values yourself.
class MyTest extends TestCase{ public function setUp(): void { parent::setUp(); // Do some heavy lifting⦠} public function it_does_something(): void { $values = [ /* ⦠*/ ]; foreach ($this->value() as $value) { // ⦠} } public function values(): array { return [ /* ⦠*/ ]; }}
We recently updated a few tests in a project we're working on, and it almost doubled the speed!
With a data provider:
Time: 00:08.517, Memory: 92.50 MBOK (43 tests, 43 assertions)
In a loop:
Time: 00:04.448, Memory: 70.14 MBOK (1 test, 43 assertions)
JavaScript Gom Jabbar
You stop to count how many tools and parsers work on your codebase: TypeScript, esbuild, swc, babel, eslint, prettier, jest, webpack, rollup, terser. You are not sure if you missed any. You are not sure if you want to know. The level of pain is so high you forget about anything else.
We’ve come a long way, but we’re not there yet.