Скука и рутина это зло

run{}

Как организовать «парсинг» внешних ресурсов при помощи DSL

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

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

Одна из составных частей моего проекта – чтение и распознавание текста с внешних ресурсов (сайтов, проще говоря).

Переписав три раза код, наконец получил то что мне нравится. В этом посте вкратце расскажу как я это вижу.

Задача

Итак, мои требования таковы:

  1. Чтение с внешних ресурсов. Оно может происходить по-разному, в разных форматах и протоколах. Может оказаться так, что данные будут предоставляться после авторизации, через протокол https или вообще в виде локально доступного файла. (Это может быть .xls к примеру) Надо предусмотреть возможность добавления новых источников.

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

  3. Удобное масштабирование. Число источников может вырасти очень стремительно. Нужно уметь раскидывать задачи и данные на несколько серверов.

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

  5. Возможность производить обработку на разных платформах. К примеру, я так и не нашёл подходящего гема для работы с русской морфологией, а вот для python есть подходящий пакет. Хочу иметь возможность загрузить страницу на Ruby, а разбить и распознать – на Python.

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

Идея

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

В AngularJS должно быть больше структуры

Angular.js – каркас для создания сложной клиентской части, как правило SPA (Single Page Application). В этом каркасе есть несколько сущностей, вроде controllers, directives, services, factories, provides, filters. С их помощью можно разбить код на независимые части.

Я освоил этот каркас довольно быстро – где-то около недели, и смотрю на него глазами опытного Rails-разработчика. И многое мне не нравится.

Rails очень хорош своей навязываемой структурой приложения – любой новый разработчик пришедший в проект уже приблизительно знает где какой код искать, где что писать. Это и есть Convention over Configuration.

В Angular есть необходимые абстракции, но нет convention. Правила именования директорий, файлов, сервисов это важно, но не это меня сейчас беспокоит. Есть множество вопросов на которые официальная документация ответов не даёт.

Где хранить состояние?

В Angular есть такая сущность как scope. Это пространство имён которым владеет controller либо directive, которое может иметь предков (parent scope). Именно через это проиходит two way binding, связь представления (view) и controller/directive.

Кроме того, есть такая вещь как $rootScope, которая выступает в роли глобальной области видимости, доступной из всех views, controllers, directives, factories.

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

Вторая мысль – использовать service. Сервис – это набор методов, которые владеют общим состоянием. Однако это состояние не глобально, для каждого контроллера и директивы при использовании создаётся отдельный экземпляр сервиса.

Я решил эту проблему с помощью пары событий между контроллерами. Первый контроллер запрашивает данные second:get, а второй отправляет second:get:error либо second:get:success. Очень не хватает каналов из Go.

Есть ещё ряд проблем, для которых я пока не придумал грамотного решения.

Итоги службы

Обычно программисты в армии не служат. А я вот сходил.

Итак, кончилась моя служба, я наконец-то вернулся домой.

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

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

Много путешествовал в Средиземном море, был на африканской земле и в Сирии. Ходил в 7-ми морях и одном океане. Много-много раз обгорел (разок до волдырей!). 8 из 12 месяцев я провёл в постоянных походах.

Армия

Завтра, 23-го апреля в 6:00 утра я ухожу в армию на год. Соотвественно, я не смогу писать в блог, делать какие-то эксперименты в течение этого года.

Надеюсь что я за этот год стану лучше.

«Ненормальное ООП». Выступление на калининградской конфе ForkConf

3 марта, два дня назад мне довелось выступать на местной калининградской IT-конференции под названием ForkConf. Анонс.

Саму презентацию можно увидеть здесь. А здесь лежат её исходники.

Тема выступления – «Ненормальное ООП». Я осознаю что на данный момент львиная доля программистов посещающих конфу пишут на PHP, Java, C++, C#. Подумал, что было бы весело взорвать их мозг разными гибкими возможностями доступными в других языках. =)

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

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

Затронул тему прототипов, классов как объектов. Рассказал про миксины и трейты. Ещё показал в чём разница между посылкой сообщения и простым вызовом метода.

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

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

Такие дела.

Как ограничить набор типов реализующих интерфейс в Go?

Когда есть свободное время я разбираюсь с языком Go, пытаюсь разобраться с его внутренностями, пытаюсь писать какие-то проекты с его помощью.

В Go вводится ряд новшеств, которых нет в других хорошо развитых и популярных языках. Соответственно, код на Go не похож на код на Java или C++, слишком уж разный набор фич, совсем другие средства выражения.

Наткнулся на прикольный способ искусственно ограничить список типов удовлетворяющих интерфейсу.

Ограничение набора типов реализующих конкретный интерфейс

К примеру если хочется использовать в своей FooFunc функции параметр a, так, чтобы a мог быть разных типов, но при этом нет одного интерфейса их объединяющего, с помощью которого можно было бы абстрагироваться. В таких случаях используют тип interface{}

func FooFunc(a interface{}) {
    // do what ever you want
}

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

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

type aTypeInterface interface {
    aMethod()
}

func FooFunc(a aTypeInterface) {
    // do what ever you want
}

func (bType) aMethod(){}
func (cType) aMethod(){}
func (dType) aMethod(){}

Теперь параметр a в функции FooFunc может быть лишь одним из указанных типов: bType, cType, dType, и это проверяется на стадии компиляции.

Наткнулся на такой код в пакете стандартной библиотеки go/ast/ast.go:

/* ... skipped ... */

// All node types implement the Node interface.
type Node interface {
    Pos() token.Pos // position of first character belonging to the node
    End() token.Pos // position of first character immediately after the node
}

// All statement nodes implement the Stmt interface.
type Stmt interface {
    Node
    stmtNode()
}

/* ... skipped ... */

// stmtNode() ensures that only statement nodes can be
// assigned to a StmtNode.
//
func (*BadStmt) stmtNode()        {}
func (*DeclStmt) stmtNode()       {}
func (*EmptyStmt) stmtNode()      {}
func (*LabeledStmt) stmtNode()    {}
func (*ExprStmt) stmtNode()       {}
func (*SendStmt) stmtNode()       {}
func (*IncDecStmt) stmtNode()     {}
func (*AssignStmt) stmtNode()     {}
func (*GoStmt) stmtNode()         {}
func (*DeferStmt) stmtNode()      {}
func (*ReturnStmt) stmtNode()     {}
func (*BranchStmt) stmtNode()     {}
func (*BlockStmt) stmtNode()      {}
func (*IfStmt) stmtNode()         {}
func (*CaseClause) stmtNode()     {}
func (*SwitchStmt) stmtNode()     {}
func (*TypeSwitchStmt) stmtNode() {}
func (*CommClause) stmtNode()     {}
func (*SelectStmt) stmtNode()     {}
func (*ForStmt) stmtNode()        {}
func (*RangeStmt) stmtNode()      {}

Пример именно такого подхода

Есть ли случаи, когда Си лучше C++?

Вопрос с таким заголовком запостил один парень на хэшкоде.

Здравствуйте. Вопрос из заголовка переписывать не буду.

Интересуют реальные примеры, т.е. не те, когда компилятора C++ под какую-то платформу нет. Различия, в несколько процентов, в производительности, несущественны.

Навеяно “мини-наездом” на C++ на Хабре

UPD: Кроме варианта, что C знаем лучше.

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

Всем привет, расскажу почему я считаю что Си лучше C++. И почему надмножество над другим языком это не всегда хорошо.

C++ FQA

больше драмы

Язык программирования всегда имеет набор фич. Замечено, что чем меньше фич и чем больше соблюдается ортогональность, тем гибче и выразительнее сам язык, тем легче понимать код.

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

  • функции, указатели: указатели на функции, функция принимающая указатели
  • функции, структуры, указатели: структуры содержащие указатели на функции

Макросы никак не взаимодействуют с другими, это просто препроцессор, поэтому тут всё понятно.

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

Теперь рассмотрим C++: функции, структуры, указатели, макросы, классы, шаблоны, ссылки, исключения, операторы, методы.

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

  • указатели и ссылки
  • структуры и классы
  • макросы и шаблоны
  • функции и методы

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

Может быть кто-то скажет: “Ну и фиг что сложный! Я умный, я осилю, я смогу управлять этой мощью!”, может и действительно осилит. Я бы избегал любых лишних мысленных телодвижений.

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

Такие дела.

PS: Линус на словцо горяч и не так вежлив как я, и если не читали, то это стоит прочесть.

Немного привёл блог в порядок

Включил тему blanket для octopress, немного подтюнинговал. Немного поменял шрифты, прописал тащить с Google Web Fonts кириллицу, достаточно добавить параметр &subset=latin,cyrillic в /source/_includes/custom/head.html.

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

Поменял в шрифт заголовка с Lobster на Marmelad.

Вообще, Octopress чертовски удобный инструмент для генерации статичного блога, очень легко настраивается под себя.

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

Повторное использование кода в gamedev. Возможно?

Ещё совсем недавно веб был совсем другой: скрипты хостились на shared-хостингах где набор доступных библиотек жёстко фиксировался хостером, и везде правил PHP.

Огромный зоопарк фреймворков, несовместимых библиотек, некоторые из которых требовали специальных опций в php.ini для своей работы, некоторые работали только под определёнными версиями PHP. В общем прелесть.

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

Потом что-то изменилось. Возможно потому что VPS вошли в моду и стали повсеместно доступны, может ещё что-то. Так или иначе Ruby on Rails стал набирать популярность.

Думаю никто не станет спорить, что сегодня RoR является наиболее комфортной платформой для веб-разработки:

  • Строгое разделение кода по функциональности. MVC во все поля.
  • Тесты, тесты, тесты. Множество фреймворков для тестирования, сложившаяся культура в сообществе.
  • Гибкое управление пакетами (rubygems & bundler), использование специальных версий библиотек не доставляет боли.
  • Огромное количество библиотек, в первую очередь для вебаю

И уже не важно тормозит Ruby или нет (по последним бенчмаркам он сравнялся с Python). Этот недостаток перевешивают преимущества, так необходимые для реальной разработки.

Конечно Rails не панацея, он может не подходить для некоторых проектов, однако большую часть веб-проектов быстрее и легче писать на Rails.

Таким образом, на мой взгляд, неупорядоченный и несовместимый веб стал упорядоченным. Взросла определённая культура среди разработчиков.

А теперь взглянем на gamedev

Там сложилась очень похожая ситуация что и недавно в вебе.

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

Или что меня не устраивает в JavaScript

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

Вполне возможно мною движет NIH-синдром, однако я попробую доказать что это не так:

Модульность

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

Типичный вебсайт сегодня — пачка jQuery-плагинов повязанных событиями. Модульная система должна давать возможности для расцвета зоопарка различных плагинов, а также безопасного из совмещения. (Лексическая область видимости для каждого модуля, “умное версионирование”).

Система типов

Здесь выбор за SoftTyping.

Причина та же что и в предыдущем пункте (сложность): необходимы стандартные средства для проверки типов. Конечно не стоит устраивать такую содомию как в Haskell, достаточно опциональных аннотаций. (привет Dart!)

Простая структура кода

Неоднозначная фраза, под ней я имел ввиду возможность легко отследить зависимости в коде. Куча асинхронного кода динамически повязанного с событиями — дикая лапша. Отлаживать такое — то ещё удовольствие.

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

Язык должен быть более строг что касается событий. Например будет обязывать описывать все события в одном месте (модуля).

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

Поэтому тут выбор за функциональным программированием. Ну и дебажить такое будет заметно легче. Мне представляется что-то похожее на Erlang. (Без процессов, конечно).

CoffeeScript и Dart

Два языка которые пытаются улучшить JavaScript. Coffee просто предлагает более приятный для глаз синтаксис, более читаемый. Dart пытается сделать какие-то более радикальные изменения, хотя мне кажется что всё осталось по старому. На мой поверхностный взгляд Dart таки предлагает большую структурированность. (Однако event-hell сохраняется). Короче не то что нужно. Не то что я хочу.

Само собой я не прав.

Как-то так.