пятница, 20 ноября 2009 г.

[comp.prog.thoughts] Так вот о языке Go

Хотя я и не написал на языке Go ни одной программы (пока?), но благодаря штудированию Effective Go, мнение о языке у меня сложилось. Однако, и я попробую показать это далее, мнение об именно Go не так интересно, как сам факт его появления. Но обо всем по порядку.

Язык Go производит впечатление добротно сделанного, практичного и минималистичного языка, уровнем повыше C – за счет сборки мусора, встроенных в язык хэш-таблиц и каналов, поддержки лямбда функций и goroutines, а так же за счет интерфейсов.

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

func demo( m map[string]int, ch chan int, err os.Error ) {
  v, present := m[ 'key' ];
  i, isRead := <-ch;
  e, isCastSuccessful := err.(*os.PathError);
  ...
}

На мой взгляд – это один и тот же паттерн. Что хорошо, т.к. разработчику приходится запоминать меньше фокусов.

Язык оставляет впечатление, что очень умные и опытные люди пробуют создать удобный для себя инструмент. Заточенный под конкретную нишу – разработку околосистемных вещей, которые сейчас пишутся на C. Я думаю, что Subversion или Apache от переписывания на Go только выиграл бы.

Однако лично меня язык Go оставил равнодушным, не захотелось мне на нем программировать. Наверное, из-за отсутствия в нем нормального ООП, шаблонов, исключений, константности для объектов. Если не сравнивать Go с C++ (поскольку в C++ нет сборки мусора), то язык D выглядит предпочтительнее, чем Go. Ведь за счет средств D можно повторить все, что есть в Go (включая goroutines и каналы).

Т.е. моя главная претензия к Go – это его ограниченные возможности по сравнению с другими современными языками. Пока ты пишешь какую-нибудь несложную утилиту командной строки или жестко ориентированный под конкретную задачу сервер, языка Go может и хватит. Но стоит ступить чуть в сторону – и что? Например, можно ли на Go эффективно написать аналог Boost.MultiIndex? Я сомневаюсь.

Что в истории с Go интересно, так это шум вокруг его появления. Имя Google – это сильно, это внушаить! ;)

Такое впечатление, что все, что выбрасываетсявыбирается из Google на свет божий, просто обречено на известность. Анонсировал Google свой Protocol Buffers – все “Вау!” Опубликовали альфа-версию Go – по всему миру “Go! Go! Go!” Ну а что этот Go? Пока ничего. Ну да ладно, жизнь не совершенна, и не стоит искать справедливости в информационных технологиях ;)

Но самое интригующее – это практически одновременный выход на свет двух очень похожих, на мой взгляд, языков – Go и Zimbu. Вот это уже тенденция. Новые минималистичные нативные языки, но со сборкой мусора. Нацеленные на замену в околосистемных нишах как C (за счет высокоуровневости), так и C++ (за счет простоты, сборки мусора и безопасности). Что позволяет мне говорить о двух вещах.

Во-первых, явно демонстрируется, что managed-платформы не подходят для целого ряда задач. Начиная от написания мелких системных утилит и текстовых редакторов вроде ViM, заканчивая высоконагруженными серверами. Безопасность, кроссплатформенность, большие библиотеки, Ынтырпрайзность и поддержка крупными корпорациями – все это хорошо. Но нафиг не упало, когда нужно написать что-то типа svn.exe.

Во-вторых, четко проявляется картина, в которой язык C не удовлетворяет современных разработчиков своей низкоуровневостью, а C++ – своей монстрообразностью и переусложненностью. Действительно, нерадостно в XXI-м веке вручную распределять память и искать библиотеки, реализующие хэш-таблицы. Равно как и нерадостно годами изучать C++ для того, чтобы быть уверенным в безопасности своего кода.

И, если поиграть в пророка, то я бы сказал, что разработчики (некоторая их часть) сейчас заинтересованы в появлении современного нативного языка. Более мощного, чем C, и менее сложного, чем C++. Более безопасного, чем каждый из них.

На его роль могли бы претендовать Eiffel, OCaml или D. Но, поезда Eiffel и OCaml уже ушли (при всем уважении к ним и к их пользователям). D так и не отправился в путь, хотя уже догнал по сложности C++ и хочет обогнать. Должен быть какой-то новый игрок. Именно новый, за которым еще не тянется след обманутых ожиданий :) Может быть это будет Zimbu. Может быть Go. Может быть еще кто-то.

Но я сомневаюсь. Поскольку практически все современные мейнстримовые языки (C++, Java, C#) начинались более простыми, чем есть сейчас. И эта сложность не случайна. Она возникла как следствие попыток решения реальных задач. Ведь любой успешный инструмент начинает применяться совсем не так, как это предполагал его автор. Как следствие, инструмент обрастает фишечками и рюшечками, без которых никак. Но которые превращают язык в неповоротливого монстра типа C++.

Это неизбежный процесс. Очень хорошо он виден именно на примере языка D. Он в начале 2000-х был почти как Zimbu сейчас. Вальтер Брайт был даже против поддержки шаблонов в языке. Сначала. Но потом появились и шаблоны, и замыкания, и константность, а на горизонте маячат еще и макросы. А ведь на D еще ничего толкового и не написали! Так что уже говорить о C++, Java или C#, на которых народ клепает чуть ли не миллионы строк в сутки, в самых разных прикладных областях.

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

Такие вот пироги. И кажется мне, что если в нишу C/C++ и ворвется какой-то молодой C++киллер, то он изначально должен быть не слабее C++ по своим выразительным возможностям, а напротив – гораздо, многократно мощнее. Но при этом он должен быть простым в изучении, а так же обеспечивать отличную интеграцию с уже написанным C/C++ кодом. Такой, что осваивается за один вечер, а потом рвет C++ как тузик грелку! :)

Появится ли что-нибудь такое? Будем посмотреть. Но вряд ли это будет Zimbu или Go.

17 комментариев:

Quaker комментирует...

И эта сложность не случайна. Она возникла как следствие попыток решения реальных задач. Ведь любой успешный инструмент начинает применяться совсем не так, как это предполагал его автор. Как следствие, инструмент обрастает фишечками и рюшечками, без которых никак. Но которые превращают язык в неповоротливого монстра типа C++. Это неизбежный процесс.
Сложность - это именно тот недостаток, с которым абсолютное большинство разработчиков языков никак не борятся. Вместо этого приводятся не слишком убедительные аргументы в защиту той или иной конструкции, а затем пишутся оправдательные книги. Читая Страуструпа, я никак не могу отделаться от мысли, что в C++ много всего накручено бездумно (чтобы было), или ради спорного сохранения совместимости со старьем. А "Дизайн и эволюция C++" - вершина маразма оправдательного жанра.
Что же касается борьбы со сложностью, то позитивные примеры все же есть. В языке Оберон-2 Вирт решил выбросить все лишнее и ненужное, оставив предельно простые и понятные конструкции. Но это скорее исключение, чем правило.
Однако при правильном подходе, как я думаю, можно и нужно контролировать сложность. Например, Eiffel не стал монстроподобным языком (хотя и немного изменился относительно первоначального варианта). Главное - не идти на поводу у велосипедостроителей и свистопердельщиков, вносить действительно важные изменения (подкрепляя их убедительными доводами на основе анализа научных публикаций и исследования альтернативных реализаций) и ориентироваться на сравнительно небольшое количество конструкций языка.

Quaker комментирует...

разработчики (некоторая их часть) сейчас заинтересованы в появлении современного нативного языка. Более мощного, чем C, и менее сложного, чем C++. Более безопасного, чем каждый из них. На его роль могли бы претендовать Eiffel, OCaml или D.
Что касается D, то он все больше и больше напоминает мне C++. Звездный час функциональных ЯП как языков общего назначения не настал и вряд ли когда-нибудь настанет. Некоторые их идеи просто перекочуют в распространенные языки. В крайнем случае, их использование будет ограничиваться написанием отдельных небольших модулей для решения узкоспециализированных задач. Что касается Eiffel, то его основная идея проектирования по контракту (Design by contract) наконец-то находит реальное массовое воплощение -
http://research.microsoft.com/en-us/projects/contracts/
http://msdn.microsoft.com/en-us/devlabs/dd491992.aspx
http://research.microsoft.com/en-us/projects/contracts/userdoc.pdf

eao197 комментирует...

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

За ссылочки про Code Contracts спасибо. Это интересно. Интересно будет проследить, получат ли они использование в реальной жизни.

Quaker комментирует...

Интересно будет проследить, получат ли они использование в реальной жизни.
Думаю, что определенное использование будет. Однако оно не будет повсеместным, как в Eiffel. Огромное количество книг и существующего ПО написано без учета этих принципов. Для всеобщего использования нужно прививать культуру программирования с использованием инвариантов, пост- и предусловий. А это длительный процесс.

eao197 комментирует...

Я еще встречался с мнением (причем высказываемым хорошими программистами), что DbC сама по себе спорная штука. И что они экспериментировали с DbC, но решили от его использования отказаться.

Вполне возможно, что это как раз одна из причин, по которым Eiffel в массы так и не вышел.

Quaker комментирует...

Тогда вопрос: каков на Ваш взгляд наилучший способ (комбинация способов) для верификации и тестирования программ?

eao197 комментирует...

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

eao197 комментирует...

Так вот про тестирование. Имхо, нельзя отделять тестирование от разработки. Т.е. вопрос нужно ставить так: "Какой наилучший способ разработки, чтобы обеспечить приемлимое качество?"

1. Итерационность. На всех стадиях, начиная от проектирования.

2. Движение вперед маленькими шагами. С разнообразными проверками того, что возможно, на каждом шаге.

3. Проектирование кода так, чтобы его можно было использовать только одним, правильным, образом.

4. Код должен быть простым. Функции маленькими. Объемный простой код (разбитый на маленькие функции) лучше компактного сложного кода.

5. При написании кода нужно выделять граничные условия, при которых написанный код нужно протестировать (например, поведение функции reverse на пустой последовательности).

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

7. То, что покрывается автоматическими unit- или функциональными тестами, очень желательно покрыть такими тестами.

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

9. Как можно раньше нужно начинать эксплуатацию кода в условиях, максимально приближенных к "боевым" (пусть и на тестовом стенде).

10. Максимальное внимание к любым мелочам, которые не согласуются с ожидаемым поведением программы. Например, при тестовом прогоне может оказаться нарушенным форматирование -- нужно либо сразу проверить, почему такое происходит, либо зафиксировать сей факт, чтобы разобраться потом. Иногда лишняя или неправильная отладочная печать, на которую не обращают внимания, является свидетельством грубой ошибки.

11. Обязательный вопрос самому себе "А нет ли где-нибудь еще такой фигни" при обнаружении ошибки (особенно ошибки в логике поведения программы).

12. Тщательный подбор входящих данных для тестирования кода. Иногда эти данные должны быть подобраны еще до разработки или на этапе проектирования. Они обязательно должны пополняться во время разработки (т.к. тогда становятся видны "граничные" условия, вызванные особенностями реализации). Если требуется анализ результатов работы программы, то его лучше проводить автоматически.

13. Очень желательна процедура code review. Хотя бы самим собой спустя некоторое время после написания кода.

Left комментирует...

Уже не впервые вижу отзыв о Go "он такой красивый пока молодой, а вот как обрастёт фичерами - станет страшный как С++". И понимаю откуда растут ноги у такого мнения - из истории C++/Java/C# - все эти языки росли по принципу "а фигли бы не добавить фичу XXX", что привело к монструозности.
Но давайте посмотрим на более другие языки. К примеру, на столь обожаемый мной JavaScript - несмотря на вполне почтенный возраст язык практически не изменился. Не силён в лиспе и схеме, но сдаётся мне там похожая ситуация. ИМХО, удачно спроектированный (с минимум тщательно отобранных концепций в дизайне) язык не будет расти так же без(д)умно как вышеупомянутые монстрики.
По крайней мере, очень хочется на это надеяться

eao197 комментирует...

2Left: Ну возьмем C++ -- он применяется в ядерах ОС (хоть и редко), и игровых движках, и в GUI библиотеках, и в обработке больших объемах данных, и в больших математических расчетах и... Аналогично Java, аналогично C#. Могут ли такой широтой применения похвастаться JavaScript или Scheme?

В том-то и дело, что как только универсальный язык начинает нравится, его начинают пихать туда, куда раньше даже не думали. А для этого язык приходится развивать (т.е. усложнять). Это происходило и с Perl-ом, и с Python-ом, и с Ruby. А уж во что со временем превратились Fortran и Cobol -- это вообще страшно сказать.

Ситуация с Lisp-ом, имхо, не простая. Поскольку лиспов было много, разных и не шибко совместимых между собой. Стандартизированы, кажется, только CLOS (а это серьезное усложнение исходного Lisp-а) и Scheme. Scheme, насколько я знаю, усложняется со временем. Но там еще та специфика, что Lisp-вые языки могут расширяться только за счет библиотек.

Так что, думаю, мнение об усложнении Go (со временем) имеет под собой серьезные основания. Другое дело -- сбудется ли это все? ;)

Left комментирует...

Не вижу связи между тем где применяется язык и его усложнением. К примеру - какие фичи были внесены в С++ для его использования в ядрах ос или в UI? А та куча фич которая была внесена в C# - для чего добавлялась? Ибо ниша C# ИМХО уж никак не поменялась с момента выхода первой версии и аж до четвёртой.
ИМХО фичи пришиваются не для того чтобы импользовать язык в другой нише - а скорее просто для привлечения новых пользователей языка, когда людям предлагают уже знакомые им по другим языкам вещи.

Left комментирует...

Ну и ещё фичи - это как правило изначальные недоделки языка :)

eao197 комментирует...

В C++ 2-й версии были внесены следующие основные изменения:
- множественное наследование;
- исключения;
- шаблоны.

Наследование очень широко используется в UI. Шаблоны широко используются в вычислениях. Исключения очень нужны как для написания надежного софта (server-side), так и при широком использовании перегруженных операторов (вычисления, например).

Как я понимаю, изменения в С# 2.0 (generic-и и partial classes) были направлены на облегчения создания для C# библиотек и дополнительных инструментов. С# 3.0 с Linq уже упрощает написание на C# бизнес-приложений.

eao197 комментирует...

>Ну и ещё фичи - это как правило изначальные недоделки языка :)

С этим соглашусь :)

Quaker комментирует...

Пусть лучше допилят компилятор java в native code (http://gcc.gnu.org/java/) - вот Вам и замена C++ с огромным количеством библиотек. Ну чем не вариант? Лучше и проще, чем очередной велосипед изобретать.

eao197 комментирует...

>Пусть лучше допилят компилятор java в native code (http://gcc.gnu.org/java/) - вот Вам и замена C++ с огромным количеством библиотек.

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

eao197 комментирует...

2Quaker: кстати, есть еще один вариант -- сделать source-to-source транслятор из Java в какой-нибудь язык. И тем самым портировать Java библиотеки. Такой эксперимент для D когда-то проделывали -- конвертировали SWT в DWT (http://www.dsource.org/projects/dwt)