Slides/Code/Resources
Rate This Talk
joind.in/talk/3627e
wkdb.yt/zombie-hunt
wkdb.yt/zombie
wkdb.yt/zombie
"Support and Maintenance" is a key part of the software development process. Delivering new features, updates, upgrades, bug fixes, and improvements to existing code is a fact of life for a PHP developer.
wkdb.yt/zombie
The constituent parts of an application, including third-party vendor code, make up a holistic system. Change to one part may require changes to other parts of the system.
wkdb.yt/zombie
Code that does not exist, does not need to be maintained, nor does it accrue additional technical debt. In the case of third-party vendor code, this might mean "replace" or "upgrade".
wkdb.yt/zombie
wkdb.yt/zombie
wkdb.yt/zombie
wkdb.yt/zombie
wkdb.yt/zombie
wkdb.yt/zombie
You don't want to be the person who has to explain to the boss and/or customer that production went down because you deleted code that was otherwise working.
wkdb.yt/zombie
wkdb.yt/zombie
wkdb.yt/zombie
Zombie Code:
Executable code that cannot reliably be identified as currently being live or dead, including both code you control and third-party vendor code.
wkdb.yt/zombie
wkdb.yt/zombie
wkdb.yt/zombie
If the potential "zombie" is actually dead code, we may end up leaving it in the code base, reducing the maintainability of the code
If the potential "zombie" is actually live code, we may end up breaking production.
wkdb.yt/zombie
wkdb.yt/zombie
wkdb.yt/zombie
Can This Code Even Run?:
If the code cannot possibly execute, e.g. there is a syntax or compile-time error in the current PHP runtime just remove it.
Try to find if an execution path exists for the code block in question.
wkdb.yt/zombie
wkdb.yt/zombie
wkdb.yt/zombie
"Unused Class/Method" Highlights
Manually Check Usages
wkdb.yt/zombie
Inspect "Unused Declarations"
wkdb.yt/zombie
Pro: Fast to Run and Built Into IDE
Con: Prone to False Positive/Negative Results
wkdb.yt/zombie
Static Analysis of code can only reveal if an execution path to a block of code exists.
It cannot reveal if a block is being used.
wkdb.yt/zombie
wkdb.yt/zombie
A code tombstone is a production-safe mechanism that triggers when a block of code that we think is dead, is used in an active production environment.
We tag code that we believe to be dead, with a Tombstone, and wait to see if the Tombstone triggers.
If not, the code is dead, and we can safely delete it.
wkdb.yt/zombie
wkdb.yt/zombie
<?php
namespace App\Service;
class Tombstone
{
public static function exhume(): void
{
$backtrace = debug_backtrace();
$zombie = $backtrace[1];
$method = $zombie['class'] . $zombie['type'] . $zombie['function'];
$tombstone = str_replace('\\', '_', $method);
$graveyard = __DIR__ . 'path/to/app/graveyard/';
if (!is_dir($graveyard) && !mkdir($graveyard, recursive: true) && !is_dir($graveyard)) {
throw new \Exception('failed to create graveyard');
}
touch($graveyard . $tombstone);
}
}
wkdb.yt/zombie
wkdb.yt/zombie
<?php
namespace App\Service;
class Tombstone
{
public static function exhume(): void
{
$backtrace = debug_backtrace();
$zombie = $backtrace[1];
$method = $zombie['class'] . $zombie['type'] . $zombie['function'];
$tombstone = str_replace('\\', '_', $method);
$graveyard = __DIR__ . 'path/to/app/graveyard/';
if (!is_dir($graveyard) && !mkdir($graveyard, 0644, true) && !is_dir($graveyard)) {
throw new \Exception('failed to create graveyard');
}
touch($graveyard . $tombstone);
}
}
We don't care who triggered the tombstone.
We don't care how many times the tombstone was triggered.
We only care that the tombstone was triggered at all.
We don't care why they triggered the tombstone.
wkdb.yt/zombie
class ZombieService
{
public function getToken(): AccessToken
{
Tombstone::exhume();
return new AccessToken([
'access_token' => bin2hex(random_bytes(32)),
'expires_in' => 3600,
]);
}
}
We add the Tombstone to the suspect code...
wkdb.yt/zombie
wkdb.yt/zombie
If the tombstone is triggered, a new file is created in our graveyard
It's alive!
wkdb.yt/zombie
<?php
namespace App\Console\Commands;
use App\Notifications\ZombiesFound;
use FilesystemIterator;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
use SplFileInfo;
class SendZombieNotificationEmail extends Command
{
protected $signature = 'zombies:send-email';
protected $description = 'Send an email listing any Zombies in the App Graveyard';
public function handle(): void
{
$files = new FilesystemIterator(storage_path('app/graveyard'));
$zombies = array_map(function (SplFileInfo $file) {
return $file->getFilename();
}, [...$files]);
if (count($zombies)) {
Notification::route('mail', 'developers@example.com')
->notify(new ZombiesFound($zombies));
}
}
}
wkdb.yt/zombie
If possible, if you want to integrate notifications for something like Slack or Bugsnag, do not trigger the notification in the Tombstone.
Create a worker that polls the graveyard and triggers the notifications.
wkdb.yt/zombie
If you do integrate to an external service, whether or not from inside the Tombstone,
YOU MUST IMPLEMENT SOME KIND OF FLOOD PREVENTION!
wkdb.yt/zombie
You must wait at least one reasonable "cycle" for your use case.
Tombstones are effective, not fast.
wkdb.yt/zombie
Just don't wait too long...
wkdb.yt/zombie
wkdb.yt/zombie
wickedbyte/tombstone
wkdb.yt/zombie
Out of the Box:
wkdb.yt/zombie
wkdb.yt/zombie
use WickedByte\Tombstone\Graveyard;
use WickedByte\Tombstone\GraveyardConfiguration;
use WickedByte\Tombstone\Handlers\InMemoryRateLimitHandler;
use WickedByte\Tombstone\Handlers\PhpErrorHandler;
Graveyard::config(new GraveyardConfiguration(handlers: [
new InMemoryRateLimitHandler(),
new PhpErrorHandler(),
// Add additional handlers for your project here...
]));
wkdb.yt/zombie
class ZombieService
{
public function getToken(): AccessToken
{
\tombstone('2024-01-01 This code is *probably* dead');
return new AccessToken([
'access_token' => bin2hex(random_bytes(32)),
'expires_in' => 3600,
]);
}
}
wkdb.yt/zombie
1. PSR-3 Compatible Logger
wkdb.yt/zombie
2. Reporting/Analysis Library
wkdb.yt/zombie
class ZombieService
{
public function getToken(): AccessToken
{
tombstone('2019-10-24', 'andysnell');
return new AccessToken([
'access_token' => bin2hex(random_bytes(32)),
'expires_in' => 3600,
]);
}
}
wkdb.yt/zombie
wkdb.yt/zombie
composer-unused/composer-unused
wkdb.yt/zombie
With a composer.json that looks like this...
wkdb.yt/zombie
We can run "composer unused"...
wkdb.yt/zombie
wkdb.yt/zombie
wkdb.yt/zombie
shipmonk/composer-dependency-analyser
wkdb.yt/zombie
wkdb.yt/zombie
Actively Remove Dead Code
The best time to remove dead code is when you are working on it.
Use your Version Control System
wkdb.yt/zombie
Don't Create Intentional Zombies!
Only commit living code.
(Even if you are really proud of the work)
wkdb.yt/zombie
Don't Use Third-Party Code Directly
Use wrapper and adapter classes to avoid using third-party classes directly.
wkdb.yt/zombie
Keep Dependencies Up to Date
Use your CI pipeline
Updates Should Be Part of Maintainance
wkdb.yt/zombie
Slides/Code/Resources
Rate This Talk
joind.in/talk/3627e
wkdb.yt/zombie-hunt