суббота, 3 августа 2013 г.

[prog.c] Оказывается WIN64 подразумевает наличие WIN32

Как-то внезапно для себя обнаружил, что предопределенный макрос _WIN64 подразумевает, что будет так же определен макрос _WIN32. Это если про Visual C++ говорить. В случае же с MinGW, аналогично, определенность __WIN64__ подразумевает определенность __WIN32__.

На первый взгляд выглядит странно. Хотя именно из-за такого поведения часть моего старого кода, обрабатывавшего макросы _WIN32 и __WIN32__ для задействования Windows-specific фрагментов, совершенно незаметно пережила переезд с 32-х на 64-е бита.

[prog.flame] Какое-то странное чувство прекрасного у Go-феров

На первых же слайдах презентации с ёмким названием "Twelve Go Best Practices", сделанной на конференции OSCON-2013, встретился пример, который я не могу пропустить без комментариев. Так что включаю режим стёба и перехожу к слайдам :)

На слайде #3 приводится пример "плохого" кода:

func (g *Gopher) DumpBinary(w io.Writererror {
    err := binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
    if err == nil {
        _, err := w.Write([]byte(g.Name))
        if err == nil {
            err := binary.Write(w, binary.LittleEndian, g.Age)
            if err == nil {
                return binary.Write(w, binary.LittleEndian, g.FurColor)
            }
            return err
        }
        return err
    }
    return err
}

На следующем слайде объясняется, чем он плох. Оказывается, нужно избегать излишней вложенности блоков при обработке ошибок (дословно: Avoid nesting by handling errors first)...

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

func (g *Gopher) DumpBinary(w io.Writererror {
    err := binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
    if err != nil {
        return err
    }
    _, err = w.Write([]byte(g.Name))
    if err != nil {
        return err
    }
    err = binary.Write(w, binary.LittleEndian, g.Age)
    if err != nil {
        return err
    }
    return binary.Write(w, binary.LittleEndian, g.FurColor)
}

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

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

type binWriter struct {
    w   io.Writer
    err error
}

// Write writes a value into its writer using little endian.
func (w *binWriter) Write(v interface{}) {
    if w.err != nil {
        return
    }
    w.err = binary.Write(w.w, binary.LittleEndian, v)
}

func (g *Gopher) DumpBinary(w io.Writererror {
    bw := &binWriter{w: w}
    bw.Write(int32(len(g.Name)))
    bw.Write([]byte(g.Name))
    bw.Write(g.Age)
    bw.Write(g.FurColor)
    return bw.err
}

Да уж, больному стало легче, он перестал дышать... Для далеких от Go читателей поясню. Теперь функция DumpBinary сначала создает вспомогательный объект, в котором хранится поток для записи двоичных данных, а так же описание последней ошибки (точнее, указатель на это описание). При создании вспомогательного объекта (с именем bw) этот указатель автоматически получает значение nil, что соответствует успешному результату записи в потом. Далее несколько раз подряд вызывается вспомогательная функция Write, которая получает указатель на вспомогательный объект bw. Функция Write сначала проверяет наличие ошибки предыдущей операции (указатель на описание ошибки в этом случае будет отличным от nil) и, если ошибок не было (bw.err == nil), производит очередную запись в поток двоичных данных. Результат успешности записи сохраняется в том же вспомогательном объекте bw, именно для этого bw передается во вспомогательную функцию Write по указателю. (Примечание. В терминах Go эта функция Write является методом для типа binWriter, а сам этот тип является приватным, т.е. не экспортируемым из пакета, т.к. его имя начинается с маленькой буквы.)

Итак, код был просто здорово улучшен! Вместо простых четырех операций записи данных в поток, мы добавили сюда еще и новый тип с методом, сохраняющим результаты своей работы модифицируя объект (сторонники функционального программирования в восторге). Код DumpBinary стал компактнее, но стал ли он проще? Например, как быстро новый разработчик, которому выпало сопровождать DumpBinary разберется, где же именно происходит обработка ошибок? И что обращения к Write(*binWriter) происходят всегда четыре раза, даже если на первом же из них произошла ошибка ввода-вывода?

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

Но автору презентации и третий вариант не удовлетворил. Он предложил четвертый. В котором вспомогательная функция Write сама разбирается с тем, какого типа аргумент ей передали. Разбирается, если я правильно понимаю, в run-time, а не в compile-time:

// Write writes a value into its writer using little endian.
func (w *binWriter) Write(v interface{}) {
    if w.err != nil {
        return
    }
    switch v.(type) {
    case string:
        s := v.(string)
        w.Write(int32(len(s)))
        w.Write([]byte(s))
    default:
        w.err = binary.Write(w.w, binary.LittleEndian, v)
    }
}

func (g *Gopher) DumpBinary(w io.Writererror {
    bw := &binWriter{w: w}
    bw.Write(g.Name)
    bw.Write(g.Age)
    bw.Write(g.FurColor)
    return bw.err
}

А теперь представим, что в тип Gopher со временем добавили еще одно поле: Pattern []byte. Что и где нужно будет менять, чтобы DumpBinary корректно сохранял новый вариант структуры? Добавление в DumpBinary еще одной строки bw.Write(g.Pattern) будет недостаточно. Нужно будет еще и добавить еще один кейс внутри select-а по типу аргумента в binWriter.Write. Причем, если мы этого не сделаем, компилятор нам по рукам не даст. Поле Pattern будет таки сериализовано, но просто как последовательность байт, без предшествующего маркера длины этой последовательности.

Ну и да, совсем маленькая мелочь. В третьем и четвертом вариантах кода есть серьезная ошибка: размер поля Name записывается в поток обычным методом Write, а не методом binary.Write с указанием binary.LittleEndian. Так что, если первый и второй варианты гарантировали, что длина Name всегда будет упакована в виде Little Endian, то третий и четвертый варианты будут это делать только в случае, если Little Endian является "родным" представлением для той платформы, на которой код работает.

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

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

func (g *Gopher) DumpBinary(w io.Writererror {
    err := binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
    if err == nil {
        _, err := w.Write([]byte(g.Name))
        if err == nil {
            err := binary.Write(w, binary.LittleEndian, g.Age)
            if err == nil {
                err = binary.Write(w, binary.LittleEndian, g.FurColor)
            }
        }
    }

    return err
}

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

Ну а на случай резкого неприятия "лесенки if-ов" достаточно вспомнить прием, который давным-давно применяется в чистых Сях для того, чтобы записывать подобные последовательности операций, каждую из которых можно выполнять только, если предыдущая завершилась успешно. Посредством goto Error :)

int DumpBinary(Gopher * g, Stream * w)
{
   int err;
   if0 != (err = binary_write_int32(w, strlen(g->Name), LITTLE_ENDIAN)) ) goto Error;
   if0 != (err = binary_write_bytes(w, g->Name, strlen(g->Name))) ) goto Error;
   if0 != (err = binary_write_int32(w, g->Age, LITTLE_ENDIAN)) ) goto Error;
   if0 != (err = binary_write_int32(w, g->FurColor, LITTLE_ENDIAN)) ) goto Error;

Error :
   return err;
}

Только вот, кажется, в Go нет goto :( Update. Оказывается, в Go есть goto. Тем более странно, что он не был зайдествован в презентации во втором примере.

Ну а в нормальных языках давно уже применяются исключения, как раз для того, чтобы лесенки из if-ов не писать. Да и шаблоны (генерики), чтобы проверки типов и поиск подходящих функций/методов были в compile-time, а не в run-time. Языки эти, правда, пообъемнее Go будут. Да и писали их не Пайк с Томпсоном, что, очевидно, является их фатальным недостатком ;)

четверг, 1 августа 2013 г.

[prog] Три года после поста про бум языкостроения. Ну и о языке ooc :)

Три года назад я написал пост "Прям бум языкостроения какой-то :)", в конце которого упомянул о то, что из всех перечисленных там языков у меня было желание посмотреть только на язык ooc. Об этом желании мне постоянно напоминал ув.тов.Alex Syrnikov. Появилось таки время посмотреть на ooc и выполнить свое обещание. Но об этом попозже.

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

  • Coherence. На данный момент его сайт -- это одна страничка с несколькими ссылками на старые презентации. И все;
  • Cola. Только страничка в Wikipedia, публично доступного релиза языка нет;
  • Ioke. Судя по репозиторию, с 2009 года не развивался. Честно скажу, чего-то подобного я и ожидал, поскольку его автором является один из тогдашних разработчиков JRuby -- Ola Bini. В свое время почитывал его блог и у меня не сложилось впечатления, что он может вложить достаточно сил в продвижение своих разработок;
  • Noop. Язык, который зародился внутри Google, о котором несколько лет назад время от времени говорили. Но который, если судить по репозиторию, с 2010 не развивается;
  • ooc. Вероятно, скорее мертв, чем жив. В репозитории коммиты довольно старые, документация убогая;
  • Lisaac. Сайт сейчас вообще не имеет никакого наполнения. Кое-что о языке осталось разве что в Wikipedia.

Еще я не понял состояние BitC и Kodu. Но за неимением возможности обосновать их смерть, пусть считаются живыми и здравствующими ;)

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

Ну а теперь, совсем кратко, о языке ooc. Посмотрел я документацию по нему. И понял, что смысла смотреть дальше нет. Не увидел ничего такого, ради чего захотелось бы его попробовать. Даже синтаксис, который в свое время напоминал синтаксис Ruby, и тот не впечатлил, скорее наоборот: не нравится мне, когда вызов метода записывается через пробел, т.е. "bar foo()" вместо "bar.foo()". Ну а так ничего особенного: объектно-ориентированный (скорее всего без множественного наследования), с поддержкой обобщенного программирования, с поддержкой лямбда-функций и какого-то варианта вывода типа аргументов методов. Язык вроде как со сборкой мусора, но, при этом, можно вернуть указатель на локальную переменную на стеке, что приведет к непредсказуемым последствиям. В общем, хотели сделать что-то вроде нативной Java или C#, но со своеобразным синтаксисом. Сделали первый работающий вариант, поигрались и, похоже, забросили. Ну и пусть там валяется без дела.

PS. Три года назад после поста о буме языкостроения я написал пост о своем отношении к разработке новых универсальных языков программирования. За это время мое отношение к данной теме совсем не изменилось. И хотя сейчас я в гораздо меньшей степени связан legacy-кодом и спокойно могу позволить себе поэкспериментировать с чем-то совсем новым (вроде недавней попытки взяться за Go), но как-то ничего не цепляет. Похоже, самое интересное сейчас творится в мире JVM -- это я о Kotlin и Ceylon.

среда, 31 июля 2013 г.

[life.cinema] Очередной кинообзор (2013/07)

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

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

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

Транс. Сделано все, безусловно, очень здорово. Вот только совершенно не уверен в том, что те на самом деле там происходило :)

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

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

Призрачный патруль. Неплохое чисто развлекательное кино. Но оставляет ощущение некоторой халтурности или недоделанности.

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

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

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

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

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

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

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

Судная ночь. Трейлер оказался намного круче и лучше самого фильма. Смело можно не смотреть.

Бросок кобры 2. Мне показалось, что первый фильм был покруче как в плане сюжета (если данное понятие здесь вообще уместно), так и в плане качества и масштабности визуальной составляющей фильма. Ну а поскольку первый фильм был сказочкой для младшего среднего возраста, то и второй такой же.

понедельник, 29 июля 2013 г.

[prog.flame] Куски из чужой презентации о старом коде, причинах...

...доведения его до такого состояния и последствиях.

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

Итак, в 2007-м году была сделана некоторая система по обслуживанию запросов пользователей. К 2012 году система перестала справляться с возросшей нагрузкой. Почему и как это произошло автор презентации говорит прямо и откровенно. Этому посвящен специальный раздел презентации "Why good code goes bad".

Основные пункты (в скобках мой перевод):

Premise: people don't suck (Предпосылка: разработчики нормальные)

Premise: code was once beautiful (Предпосылка: изначально код был хорошим)

code tends towards complexity (gets worse) (код имеет тенденцию усложняться (т.е. становиться хуже))

environment changes (изменяется среда)

scale changes (изменяется нагрузка)

Факторы, которые обуславливают усложнение кода:

without regular love, code grows warts over time (если с кодом не трахаться постоянно не уделять коду постоянного внимания, то со временем он покрывается бородавками)

localized fixes and additions are easy & quick, but globally crappy (локальные исправления и дополнения делаются легко и быстро, но в общем все становится только хуже)

features, hacks and workarounds added without docs or tests (фичи, хаки и воркэраунды добавляются без их документирования или покрытия тестами)

maintainers come & go, (занимающиеся сопровождением разработчики приходят и уходят)

... or just go. (или же просто уходят)

Факторы, которые обуславливают изменение среды, в которой работает система:

infrastructure (hardware & software), like anybody's, is always changing (инфраструктура (железо и софт), как и везде, постоянно изменяется)

properties of networks, storage (изменяются свойства как сетевых, так и подсистем хранения данных)

design assumptions no longer make sense (исходные проектные решения сейчас уже не имеют смысла)

scale changes (design for 10x growth, rethink at 100x) (изменилась нагрузка (проектировали для 10-кратного роста, приходится иметь дело со 100-кратным))

new internal services (beta or non-existent then, dependable now) (появление новых внутренних сервисов, которых не было тогда, на которые приходится опираться сейчас)

once-modern home-grown invented wheels might now look archaic (когда-то самостоятельно переизобретенное колесо казалось очень современным, сейчас же выглядит старьем)

Почему система перестала нормально работать:

code was too complicated (код стал слишком уж сложным)

future maintainers slowly violated unwritten rules (приходящие на сопровождение разработчику потихоньку нарушают неписанные правила и соглашения)

or knowingly violated them, assuming it couldn't be too bad? (или же делают это сознательно, предполагая, что ничего плохого не случится)

single-threaded event-based callback spaghetti (спаггети из событийных коллбэков в однопоточном коде)

hard to know when/where code was running, or what "blocking" meant (из-за чего сложно понять какой и где код работает или что означает "блокирует")

Ну и выдержки из финального заключения о том, во что превратился код через пять лет (с 2007 до 2012):

incomplete docs, tests (неполная документация и наборы тестов)

stalling event loop (медленный и дырявый цикл обработки событий)

ad-hoc threads... (бездумное использование потоков)
... stalling event loops (останавливающийся цикл обработки событий)
... races (гонки)
... crashes (падения)
copy/paste code (копипащенный код)
... incomplete code (недоделанный код)

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