
Как найти недоступные функции с 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
, так что его методы теперь являются возможными целями динамических вызовов.
Это приводит к ситуации с курицей и яйцом. Когда мы сталкиваемся с каждой новой достижимой функцией, мы обнаруживаем больше вызовов метода интерфейса и больше преобразования конкретных типов в типы интерфейсов. Но по мере того, как поперечный продукт этих двух наборов (вызовы метода интерфейса × бетонные типы) растут все больше, мы обнаруживаем новые достижимые функции.
Этот класс задач, называемый «динамический программирование», может быть решен путем (концептуально), создавая контрольные марки в большой двухмерной таблице, добавляя строки и столбцы по ходу дела, пока не будет больше проверки. Контрольные отметки в финальной таблице рассказывают нам, что доступно; Попустые ячейки являются мертвым кодом.
Динамические вызовы (не-метод) функций обрабатываются аналогично интерфейсам одного метода. И звонки сделаныиспользуя отражениесчитается, что он достигает любого метода любого типа, используемого в конверсии интерфейса, или любого типа, производимого из одного, используя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.
Оригинал