Существует такая методология разработки, как "литературное программирование", которую мне удалось опробовать на практике на прошлой неделе. Задача для него была следующая - реализовать двоично-десятичный счётчик на языке описания аппаратуры. После её решения в этом стиле, мне так понравился данный подход, что так же была написана и реализация обобщённого счётчика для произвольного числа разрядов и системы счисления, что же является значительно более сложной задаче (или, скорее, запутанной, особенно с учётом выбранного мною подхода к реализации). Как следствие возникло желание немного рассказать об этой практике, пока ощущения ещё свежие.
Сперва о инструментарии, который мною использовался. Это была реализация системы литературного программирования noweb, которая не привязана ни к какому языку программирования (что оказалось крайне приятным, так как VHDL специально никто ничего подобного не делал). В качестве редактора использовался emacs + noweb.el. Текстовая часть кода оформлялась в LaTeX (тут была альтернатива в качестве html). В принципе, эти инструменты позволили с комфортом работать над задачей, после некоторой привычки. К сожалению, noweb.el является достаточно кривой реализацией, с некоторым количеством неприятных моментов (часто слетает подсветка синтаксиса).
Русская вики даёт следующее определение для литературного программирования:
Литературное (английский термин намеренно двусмысленный), или грамотное программирование (англ. Literate Programming) — концепция, методология программирования и документирования. Термин и саму концепцию разработал Дональд Кнут в 1981 году при разработке системы компьютерной вёрстки TeX.
Как мне кажется теперь, данное определение является довольно неудачным, так как в нём логически разделены такие понятия как документация и программирование. Дело в том, что при работе в данной стилистике, код и его описание сливаются между собой, и представляются для разработчика единым целым, и правильнее даже сказать, что не работает с программным кодом, а работает именно с текстом, со статьёй, посвящённой задаче, в которой она постепенно решается. Причём, у данной статьи отличие от той, что можно было бы написать себе в блог лишь в том, что нельзя опустить часть из подробностей.
Не знаю как все, но лично я, как правило, перед тем как решать какую-то задачу, в случае если она не совсем тривиальна, или по тем или иным причинам для меня не обычна, я сперва беру блокнот с ручкой, и пишу свои рассуждения о ней на бумаге. А затем, поглядывая в записи, пишу код. Само собой, никакого желания ни документировать, ни писать комментарии после этого как правила нет. В случае же использования литературного программирования, этот текст пишется в текстовом редакторе. Причём, не имеет никакого значения, как именно должен быть записан код, текст пишется именно так, как он думается, как он придумывается. Вот формулируется мысль о каком-то конкретном аспекте поведения, и прямо тут же, вы пишете:
<<фича такая-то>>=
Код этой фичи, абсолютно выдранный из контекста.
@
И так пишется дальше. После того, как осмысленная для данной задачи часть программы закончилась, аэроплан вашей мысли садится на землю и возникает резонная мысль о том, что исходный код программы должен быть в файле с каким-то именем, и он должен иметь какую-то структуру (собственно только тут и появляется различие между статьёй с литературной программой от просто статьи на тему решаемой задачи). И пишется примерно следующий код:
<<имя_файла>>=
Заголовок такой-то
<<контескт такой-то фичи>>
<<контескт такой-то фичи>>
<<фича такая-то>>
<<фича такая-то>>
<<фича такая-то>>
Ещё какой-то код, необходимый тут.
@
Теперь, мы можем двумя простыми командами получить как статью, в которой довольно неплохо описано, что делает программа (noweave -delay programm.nw > text.tex) и собственно сам код программы (notangle -Rимя_файла programm.nwп > имя_файла). Так как вы нормальный человек, а следовательно не достаточно внимательны, команда для кода скорее всего любезно скажет вам, какие аспекты программы вы забыли, и вы будете вынуждены, добавить пару абзацев текста.
После этого происходит запуск программы, и, скорее всего, она не работает. В случае если ошибки синтаксические, то исправлять их очень не удобно, и не понятно, так как компилятор говорит вам в где ошибка в сгенерированном коде, и вам приходится искать этот код в вашей статье и править там. Но в случае если ошибка является довольно сложной, которую не удаётся сразу найти при прочтение сгенерированного кода, то вы получается значительное преимущество. Это связано с тем, что перечитывая статью, очень хорошо видны все не стыковки и ошибки такого рода, и главным образом это происходит по тому, что код изложен в той последовательности, в которой был написан, а следовательно, вы можете повторно произвести осмысление задачи с новыми данными (и вам не прийдётся начинать всё сначала).
Примерно так выглядит разработка в литературном стиле у меня. Сейчас попробую тезисами привести преимущества и недостатки данного стиля (подчёркиваю, я его крайне мало практиковал и только для малых задачей, так что это не более чем размышления).
Преимущества:
- Вы пишите код к той форме и в том порядке, в котором вы думаете. Соответственно он намного проще проверяется, намного проще восстанавливается в голове, так как в нём все его детали всегда явно присутствуют и вводятся последовательно.
- Наличие такой детальной документации в коде, позволяет в нём разобраться любому человеку, и для этого не обязательно знать язык разработки.
Недостатки:
- Не понятно как это можно применять для динамических языков, таких как Smalltalk и Lisp.
- Затруднена разработка по чуть-чуть, постоянно тестируя, хотя как мне кажется данный недостаток слегка надуман в данном случае.
- Не совсем понятно, как такой стиль может применяться в коллективе, так как все думают по разному.
- (самый существенный, также его отмечает Кнут) Мало уметь программировать, надо уметь и писать.
Непонятные моменты:
- Время написания программы значительно выше, нежели в случае традиционного подхода, но в тоже время количество ошибок меньше. Сложные ошибки исправлять проше, а простые труднее.
Не смотря на то, что численно недостатков больше, первое преимущество их может переплюнуть с лёгкостью. Теперь, вероятно, буду стараться использовать этот подход как можно чаще.
Там лежит как сам исходник литературной программы, так и сгенерированный из него код и статья. Реализация простого счётчика едва ли может отразить стиль разработки, но в случае с обобщённым счётчиком, быть может это и оправдано.
1. В случае если ошибки синтаксические, то исправлять их очень не удобно, и не понятно, так как компилятор говорит вам в где ошибка в сгенерированном коде, и вам приходится искать этот код в вашей статье и править там.
ReplyDeleteНе совсем так: "noweb" позволяет пометить в извлечённой программе номера строк литературного исходника.
Вы, видя где проблема в коде, просто прыгаете в редакторе к номеру строки лит,исходника, открывающей или закрывающей нужный кусок.
2. Затруднена разработка по чуть-чуть, постоянно тестируя, хотя как мне кажется данный недостаток слегка надуман в данном случае
Да. В один файл литисходника можно вставить сколько угодно программных файлов. Казалось бы, держи тесты тут же, это как правило маленькие скрипты.
Однако это настолько замусорит файл, что двигаться по нему, отыскивая нужные куски, станет мучительно, а после некоторого уровня сложности (самой ли программы, или программы с пробами, тестами, справками и т.д) - просто невозможным, т.к. человеческий мозг имеет ограниченную память. Вам придется больше напрягаться вспоминая что где лежит, что конкурирует с усилиями на само программирование и решение задачи.
Потому я написал собственный инструмент для Lit.Programming'а, который для решения подобных вопросов использует "складной HTML". Пусть ваш файл 5 мегабайт текста - когда надо работать над парой сотен строк в разных местах, я смогу их держать открытыми, остальное - сложено. Во-вторых, я не должен более запоминать структуру моего проекта, у меня есть наверху "карта", an outline из которой я могу прыгать куда надо.
Такой складной HTML можно держать как "файл проекта", и держать в нём в подразделах любые тесты, варианты, пробы, куски-образцы - они не мусорят кода.
Т.е. я написал скрипт (на Perl'е), который берет лит-исходник написанный с разметкой noweb'а (с одним уточнением), и "на ходу" творит из него html файл, одной командой или перезагрузкой страницы в браузере, если мой проект лежит под локальным веб сервером.
Документация в "складном HTML" (слишком неряшливая, т.к. я пробовал разные идеи обращения с кодом и проектом, когда писал этот файл, но пока сойдет) лежит здесь:
http://literate-molly.sourceforge.net/
Читать в JavaScript-enabled браузере, который НЕ майкрософтовский IE.
Сгрузить скрипт (это скрипт на пёрле) и попробовать можно отсюда:
http://github.com/unixtechie/Literate-Molly
P.S. Еще в скрипт встроена одна новая штука: "виртуальные виды" вашего кода любое количество раз.
Т.е. допустим, ведя дневник своих мыслей во время программирования, я ввёл программные куски chunk1, chunk2, chunk3 ... chunk 10
В моём скрипте я могу создать потом другие описания -
-- например, обзор с пояснением главной мысли (в который войдет только chunk1, 4, 8 , 9);
-- или выделить документацию какого-то "интерфейса" (что надо понимать и изменять для работы с ним) - из chunk 3,4,7;
-- или собрать вс6, что нужно для устранения "бага Х" в одном дополнительном подразделе
Всё это будет такой же literate текст со вставленным кодом, только я его не переписываю 100 раз, я вставляю некую ссылку на него из разных мест.
Когда исправитель бага прочитает, он кликнет на ссылку (FLINK), у него откроется сложенная рамочка с программой внутри его пояснений, и он в редакторе прыгнет к нужной строке чтобы редактировать "локально", не держа в голове два десятка мест, которые надо не упустить, чтобы сделать что-то.
Интересная статья. Вот только я не уловил, в чем проблема с динамическими языками.
ReplyDelete2 Anonymous
ReplyDeleteБольшое спасибо за комментарий, узнал довольно много нового. Очень понравилась идея "виртаульных видов", определённо, в случае если будут большие литературные проекты, попробую.
2 yasha-makarov
>> Интересная статья. Вот только я не уловил, в чем проблема с динамическими языками.
Тут под динамическими языками имелись в виду те, что работают с "живой" системой. Когда есть образ, который постоянно изменяется. В таком случае стиль разработки превращается в гипотеза-проверка-гипотеза-проверка. Тут помимо инструментальной поддержки (в принципе, прикрутить не так уж и сложно), не совсем понятно как это уживётся с текстом. А с языками, где пишется реализация, и потом проверяется проблем быть не должно.
По-моему два стиля, стиль маленьких шагов и стиль крупной реализации, не зависят от языков программирования.
ReplyDeleteПотом система на чем бы она не была написана строгий алгоритм, который можно описать. Другое дело, что этот алгоритм можно описывать последовательно инструкцию за инструкцией, а можно сначала дергать различные куски, а потом уже описывать нечто связное состоящие из них. К сожалению, у меня нет опыта литературного программирования, но насколько я понял статьи Кнута, фишка именно в том что можно описывать реализацию в любой последовательности, в отличии от кода программа, в которой инструкции должны идти друг за другом.
Не могу с этим согласиться. Писал более менее крупные вещи на C и Erlang, стиль разработки различен координально, именно из-за динамичности Erlang-а. Просто в случае C для потсонного естирования нужны лишние телодвижния.
ReplyDeleteА в случае литературного программирования, либо каждый раз по многу переписывать (и лит код, и сам код, по разу на гипотезу), либо постоянно переключаться с литературного языка на обычный (а это может размазывать внимание, что убьёт главный плюс литературного подхода). Но тут, вероятно, надо просто пробовать, так как могу и жестоко ошибаться.
Я понимаю это так, что в литературном стиле проблематично написать программу в области, в которой много неизвестного, много надо экспериментировать.
ReplyDeleteУ тебя очень странный русский язык. Статья хорошая, но читается с трудом.
ReplyDelete