Rauf Aliev (rauf) wrote,
Rauf Aliev
rauf

Искусство программирования под Unix (и не только). Часть первая, «правило модульности»

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

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

Есть ряд книг, которые, на мой взгляд, являются своеобразными «библиями» для тех, кто решил связать свое будущее с разработкой ПО. С одной из них я хотел бы начать цикл статей. Это книга Эрика Рейнмонда, «Искусство программирования под Unix». Я бы рекомендовал эту книгу не только тем, кто выбрал для себя открытые операционные системы. В основе лежит довольно универсальная философия, пригодная абсолютно всем, связавшим свою профессию с программированием.

Эрик Реймонд выделяет 17 правил этой «философии». Я буду посвящать по одной заметке на каждое правило. Я постараюсь изложить эти концепции в максимально понятной, упрощенной и популярной форме, насколько это будет возможно.



Начнем с самого первого правила — Правила модульности. Оно звучит так: «Простые блоки связывайте друг с другом ясными и понятными интерфейсами» (Rule of Modularity: Write simple parts connected by clean interfaces).

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

Два примера из реальной жизни: 1) классический домашний ПК из системного блока и монитора 2) ноутбук. К примеру, на обоих погас экран. Причина может быть в чем угодно, как мы понимаем. Но в случае домашнего ПК легко проверить первое предположение: а работает ли монитор на другом компьютере? если не работает — причина в мониторе. Если да — причина в компьютере. Далее можно попробовать видеокарту на другом компьютере - и сразу понять, в ней дело или нет. Аналогично дело обстоит с клавиатурой, мышью..

В случае ноутбука, все сложнее. Для простого обывателя у него есть два состояния: либо работает, либо нет. Если что-то вышло из строя, найти причину не так-то и просто. Причиной отказа экрана может быть как программный сбой, так и механическое повреждение. Да, для мастера это разные компоненты, но для вас — один ноутбук. Следовательно, определить неисправность выйдет дороже, чем в предыдущем примере. Придется, по крайней мере, обращаться в сервисную мастерскую за первичной диагностикой. В первом же случае вы можете определить неисправность самостоятельно и в случае выхода из строя монитора везти в сервис только его.

То же и с разработкой ПО. Каждый компонент должен выполнять одну, очень простую задачу. И понятным образом интегрироваться с другими такими же компонентами. Чем более универсальным будет такой механизм, чем лучше.

Из нашей практики: мы сейчас разрабатываем сервис подбора аксессуаров и аналогов для товаров Связного. Сама по себе это довольно сложная система, но если ее рассматривать как отдельный компонент, решение сразу приобретает красивый и законченный вид. Мы будем ее внедрять в колл-центре, в ПО электронных каталогов, на сайте Связного, в систему рассылок upsale. И чем яснее и понятнее мы сделаем ее внешние интерфейсы, чем проще будет ее внедрять в новую систему, о которой, может быть, мы еще даже не задумывались.

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

Когда-то давно, после института, я писал систему проектирования воздуховодов для рязанской компании Эковент. Там можно было создавать произвольную модель воздуховодов - труб с разветвлениями во всех трех измерениях. В какой-то момент пришлось полностью переделать всю программную часть, потому что она стала просто слишком сложной. Отдельные компоненты, отвечающие за изменение конструкции — такие как, например, добавление элемента, имели текстовый интерфейс в виде текстовых команд вида «add block width=100 height=100 depth=10». Разумеется, пользователя никто не заставлял набирать такие команды, для этого был отдельный графический оконный интерфейс, где он вводил ширину, высоту, глубину, но далее форма все равно преобразовывалась в команду. Это позволяло очень просто отлаживать сложные ситуации, когда нужно было создать сотню объектов и выполнить их отрисовку.

image

В UNIX-мире уже давно изобретен один унифицированный интерфейс для командной строки, называется он “каналом” (pipe, |). Через каналы можно очень просто связывать разные программы, выполняющие одну, очень простую задачу. Например, есть утилитка, которая считает число строк, символов (wc), и утилитка, которая показывает на экран содержимое файла (cat). Поставив их друг за другом, мы можем получить, например, число строк в файле (cat file | wc -l). И таких “утилиток” уже разработано очень много. Возможность комбинировать их «на лету» помогает решить довольно сложные задачи буквально за секунды.

В итоге, несколько простых советов — мои следствия правила модульности:

  • Каждый блок, программа, модуль должны делать только одну вещь и делать ее хорошо. Если цель, назначение блока начинает «размазываться», стоит задумываться о ее разделении на части.

  • Если есть возможность, выбирайте интерфейсы из числа промышленных стандартов (XML, HTTP, RPC, CSV, JSON) и ни в коем случае не выходите за их рамки.

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

  • Если можно упростить функционал блока, разбив его на более мелкие блоки, с более четкой выполняемой задачей, со стандартными интерфейсами, без заметного ущерба в производительности — разбивайте. Даже, если есть ущерб в производительности — тщательно продумайте, иногда им стоит жертвовать. «Железо» сейчас стоит дешевле.


Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments