Zombie Hunt
Find and Safely Remove Dead Code
BEFORE WE BEGIN...
Slides/Code/Resources
Rate This Talk
joind.in/talk/3627e
wkdb.yt/zombie-hunt
Hello, World
wkdb.yt/zombie
- Contract PHP Developer and Consultant
- Currently Living in Dallas w/ Wife and Dog
- Never Intended to Be a Developer
Three Laws of Software Development
wkdb.yt/zombie
1. Change Happens
"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.
Three Laws of Software Development
wkdb.yt/zombie
2. Code Does Not Occur in Isolation
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.
Three Laws of Software Development
wkdb.yt/zombie
3. It is Cheaper to Remove than to Refactor
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".
It Was a Quiet Friday Afternoon...
wkdb.yt/zombie
wkdb.yt/zombie
It Was a Quiet Friday Afternoon...
wkdb.yt/zombie
It Was a Quiet Friday Afternoon...
wkdb.yt/zombie
It Was a Quiet Friday Afternoon...
wkdb.yt/zombie
Three Four Laws of Software Development
wkdb.yt/zombie
4. Nobody Is Happy When Production Breaks
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.
It could Happen to You!
wkdb.yt/zombie
It could Happen to You!
wkdb.yt/zombie
wkdb.yt/zombie
Introducing The Undead
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
Introducing The Undead
wkdb.yt/zombie
wkdb.yt/zombie
Maintainable
Removable
Zombie
The Zombie Problem
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
How To Hunt Zombies
wkdb.yt/zombie
First Thing First
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.
Preliminary Static Analysis
Try to find if an execution path exists for the code block in question.
wkdb.yt/zombie
Preliminary Static Analysis: GREP
wkdb.yt/zombie
Preliminary Static Analysis: PHPStorm
wkdb.yt/zombie
Preliminary Static Analysis: PHPStorm
"Unused Class/Method" Highlights
Manually Check Usages
wkdb.yt/zombie
Preliminary Static Analysis: PHPStorm
Inspect "Unused Declarations"
wkdb.yt/zombie
Static Analysis Pros/Cons
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.
Static Analysis Pros/Cons
wkdb.yt/zombie
wkdb.yt/zombie
What Is A TombStone?
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
Let's Build a Tombstone!
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);
}
}
Debug Backtrace
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
BurYING the 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
Now, We Wait...
wkdb.yt/zombie
Checking The GraveYard
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
Best Practices
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
Best Practices
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
Best Practices
You must wait at least one reasonable "cycle" for your use case.
Tombstones are effective, not fast.
wkdb.yt/zombie
Best Practices
Just don't wait too long...
wkdb.yt/zombie
Advanced TombStones
wkdb.yt/zombie
Advanced TombStones
wickedbyte/tombstone
wkdb.yt/zombie
Advanced TombStones
Out of the Box:
- PHP Error Handing
- File System Graveyard
- PSR-3 Loggers
- PSR-6/PSR-16 Cache Implementations
- PSR-14 Event Dispatcher Implementations
wkdb.yt/zombie
Advanced TombStones
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...
]));
Advanced TombStones
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,
]);
}
}
Advanced TombStones
wkdb.yt/zombie
Advanced TombStones
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,
]);
}
}
Advanced TombStones
wkdb.yt/zombie
What About Our Vendor Code?
wkdb.yt/zombie
Dead Vendor Eviction
composer-unused/composer-unused
wkdb.yt/zombie
With a composer.json that looks like this...
wkdb.yt/zombie
Dead Vendor Eviction
We can run "composer unused"...
wkdb.yt/zombie
Dead Vendor Eviction
wkdb.yt/zombie
Dead Vendor Eviction
wkdb.yt/zombie
shipmonk/composer-dependency-analyser
Dead Vendor Eviction
wkdb.yt/zombie
Dead Vendor Eviction
Undead Prevention
wkdb.yt/zombie
Undead Prevention
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
Undead Prevention
Don't Create Intentional Zombies!
Only commit living code.
(Even if you are really proud of the work)
wkdb.yt/zombie
Undead Prevention
Don't Use Third-Party Code Directly
Use wrapper and adapter classes to avoid using third-party classes directly.
wkdb.yt/zombie
Undead Prevention
Keep Dependencies Up to Date
Use your CI pipeline
Updates Should Be Part of Maintainance
wkdb.yt/zombie
Thank You!
Slides/Code/Resources
Rate This Talk
joind.in/talk/3627e
wkdb.yt/zombie-hunt
Zombie Hunt: Find and Safely Remove Undead Code
By Andy Snell
Zombie Hunt: Find and Safely Remove Undead Code
Over time, and without careful maintenance, software projects tend to accumulate code and dependencies that just don’t belong in the code base anymore. Dead, rotting, and outdated code is a stumbling block to upgrading, refactoring, and maintaining legacy code, and at worse could become a security vulnerability. We’ll cover how to identify zombie code and dependencies, including using some newer tools built into PhpStorm and GitHub. We’ll also talk about different types of code tombstones and how to use them. Finally, we’ll discuss how to avoid having zombies in your code in the first place!
- 465