Zombie Hunt
Find and Remove Dead Code and Dependencies
Andy Snell | @andrewsnell
It was a Quiet Friday...
Boss: Our biggest client is pushing us to add some different payment methods to what they currently accept online. As usual, they need it done yesterday. Just get them setup to accept bitcoin -- that will keep them happy for the moment-- get it done as soon as you can.
@andrewsnell
You: Ok
JusT Use Someone Else's Work
Easy! Let's Just Integrate with CoinBase using Composer
OutDated Code, Ugh
@andrewsnell
Is It Dead?
It could Happen to You!
@andrewsnell
It could Happen to You!
Introducing The Undead
Zombie Code is any executable code in a project that cannot be readily identified as currently being used. This includes both code you control and third-party vendor code.
Even if a vendor package is not being used by your project, it still constrains the other packages pulled in by composer.
@andrewsnell
Introducing The Undead
Maintainable
Removable
A Problem
@andrewsnell
Zombie code is a major blocker to Maintainable and Clean Code
@andrewsnell
Introducing The Undead
Why Do We Want Maintainable And Clean Code?
Maintainable Code is:
- Readily Comprehensible
- Cheap to Update
- Secure and Resilient to Avoidable Failures
@andrewsnell
Keeping Your Code Maintainable Requires Regular Pruning of Dead Code and Vendor Packages
@andrewsnell
Why Do We Want Maintainable And Clean Code?
@andrewsnell
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.
Let's Hunt Zombies
@andrewsnell
Preliminary Static Analysis
Try to find if an execution path exists for the code block in question.
@andrewsnell
Preliminary Static Analysis: GREP
@andrewsnell
Preliminary Static Analysis: PHPStorm
Find in Path Tool
@andrewsnell
Preliminary Static Analysis: PHPStorm
"Unused Class/Method" Highlights
Manually Check Usages
Preliminary Static Analysis: PHPStorm
Inspect "Unused Declarations"
@andrewsnell
Static Analysis Pros/Cons
Pro: Fast to Run and Built Into IDE
Con: Prone to False Positive/Negative Results
@andrewsnell
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.
@andrewsnell
Static Analysis Pros/Cons
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.
@andrewsnell
Not My Idea...
Chris Tankersley
Night of the Living Dead (Code)
php[architect] February 2019
@andrewsnell
Let's Build a Tombstone!
@andrewsnell
<?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)) {
throw new \Exception('failed to create graveyard');
}
touch($graveyard . $tombstone);
}
}
Debug Backtrace
@andrewsnell
@andrewsnell
<?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)) {
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.
@andrewsnell
We don't care why they triggered the tombstone.
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...
@andrewsnell
Now, We Wait...
@andrewsnell
Checking The GraveYard
If the tombstone is triggered, a new file is created in our graveyard
It's alive!
@andrewsnell
<?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();
}, iterator_to_array($files));
if (count($zombies)) {
Notification::route('mail', 'developers@example.com')
->notify(new ZombiesFound($zombies));
}
}
}
What About Integrations?
If possible, if you want to integrate notifications for something like Slack or Bugsnag, do not trigger the notification in the Tombstone.
@andrewsnell
Create a worker that polls the graveyard and triggers the notifications.
What About Integrations?
If you do integrate to an external service, whether or not from inside the Tombstone,
YOU MUST IMPLEMENT SOME KIND OF FLOOD PREVENTION!
@andrewsnell
How Long Do We Have to Wait?
You must wait at least one reasonable "cycle" for your use case.
Tombstones are effective, not fast.
@andrewsnell
Advanced TombStones
@andrewsnell
Advanced TombStones
@andrewsnell
// define tombstone() by adding to composer autoloader or including it
include ('vendor/scheb/tombstone/tombstone.php');
$log_dir = 'path/to/logging/dir/';
$stream_handler = new StreamHandler($log_dir . 'tombstone.log');
GraveyardProvider::getGraveyard()->addHandler($stream_handler);
$analyzer_log_handler = new AnalyzerLogHandler($log_dir);
GraveyardProvider::getGraveyard()->addHandler($analyzer_log_handler);
$logger = new Monolog\Logger('tombstone_logger');
$log_handler = new PsrLoggerHandler($logger, Psr\Log\LogLevel::WARNING);
GraveyardProvider::getGraveyard()->addHandler($log_handler);
$bufferedGraveyard = new BufferedGraveyard(GraveyardProvider::getGraveyard());
GraveyardProvider::setGraveyard($bufferedGraveyard);
register_shutdown_function(function () use ($bufferedGraveyard) {
$bufferedGraveyard->flush();
});
Advanced TombStones
class ZombieService
{
public function getToken(): AccessToken
{
tombstone('2019-10-24', 'andysnell');
return new AccessToken([
'access_token' => bin2hex(random_bytes(32)),
'expires_in' => 3600,
]);
}
}
@andrewsnell
Advanced TombStones
Super-Advanced TombStones
@andrewsnell
What About Our Vendor Code?
@andrewsnell
Dead Vendor Eviction
@andrewsnell
composer-unused
With a composer.json that looks like this...
@andrewsnell
composer-unused
We can run "composer unused"...
@andrewsnell
composer-unused
Undead Prevention
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
@andrewsnell
Undead Prevention
Don't Create Intentional Zombies!
Only commit living code.
(Even if you are really proud of the work)
@andrewsnell
Undead Prevention
Don't Use Third-Party Code Directly
Use wrapper and adapter classes to avoid using third-party classes directly.
@andrewsnell
Undead Prevention
Keep Dependencies Up to Date
Use your CI pipeline
Updates Should Be Part of Maintainance
@andrewsnell
Andy Snell
Thank You!
@andrewsnell
bit.ly/2wcVx7n
Slide Deck & Resources
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!
- 259