Я не совсем понимаю, почему в треде пошли минусы, но здорово, что такой подход в PHP требует совсем незначительного количества дополнительного кода.
Сложно судить, насколько такой подход в PHP распространён, на корпусе из статьи 709 использований, а у preg_match — 5486 (при этом не все из них разбирают URL'ы). Но вроде бы достаточно часто используется. :)
Возможно, если регулярка уже не читабельна и при этом всё равно посредственно справляется с задачей, хорошо бы URL распарсить какой-нибудь либой и проверять домен или другую интересующую часть через switch/мапу/ifelse.
Я не эксперт в PHP, но в Go есть url.Parse, например.
u, err := url.Parse(urlString)
if err != nil {
// URL не валиден.
}
switch u.Host {
case "blah.com", "foo.com":
// Good.
default:
// Опционально обработать остальные урлы.
}
Если "слишком много кода", то можно это в функцию завернуть.
phpgrep всё же не столько про инспекции. Вы можете с помощью него упрощать рефакторинг, поиск bad practices по вашим собственным правилам. Ещё можно узнавать ответы на вопросы: "как часто в нашем проекте эта функция вызывается с определённой комбинацией аргументов?" или "есть ли у нас места, где тяжёлая для вычисления функция вызывается в цикле?".
Такой анализ можно выполнять и без phpgrep, но это потребует либо больше кода, либо ниндзюцу с регулярными выражениями (если попробуете грепать).
Есть шанс, что позже добавлю опцию replace, чтобы не нужно было руками всё заменять.
В языках, где = не является выражением, yoda style является нежелательным. :)
От этого обидно, что в PHP приходится такое использовать как защиту от ошибки. Но есть линтеры, которые найдут подозрительные применения присваиваний, так что иногда проще сделать линтеры на CI построже, а код оставить читабельным.
В Python частично решили проблему, там присваивание-как-выражение другой токен имеет, опечатку допустить риск почти нулевой. Но даже если запись другая, всё равно эту фичу лучше использовать с ограничениями.
В go-critic даже проверка есть, yodaStyleExpr, которая просит переписать в нормальную запись.
2) При повторном оборачивании ошибки (например, на уровень выше), стектрейс затирается менее информативным.
Это верно. Нет нормального способа "добавить стек, если его ещё нет". Но всё же у вас неточность.
Стек скорее не затирается, а дополняется. Если внутри одной горутины, будет дублирование, которое можно при печати самой ошибки убирать, примеры есть внутри pkg/errors, но если из разных горутин, то стеки будут различаться.
3) При каждом оборачивании обязательно передавать дополнительный текст ошибки, что мне кажется некоторым оверхедом при написании/чтении кода.
Есть WithMessage, который добавляет только сообщение, а есть WithStack, который добавляет только стек.
Быстрее выделение, быстрее очистка, не увеличивает объём работы для GC.
А ещё по времени выполнения более детерменированно.
То, что в Go есть типы-значения (то, что можно использовать не по указателю), позволяет больше всего на стеке размещать, что может иногда сильно помогать.
Escape analysis правда до сих пор многие вещи не распознаёт, но у Matthew Dempsky как-то делился своим полным переписыванием этой части, может, её дорабатывать будет проще. :)
Лучше запускать компиляцию с флагом -gcflags='-m=2', тогда будет понятно, что происходит с аллокациями, что идёт в кучу, а что на стек:
foo.go:16:13: new(bytes.Buffer) escapes to heap
foo.go:16:13: from buf (assigned) at foo.go:16:7
foo.go:16:13: from buf (interface-converted) at foo.go:17:21
foo.go:16:13: from buf (passed to call[argument escapes]) at foo.go:17:21
Даже если у вас new(bytes.Buffer) внутри case, он будет размещён на куче. Когда buf передаётся в качестве io.Writer, будет безусловная аллокация.
Вообще interface{} с производительностью слабо совместим.
Множественные case в type switch плохи как минимум тем, что туда значение пробрасывается как interface{}.
Если нужна скорость, сделайте числовые типы первыми и используйте для (u)int16/32/64 методы binary.BigEndian.PutUint16/32/64 напрямую.
Это может быть эффективнее.
Учтите, что type switch выполняет ветки последовательно, поэтому более частые типы стоит ставить первыми.
Для числовых типов вы вполне можете сделать пути выполнения без лишних аллокаций.
Слайсы, выделяемые в "buf := make([]byte, 4)", где размер заранее известен, Go спокойно размещает на стеке (но в этой функции буфер убегает через return, так что всё равно ему место в куче).
Идея в том, чтобы найти какие ошибки вообще совершались в некоторых проектах, чтобы диагностировать их в будущем, в том числе в других проектах. Плюс если у нас есть reproducer (пример кода с дефектом), проще делать тесты для линтера.
Если ошибку поправили, это не значит, что новый программист не может внести её ещё раз.
В компиляторе gc (который знаком большинству людей) нет векторизации циклов. Вообще. By design. Никто пока не предложил как её внедрить, чтобы не нарушить одно из:
1. Не замедляет время компиляции.
2. Реализация не слишком сложная (maintainability).
3. Имеет примеры важного кода, который будет сильно ускоряться, кроме синтетики.
Я не утверждаю, что это бесполезные вещи, просто напоминаю, что приоритет у этого всего довольно низкий, а порог для включения этих оптимизаций в ядро Go довольно высокий. Как-то так.
Математический код и HPC может и получит буст от векторизации, но для gc по-моему это не самые частые пользователи. Возможно в будущем что-то изменится, но пока ситуация такая. Где-то ещё были разные связанные с этим proposal'ы, в том числе о введении примитивов для использования FMA инструкций, но под рукой списка нет, можете поискать на github трекере, если интересно.
Возможно LLVM-based компилятор будет лучше, но по-моему там пока ещё не достаточно всё зрелое.
Я так, понимаю, линтер собранный lintpack-ом берет задачу парсинга и прохода по коду на себя, а линтеры ответственны только за саму проверку и ничего больше. Это сильно уменьшает затраты на написание (и включение в pipeline) нового линтера.
Да, всё так.
Плюс из коробки всё для тестирования (работает с coverage) и интеграционного тестирования.
Спасибо!
Я не совсем понимаю, почему в треде пошли минусы, но здорово, что такой подход в PHP требует совсем незначительного количества дополнительного кода.
Сложно судить, насколько такой подход в PHP распространён, на корпусе из статьи 709 использований, а у preg_match — 5486 (при этом не все из них разбирают URL'ы). Но вроде бы достаточно часто используется. :)
Возможно, если регулярка уже не читабельна и при этом всё равно посредственно справляется с задачей, хорошо бы URL распарсить какой-нибудь либой и проверять домен или другую интересующую часть через switch/мапу/ifelse.
Я не эксперт в PHP, но в Go есть url.Parse, например.
Если "слишком много кода", то можно это в функцию завернуть.
phpgrep
всё же не столько про инспекции. Вы можете с помощью него упрощать рефакторинг, поиск bad practices по вашим собственным правилам. Ещё можно узнавать ответы на вопросы: "как часто в нашем проекте эта функция вызывается с определённой комбинацией аргументов?" или "есть ли у нас места, где тяжёлая для вычисления функция вызывается в цикле?".Такой анализ можно выполнять и без phpgrep, но это потребует либо больше кода, либо ниндзюцу с регулярными выражениями (если попробуете грепать).
Есть шанс, что позже добавлю опцию replace, чтобы не нужно было руками всё заменять.
В языках, где
=
не является выражением, yoda style является нежелательным. :)От этого обидно, что в PHP приходится такое использовать как защиту от ошибки. Но есть линтеры, которые найдут подозрительные применения присваиваний, так что иногда проще сделать линтеры на CI построже, а код оставить читабельным.
В Python частично решили проблему, там присваивание-как-выражение другой токен имеет, опечатку допустить риск почти нулевой. Но даже если запись другая, всё равно эту фичу лучше использовать с ограничениями.
В go-critic даже проверка есть, yodaStyleExpr, которая просит переписать в нормальную запись.
Возможности такого анализа ограничены.
Это подходит для прототипирования, но не для production решения. Хотя если аккуратно написать шаблоны, должно помочь.
Скорее всего, тут сработало сравнение со строкой через
==
вместо===
. Это не очень хорошая практика.Это верно. Нет нормального способа "добавить стек, если его ещё нет". Но всё же у вас неточность.
Стек скорее не затирается, а дополняется. Если внутри одной горутины, будет дублирование, которое можно при печати самой ошибки убирать, примеры есть внутри
pkg/errors
, но если из разных горутин, то стеки будут различаться.Есть
WithMessage
, который добавляет только сообщение, а естьWithStack
, который добавляет только стек.Быстрее выделение, быстрее очистка, не увеличивает объём работы для GC.
А ещё по времени выполнения более детерменированно.
То, что в Go есть типы-значения (то, что можно использовать не по указателю), позволяет больше всего на стеке размещать, что может иногда сильно помогать.
Escape analysis правда до сих пор многие вещи не распознаёт, но у Matthew Dempsky как-то делился своим полным переписыванием этой части, может, её дорабатывать будет проще. :)
Лучше запускать компиляцию с флагом
-gcflags='-m=2'
, тогда будет понятно, что происходит с аллокациями, что идёт в кучу, а что на стек:Даже если у вас
new(bytes.Buffer)
внутри case, он будет размещён на куче. Когда buf передаётся в качествеio.Writer
, будет безусловная аллокация.Вообще
interface{}
с производительностью слабо совместим.Множественные case в type switch плохи как минимум тем, что туда значение пробрасывается как
interface{}
.Если нужна скорость, сделайте числовые типы первыми и используйте для
(u)int16/32/64
методыbinary.BigEndian.PutUint16/32/64
напрямую.Это может быть эффективнее.
Учтите, что type switch выполняет ветки последовательно, поэтому более частые типы стоит ставить первыми.
Для числовых типов вы вполне можете сделать пути выполнения без лишних аллокаций.
Слайсы, выделяемые в "
buf := make([]byte, 4)
", где размер заранее известен, Go спокойно размещает на стеке (но в этой функции буфер убегает через return, так что всё равно ему место в куче).Вот пример для специализации ветки
uint64
: https://play.golang.org/p/1srtJDsIFIJ.Разница в производительности:
Надеюсь, это является ответом на ваш вопрос.
Готово. А ещё приглашаю в канал
#gocontributing
в слаке, если вам там ещё нет. :)Я тогда допишу в скором времени в этой же статье.
В основном потому что мне захотелось его туда добавить.
Новое место работы, если оно нравится, обычно вызывает у людей какие-то эмоции.
Мне кажется забавный гофер в толстовке получился.
Секцию "о себе" в первый раз использовал. Возможно слегка переборщил и можно было без самого лого, одного лишь талисмана оставить.
Идея в том, чтобы найти какие ошибки вообще совершались в некоторых проектах, чтобы диагностировать их в будущем, в том числе в других проектах. Плюс если у нас есть reproducer (пример кода с дефектом), проще делать тесты для линтера.
Если ошибку поправили, это не значит, что новый программист не может внести её ещё раз.
Надеюсь, так понятнее.
Активность в репозитории до сих пор вроде бы есть, звёздочку поставил. :)
Apocalypse на момент выхода действительно ощущалась слишком сырой.
Возможно как-нибудь гляну что успели доработать в проекте OpenApoc.
1. Не замедляет время компиляции.
2. Реализация не слишком сложная (maintainability).
3. Имеет примеры важного кода, который будет сильно ускоряться, кроме синтетики.
Я не утверждаю, что это бесполезные вещи, просто напоминаю, что приоритет у этого всего довольно низкий, а порог для включения этих оптимизаций в ядро Go довольно высокий. Как-то так.
Математический код и HPC может и получит буст от векторизации, но для gc по-моему это не самые частые пользователи. Возможно в будущем что-то изменится, но пока ситуация такая. Где-то ещё были разные связанные с этим proposal'ы, в том числе о введении примитивов для использования FMA инструкций, но под рукой списка нет, можете поискать на github трекере, если интересно.
Возможно LLVM-based компилятор будет лучше, но по-моему там пока ещё не достаточно всё зрелое.
Способов несколько.
Самый простой — это передача компилятору ключа
-gcflags=-S
.Для примера, соберите вот этот файл (назовём его
foo.go
):С помощью команды
go tool compile -S foo.go
.Помимо прочего, вы увидите вызовы
runtime.concatstring2(SB)
в f1 иruntime.concatstring3(SB)
в f2.Одна из причин, почему мне Go нравится и почему я пробую работать над статическим анализом для него — мне это по силам.
Да, всё так.
Плюс из коробки всё для тестирования (работает с coverage) и интеграционного тестирования.
Конечно, пишут. Библиотеки для написания линтеров и металинтеров, например:
https://go-toolsmith.github.io/
go-namecheck
верифицирует (некоторые) конвенции, чтобы не проверять глазками.Я бы предпочёл, чтобы как можно больше работы по незначительным вещам выполнялось автоматически или, хотя бы, детектировались программами.
Code review это не отменяет. Тут как с линтерами. Они ревью не заменяют, но могут сильно помогать.