четверг, 9 августа 2012 г.

Сложности ручного рефакторинга

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

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

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

В Ruby есть системы автоматизированного тестирования. Но с ними я пока не сошёлся характерами.  Самая удобная - это Rspec. А самая забавная - cucumber.

Конечно, удобство Emacs+RSense+RAutocomplete очень трудно переоценить. Он подставляет любое имя, которое хотя бы раз увидел. Привыкнув к его странной клавиатурной кодировке начинаешь работать быстрее.

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

Поэтому циклы обратной связи должны быть как можно более короткими.

WPF и IronRuby

Создавая интерфейс программы для клиента на IronRuby столкнулся с интересной проблемой.

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

Отладка SharpDevelop не очень помогла. Пошагово в событие даже не заходит, а вывод в лог не даёт нормального отображения.

Пришлось открывать консоль ir и лишь там выяснилось, что есть две структуры WindowState для WPF и FormWindowState для WinForms. И из-за этой мелочи целый день ковырялся в коде, пытаясь понять, что же мешает разворачивать форму.

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

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

Ruby и таблицы решений


Сегодня создавал интерфейс для редактора настроек для сканера данных котла.

    Так как СКАДА поддерживает древовидные параметры, то нужен был редактор OPC параметров. Для каждого объекта опроса есть каналы. В каждом канале есть параметры.

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

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

  Саму таблицу решений вида:



можно было задать простым хешем. Но такое решение не масштабируемо, да и требует много спагетти-кода. Поэтому было решено создать класс с DSL для описания таблиц решения.

Воспользовался тем, что в рубине можно задавать массивы слов через %w| | и символы как is_editing: создал код:

question :is_editing do
   is_editing?
end

action :enable_add_btn do
   @add_btn.enable
end

condition is_editing:          %w| + - |
decision enable_add_btn:  %w| -  x |

Так как был использован WxRuby, то пришлось писать решения для запрешения.

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

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

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


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







вторник, 7 августа 2012 г.

Новые задачи и ловушки



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

     Но вот ведь проблема: код, который активирует только эти кнопки легко написать за 30 минут. Но расширить его не просто, да и отладить сложно.

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

Пришлось думать о том, чтобы создать свою реализацию. Но как инициализировать таблицу решений? Склоняюсь к двум способам: DSL или базовые конструкции Ruby.

Чистый код против скорости работы



Когда стоит задача и клиент хочет, чтобы было уже вчера - тогда не до чистоты. Нужно радовать клиента.

Так и сегодня потребовалось оптимизировать скорость отчёта.
В старой версии отчёт опрашивал базу для каждого счётчика два раза. При малом количестве счетчиков работа отчёта была приемлема - порядка 10-20 секунд.

Но клиенты добавили более четырехсот счётчиков и стало трудно дождаться его выполнения. Пришлось пожертвовать памятью и кэшировать запрос "кучкой". Используя Ruby это было сделано за 5 часов работы (с отладкой) и после применения кэша в памяти скорость выполнения сократилась до 2 секунд.

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

 Произошло создание двух сущностей, выполняющих одну и ту же задачу. Это один из "запахов кода". Но задача была выполнена быстро, и клиент остался доволен.

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

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

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

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

Откуда взять это время на тестирование и сам рефакторинг, спросите вы? У программистов всегда есть задачи, но бывают дни, когда скорость работы мала, или просто "башка не варит".

Вот в эти дни и можно привести в чистоту кодовую базу. Также, если у вас есть время после задачи, то не спешите браться за новую. Немножко прибраться - потом самим же будет удобнее добавлять функционал.

понедельник, 6 августа 2012 г.

Ruby как новая любовь




После полугода изучения Ruby и Rails я попробовал создать приложение. И, о - чудо: приложение было создано за 3 недели.

Подобное приложение я обычно создаю за 2-3 месяца, а тут такое ускорение. Что-то в этом языке есть такое, что привлекает. Его видимая простота - лишь завеса. Внутри он очень сложен и расширяем.

Писать на мета уровне очень интересно. Получаешь ни с чем несравненное удовольствие, от того, что рутину по созданию методов ORM он взял на себя через ActiveRecord или Sequel.

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

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

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