понедельник, 5 июля 2010 г.

Stop rolling your own CSV parser

Your own CSV parser

Вот именно такую заметку я нашел, когда пытался подыскать подходящую версию CSV парсера для нашего приложения, - Stop Rolling Your Own CSV Parser.

Каждое предложение было до боли знакомо - после безуспешных попыток привести код в приемлемое состояние, захотелось все же воспользоваться готовым решением. Читая заметку, я был согласен с автором на 100%.

Дальнейший анализ все же показал, что не все так просто.

Во-первых, предлагаемый в заметке FileHelpers нам не подошел. Во-вторых, другие человеческие варианты было сложно найти (интернет засоряют написанные на коленке варианты). В-третьих, вот этот комментарий помог понять, что сложность CSV парсера слегка преувеличена:
I'm sorry, I don't get it. What's so hard about writing a CSV parser? It's just a finite state machine (one state variable + one large switch statement). And why would one want to use regular expressions here?
Я уже писал об этом (по ссылке чуть-чуть UML, кода, паттернов), на самом деле CSV парсер - это не сложно, зато вы получаете именно тот интерфейс и именно ту функциональность, которые нужны для вашего приложения. К примеру, в нашем случае это потоковое чтение данных, поддержка gzip (сжатых файлов), работа с большими файлами (тестировалось на файлах объемом до нескольких сотен мегабайт).

На этот раз, правда, по просьбам читателей код теперь на github, так что CSV parser теперь еще можно и скачать, а можно и что-нибудь поменять в нем прямо на github-e. Как видите, все очень просто. Парсер не универсален, но он успешно парсил реальные файлы - именно те, которые нам нужно было парсить.

Свое vs. Чужое

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

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

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

На что тратиться основное время при использовании готовых решений?

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

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

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

Исправление ошибок Ошибки бывают, иногда их много. Иногда вы можете сами их исправить (в случае open source и со значительными затратами времени на погружение), иногда нет. Иногда их исправляют быстро, иногда вам приходится жить с ними, наслаивая очередной костыль на без того сложное решение. Если, конечно, ошибку можно обойти. Отдельно стоит упомянуть случаи, когда обработка ошибок (в правильном понимании) отсутствует как класс, и вы получаете абсолютно загадочные сообщения наподобие ThisNeverHappensError.

Проблема в квадрате И что также стоит принять во внимание - один из приведенных выше факторов может вырасти до проблемы просто неприличных размеров:
  1. Готовое решение более чем на 80% кастомизировано (я такое видел)
  2. Изучение готового решения не заканчивается никогда, новички просто не могут осилить его сложность
  3. Оно просто не справляется со своими обязанностями, там слишком много ошибок
Некоторые говорят, что вот решение активно поддерживается - на носу следующая версия, они делают работу за нас. Фуух.. На самом деле - это проблема. Только вы отмучались с предыдущей, как уже нужно мигрировать на следующую :) И исправлять баги уже следующей версии.

Чем же привлекательно свое решение?

У вас есть шанс построить максимально простое решение, которое:
  • Легко понимать
  • Легко поддерживать
В нем отсутствуют понятия, не нужные для вашего приложения, но требующие их изучения. Максимально простое решение требует минимальных затрат на его изучение, а как известно работа программиста состоит лишь на 20% процентов из написания нового кода и на 80% из работы с уже существующим.

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

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

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

Грязные детали

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

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

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

Мои мысли результат не только моего опыта, у меня в запасе есть пара ярких примеров обоснования описанной концепции (Подчеркивая мысль, указанную в Грязных деталях выше - Ayende и Jeremy пришлось сначала наступить на грабли и только потом они уже написали свое. К сожалению, такова реальность):

Ayende Rahien (разработчик Rhino Mocks, NHibernate Profiler, etc.) настолько возненавидел SQL Server Integation Services, что решил написать свой ETL framework, в чем и преуспел - Rhino ETL.

Jeremy D. Miller пишет о том, как построить свой, родной Composite UI Application Block - How to build your own CAB, объясняет зачем это делать, почему это сравнимо по скорости с использованием CAB и рассказывает как это сделать.

Disclaimer

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

1 комментарий: