⚠️ Обращайте внимание на даты.
Этот блог больше не ведётся с 17 января 2023, и на тот момент с написания этой страницы (24.09.2015) прошло 7 лет.
Я хочу поговорить о нескольких больных темах. Во-первых, о Clojure, в котором я так толком и не продвинулся, но которым я по-прежнему восхищён, даже несмотря на полное отсутствие вакансий на него. Во-вторых, о редакторах и IDE, потому что существенной разницы между ними так и не появилось, а самое важное остаётся уделом хипстеров из мира программирования. В-третьих, я затрону такие вещи, как GameMaker и Scratch, идеи которых по сей день презираются, развиваются мелкими шажками и с большой осторожностью.
Все эти три пункта довольно грустные. Если утрировать, то это:
- язык, которым никто не пользуется
- спор, в котором нет смысла
- дело, которым никто не занимается
Эти три вещи кое-что объединяет.
Язык, которым никто не пользуется
Clojure практически во всех случаях бросают из-за скобочек. Серьёзно. И против этого всегда приводится один и тот же довод – что со временем любой кложурист перестаёт замечать скобочки, успешно донося структуру кода при помощи отступов.
Кложуристы при этом почти всегда умалчивают о такой мощной вещи, как Paredit: это изначально плагин для emacs
, который даёт возможность поддерживать баланс (открыть-закрыть) скобочек механически. Подобная штука встречается сейчас во многих редакторах, даже в Atom, в котором я сейчас это пишу: я открываю скобку, редактор за курсором ставит закрывающую, и всё, что я напишу далее, оказывается внутри них, я открываю скобку с выделенным словом и это слово оказывается в скобках. И… это удобно.
Paredit, конечно, не так прост. Он не только “дозакрывает” скобки, но и предоставляет средства по перемещению по окрестностям, выделению и раскрытию целых S-выражений (любое правильное скобочное выражение). Выражения можно менять местами, быстро выделять и раскрывать наружу.
Существует Paredit давно. Разработан он, предположительно, для Common Lisp, но больше всего применялся, похоже, для Emacs Lisp – диалекте Lisp, используемом в редакторе Emacs для расширений. У него было достаточно времени “созреть”. В Clojure синтаксис чуть сложнее, но идея в целом та же, и практически никто сейчас всерьёз не работает с Clojure без Paredit. Он просто вызывает страшное привыкание.
Спор, в котором нет смысла
А что долго говорить о бессмысленном? Среда разработки сильнее заточена под конкретную технологию, имеет больше инструментов для неё. В чём это выливается? В лучшем умении анализировать язык этой технологии. И… постойте-ка, всё. То есть, добавив/разработав в редактор достаточно вспомогательных инструментов, о границе IDE/редакторы можно забыть.
Это не новость и не размышления, это скорее напоминание.
Из чего сейчас состоят наши инструменты разработки? В основном это редактор для ввода кода. Иногда ещё и интерактивная консолька для ввода кода, чаще ради запуска программ. Иногда бывает такая роскошь, как визуальный редактор интерфейсов.
Дело, которым никто не занимается
“А теперь я расскажу вам свою биографию”. Точнее, ту её часть, что относится к теме.
Я некогда нашёл Game Maker 6. Он меня, конечно, удивил – оказалось, игра это всего лишь кучка объектов, в которых при определённых событиях что-то происходит!.. Но уже до этого я по школьному учебнику копался в среде “ЛогоМиры”, где программы писались из несложных текстовых инструкций и выполнялись либо единоразово (по щелчку на объект), либо каждый шаг среды. В этом смысле GM привёл в некоторое замешательство “что, всё так просто?” Разумеется, вкапываясь глубже и перейдя на GM7 (просто чтобы вы понимали, сколько времени прошло), я начал встречать определённые проблемы и ограничения, которые возникают без использования встроенного скриптового языка GML, и… жизнь отвлекла меня на переход в другую школу и изучение языка С. Потом я снова вернулся к GML и понял, что язык мне понятен и нужно только понять, как работает то, чем мы с помощью этого языка управляем.
В самом Game Maker, если не трогать GML, система написания алгоритмов очень несложная, но документация по ней была не очень понятной без предварительного опыта написания кода. На тот момент у меня его не было настолько, что читая раздел “GML Reference” и справку на русском языке я около года пытался вникнуть в то, что означает “возвращает значение” почти под каждым пунктом. Но мы отвлеклись. При составлении алгоритмов из “кнопочек” в GM важных принципов надо знать всего три:
- Серые квадраты просто выполняют какое-то действие.
- Фиолетовые восьмиугольники что-то вычисляют и в зависимости от этого выполняют (возможно, не один раз) или не выполняют следующее за ними действие.
- Фиолетовым восьмиугольникам можно подсунуть вместо одного действия несколько действий, если завернуть их в блок (фиолетовые треугольники), тогда они кажутся, как одно.
Оглядываясь назад, я понимаю, что это “инструкции” (statement), “конструкции” (clause) и “составные инструкции” (compound statement). Т. е. это просто инструкции (присваивания, вызовы), это команды циклов/ветвления и аналогичные (for
/while
/repeat
/if
/with
), а также фигурные скобки ({}
). Упущена важная часть – выражения. Их мы вынуждены вводить прямым текстом.
Экспрессия? Выражение? Эм, что?
Под выражением я понимаю любую синтаксическую конструкцию языка, которая возвращает значение, и вместо которой может с тем же успехом фигурировать любая другая синтаксическая конструкция и не приводить к синтаксической ошибке. Т. е. в большинстве языков (2 + 2)
является выражением, хотя может быть заменено простым литералом 4
– литералы же, по сути, тоже являются синтаксическими конструкциями (да-да!), просто они всегда означают одно и то же.
Интересно то, что я в последнее время работаю в основном с языками, где выражениями является практически всё. Первые подобные приёмы я видел в С – там, например, присваивание возращает присвоенное значение, что позволяет его (а) использовать в местах вроде условий у while
, где инструкции писать нельзя и (б) позволяет присваивать по цепочке, а-ля a = b = 5
. В GML, впрочем, даже этого нет – присваивание является инструкцией и в выражении фигурировать не может (a = b = 5
в GML больше похоже на a = (b == 5)
). Но если погружаться глубже – в Ruby определения классов и методов, циклы и условные операторы (даже так!) возвращают какие-то значения. Похожее происходит в CoffeeScript (но не в JavaScript, который в этом отношении ближе к С) и даже в Clojure (скорее особенно в Clojure, там выражениями после разворачивания макросов является вообще всё).
Так что GM – не лучший пример среды, делающей программирование проще, хотя в нём есть интересные моменты. Чуть дальше зашёл Scratch, по следам которого спешил Stencyl. Чуть более похожий на IDE – TouchDevelop, но он всё ещё является скорее экспериментом и обучающей игрушкой, нежели рабочим инструментом.
Работа с текстом… или нет
Из-за текстовой природы нашего кода мы вынуждены делать целый ряд странных вещей в наших редакторах. И без этих вещей редактор кажется очень неудобным для редактирования кода. Мы перестаём со временем воспринимать код, как текст, видя синтаксис насквозь, и привыкаем к этому. Текстовые редакторы нас в этом поддерживают (подсветка синтаксиса, сниппеты, вот это всё). Но всё-таки ряд вещей остался нерешённым.
К примеру, форматирование. Код можно писать по-разному, в том числе и вовсе в одну строку (в большинстве языков…). Чтобы люди писали код понятно, принимаются разные меры. Считается хорошей практикой завести “свод правил” для стиля кода, т. н. “styleguide”. Это решает сразу две задачи: учит писать понятнее (тех, кто ещё этому не обучался) и делает код проекта более однородным. Но у этого есть и проблемы.
В каких-то языках синтаксис свободнее, в каких-то не настолько. В Ruby, например, синтаксис свободен почти до неприличия, но я буду его использовать, только как пример:
- Можно не ставить круглые скобки при вызове функции.
- Можно обозначать блоки фигурными скобками или
do
..end
. - Длинное допущение. Готовьтесь. Можно при вызове функции (1), когда последним аргументом вызова является мап (2), не обозначать мап фигурными скобками. Звучит очень “узко”, но применяется поразительно часто, т. к. выглядит, как аргументы с названиями, написанными прямо в вызове.
- Можно не разделять инструкции знаком
;
, если размещать по одной на строке. - Можно не отделять инфиксный оператор пробелом от своих операндов, как
1+2
и1 + 2
. Это, кстати, почти везде. - Можно не ставить скобки, где вместо них срабатывает повышенный приоритет операторов: как в
1 * 2 + 3
и(1 * 2) + 3
. - В больших числах можно разделять группы разрядов знаком
_
, чтобы их было легче воспринимать: как в1_000_000
и1000000
. - Можно смотреть что-то внутри класса/модуля с помощью оператора
.
(точки, да), а можно с помощью оператора::
. ВродеMath.log(2)
иMath::log(2)
. - В хэшмапах можно обозначать ключи-символы без стрелочки
=>
. Например,{:a => 2}
и{a: 2}
.
Думаю, достаточно… для начала? По большинству правил из списка есть какое-то общепринятое мнение. Но не всегда. В некоторых случаях аргументы есть в обе стороны. В лучшем случае стороны договорятся и где-то зафиксируют свою договорённость. В худшем – “война правок” и коммиты из “синтаксиса покрасивее”.
Видите проблему? Человек может смириться с чужой нормой, но ему всё равно будет не очень удобно. Как можно решить эту проблему? Можно формально определить оба стиля (проектный и личный), при редактировании автоформатировать в личный, при сохранении форматировать в проектный. Костыль? Ещё какой.
Я могу ещё примеров накидать. К примеру, у нас есть подсветка синтаксиса. Есть статический анализатор на предмет неочевидных багов/опечаток и нарушений стиля. Есть инструменты для рефакторинга. Им всем приходится разбирать код. И частенько они все делают это по разным алгоритмам разной степени сложности. И у них бывают расхождения. Особенно комично, когда какой-то из алгоритмов расходится по версии с самим языком. Ведь для всех этих задач, да и для программиста тоже, нужно синтаксическое дерево, и каждый его строит, как знает.
Может, стоит задуматься о том, чтобы перестать редактировать код как текст и начать редактировать синтаксическое дерево, в которое он всё равно будет преобразован?