Новорічні обіцянки Go-розробника

image
Це переклад посади одного з головних розробників мови Go, Расса Коксу, де він в традиційному для новорічного часу форматі дає собі обіцянки і планує виконати їх.
Настав час прийняття рішень, і я подумав, що має сенс трохи розповісти про те, над чим я хочу працювати у новому році стосовно Go.
Кожен рік я ставлю перед собою мету — допомогти Go-розробникам. Я хочу бути впевнений, що те, що роблять творці Go, робить позитивний вплив на всіх Go-розробників. Тому що у них є маса способів зробити помилку: наприклад, можна витратити занадто багато часу на очищення або оптимізацію коду, яким це не потрібно; відгукуватися тільки на найпоширеніші або недавні скарги і запити; надто зосереджуватися на короткострокових поліпшення. Тому так важливо поглянути на все зі сторони і зайнятися тим, що принесе найбільше користі для Go-спільноти.
У цій статті я опишу кілька основних завдань, на яких я зупинюся в цьому році. Це мій власний список, а не всієї команди творців Go.
По-перше, я хочу отримати зворотній зв'язок з усього того, що тут написано. По-друге, хочу показати, що дійсно вважаю важливими наведені нижче проблеми. Я думаю, що люди занадто часто сприймають недолік активності з боку команди Go як сигнал, що все і так чудово, хоча насправді ми просто вирішуємо інші, більш пріоритетні завдання.
Аліаси типів
У нас є постійно спливаюча проблема з переміщенням типів з одного пакета на інший при масштабних рефакторингах кодової бази. Ми намагалися вирішити її в минулому році з допомогою загальних аліасів, але це не спрацювало: ми дуже погано пояснили суть зроблених змін, та й самі зміни запізнилися, код не був готовий до виходу Go 1.8. Витягуючи уроки з цього досвіду, я виступив і написав статтю про що лежить в основі проблеми, і це породило продуктивну дискусію у баг-трекері Go, присвячену можливим рішенням. Схоже, що впровадження більш обмежених аліасів типів буде правильним наступним кроком. Сподіваюся, що вони з'являться в Go 1.9. #18130
Управління пакетами
У лютому 2010 я розробив для Go підтримку скачування опублікованих пакетів (goinstall, стала go get). З тих пір багато чого сталося. Зокрема, екосистеми інших мов серйозно підняли планку очікувань для систем управління пакетами, а OpenSource-спільнота здебільшого відповідно на семантичне версіонування, дає базове розуміння про сумісність різних версій. Тут Go потребу в поліпшеннях, і група людей вже працює над вирішенням. Я хочу впевнитися, що ці ідеї будуть грамотно інтегровані в стандартний інструментарій Go. Хочу, щоб управління пакетами стало ще одним достоїнством Go.
Поліпшення в системі складання
У складання командою
go
є ряд недоліків, які пора виправити. Нижче представлені три характерних прикладу, яким я маю намір присвятити свою роботу.
Збірки можуть бути дуже повільними, тому що утиліта
go
недостатньо активно кешує результати складання. Багато хто не розуміють, що
go install
зберігає результати своєї роботи, а
go build
— ні, тому раз за разом запускають
go build
і збірка проходить очікувано повільно. Те ж саме відноситься до повторюваних
go test
go test –i
при наявності модифікованих залежностей. По можливості всі типи збірок повинні бути інкрементальнимі. #4719
Результати тестів теж потрібно кешувати: якщо вхідні дані не змінювались, то зазвичай немає потреби перезавантажувати сам тест. Це значно здешевить запуск «всіх тестів» за умови незначних змін або їх відсутності. #11193
Робота поза GOPATH повинна підтримуватися майже так само, як і робота всередині неї. Зокрема, повинна бути можливість запустити
git clone
, увійти в директорію з допомогою
cd
, а потім запустити команду
go
, і щоб все це чудово працювало. Управління пакетами лише підвищує важливість цієї задачі: ви повинні мати можливість працювати з різними версіями пакета (скажімо, v1 і v2) без необхідності підтримувати для них окремі GOPATH. #17271
Основний набір коду
Мені здається, що мого виступу і статті про рефакторинге кодової бази пішли на користь конкретні приклади реальних проектів. Також ми прийшли до висновку, що додавання в vet повинні вирішувати проблеми, характерні для реальних програм. Мені б хотілося, щоб подібний аналіз реальної практики перетворився у стандартний спосіб обговорення і оцінки змін в Go.
Зараз не існує загальноприйнятого характерного коду, щоб проводити подібний аналіз: кожен повинен спочатку створити свій проект, а це дуже великий обсяг роботи. Я хотів би зібрати єдиний, автономний Git-сховище, що містить наш офіційний базовий набір коду для проведення аналізу, з яким може звірятися співтовариство. Можлива відправна точка — топ-100 Go-репозиторіїв на GitHub за кількістю зірок, форков або обох параметрів.
Автоматичний vet
Go-дистрибутив поставляється з потужним інструментом —
go vet
, який вказує на типові помилки. Планка для цих помилок висока, так що до його повідомлень потрібно прислухатися. Але головне — не забувати запустити vet. Хоча було б зручніше про це не пам'ятати. Я думаю, що ми могли б запускати vet паралельно з фінальним компилированием і линковкой бінарника, які відбуваються при виконанні
go test
без будь-якого уповільнення. Якщо у нас це вийде і якщо ми обмежимо дозволені vet-перевірки вибіркою, яка на 100% точна, то ми взагалі зможемо перетворити передачу vet в попередню умову для запуску тесту. Тоді розробникам не потрібно пам'ятати про те, що потрібно запустити
go vet
. Вони запустять
go test
, і vet зрідка буде повідомляти щось важливе, уникаючи зайвої налагодження. #18084 #18085
Помилки і загальноприйняті методики
Частиною загальноприйнятої практики за повідомленнями про помилки в 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 для зберігання інформації, яка пов'язана із запитом (наприклад, про таймаутах, про те, скасовано запит і про авторизаційних даних). Індивідуальний контекст неизменяем (рядки або цілочисельні значення): можна отримати тільки новий, оновлений контекст і передати його явним чином вниз по стеку викликів, або (що рідше зустрічається) назад наверх. Контекст сьогодні передається через API (наприклад, database/sql і net/http), в основному для того, щоб вони могли зупинити обробку запиту, коли викликає більше не потрібен результат обробки. Інформація про таймаутах цілком підходить для передачі в контексті, але абсолютно не підходить для опцій бази даних, наприклад, тому що вони навряд чи будуть так само добре застосовуватися до всіх можливих БД-операціях в ході виконання запиту. А що щодо джерела часу або логер? Чи можна зберігати їх у контексті? Я спробую зрозуміти і описати критерії того, що можна використовувати в контексті, а що не можна.
Модель пам'яті
На відміну від інших мов, модель пам'яті Go навмисно зроблена скромною, не дає користувачам багато обіцянок. На самому справі в документі сказано, що особливо-то і читати його не варто. У той же час вона вимагає від компілятора більше, ніж в інших мовах: зокрема, гонка на цілочисельних значень не є виправданням для довільного поведінки вашої програми. Є й повні прогалини: наприклад, не згадується пакет sync/atomic. Думаю, розробники основного компілятора і runtime-системи погодяться зі мною в тому, що ці атомики повинні вести себе так само, як seqcst-атомики в С++ або volatile в Java. Але при цьому ми повинні акуратно вписати це в модель пам'яті і довгий-довжелезний пост в блозі. #5045 #7948 #9442
Незмінюваність
Race detector — одна з найулюбленіших можливостей Go. Але не мати race взагалі було б ще краще. Мені б дуже хотілося, щоб існував розумний спосіб інтегрувати в Go незмінюваність посилань, щоб програмісти могли робити ясні, перевірені судження про те, що може і що не може бути написано, тим самим запобігаючи певні race condition на етапі компіляції. В Go вже є один незмінний тип—
string
; було б добре заднім числом визначати, що
string
— це іменований тип (або псевдонім) для незмінного
[]byte
. Не думаю, що це вдасться реалізувати в цьому році, але я хочу розібратися в можливих рішеннях. Javari, Midori, Pony і Rust вже позначили цікаві варіанти, до того ж є ще цілий ряд досліджень на цю тему.
В довгостроковій перспективі, якщо нам вдасться статично виключити можливість race condition, це дозволить відмовитися від більшої частини моделі пам'яті. Можливо, це нездійсненна мрія, але, повторюся, я хотів би краще зрозуміти потенційні рішення.
Дженерики
Самі палкі суперечки між 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 або LINQ, щоб помилки невідповідності типів ловилися при компіляції, а не при виконанні. Можна написати ще купу структур даних або алгоритмів з використанням дженериків, але я вважаю, що такі високорівневі інструменти більш цікаві.
На впродовж кількох років ми старалися знайти правильний спосіб впровадження дженериків в Go. Останні кілька пропозицій стосувалися розробки рішення, яке забезпечувало б загальний параметричний поліморфізм (як
chan T
) та уніфікацію
string
та
[]byte
. Якщо останнє вирішується параметризацией на основі незмінюваності, як описано у попередній частині, тоді, можливо, це спростить вимоги до архітектури дженериків.
Коли в 2008 я вперше задумався про дженериках в Go, головними прикладами були C#, Java, Haskell і ML. Але жоден з підходів, реалізованих у цих мовах, не виглядав ідеально відповідним для Go. Сьогодні є більш свіжі рішення, наприклад, у Dart, Midori, Rust і Swift.
Минуло кілька років з тих пір, як ми наважилися взятися за цю тему і розглянути можливі варіанти. Мабуть, настав час знову озирнутися по сторонах, особливо в світлі інформації про змінюваність/незмінюваності і рішеннях з нових мов. Не думаю, що дженерики з'являться в Go в цьому році, але я хотів би розібратися в цьому питанні.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.