Плюсы и минусы отказа Go от неиспользуемых зависимостей

Новый язык Google Go пытается упростить управление зависимостями с помощью явно требует, чтобы все зависимости, перечисленные в модуле, действительно использовались. Компилятор отклонит модуль, который объявляет зависимость от модуля, не используя ничего из этого модуля.

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

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

Есть ли у вас опыт применения этого в других языках? Каковы плюсы и минусы этого подхода?


person Carl Seleborg    schedule 01.12.2009    source источник


Ответы (3)


Вам нужно не только явно использовать все зависимости, но также должны использоваться все переменные. Компилятор выдаст вам ошибки, если у вас есть неиспользуемые переменные.

Они раздражают. Но это сделает других счастливыми, потому что они получат чистый код.

Я думаю, что, вероятно, разработчики Go хотели, чтобы Go был языком, который в значительной степени зависит от IDE.

person Randy Sugianto 'Yuku'    schedule 01.12.2009
comment
+1 за дизайн на основе IDE. Я пришел к такому же выводу, в основном из-за того, что только объектные файлы имеют что-то похожее на заголовок, а также из-за того, что Go включает модули для разбора Go. - person György Andrasek; 01.12.2009

Я предполагаю, что самой большой мотивацией для этого и самым большим результатом является улучшение времени компиляции. В техническом видео-предварительном обзоре подчеркивается их способность компилировать большие объемы кода за короткие промежутки времени (в их примере было 200 000 строк кода за 8 секунд на MacBook — технические характеристики машины не приводились).

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

Вот пример того, как что-то будет работать в текущей системе C/C++:

Класс A определен в заголовочном файле C_H и реализован в C_CPP. Класс B является производным от C и реализован в файлах B_H и B_CPP. Класс A является производным от B и реализован в файлах A_H и A_CPP.

Из-за цепочек производных A_CPP включает A_H, B_H и C_H. B_CPP включает B_H и C_H. C_CPP включает C_H. Из-за особенностей препроцессора C, который, по сути, превращает #include в операцию вырезания и вставки, содержимое C_H проходит через компилятор 3 раза, а B_H — дважды.

Кроме того, содержимое A_CPP, B_CPP и C_CPP живет в своих собственных объектных файлах. Следовательно, когда компоновщик переходит к разрешению A.o, он вынужден загружать и обрабатывать как B.o, так и C.o. Кроме того, когда он разрешает B.o, он должен снова обрабатывать C.o.

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

Go подходит к вещам по-другому. Слова прямо из PDF из их технического доклада:

«Компилятор Go извлекает информацию о типе транзитивной зависимости из объектного файла — но только то, что ему нужно. Если A.go зависит от B.go, зависит от C.go: — скомпилируйте C.go, B.go, затем A.go. - для компиляции A.go компилятор читает B.o, а не C.o. В масштабе это может дать огромное ускорение."

Хорошо, небольшая касательная сделана. Почему это актуально? Ответ также содержится в PDF-файле Go Tech Talk:

«Пакетная модель: явные зависимости для ускорения сборки».

Я предполагаю, что разработчики Go придерживались мнения, что, когда время компиляции измеряется в секундах, даже для очень больших проектов, разработчикам более продуктивно сохранять такое короткое время компиляции. Скажем, мне требуется 8 секунд, чтобы скомпилировать 200 000 строк кода и обнаружить, что у меня есть посторонний импорт пакета, 5-10 секунд (с хорошей IDE или хорошим знакомством с вашей средой разработки), чтобы найти и исправить это, и еще 8 секунд на перекомпиляцию. Назовите это 30 секундами всего - и теперь все мои будущие компиляции остаются в диапазоне 10 секунд. Или мы можем позволить нашему модулю расти, включая ненужные зависимости, и наблюдать, как время компиляции увеличивается с 8 до 10, 12 или 15 секунд. Это кажется не таким уж большим, потому что мы все привыкли компилировать время порядка минут, но когда вы начинаете понимать, что это снижение производительности на 25%, вы останавливаетесь и думаете об этом на минуту.

Время компиляции Go уже молниеносно. Учтите также, что скорости процессоров по-прежнему растут (хотя и не так сильно, как в прошлом) и что количество доступных ядер также увеличивается (а компиляция больших объемов кода хорошо подходит для многопоточности). 200 000 строк кода за 8 секунд сегодня означают, что вполне разумно представить, что 200 000 строк кода компилируются практически мгновенно за 10 лет. Я думаю, что команда Go приняла сознательное решение оставить время компиляции в прошлом, и хотя проблема, которую вы поднимаете, является лишь очень небольшой частью этого, она является частью тот.

С другой стороны, команда Go также, кажется, разработала философию дизайна языка, которая требует использования некоторых хороших методов программирования. К их чести, они приложили усилия, чтобы добиться этого без серьезных потерь производительности, и в значительной степени преуспели. [Кроме того: единственные две вещи, которые я могу придумать навскидку, которые действительно влияют на производительность, — это сборка мусора и принудительно инициализированные переменные — и последнее довольно тривиально в наши дни.] Это будет сильно раздражать некоторых программистов, в то время как другие будут счастливы. . Это старый-престарый спор в мире программирования, и совершенно ясно, на чьей стороне Go, нравится вам это или нет.

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

person Russell Newquist    schedule 01.12.2009
comment
Если для модуля A требуется модуль B, для которого требуется модуль C, большинство компиляторов скомпилируют C, скомпилируют B и C (снова), а затем скомпилируют A, B (снова) и C (снова), а затем решат проблемы с компоновкой — нет, они бы 'т. Они компилируют каждый исходный файл один раз, а затем связывают их вместе. - person Nick Johnson; 01.12.2009
comment
Ник, я немного ошибся и сейчас обновлю свой ответ с исправлением. Однако, если вы используете заголовочные файлы в C/C++, вы получите большие фрагменты кода, которые компилируются более одного раза. - person Russell Newquist; 01.12.2009
comment
Да, заголовки включаются более одного раза, потому что они обрабатываются препроцессором. Если у вас есть «большие» биты кода в ваших файлах заголовков, УР ДЕЛАЕТ ЭТО НЕПРАВИЛЬНО. - person Nick Johnson; 01.12.2009
comment
Но нравится вам это или нет, многие люди делают это именно так. А в больших проектах это может быть трудно контролировать. - person Russell Newquist; 02.12.2009
comment
Кроме того, даже если в файлах заголовков находится очень небольшой процент вашего кода, в больших проектах он накапливается. - person Russell Newquist; 02.12.2009
comment
Кроме того, для определенных проблем характер встраивания и шаблонов C/C++ иногда делает очень проблематичным, если не невозможным, сохранение кода за пределами файлов заголовков. - person Russell Newquist; 02.12.2009

Как упомянул yuku, если у вас есть IDE, которая соответствует тому, что Netbeans и Eclipse могут сделать для java, вам не нужно заботиться о таких вещах.

Щелкните правой кнопкой мыши маленькую лампочку в поле и выберите «Удалить все неиспользуемые зависимости».

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

Единственная разница здесь в том, что, в отличие от других языков, у вас есть компилятор, который жалуется в дополнение к IDE, но если вы все равно используете IDE, это перестает быть проблемой.

На работе у нас есть правила кодирования, которые в значительной степени гласят, что мы должны делать то же самое (сами), помимо прочего, для других языков, конечно. Так что я бы сказал, что у такого рода действительно есть реальное применение. Хотя ИМХО, компилятор должен дать разработчику возможность включать и выключать это поведение. Кто-нибудь строгий режим?

person bguiz    schedule 01.12.2009
comment
+1 за строгий режим — мне нравится эта функция, но я также думаю, что было бы неплохо иметь возможность ее отключить. - person Russell Newquist; 01.12.2009