Category: дизайн

cartoon

Мой доклад на SoftwarePeople

Это будет доклад о том, что вы всегда хотели знать, но боялись спросить. Это доклад о проектировании. Главных тезиса два.

1) Хорошо организованная группа профессионалов всегда проектирует лучше, чем один мегаумный архитектор. Я собираюсь это настолько хорошо обосновать, что это близко к "доказать".
2) Менеджер может эффективно направлять и контролировать процесс проектирования, в том числе надежно отмечать его прогресс. Я собираюсь не просто это сказать, а наглядно и просто показать - как, так, что вы сможете это повторить. Ведь понимание требуется для действия - или нафиг оно не нужно, такое негодное понимание.

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

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

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

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

P.P.S.: На тот случай, если кто не знает, что такое SoftwarePeople. www.softwarepeople.ru. Регистируйтесь.
cartoon

Полностью распределенная разработка - что в основе

Я не успел подробно расспросить codedgers о том, как у них все устроено, но так даже интереснее. Когда теперь я знаю, что так можно, – можно попробовать применить “дедуктивный метод”.

Collapse )
cartoon

Design Review и его роль

Про код-ревью слышали многие. Про Design Review - нет. Меж тем, именно присутствие design review в паре с code review  позволяет во многих случаях сделать всю систему review более эффективной, и многократно усилить ее положительный эффект. 

management@RSDN: Code Review: а оно надо?

D>>кто-нибудь использует такую процедуру как Code review? Как это происходит? вы реально выделяете час времени в неделю, садитесь с командой и начинаете стебаться друг над другом???

B>3 дня назад мне делали. общий обьем — что-то около 3к строк (всего).

B>человек затратил около 2-х недель, что бы вычитать весь мой код.
B>сидели 2 часа в компании с еще одним, в митингруме с проектором — отвечал на вопросы. вроде отбился.

Нормальный средний темп кодревью, при котором реально найти ошибки — примерно 200 строк кода в час. Плюс-минус, потому, что средний.

3к строк будет примерно 15 часов чистого времени. При условии 4-х часов чистого времени в день (полная загрузка) имеем дня 4, вообще-то. То есть, кодревью выполнялось скорее медленно. Если ревьювер при этом еще и не нашел в коде багов (критерий эффективности ревью, ваще на нем ошибки положено ловить) — это отрицательная работа. От такого code review пользы немного.

Медленно и неэффективно оно может быть по разным причинам. Например:
1) Человек, кто проводит код ревью, не проводил ранее дизайн ревью этого же кода. Лечится либо kick-off meeting перед началом ревью (что считается необходимым для экспертизы материала такого объема), где автор объясняет, что тут ваще происходит, и как оно ваще работает. Либо, что гораздо эффективнее во всех смыслах, введением отдельного дизайн-ревью, где автор докладывает, как он собирается решать задачу, до того, как пишет основную массу кода. Для дизайн ревью в простейшем случае сойдет устный доклад у маркерной доски. И те же люди, кто проводил дизайн ревью, потом выполняют код ревью. 
2) Человек вообще не специалист в данной теме — маловероятно, что он найдет какие-либо ошибки кроме пунктуации, эстетики, и соблюдения стандарта кодирования. Подобные ошибки, к слову, ловятся в темпе побыстрее чем 200 строк в час.
3) Давайть на ревью такие большие фрагменты кода — фашызм. Надо стараться бить на части. Опять же, проводя до этого дизайн ревью, чтобы люди были в курсе.

Вообще, дизайн ревью необходимы для того, чтобы сделать code review более эффективными на практике. Почему:
1) Они снижают затраты на code review, и совокупные затраты на все review.
2) Ошибка, которая может быть обнаруженная на дизайн ревью, в исправлении почти бесплатна, и является напротив — наиболее дорогой ошибкой, будучи найденной на код ревью. 
3) Надо учесть социальный фактор. Бывает так, что на кодревью выясняется, что весь подход к решению задачи неправильный, и делать по хорошему надо не так. Однако, автор уже написал дохрена кода! Во-первых, ревьюверы чувствуют себя виноватыми, и им тяжело сказать человеку, что надо все выбросить, и переделать. Во-вторых, человеку самому грустно и обидно это делать. Поэтому, такой код имеет все шансы пройти кодревью, и таки оказаться в репозитории. Если не проводить design review, подобная ситуация будет возникать регулярно.
4) Design review позволяют привить культуру предварительного проектирования в масштабе компании, и являются, кроме того, единственным способом как-то контроллировать закрытие задачи "проектирование".

И 5-е. Пожалуй, самое существенное. Введение design review имеет радикальный положительный эффект на сроки разработки и их предсказуемость в условиях наличия новых, неопытных сотрудников, в условиях присутствия значительной базы существующего кода. Проверено.

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

Введя обязательное design review, вы бьете подход к проблеме на два этапа, проскочить которые нельзя. 1 — "я понимаю, что и как я собираюсь сделать", и 2 — "я делаю". Делая это, вы выносите источник неопределенности в первый этап, который строго ограничиваете временными рамками, и сотрудник делает доклад о выбранном подходе к решению проблемы. Он должен описать изменения с точностью до класса. Его доклад слушают 1-2 опытных разработчика, и ищут ошибки. По показаниям — проводя ликбез в виде кратких лекций по архитектуре и принципам устройства системы, если это необходимо (правильное решение задачи проектирования является как бы практическим заданием к лекции — так автоматически само собой получается). 

После того, как предлагаемое решение прошло design review, у сотрудника уже практически не остается шансов дать большую вариацию в сроке выполнения работы — вся неопределенность снята . Как и по крупному налажать. Кроме того, при данной схеме мы контроллируем обучение сотрудника.

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

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

Gaperton on Software Architecture

Что такое архитектура ПО, чем она отличается от "дизайна", и что же все-таки является входными данными для разработки архитектуры. Дискуссия на РСДН по данному вопросу была изумительна.

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

Надо расставить точки над i по крайней мере в моей позиции. Надо для начала определить, что такое "архитектура", и разобрать это определение на конкретных примерах. То, чего, почему-то, сильно не любит публика на РСДН. Странно, но по факту оказывается так. Ок, essense of software architecture минут за 10.

Collapse )
cartoon

Что такое архитектура

Интересная дискуссия на РСДН.

Тред дискуссии с моим участием:
http://rsdn.ru/forum/message/3288567.1.aspx
Но и вокруг интересно.

Здравствуйте, Sinix, Вы писали:

S>Пока из общения с товарищами выходит что проектирование чисто от требований — нормальный подход. Смущает две вещи — то, что требования направлены на решение текущих задач и строить на их основе архитектуру системы — как то не того...
S>и то, что эти вопросы в принципе не подымаются в "тру" методологиях — такие вопросы отдаются на откуп архитекторам (если он есть) или обходятся кучей методик (не будем о DDD — про неё даже Эванс не может уверенно сказать что оно такое, куда уж простым смертным )

"Учителя разных школ психотерапии имеют больше общего между собой, чем их ученики — с ними". Слишком много придаете значения методологиям и букве. Они на самом деле все об одном, фундаментальной разницы между ними нет.

Первое — что такое "архитектура". Нет единого понимания данного термина, что служит поводом для спекуляций.

Определяем три термина. Архитектура, дизайн, детальный дизайн.

Детальный дизайн — алгоритмы и структуры данных.
Дизайн — разбиение функциональности по компонентам, модулям, или классам.
Архитектура — то, что не перечисленное. Комплекс базовых технологий, на которые опирается решение, плюс — идиом или паттернов проектирования, заложенных в "ядро", "фреймворк", или философию построения системы. Если перечисленное вообще есть. Скажем, вы решили, что сериализация объектов у вас будет всегда в XML — это архитектурное решение.

Дизайн и архитектуру часто путают.

S>Это я дурак и что-то упустил или это такая модель бизнеса — "Сделаем всё за деньги. Не понравилось — платите, не можете сформулировать хотелки — платите, ошиблись — платите"? Потому что я не могу больше придумать доводов, зачем так опровергать тот факт, что архитектура не может быть построена на информации о сиюминутных целях.

Разумеется, надо принимать во внимание как требования, так и технические ограничения, так и ваши возможности (знаем Java — архитектура будет базироваться на Java а не дотнет). Будущие требования тоже имеют значение. Главное, архитектуру не путайте с дизайном. Думаю, как только вы мои определения почитали, вам уже все стало понятнее, не так ли?

И следующий пост - плюс ответ на него.
_________________________________
S>Ок. Принято. Вопрос: должен ли дизайн (с архитектурой вроде всем ясно) проектироваться исключительно на основе требований? (Вопрос провокационный, больше чтобы было от чего идти). Если нет, как можно компенсировать перекос дизайна из-за учёта только актуальных требований?

Да должен. Уточню определение, чтобы избежать множественного толкования.

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

Архитектура реализует класс требований. Это — "стратегический дизайн", цель которого — выигрышь в долговременной перспективе, а не в контексте данного проекта с данными требованиями.

Дизайн — исходит их текущих требований, плюс ограничения, диктуемые архитектурой. Если ограничения нам сильно прям мешают, можно внести правку архитектуры. Целесообразность правки решается по месту по критерию riskk-value-cost.

S>Интересно также ваше мнение про модель бизнеса — я не могу придумать других рациональных объясенений такой агрессивной пропаганде решений "здесь и сейчас". Или это только моя мнительность и кто-то кроме вас говорил о "надо принимать во внимание как требования, так и технические ограничения, так и ваши возможности" и "Будущие требования тоже имеют значение"?

По моему это простой здравый смысл. Любой реалистичный план исходит в первую очередь из ваших возможностей. Если в эти возможности не входит снятие технических ограничений — надо учитывать и их. Потому, что вложение в технологию — дорогостоящая штука, это стратегическое решение. Как впрочем и ее разработка. А что требований — так нафига делать то, что никому не нужно? Архитетура — это стратегический дизайн, который по сути есть набор ограничений для тактического дизайна. Этим все сказано ИМХО.

Другими словами, я понимаю архитектуру как результат и основной составной элемент процесса стратегического планирования. Составной элемент — потому, что этот процесс непрерывен, и архитектура должна корректироваться по ходу времени, отражая представление о будущем, угрозах, рисках, и возможностях. Текущая архитектура может отличаться от "эталонной", которая отражает текущие представления, и ничего страшного в этом нет — работы по рефакторингу привязываются к плану текущих работ, диктуемых текущими требованиями, но — вектор направления в дизайне всегда в сторону "эталонной" архитектуры.
cartoon

Guide for mastering project plans

Gaperton's comprehensive guide for mastering project plans:

Как составлять планы, или "декларативное планирование"
http://gaperton.livejournal.com/16087.html

Оценка трудозатрат при разработке ПО
http://gaperton.livejournal.com/20165.html

Эффективный MS Project. The missing tutorial.
http://gaperton.livejournal.com/19205.html




А литературы по составлению хороших планов я не встречал. Даже принципы нормально нигде не излагаются, чтоб сразу и в одном месте. А они есть, и достаточно простые.

Collapse )
cartoon

О приоритетах, их влиянии на разработку, и рефакторинге

G>>наличием приоритетов по срочности работ (что приводит к "грязным решениям")

I>перечитывал старую тему, с целью вырости над собой и увидел оставленную фразу. не сможете более подробно её прокомментировать? что имели в виду когда писали одновременно приоритетность срочности и грязные решения? заранее спасибо.

Collapse )
инженер

Хороший Agile

http://rsdn.ru/forum/message/2919687.1.aspx
- это мертвый agile, как сказали бы (и говорили) "отцы" форума. Однако, коллеги, удивительное рядом. Я нашел легковесную методологию, которая не убьет проект, не противоречит здравому смыслу, и вполне жизнеспособна. Короче — не killed in assembly . Более того — она мне нравится, и я ее рекомендую. Очень хочу собрать консилиум "эдиповых отцов" и просто "жостких патерналистических мужиков", чтобы это обсудить.

(Для читателей ЖЖ, которые не могут оценить шутку про "эдиповых отцов" - это наш свежий РСДН-овский прикол, недавно в форуме управление проектами проходила очень живая тема - "о нарциссизме в разработке софта" - читайте, афтаржжотсцуко
http://rsdn.ru/forum/message/2908180.1.aspx
)
Collapse )
cartoon

Недостатки agile

Продолжение дискуссии. Ах, какой gaperton ретроград :)

http://rsdn.ru/forum/message/2646352.1.aspx
G>Здравствуйте, rsn81, Вы писали:

G>>>С логикой что-то не так?
R>>Теперь понятно.
R>>
Практически, спорить не буду, потому что не имею (кто знает, может тоько пока) опыта работы в таких огромных командах.
R>>
Теоретически... не согласен, потому что читаю труды людей, которых уважаю, которые вроде бы имеют такой опыт и говорят противоположные вам вещи.

G>>>При том, что работать большими командами все-таки необходимо, нравится это кому-то, или нет, а подход ХР противоречит основным принципам работы больших команд — он практически гарантирует провал на большой команде.
R>>Аналогично: не знаю, но сомневаюсь.

G>В таком случае должно быть какое-то простое и понятное объяснение — каким образом и почему разработка у них хорошо масштабируется. Я на слово даже уважаемым людям не верю — без понимания сути и причин пользы от такой веры никакой нет. А раз так — зачем себе голову забивать религиозными по сути штуками. Бритва Оккама.

Объяснение у них, очевидно, такое.

Collapse )
инженер

Problem K на Эрланг: решение №2, последнее + выводы

Исправленное решение - по результатам обсуждения на РСДН.



1. В целом, то что я описал в качестве подхода к "чистому" дизайну - вроде как кажется правильно. Однако, не все типы следует скрывать за абстрактными интерфейсами. Некоторые общие вариантные типы, которые используются в куче мест, лучше таки выставить наружу вместе с их структурой, - и это приведет к существенному упрощению дизайна. Примером является абстрактное синтаксическое дерево в компиляторах.

Мы сделаем это для элементарных типов, одновременно с применением еще одного дизайнерского хода - "слоеных" архитектрур. Типа как в семиуровневой модели OSI, и все такое. Суть слоев проста - верхний слой использует нижний, нижний ничего не знает про верхний. Также, этими средствами я исправляю косяк в моем предыдущем дизайне - а именно, такие важные штуки как value, ref, и error у меня не проходят явными типами, их структура - это неявный контракт между модулями cell, expression, и operation, что, вообще говоря, error-prone и, если кто не заметил - то это очень сильный косяк.

Для Хаскеля это не будет _таким_ сильным косяком, там такие типы явно определяются, а вот для динамического Эрланга - это реально сильный косяк. Вообще, планка good enough дизайна для Эрланга выше, чем для Хаскеля с его системой типов, строгой типизаций.

Итак, выделяем слой базовых типов в явном виде. Это будут reference, value, и operation.

Начнем с главного - value. Имеем конструктор из строки create, которая нам обязательно вернет валидный value. Ошибка - также вполне валидный value.

Обратите внимание - пустое значение также значение, и оно может трактоваться как 0 в операциях с целыми. Потому, у нас определены функции приведения к целому (as_integer) для нашего значения, которыми воспользуется operation. Результат придения обязан быть целым, или вылетит value:error в виде exception.

Кроме того, кто-то (а именно - expression) может захотеть создать число из строки, зная, что там должно лежать число (конструктор integer). Тут уж извините - если не вышло, то ничего не создастся и вылетит value:error.

Принципиально здесь следующее - вылет exception находится также в данном модуле, и вылетает гарантированно альтернатива варианта error. Если мы не хотим exception, а хотим чтобы вернулся честный variant, то нам достаточно перед вызовом нашей функции написать catch.

-module( value ).
-export( [ create/1, integer/1 ] ). %% create value from string
-export( [ as_string/1, as_integer/1 ]  ).
-export( [ throw_error/1 ] ). %% throw value:error

%% Public data type
%% value() = integer() | string() | error() | empty.
create( [ $', String ] ) -> String;
create( [ $#, String ] ) -> error( list_to_atom( String ) );
create( "" ) -> empty;
create( Int ) -> catch integer( Int ).

%% to_string( value() ) -> string().
%% get text representation for the given cell value
as_string( Int ) when is_integer( Int ) -> integer_to_list( Int );
as_string( { error, Msg } ) -> "#" ++ atom_to_list( Msg );
as_string( empty ) -> "";
as_string( String ) is_list( String ) -> String.

%% error( atom() ) -> error() = { error, Code }
error( Code ) when is_atom( Code ) -> { error, Code }.
throw_error( Msg ) when is_atom( Msg ) -> throw( error( Msg ) ).

%% integer( String ) -> integer() | throw error()
integer( String ) ->
	case string:to_integer( T ) of
		{ Int, [] } -> Int;
		{ error, no_integer } -> throw_error( 'not a number' ).
	end.

as_integer( empty ) -> 0;
as_integer( Int ) when is_integer( Int ) -> Int;
as_integer( _ ) -> throw_error( 'not a number' ).


ref у нас можно создавать из текста, а если не получается - то вылетает exception. Что логично - результат данной операции _только_ валидный тип ref, и ничто иное. Обратите внимание - как именно он вылетает. Вылетает гарантировано value:error, через функциональный интерфейс.

-module( ref ).
-export( [ create/1, create/2, row_range/3 ] ).

%% create( integer(), integer() ) -> ref().
create( Row, Col ) -> { Row, Col }.

%% create( string() ) -> ref() | throws value:error().
create( [ Row, Col ] ) when Row >= $a, Row =< $z, Col >= $0, Col =< $9 ->
	{ Row - $a + 1, Col - $0 };
create( _ ) -> value:throw_error( 'invalid ref' ).

%% row_range( Row, From, To ) -> [ ref() ]
%% Row = From = To = integer()
row_range( Row, From, To ) -> [ ref( Row, Col ) || Col <- lists:seq( From, To ) ].


Если вдуматься, то бинарные операции также могут являться элементарным типом данных. С операциями мы также правим один косяк. Теперь конструктор у нас возвращает тупо бунарную функцию, которая делает то, что нам нужно. Вот такую: fun( value(), value() ) -> value() | throws error(). Да, из нее могут лететь exception, но если мы напишем catch F( X, Y ) то тип этого выражения будет честный value(). Чем мы и воспользуемся в реализации expression.

Использование функций в качестве возворащаемых значений - чрезвычайно мощное средство инкапсуляции, которое радикально уменьшает связность. Чем это лучше предыдущего варианта? Например, я могу эту функцию передать в третий модуль параметром, и этот модуль может ничего не знать о модуле operation. Что в предыдущем дизайне невозможно. ФВП - совершенно потрясающая штука, я их ни на какие классы теперь не променяю.

-module( operation ).
-export( [ tokens/0, create/1 ] ).

%% create( char() ) -> Operation
%% Operation = fun( value(), value() ) -> value() | throws error()
%% create binary operator...
create( $+ ) -> fun add/2;
create( $- ) -> fun sub/2;
create( $* ) -> fun mult/2;
create( $/ ) -> fun divd/2.

%% tokens() -> [ char() ].
%% binary operators' token list...
tokens() -> "+-*/".

%% Binary operations implementation... 
%%______________________________________
add( X, Y ) when is_list( X ), is_list( Y ) -> X ++ Y;
add( X, Y ) -> value:as_number( X ) + value:as_number( Y ).

mult( X, Y ) -> value:as_number( X ) * value:as_number( Y ).

sub( X, Y ) -> value:as_number( X ) - value:as_number( Y ).

divd( X, Y ) ->
	X_1 = value:as_number( X ),
	Y_1 = case value:as_number( Y ) of
		0 -> value:throw_error( 'division by zero' );
		Y -> Y
	end,
	X_1 / Y_1.


Вот так. Теперь перейдем, так сказать, к логике нашего решения. Как я уже говорил, Трурль с РСДН предложил замечательное решение проблемы связности модуля expression. Суть его состоит в том, чтобы конструктор expression возвращал список ссылок, которые надо просчитать. Мы дополним его решение применением ФВП, и получим волшебную штуку - интерфейс expression сократился до одной функции.

create_expr( string() ) -> { Expression, [ ref() ] } | value().
Expression = fun( [ value() ] ) -> value()

То есть, мы создаем выражение из строки, результат будет либо value, либо пара функция - список аргументов. Который у нас - список ref(). Мы должны заменить ref на конкретные значения, и вызвать нашу функцию - и все. Идея предельно проста.

-module( expression ).
-export( [ create/1 ] ).

%% create_expr( string() ) -> { Expression, [ ref() ] } | value().
%% Expression = fun( [ value() ] ) -> value()
create( String ) -> catch
		{ Expr, Refs } = create_expr( String ),
		F = fun( Args ) -> catch
			{ Res, [] } = evaluate( Expr, Args ),
			Res
		end,
		{ F, Refs }

%% create( string() ) -> { expression(), refs() }.
%% create expression from text...		
create_expr( Expr ) ->
	Pos = string:cspan( Expr, operation:tokens() ),
	case lists:split( Pos, Expr ) of
		{ Val, [ Op | Rest ] } ->
			L = create_arg( Val ),
			{ R, Refs } = create_expr( Rest ),
			Node = { L, operation:create( Op ), R },
			{ Node, update_refs( L, Refs ) };
		{ Val, [] } ->
			H = create_arg( Val ),
			{ H, update_refs( H, [] ) }
	end.

update_refs( { ref, Ref }, Refs ) -> [ Ref | Refs ];
update_refs( _, Refs ) -> Refs. 

%% create_arg( string() ) -> Ref | integer()
%% Ref = { integer(), integer() }
create_arg( Arg ) ->
	try value:integer( Arg ) catch
		{ error, _ } -> ref:create( Arg )
	end.

%% evaluate( Expression, Args ) -> { Value, [] }.
%% Evaluate expression.
evaluate( { A, Op, B }, Args ) ->
	{ ResA, Args_1 } = evaluate( A, Args ),
	{ ResB, Args_2 } = evaluate( B, Args_1 ),
	{ Op( ResA, ResB ), Args_2 };
evaluate( { ref, _ }, [ Arg | Rest ] ) -> { Arg, Rest };
evaluate( Val, Args ) -> { Val, Args }.


Теперь клетка таблицы. Ее можно создать из текста, также ее можно рассчитать, тогда она перестанет быть клеткой, и станет просто value(). Собственно, что такое клетка, если вдуматься? Это либо value(), либо формула. Что такое процесс вычисления? Замена в таблице cell() на value(). Вот и все.

Код выглядит так страшно потому, что мы вычисляем формулы рекурсивно, ловя циклы и модифицируя sheet, который протягивается сквозь алгоритм "монадическим образом" ((с) Зефиров). Да собственно, если вдуматься, то и не так страшно он выглядит.

-module( cell ).
-export( [	create/1, evaluate/2 ] ).

%% create( String ) -> value() | Formula
%% Formula = { expression(), [ ref() ] }
%% value:create, expression:create, Cell
create( [ $= | Expr ] ) -> expression:create( Expr );
create( Value ) -> value:create( Value ).

%% evaluate( ref(), sheet() ) -> sheet().
%% eval_and_get
evaluate( Ref, Sheet ) ->
	{ _, Sheet_1 } = eval_and_get( Ref, Sheet ),
	Sheet_1.

%% eval_and_get( ref(), sheet() ) -> { value(), sheet() }.
%% table:get_cell, table:set_cell, value:error, Cell
eval_and_get( Ref, Sheet ) ->
	case table:get_cell( Ref, Sheet ) of
		updating -> { value:error( 'cyclic reference' ), Sheet };
		{ Expr, Refs } ->
			Sheet_1 = table:set_cell( Ref, updating, Sheet ),
			{ Args, Sheet_2 } = lists:mapfoldl( fun eval_and_get/2, Sheet_1, Refs ),
			Value = Expr( Args ),
			{ Value, table:set_cell( Ref, Value ) };
		Value -> { Value, Sheet }
	end.


Вот, остался модуль sheet. К моему сожалению - он не дописан до конца, потому как мне пришла идея выделить способ хранения таблицы в отдельный модуль "базового" уровня, (table), но я не дописал sheet до конца. И я чувствую, что уже не допишу. В принципе, так - правильно, и это последний штих к дизайну. Но сил возится с этой задачей у меня больше нет. Есть у меня такое неприятное свойство - когда я вижу, что дальнейшее решение тривиально, то у меня напрочь пропадает интерес к задаче. Пальцы - не печатают. И все, хоть что делай. Поэтому привожу код как есть - пытливый ум восстановит картину.

-module( sheet ).
-export( [	new/0, 
			create_row/3, row_as_text/3, evaluate/1 ] ).

%% new() -> sheet()
%% construct new sheet...
new() -> table:new().

%% update_row( Row, [ string() ], sheet() ) -> sheet()
%% table:set_cell, ref:row_range, create_cell
create_row( Row, TextCells, Sheet ) ->
	Cells = [ cell:create( String ) || String <- TextCells ],
	Refs = ref:row_range( Row, 1, lists:length( Cells ) ),
	lists:foldl( fun table:set_cell/2, Sheet, lists:zip( Refs, Cells ) ).

%% get_row( Row, Length, sheet() ) -> [ string() ]
%% value:as_string, table:get_cell, ref:row_range
row_as_text( Row, Cols, Sheet ) ->
	[ value:as_string( table:get_cell( Ref, Sheet ) ) || Ref <- ref:row_range( Row, 1, Length ) ].
	
%% evaluate( sheet() ) -> sheet().
%% cell:evaluate, table:get_refs
evaluate( Sheet ) -> lists:foldl( fun cell:evaluate/2, Sheet, table:get_refs() ).

get_cell( Row, Col, Sheet ) -> 
	Value = table:get_cell( ref:create( Row, Col ), Sheet ),
	value:as_string( Value ).
	
set_cell( Row, Col, String, Sheet ) ->
	 table:set_cell( ref:create( Row, Col ), cell:create( String ), Sheet ),

-module( table ).
-export( [ new/0, set_cell/2, set_cell/3, get_cell/2, get_refs/1 ] ).

%% new() -> sheet()
%% construct new sheet...
new() -> gb_trees:empty().

get_refs( Sheet ) -> gb_trees:keys( Sheet ).

%% set_cell( ref(), cell(), sheet() ) -> sheet().
set_cell( _, empty, Sheet ) -> Sheet; %%% do NOT store empty values...
set_cell( Ref, Value, Sheet ) -> gb_trees:enter( Ref, Value, Sheet ).

%% set_cell( { ref(), cell() }, sheet() ) -> sheet().
set_cell( { Ref, Value }, Sheet ) -> set_cell( Ref, Value, Sheet ).

%% get_cell( ref(), sheet() ) -> Value.
get_cell( Ref, Sheet ) -> 
	case gb_trees:lookup( Ref, Sheet ) of
		{ value, Cell } -> Cell;
		_ -> empty
	end.


Вот так, мне кажется, должен выглядеть идиоматический дизайн для данной задачи. Выводы:
1) ФВП - рулят.
2) Для описания дизайна чистых программ великолепно подходит метод CRC-Cards. Буду им пользоваться.
3) Дизайн в случае функциональной чистоты - удивительно простое и приятное занятие, не приходится морочится с состоянием, identity, и следующими из них схемами агрегации. Дизайн уже не является трудоемкой фазой разработки. Это меня изумляет.
4) Рефакторинг чистой программы - чистое удовольствие. Цена ошибки дизайна на порядок ниже в случае чистой программы.
5) Перечисленное, кстати, означает применимость подходов agile в случае функциональной чистоты. Я пока особых противопоказаний не вижу. Удивительное рядом, господа.
6) Организация разработки в случае смешанных чисто-грязных языков, таких как Эрланг или Немерле, вообще отдельная тема. Дело в том, что функциональная декомпозиция (в рамках моего определения) получается 100% отделена от декомпозиции по состоянию (то есть объектной модели). Это сложно понять, это надо объяснять отдельно и долго. Грубо говоря, можно спроектировать, написать, и отладить чистую часть независимо - первым этапом. А вторым этапом - натянуть на эту готовую систему объектную модель, в которые завернуть состояние. Это потрясающе классный подход - сначала проектируем ядро системы - наслаждаясь дешевым дизайном и рефакторингом, а потом нарезаем ее на объекты в разных позах (что элементарно - stateless код гораздо пригоднее к повторному использованию - в нем нет самой сильной связности - по состоянию) - радикально сокращая таким образом комбинаторную сложность дизайна (вместо X*Y получим X+Y, если вы понимаете о чем я).

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