Как найти недоступные функции с DeadCode

Как найти недоступные функции с DeadCode

29 июня 2025 г.

Функции, которые являются частью исходного кода вашего проекта, но никогда не могут быть достигнуты в каком -либо выполнении, называются «мертвый код», и они прилагают усилия по обслуживанию кодовой базы. Сегодня мы рады поделиться инструментом по имениdeadcodeЧтобы помочь вам определить их.

$ go install golang.org/x/tools/cmd/deadcode@latest
$ deadcode -help
The deadcode command reports unreachable functions in Go programs.

Usage: deadcode [flags] package...

Пример

За последний год или около того мы внесли много изменений в структуруGopls, языковой сервер для Go, который Powers против кода и других редакторов. Типичное изменение может переписать некоторую существующую функцию, заботясь о том, чтобы его новое поведение удовлетворяло потребности всех существующих вызывающих абонентов.

Иногда, приложив все эти усилия, мы обнаруживали наше разочарование, что один из абонентов никогда не был достигнут ни в каком исполнении, так что это могло бы безопасно удалить. Если бы мы знали это заранее, наша задача рефакторинга была бы проще.

Программа Simple Go ниже иллюстрирует проблему:

module example.com/greet
go 1.21


package main

import "fmt"

func main() {
    var g Greeter
    g = Helloer{}
    g.Greet()
}

type Greeter interface{ Greet() }

type Helloer struct{}
type Goodbyer struct{}

var _ Greeter = Helloer{}  // Helloer  implements Greeter
var _ Greeter = Goodbyer{} // Goodbyer implements Greeter

func (Helloer) Greet()  { hello() }
func (Goodbyer) Greet() { goodbye() }

func hello()   { fmt.Println("hello") }
func goodbye() { fmt.Println("goodbye") }

Когда мы выполняем это, это говорит привет:

$ go run .
hello

Из ее вывода ясно, что эта программа выполняетhelloфункционировать, но неgoodbyeфункция Что менее ясно с первого взгляда, так это то, чтоgoodbyeФункция никогда не может быть вызвана. Однако мы не можем просто удалитьgoodbye, потому что это требуетсяGoodbyer.Greetметод, который, в свою очередь, необходим для реализацииGreeterинтерфейс которогоGreetметод, который мы можем видеть, вызывается изmainПолем

Но если мы будем работать вперед от Main, мы увидим, что нетGoodbyerценности когда -либо создаются, поэтомуGreetпозвонить вmainможет только достичьHelloer.GreetПолем Это идея алгоритма, используемогоdeadcodeинструмент.

Когда мы запускаем DeadCode в этой программе, инструмент говорит нам, чтоgoodbyeфункция иGoodbyer.GreetМетод оба недоступны:

$ deadcode .
greet.go:23: unreachable func: goodbye
greet.go:20: unreachable func: Goodbyer.Greet

С этими знаниями мы можем безопасно удалить обе функции, а такжеGoodbyerТип сам.

Инструмент может также объяснить, почемуhelloфункция живой. Он отвечает цепочкой функциональных вызовов, которые достигаютhello, начиная с главного:

$ deadcode -whylive=example.com/greet.hello .
                  example.com/greet.main
dynamic@L0008 --> example.com/greet.Helloer.Greet
 static@L0019 --> example.com/greet.hello

Вывод предназначен для того, чтобы быть легко читаемым на терминале, но вы можете использовать-jsonили-f=templateФлаги, чтобы указать более богатые выходные форматы для потребления другими инструментами.

Как это работает

АdeadcodeкомандованиенагрузкиВсанары, иПроверки типауказанные пакеты, затем преобразуют их впромежуточное представлениеПодобно типичному компилятору.

Затем он использует алгоритм под названиемАнализ быстрого типа(RTA), чтобы создать набор функций, которые можно добраться, что изначально представляет собой только точки входа каждогоmainПакет:mainФункция и функция инициализатора пакета, которая назначает глобальные переменные и вызовы именованные функцииinitПолем

RTA рассматривает операторы в теле каждой достижимой функции, чтобы собрать три вида информации: набор функций, которые он вызывает напрямую; набор динамических вызовов, которые он выполняет с помощью методов интерфейса; и набор типов, которые он преобразует в интерфейс.

Прямые вызовы функций просты: мы просто добавляем Callee в набор достижимых функций, и если мы впервые столкнулись с Callee, мы проверяем его функциональный корпус так же, как и для Main.

Динамические вызовы с помощью методов интерфейса сложнее, потому что мы не знаем набора типов, которые реализуют интерфейс. Мы не хотим предполагать, что каждый возможный метод в программе, тип которых является возможной целью для вызова, потому что некоторые из этих типов могут быть созданы только из мертвого кода! Вот почему мы собираем набор типов, преобразованных в интерфейсы: конверсия делает каждый из этих типов доступным изmain, так что его методы теперь являются возможными целями динамических вызовов.

Это приводит к ситуации с курицей и яйцом. Когда мы сталкиваемся с каждой новой достижимой функцией, мы обнаруживаем больше вызовов метода интерфейса и больше преобразования конкретных типов в типы интерфейсов. Но по мере того, как поперечный продукт этих двух наборов (вызовы метода интерфейса × бетонные типы) растут все больше, мы обнаруживаем новые достижимые функции.

Этот класс задач, называемый «динамический программирование», может быть решен путем (концептуально), создавая контрольные марки в большой двухмерной таблице, добавляя строки и столбцы по ходу дела, пока не будет больше проверки. Контрольные отметки в финальной таблице рассказывают нам, что доступно; Попустые ячейки являются мертвым кодом.

The main function causes Helloer to be instantiated, and the g.Greet calldispatches to the Greet method of each type instantiated so far.

Динамические вызовы (не-метод) функций обрабатываются аналогично интерфейсам одного метода. И звонки сделаныиспользуя отражениесчитается, что он достигает любого метода любого типа, используемого в конверсии интерфейса, или любого типа, производимого из одного, используяreflectупаковка. Но принцип одинаков во всех случаях.

Тесты

RTA-это анализ целой программы. Это означает, что он всегда начинается с основной функции и работает вперед: вы не можете начать с библиотечного пакета, такого какencoding/jsonПолем

Тем не менее, большинство библиотечных пакетов имеют тесты, а тесты имеют основные функции. Мы не видим их, потому что они генерируются за кулисамиgo test, но мы можем включить их в анализ, используя-testфлаг.

Если это сообщает, что функция в библиотечном пакете мертва, это признак того, что ваш тестовый покрытие может быть улучшено. Например, эта команда перечисляет все функции вencoding/jsonкоторые не достигаются ни одним из его тестов:

$ deadcode -test -filter=encoding/json encoding/json
encoding/json/decode.go:150:31: unreachable func: UnmarshalFieldError.Error
encoding/json/encode.go:225:28: unreachable func: InvalidUTF8Error.Error

(-filterФлаг ограничивает вывод до пакетов, соответствующих регулярному выражению. По умолчанию инструмент сообщает все пакеты в начальном модуле.)

Обоснованность

Все инструменты статического анализаобязательносоздать несовершенные приближения возможного динамического поведения целевой программы. Предположения и выводы инструмента могут быть «звуковыми», означающими консервативные, но, возможно, чрезмерно осторожные, или «необразные», что означает оптимистичный, но не всегда правильный.

Инструмент DeadCode не является исключением: он должен аппроксимировать набор целей динамических вызовов через значения функции и интерфейса или использование отражения. В этом отношении инструмент - звук. Другими словами, если она сообщает о функции как мертвый код, это означает, что функция не может быть вызвана даже с помощью этих динамических механизмов. Однако инструмент может не сообщать о некоторых функциях, которые на самом деле никогда не могут быть выполнены.

Инструмент DeadCode также должен аппроксимировать набор вызовов, выполненных из функций, не записанных в Go, которые он не видит. В этом отношении инструмент не звучит. Его анализ не знает о функциях, вызванных исключительно из кода сборки, или о псевдонировании функций, которые возникают изgo:linknameДиректива. К счастью, обе эти функции редко используются вне времени выполнения.

Попробуйте это

Мы бежимdeadcodeПериодически в наших проектах, особенно после рефакторинга, чтобы помочь определить части программы, которые больше не нужны.

С помощью мертвого кода, вылезающего, вы можете сосредоточиться на устранении кода, время которого подошло к концу, но это упорно остается живым, продолжая истощать вашу жизненную силу. Мы называем такие функции нежити «код вампира»!

Пожалуйста, попробуйте:

$ go install golang.org/x/tools/cmd/deadcode@latest

Мы нашли это полезным, и мы надеемся, что вы тоже.


Алан Донован

ФотоМария ЗаричнаНеспособный

Эта статья доступна наБлог GOПод CC по лицензии 4,0.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE