Постепенная автоматизация рутинных задач

Как заавтоматизировать рутину, если времени катастрофически не хватает

11 Oct 2014


В 19 выпуске Perl-журнала PragmaticPerl опубликованы две мои статьи: Постепенная автоматизация рутинных задач, Постепенная автоматизация в примерах. Здесь находится дополняемая и улучшаемая версия первой из них.

Бывает так: есть несложная последовательность операций (в командной строке, разумеется), которую приходится время от времени повторять; хорошо бы написать нормальный скрипт, да все руки не доходят, и времени жалко, да и на самом деле не такое уж плевое дело – аккуратно запрограммировать эту рутину, а задача опять должна быть выполнена “вот прям сейчас же”, и ни минуты лишней нет. И повторяются и повторяются однотипные действия: mkdir, mysql_install_db, chown, vim my.cnf, service start, create database, grant all privileges ...

Знакомая картина? Как насчет что-нибудь изменить? Хочу рассказать про один метод как раз для таких случаев.

Итак, автоматизация рутины. На что обычно похожа такая задача?

  • задача не очень-то сложная, но какая-то неформализованная;
  • в задаче не видно хитрых алгоритмов и структур данных, зато есть внешние программы, интерактивное редактирование конфигов, обращения к другим серверам и т.п.;
  • этой разработки нет в квартальных планах вашей организации: либо это ваша личная задача, либо никто не заботится о планировании подобных вещей;
  • самый заинтересованный пользователь – вы; вы сам себе заказчик, product owner, project manager и QA engineer;
  • потенциальная аудитория пользователей невелика1;
  • время на разработку очень ограничено.

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

Так как главный получатель выгоды – вы сами, в ваших собственных интересах сделать первый прототип очень быстро, хотя бы и в ущерб полной функциональности: первый успех воодушевит на дальнейшие доработки, а хотя бы частичная автоматизация сэкономит вам время.

Сверхбыстрое прототипирование без сложных алгоритмов, вызов внешних программ, ssh, проверки существования файлов и т.п. – это область, в которой силен шелл (shell). Однако большие шелл-программы тяжелы в отладке и доработке, так что в какой-то момент стоит перейти от шелльного прототипа к Perl.

Вот и готов метод “постепенной автоматизации”: быстрый первый прототип на шелле, серия коротких самодостаточных доработок, своевременный переход на Perl. Пожалуй, это сработает и с другим интерпретируемым языком вроде Ruby или Python, но переход от шелла к универсальному языку самый гладкий именно с Perl, надо пользоваться этим.

В этой статье я опишу пошаговый рецепт постепенной автоматизации и свои соображения “почему это работает именно так”. В соседней статье – пример применения этого алгоритма.

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

Пошаговый алгоритм автоматизации рутинных операций

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

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

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

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

Итак, поехали. Дано: имеем задачу, которая в принципе решается в командной строке.

Шаг 1. Выполнить все команды вручную

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

Приобретения первого шага:

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

Шаг 2. Шелльный однострочник

Небольшая последовательность команд легко склеивается в шелльный однострочник. Выполнение действий одно за другим обеспечивается точкой с запятой ;, выполнение при условии успеха или неудачи предыдущей команды – && и ||, также полезны перенаправление вывода, пайпы и фоновое выполнение. Интерактивное редактирование обычно несложно заменяется на sed -i, генерация текста по шаблону – tpage, повторение однообразных действий реализуется циклами for и while.

Если у получившегося микро-скрипта нет параметров или они устроены очень просто – однострочник можно сделать шелльным алиасом и вызывать по имени.

Приобретения второго шага:

  • однострочник легко вытащить из истории команд по Ctrl-r и запустить снова (возможно, подправив некоторые параметры);
  • атомарность: последовательность действий запускается целиком, нет возможности отвлечься посреди выполнения;
  • однострочник легко скопировать и поделиться им с коллегами.

Если последовательнось действий оказывается длинее 5-6 операций или включает в себя сложное ветвление – можно после ручного выполнения сразу перейти к 3 шагу.

Шаг 3. Шелльный скрипт

Команды, выполняемые вручную в шелле, играючи превращаются в шелльный скрипт: копируем команды из истории и сохраняем их в отдельном файле.

Как и для однострочника, пригодятся &&, ||, sed -i и tpage. Кроме того, удобнее, чем в однострочнике, использовать циклы, переменные, временные файлы. Появляется возможность передавать в скрипт параметры (лучше ограничиться позиционными).

Приобретения третьего шага:

  • скрипт можно вызывать по короткому и понятному имени;
  • при запуске в скрипт можно передавать параметры.

Этот этап – прототипирование на шелле – стоит пройти, даже если сразу понятно, что в дальнейшем понадобится скрипт на Perl.

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

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

Шаг 4. Механическая трансляция в Perl-скрипт

Шелльный скрипт очень легко превратить в простецкий Perl-скрипт: вызовы внешних программ заменить на qx(), if-ы и while-ы заменить на Perl-овые, аргументы командной строки $1, $2 заменить на $ARGV[0], $ARGV[1] и т.д.

Сразу же стоит добавить use strict и use warnings.

При всей своей простоте этот шаг, пожалуй, самый неочевидный из всей последовательности: велик соблазн выкинуть шелльную версию и сразу начать писать “взрослый” Perl, с модулями, плагинами, валидацией и инкапсуляцией. Однако я очень (очень) рекомендую все-таки сдержать себя и начать с “наивного” Perl. Во-первых, у вас уже есть шелльный скрипт, отлаженный на реальных случаях. Глупо просто так терять все знания, собранные в нем. Во-вторых, переписать скрипт с нуля – дольше, чем переделать shell в Perl. А долгая разработка – это риск вообще ее не закончить.

Приобретения четвертого шага:

  • котроль очевидных глупых ошибок в коде (неинициализированные переменные и т.п.),
  • пригодный к любым улучшениям код на удобном языке.

Вообще-то желательно перейти от шелльного скрипта к Perl-овому достаточно рано, пока скрипт не вырос и не обзавелся сложными конструкциями. Но даже если вам в наследство достался уже достаточно большой и сложный шелльный скрипт, в котором понадобилось сделать существенные улучшения, поступите с ним как с прототипом с шага 2. Вместо того, чтобы с нуля переписывать скрипт на Perl (или другой язык), сделайте “механический” подстрочник. Это действительно просто и не требует сложных решений, и ничего из заковыристой логики не потеряется. А затем уже беритесь рефакторить получившийся “автоперевод”.

Шаг 5. Простой, но аккуратный Perl-скрипт

Начинаем улучшать бесхитростный Perl-скрипт, полученный на предыдущем шаге. Рекомендую действовать в таком порядке:

  1. use Getopt::Long; именованные параметры командной строки.
  2. По -h выдаем простейшую справку: самые типичные примеры использования. О подробном описании параметров пока можно не заботиться, все равно они еще поменяются.
  3. Базовая валидация параметров. Страховка от самых досадных ошибок.
  4. Очевидные умолчальные значения. Сложные случаи, когда непонятно, каково должно быть дефолтное поведение, смело оставляйте на потом.

Что получаем на этом шаге:

  • именованные опции и умолчальные значения делают использование скприпта более простым;
  • “пользовательская документация” позволяет легко разобраться с использованием скрипта не только автору, но и любому другому заинтересованному потребителю;
  • скрипт делает нужную работу и делает это предсказуемым, документированным образом, так что можно начинать рекомендовать свой скрипт коллегам.

Шаг 6. Постепенные улучшения Perl-скрипта

Перечислю очевидные улучшения и преимущества, которые они дают. Выбирайте то, что для вас важнее, дополняйте список своими любимыми рефакторингами.

  • вызовы внешних grep, sed, awk заменить на внутренние grep, map – скорость и переносимость;
  • использовать хеши и вообще сложные структуры данных – скорость, возможность реализовать сложное поведение;
  • деление кода на функции – удобство отладки и доработки;
  • использовать библиотеки вместо внешних программ – переносимость;
  • полноценная валидация параметров – надежность;
  • завершаться с правильным кодом – пригодность к использованию в автоматическом режиме;
  • подробная справка – удобство для интерактивного использования;
  • умные умолчальные значения в сложных случаях – удобство для интерактивного использования;
  • другие улучшения юзабилити (см. например статью про юзабилити CLI) – тоже удобство при интерактивном использовании;
  • обработка краевых случаев (race condition, недоступность ресурсов и т.п.) – надежность, пригодность к использованию в автоматическом режиме;
  • настройка через внешние конфигурационные файлы – гибкость, пригодность для широкого класса задач;
  • логирование – пригодность к использованию в автоматическом режиме, удобство отладки.

Особые случаи: администрирование

Если задача совсем админская (установка ПО, активная работа с удаленными серверами, однотипная обработка многих серверов) – рассмотрите фреймворки Rex, Fabric, Ansible.

Шаг 7. Большому кораблю – большое плавание

Если разработка небольшой автоматизации дошла до этого шага, значит, автоматизация оказалась (или стала) не такой уж небольшой ^_^

Стоит подумать над тем, чтобы выложить свою утилиту в open source и порекламировать в сообществе: если инструмент так хорошо работает для вас, значит, может пригодиться еще кому-нибудь.

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

И немного ‘философии’

Польза постепенности

О постепенной (инкрементальной, итеративной) разработке тоже много пишут (например, см. раздел “Succession” здесь, еще ссылки в конце статьи). Самое важное для наших задач:

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

Почему это хорошо работает:

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

Perl и shell – братья навек

Хочу еще написать о связке “прототипе на шелле” – “скрипт на Perl”. Довольно часто встречаются мнения “зачем мне шелл, у меня есть Perl” или наоборот “Perl? Да ну, зачем, на баше напишу”.

По-моему, надо использовать и тот, и другой – в тех ситуациях, где проявляются их сильные стороны:

  • Шелл весьма выразителен для обеспечения базового поведения: запуск внешних программ, условное выполнение, повторение однотипных команд. Поэтому простой скрипт на шелле можно написать быстрее, чем скрипт на Perl. Кроме того, шелл побуждает сконцентрироваться на главной функциональности. Так что полезно начинать разработку автоматизаций именно с простого шелльного скрипта.
  • Перейти от шелла к Perl надо, потому что Perl гораздо лучше подходит для умной валидации, проще для отладки и доработок, позволяет использовать сложные структуры данных; шелл же тяжел для рефакторингов и отладки, на нем сложно обеспечить бескомпромиссно-удобное поведение.
  • Переходить от шелла к Perl стоит через механический “подстрочный” перевод, потому что с одной стороны его можно сделать очень быстро и просто, а с другой – он обеспечивает сохранность уже реализованной логики работы.

И если вам досталось поддерживать и дорабатывать большой и сложный шелльный скрипт – рассматривайте его как готовый прототип, “протранслируйте” на Perl и рефакторите дальше.

Заключение

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

И напоследок пара слов о происхождении “постепенной автоматизации”. Простота манипуляций с файлами и процессами, отсутствие границы между интерактивным шеллом и интерпретатором шелльных скриптов, бесшовная интеграция шелла и Perl-а – все это уже давно встроено в Unix и Unix-подобные системы, шеллы и Perl соответственно, нам остается просто использовать эти возможности для накапливаемых пошаговых улучшений своих программ. Так что большое спасибо Кену, Брайану, Стивену и Ларри2 ^_^

Удачных автоматизаций!

Ссылки

О вреде прерываний для программистов:

О пользе постепенности:

Фреймворки для легковесной автоматизации администрирования:

Статья про юзабилити CLI

  1. Конечно, бывают исключения, см. описание 7 шага.

  2. Естественно, я имею в виду Кена Томпсона, Брайана Кернигана, Стивена Борна и Ларри Уолла. Благодаря им у нас есть вся эта классная юниксовая среда, и это здорово.