суббота, 31 октября 2009 г.

[comp.prog.thoughts] Мои крамольные мысли о будущем языков программирования

Нижеследующий поток сознания навеян просмотром темы “Язык будущего. Какой он?” на сайте RSDN. Я не согласен с тем, что будущее языков программирование лежит где-то в области хардкорного функционального программирования с зависимыми типами (там дали ссылку на почти 50-страничный документ Dependent Types At Work, в котором нет (на первый взгляд) ни одной практически-значимой задачи, типичный академизм). Так же далекое будущее не связано с освоением multi/manycores платформ – по сути, это уже идет сейчас. Так что же требуется от языка программирования будущего?

Начну, согласно литературным канонам, издалека. С апреля месяца я работал над серией проектов (не очень связанных между собой), практически в авральном режиме. Требовалось делать все очень быстро, поскольку сроки были буквально “вчера”. И мне приходилось постоянно сообщать свои прогнозы руководству. Естественно, первые прогнозы никогда не оправдывались. В лучшем случае я ошибался в 1.5-2 раза. Т.е. заявлял срок в один месяц или в 10 дней, на практике же получалось два месяца или 15 дней.

Поскольку мне еще не приходилось так часто ошибаться в своих прогнозах (да еще за такое короткое время), то я попытался проанализировать, чем же такие ошибки были вызваны. Мне кажется (именно кажется, это не подтверждено никакими статистическими экспериментами), что основных причин две:

  • невозможность представить себе всю задачу в деталях и
  • необходимость записывать собственные мысли в программный код даже для самых мелких деталей задачи.

Продемонстрирую это на примере. Стоит задача написать приложение, которое будет подключаться к POP3-серверу, забирать письма, фильтровать уже обработанные, выдергивать из необработанных писем нужную информацию и отсылать ее дальше. Предварительный прогноз (после того, как была написана тестовая программка для проверки возможностей класса POP3ClientSession из библиотеки POCO) – три-четыре дня. Но ушло почти шесть дней. Поскольку первые мысли были приблизительно такие: большой цикл, в начале открытие POP3-сессии, получение списка писем, последовательный перебор писем, выделение или генерация для каждого письма Message-ID, проверка уникальности Message-ID по БД, выделение нужной информации из письма, передача получателю, фиксация Message-ID в БД, удаление письма с POP3-сервера, периодическая чистка БД, а если долго не было нормальных POP3-сессий или ответов от получателя, то завершение работы.

В принципе, ничего сложного. Вот, скажем, последний пункт: если долго не было нормальных POP3-сессий или ответов от получателя, то завершение работы. Казалось бы, что в этом сложного. Делается за 15-минут, ну за полчаса. Но во что все это реально выливается на практике?

Во-первых, ограничение времени отсутствия нормальной работы должно конфигурироваться. Значит, в структуру cfg_t вносится еще одно поле, m_max_time_without_successful_receive. С комментариями, с объяснением назначения этого поля и определением его значения по умолчанию.

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

Далее, если разбор конфигурационного файла контролируется unit-тестами (а по хорошему должен контролироваться), то следует прописать в unit-тестах несколько test case для проверки обработки этого значения.

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

Далее, нужно выделить в коде агента места, в которых становится понятен результат последней операции обработки почты. Если таких мест несколько, то желательно либо выполнить рефакторинг, чтобы такое место было одно, либо же придумать, как в каждом из них выполнять анализ результата и обновление m_last_receive_timestamp с минимальным дублированием кода. Если был произведен рефакторинг, то нужно убедиться, что операция чтения почты не поломалась.

Далее, нужно выделить в коде места, в которых будет проверяться, не слишком ли долго не было успешного чтения почты. Если таких мест несколько, то…, а если нет, то…

Далее, нужно решить, как именно будет логироваться факт завершения работы программы из-за превышения времени. Сообщение лога должно быть специальным образом описано и задокументированно.

Далее, пользователь с помощью удаленного мониторинга должен видеть, какой результат был у последней операции чтения почты. А так же сколько времени прошло с момента последнего успешного чтения почты (тут еще нужно выяснить, может быть пользователя больше интересует сколько времени еще осталось до принудительного завершения работы, а может пользователь хочет видеть и то, и другое?). В соответствующие структуры добавляются специальные мониторы m_last_pop3_session_result и m_time_from_last_receive. С комментариями.

Далее, в код агента добавляется обновление мониторов m_last_pop3_session_result и m_time_from_last_receive.

Далее, все это тестируется в сборе (а до этого тестировалось по мере выполнения каждого из шагов).

Вот теперь все. На практике эта работа у меня заняла что-то около 2.5-3 часов рабочего времени. 3 часа рабочего времени – это практически половина рабочего дня (даже больше, если брать в расчет разные производственные и непроизводственные разговоры с коллегами). Т.е. за полдня удается сделать одну не очень значительную детальку не очень сложной программки. А таких деталек… И ведь никогда не известно их точное количество – всегда по ходу разработки на свет божий выбираются такие невероятные детали, о которых изначально даже не задумывались. А время идет.

Какое отношение все это имеет к будущим языкам программирования? Прямое :)

Программист делает двойную работу. Первая часть его работы – составление детального плана реализации какой-то возможности. Что-то вроде приведенного выше перечня абзацев, начинавшихся со слова “Далее”. Это очень трудоемкая работа, поскольку здесь идет прокручивание в голове разработчика какой-то мысленной модели решаемой задачи. По возможности полной модели. С учетом различных тонкостей и нюансов.

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

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

Так вот мне кажется, что языки будущего должны максимально близко подбираться к выразительности естественного языка, но при этом допускать только однозначное толкование написанного. В идеале, разработчик описывает что-то вроде простого текста, а из этого текста генерируется работающая программа.

Произойдет ли это когда-нибудь? Я не знаю, да и не уверен в этом.

PS. Все вышесказанное является моим субъективным имхом. Причем сегодняшним. Завтра все может поменяться и я буду думать по другому :)

PPS. Мое скептическое отношение к функциональщине (и зависимым типам как к одному из ее проявлений) основывается на том, что в функциональном подходе разрыв между планом действий и программой еще больше. Ведь в процессе кодирования разработчику нужно не только думать о коде, но нужно еще и формулировать правила доказательства корректности этого кода. И после этого все равно нужно проводить функциональные тесты.

Отправить комментарий