- PVSM.RU - https://www.pvsm.ru -
Это перевод поста одного из главных разработчиков языка Go, Расса Кокса, где он в традиционном для новогоднего времени формате дает себе обещания и планирует выполнить их.
Наступило время принятия решений, и я подумал, что имеет смысл немного рассказать о том, над чем я хочу работать в наступившем году применительно к Go.
Каждый год я ставлю перед собой цель — помочь Go-разработчикам. Я хочу быть уверен, что то, что делают создатели Go, оказывает положительное влияние на всех Go-разработчиков. Потому что у них есть масса способов совершить ошибку: например, можно потратить слишком много времени на очистку или оптимизацию кода, которому это не требуется; откликаться только на самые распространенные или недавние жалобы и запросы; излишне сосредотачиваться на краткосрочных улучшениях. Поэтому так важно взглянуть на все со стороны и заняться тем, что принесет больше всего пользы для Go-сообщества.
В этой статье я опишу несколько основных задач, на которых я сосредоточусь в этом году. Это мой собственный список, а не всей команды создателей Go.
Во-первых, я хочу получить обратную связь по всему тому, что здесь написано. Во-вторых, хочу показать, что действительно считаю важными описанные ниже проблемы. Я думаю, что люди слишком часто воспринимают недостаток активности со стороны команды Go как сигнал, что все и так прекрасно, хотя на самом деле мы просто решаем другие, более приоритетные задачи.
У нас есть постоянно всплывающая проблема с перемещением типов из одного пакета в другой при масштабных рефакторингах кодовой базы. Мы пытались решить ее в прошлом году с помощью общих алиасов [1], но это не сработало: мы слишком плохо объяснили суть сделанных изменений, да и сами изменения запоздали, код не был готов к выходу Go 1.8. Извлекая уроки из этого опыта, я выступил [2] и написал статью [3] о лежащей в основе проблеме, и это породило продуктивную дискуссию [4] в баг-трекере Go, посвященную возможным решениям. Похоже, что внедрение более ограниченных алиасов типов [5] будет правильным следующим шагом. Надеюсь, что они появятся в Go 1.9. #18130 [4]
В феврале 2010 я разработал для Go поддержку скачивания опубликованных пакетов (goinstall, ставшую go get). С тех пор многое произошло. В частности, экосистемы других языков серьезно подняли планку ожиданий для систем управления пакетами, а OpenSource-сообщество по большей части согласно на семантическое версионирование [6], дающее базовое понимание о совместимости разных версий. Здесь Go нуждается в улучшениях, и группа людей уже работает над решением [7]. Я хочу удостовериться, что эти идеи будут грамотно интегрированы в стандартный инструментарий Go. Хочу, чтобы управление пакетами стало еще одним достоинством Go.
У сборки командой go
есть ряд недостатков, которые пора исправить. Ниже представлены три характерных примера, которым я намерен посвятить свою работу.
Сборки могут быть очень медленными, потому что утилита go
недостаточно активно кеширует результаты сборки. Многие не понимают, что go install
сохраняет результаты своей работы, а go build
— нет, поэтому раз за разом запускают go build
и сборка проходит ожидаемо медленно. То же самое относится к повторяющимся go test
без go test –i
при наличии модифицированных зависимостей. По мере возможности все типы сборок должны быть инкрементальными. #4719 [8]
Результаты тестов тоже нужно кешировать: если входные данные не менялись, то обычно нет нужды перезапускать сам тест. Это сильно удешевит запуск «всех тестов» при условии незначительных изменений или их отсутствии. #11193 [9]
Работа вне GOPATH должна поддерживаться почти так же, как и работа внутри нее. В частности, должна быть возможность запустить git clone
, войти в директорию с помощью cd
, а затем запустить команду go
, и чтобы все это прекрасно работало. Управление пакетами лишь повышает важность этой задачи: вы должны иметь возможность работать с разными версиями пакета (скажем, v1 и v2) без необходимости поддерживать для них отдельные GOPATH. #17271 [10]
Мне кажется, что моему выступлению и статье о рефакторинге кодовой базы пошли на пользу конкретные примеры из реальных проектов. Также мы пришли к выводу, что добавления в vet [11] должны решать проблемы, характерные для реальных программ. Мне бы хотелось, чтобы подобный анализ реальной практики превратился в стандартный способ обсуждения и оценки изменений в Go.
Сейчас не существует общепринятого характерного набора кода, чтобы проводить подобный анализ: каждый должен сначала создать свой проект, а это слишком большой объем работы. Я хотел бы собрать единый, автономный Git-репозиторий, содержащий наш официальный базовый набор кода для проведения анализа, с которым может сверяться сообщество. Возможная отправная точка — топ-100 Go-репозиториев на GitHub по количеству звезд, форков или обоих параметров.
Go-дистрибутив поставляется с мощным инструментом — go vet
[12], который указывает на распространенные ошибки. Планка для этих ошибок высокая, так что к его сообщениям нужно прислушиваться. Но главное — не забывать запустить vet. Хотя было бы удобнее об этом не помнить. Я думаю, что мы могли бы запускать vet параллельно с финальным компилированием и линковкой бинарника, которые происходят при выполнении go test
без какого-либо замедления. Если у нас это получится и если мы ограничим разрешенные vet-проверки выборкой, которая на 100% точна, то мы вообще сможем превратить передачу vet в предварительное условие для запуска теста. Тогда разработчикам не понадобится помнить о том, что нужно запустить go vet
. Они запустят go test
, и vet изредка будет сообщать что-то важное, избегая лишней отладки. #18084 [13] #18085 [14]
Частью общепринятой практики по сообщениям об ошибках в Go является то, что функции включают в себя релевантный доступный контекст, в том числе и информацию о том, попытка какой операции была осуществлена (имя функции и ее аргументы). Например, эта программа
err := os.Remove("/tmp/nonexist")
fmt.Println(err)
выводит
remove /tmp/nonexist: no such file or directory
Далеко не весь Go-код поступает так же, как это делает os.Remove
. Очень много кода делает просто
if err != nil {
return err
}
по всему стеку вызовов и выкидывает полезный контекст, который стоило бы показывать (например, как remove /tmp/nonexist:
выше). Я хотел бы понять, не ошибаемся ли мы в наших ожиданиях по включению контекста, и можем ли сделать что-нибудь, что облегчит написание кода, возвращающего более информативные ошибки.
Также в сообществе идут различные дискуссии об интерфейсах для очистки ошибок от контекста. И я хочу понять, когда это оправданно, и должны ли мы выработать какую-то официальную рекомендацию.
В Go 1.7 мы добавили новый пакет context [15] для хранения информации, которая как-либо связана с запросом (например, о таймаутах, о том, отменен ли запрос и об авторизационных данных [16]). Индивидуальный контекст неизменяем (как строки или целочисленные значения): можно получить только новый, обновленный контекст и передать его явным образом вниз по стеку вызовов, либо (что реже встречается) обратно наверх. Контекст сегодня передается через API (например, database/sql [17] и net/http [18]) в основном для того, чтобы они могли остановить обработку запроса, когда вызывающему больше не нужен результат обработки. Информация о таймаутах вполне подходит для передачи в контексте, но совершенно не подходит для опций базы данных, например, потому что они вряд ли будут так же хорошо применяться ко всем возможным БД-операциям в ходе выполнения запроса. А что насчет источника времени или логгера? Можно ли хранить их в контексте? Я попытаюсь понять и описать критерии того, что можно использовать в контексте, а что нельзя.
В отличие от других языков, модель памяти [19] в Go намеренно сделана скромной, не дающей пользователям много обещаний. На самом в деле в документе сказано, что особо-то и читать его не стоит. В то же время она требует от компилятора больше, чем в других языках: в частности, гонка на целочисленных значениях не является оправданием для произвольного поведения вашей программы. Есть и полные пробелы: например, не упоминается пакет sync/atomic [20]. Думаю, разработчики основного компилятора и runtime-системы согласятся со мной в том, что эти атомики должны вести себя так же, как seqcst-атомики в С++ или volatile в Java. Но при этом мы должны аккуратно вписать это в модель памяти и в длинный-предлинный пост в блоге. #5045 [21] #7948 [22] #9442 [23]
Race detector [24] — одна из самых любимых возможностей Go. Но не иметь race вообще было бы еще лучше. Мне бы очень хотелось, чтобы существовал разумный способ интегрировать в Go неизменяемость ссылок [25], чтобы программисты могли делать ясные, проверенные суждения о том, что может и что не может быть написано, тем самым предотвращая определенные race condition на этапе компиляции. В Go уже есть один неизменяемый тип— string
; было бы хорошо задним числом определять, что string
— это именованный тип (или алиас) для неизменяемого []byte
. Не думаю, что это удастся реализовать в этом году, но я хочу разобраться в возможных решениях. Javari, Midori, Pony и Rust уже обозначили интересные варианты, к тому же есть еще целый ряд исследований на эту тему.
В долгосрочной перспективе, если нам удастся статически исключить возможность race condition, это позволит отказаться от большей части модели памяти. Возможно, это несбыточная мечта, но, повторюсь, я хотел бы лучше понять потенциальные решения.
Самые жаркие споры [26] между Go-разработчиками и программистами на других языках разгораются по поводу того, следует ли Go поддерживать дженерики (или как давно это должно было произойти). Насколько я знаю, команда создателей Go никогда не говорила, что в Go не нужны дженерики. Мы говорили, что существуют более важные задачи, требующие решения. Например, я считаю, что улучшение поддержки управления пакетами окажет куда более сильное положительное влияние на большинство Go-разработчиков, чем внедрение дженериков. Но в то же время мы осознаём, что в ряде случаев нехватка параметрического полиморфизма является серьезным препятствием.
Лично я хотел бы иметь возможность писать общие функции для работы с каналами, например:
// Join делает все полученные во входных каналах сообщения
// доступными для получения из возвращаемого канала.
func Join(inputs ...<-chan T) <-chan T
// Dup дублирует сообщени из c и в c1 и в c2
func Dup(c <-chan T) (c1, c2 <-chan T)
Также я хотел бы иметь возможность написать на Go более высокоуровневые абстракции обработки данных, аналогичные FlumeJava [27] или LINQ [28], чтобы ошибки несоответствия типов ловились при компиляции, а не при выполнении. Можно написать еще кучу структур данных или алгоритмов с использованием дженериков, но я считаю, что такие высокоуровневые инструменты более интересны.
На протяжении нескольких лет [29] мы старались [30] найти правильный способ внедрения дженериков в Go. Последние несколько предложений касались разработки решения, которое обеспечивало бы общий параметрический полиморфизм (как chan T
) и унификацию string
и []byte
. Если последнее решается параметризацией на основе неизменяемости, как описано в предыдущей части, тогда, возможно, это упростит требования к архитектуре дженериков.
Когда в 2008 я впервые задумался о дженериках в Go, главными примерами были C#, Java, Haskell и ML. Но ни один из подходов, реализованных в этих языках, не выглядел идеально подходящим для Go. Сегодня есть более свежие решения, например, в Dart, Midori, Rust и Swift.
Прошло несколько лет с тех пор, как мы отважились взяться за эту тему и рассмотреть возможные варианты. Вероятно, пришло время снова оглядеться по сторонам, особенно в свете информации об изменяемости/неизменяемости и решениях из новых языков. Не думаю, что дженерики появятся в Go в этом году, но я хотел бы разобраться в этом вопросе.
Автор: Badoo
Источник [31]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/238150
Ссылки в тексте:
[1] общих алиасов: https://golang.org/issue/16339
[2] выступил: https://www.youtube.com/watch?v=h6Cw9iCDVcU
[3] написал статью: https://talks.golang.org/2016/refactor.article
[4] продуктивную дискуссию: https://golang.org/issue/18130
[5] алиасов типов: https://golang.org/design/18130-type-alias
[6] семантическое версионирование: http://semver.org/
[7] работает над решением: https://blog.gopheracademy.com/advent-2016/saga-go-dependency-management/
[8] #4719: https://golang.org/issue/4719
[9] #11193: https://golang.org/issue/11193
[10] #17271: https://golang.org/issue/17271
[11] добавления в vet: https://golang.org/src/cmd/vet/README
[12] go vet
: https://golang.org/cmd/vet/
[13] #18084: https://golang.org/issue/18084
[14] #18085: https://golang.org/issue/18085
[15] пакет context: https://golang.org/pkg/context/
[16] таймаутах, о том, отменен ли запрос и об авторизационных данных: https://blog.golang.org/context
[17] database/sql: https://golang.org/pkg/database/sql
[18] net/http: https://golang.org/pkg/net/http
[19] модель памяти: https://golang.org/ref/mem
[20] пакет sync/atomic: https://golang.org/pkg/sync/atomic/
[21] #5045: https://golang.org/issue/5045
[22] #7948: https://golang.org/issue/7948
[23] #9442: https://golang.org/issue/9442
[24] Race detector: https://golang.org/doc/articles/race_detector.html
[25] неизменяемость ссылок: https://www.google.com/search?q=%22reference%20immutability%22
[26] жаркие споры: https://research.swtch.com/dogma
[27] FlumeJava: https://research.google.com/pubs/archive/35650.pdf
[28] LINQ: https://en.wikipedia.org/wiki/Language_Integrated_Query
[29] протяжении нескольких лет: https://golang.org/design/15292-generics
[30] старались: https://research.swtch.com/generic
[31] Источник: https://habrahabr.ru/post/320724/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.