...

суббота, 15 ноября 2014 г.

Дайджест интересных материалов о создании контента, маркетинге и Growth Hacking #1

Привет, Хабр! Еженедельно я буду публиковать здесь подборку ссылок на материалы о создании контента, маркетинге и Growth Hacks (в будущем дайджест будет выходить дважды в неделю).

image


Статьи на английском помечаются специальной иконкой.


Создание контента




image

Маркетинг




image

Growth Hacks




image

Спасибо за внимание. Ваши замечания и поправки оставляйте в комментариях или пишите личными сообщениями.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[recovery mode] ReactOS 0.3.17 — «нумерологический» релиз операционной системы

Добрый день! image

Меня зовут Алексей Брагин, я приветствую всех гостей на открытии блога компании «Фонд ReactOS», которое стало возможным благодаря программе поддержи стартапов от «Тематических Медиа».


От лица всей команды разработчиков проекта ReactOS представляю вашему вниманию новый релиз нашей операционной системы под номером 0.3.17



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



Однако, это не единственное существенное улучшение, попавшее в релиз 0.3.17. Стоить отметь серьезный прогресс исправления ошибок в обработке шрифтов:



  1. Баг CORE-4657 "Gimp/GTK+ drawing problems"

    Стало


  2. Баг CORE-8525 "JRE installer and Winrar shows Marlett font instead the proper one"

    Стало





Впервые в этом релизе возможна стабильная работа Oracle/Sun JAVA и программ, использующих ее.
Netbeans 6.7

Netbeans 8.0


Image

I have not tested it beyond the last image, neither have I tried to run it after a re-boot. So here are the images of what I have managed to do.


Image


There is a little fault in this next image; the window border of the IDE is invisible (it is there, because when I took the screenshot, the area was highlighted and that is why you can see a bit of the desktop).


Image


Image




IntelliJ IDEA 13 Community Edition

Некоторым тестерам проекта ReactOS удалось запустить достаточно «требовательные» приложения и игры:


Steam
image


Warcraft 3
image


Macromedia Flash 8
image


Microsoft Office Excel Viewer
image


Stellarium
image


SeemSity3000
image



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


Да-да, вы не ослышались, череде релизов 0.3.ХY пришел CTRL+C. Впереди 0.4 и несколько сюрпризов!


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


P.S. Если вы все еще не догадались, почему этот релиз назван мной «нумерологическим», обратите свое внимание на номер версии, дату выхода и url новости о релизе. Честное слово, мы не добивались этого намеренно.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Apple прокомментировала ситуацию с уязвимостью Masque в iOS

На этой неделе компания FireEye опубликовала информацию, относящуюся к т. н. «Masque» уязвимости в iOS. Уязвимость позволяет установить вредоносное приложение поверх уже существующего, причем это новое приложение получит доступ ко всем файлам предыдущего. Это при условии того, что устанавливаемое приложение будет иметь тот же самый идентификатор «bundle identifier», который iOS & OS X используют для идентификации приложений на уровне ОС, например, при доставке им обновлений. Уязвимости подвержены все версии iOS начиная с 7.1.1, включая, последнюю iOS 8.1.1 beta.


В сценарии атаки с использованием этой уязвимости, пользователю в сообщении приходит ссылка на установку вредоносного приложения, которое маскируется под легитимное. Как и в случае с вредоносным ПО iOS/Wirelurker, о котором мы писали здесь, для установки приложения (IPA-контейнер) на iOS без jailbreak, вредоносная программа должна использовать метод «enterprise provisioning» и устанавливаемый файл должен быть подписан цифровым сертификатом выданным Apple. Это новое приложение должно иметь «bundle identifier» уже установленного приложения (но не того, которое является «родным» для iOS), что позволит ему при установке получить доступ ко всем файлам старого и в дальнейшем отправить их на сервер атакующих.


У нового приложения нет возможности перезаписать встроенное изначально в iOS приложение, например, браузер Safari или Mail, однако, используя общеизвестные «bundle identifier» других приложений, оно может получить доступ ко всем его конфиденциальным данным. Это могут быть данные онлайн-банкинга, личные сообщения и любая другая информация, которая интересует злоумышленников.


Преимущество метода «enterprise provisioning» заключается в том, что приложение, поставляемое по этой схеме можно не отправлять на подтверждение его безопасности компании Apple, как в случае с App Store. Кроме этого, для устройств без jailbreak это является практически единственным способом установки приложения на iOS в обход App Store. Подразумевается, что приложение будет подписано сертификатом, выданном компанией Apple, и этого достаточно для подтверждения его легитимности.



We designed OS X and iOS with built-in security safeguards to help protect customers and warn them before installing potentially malicious software. We're not aware of any customers that have actually been affected by this attack. We encourage customers to only download from trusted sources like the App Store and to pay attention to any warnings as they download apps. Enterprise users installing custom apps should install apps from their company's secure website.





Apple

Apple также обновила информацию по схеме «enterprise provisioning» в своей статье support.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Перевод] Выразительный JavaScript: Проект: язык программирования

Содержание


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


Хэл Абельсон и Жеральд Сасман, «Структура и интерпретация компьютерных программ».


Когда учение спросил учителя о природе цикла Данных и Контроля, Юань-Ма ответил: «Подумай о компиляторе, компилирующем самого себя».


Мастер Юань-Ма, «Книга программирования»


Создать свой язык программирования удивительно легко (пока вы не ставите запредельных целей) и довольно поучительно.


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


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



Разбор (parsing)




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

У нашего языка будет простой и однородный синтаксис. В Egg всё будет являться выражением. Выражение может быть переменной, число, строка или приложение. Приложения используются для вызова функций и конструкций типа if или while.


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


Приложения записываются так же, как в JS — при помощи скобок после выражения и с любым количеством аргументов в скобках, разделённых запятыми.



do(define(x, 10),
if(>(x, 5)),
print("много"),
print("мало"))


Однородность языка означает, что то, что в JS является операторами, применяется так же, как и остальные функции. Так как в синтаксисе нет концепции блоков, нам нужна конструкция do для обозначения нескольких вещей, выполняемых последовательно.


Структура данных, описывающая программу, будет состоять из объектов выражений, у каждого из которых будет свойство type, отражающее тип этого выражения и другие свойства, описывающие содержимое.


Выражения типа “value” представляют строки или числа. Их свойство value содержит строку или число, которое они представляют. Выражения типа “word” используются для идентификаторов (имён). У таких объектов есть свойство name, содержащее имя идентификатора в виде строки. И наконец, выражения “apply” представляют приложения. У них есть свойство “operator”, ссылающееся на применяемое выражение, и свойство “args” с массивом аргументов.


Часть >(x, 5) будет представлена так:



{
type: "apply",
operator: {type: "word", name: ">"},
args: [
{type: "word", name: "x"},
{type: "value", value: 5}
]
}


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


Структура синтаксического дерева




Сравните это с парсером, написанным нами для файла настроек в главе 9, у которого была простая структура: он делил ввод на строки и обрабатывал их одну за другой. Там было всего несколько форм, которые разрешено принимать строке.

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


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


Первая часть парсера:



function parseExpression(program) {
program = skipSpace(program);
var match, expr;
if (match = /^"([^"]*)"/.exec(program))
expr = {type: "value", value: match[1]};
else if (match = /^\d+\b/.exec(program))
expr = {type: "value", value: Number(match[0])};
else if (match = /^[^\s(),"]+/.exec(program))
expr = {type: "word", name: match[0]};
else
throw new SyntaxError("Неожиданный синтаксис: " + program);

return parseApply(expr, program.slice(match[0].length));
}

function skipSpace(string) {
var first = string.search(/\S/);
if (first == -1) return "";
return string.slice(first);
}


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


Пропустив начальные пробелы, parseExpression использует три регулярки для распознавания трёх простых (атомарных) элементов, поддерживаемых языком: строк, чисел и слов. Парсер создаёт разные структуры для разных типов. Если ввод не подходит ни под одну из форм, это не является допустимым выражением, и он выбрасывает ошибку. SyntaxError – стандартный объект для ошибок, который создаётся при попытке запуска некорректной программы JavaScript.


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



function parseApply(expr, program) {
program = skipSpace(program);
if (program[0] != "(")
return {expr: expr, rest: program};

program = skipSpace(program.slice(1));
expr = {type: "apply", operator: expr, args: []};
while (program[0] != ")") {
var arg = parseExpression(program);
expr.args.push(arg.expr);
program = skipSpace(arg.rest);
if (program[0] == ",")
program = skipSpace(program.slice(1));
else if (program[0] != ")")
throw new SyntaxError("Ожидается ',' or ')'");
}
return parseApply(expr, program.slice(1));
}


Если следующий символ программы – не открывающая скобка, то это не приложение, и parseApply просто возвращает данное ей выражение.


В ином случае, она пропускает открывающую скобку и создаёт объект синтаксического дерева для этого выражения. Затем она рекурсивно вызывает parseExpression для разбора каждого аргумента, пока не встретит закрывающую скобку. Рекурсия непрямая, parseApply и parseExpression вызывают друг друга.


Поскольку приложение само по себе может быть выражением (multiplier(2)(1)), parseApply должна, после разбора приложения, вызвать себя снова, проверив, не идёт ли далее другая пара скобок.


Вот и всё, что нам нужно для разбора Egg. Мы обернём это в удобную функцию parse, проверяющую, что она дошла до конца строки после разбора выражения (программа Egg – это одно выражение), и это даст нам структуру данных программы.



function parse(program) {
var result = parseExpression(program);
if (skipSpace(result.rest).length > 0)
throw new SyntaxError("Неожиданный текст после программы");
return result.expr;
}

console.log(parse("+(a, 10)"));
// → {type: "apply",
// operator: {type: "word", name: "+"},
// args: [{type: "word", name: "a"},
// {type: "value", value: 10}]}


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


Интерпретатор




А что нам делать с синтаксическим деревом программы? Запускать её! Этим занимается интерпретатор. Вы даёте ему синтаксическое дерево и объект окружения, который связывает имена со значениями, а он интерпретирует выражение, представляемое деревом, и возвращает результат.

function evaluate(expr, env) {
switch(expr.type) {
case "value":
return expr.value;

case "word":
if (expr.name in env)
return env[expr.name];
else
throw new ReferenceError("Неопределённая переменная: " +
expr.name);
case "apply":
if (expr.operator.type == "word" &&
expr.operator.name in specialForms)
return specialForms[expr.operator.name](expr.args,
env);
var op = evaluate(expr.operator, env);
if (typeof op != "function")
throw new TypeError("Приложение не является функцией.");
return op.apply(null, expr.args.map(function(arg) {
return evaluate(arg, env);
}));
}
}

var specialForms = Object.create(null);


У интерпретатора есть код для каждого из типов выражений. Для литералов он возвращает их значение. Например, выражение 100 интерпретируется в число 100. У переменной мы должны проверить, определена ли она в окружении, и если да – запросить её значение.


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


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


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


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


Специальные формы




Объект specialForms используется для определения особого синтаксиса Egg. Он сопоставляет слова с функциями, интерпретирующими эти специальные формы. Пока он пуст. Давайте добавим несколько форм.

specialForms["if"] = function(args, env) {
if (args.length != 3)
throw new SyntaxError("Неправильное количество аргументов для if");

if (evaluate(args[0], env) !== false)
return evaluate(args[1], env);
else
return evaluate(args[2], env);
};


Конструкция if языка Egg ждёт три аргумента. Она вычисляет первый, и если результат не false, вычисляет второй. В ином случае вычисляет третий. Этот if больше похож на тернарный оператор ?:. Это выражение, а не инструкция, и она выдаёт значение, а именно, результат второго или третьего выражения.


Egg отличается от JavaScript тем, как он обрабатывает условие if. Он не будет считать ноль или пустую строку за false.


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


Форма для while схожая.



specialForms["while"] = function(args, env) {
if (args.length != 2)
throw new SyntaxError("Неправильное количество аргументов для while");

while (evaluate(args[0], env) !== false)
evaluate(args[1], env);

// Поскольку undefined не задано в Egg,
// за отсутствием осмысленного результата возвращаем false
return false;
};


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




specialForms["do"] = function(args, env) {
var value = false;
args.forEach(function(arg) {
value = evaluate(arg, env);
});
return value;
};


Чтобы создавать переменные и давать им значения, мы создаём форму define. Она ожидает word в качестве первого аргумента, и выражение, производящее значение, которое надо присвоить этому слову в качестве второго. define, как и всё, является выражением, поэтому оно должно возвращать значение. Пусть оно возвращает присвоенное значение (прям как оператор = в JavaScript).



specialForms["define"] = function(args, env) {
if (args.length != 2 || args[0].type != "word")
throw new SyntaxError("Bad use of define");
var value = evaluate(args[1], env);
env[args[0].name] = value;
return value;
};


Окружение




Окружение, принимаемое интерпретатором — это объект со свойствами, чьи имена соответствуют именам переменных, а значения – значениям этих переменных. Давайте определим объект окружения, представляющий глобальную область видимости.

Для использования конструкции if мы должны создать булевские значения. Так как их всего два, особый синтаксис для них не нужен. Мы просто делаем две переменные со значениями true и false.



var topEnv = Object.create(null);

topEnv["true"] = true;
topEnv["false"] = false;

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

var prog = parse("if(true, false, true)");
console.log(evaluate(prog, topEnv));
// → false


Для поддержки простых арифметических операторов и сравнения мы добавим несколько функций в окружение. Для упрощения кода мы будем использовать new Function для создания набора функций-операторов в цикле, а не определять их все по отдельности.



["+", "-", "*", "/", "==", "<", ">"].forEach(function(op) {
topEnv[op] = new Function("a, b", "return a " + op + " b;");
});


Также пригодится способ вывода значений, так что мы обернём console.log в функцию и назовём её print.



topEnv["print"] = function(value) {
console.log(value);
return value;
};


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



function run() {
var env = Object.create(topEnv);
var program = Array.prototype.slice
.call(arguments, 0).join("\n");
return evaluate(parse(program), env);
}


Использование Array.prototype.slice.call – уловка для превращения объекта, похожего на массив, такого как аргументы, в настоящий массив, чтобы мы могли применить к нему join. Она принимает все аргументы, переданные в run, и считает, что все они – строчки программы.



run("do(define(total, 0),",
" define(count, 1),",
" while(<(count, 11),",
" do(define(total, +(total, count)),",
" define(count, +(count, 1)))),",
" print(total))");
// → 55


Эту программу мы видели уже несколько раз – она подсчитывает сумму чисел от 1 до 10 на языке Egg. Она уродливее эквивалентной программы на JavaScript, но не так уж и плоха для языка, заданного менее чем 150 строчками кода.


Функции




Язык программирования без функций – плохой язык.

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



specialForms["fun"] = function(args, env) {
if (!args.length)
throw new SyntaxError("Функции нужно тело");
function name(expr) {
if (expr.type != "word")
throw new SyntaxError("Имена аргументов должны быть типа word");
return expr.name;
}
var argNames = args.slice(0, args.length - 1).map(name);
var body = args[args.length - 1];

return function() {
if (arguments.length != argNames.length)
throw new TypeError("Неверное количество аргументов");
var localEnv = Object.create(env);
for (var i = 0; i < arguments.length; i++)
localEnv[argNames[i]] = arguments[i];
return evaluate(body, localEnv);
};
};


У функций в Egg своё локальное окружение, как и в JavaScript. Мы используем Object.create для создания нового объекта, имеющего доступ к переменным во внешнем окружении (своего прототипа), но он также может содержать новые переменные, не меняя внешней области видимости.


Функция, созданная формой fun, создаёт своё локальное окружение и добавляет к нему переменные-аргументы. Затем она интерпретирует тело в этом окружении и возвращает результат.



run("do(define(plusOne, fun(a, +(a, 1))),",
" print(plusOne(10)))");
// → 11

run("do(define(pow, fun(base, exp,",
" if(==(exp, 0),",
" 1,",
" *(base, pow(base, -(exp, 1)))))),",
" print(pow(2, 10)))");
// → 1024


Компиляция




Мы с вами построили интерпретатор. Во время интерпретации он работает с представлением программы, созданным парсером.

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


По традиции компиляция также превращает программу в машинный код – сырой формат, пригодный для исполнения процессором. Но каждый процесс превращения программы в другой вид, по сути, является компиляцией.


Можно было бы создать другой интерпретатор Egg, который сначала превращает программу в программу на языке JavaScript, использует new Function для вызова компилятора JavaScript и возвращает результат. При правильной реализации Egg выполнялся бы очень быстро при относительно простой реализации.


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


Мошенничество




Когда мы определяли if и while, вы могли заметить, что они представляли собой простые обёртки вокруг if и while в JavaScript. Значения в Egg – также обычные значения JavaScript.

Сравнивая реализацию Egg, построенную на JavaScript, с объёмом работы, необходимой для создания языка программирования непосредственно на машинном языке, то разница становится огромной. Тем не менее, этот пример, надеюсь, даёт вам представление о работе языков программирования.


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


Такой язык не обязан напоминать обыный ЯП. Если бы JavaScript не содержал регулярных выражений, вы могли бы написать свои парсер и интерпретатор для такого суб-языка.


Или представьте, что вы строите гигантского робота-динозавра и вам нужно запрограммировать его поведение. JavaScript – не самый эффективный способ сделать это. Можно вместо этого выбрать язык примерно такого свойства:



behavior walk
perform when
destination ahead
actions
move left-foot
move right-foot

behavior attack
perform when
Godzilla in-view
actions
fire laser-eyes
launch arm-rockets


Обычно это называют языком для выбранной области (domain-specific language) – язык, специально предназначенный для работы в узком направлении. Такой язык может быть более выразительным, чем язык общего назначения, потому что он разработан для выражения именно тех вещей, которые надо выразить в этой области – и больше ничего.


Упражнения




Массивы



Добавьте поддержку массивов в Egg. Для этого добавьте три функции в основную область видимости: array(...) для создания массива, содержащего значения аргументов, length(array) для возврата длины массива и element(array, n) для возврата n-ного элемента.

// Добавьте кода

topEnv["array"] = "...";

topEnv["length"] = "...";

topEnv["element"] = "...";

run("do(define(sum, fun(array,",
" do(define(i, 0),",
" define(sum, 0),",
" while(<(i, length(array)),",
" do(define(sum, +(sum, element(array, i))),",
" define(i, +(i, 1)))),",
" sum))),",
" print(sum(array(1, 2, 3))))");
// → 6


Замыкания



Способ определения fun позволяет функциям в Egg замыкаться вокруг окружения, и использовать локальные переменные в теле функции, которые видны во время определения, точно как в функциях JavaScript.

Следующая программа иллюстрирует это: функция f возвращает функцию, добавляющую её аргумент к аргументу f, то есть, ей нужен доступ к локальной области видимости внутри f для использования переменной a.



run("do(define(f, fun(a, fun(b, +(a, b)))),",
" print(f(4)(5)))");
// → 9


Объясните, используя определение формы fun, какой механизм позволяет этой конструкции работать.


Комментарии



Хорошо было бы иметь комментарии в Egg. К примеру, мы могли бы игнорировать оставшуюся часть строки, встречая символ “#” – так, как это происходит с “//” в JS.

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



// Поменяйте старую функцию
function skipSpace(string) {
var first = string.search(/\S/);
if (first == -1) return "";
return string.slice(first);
}

console.log(parse("# hello\nx"));
// → {type: "word", name: "x"}

console.log(parse("a # one\n # two\n()"));
// → {type: "apply",
// operator: {type: "word", name: "a"},
// args: []}


Чиним область видимости



Сейчас мы можем присвоить переменной значение только через define. Эта конструкция работает как при присвоении старым переменным, так и при создании новых.

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


Добавьте форму set, схожую с define, которая присваивает переменной новое значение, обновляя переменную во внешней области видимости, если она не задана в локальной. Если переменная вообще не задана, швыряйте ReferenceError (ещё один стандартный тип ошибки).


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



Object.prototype.hasOwnProperty.call(scope, name);


Это вызывает метод hasOwnProperty прототипа Object и затем вызывает его на объекте scope.



specialForms["set"] = function(args, env) {
// Ваш код
};

run("do(define(x, 4),",
" define(setx, fun(val, set(x, val))),",
" setx(50),",
" print(x))");
// → 50
run("set(quux, true)");
// → Ошибка вида ReferenceError


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


[Перевод] Выразительный JavaScript: Модули

Содержание


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


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


Мастер-программист знает, когда нужна структура, а когда нужно оставить вещи в простом виде. Его программы словно глина – твёрдые, но податливые.


Мастер Юан-Ма, Книга программирования


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


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



Зачем нужны модули




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

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


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


Пространство имён




У большинства современных ЯП есть промежуточные области видимости (ОВ) между глобальной (видно всем) и локальной (видно только этой функции). У JavaScript такого нет. По умолчанию, всё, что необходимо видеть снаружи функции верхнего уровня, находится в глобальной ОВ.

Загрязнение пространства имён (ПИ), когда не связанные друг с другом части кода делят один набор переменных, упоминалась в главе 4. Там объект Math был приведён в качестве примера объекта, который группирует функциональность, связанную с математикой, в виде модуля.


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


Повторное использование




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

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


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


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


И такой сервис есть! Он называется NPM (npmjs.org). NPM – онлайн-база модулей и инструмент для скачивания и апгрейда модулей, от которых зависит ваша программа. он вырос из Node.js, окружения JavaScript, не требующего браузера, которое мы обсудим в главе 20, но также может использоваться и в браузерных программах.


Устранение связей (Decoupling)




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

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


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


Использование функций в качестве пространств имён




Функции – единственная вещь в JavaScript, создающая новую область видимости. Если нам нужно, чтобы у модулей была своя область видимости, придётся основывать их на функциях.

Обратите внимание на простейший модуль, связывающий имена с номерами дней недели – как делает метод getDay объекта Date.



var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
function dayName(number) {
return names[number];
}

console.log(dayName(1));
// → Вторник


Функция dayName – часть интерфейса модуля, а переменная names – нет. Но хотелось бы не загрязнять глобальное пространство имён.


Можно сделать так:



var dayName = function() {
var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
return function(number) {
return names[number];
};
}();

console.log(dayName(3));
// → Четверг


Теперь names – локальная переменная безымянной функции. Функция создаётся и сразу вызывается, а её возвращаемое значение (уже нужная нам функция dayName) хранится в переменной. Мы можем написать много страниц кода в функции, объявить там сотню переменных, и все они будут внутренними для нашего модуля, а не для внешнего кода.


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



(function() {
function square(x) { return x * x; }
var hundred = 100;

console.log(square(hundred));
})();
// → 10000


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


А зачем мы заключили функцию в круглые скобки? Это связано с глюком синтаксиса JavaScript. Если выражение начинается с ключевого слова function, это функциональное выражение. А если инструкция начинается с function, это объявление функции, которое требует названия, и, так как это не выражение, не может быть вызвано при помощи скобок () после неё. Можно представлять себе заключение в скобки как трюк, чтобы функция принудительно интерпретировалась как выражение.


Объекты в качестве интерфейсов


Представьте, что нам надо добавить ещё одну функцию в наш модуль «день недели». Мы уже не можем возвращать функцию, а должны завернуть две функции в объект.



var weekDay = function() {
var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
return {
name: function(number) { return names[number]; },
number: function(name) { return names.indexOf(name); }
};
}();

console.log(weekDay.name(weekDay.number("Sunday")));
// → Sunday


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



(function(exports) {
var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];

exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};
})(this.weekDay = {});

console.log(weekDay.name(weekDay.number("Saturday")));
// → Saturday


Отсоединяемся от глобальной области видимости




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

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


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


Нам понадобятся две вещи. Во-первых, функция readFile, возвращающая содержимое файла в виде строки. В стандартном JavaScript такой функции нет, но разные окружения, такие как браузер или Node.js, предоставляют свои способы доступа к файлам. Пока притворимся, что у нас есть такая функция. Во-вторых, нам нужна возможность выполнить содержимое этой строки как код.


Выполняем данные как код




Есть несколько способов получить данные (строку кода) и выполнить их как часть текущей программы.

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



function evalAndReturnX(code) {
eval(code);
return x;
}

console.log(evalAndReturnX("var x = 2"));
// → 2


Способ лучше – использовать конструктор Function. Он принимает два аргумента – строку, содержащую список имён аргументов через запятую, и строку, содержащую тело функции.



var plusOne = new Function("n", "return n + 1;");
console.log(plusOne(4));
// → 5


Это то, что нам надо. Мы обернём код модуля в функцию, и её область видимости станет областью видимости нашего модуля.


Require




Вот минимальная версия функции require:

function require(name) {
var code = new Function("exports", readFile(name));
var exports = {};
code(exports);
return exports;
}

console.log(require("weekDay").name(1));
// → Вторник


Так как конструктор new Function оборачивает код модуля в функцию, нам не надо писать функцию, оборачивающую пространство имён, внутри самого модуля. А так как exports является аргументом функции модуля, модулю не нужно его объявлять. Это убирает много мусора из нашего модуля-примера.



var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];

exports.name = function(number) {
return names[number];
};
exports.number = function(name) {
return names.indexOf(name);
};


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



var weekDay = require("weekDay");
var today = require("today");

console.log(weekDay.name(today.dayNumber()));


У такого простого варианта require есть недостатки. Во-первых, он загрузит и выполнит модуль каждый раз, когда его грузят через require – если у нескольких модулей есть одинаковые зависимости, или вызов require находится внутри функции, которая вызывается многократно, будет потеряно время и энергия.


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


Вторая проблема – модуль не может экспортировать переменную напрямую, только через объект export. К примеру, модулю может потребоваться экспортировать только конструктор объекта, объявленного в нём. Сейчас это невозможно, поскольку require всегда использует объект exports в качестве возвращаемого значения.


Традиционное решение – предоставить модули с другой переменной, module, которая является объектом со свойством exports. Оно изначально указывает на пустой объект, созданный require, но может быть перезаписано другим значением, чтобы экспортировать что-либо ещё.



function require(name) {
if (name in require.cache)
return require.cache[name];

var code = new Function("exports, module", readFile(name));
var exports = {}, module = {exports: exports};
code(exports, module);

require.cache[name] = module.exports;
return module.exports;
}
require.cache = Object.create(null);


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


Такой стиль системы модулей называется CommonJS, по имени псевдостандарта, который первым его описал. Он встроен в систему Node.js. Настоящие реализации делают гораздо больше описанного мною. Главное, что у них есть более умный способ перехода от имени модуля к его коду, который разрешает загружать модули по относительному пути к файлу, или же по имени модуля, указывающему на локально установленные модули.


Медленная загрузка модулей




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

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


Второй вариант – оборачивать код модуля в функцию, чтобы загрузчик модулей сначала грузил зависимости в фоне, а потом вызывал функцию, инициализирующую модуль, после загрузки зависимостей. Этим занимается система AMD (асинхронное определение модулей).


Наша простая программа с зависимости выглядела бы в AMD так:



define(["weekDay", "today"], function(weekDay, today) {
console.log(weekDay.name(today.dayNumber()));
});


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


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



define([], function() {
var names = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
return {
name: function(number) { return names[number]; },
number: function(name) { return names.indexOf(name); }
};
});


Чтобы показать минимальную реализацию define, притворимся, что у нас есть функция backgroundReadFile, которая принимает имя файла и функцию, и вызывает эту функцию с содержимым этого файла, как только он будет загружен. (В главе 17 будет объяснено, как написать такую функцию).


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


Функция getModule принимает имя и возвращает такой объект, и убеждается в том, что модуль поставлен в очередь загрузки. Она использует кеширующий объект, чтобы не грузить один модуль дважды.



var defineCache = Object.create(null);
var currentMod = null;

function getModule(name) {
if (name in defineCache)
return defineCache[name];

var module = {exports: null,
loaded: false,
onLoad: []};
defineCache[name] = module;
backgroundReadFile(name, function(code) {
currentMod = module;
new Function("", code)();
});
return module;
}


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


Функция define сама использует getModule для загрузки или создания объектов модулей для зависимостей текущего модуля. Её задача – запланировать запуск функции moduleFunction (содержащей сам код модуля) после загрузки зависимостей. Для этого она определяет функцию whenDepsLoaded, добавляемую в массив onLoad, содержащий все пока ещё не загруженные зависимости. Эта функция сразу прекращает работу, если есть ещё незагруженные зависимости, так что она выполнит свою работу только раз, когда последняя зависимость загрузится. Она также вызывается сразу из самого define, в случае когда никакие зависимости не нужно грузить.



function define(depNames, moduleFunction) {
var myMod = currentMod;
var deps = depNames.map(getModule);

deps.forEach(function(mod) {
if (!mod.loaded)
mod.onLoad.push(whenDepsLoaded);
});

function whenDepsLoaded() {
if (!deps.every(function(m) { return m.loaded; }))
return;

var args = deps.map(function(m) { return m.exports; });
var exports = moduleFunction.apply(null, args);
if (myMod) {
myMod.exports = exports;
myMod.loaded = true;
myMod.onLoad.every(function(f) { f(); });
}
}
whenDepsLoaded();
}


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


Первое, что делает define, это сохраняет значение currentMod, которое было у него при вызове, в переменной myMod. Вспомните, что getModule прямо перед исполнением кода модуля сохранил соответствующий объект модуля в currentMod. Это позволяет whenDepsLoaded хранить возвращаемое значение функции модуля в свойстве exports этого модуля, установить свойство loaded модуля в true, и вызвать все функции, ждавшие загрузки модуля.


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


Настоящая реализация AMD гораздо умнее подходит к превращению имён модулей в URL и более надёжна, чем показано в примере. Проект RequireJS предоставляет популярную реализацию такого стиля загрузчика модулей.


Разработка интерфейса




Разработка интерфейсов – один из самых тонких моментов в программировании. Любую нетривиальную функциональность можно реализовать множеством способов. Поиск работающего способа требует проницательности и предусмотрительности.

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


Предсказуемость




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

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


Компонуемость




Старайтесь использовать в интерфейсах настолько простые структуры данных, насколько это возможно. Делайте так, чтобы функции выполняли простые и понятные вещи. Если это применимо, делайте функции чистыми (см. Главу 3).

К примеру, частенько модули предлагают свою версию массивоподобных коллекций объектов со своим интерфейсом для подсчёта и извлечения элементов. У таких объектов нет методов map или forEach, и никакая функция, ожидающая настоящий массив, не сможет с ними работать. Это пример плохой компонуемости – модуль нельзя легко скомпоновать с другим кодом.


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


Многослойные интерфейсы




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

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


Итог




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

Хотя JavaScript совершенно не помогает делать модули, его гибкие функции и объекты позволяют сделать достаточно неплохую систему модулей. Область видимости функций используется как внутреннее пространство имён модуля, а объекты используются для хранения наборов переменных.


Есть два популярных подхода к использованию модулей. Один – CommonJS, построенный на функции require, которая вызывает модули по имени и возвращает их интерфейс. Другой – AMD, использующий функцию define, принимающую массив имён модулей и, после их загрузки, исполняющую функцию, аргументами которой являются их интерфейсы.


Упражнения




Названия месяцев



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

// Ваш код

console.log(month.name(2));
// → March
console.log(month.number("November"));
// → 10


Вернёмся к электронной жизни



Надеюсь, что глава 7 ещё не стёрлась из вашей памяти. Вернитесь к разработанной там системе и предложите способ разделения кода на модули. Чтобы освежить вам память – вот список функций и типов, по порядку появления:

Vector
Grid
directions
directionNames
randomElement
BouncingCritter
elementFromChar
World
charFromElement
Wall
View
WallFollower
dirPlus
LifelikeWorld
Plant
PlantEater
SmartPlantEater
Tiger


Не надо создавать слишком много модулей. Книга, в которой на каждой странице была бы новая глава, действовала бы вам на нервы (хотя бы потому, что всё место съедали бы заголовки). Не нужно делать десять файлов для одного мелкого проекта. Рассчитывайте на 3-5 модулей.


Некоторые функции можно сделать внутренними, недоступными из других модулей. Правильного варианта здесь не существует. Организация модулей – вопрос вкуса.


Круговые зависимости



Запутанная тема в управлении зависимостями – круговые зависимости, когда модуль А зависит от Б, а Б зависит от А. Многие системы модулей это просто запрещают. Модули CommonJS допускают ограниченный вариант: это работает, пока модули не заменяют объект exports, существующий по-умолчанию, другим значением, и начинают использовать интерфейсы друг друга только после окончания загрузки.

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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


пятница, 14 ноября 2014 г.

16 занимательных проектов для вашего нового Raspberry Pi


Новый Raspberry Pi стал меньше, дешевле (всего $20) и энергоэффективней. Неплохое обновление для устройства, которое и так отлично продаётся. Мы собрали наиболее крутые проекты на базе Pi, которые можно реализовать как на новой, так и на оригинальной модели.


1. Stop-motion камера



Есть множество способов использовать дополнительный модуль камеры с Pi. И один из них — stop-motion анимация. Берете фигурки Lego и можете снимать целые полнометражные фильмы, используя эту технику. Кроме Raspberry Pi и модуля камеры также понадобятся макетная плата, джамперы и тактильная кнопка.


Подробную инструкцию можно найти на сайте Raspberry Pi.


2. Беспроводная точка доступа

Настройте доступ к сети с помощью Raspberry Pi. По желанию вы также сможете создать отдельную гостевую сеть. Для этого вам нужны SD-карта, сетевой USB-адаптер и немного навыков кодинга, чтобы всё правильно настроить.


Смотри руководство на сайте The MagPi.


3. Тачскрин для приборной панели авто



Если вы готовы потратить достаточное количество времени, попробуйте создать на базе Raspberry Pi сенсорную панель, которая сэкономит вам несколько сотен долларов. Благодаря открытому софту XBMC Media Center вы сможете проигрывать музыку, смотреть видео и фото, а также много чего ещё (только не забывайте следить за дорогой).


Детали найдёте на Instructables.


4. Робот

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


Детальная инструкция на сайте Raspberry Pi.


5. Камера с датчиком движения

Хотите узнать, кто заходил в ваш кабинет, пока вас не было на месте? Или может сделать удачный кадр белки, которая зачастила к вам во двор? Камера с датчиком движения на базе Raspberry Pi может выручить вас. Для её создания вам понадобятся пассивный ИК-датчик и навыки кодинга, но затраченное на гаджет время окупится сполна.


На Instructables есть детальное видеоруководство.


6. Стратостат



Благодаря прикреплённому к аэростату Raspberry Pi можно запечатлеть невероятной красоты кадры из стратосферы. Отслеживать за его перемещением можно с помощью GPS, а снимки получить удалённо, если устройство-таки затерялось.


В блоге Дейва Акермана вы найдёте массу полезных ссылок.


7. Конвертер речи

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


За деталями о проекте заходите на сайт Kolbire.


8. Фотокамера



Есть несколько разных способов сделать простую фотокамеру на базе Raspberry Pi. И чем больше времени вы потратите на её создание, тем лучше результат. Если вы сможете перепаять элементы на плате, у вас может получиться тонкая камера с TFT-экраном в корпусе, распечатанном на 3D-принтере. Как это сделать? Смотри видео.


9. Ламповые часы

Сделать часы с помощью Raspberry Pi и ламп Nixie не такая уж и сложная задача, а результат впечатляет. К тому же, если подключить их к интернету, можно настроить автоматический переход на летнее время и обратно. Всего несколько строчек кода — и у вас на столе беспроводное устройство.


Смотрите инструкции на сайте Мартина Олдфила.


10. Передатчик азбуки Морзе



Raspberry Pi позволит объединить старые и новые технологии и создать передатчик азбуки Морзе. Вам придётся потрудиться, чтобы запрограммировать Pi, но когда всё будет закончено, у вас на руках будет устройство способное кодировать и декодировать Морзе. Можно даже заморочиться и создать олдскульный передатчик.


Полное руководство на сайте Raspberry Pi.


11. Метеостанция

Компактная, дешёвая, с малым расходом энергии — Raspberry Pi идеально подходит для создания собственной метеостанции. После этого вам больше не придётся полагаться на прогноз погоды по ТВ. Правда, понадобится немного «железа», но всё не так сложно, как может показаться на первый взгляд.


Больше — на DragonTail.


12. Скворечник



Коробка плюc Raspberry Pi — и у вас появится возможность наблюдать, как живут птицы. Можно даже настроить онлайн-стрим. Среди необходимых деталей — инфракрасные светодиоды и модуль камеры NoIR.


Смотри инструкцию на сайте Raspberry Pi.


13. Wi-Fi-печь



Для создания этой буржуйки Джеймс Гао взял старую электропечь, Raspberry Pi и детали, распечатанные на 3D-принтере. С помощью системы обратной связи с замкнутым контуром осуществляется удалённое управление, а также автоматически регулируется уровень температуры (для этого нужны термопара и шаговый мотор).


Код и фотографии можно найти в блоге Raspberry Pi.


14. Ретро игровая консоль

Raspberry Pi легко можно превратить в консоль, карманный компьютер или олдскульный игровой автомат. Самый простой способ — загрузить эмулятор на SD-карту и подключить USB-контроллеры. Можно уложиться за час, RetroPie вам в помощь.


Полное руководство есть на Lifehacker.


15. Булава для жонглирования



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


Смотрите блог Лорен Эгт, там есть все детали проекта.


16. Цифровой сад



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


На YouTube-канале Geek Gurl Diaries есть пошаговое руководство.


Перевод материала «16 Fun Projects for Your New Raspberry Pi» с fieldguide.gizmodo.com.


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Ловцы молний. Необычные эксперименты с грозой

Добрый день, уважаемые читатели хабра.

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

image

Меня всегда вдохновляла красота и мощь молний. Сила тока в разряде молнии достигает 10-300 тысяч ампер, а напряжение — от десятков миллионов до миллиарда вольт. Мощность разряда — от 1 до 1000 ГВт. Вот было бы хорошо «приручить» эту энергию!

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

Рождение идеи




Первое, что приходит на ум, это подвести к грозовой туче провод и разрядить заряд на землю. Но как поднять провод так высоко? Обдумав все возможные варианты я пришел к выводу, что это можно сделать с помощью воздушного змея. Еще до того как началась гроза я хорошенько испытал воздушного змея. Меня приятно обрадовала его подъёмная сила! Даже в небольшой ветер змей подымал достаточно тяжёлые грузы, а в сильный ветер его с трудом удавалось удержать за леску. Но провод змей высоко поднять не мог, так как уже 100 метров провода весило 2 кг и провод обладал большой парусностью — его сдувало ветром в сторону. Решено было заменить провод тонкой проволокой. Ничего, что проволока не выдержит огромный ток молнии и мгновенно сгорит, на месте проволочки образуется ионизированный канал, и по малому сопротивлению этого канала пройдет основной заряд молнии. Чтобы добиться минимального веса, парусности и как следствие максимальной высоты я использовал проволоку разной толщины: первые 100 метров от змея — самая толстая ≈0,3 мм, следующие 100 метров — тоньше, и так далее, чтобы она не порвалась под собственным весом. Леску, на которой я пускал змея тоже выбрал как можно тоньше — 0,25 мм. Змея она держала надёжно. Пробный запуск показал, что змей с проволокой способен взлететь на высоту 300 — 500 метров. Тучи конечно выше, но попробовать всё-таки стоит!

Первый опыт






Дождавшись грозовой погоды, мы бросаем все дела, прыгаем на скутер и летим на максимальной скорости под тучу! В то самое место, где сильнее сверкают молнии и гремит гром. Это настолько захватывающе, что сильный ветер и ливень для нас уже не помеха! Добравшись на место, мы разматываем 200 метров лески и ложем её ровной линией на землю. Привязываем воздушного змея и ставим возле него баллон, вокруг которого аккуратно намотана проволока. Баллон ставим на изолированный ящик и заземляем его через измерительные токовые шунты, а так-же подсоединяем различные бытовые приборы, что бы посмотреть, что с ними будет после разрушительной силы грозы. Как только змей начинает взлетать, мы убегаем на безопасное расстояние и наблюдаем за происходящим. Змей довольно не плохо взлетел, но молния ни как не хотела в него попадать, хотя рядом громко громыхала. Мы пробовали ещё несколько раз в другом месте и опять неудачно. Стало ясно, что нужно что-то менять.

Ура! Нам удалось покорить грозу!




Молния вблизи, да еще и вызванная тобой, это действительно круто.

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



В прошлом ролике я подвязал тоненькую проволочку к змею и запустил его в грозу, но ничего не вышло. Теперь я доработал эту технологию и подал на проволочку высокое напряжение из телевизора «Юность». На аноде кинескопа в нём используется 10 000 вольт! Этого вполне достаточно, что бы вызвать начальную ионизацию. В темноте, даже можно наблюдать, как светится коронный разряд, на кончике проволочки, который закреплён на верхушке змея. В грозовую погоду я выехал за город и на высоком холме включил портативный телевизор «Юность» от аккумулятора. Корпус телика я хорошенько заземлил, а высоковольтный вывод подключил к тоненькой медной проволоке, намотанной на бутылке. Пока воздушный змей набирал высоту, проволока легко сматывалась с бутылки. Я в это время, наблюдал за процессом из безопасного места. Змей то набирал высоту, то опускался, от чего проволока касалась земли и искрила. При очередном порыве ветра змей резко рванул вверх и молния с оглушительным треском бахнула в телевизор. Я не ожидал, что от молнии будет на столько сильная ударная волна, которой отбросило мою видеокамеру. Ощущения от молнии просто непередаваемые! Звук — как взрыв артиллерийского снаряда, только внушительнее и резче. Вспышка — это нечто! Рассмотреть её удалось хорошо, так как её я видел несколько минут, особенно если глаза закрыть. А внутренние ощущения не передать словами! Мы после молнии не сразу пришли в себя. Просто не верилось, что такое можно сделать своими руками! А потом, как не совсем вменяемые бегали по лесу, опасаясь, что на такой шум могут приехать военные.

Всего за 5 минут мы делители домой и теперь можно спокойно изучить последствия удара молнии. Если рассмотреть видео, которое я заснял, по кадрам, то можно заметить искры, которые расходятся кольцами от телевизора — это магнитной индукцией сорвало оставшиеся витки проволоки с бутылки. Потом видно как молния перескочила на антенну телевизора и мгновенно её испарила! Молния вышла из переключателя каналов в землю, оплавив его как после сварки. Провод от аккумулятора отгорел. Расплавленной земли в месте где ударила молния, я почему то не увидел. Может мне попалась слабенькая молния! Но за то обнаружил три отверстия на земле, вокруг которых выгорела трава. Получается, что молния вошла в землю в трёх разных местах, одно возле переключателя каналов телевизора, а другие в метре от телевизора! Почему так произошло? Может быть была серия молний и каждая ударила в новое место? А что же случилось с телевизором? К моему удивлению кинескоп не взорвался, на нем появились какие то странные пятна. Задняя стенка слетела, оплавилась и покрылась пузырьками. Антенна полностью испарилась, остался только пиптык. Плата покрылась странным фиолетовым налётом и много дорожек перегорело. Из динамика вырвало мембрану. А вот аккумулятор жалко. Хоть он и находился в стороне и в него не было прямого попадания молнии, он оплавился и потрескался и полностью разрядился. После полной зарядки, к моему удивлению, он заработал нормально! И трещины оказались не сквозными — заплавленными изнутри.

Теперь главный секрет молнии разгадан. А во что ты хотел бы разрядить грозу? Напиши в комментариях и мы сделаем это!

А почему бы нам не получить шаровую молнию?




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



Жаль, что фотоаппарат, который снимал на видео воздушного змея, выключился и не заснял то, как он светился. Вообще, заснять молнию близко, не такая уж и лёгкая задача. От мощного электромагнитного импульса фотик зависает, а флешка из него не читается. Но одна камера оказалась более выносливой и не разу не выключилась за время съёмок. Но тут я столкнулся с другой трудностью. Вспышка молнии вблизи выглядит очень ярко, как тысяча дуг от сварочного аппарата. Камера не успевает подстроить экспозицию и ослепляется, из-за чего кадр с молнией получается засвеченным. Уменьшение экспозиции и спортивный режим съёмки тут не помогает. Конечно в идеале грозовые явления нужно снимать скоростной камерой, но стоимость такой камеры просто шокирует: Sony NEX-FX700R которая способна снимать 960 кадров в секунду, стоит 7000$, а Fastec TS3Cine на 10000 кадров в сек. стоит 30000$. Даже на списанную камеру в убитом состоянии я не скоро насобираю деньги. Может ты знаешь, чем можно заснять качественно грозовые явления? Делись своими идеями! Буду рад любой помощи.

Самое интересное и необычное впереди!




Жаль, но сезон гроз закончился. А ещё так много идей осталось не выполненных! Ну а пока на улице холода, самое время хорошенько подготовится к следующему сезону. Я уже готовлю десяток усовершенствованных установок для ловли молний. Проволочка будет подыматься с помощью модельного ракетного двигателя, что даст значительный прирост в высоте. Управление запалом будет дистанционное, что повысит безопасность. Все необходимые приборы и проволока будут заранее закреплены в каждой установке так, что выехав на место, не придётся терять драгоценного времени. Готовлю подходящую видеоаппаратуру, чтобы качественно заснять молнию в полный ракурс. Получить SLOW-MO кадры удара молнии в:

— дерево

— баллон с газом

— телефон nokia

— работающую микроволновку

— и многое другое (предложи в комментариях)

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

Ужасы нашего городка




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

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

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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Приглашаем на конференцию по web-разработке 29 ноября


Друзья, 29 ноября в московском офисе Mail.Ru Group пройдёт конференция, посвящённая веб-разработке в мобильном и «большом» сегментах сети. Перед слушателями выступят специалисты, работающие в проектах Почта Mail.Ru, Одноклассники, Облако Mail.Ru и Редактор документов.


Программа конференции:


Андрей Сумин, руководитель разработки клиентской части Mail.Ru, представит доклад «Редизайн, ни одного пользователя не пострадало».


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



Иван Чашкин, разработчик touch Почты Mail.Ru, назвал своё выступление «Батарейка в телефоне, как сберечь».


Технологии развиваются, процессоры становятся быстрее, а памяти больше. И лишь одно остается практически неизменным, аккумулятор и сутки работы девайса. В докладе Иван покажет самый low-level расхода батареи на примере iPhone 6, и мы попробуем понять, куда уходит заряд.


Егор Дыдыкин, тимлид frontend-команды Облако Mail.Ru, расскажет как «Сделать и выкатить общий блок на весь портал». Будет освещен процесс разработки и внедрения единого кросс-портального компонента, интегрируемого в разные среды с разными нуждами и задачами.



  1. Динамическое построение конфигурируемых на клиенте блоков, перестроение и перекомпоновка.

  2. Сложности расчетов и внедрения.

  3. Организация асинхронного API для взаимодействия с проектом.

  4. Темизация компонентов.

  5. Профит.




Александр Русаков, разработчик Редактора Mail.Ru, расскажет о длительном пути, который пришлось пройти для решения поставленной задачи — просмотр и редактирование офисных документов в браузере. Отобразить на клиентской стороне текст со сложным форматированием, картинками и диаграммами не кажется сложной задачей, пока не появляется требование добиться идентичного отображения в разных браузерах.

В докладе будут освещены следующие темы:



  • проблемы использования стандартных методов при расчете размеров текста и отображения документа средствами HTML и SVG;

  • почему мы отказались от использования вложенных структур данных при редактировании и что мы используем вместо них;

  • как мы перешли на Canvas, его достоинства для нас и недостатки;

  • как один и тот же код работает и на клиенте, и на сервере; наш опыт использования Node.js.




Константин Лебедев, ведущий JavaScript разработчик проекта Почта Mail.Ru, последние несколько лет занимается разработкой Open Source решений. Имеет опыт внедрения, продвижения и поддержки решения как в рамках компании, так и за её пределами.

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


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



  • Мобильные особенности — из чего строится взаимодействие с пользователем мобильного устройства, почему ему нелегко, а разработчику еще тяжелее.

  • Вёрстка — древнее искусство двигать пиксели по экрану во имя юзера и здесь имеет свои особенности, связанные с ограниченностью мобильных устройств (ничего личного) и разнообразным животным миром их браузеров.

  • JavaScript — вы же не думали, что такой во всем особенный мобильный мир никак не задел ваш любимый JavaScript? Конечно, и здесь есть свои странные подходы, как правило связанные с оптимизацией и характером устройства.

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




Как обычно, адрес нашего офиса — Ленинградский проспект 39, строение 79. Не забудьте зарегистрироваться и взять паспорт или водительские права. Сбор гостей начинается в 9.00, а сама конференция стартует в 10.00.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Из жизни юзабилити-лаборатории Mail.Ru Group


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



Юзабилити-лаборатория Mail.Ru Group оснащена мощным оборудованием для проведения различных исследований с пользователями. Это и монитор Tobii со встроенным Eyetracking; и мобильный стенд для тестирования смартфонов и планшетов, также способный записывать движение глаз; и BioPac — аппарат для записи психофизиологических реакций пользователя.





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


Мы подсчитали, что через юзабилити-лабораторию Mail.Ru Group проходит, в среднем, 40-50 человек в месяц, и сегодня мы расскажем про разные милые и курьезные случаи, связанные с респондентами.




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


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


Ещё один пришёл к нам в тапочках (нет, это был не сотрудник).


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


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


Очень мы переживали, когда к нам приходили сильно беременные женщины на тестирование проекта «Дети», но всё прошло хорошо. Ещё бывало, что приходили мамочки с детьми. Пока мамы были заняты, дети развлекались в нашей лаундж-зоне.


А теперь приведём цитаты респондентов, которые мы бережно собираем и вывешиваем на стену над рабочими столами.


Юзабилити-тестирование подарков в соцсети Одноклассники. Пользователи говорят про подарки:



  • «Рога один раз принял с дуру… Ну, подумал, сами отпадут»

  • «Когда мужчина дарит такую шляпу, это очень грустно… Ты ещё шубу мне ВКонтакте подари!»

  • «Размер — это не главное»




Юзабилити-тестирование сайта ArcheAge:


  • «Погоди аккаунт создавать, у нас ещё предварительные ласки не закончились»

  • «Море можно сделать поводянистее»




Юзабилити-тестирование проекта Авто Mail.Ru:


  • «Меня мало интересуют законы, потому что я их не соблюдаю» [про ПДД]

  • «Такая статья длинная, вот бывает такое, что автора понесло и вынесло»

  • «Я не люблю писать, не люблю читать»




Интервью с пользователями проекта Поиск Mail.Ru:


  • «Я в этом плане такой консервант» [про принятие новых фич]

  • «Мы перелопатили половину интернета»

  • «За столько времени в интернете всё уже найдено»




И на десерт, мы сняли небольшое видео о нашей лаборатории:


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


VexorCI под капотом

Привет, Хабр! Время долгожданного поста про внутреннее устройство Vexor – облачного continuous integration для разработчиков, позволяющего эффективно тестировать проекты и платить только за те ресурсы, которые реально используются.

image



История


Проект появился из внутренних наработок компании Evrone. Изначально для прогона тестов мы использовали Jenkins. Но когда стало очевидно, что CI-сервис нужно дорабатывать под наши специфические задачи, мы поменяли его на GitlabCI. Этому было несколько причин:



  • GitlabCI написан на Ruby, который является родным языком команды

  • он небольшой и простой, а значит его легко дорабатывать.




За время использования, как это часто бывает, GitlabCI довольно сильно мутировал. И в тот момент, когда он уже имел мало общего с оригиналом, мы просто переписали все с нуля. Так появилась первая версия Vexor, которая использовалась только внутри команды.

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


Но если задуматься, то понимаешь:



  • По ночам и выходным воркеры под тесты не нужны вообще.

  • Если команда большая и процесс построен так, что одновременно делается много коммитов, то параллельных воркеров нужно очень и очень много. Например, если используются недельные итерации, то обычно в конце итерации релизится несколько фич и одновременно делается 5-20 пулл-реквестов, каждый их которых сопровождается прогоном тестов. Складывается ситуация, при которой нужно, например 20+ воркеров.




Очевидно, что воркеры нужно поднимать и удалять автоматически, ориентируясь на текущие запросы.

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


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


Первоначально в качестве облака мы использовали Amazon EC2. Но на Amazon диски, которые подключаются к серверам, физически находятся не на хосте, а в отдельном хранилище, которое подключено по сети. Когда диски интенсивно используются (а скорость прогона тестов очень зависит от скорости работы дисков), скорость будет упираться в пропускную способность канала до хранилища и выделенной полосы. Amazon решает эту проблему только за отдельные деньги, платить которые совершенно не хотелось. Рассмотрели другие варианты: Rackspace, DigitalOcean, Azure и GCE. Сравнив их, мы остановились на Rackcspace.


Архитектура


Теперь немного об архитектуре.


image


VexorCI – не монолитное приложение, а набор связанных между собой приложений. Для общения между ними используется преимущественно RabbitMQ. Чем в нашем случае хорош кролик:



  • Он умеет message acknowledgement и publisher confirm. Это позволяет решить массу проблем, в частности, позволяет писать приложения в популярном в Erlang’е стиле “Let it crash”. То есть при возникновении любых проблем происходит падение, но как только сервис вернется к нормальной работе, все задачи будут выполнены и ни одна из их не будет потеряна.

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

  • RabbitMQ устойчиво работает с сообщениями большого размера. Рекорд на сегодняшний день – 120Mb в одном сообщении. В VexorCI не стоит задача обрабатывать миллионы сообщений в минуту, но само сообщение может весить десятки Mb или больше (например, при передаче логов).




Есть у RabbitMQ и известные недостатки, с которыми тоже приходится иметь дело:

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

  • C RabbitMQ сложно обеспечить высокую доступность. Есть целых три решения для этого, но реальную высокую доступность обеспечивают только federation и shovel. Которые, в отличие от cluster (про который можно прочитать тут), не так просто интегрировать в существующую архитектуру приложения, так как они не обеспечивают консистентность данных.




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

Логи


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


В Vexor за это отвечает связка elasticsearch+logstash+logstash-forwarder. Все логи наших приложений пишутся сразу в JSON формате, логируются все события с приложений, а также дополнительно собираются логи PG, RabbitMQ, Docker и прочие системные сообщения (dmesg, mail и другие). Мы стараемся писать все по максимуму, ведь воркеры работают только определенное время. После отключения сервера с воркером мы не узнаем о проблеме ничего, кроме того, что сохранится в логах.


Контейнер


Для запуска тестов воркерами мы используем Docker. Это отличное решение для работы с изолированными контейнерами, которое предоставляет все необходимые инструменты. Сейчас Docker работает очень стабильно и доставляет минимум проблем (особенно при условии, что у вас свежее ядро OS). Хотя баги тоже встречаются, например, вот такой.


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


Для того, чтобы использовать один образ для всех поддерживаемых языков и не делать размер образа слишком большим, нужные версии языков (Ruby, Python, Node.js, Go — полный и актуальный список поддерживаемых языков тут) мы ставим из пакетов при запуске билдов. Эта довольно быстрая процедура занимает несколько секунд, при этом такое решение позволяет нам легко поддерживать большой набор версий языков, не перегружая образ.


Deb-пакеты для образа мы пересобираем минимум раз в неделю. Они всегда доступны в публичном репозитории по адресу “http://ift.tt/1BnqG2I main”. Если, например, вы используете Ubuntu 14.04 amd64, то при его подключении можете получить сразу 12 версий Ruby, уже скомпиллированных вместе с последними версиями bundler и gem, полностью готовыми к установке.


Для того чтобы не делать apt-get update при установке пакетов в runtime и использовать fuzzy matching для версий, мы написали утилиту, с помощью которой можно очень быстро ставить пакеты нужных версий из нашего репозитория, например:


$ time vxvm install go 1.3

Installed to /opt/vexor/packages/go-1.3



real 0m3.765s


Конфигурация


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


Чтобы не изобретать велосипед, мы используем файл конфигурации .travis.yml. Travis CI один из самых популярных и известных сервисов сегодня, а значит хорошо, если пользователи будут испытывать минимум трудностей при переходе с него. Ведь если в корневой директории проекта уже есть .travis.yml, то все запустится мгновенно. Ну и команда получит радости быстрого CI за наши скромные поминутные тарифы :)


Серверы


Мы администрируем много серверов, на которых выполняется много задач. Поэтому мы активно используем различные инструменты, например Ansible, Packer и Vagrant. Ansible отвечает за выделение и настройку серверов и отлично выполняет свои задачи. Packer и Vagrant используются для сборки и тестирования образов Docker и серверов с воркерами. Для автоматизации сборки образов используется сам же VexorCI, который автоматически пересобирает все нужное.


Кому подходит наш проект?


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


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


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


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


This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.