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