Pull to refresh
139
0
Искандер @quasilyte

https://www.quasilyte.dev/ebiten/ru/

Send message

Спасибо!


Я не совсем понимаю, почему в треде пошли минусы, но здорово, что такой подход в 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, которая просит переписать в нормальную запись.

Возможности такого анализа ограничены.


Это подходит для прототипирования, но не для production решения. Хотя если аккуратно написать шаблоны, должно помочь.


while ($percentage == '100')

Скорее всего, тут сработало сравнение со строкой через == вместо ===. Это не очень хорошая практика.

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, так что всё равно ему место в куче).


Вот пример для специализации ветки uint64: https://play.golang.org/p/1srtJDsIFIJ.
Разница в производительности:


$ go-benchrun Old New -benchmem -count=10
  Running old benchmarks:
goos: linux
goarch: amd64
BenchmarkOld-8       1000000          1402 ns/op         816 B/op         11 allocs/op
BenchmarkNew-8      50000000            24.3 ns/op         8 B/op          1 allocs/op
PASS
ok      _/<ВЦ>  12.802s
  Benchstat results:
name   old time/op    new time/op    delta
Old-8    1.42µs ± 5%    0.03µs ± 3%  -98.24%  (p=0.000 n=10+10)

name   old alloc/op   new alloc/op   delta
Old-8      816B ± 0%        8B ± 0%  -99.02%  (p=0.000 n=10+10)

name   old allocs/op  new allocs/op  delta
Old-8      11.0 ± 0%       1.0 ± 0%  -90.91%  (p=0.000 n=10+10)

Надеюсь, это является ответом на ваш вопрос.

Готово. А ещё приглашаю в канал #gocontributing в слаке, если вам там ещё нет. :)

О, об этом я как-то не подумал. Хорошее дополнение, спасибо!

Я тогда допишу в скором времени в этой же статье.
Зачем в этой статье логотипы VK?

В основном потому что мне захотелось его туда добавить.


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


Секцию "о себе" в первый раз использовал. Возможно слегка переборщил и можно было без самого лого, одного лишь талисмана оставить.

Значит я плохо описал. :)

Идея в том, чтобы найти какие ошибки вообще совершались в некоторых проектах, чтобы диагностировать их в будущем, в том числе в других проектах. Плюс если у нас есть reproducer (пример кода с дефектом), проще делать тесты для линтера.

Если ошибку поправили, это не значит, что новый программист не может внести её ещё раз.

Надеюсь, так понятнее.
Только сейчас нашёл статью и новость об OpenApoc.
Активность в репозитории до сих пор вроде бы есть, звёздочку поставил. :)

Apocalypse на момент выхода действительно ощущалась слишком сырой.
Возможно как-нибудь гляну что успели доработать в проекте OpenApoc.
В компиляторе gc (который знаком большинству людей) нет векторизации циклов. Вообще. By design. Никто пока не предложил как её внедрить, чтобы не нарушить одно из:
1. Не замедляет время компиляции.
2. Реализация не слишком сложная (maintainability).
3. Имеет примеры важного кода, который будет сильно ускоряться, кроме синтетики.

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

Математический код и HPC может и получит буст от векторизации, но для gc по-моему это не самые частые пользователи. Возможно в будущем что-то изменится, но пока ситуация такая. Где-то ещё были разные связанные с этим proposal'ы, в том числе о введении примитивов для использования FMA инструкций, но под рукой списка нет, можете поискать на github трекере, если интересно.

Возможно LLVM-based компилятор будет лучше, но по-моему там пока ещё не достаточно всё зрелое.
Как Вы узнали, какой код генерирует компилятор? Где это посмотреть?

Способов несколько.
Самый простой — это передача компилятору ключа -gcflags=-S.


Для примера, соберите вот этот файл (назовём его foo.go):


package foo

func f1(x, y string) string { return x + y }
func f2(x, y, z string) string { return x + y + z }

С помощью команды go tool compile -S foo.go.
Помимо прочего, вы увидите вызовы runtime.concatstring2(SB) в f1 и runtime.concatstring3(SB) в f2.

Логично, что более выразительные (или сложные) языки анализировать не так просто.

Одна из причин, почему мне Go нравится и почему я пробую работать над статическим анализом для него — мне это по силам.
Я так, понимаю, линтер собранный lintpack-ом берет задачу парсинга и прохода по коду на себя, а линтеры ответственны только за саму проверку и ничего больше. Это сильно уменьшает затраты на написание (и включение в pipeline) нового линтера.

Да, всё так.
Плюс из коробки всё для тестирования (работает с coverage) и интеграционного тестирования.

На Go вообще что-нибудь пишут кроме линтеров и металинтеров?

Конечно, пишут. Библиотеки для написания линтеров и металинтеров, например:
https://go-toolsmith.github.io/

Так, это, надо вырабатывать best practices, составлять code conventions

go-namecheck верифицирует (некоторые) конвенции, чтобы не проверять глазками.


и обязывать разрабов следовать им.

Я бы предпочёл, чтобы как можно больше работы по незначительным вещам выполнялось автоматически или, хотя бы, детектировались программами.


Ну тогда введите ещё code review.

Code review это не отменяет. Тут как с линтерами. Они ревью не заменяют, но могут сильно помогать.

Information

Rating
Does not participate
Location
Грузия
Registered
Activity