Zombie Hunt

Find and Remove Dead Code and Dependencies


php[world] 2019

It was a Quiet Friday...

Boss: I just got back from a conference and this blockchain thing is going to be big!  We need to have all the bits and blocks in our app.

JusT Use Someone Else's Work

Easy! Let's Just Integrate with CoinBase using Composer

OutDated Code, Ugh

Is It Dead?

It could Happen to You!

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.

Introducing The Undead

Maintainable

Removable

A Problem

Zombie code is a major blocker to Maintainable and Clean Code

Why Do We Want Maintainable And Clean Code?

Maintainable Code is:

  • Readily Comprehensible 
  • Cheap to Update
  • Secure and Resilient to Avoidable Failures

Let's Hunt Zombies

Preliminary Static Analysis

Try to find if an execution path exists for the code block in question.

Preliminary Static Analysis: GREP

Preliminary Static Analysis: PHPStorm

Find in Path Tool

Preliminary Static Analysis: PHPStorm

"Unused Class/Method" Highlights

Manually Check Usages

Preliminary Static Analysis: PHPStorm

Inspect "Unused Declarations"

Static Analysis Pros/Cons

Pro: Fast to Run and Built Into IDE

Con: Prone to False Positive/Negative Results

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.

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 is dead, with a Tombstone, and wait to see if the Tombstone triggers.

If not, we can safely delete it.

Not My Idea...

Chris Tankersley

 

Night of the Living Dead (Code)

php[architect] February 2019

Let's Build a Basic Tombstone!

<?php

namespace App\Service;

class Tombstone
{
    public static function bury(): void
    {
        $backtrace = debug_backtrace();
        
        $zombie = $backtrace[1];
        
        $method = $zombie['class'] . $zombie['type'] . $zombie['function'];
        
        $tombstone = str_replace('\\', '_', $method);
        
        $graveyard = storage_path('app/graveyard/');

        if (!is_dir($graveyard) && !mkdir($graveyard, 0777, true)) {
            throw new \Exception('failed to create graveyard');
        }

        touch($graveyard . $tombstone);
    }
}

Debug Backtrace

<?php

namespace App\Service;

class Tombstone
{
    public static function bury(): void
    {
        $backtrace = debug_backtrace();
        
        $zombie = $backtrace[1];
        
        $method = $zombie['class'] . $zombie['type'] . $zombie['function'];
        
        $tombstone = str_replace('\\', '_', $method);
        
        $graveyard = storage_path('app/graveyard/');

        if (!is_dir($graveyard) && !mkdir($graveyard, 0777, 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.

BurYING the Zombie

class ZombieService
{
    public function getToken(): AccessToken
    {
        Tombstone::bury();
        return new AccessToken([
            'access_token' => bin2hex(random_bytes(32)),
            'expires_in' => 3600,
        ]);
    }
}

We add the Tombstone to the suspect code...

Now, We Wait...

Checking The GraveYard

If the tombstone is triggered, a new file is created in our graveyard

It's alive!

<?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()
    {
        $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.

 

Create a worker that polls the graveyard and triggers the notifications.

 

What About Integrations?

If you do integrate to an external service in the Tombstone,

 

YOU MUST IMPLEMENT SOME KIND OF FLOOD PREVENTION!

How Long Do We Have to Wait?

You must wait at least one reasonable "cycle" for your use case.

Tombstones are effective, not fast.

Advanced TombStones

Advanced TombStones

// 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);
// 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();
});
class ZombieService
{
    public function getToken(): AccessToken
    {
        tombstone('2019-10-24', 'andysnell');
        return new AccessToken([
            'access_token' => bin2hex(random_bytes(32)),
            'expires_in' => 3600,
        ]);
    }
}

Super-Advanced TombStones

What About Our Vendor Code?

Dead Vendor Eviction

composer-unused

With a composer.json that looks like this...

composer-unused

We can run "composer unused"...

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

Undead Prevention

Don't Create Intentional Zombies!

Only commit living code.

(Even if you are really proud of the work)

 

Use your Version Control System

Undead Prevention

Document, Document, Document!

Commit Vendor Installs Separately

Write Good Commit Messages

Undead Prevention

Don't Use Third-Party Code Directly

Use wrapper and adapter classes to avoid using third-party classes directly.

Undead Prevention

Keep Dependencies Up to Date

Use your CI pipeline

Updates Should Be Part of Deployments

Andy Snell

Thank You!

@andrewsnell

Slide Deck:

https://bit.ly/33XyrN3

Rate This Talk: https://joind.in/talk/7e177

Zombie Hunt - php[world]

By Andy Snell

Zombie Hunt - php[world]

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!

  • 112
Loading comments...

More from Andy Snell