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

run{}

Mustache, шаблонизатор лишённый логики

Наверное вы уже видели Mustache? Это удобный шаблонизатор, имеющий множество реализаций для различных платформ и исповедующий принцип: «Шаблоны без логики» (logic-less templates). Сейчас немного расскажу что это такое и почему это круто.

Если вы когда-нибудь видели простыню php-кода где весело перемешаны вызовы к БД, обращения к каким-то глобальным переменным, вывод html-шаблона – всё-всё строк эдак в тыщу? Я видел. Это добро к тому же это ещё и постоянно ломалось где-то посередине.

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

Mustache – это шаг в сторону большей структуризации. В PHP нет чёткого разделения на обычный код и html-шаблоны, в Ruby/Rails есть erb для шаблонов, но даже там можно наварить кашу. Если вы используете Mustache у вас просто нет шансов. Код отдельно, шаблоны отдельно, только так, только хардкор! Вы даже не можете вызвать какой-то фильтр в шаблоне, такой код необходимо писать вне шаблона.

Шаблонизатор принимает JSON-подобную структуру (вложенные словари, списки; строки, числа) и буквально отображает данные на шаблон (разговорным языком, маппит).

Вот пара примеров:

ActiveRecord Store в Rails 3.2

Недавно довелось пользоваться store-атрибутами которые появились в последних рельсах. На самом деле store-атрибуты довольно простая вещь: это поле в таблице типа text, которое хранит сложные данные сериализованные в текстовом формате. Подобное может быть очень полезно для множества различных настроек, для которых не хочется создавать по отдельному столбцу в таблице.

Т.к. это просто кусок сериализованных данных, то никакого индексирования и поиска по полям само-собой нет. (В отличие от документно-ориентированной MongoDB)

Вроде бы ничего особенного, главная фича в том, что с полями хранимыми с помощью store можно работать также как с обычными полями: использовать в формах, проводить валидацию. Примеры использования можно увидеть здесь, здесь и здесь.

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

Однако вылезла следующая проблема: непонятно как работать с этими данными на клиентской стороне, через формы. Значения store-полей выводились как есть, используя ruby-синтаксис, тогда как мне нужен был JSON.

К примеру

class Activity < ActiveRecord::Base
  store :schedule, accessors: [:monday]
end

и если присвоить полю monday значение [{:start => "01:00am", :end => "02:00am"}] и вывести в форме через <%= f.input :monday %> то значение будет обычная строка [{:start => "01:00am", :end => "02:00am"}], тогда как мне нужны данные в формате JSON для более удобной работы через JavaScript.

Сделал небольшой хак, позволяющий присваивать и получать JSON.

class Activity < ActiveRecord::Base
  store :schedule

  def schedule
    map = self[:schedule].map { |day, value| [day, JSON(value)] }
    Hash[map]
  end

  [:monday].each do |day|
    define_method(:"#{day}=") do |value|
      value = value.to_json unless value.kind_of? String
      self[:schedule][day] = value
    end

    define_method(day) do
      self[:schedule][day]
    end
  end

Теперь можно получать и присваивать JSON через обычные accessors, а к объектам обращаться с помощью .schedule[:monday].

Не самое элегантное решение, но проблему решает.

Пять правил Роба Пайка

Наткнулся на пять правил программирования, написанных Робом Пайком:

  1. You can’t tell where a program is going to spend its time. Bottlenecks occur in surprising places, so don’t try to second guess and put in a speed hack until you’ve proven that’s where the bottleneck is.
  2. Measure. Don’t tune for speed until you’ve measured, and even then don’t unless one part of the code overwhelms the rest.
  3. Fancy algorithms are slow when n is small, and n is usually small. Fancy algorithms have big constants. Until you know that n is frequently going to be big, don’t get fancy. (Even if n does get big, use Rule 2 first.)
  4. Fancy algorithms are buggier than simple ones, and they’re much harder to implement. Use simple algorithms as well as simple data structures.
  5. Data dominates. If you’ve chosen the right data structures and organized things well, the algorithms will almost always be self-evident. Data structures, not algorithms, are central to programming.

Роб Пайк крутой чувак и я его очень уважаю. Он участвовал в разработке операционных систем Inferno, Plan 9, а также языков Alef, Limbo, Newsqueak и на данный момент занят языком Go.

Перевод этих пяти правил:

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

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

  3. Нетривиальные алгоритмы работают медленно если достаточно маленькое, и обычно именно так и случается. Зато в них достаточно большие константы сложности. Пока не убедитесь что будет достаточно большим, не усложняйте код нетривиальными алгоритмами. (И даже если достаточно велико, убедитесь что выполняется второе правило)

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

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

Повесил на стенку.

rename watcher -> go-retest

Небольшой watcher описанный в предыдущем посте теперь называется go-retest, вызывается одноимённой командой и живёт по адресу: https://bitbucket.org/vladimir_vg/go-retest

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

Чем чёрт не шутит.

Постоянный прогон тестов для Go

Понадобилась мне небольшая тулза, которая бы перезапускала тесты при изменениях кода, а также уведомляла об ошибках. Проще говоря мне нужено что-то вроде autotest для Rails или watchr.

Накидал небольшой код который мониторит все *.go файлы и запускает тексты. Использовал последний свежий Go из репозитория. Использовал команду notify-send для системных уведомлений, гарантированно работает на Ubuntu Oneric 11.10. Если есть у кого желание, то можно форкнуть gist и добавить функции системного уведомления для вашей системы.

package main

import "log"
import "os"
import "regexp"
import "os/exec"
import "exp/inotify"

func runTests() {
    cmd := exec.Command("go", "test")
    cmd.Stdout = os.Stdout
    if err := cmd.Run(); err != nil {
        log.Println(err)
        notifySystem("go test failed")
    }
}

// probably will work only for Ubuntu
func notifySystem(message string) {
    cmd := exec.Command("notify-send", message)
    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
}

func setupWatcher() *inotify.Watcher {
    watcher, err := inotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }

    err = watcher.AddWatch(".", inotify.IN_MODIFY)
    if err != nil {
        log.Fatal(err)
    }

    return watcher
}

func main() {
    watcher := setupWatcher()

    runTests()
    for {
        select {
        case ev := <-watcher.Event:
            matched, err := regexp.MatchString(".*\\.go", ev.Name)
            if err != nil {
                log.Fatal(err)
            }

            if matched {
                // On my machine it always raised two events
                // ignore second, prevent twice tests run.
                // Probably it's depends of test editor.
                // If so, feel free to
                // remove line above:
                <-watcher.Event

                runTests()
            }
        case err := <-watcher.Error:
            log.Println("error:", err)
        }
    }
}

UPD: Теперь watcher живёт по адресу: https://bitbucket.org/vladimir_vg/go-retest. Подробнее в следующем посте.

Странное поведение выражения с if в Ruby

Первым делом код:

class A
  def foo
    :method_value
  end

  def bar
    foo = :variable_value if false
    foo
  end
end

Если мы используем локальную переменную с таким же именем как и метод, то метод перекрывается ею. В приведённом коде я ожидал возврата :method_value, т.к. мы не используем переменную foo. Метод bar возвращает nil. Забавно, правда?

Переиначил приведённый выше пример, но получил тот же результат:

class A
  def foo
    :method_value
  end

  def bar
    if false
      foo = :variable_value
    end
    foo
  end
end

Наверное эта «фича» находится в родственных связях с багом описанном в весёлом WAT видео:

1.9.2p290 :001 > a
NameError: undefined local variable or method `a' for main:Object
1.9.2p290 :002 > b
NameError: undefined local variable or method `b' for main:Object
1.9.2p290 :003 > a = b
NameError: undefined local variable or method `b' for main:Object
1.9.2p290 :004 > a
 => nil

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

WAT?

Сравнение функций в Go

Мне всегда казалось естественным сравнивать функции в языках с поддержкой ФВП. Понятное дело, что сравнить ведут ли себя функции одинаково, в одинаковый код ли они скомпилировались дело сложное и, в действительности, не нужное. Но почему бы не сравнивать их указатели? Возвращать true когда это гарантированно так.

Некоторое время назад, разбираясь с Haskell я поднимал тему на ЛОРе.

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

Так что этого стоит избегать.

Пока я выяснял степень православности сравнения указателей функций, я накалякал вот такой костылик:

package main

import "reflect"
import "fmt"

func f() {}
func g() {}

func equals(f, g interface{}) bool {
    fPtr := reflect.ValueOf(f).Pointer()
    gPtr := reflect.ValueOf(g).Pointer()
    return fPtr == gPtr
}

func main() {
    if equals(f, g) {
        fmt.Println("f and g are equal!")
    } else {
        fmt.Println("f and g are not equal.")
    }
}

Ответ разумеется "f and g are not equal.".

Всем привет! Снова.

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

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

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

init

Hello there, this is my first post in this blog. Actually I wrote it mostly for testing purposes rather than to provide some kind of information.

btw, English isn’t my native language, so probably my texts may contain grammatical mistakes. I will be glad to accept your pull-request with fixes.