...

суббота, 26 октября 2013 г.

Дайджест интересных материалов из мира веб-разработки и IT за последнюю неделю № 80 (20 — 26 октября 2013)

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




Метки лучше разделять запятой. Например: общение, социальные сети, myspace.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 fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Google: качество управления роботизированных авто выше, чем у живых водителей

Крис Урмсон (Chris Urmson), глава подразделения Google, занимающегося проектом автомобилей с автономным управлением, выступая на конференции RoboBusiness в своём докладе Where the Robot Meets the Road: Realizing Self-Driving Vehicles представил аудитории результаты исследований, из которых следует, что сенсоры и программное обеспечение в целом справляется с управлением авто лучше, чем даже профессиональные водители, принимающие участие в тестировании.



Google проводит программу по тестированию автономных автомобилей (участвуют Lexus и Prius) на территории Калифорнии, Невады и Флориды с 2010 года — законодательство этих штатов разрешает подобные исследования. В ходе этой программы собирались и анализировались данные, которые были оформлены в два отчёта.

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


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


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


[Источник]


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 fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Вышел WordPress 3.7 “Basie”


сегодня в 20:17



24 октября была выпущена очередная версия известной CMS Wordpress. Wordpress 3.7 носит название 'Basie' в честь известного джазового пианиста Каунта Бэйси.


Нововведения




В этом релизе были сделаны важные архитектурные обновления:


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

  • Улучшены рекомендации по созданию пароля. Ваш пароль — это первая линия обороны сайта в случае хакерской атаки, поэтому разработчики ввели более жесткую проверку пароля. Теперь WordPress сообщит вам о низком уровне безопасности, если пароль сайта будет содержать даты, имена, часть алфавита или раскладки клавиатуры («abcdefg», «qwerty') и подобные вещи.

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




Так же вас ждут более 400 закрытых задач в Trac, множество возможностей для управления автоматическим обновлением, поддержка более сложных date query и другие фишки, узнать подробнее о которых можно в кодексе.

Следующая версия Wordpress будет выпущена в декабре.



Developers, stick with Russians – work in London




Переводы с

карты на карту


Переводы

через QR-Код


Новая функция

«Мой контроль»




Возьми Lumia 925 на тест-драйв сейчас.




Впечатляющие возможности

в стильном тонком корпусе из металла




Boomburum

исследует LTE


Эволюция средств связи

в путешествии по России




Проблемы коммуникации внутри бизнеса?


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


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 fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Как мы использовали Matlab. История одного фейла

Имена изменены, все совпадения случайны.

Увертюра




Сразу оговорюсь, Matlab — отличный инструмент. Отличный инструмент, который мы использовали не по назначению.

Взгляните на нашу компанию. Наша сфера деятельности — разработка ПО для промышленности и много чего еще. В компании работает около 100 человек, а я в этой компании — один из сотрудников, занимающийся разработкой алгоритмов. Есть у нас и Флагманский продукт, приносящий основную прибыль.


Флагманский продукт — бизнес-приложение. В нем много формочек и отчетиков, своя база данных и вычислительное ядро. Вычислительное ядро написано на C# без привлечения нативного кода. Такое решение было принято разработчиками осознанно. Parallel.For был так соблазнителен, а C++ все хотели забыть как страшный сон.




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


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


Идея


Идея была проста. Вместо того, чтобы платить зарплату одному умному программисту (который бы умел писать хороший код на C# и разбирался в тонкостях нашего «матана», а такого непросто найти), можно взять двух «полуумных». Первый будет писать прототипы на Matlab, второй — переносить решение на C#.

Matlab в таком случае преподносился как инструмент для документирования алгоритма. У такого решения были озвучены следующие преимущества:



  • Matlab — простой язык, который опытный программист может выучить за 1 день

  • Все математики пишут на Matlab




Стоить отметить, что ранее для документирования алгоритма использовался Mathcad, UML-диаграммы и даже просто лист бумаги.

С идеей спорили, ее не принимали. Но в итоге Главный Алгоритмист всех убедил, и была создана команда алгоритмистов, пишущих на матлабе. В эту команду вошел и я. В нашу команду также вошел Самый Бесполезный Программист из числа разработчиков, который должен был бы, не думая, переносить наши матлаб-экзерсисы на C#.


Замечу, что кроме предложений отказаться от этой затеи, было и предложение использовать, по крайней мере, python+numpy. Хотя бы той причине, что сколько-нибудь значительного опыта разработки на Matlab ни у кого не было (я раньше писал на python и R, остальные были математики и инженеры, использующие Matlab как продвинутый калькулятор). Предложения, как можно понять, отклонили.


Прошел год...


Прошел год и сейчас уже можно подвести некоторые итоги:



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

  • Раз уж мы отказались от схем и Mathcad, то появилось желание использовать для наших Matlab программ реальные источники данных. В итоге мы немало времени занимались дублированием части функционала Флагманского продукта на Матлабе

  • Пока мы занимались программированием, времени на работу над собственно алгоритмом оставалось немного

  • Matlab не подходит для разработки больших программ. По крайней мере, не подходит нам. Динамическая нестрогая типизация, медленные циклы, ооочень-медленный ООП (настолько, что пришлось частично отказаться от использования ООП), неполная поддержка разреженных матриц

  • Переносить код с Matlab на C# непросто. Еще сложнее бэкпортировать рефакторинги и оптимизации




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

Вместо заключения


В самом начале статьи я немного слукавил, т.к. история продолжается до сих пор. Мы продолжаем писать на Matlab, наш код продолжают переносить на C#. Хотя теперь уже все согласны, что у идеи Главного Алгоритмиста есть изъяны. Но менять уже что-либо слишком поздно.


А тем временем начинают поговаривать о дополнительном перекодировании решений с Matlab уже на С++…


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 fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Open voting project. Размышления вслух

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

обьём всех результатов соответствует примерно 300ам гигабайтам. т.е. 60 DVD болванок. Это кстати в самом неоптимизированном варианте, который например подразумевает полное написание имён и адресов голосующих. Если же всех их пронумеровать…



Или использовать специально

введённую для этого нумерацию, то можно загнать всё и на одну двухслойную DVD болванку. И так сказать вручить каждому по экземпляру для чательного изучения и на память потомкам.Кстати много типовых имён, фамилий, городов и улиц — это всё можно закодировать номерками, все маруси например 12 а пети 14, сидоровы 155, тогда 0e9b — петя сидоров. Кстати в пресловутых 150метрах вероятно отмечены и дети, а они как известно не голосующие. Есть вообще минималистский вариант, так сказать, asm forever. Это когда мы каждому присваиваем не номер, а номер ячейки в файле, так сказать… Ну ты маруся, типо десятая ячейка, запомни!, а ты, петя — 200010ая. При таком раскладе все результаты с учётом мнения даже малых детей займут 150метров, при этом выбор можно осуществить из 255 вариантов. Итак первое что нам нужно структара файла (базы данных) результатов. Причем имем два требования, первое — персонифицированность результатов + второе минимальный размер. Как всё работает? Очень просто — люди каким-то способом заносят код своего выбора в файл (базу). Ну к примеро это может происходить на базе учебных заведений при этом им печатается справка-подтверждение(она будет нужна в случае последующих нестыковок и разбирательств по данному голосу). Люди приходят — заполняют. наблюдатели наблюдают. Коммисия — консультирует. К концу дня все результаты сливают в один файл(вышеописанный). Потом этот файл зеркалят по туче публичных веб серверов и раздают всем желающим для проверки. На этом этапе уже можно подсчитывать предварительный результат. Наверняка найдутся правокаторы, которые будут утверждать, что их обманули, голос подменили, ну а кто-то может и вправду случайно ошибся. на второй день они могут осуществить редактирование своей ячейки. Вечером второго дня например формируется второй файл, с изменениями, и тоже идет в паблик. Ну что же, можно так играть до трёх раз. По идее, если всё идёт нормально, то людей апротестующих свой результат не должно быть больше процента и в случае большого разрыва между кандидатами, это можно вообще не рассматривать. на третий день подводятся итоги. Копии всех результатов на руках у населения, и в музеях истории, все рады, все смеются. Это так сказать, лишь один из вариантов открытого голосования. Если поколдовать с софтом, можно дотянуть это до уровня голосования по мобильному телефону. Вобщем, коренное отличие открытого голосования от закрытого в том, что при открытом мнение каждого известно каждому. Текущий же закрытый метод не позволяет узнать даже самому голосующему как был учтен его голос.

Персонификаци аутентификация запросов.

Тут конечно огромное поле вариантов. Мне, например, очень нравится система переменных кодов в ВТБшной системе телебанк. Видимо, талантливые парни её разработали. Для тех, кто с ней не знаком, кратко опишу её. Людям даются разовые пластиковые карты(просто пластик не магнитный и не чипованный). На них напечатоно 125 циферок. Циферки скрыты резиновым напылением, как в лотырейном билете. Каждая циферка — это пароль. Но пароль разовый, на одну операцию. Таким образом, злоумышленник даже перехватив пароль, может провернуть лишь одно голосование. При этом введу того, что результат публичен, то голосующий увидит такое вмешательство и произведет изменение результата следующим переменным кодом.


Порядок внедрения (аля продвижения)

Здесь должна была быть иллюстрация в стиле Криса Касперски но по этическим соображениям я отказался от этой идеи.

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


За что мы голосуем.

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


Демократия и BSDm.

Вы когда нибудь задумывались имеет ли демократия что-то общее с подчинением. Кое что имеет. Если получив в руки демократический механизм человек осознано переходит на другой механизм, то по правилам демакратической игры мы не сможем ему помешать, если это касается «только его». К примеру, даже в условиях идеальной демакратической модели человек может переключится на режим подчинённого, режим ведомого. Демакратические законы защишают подобное волеизьявление. К примеру, кто-то не хочет думать сам и пологается на чей-то авторитет, причём добровольно. Если по каким-то причинам таких людей становится 99%, то идеальная модель демократии плавно трансформируется в толпэлитаризм. Получается, сами по себе демократические принцыпы защищаю только от грубоых форм диктатуры, просто напросто переводя политическую игру на новый концептуальный уровень (информационный, интеллектуальный). Хороший пример — экономика. Здесь нет голосований, но есть рынок и деньги, но суть таже. Сколько наших сограждан, к примеру, осознают что делая покупку они принимают участие в голосовании??? А ведь это голосование, к примеру, не мене важно чем выборы президента. Или другой пример у человека появилась свободная минутка, — чем её занять? Это ли не важнейшие выборы?


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 fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Собеседование разработчика

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

Помню, как меня впервые попросили прособеседовать по WPF молодого человека – я несколько часов составлял список того что стоит спросить (и перепроверял ответы, чтобы самому не ударить лицом в грязь) чтобы хоть с какой-то то долей уверенности потом сказать, нужен нам в компании такой человек или нет. И вот, вооружившись 10-15 вопросами, я вхожу в переговорную, представляюсь, задаю пару общих вопросов и между прочим уточняю:

— А сколько у вас лет опыта разработки с использованием WPF?

— Я не знаю WPF….

— …

Этот неловкий момент когда ты понимаешь, что предусмотрел всё, кроме самого очевидного…


Другой, не менее неожиданный для меня поворот был, когда в CV соискателя был указан опыт разработки 5 лет и перечислены куча интригующих описаний проектов, а по факту, человек с трудом смог объяснить, чем отличаются ссылочные типы от значимых, а про сборку мусора сказал, что знает лишь то, что о памяти в .Net думать не нужно…


Что же можно спросить, чтобы можно было положиться на ответ, как на что-то значимое?

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

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

— Просить показать пример кода? Но какой код вам покажут? Сколько людей его уже правило? В каких условиях он был написан? Что если эти блестящие 300 строк писались месяц под шум прибоя атлантического океана в сезон дождей? Сможем ли мы потом воссоздать «рабочую» атмосферу, чтобы получить следующие 300 блестящих строк?


Я хочу поделится своей идей и услышать конструктивную критику такого подхода к проведению собеседований. Моя идея заключается в том, чтобы показывать «СВОЙ» код и слушать. За вечер я набросал пример ужасного кода, включив в него самые распространённые «ошибки». Я ожидаю, что старший разработчик, с реальным опытом разработки от 4 лет, должен идентифицировать более 80% процентов ошибок и указать на существующие проблемы в гипотетической архитектуре.


И так, собственно код:



1 using System;
2 using System.Collections.Generic;
3
4 namespace App.Services
5 {
6 public enum LoginResult
7 {
8 Unknown = 0,
9 Success = 1,
10 WrongLogin = -1,
11 WrongPass = -2,
12 Error
13 }
14
15 public class LoginService
16 {
17 public string LastError = string.Empty;
18
19 /// <summary>
20 /// Allow to login new user
21 /// </summary>
22 /// <param name="login">login</param>
23 /// <param name="password">password</param>
24 /// <param name="asAdmin">asAdmin</param>
25 /// <returns>login result</returns>
26 public LoginResult Login(string login, string password)
27 {
28 List<Login> dbLogins = new List<Login>();
29 try
30 {
31 dbLogins.AddRange(
32 DAL.GetItems<Login>(
33 "select * from db.Login where Name='" + login + "'"));
34 }
35 catch (Exception ex)
36 {
37 lock ((object)777)
38 {
39 LastError = ex.Message;
40 }
41 throw ex;
42 }
43 if (dbLogins.Count < 1)
44 {
45 return LoginResult.WrongLogin;
46 }
47
48 var prevUser = App.CurrentUser;
49 App.CurrentUser = dbLogins[0];
50 if (password.CompareTo(App.CurrentUser.Password) != 0)
51 {
52 App.CurrentUser = prevUser;
53 return LoginResult.WrongPass;
54 }
55
56 var log = System.IO.File.AppendText(App.LogFile);
57 log.WriteLine("New user loggined. Login=" + App.CurrentUser.Name);
58
59 if (!(bool)((EventService)App.Service).SendWithConfirm(prevUser))
60 {
61 log.Write("Error sending to user.");
62 }
63
64 GC.Collect();
65 GC.Collect();
66
67 return LoginResult.Success;
68 }
69 }
70 }


Ошибки ( номер строки и описание, которое я ожидаю услышать от соискателя):

12 – Значение для «Error» будет «-1», что продублирует уже существующее и не позволит в будущем отличить одно от другого.

17 (1) – Public field. По правилам хорошего тона не рекомендуется делать поля публично доступными.

17 (2) – Запись в переменную далее по коду «реализована» через lock, но внешний потребитель может и не знать о том что обращение к переменной следует синхронизировать с кем-либо.

20 – Бессмысленные комментарии.

24 – Комментарий не соответствующий действительности.

28 – Публичный метод принял параметры из вне, но не выполнил проверку на их корректность (как минимум на null).

32 – Сильная связанность.

33 (1) – Потенциальная место для использования SQL инъекции. Так как для формирования запроса используется конкатенация а не параметры. А во вторых – не параметризованные запросы не кэшируются сиквел сервером (если СУБД сиквел).

33 (2) – Подобный стиль формирования запросов «привязывает» приложение к конкретной СУБД).

33 (3) – Конкатенация строк таким образом не самое эффективное решение.

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

37 – Этот lock не будет работать.

39 – Бессмысленное значение в переменной и никакой пользы для разработчиков при диагностики.

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

49 (1) – Разрушение состояния приложения, так как при ошибке ниже (null пароль) в поле текущего пользователя будет уже новый логин.

49 (2) – Неявная бизнес логика. Почему отсекли другие логины?

50 (1) – Пароль не проверен на null и может быть исключение которое приведёт к разрушению состояния приложения.

50 (2) – Пароль хранится и используется в открытом виде.

53 – По правилам хорошего тона – не следует сообщать столь подробную информацию о причинах ошибки аутентификации.

56 (1) – Отсутствие продуманного механизма логирования как такового.

56 (2) – Открывается поток, но его закрытие и уничтожение не выполнены, данные могут быть и не записаны в конечный файл.

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

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

59 (1) – Кроме связанности, вложенные вызовы с «завышенными» ожиданиями касательно возвращаемого результата.

59 (2) – Неочевидная бизнес логика.

61 – Бессмысленная запись в лог.

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


Может, кто-то уже применял такой подход и может поделиться своим опытом?


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 fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



В Кении статьи Википедии стали доступны по SMS


сегодня в 16:41


Фонд Wikimedia Foundation объявил о сотрудничестве с международным оператором Airtel в рамках которого планируется предоставить бесплатный доступ к статьям Википедии жителям Африки при помощи обычных мобильных телефонов.

Хотя на континенте некоторые телеком-операторы и предоставляют услугу «нулевого трафика» за доступ к мобильным версиям Википедии, проект ориентирован прежде всего на людей, которые не обладают смартфоном и тем более активным подключением к интернету. Согласно имеющейся статистике в Кении примерно 60% населения имеют мобильные телефоны, но траты на них составляют почти четверть месячного дохода. Таким образом свободный доступ для них к сетевой энциклопедии (даже с учётом того, что это смартфон и есть доступ в сеть) может оказаться весьма кстати. В качестве страны для пилотной программы была выбрана Кения.


Работает «SMS-Википедия» примерно также, как и остальные SMS-сервисы. Владельцу телефона требуется отправить USSD-запрос *515# и подождать пока система попросит ввести интересующую его тему для поиска:


image





Далее будут показаны статьи Википедии, содержащие введённое слово. Примерно так:
image

И, наконец, можно будет выбрать разделы выбранной на предыдущем этапе статьи:


image

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


image

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


[Источник]





Developers, stick with Russians – work in London




Переводы с

карты на карту


Переводы

через QR-Код


Новая функция

«Мой контроль»




Возьми Lumia 925 на тест-драйв сейчас.




Впечатляющие возможности

в стильном тонком корпусе из металла




Boomburum

исследует LTE


Эволюция средств связи

в путешествии по России




Проблемы коммуникации внутри бизнеса?


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


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 fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Удаленная работа — это не «фриланс»


Сегодня на глаза попался старый вопрос "Почему работодатель предпочитает нанимать веб-разработчика в офис?": habrahabr.ru/qa/22292/. Вопрос был задан еще в 2012 году, но, на мой взгляд, ситуация с тех пор не сильно изменилась.


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


Многие, как мне кажется, представляют себе фрилансера примерно так:



  • Приходит на какую-то отдельную небольшую задачу, выполняет ее и уходит.

  • Работает над несколькими мелкими проектами одновременно. Либо имеет постоянную работу, а фрилансером просто подрабатывает в оставшееся время.

  • Его не беспокоит проект в целом. Только то, за что он отвечает.

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

  • Работает когда хочет. С ним сложно связаться.






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

Однако существует и другая категория удаленщиков. Это люди, ориентированные на фултайм, как на обычной работе «в офисе», только не в офисе, а из дома. Чем они отличаются от тех, кого обычно принято считать «фрилансерами»:



  • Работают на полной ставке(40 часов в неделю).

  • Получают фиксированную зарплату, либо почасовую, но все равно отрабатывают 160 часов в месяц.

  • Занимаются не одной единственной задачей, а занимают фиксированную должность. Работают полгода, год, два. Столько, сколько требуется проекту.

  • Всегда присутствуют на связи в рабочие часы. Активно взаимодействуют с менеджером, и со всей командой.

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


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


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


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


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


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


Парни, зачем нам ваша зарплата выше рыночной, если снять квартиру в центре Москвы рядом с вашим офисом все равно съест минимум половину? А если ездить через всю столицу по полтора часа в одну сторону — так это выходит, я буду работать не 8 часов в день, а все 11. При той же оплате. Лучше уж я в своем Мухосранске устроюсь за зарплату в два раза ниже. :)


Ребята, я не знаю, что за тараканы у вас в головах, но давайте обсудим те «проблемы» с удаленными работниками, о которых чаще всего приходится слышать:



Нету никакого контроля за сотрудниками. Мало ли чем он там занимается?





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

Во-вторых, требуйте постоянного присутствия в чате в рабочие часы, и устраивайте митинги. Не обязательно требовать детальных отчетов — это будет контрпродуктивно, — но выяснять в двух словах ситуацию периодически можно. Менеджер проекта в офисе ведь тоже не висит над подчиненными 24/7, а работа как-то движется. Да и в офисе люди могут болтать друг с другом, и покурить ходить.


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



Ладно, допустим, следить я за его присутствием могу. Но вдруг он пропадет внезапно с концами? Я не готов брать на себя такие риски.





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

Хорошо, вот я обезопасил себя от внезапных исчезновений работника. А как я пойму, насколько он хороший специалист, и как он справляется со своими обязанностями?





А не нужно сразу давать много ответственности. Дайте ему тестовое задание(пусть даже и оплачиваемое). И посмотрите, как он с ним справится.

Убедили. Вот нашел я ответственного и хорошего специалиста. Но ведь это еще не все. В офисе есть командное общение. В том числе неформальное. Это важная часть работы.





Согласен. Но общение может быть и удаленным. В том числе и неформальное. Коллектив может быть коллективом и не будучи знакомым лично друг с другом. Разве вы сами в Интернете общаетесь только с теми людьми, с кем лично знакомы?

Да, но вот в офисе, к примеру, я могу подойти к Семену, и объяснить на пальцах, тыкнуть в монитор. А с фрилансером еще и придется опять согласовывать всякие ТЗ.





Так вы ведь нанимаете не «фрилансера», а постоянного наемного работника со своими обязанностями. Никаких тех. заданий. Все как в обычном офисе.

А «тыкать пальцем в монитор» можно с помощью TeamViewer или расшариванием экрана в Skype. В XXI же веке живем, товарищи!


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


Заключение




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

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


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 fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Визуализация двумерного гауссиана на плоскости

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

Данные и их взаимное расположение


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



для которого мы можем легко узнать ковариационную матрицу и средние значения (центр будущего эллипса):



plot(data[, 1], data[, 2], pch=19, asp=1,
col=rgb(0, 0.5, 1, 0.2),
xlab="x", ylab="y")

sigma <- cov(data)
m.x <- mean(data[, 1])
m.y <- mean(data[, 2])


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



Для определения размера эллипса вспомним расстояние Махаланобиса между двумя случайными векторами из одного вероятностного распределения с ковариационной матрицей Σ:



Так же можно определить расстояние от случайного вектора x до множества со средним значением μ и ковариационной матрицей Σ:



Стоит заметить, что в случае, когда Σ равна единичной матрице, расстояние Махаланобиса вырождается в Евклидово расстояние. Смысл расстояния Махаланобиса в том, что оно учитывает корреляцию между переменными; или другими словами, учитывается разброс данных относительно центра масс (предполагается, что разброс имеет форму эллипсоида). В случае же использования Евклидова расстояния, используется предположение, что данные распределены сферически (равномерно по всем измерениям) вокруг центра масс. Проиллюстрируем это следующим графиком:



Желтым цветом отмечен центр масс, а две красные точки из набора данных, расположенные на главных осях эллипса, находятся на одинаковом, в смысле Махаланобиса, расстоянии от центра масс.


Эллипс и распределение хи-квадрат


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



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



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



Теперь вспомним расстояние Махаланобиса, и рассмотрим его квадрат:



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


Наступает тонкий момент для понимания, у меня это заняло некоторое время, что бы осознать: квадрат расстояния Махаланобиса это сумма квадратов k-ого количества нормально распределенных случайных величин, где n — это размерность пространства.


Вспомним, что такое распределение хи-квадрат — это распределение суммы квадратов k независимых стандартных нормальных случайных величин (это распределение параметризируется количеством степеней свободы k). А это как раз и есть расстояние Махаланобиса. Таким образом вероятность того, что x находится внутри эллипса выражается следующей формулой:



И вот мы пришли к ответу на вопрос о размере эллипса — его размер мы будем детерминировать квантилямираспределения хи-квадрат, это легко делается в R (где q из (0, 1) и k — количество степеней свободы):



v <- qchisq(q, k)


Получение контура эллипса




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

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



Рассмотрим разложение ковариационной матрицы в следующем виде:



где U — матрица образованная единичными собственными векторами матрицы Σ, а Λ — диагональная матрица, составленная из соответствующих собственных значений.



e <- eigen(sigma)


А также рассмотрим следующее выражение для случайного вектора из многомерного нормального распределения:



Таким образом, распределение N(μ, Σ) — это, по сути, стандартное многомерное нормальное распределение N(0, I) масштабированное на Λ^(1/2), повернутое на U и смещенное на μ.


Давайте теперь напишем функцию, которая рисует эллипс, со следующими входными данными:



  • m.x, m.y — координаты центра масс

  • sigma — ковариационная матрица

  • q — квантиль распределения хи-квадрат

  • n — плотность дискретизации эллипса (количество точек по которым будет строится эллипс)



GetEllipsePoints <- function(m.x, m.y, sigma, q = 0.75, n = 100)
{
k <- qchisq(q, 2) # вычисляем значение квантиля
sigma <- k * sigma # масштабирование ковариационной матрицы на значение квантиля
e <- eigen(sigma) # вычисление собственных значений масштабированной ковариационной матрицы
angles <- seq(0, 2*pi, length.out=n) # разбиваем круг на n-ое количество углов
cir1.points <- rbind(cos(angles), sin(angles)) # генерируем точки на единичной окружности
ellipse.centered <- (e$vectors %*% diag(sqrt(abs(e$values)))) %*% cir1.points # масштабируем и поворачиваем полученный датасет
ellipse.biased <- ellipse.centered + c(m.x, m.y) # смещаем его до центра масс
return(ellipse.biased) # готово
}


Результат




Следующий код рисует множество доверительных эллипсов вокруг центра масс датасета:

points(m.x, m.y, pch=20, col="yellow")

q <- seq(0.1, 0.95, length.out=10)
palette <- cm.colors(length(q))
for(i in 1:length(q))
{
p <- GetEllipsePoints(m.x, m.y, sigma, q = q[i])
points(p[1, ], p[2, ], type="l", col=palette[i])
}

e <- eigen(sigma)
v <- (e$vectors %*% diag(sqrt(abs(e$values))))
arrows(c(m.x, m.x), c(m.y, m.y),
c(v[1, 1] + m.x, v[1, 2] + m.x), c(v[2, 1] + m.y, v[2, 2] + m.y),



В итоге получаем такую картину:



Почитать


Код можно найти у меня на гитхабе.


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 fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



[recovery mode] История одного Google Chrome расширения

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

Немного поразмыслив я пришел к варианту google chrome extension:



  • Crome использует Chromium движок, который является форком WebKit (а это Safari), так же не забываем Blink (а это уже новая (хотя я все еще использую старую с bookmarks'ами) Opera). Таким образом, написав расширения для chrome, мы с минимальными переделками (а то и без них) сможем его портировать на еще 2 браузера

  • Нет опыта работы с API Google Chrome

  • Google все-таки компания добра :)


Когда мысли немного улеглись, первое что я сделал — это ввел в поиске харба "расширение Google Chrome". Увидев обширный вариант статей по данной теме, я со спокойной душой ушел домой полностью уверенный в том, что завтра с утра прочитав их, к концу рабочего дня дело будет 'в шляпе' (как же я тогда ошибался). Прочитав парочку их них я имел общее представление о том как это работает, но этого оказалось мало для воплащения моих идей. Что ж, приступим…



Открываем google chrome, вводим chrome://extensions, ставим галочку Developer mode, нажимаем кнопку Load unpacked extension, выбрав папку нажимаем Ok.



В начале было слово манифест. Ниже можете увидеть содержимое этого файла (manifest.json — это обязательное название файла манифеста)


manifest.json


{
"manifest_version": 2,
"name": "My application", // тут надеюсь все понятно
"version": "0.9",

"icons": {
"16": "./16x16.png",
"32": "./32x32.png",
"48": "./48x48.png",
"128": "./128x128.png"
},

"permissions": [
"tabs",
"http://*/*",
"https://*/*"
],

"background" : {
"page": "background.html"
},

"content_scripts":[{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [
"script_in_content.js"
]
}],

"browser_action": {
"default_title": "Application",
"default_icon" : "./16x16.png"
// "default_popup": "login.html" // это имя html-страницы расширения, которая будет всплывать при нажатии на иконку, можно с помощью JS устанавливать различные html страницы
}
}







manifest_version — на данный момент значение 2 обязательное.

version — версия вашего расширения, может содержать только цифры и `.` (те. '2.1.12', '0.59' и тд)

icons — это список всех иконок которые будут отображатся в браузере в различных местах (16 — в адресной строке, 48 — в списке всех расширений и тд.)

permissions — здесь перечислен массив с разрешениями, мне нужно было только tabs.http и https нужен для ajax обмена с любыми сайтами, а также для того что-бы script_in_content.js мог обмениватся данными с фоновой страницей — background.html.

background — это имя фоновой страницы. Фоновая страницы — важный элемент, хотя для некоторых приложений он и не обязателен. Зачем она нужна немного по-позже.

content_scripts — здесь говорится что файл script_in_content.js, будет автоматически загружатся для страницы открытой во вкладке. Страница должны быть открыта с сайтов http://*/* те, всех сайтов с http, но не https, хотя можно было бы указать и их.

browser_action — существует 2 варанта отображения иконки расширения: browser_action и page_action

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


browser_action наоборот не считаются индивидуальными и отображаются не в адресной строке, а в панеле для расширений. Даную иконку никак нельзя скрыть на JS (но можно заблокировать), она отображается постоянно. У browser_action есть одно преимущество по сравнению с page_action, поверх иконки browser_action можно написать пару красивых символов (у меня влазит только 4).



Я выбрал browser_action, как мне необходимо работать не с одним сайтом, а с несколькими. И да, нанесение красивых символов на иконку.

Вот что Google говорит по этому поводу:



Do use page actions for features that make sense for only a few pages.

Don't use page actions for features that make sense for most pages. Use browser actions instead.



И так, что будет делать наше приложение; скажу сразу, приложение, которое будет далее описано, — это малая часть того что было сделано для клиента. Когда менеджер заходит на сайт hantim.ru для просмотра информации о контракте/вакансии, приложение парсит html код страницы и находит информацию (вакансия, город и тд). При клике на иконку расширения — отображается форма логина, куда менеджер вводит свои данные, а потом может добавить выбранную вакансию/контракт в свой профиль на корпоративном сайте.


Теперь о том как все это работает. Google предоставляет нам такую картину:


1) Inspected window — это то что мы открыли во вкладке, content scripts — это наш script_in_content.js, он имеет полный доступ к DOM страницы.

2) Background page — это сердце приложения, в нашем случае — это background.html.

3) DevTools page — это то, что будет отображатся при клике на иконку расширения (login.html или find.html в нашем случае).


Единственное что меня смущает в данной картинке так это связь DevTools page и Inspected window. Я не нашел решения, что бы передать данные из одной области в другую. Но если выставить Background page как посредника, и через него передавать данный, то все заработает.


И так, настало время кода. Начнем с невидимой стороны.


background.html


<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="lib.js"></script>
<script type="text/javascript" src="bg.js"></script>
</head>
<body></body>
</html>







Надеюсь с этим вопросов не должно возникнуть. Одно замечание: «background.html — загружается только один раз за все время работы браузера, а имеено при его запуске». Здесь вы можете увидеть что мы загружаем 2 js файла (lib.js — набор функций, bg.js — 'голова' приложения).
bg.js


/**
* OnLoad function
*
* @return void
*/
window.onload = function(){

// tmp storage
window.bg = new bgObj();

// some variables !!! important
window.bg.api_site_host = 'http://katran.by';

// get all graber hosts: !!!once!!!
new Ajax({
url: window.bg.api_site_host+'/regexp.php',
response: 'json',
async: false,
onComplete: function(data){
if(data && data.status && (data.status === 'ok'))
window.bg.grabber_hosts = data.data;
}
}).send();

// set handler to tabs
chrome.tabs.onActivated.addListener(function(info) {
window.bg.onActivated(info);
});

// set handler to tabs: need for seng objects
chrome.extension.onConnect.addListener(function(port){
port.onMessage.addListener(factory);
});

// set handler to extention on icon click
chrome.browserAction.onClicked.addListener(function(tab) {
window.bg.onClicked(tab);
});

// set handler to tabs
chrome.tabs.onUpdated.addListener(function(id, info, tab) {
// if tab load
if (info && info.status && (info.status.toLowerCase() === 'complete')){
// if user open empty tab or ftp protocol and etc.
if(!id || !tab || !tab.url || (tab.url.indexOf('http:') == -1))
return 0;

// save tab info if need
window.bg.push(tab);

// connect with new tab, and save object
var port = chrome.tabs.connect(id);
window.bg.tabs[id].port_info = port;

// run function in popup.html
chrome.tabs.executeScript(id, {code:"initialization()"});

// send id, hosts and others information into popup.js
window.bg.tabs[id].port_info.postMessage({method:'setTabId', data:id});
window.bg.tabs[id].port_info.postMessage({method:'setHosts', data:window.bg.grabber_hosts});
window.bg.tabs[id].port_info.postMessage({method:'run'});

// if user is logged into application set find.html popup
if(window.bg.user.id)
chrome.browserAction.setPopup({popup: "find.html"});
};
});

window.bg.onAppReady();
};


/**
* Functino will be called when popup.js send some data by port interface
*
* @return void
*/
function factory(obj){
if(obj && obj.method){
if(obj.data)
window.bg[obj.method](obj.data);
else
window.bg[obj.method]();
}
}


/**
* Popup object
*
* @version 2013-10-11
* @return Object
*/
window.bgObj = function(){
};


/**
* Pablic methods
*/
window.bgObj.prototype = {

/**
* some internal params
*/
tabs: {},
user: {},
popup_dom: {},
active_tab: {},
grabber_hosts: {},
done_urls: [],

/**
* init() function
*/
onAppReady: function()
{
// if user not logged into application set login.html popup
chrome.browserAction.setPopup({popup: "login.html"});
},

/**
* Function add tab into $tabs object, if need
*/
push: function(tab)
{
if(tab.id && (tab.id != 0)){
if(!this.tabs[tab.id])
this.tabs[tab.id] = {tab_obj:tab};
}
},

/**
* Function will be called from popup.js
*/
mustParsed: function(data)
{
if(this.tabs[data.tab_id]){
var id = data.tab_id;
this.tabs[id].must_parsed = data.find;

// run parser in popup.js, if need
if(this.tabs[id].must_parsed && (this.tabs[id].must_parsed === true))
this.tabs[id].port_info.postMessage({method:'parsePage'});
}
},

/**
* Function will be called from popup.js
*/
matchesCount: function(data)
{
if(data.tab_id && this.tabs[data.tab_id]){
var id = data.tab_id;
this.tabs[id].matches = data.matches;
this.tabs[id].matches_count = this.tabs[id].matches.length+'';

if(this.tabs[id].matches_count && this.tabs[id].matches_count != '0'){
chrome.browserAction.setBadgeText({text: this.tabs[id].matches_count});
return 0;
}
}

// show default text
chrome.browserAction.setBadgeText({text:''});
},

/**
* Function will be called when user change active tab
*/
onActivated: function(info)
{
// set active tab
this.active_tab = info;

var data = {};
data.matches = [];

if(info.tabId){
data.tab_id = info.tabId;
if(!this.tabs[data.tab_id])
this.tabs[data.tab_id] = {};
if(!this.tabs[data.tab_id].matches)
this.tabs[data.tab_id].matches = [];

data.matches = this.tabs[data.tab_id].matches;
}

// set actual count of matches for current tab
this.matchesCount(data);

// if user is logged into application set find.html popup
if(this.user.id)
chrome.browserAction.setPopup({popup: "find.html"});
},

/**
* Function will be called when user click on extension icon
*/
onClicked: function(tab)
{
alert('Произошла ошибка. Обратитесь к разработчикам данного приложения.');
return 0;
},

/**
* Function will be called from login.js
*/
loginUser: function(user_data)
{
var self = this;
var json_data = false;

// get all graber hosts: !!!once!!!
new Ajax({
url: window.bg.api_site_host+'/login.php?user='+encodeURIComponent(JSON.stringify(user_data)),
method: 'post',
response: 'json',
async: false,
onComplete: function(data){
if(data && data.status){
// if login - ok
if(data.status === 'ok')
self.user = data.data;

json_data = data;
}
}
}).send();

// return value for login.js
return json_data;
},

/**
* Function will be called from login.js and others places
*/
setPopup: function(popup_file)
{
chrome.browserAction.setPopup({tabId: this.active_tab.tabId, popup: popup_file});
},

/**
* Function will be called from find.js and others places
*/
getMatches: function()
{
// init if need
if(!this.tabs[this.active_tab.tabId])
this.tabs[this.active_tab.tabId] = {};
if(!this.tabs[this.active_tab.tabId].matches)
this.tabs[this.active_tab.tabId].matches = [];

// if user alredy send this url - remove
for(var i = 0, cnt = this.tabs[this.active_tab.tabId].matches.length; i < cnt; i++){
for(var j = 0, len = this.done_urls.length; j < len; j++){
if(this.tabs[this.active_tab.tabId].matches[i].url === this.done_urls[j]){
this.tabs[this.active_tab.tabId].matches[i].url = '';
break;
}
}
}

return this.tabs[this.active_tab.tabId].matches;
},

/**
* Function will be called from find.js and others places
*/
addUrlToGrabber: function(url)
{
// if $url == '' - already used
if(json_data.status && (json_data.status === 'ok')){
var matches = this.tabs[this.active_tab.tabId].matches;
for(var i = 0, cnt = matches.length; i < cnt; i++){
if(matches[i].url && (matches[i].url === url))
matches[i].url = '';
this.done_urls.push(url);
}
}

// return value for login.js
return json_data;
},


/**
* Empty method
*/
empty: function()
{
}
}







Первым делом мы дожидаемся window.onload, потом посылаем запрос на katran.by (получим json данные, с какого сайта и каким RegExp'пом мы дастанем необходимые данные), потом вешаем handler'ы на вкладки браузера (для этого мы и указали в манифесте permissions ~ tabs).

chrome.tabs.onActivated.addListener(function(info) {
window.bg.onActivated(info);
});


onActivated — происходит тогда, когда пользователь перешел на новую вкладку (по клику или по alt+tab).



chrome.tabs.onUpdated.addListener(function(id, info, tab) {
.....
});


onUpdated — происходит тогда, когда страница полностью (загрузился не только DOM, а и все картинки) загрузилась во вкладке.



chrome.browserAction.onClicked.addListener(function(tab) {
window.bg.onClicked(tab);
});


onClicked — происходит тогда, когда пользователь кликает на иконке приложения. Небольшое замечание, если во время клика default_popup установлено, то обработчик onClicked — не запустится. default_popup это html страница которая будет отображаеться после нажатия на иконку расширения. default_popup можно выставить в манифесте, а так же с помощью chrome.browserAction.setPopup({popup: «find.html»}); или chrome.pageAction.setPopup({popup: «find.html»});



chrome.extension.onConnect.addListener(function(port){
port.onMessage.addListener(factory);
});


Эта темная магия конструкция, нужна для приема данных, посланных от script_in_content.js с помощью port.

Обработкой данных занимается factory(obj)



function factory(obj){
if(obj && obj.method){
if(obj.data)
window.bg[obj.method](obj.data);
else
window.bg[obj.method]();
}
}


Что же происходит когда пользователь загружает вкладку, а происходит следующее:



  • Вызывается handler onUpdated

  • if (info && info.status && (info.status.toLowerCase() === 'complete')) если все загружено — продолжаем разбор полетов.

  • if(!id || !tab || !tab.url || (tab.url.indexOf('http:') == -1)) если пользователь открыл не web-сайт (проверку на https — забыл, только сейчас заметил :) ), а к примеру вкладку настройки или ftp и тд., то ничего не делаем

  • window.bg.push(tab); — созраняем информацию об текущей вкладке

  • chrome.tabs.executeScript(id, {code:"initialization()"}); — сейчас мы приказываем script_in_content.js выполнить функцию initialization()

  • window.bg.tabs[id].port_info.postMessage({method:'setTabId', data:id}) — мы посылаем данные в script_in_content.js

  • chrome.browserAction.setPopup({popup: "find.html"}); — устанавливаем popup страницу, если пользователь авторизовался ранее


Есть 2 способа передать данные от background.html к script_in_content.js:



  1. сhrome.tabs.executeScript(integer tabId, InjectDetails details, function callback) — одно но, таким спосабом можно передавать данные только в виде строки (не объект, не массив)

  2. сhrome.tabs.sendMessage(integer tabId, any message, function responseCallback) — так можно передавать что угодно, правдо потребуются дополнитеные настройки


И так, мы послали данные в script_in_content.js, значит настало время рассмотреть его код.


script_in_content.js


// set handler to tabs: need for seng objects to backgroung.js
chrome.extension.onConnect.addListener(function(port){
port.onMessage.addListener(factory);
});


/**
* Function remove spaces in begin and end of string
*
* @version 2012-11-05
* @param string str
* @return string
*/
function trim(str)
{
return String(str).replace(/^\s+|\s+$/g, '');
}


/**
* Functino will be called from background.js
*
* @return void
*/
function initialization(){
window.popup = new popupObj();
}


/**
* Functino will be called when background.js send some data by port interface
*
* @return void
*/
function factory(obj){
if(obj && obj.method){
if(obj.data)
window.popup[obj.method](obj.data);
else
window.popup[obj.method]();
}
}


/**
* Popup object
*
* @version 2013-10-11
* @return Object
*/
window.popupObj = function(){
};


/**
* Pablic methods
*/
window.popupObj.prototype = {

/**
* some internal params
*/
available_hosts: [],
total_host: null,
matches: [],
tab_id: null,
port: null,
cars: [],

/**
* Function will be called from bg.js
*/
setHosts: function(hosts)
{
this.available_hosts = hosts;
},

/**
* Function will be called from bg.js
*/
setTabId: function(id)
{
this.tab_id = id;
},

/**
* Function check total host
*/
run: function()
{
// get total host
if(document.location.host && (document.location.host != ''))
this.total_host = document.location.host;
else if(document.location.hostname && (document.location.hostname != ''))
this.total_host = document.location.hostname;

if(!this.total_host || (this.total_host === ''))
return 0;

var find = false;
// if total host in array $available_hosts - parse page for finde cars
for (host in this.available_hosts) {
if(this.total_host.indexOf(host) != -1){
this.total_host = host;
find = true;
break;
}
};

// create connection to backgroung.html and send request
this.port = chrome.extension.connect();
this.port.postMessage({method:'mustParsed', data:{tab_id:this.tab_id, find:find}});
},

/**
* Function will be called from bg.js
* Parse page
*/
parsePage: function()
{
// reset variable before parse
this.matches = [];

if(!this.available_hosts[this.total_host])
return 0;

var html = window.document.body.innerHTML;
var reg_exp = this.available_hosts[this.total_host];
var matches = {};
var match = [];
var find = false;
for(var i = 0, len = reg_exp.length; i < len; i++) {
var exp = new RegExp(reg_exp[i].reg_exp, reg_exp[i].flag);
match = exp.exec(html);

if(match && match.length && reg_exp[i].index){
matches[reg_exp[i].field] = trim(match[reg_exp[i].index]);
find = true;
}
else if(match && match.length){
matches[reg_exp[i].field] = match;
find = true;
}
}

// this url will be send to site
if(find === true){
matches.url = document.location.href;
this.matches.push(matches);
}

// send count of matches
this.port.postMessage({method:'matchesCount', data:{tab_id:this.tab_id, matches: this.matches}});
}
}







Первое что бросается в глаза — это прием данных от background.html, как можете заметить он такой же как и в bg.js:

chrome.extension.onConnect.addListener(function(port){
port.onMessage.addListener(factory);
});


Как помните, ранее в bg.js мы запустили initialization(), setTabId(), setHosts() и run(). Найбольший интерес представляет window.popup.run(). Там проверяется доменное имя сервера открытой страницы, и если это имя совпадает со списком сайтов которые нам интересны (данные с которых необходимо передать на корпоративный ресурс) — find = true; и отправляем запрос window.bg.mustParsed(obj) в bg.js.



/**
* Function will be called from script_in_content.js
*/
mustParsed: function(data)
{
if(this.tabs[data.tab_id]){
var id = data.tab_id;
this.tabs[id].must_parsed = data.find;

// run parser in popup.js, if need
if(this.tabs[id].must_parsed && (this.tabs[id].must_parsed === true))
this.tabs[id].port_info.postMessage({method:'parsePage'});
}
}


Если совпадение домена было найдено, то запускаем парсер страницы parsePage() в script_in_content.js.



/**
* Function will be called from bg.js
* Parse page
*/
parsePage: function()
{
// reset variable before parse
this.matches = [];

if(!this.available_hosts[this.total_host])
return 0;

var html = window.document.body.innerHTML;
var reg_exp = this.available_hosts[this.total_host];
var matches = {};
var match = [];
var find = false;
for(var i = 0, len = reg_exp.length; i < len; i++) {
var exp = new RegExp(reg_exp[i].reg_exp, reg_exp[i].flag);
match = exp.exec(html);

if(match && match.length && reg_exp[i].index){
matches[reg_exp[i].field] = trim(match[reg_exp[i].index]);
find = true;
}
else if(match && match.length){
matches[reg_exp[i].field] = match;
find = true;
}
}

// this url will be send to site
if(find === true){
matches.url = document.location.href;
this.matches.push(matches);
}

// send count of matches
this.port.postMessage({method:'matchesCount', data:{tab_id:this.tab_id, matches: this.matches}});
}


Если скрипт что-то нашел на странице, то все что он нашел складывает в массивчик, добавляет к нему текущий url страницы и отправляет назад в bg.js, мол: «Смотри что я нашел...». В ответ на это, bg.js анализирует входные данные, и если RegExp'ы нашли что-то — пишет поверх иконки колличество совпадений (1, 2 и тд.) chrome.browserAction.setBadgeText({text: this.tabs[id].matches_count});.


Это вроде все основные моменты рыботы связки bg.js и script_in_content.js.

Теперь поговорим о popup. Когда пользователь кликает по иконке приложения — отображается форма login.html.

Менеджер вводит свои данные от корпоративного сайта, нажимает Login и тут происходит следующее:


login.html


<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="login.js"></script>
<link type="text/css" rel="stylesheet" href="login.css">
<title>Grabber popup</title>
</head>
<body>
<div class="body">
<div class="emptyLogin">
<div id="error_message"> </div>
<form name="login_form" action="" method="get" id="popup_login_form">
<table>
<tbody>
<tr>
<td align="right">Ваш E-mail:</td>
<td><input type="text" name="login" value="" tabindex="1"></td>
</tr>
<tr>
<td align="right">Пароль:</td>
<td><input type="password" name="pass" value="" tabindex="2"></td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="Login" class="button"></td>
</tr>
</tbody>
</table>
</form>
</div>
<div id="loader"><img src="ajax-loader.gif" title="Loding" alt="Loading"></div>
</div>
</body>
</html>







login.js


/**
* OnLoad function
*
* @return void
*/
window.onload = function(){

// set some events handlers
document.getElementById('popup_login_form').onsubmit = function(obj){
// fade popup
document.getElementById('loader').style.display = 'block';
document.getElementById('error_message').innerHTML = ' ';

if(obj.target.elements && obj.target.elements.length && (obj.target.elements.length === 3)){
var data = {};
data.login = obj.target.elements[0].value;
data.pass = obj.target.elements[1].value;

setTimeout(function(){
var bg_wnd = chrome.extension.getBackgroundPage();
var result = bg_wnd.bg.loginUser(data);

if(result && result.status && (result.status === 'error'))
document.getElementById('error_message').innerHTML = result.mess;
else{
// set new popup html code and close popup window
bg_wnd.bg.setPopup('find.html');
window.close();
}

// hide fade on popup
document.getElementById('loader').style.display = 'none';
}, 500);
}
return false;
};
}







Задача login.js состоит в том, что бы повесить onsubmit на форму, и отправить логин/пароль в background.html (bg.js),

а делается это с помощью следующей конструкции (как увидите, мы пожем на прямую вызывать методы объекта bg.js):

var bg_wnd = chrome.extension.getBackgroundPage();
var result = bg_wnd.bg.loginUser(data);


bg_wnd.bg.loginUser(data) отправляет данные на сервер, если все хорошо, то popup login.html сменяет find.html,

а данные о пользователе сохраняются в переменной. Смена popup происходит следующим образом:



/**
* Function will be called from login.js and others places
*/
setPopup: function(popup_file)
{
chrome.browserAction.setPopup({tabId: this.active_tab.tabId, popup: popup_file});
},


Небольшое замечание, если пользователь открыл popup login.html поставил курсор в поле 'Ваш E-mail:' и нажимает TAB (первый раз) в надежде перейти к паролю, то его ожидает разачарование, фокус не сменится. Данный баг все еще актуален.


Так, осталось совсем чуть-чуть.

После того как мы успешно авторизовались, мы поменя popup на find.html.


find.html


<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="find.js"></script>
<link type="text/css" rel="stylesheet" href="find.css">
<title>Grabber</title>
</head>
<body>
<div class="body">
<div class="carsRows" id="popup_cars_rows">
<h3 style="text-align: center; margin: 5px 0;">Найденные данный странице</h3>
<form name="cars_form" action="" method="get" id="popup_cars_form">
<table id="popup_cars_table">
<thead>
<tr>
<th class="make">Вакансия</th>
<th class="info">Город</th>
<th class="addBtn"> </th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</form>
</div>
<div class="carsRows" id="popup_cars_rows_none" style="display: none;">
<h3 style="text-align: center; margin: 5px 0;">Ничего не найдено на странице</h3>
</div>
<div id="loader"><img src="ajax-loader.gif" title="Loding" alt="Loading"></div>
</div>
</body>
</html>







find.js


/**
* OnLoad function
*
* @return void
*/
window.onload = function(){

// set new popup html code and close popup window
window.bg_wnd = chrome.extension.getBackgroundPage();
var rows = window.bg_wnd.bg.getMatches();

// function render popup
renderPopup(rows);
}


/**
* Function set cars into html
*
* @param array $rows
* @return void
*/
function renderPopup(rows)
{
if(rows.length === 0){
document.getElementById('popup_cars_rows').style.display = 'none';
document.getElementById('popup_cars_rows_none').style.display = 'block';
return 0;
}
else{
document.getElementById('popup_cars_rows').style.display = 'block';
document.getElementById('popup_cars_rows_none').style.display = 'none';
}

for (var i = 0, cnt = rows.length; i < cnt; i++)
renderRow(rows[i]);
}


/**
* Function set cars into html
*
* @param object $row
* @return void
*/
function renderRow(row)
{
var tbl = document.getElementById('popup_cars_table').children[1];

// add divided row
var td = tbl.insertRow(-1).insertCell(-1);
td.setAttribute('colspan', '3');
td.innerHTML = '<hr style="border: 1px solid #909090; width: 75%">';

var tr = tbl.insertRow(-1);
var td1 = tr.insertCell(-1);
var td2 = tr.insertCell(-1);
var td3 = tr.insertCell(-1);
var vacancy = [];
var city = [];

var hash = {
vacancy: 'вакансия',
city: 'город',
}

var table_row = [];
for(key in row){
if(hash[key]){
if(key == 'vacancy')
vacancy.push(row[key]);
if(key == 'city')
city.push(row[key]);
}
}

td1.innerHTML = vacancy.join(' ');;
td2.innerHTML = city.join(' ');
td3.innerHTML = (row.url === '')?'<b><em>Добавлено</em></b>':'<input type="button" value="Дабавить" name="cars[]" class="button"><input type="hidden" value="'+row.url+'" name="url[]">';
td3.children[0].addEventListener('click', function(){addToGrabber(event)}, false);
}


function addToGrabber(e)
{
// hide fade on popup
document.getElementById('loader').getElementsByTagName('img')[0].style.marginTop = (window.innerHeight/2-10)+'px';
document.getElementById('loader').style.display = 'block';

if(e && e.srcElement){
var url = e.srcElement.parentNode.children[1].value;

setTimeout(function(){
var result = window.bg_wnd.bg.addUrlToGrabber(url);
e.srcElement.parentNode.innerHTML = '<b><em>Добавлено</em></b>';

// hide fade on popup
document.getElementById('loader').style.display = 'none';
}, 500);
}
}







Как только find.html загрузился, в работу вступает find.js. Его задача спросить bg.js: 'Что там у тебя есть на текущую страницу' — и отобразить то, что отдал bg.js.

/**
* OnLoad function
*
* @return void
*/
window.onload = function(){

// set new popup html code and close popup window
window.bg_wnd = chrome.extension.getBackgroundPage();
var rows = window.bg_wnd.bg.getMatches();

// function render popup
renderPopup(rows);
}


Так выглядит готовое решение.



C кнопкой 'Добавить' я думаю сами разберетесь, как она работает. На последок хочу сказать как все это дело отлаживается.

background.html — что бы посмотреть работу скриптов bg.js и lib.js нужно кликнуть на линку background.html на странице chrome://extensions.



script_in_content.js — он выполняется в контексте страницы, поэтому можете смело инспектировать страницу и смотреть консоль с выводом ошибок в нее.

login.html и find.html — что бы вывести их Developer Tools, нужно кликнуть на иконке приложения и правым кликом мышки выбрать инспекцию страницы.


PS. Весь JavaScript должен находится в js файлах, если вы его вставите в html — chrome будет ругаться.

Также пару ссылок:

на документацию: manifest.json, Chrome's API

на github.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 fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: