Zombie Hunt
Find and Safely Remove Dead Code
BEFORE WE BEGIN...
Slides/Code/Resources
Rate This Talk
joind.in/talk/3627ewkdb.yt/zombie-huntHello, 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/zombie1. 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/zombie2. 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/zombie3. 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/zombieIt Was a Quiet Friday Afternoon...


wkdb.yt/zombieIt Was a Quiet Friday Afternoon...
wkdb.yt/zombieIt Was a Quiet Friday Afternoon...


wkdb.yt/zombie
Three Four Laws of Software Development
wkdb.yt/zombie4. 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/zombieIt could Happen to You!

wkdb.yt/zombie
wkdb.yt/zombieIntroducing 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/zombieIntroducing The Undead
wkdb.yt/zombiewkdb.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/zombieHow To Hunt Zombies

wkdb.yt/zombieFirst Thing First
wkdb.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.
Preliminary Static Analysis
Try to find if an execution path exists for the code block in question.
wkdb.yt/zombiePreliminary Static Analysis: GREP

wkdb.yt/zombiePreliminary Static Analysis: PHPStorm

wkdb.yt/zombiePreliminary Static Analysis: PHPStorm


"Unused Class/Method" Highlights

Manually Check Usages
wkdb.yt/zombiePreliminary Static Analysis: PHPStorm

Inspect "Unused Declarations"
wkdb.yt/zombieStatic Analysis Pros/Cons
Pro: 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.
Static Analysis Pros/Cons
wkdb.yt/zombie
wkdb.yt/zombieWhat 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/zombieLet'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/zombieBurYING 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/zombieNow, We Wait...

wkdb.yt/zombieChecking 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/zombieBest 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/zombieBest 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/zombieBest Practices
You must wait at least one reasonable "cycle" for your use case.
Tombstones are effective, not fast.

wkdb.yt/zombieBest Practices
Just don't wait too long...

wkdb.yt/zombieAdvanced TombStones
wkdb.yt/zombieAdvanced TombStones
wickedbyte/tombstone
wkdb.yt/zombieAdvanced 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/zombieAdvanced TombStones
wkdb.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...
]));Advanced TombStones
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,
]);
}
}Advanced TombStones
wkdb.yt/zombieAdvanced TombStones
1. 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,
]);
}
}Advanced TombStones
wkdb.yt/zombieWhat About Our Vendor Code?

wkdb.yt/zombieDead Vendor Eviction
composer-unused/composer-unused
wkdb.yt/zombie
With a composer.json that looks like this...
wkdb.yt/zombieDead Vendor Eviction

We can run "composer unused"...
wkdb.yt/zombieDead Vendor Eviction

wkdb.yt/zombieDead Vendor Eviction
wkdb.yt/zombieshipmonk/composer-dependency-analyser
Dead Vendor Eviction
wkdb.yt/zombieDead Vendor Eviction

Undead Prevention

wkdb.yt/zombieUndead 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/zombieUndead Prevention
Don't Create Intentional Zombies!
Only commit living code.
(Even if you are really proud of the work)
wkdb.yt/zombieUndead Prevention
Don't Use Third-Party Code Directly
Use wrapper and adapter classes to avoid using third-party classes directly.
wkdb.yt/zombieUndead Prevention
Keep Dependencies Up to Date
Use your CI pipeline
Updates Should Be Part of Maintainance
wkdb.yt/zombieThank You!
Slides/Code/Resources
Rate This Talk
joind.in/talk/3627ewkdb.yt/zombie-huntZombie 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!
- 849