Slides/Code/Resources
Rate This Talk
joind.in/talk/3627ewkdb.yt/zombie-huntwkdb.yt/zombiewkdb.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/zombiewkdb.yt/zombiewkdb.yt/zombiewkdb.yt/zombiewkdb.yt/zombiewkdb.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/zombiewkdb.yt/zombiewkdb.yt/zombieZombie 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/zombiewkdb.yt/zombiewkdb.yt/zombieIf 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/zombiewkdb.yt/zombiewkdb.yt/zombieCan 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/zombiewkdb.yt/zombiewkdb.yt/zombie"Unused Class/Method" Highlights
Manually Check Usages
wkdb.yt/zombieInspect "Unused Declarations"
wkdb.yt/zombiePro: Fast to Run and Built Into IDE
Con: Prone to False Positive/Negative Results
wkdb.yt/zombieStatic 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/zombiewkdb.yt/zombieA 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/zombiewkdb.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/zombiewkdb.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/zombieclass 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/zombiewkdb.yt/zombieIf 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/zombieIf 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/zombieIf you do integrate to an external service, whether or not from inside the Tombstone,
YOU MUST IMPLEMENT SOME KIND OF FLOOD PREVENTION!
wkdb.yt/zombieYou must wait at least one reasonable "cycle" for your use case.
Tombstones are effective, not fast.
wkdb.yt/zombieJust don't wait too long...
wkdb.yt/zombiewkdb.yt/zombiewickedbyte/tombstone
wkdb.yt/zombieOut of the Box:
wkdb.yt/zombiewkdb.yt/zombieuse 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/zombieclass 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/zombie1. PSR-3 Compatible Logger
wkdb.yt/zombie2. Reporting/Analysis Library
wkdb.yt/zombieclass ZombieService
{
public function getToken(): AccessToken
{
tombstone('2019-10-24', 'andysnell');
return new AccessToken([
'access_token' => bin2hex(random_bytes(32)),
'expires_in' => 3600,
]);
}
}wkdb.yt/zombiewkdb.yt/zombiecomposer-unused/composer-unused
wkdb.yt/zombieWith a composer.json that looks like this...
wkdb.yt/zombieWe can run "composer unused"...
wkdb.yt/zombiewkdb.yt/zombiewkdb.yt/zombieshipmonk/composer-dependency-analyser
wkdb.yt/zombiewkdb.yt/zombieActively Remove Dead Code
The best time to remove dead code is when you are working on it.
Use your Version Control System
wkdb.yt/zombieDon't Create Intentional Zombies!
Only commit living code.
(Even if you are really proud of the work)
wkdb.yt/zombieDon't Use Third-Party Code Directly
Use wrapper and adapter classes to avoid using third-party classes directly.
wkdb.yt/zombieKeep Dependencies Up to Date
Use your CI pipeline
Updates Should Be Part of Maintainance
wkdb.yt/zombieSlides/Code/Resources
Rate This Talk
joind.in/talk/3627ewkdb.yt/zombie-hunt