28 октября 2008 г.
S?XBuilder
9 августа 2008 г.
ООП как создание предприятия
Недавно я писал об опасности излишней "объектаризации" (или объективизации?..) и провел аналогию между написанием программы и управлением предприятием. Сейчас давайте рассмотрим эту аналогию поподробнее.
Прежде всего, условимся считать объектом рабочего либо механизм. В большинстве
случаев особой разницы нет, потому что в современном производстве мало задач,
с которыми бы не справился робот конечной сложности. Какую-нибудь узкоспециализированную
утилиту, типа юниксовой ls можно считать монолитным инструментом, упрощающим
отдельную задачу. И когда мы пишем "ls | sort", мы просто последовательно
применяем два разных инструмента, как последовательно применяем зубило и напильник
при обработке куска металла.
Пока мы можем без труда справиться с некоторой задачей в одиночку, не имеет смысла
разделять эту задачу на объекты. Так, можно без посторонней помощи, последовательным
применением пилы, рубанка, наждачной шкурки и молотка с гвоздями можно смастерить,
скажем, табуретку. Но мы хотим не просто табуретку, мы хотим красивую
табуретку, которая не будет портить наш интерьер своей угловатостью, а напротив,
будет радовать глаз. И появляется идея пригласить резчика и художника, чтобы они
украсили наше произведение. (И что-то мне это уже напоминает: табуретка с двумя
декораторами...)
Потом мы вдруг осознаем, что на наши резные-расписные табуретки есть спрос - и
вместе с теми же резчиком и художником открываем фирму по производству табуреток.
И дела идут хорошо, так что мы решаем расширить ассортимент продукции и в
дополнение к табуреткам производить резные-расписные столики. Но у главного и
единственного нашего плотника почему-то не выходит производство столов, хотя стол
с виду и похож на табуретку. Так что мы нанимаем человека, умеющего делать столы -
экземпляры другого подкласса класса мебель. И вот у нас уже полноценная
мебельная мануфактура (коли уж производство нисколько не автоматизировано, но
разделение труда налицо). Долго ли, коротко ли, но вот мы уже владеем небольшим
заводом по производству резной да расписной мебели. Завод состоит из трех цехов -
сборочного, художественного и цеха художественной резьбы.
Но мы кое-что забыли - транспортный отдел. Ведь завод производит много продукции,
а значит требует поставок больших объемов сырья. И поэтому мы заключили контракт
с транспортной фирмой, которая раз в неделю на больших грузовиках будет привозить
нам доски и краску. А также арендуем склад, чтобы сохранять готовую продукцию до
приезда заказчика. А поскольку дела идут хорошо, появляются деньги на механизацию
производства - как минимум ручные рубанки заменить электрическими. И вот у нас уже
фабрика: рабочих немало, да и труд не только ручной.
А так как наша серо-буро-малиновая раскраска табуреток стала настоящим хитом среди сельских жителей, прогрессивный директор близлежащего молокозавода возжелал заключить с нами контракт на покраску коровника, дабы повысить производительность буренок. Но мы же не занимаемся покраской помещений, мы делаем мебель! Однако предложенный увесистый чемодан наличности убеждает нас в обратном... Так что пора расширять художественный цех и проводить среди художников инструктаж по технике безопасности при общении с коровами. И вообще, почему бы не принимать подобные заказы от всех желающих - спрос-то есть...
А теперь посмотрим на ситуацию глобально. Кто-то покупает продукцию нашего завода, доставляет ее конечному потребителю, распродает по более высокой цене. А кто-то пользуется производственной можностью нашего художественного цеха, давая свои изделия нам на раскраску. В свою очередь, и наша фирма пользуется внешними ресурсами - арендованными складами и услугами транспортной компании. И мы видим, как наша, уже не такая и простая, система оказывается тесно вплетенной в окружение, точно так же, как типичная большая программная система никогда не бывает полностью автономной. Постойте, а разве мы не программную систему проектировали только что?..
6 августа 2008 г.
ООП навязывает управленческий образ мышления
... причем в худшем его проявлении - в стремлении переложить всю работу на
подчиненных и нежелании делать ничего самому. Беда в том, что в результате
самому работать приходится еще больше.
Например, недавно мне пришлось писать модуль вычисления выражений (короче,
калькулятор, считающий "a + 2px * sqrt(2) - 3.0em") на C#. В этом языке нет
ничего, кроме объектов. Поэтому я, недолго думая, создал классы Лексема,
Выражение, Оператор и т. п. И оказался в глубокой задумчивости:
а какие же "приказы" им раздать, чтобы они сами все сделали?
Здесь полезно обратиться к аналогии между ООП и управлением предприятием, где
каждый объект - это работник, а программист (ну или разработчик) олицетворяет
высшее руководство. (Можно еще добавить, что операционная система - это
государство, а аппаратура - это сама природа).
Итак, как же мы бы организовали вычисление выражения на предприятии? В нашем изначальном "объектном" варианте задействована толпа служащих - числа, операторы, функции, единицы измерения и т. д. И я не завидую тому менеджеру, которому предстоит давать указания всем этим людям (читай - написать программу взаимодействия всем этим объектам). А как бы вы поступили на месте этого несчастного менеджера? Моим ответом было "написать значение на лбу у каждого, выстроить в ряд и посчитать все самому". Потому что это несравненно проще, чем объяснять каждому правила поведения в нашем выдуманном мире. Так зачем же нам живые люди (объекты)? Нам и мертвые сгодятся! (пассивные структуры). Или, еще лучше, собрать механизм, который, в отличие от мертвых людей, не разлагается. (Написать единый модуль в структурном стиле). Ну или нанять квалифицированного специалиста (купить готовую библиотеку, решающую нашу задачу).
Мораль сей басни в том, что при повсеместном применении объектно-ориентированного
подхода сложность программы не уменьшается, а только увеличивается, поскольку
от менеджера (программиста) требуется координация действий огромного количества
низкоквалифицированного персонала (простых объектов). Возникает бюрократическая
лестница невероятной длины, негативно сказывающаяся как на ясности, так и на
производительности программной системы - работники тратят бОльшую часть времени
на общение между собой, пусть и по делу.
1 августа 2008 г.
Проект: SmartEdit Control
Недавно мне пришлось работать с замечательным графическим редактором
CorelDraw. Это не просто чрезвычайно мощный инструмент для работы с
векторной графикой, в том числе и инженерной, но и просто очень
качественно выполненный программный продукт. Об этом говорит следующая
маленькая и, я подозреваю, мало кому известная деталь: почти все
текстовые поля для ввода чисел (длина, координаты, ...) понимают
арифметические выражения и единицы измерения. То есть, если вы хотите
сдвинуть объект на 5 мм, вы щелкаете по полю и дописываете "+ 5mm".
И все работает! Несмотря на то, что исходное значение дано в дюймах.
В Photoshop, насколько я знаю, тоже нечто подобное реализовано.
А вот в Microsoft Office Word, когда нужно сделать ровно двадцатимиллиметровый
текст, приходится отвлекаться от работы и вычислять "20.0 / 25.4 * 72".
Мелочь, конечно, а неприятно. Именно из таких маленьких
разочарований и складывается плохое настроение. Компьютеры давно уже существенно
более мощные, чем необходимо, но почему-то калькулятор под рукой держать
все еще приходится ("покажите мне удобную программу-калькулятор!").
Это я к тому, что подобная функциональность нужна везде, где от
пользователя требуется ввод чисел, особенно имеющих размерность.
Так же как и каждый второй список нуждается в
естественной, а не алфавитной, сортировке.
Так что сделаю-ка я .NET-компонент, совмещающий удобство TextField-а для
программиста с удобством для пользователя.
А пока я ухожу в гугл. Вдруг есть уже такое в этом мире?
24 июля 2008 г.
История одного проекта 3
Не пытайтесь повторить это самостоятельно!
...потому что лучше пару раз почесать затылок, склонившись над листом бумаги, и сразу увидеть Корень Зла. Иначе он обовьет ваше несчастное тело быстрее, чем вы что-то сможете сделать. И вам придется долго и упорно грызть это корневище, чтобы, наконец, выбратся из этого персонального ада.
А корень этот на вкус напоминает кусок гнилого кода.. Чем, собственно, он и является.
Хотя мне, наверное, следовало бы предупредить вас в
самом начале, потому что сейчас речь пойдет о банальнейших
проблемах производительности.
Итак, в прошлый раз
мы построили интерфейс, представляющий текстовое поле в виде бесконечного
прямоугольного массива символов. Так что можно идти переопределять процедуру
прорисовки окна. Так, а раз достаточно просто перерисовать каждый отдельный
символ, то почему бы при перерисовке просто не вызвать m*n раз процедуру,
рисующую один-единственный символ? А теперь объявим эту процедуру абстрактной.
Отлично, теперь мы имеем довольно общий шаблон редактора, который всего лишь
надо параметризовать процедурой прорисовки. То есть теперь, в принципе,
есть на что посмотреть! Да, мерзко писать код, не имея возможности запустить
его и посмотреть, что получится. И мы порождаем подкласс, назовем его
ColorApxEd, который просто рисует цветные буковки на цветном фоне:
Отлично, все работает! Но так не бывает. Этот скриншот был сделан намного
позже. Вначале, конечно же, ничего не работало. Но мало-помалу смертельные
баги были раздавлены, и на свет явился очень мерзкий и живучий таракан.
Дело в том, что (1) при нажатии любой клавиши перерисовывалось все окно, и
(2) при перерисовке текст мелькал. "Приехали".
Что ж, лучше поздно, чем никогда, наконец, взяться за ум. Тут-то я и узнал, что
же такое двойная буферизация. А также понял, что прорисовку необходимо
выполнять с умом - ровно то, что нужно, и ничего более. Еще одна неделя работы.
Двойную буферизацию я прикрутил быстро, и все потихоньку начало работать.
И можно было, наконец, писать градиентную подсветку.
Вот что у меня получилось. Почему-то это было не то, что я хотел. К тому же,
оно ужасно тормозило. Поэтому я решил применить тяжелую артиллерию - кэш
фрагментов градиента. Таким образом примитивнейший, неоптимизированный расчет
градиента выполнялся только единожды - при первом появлении данного куска
градиента. На этом шаге у меня, как всегда, пропало желание доводить до ума
почти свершенное, и я решил поскорее сделать простейший редактор на основе
только что созданного компонента. Небезосновательно полагая, что нигде более
я его и не применю.
Тут-то все и умерло, ибо я уехал учиться.
Но через два года, этим летом, я решил покопаться в старых наработках.
И наткнулся на APXN. И ужасно удивился, что "почти всё почти работает".
К тому же, на новом компьютере скорость перерисовки была вполне приемлемой.
Кстати, новый компьютер с первого взгляда и помедленнее должен быть -
1600 MHz против старых 2400. Вот что значит Intel Core Duo, даже и не "2", да
и программа многопоточность не использует.
И я просто занялся шлифовкой того, что было. Решил сделать "конфетку" из
"будто бы и непригодного материала". И если раньше я пытался в нем что-то
написать, и инстинктивно пытался Ctrl+прыгать по тексту, а также тыкать Ctrl+Del,
и оно не работало, и я тут же проклинал эту гадость и возвращался к Notepad++,
то после того, как я реализовал полный спектр клавиатурных команд, стало можно
с ним работать! Вроде мелочь, а ведь именно из мелочей складывается настроение.
А еще я поленился прикручивать поддержку одновременного открытия нескольких
файлов, но заменил это кое-чем, не встречающимся в большинстве "блокнотов" -
программа запоминает состояние (позицию прокрутки, каретку, закладки...)
для каждого файла. Заодно вынуждает почаще сохраняться (в пику проклятому n++,
не раз терявшему бесценные байты при неожиданной смерти системы :)
Теперь я уверовал в истинную силу юзабилити, когда пара строк кода просто делает
работу возможной. А еще пара строк - приятной.
P. S. Только что прочитал одну отвратительно написанную книжечку
по методам программирования, и с ужасом понял, что сам пишу так же.
Так что не обращайте внимания на эти три небольшие заметки.
Впрочем, судите строго.
21 июля 2008 г.
История одного проекта 2
Итак, ранее я познакомил вас со своей дурной идеей,
пришло время перейти к тому, как все это было реализовано.
Как я уже говорил, раз уж я решил ограничится моноширинными шрифтами,
то вся рабочая область редактора может быть представлена прямоугольным
двумерным массивом символов. Такой взгляд на текст чрезвычайно удобен
с точки зрения прорисовки. Действительно, определили, какая часть этого
прямоугольника видна на экране - и перерисовываем подряд все клетки.
С другой стороны, мы имеем дело с текстом, а при работе с текстом
крайне удобно иметь, кроме адресации "строка-столбец", еще и последовательную
адресацию. К тому же, хранить текст в прямоугольном массиве крайне
нерационально.
Поскольку все мои мысли были заняты именно графикой, то я решил хранить
текст в виде списка строк. На языке Си это можно представить так:
struct ApxString {
char *chars; // строка символов
short *styles; // строка стилей символов
size_t size; // выделенная длина строк
size_t length; // длина текста в строке
} buffer[]; // сам буфер в виде масива строк
size_t bufsize; // размер буфера
Возможно, именно в этом заключалась моя главная ошибка. Ведь для работы с
этой структурой совершенно невозможно было использовать никакие библиотечные
функции, хотя бы потому, что необходимо было параллельно каждой манипуляции
со строкой символов делать то же со строкой стилей (которые еще short
по давно забытой причине, хотя char'а бы вполне хватило).Первым делом я решил абстрагироваться от тонкостей управления памятью. Это было достигнуто введением свойств CharAt[Row, Col], StyleAt[Row, Col] и RowLengths[Row, Col]>, которые, соответственно, позволяют прочитать или установить символ, стиль или использованную длину строки. Причем эти свойства ведут себя корректно при любых значениях параметров, автоматически перераспределяя память, если необходимо, и игнорируя отрицательные значения. Если символ не определен, то возвращается #0, если стиль не определен - возвращается стиль конца строки. Полная иллюзия бесконечного прямоугольного массива символов и стилей. Конечно, во всякой нетривиальной абстракции есть дыры. В данном случае дыра - это ограниченность памяти. Но я решил пренебречь этим ограничением, поскольку мегабайт текста в этой структуре будет занимать порядка 10MB: по 3 байта на символ, плюс 8 байт на длины, плюс запас на округление длины каждой строки вверх, скажем, до 20 символов. Так что все действительно не так уж страшно.
Осталось построить интерфейс для доступа тексту как к одной строке. Для этого я просто написал функции преобразования индексов - из двумерных в одномерный и обратно. Теперь для доступа к n-му символу нужно написать что-то вроде CharAt[IndexToPos(n)].
Выглядит неплохо, не так ли? Нет! Здесь допущена принципиальная ошибка:
интерфейс удобен для самого компонента, но не для тех кто им пользуется.
Никому не интересно внутреннее представление текста. Им нужен сам текст.
И возможность работы с этим текстом как с обычной строкой. Поэтому более
правильным, как мне сейчас кажется, было бы хранение текста в виде непрерывного
массива с линейной адресацией, с каким-нибудь умным механизмом
отображения символов на прямоугольную решетку.
В широко известном компоненте Scintilla
применяется именно список строк, видимо, по соображениям производительности.
Поверх рассмотренной абстракции лежит слой функций для манипуляции текстом -
вставка и удаление произвольных кусков текста и целых строк.
Также этот слой ответственен за поддержку отмены, что само по себе
заслуживает упоминания, поскольку я совершенно забыл об этой функции, пока
не сделал первую рабочую версию редактора. Внедрение какого-то подобия
отмены стало настоящим кошмаром. Признаюсь, она и сейчас работает весьма
криво. Архитектурные недоработки даром не проходят!
Теперь, когда появились высокоуровневые средства манипуляции текстом,
можно строить слой обработки действий пользователя. Фактически вся работа
делается в обработчиках KeyDown и KeyPress, первый из которых вызывается
для всех клавиш, а второй - для символьных и некоторых командных (например,
я очень удивился, когда узнал, что Ctrl+A - на самом деле символ с кодом 1).
Вот тут писать пришлось много. Вот лишь неполный список того,
на что должно реагировать уважающее себя текстовое поле:
- символьные клавиши
- Enter
- Delete и Backspace
- PageUp/PageDown
- Стрелки
- Ctrl+Delete и Ctrl+Backspace
- Ctrl+Стрелки
А как же градиентная прорисовка, из-за которой, собственно, все и затевалось? Об этом чуть позже.
19 июля 2008 г.
История одного проекта 1
Хочу поделиться с вами частью своего небольшого опыта разработки ПО.
А именно, расскажу вам историю реализации одной бредовой идеи,
посетившей меня в конце 11 класса, когда я еще не осознавал,
насколько это сложно - создавать законченный программный продукт.
(Сам "продукт" в скором времени будет доступен в открытых исходниках).
В то время я немного увлекался компьютерной графикой, а также потихоньку
делал программку для защиты на экзамене по информатике (тогда я и начал
понимать, что такое законченный программный продукт).
Программка та не представляет никакого интереса даже для меня, важны те
условия, в которых я ее создавал - Borland Delphi 7 с ее весьма
своеобразным редактором кода. Я очень долго ковырялся в цветах
подсветки синтаксиса и как-то раз мне в голову пришла идея.
"А почему бы не сделать такую подсветку, где цвет фона текста меняется
не резко, а плавно? Ведь на это же, наверное, намного приятней смотреть!"
И я решил сделать компонент, который предоставлял бы такую подсветку.
И тут же возникла первая проблема. Готового текстового редактора,
который позволил бы управлять прорисовкой своего фона, у меня не было.
Как присобачить подсветку синтаксиса к обычному текстовому полю, я тогда еще не знал
(впрочем, и сейчас я знаю, что это нехорошо). А в интернете поискать не догадался,
верите ли.
Так что я принял решение написать редактор с нуля. С абсолютного делфионского нуля,
точнее, с TCustomControl'а, который всего лишь предоставляет окно и поверхность
для рисования. Естественно, образцом для подражания был пресловутый делфионский
редактор.
Что ж, дурному коту, как и бешеной собаке, семь верст не крюк - я принялся за работу.
Задумывались ли вы когда-нибудь, как устроено обыкновенное текстовое поле, такое
как в Блокноте? Скорее всего да, поскольку если вас не отпугнуло начало моего рассказа,
то, наверное, вы знакомы с компьютером если не на уровне "физиолога", то на уровне
"терапевта" уж точно. И я рад за вас, если этот "простенький механизм" для вас
действительно простенький. Мне он таковым не кажется до сих пор.
А мне предстояло реализовать нечто подобное самому, от начала и до конца.
Странно, но я не испугался. "Меньше знаешь - крепче спишь". Ведь и Линус Торвальдс,
как он пишет в своей книге "Just For Fun", признается, что если бы он заранее
знал, сколько работы ему предстоит проделать, врядли бы взялся за свой проект.
Впрочем, у незнания всегда есть и негативные последствия, причем, к сожалению,
они обычно преобладают.
Но вернемся к нашим баранам.
Целью было создание VCL-компонента, аналогичного делфионскому редактору кода,
с подсветкой синтаксиса, свободной кареткой и т.д.,
отличающегося "революционным" графическим дизайном. А также,
разумеется, создание простенького редактора на основе этого компонента.
Целью не являлось создание редактора общего назначения, а лишь редактора кода.
Целевая аудитория - все, кому надоело писать ad hoc программки в "блокноте",
и кто, к тому же, любит вещицы с переподвыпердовертом. (Такие как я :)
Раз уж я делаю редактор кода, то можно ограничиться моноширинными шрифтами.
А следовательно - просто разбить компонент на прямоугольные ячейки одинакового
размера. Это во много раз упрощает задачу.
Остается лишь реализовать "текстовый" интерфейс к этой прямоугольной решетке и
должным образом редактировать решетку при нажатии клавиш. Всё!
А вот о том, что же я сделал, и почему это неправильно, я напишу завтра.