Pull to refresh

Удаляйте свой мертвый код

Reading time 6 min
Views 24K
Original author: Dan Crosta


Пост «Удаление кода» Неда Бэтчелдера (Ned Batchelder) недавно появился на HN, хотя изначально он был написан в 2002 году. Здесь я хочу повторить несколько мыслей Неда, и занять более решительную, чем он, позицию: удаляйте код, как только вы замечаете, что он больше не нужен, без лишних вопросов. Я также предложу некоторые советы из окопов, как определять кандидатов в мертвый код.

То что мертво умереть не может!


Это не просто «очень умная» и своевременная отсылка к поп-культуре. Мертвый код, то есть код, который никогда не выполняется в вашей программе — это реальная помеха для поддержки вашей кодовой базы. Сколько раз вы не могли добавить что-то, что казалось простой функцией или улучшением, только потому что были поставлены в тупик сложностью кода, который должен работать рядом с этой функцией? Насколько приятнее была бы ваша жизнь, если бы добавить новую функцию или исправить ошибку было бы так же просто, как вы предполагали, когда планировали свою работу?

Каждый раз, когда вы хотите внести изменения, необходимо учитывать, как они взаимодействуют со всеми существующими функциями, «костылями», известными ошибками и ограничениями окружающего кода. Это легче сделать, имея меньше кода вокруг объекта, который вы хотите добавить, т.к. при этом нужно будет учитывать меньше вещей и будет меньше вариантов, что что-то пойдет не так. Мертвый код особенно вреден, потому что кажется, что нужно учитывать взаимодействие с ним, но, поскольку он мертв, это всего лишь отвлекающий маневр. Он не может принести пользу вам, так как никогда не выполняется.

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

Он должен уйти


Нед (Бэтчелдер, не Старк) был немного более деликатным и тактичным, чем я в этой статье:
Скажем, у вас есть превосходный класс, который имеет кучу методов. В один прекрасный день вы обнаруживаете, что некий отдельный метод больше не вызывается. Вы оставите его или удалите?

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

Я скажу так: выжигайте землю и не оставляйте код в живых. Лучший код, это код которого у вас нет.

Если вы не такие смелые как я, помните, что системы контроля версий прикроют вас в случае, если вам когда-либо снова понадобится этот код.

Тем не менее, я никогда не испытывал необходимости возвращать что-то, что я уже удалил, по крайней мере, не в буквальном смысле возвращения строка за строкой, дословно, участка кода, который я ранее удалил.

Конечно, я не говорю о таких вещах, как возврат ошибочных коммитов — мы все люди, и я делаю так же много ошибок, как и другие. Я имею в виду, что я никогда не удалял объекты, отправленные в продакшен, а затем возвращал спустя несколько недель или месяцев, думая: «Ну и дела, похоже код, который я писал год или более назад был очень хорош, так что верну-ка я его обратно». Кодовая база живет и развивается с течением времени, поэтому старый код, вероятно, не вяжется с новыми идеями, техниками, фреймворками и стилем используемым сегодня. Я мог бы вернуться к старой версии для обновления, особенно, если это какие-то тонкие моменты, но я никогда не возвращал код обратно массово.

Так что сделайте одолжение себе и своей команде и удалите мертвый код как только заметите его.

Как мы сюда попали?


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

Добавлю еще одну гипотезу в список: мы все можем просто лениться. Безусловно, легче чего-то не делать (т. е. оставить как есть), чем что-то сделать (удалить).

Лень, в конце концов, одна из трех великих добродетелей программиста. Но лень, про которую говорит Ларри Уолл другого рода: «Качество, которое заставляет вас прилагать больше усилий, чтобы снизить общие затраты энергии». С этой точки зрения, удаление мертвого кода это Лень с большой буквы Л — делать что-то, что легко сделать сейчас, чтобы уберечь себя от необходимости делать что-то сложное в будущем. Мы все должны стараться развивать такую Лень. Мне нравится думать о ней, как о «дисциплинированной лени», нашей ежедневной привычке.

Как нам выбраться отсюда?


Я провожу большую часть своего времени программируя на Python, для которого, к сожалению, IDE обычно не могут правильно анализировать полную кодовую базу и автоматически находить никогда не вызываемый код. Но, сочетая дисциплину и некоторые инструменты для анализа программы во время выполнения (run-time tooling), мы можем подойти к этой проблеме с двух сторон.

В простых случаях, хорошее чувство кода может помочь выявить и удалить мертвый код в то время как вы вносите изменения. Представьте, что вы работаете над определенной функцией и замечаете, что одна из веток if/else никогда не может быть выполнена. Я называю это «dead code in the small»* и его довольно легко заметить и удалить, но это требует немного больше усилий, чем можно было бы потратить.

До тех пор пока вы не выработаете привычку замечать это во время всей вашей обычной рутинной работы вы можете добавить ещё один шаг к действиям, которые вы выполняете перед коммитом: проверьте нет ли рядом с вашими изменениями мертвого кода. Это может произойти как раз перед отправкой кода вашим коллегам (вы ведь проводите code review, правда?) так что им не придётся повторять этот процесс, во время просмотра ваших изменений.

Другой вид мертвого кода появляется при удалении последнего, использующего его класса или функции, когда ты вносишь изменения, не понимая, что это последнее место, которое его использует. Это «dead code in the large»*, и его труднее обнаружить в ходе обычного программирования, если только вам не повезло обладать эйдетической памятью, или знать кодовую базу как свои пять пальцев.

Вот когда нам могут помочь инструменты для анализа программы во время выполнения. В Magnetic мы используем пакет Неда (да, того же Неда) coverage.py, чтобы помочь нам принимать решения о мертвом коде. Обычно coverage используется во время тестирования, чтобы гарантировать, что ваши тест-кейсы правильно выполняют тестируемый код, но мы также используем его в нашем коде «работающем как обычно», чтобы понять, что используется, а что нет:
import coverage

cov = coverage.Coverage(
    data_file="/path/to/my_program.coverage",
    auto_data=True,
    cover_pylib=False,
    branch=True,
    source=["/path/to/my/program"],
)
cov.start()

# ... делаем что-нибудь ...

cov.stop()
cov.save()

Здесь создается объект Coverage с несколькими опциями, чтобы сделать отчет более удобным. Сначала мы говорим ему, где сохранять свои данные (мы будем использовать их позже, для создания удобного HTML-отчета, о том что используется, а что — нет), и просим его автоматически открывать и добавлять их в этот файл с помощью auto_data=True. Далее мы просим не беспокоится об обработке стандартной библиотеки и установленных пакетов — это не наш код, так что мы можем предположить, что многое из того что он содержит, может нами не использоваться. Это не мёртвый код, который мы должны поддерживать, так что можно смело его игнорировать. Мы просим его вычислить покрытие ветвлений (выполняются ли оба состояния true и false для каждого оператора if). И, наконец, мы указываем где находятся наши исходники, так что он может соединить свои знания о том, что используется и не используется в исходном коде, для составления отчета.

После запуска нашей программы, мы можем создать HTML-отчет:
$ COVERAGE_FILE=/path/to/my_program.coverage coverage html -d /path/to/output

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

(полный пример HTML-отчета можно найти в документации coverage.py)

Строки выделенные красным цветом не вызывались во время выполнения программы. Эти строки (и, возможно, методы) кандидаты для удаления в качестве мертвого кода.

Я оставлю вам три предупреждения об использовании этого подхода для нахождения и удаления мертвого кода:
  1. Будьте осторожны при рассмотрении итогов работы coverage — тот факт, что строка или функция не были выполнены в течение одного запуска программы не означает, что они обязательно мертвы или недоступны вообще. Вы всё равно должны проверять код, чтобы определить, действительно ли они совершенно мертвы в вашем приложении.
  2. Вычисление покрытия означает, что ваша программа должна выполнить больше работы, так она будет медленнее, при запуске в этом режиме. Я бы не рекомендовал запускать это в продакшене, но в промежуточной среде (staging environment) или в целевых сценариях все должно быть в порядке. Как всегда, если производительность является важной задачей, вы должны точно измерить, какое влияние оказывает вычисление покрытия, прежде чем запускать его.
  3. Наконец, не доверяйте отчетам о покрытии кода во время тестовых запусков. Какой-то код может быть мертв, но тесты все равно будут запускать его; и какой-то код может быть жив, но не запускаться тестами!


Слова на прощание


Перед тобой, дорогой читатель, я должен извиниться. Я опустил важную часть поста Неда, когда цитировал его ранее.
Он говорит:
В данном случае нет простого ответа на этот вопрос, потому что это зависит от класса и от метода. [...] Грубый ответ может быть таков: если этот класс является частью фреймворка, то оставьте его, если же он часть приложения, то удалите его.

Если вы пишете библиотеку или фреймворк, а не приложение, то вопрос мертвого кода становится с одной стороны сложнее, а с другой проще. В сущности, вы никогда не сможете удалить часть публичного API (за исключением несовместимости в мажорных версиях). Фактически весь ваш публичный API это живой код, даже если вы сами не используете его. Но за внешним интерфейсом, все ещё может возникать мертвый код, и он должен быть удален.

Удаляйте свой мертвый код!



* — отсылки к парадигмам программирования «programming in the small» и «programming in the large» (прим. перев.)
Tags:
Hubs:
+26
Comments 61
Comments Comments 61

Articles