Что под капотом тревел-стартапов или зачем программисту идти на Hack`n`Roll

26 ноября прошла конференция «TravelTech: перспективы онлайн-сервисов», где эксперты из тревел-отрасли попытались разобраться, что сейчас происходит на рынке.

Сначала у меня было мнение, что все тревел-стартапы — это просто интерфейсные фантики и много пиара, но я пообщался с Романом Спиридоновым (CTO Excursopedia) и он открыл мне глаза на open source, алгоритмы эффективного поиска и фрод в сфере тревел-проектов.

4-6 декабря на будет проходить Hack’n’Roll (типа хакатон), где можно будет продемонстрировать свою программистскую силушку и принять участие в развитии онлайн-сервисов в России.

Под катом — то, какие вызовы и возможности есть для программистов тревел-сегменте.

Фрод

Пока Palantir не в свободном доступе, каждый борется с фродом как может.


Travel вообще очень сильно подвержен фрод-преступлениям, поэтому как только компания достигает более-менее приличного уровня, приходится прикладывать огромные усилия по борьбе с фродом.
Вот презентация AirBnb про real time detection фрода.

Вот тут описан сценарий профессионального фрода с авиабилетами.
И даже симпозиумы собирают — 4th Annual Travel Industry Fraud Event — Travel Fraud Prevention Symposium 2016.

Мета-поисковики авиабилетов

image

Существенная часть работы в этих проектах состоит в оптимизации (кешировании) запросов к GDS/агентам, при этом обеспечивая для клиента реальные цены на билеты.
Про внутреннюю кухню можно почитать в интервью Артема Коновалова, основателя Corner (стартап по поиску авиабилетов):

— В чем отличие метапоисковика от агента? Агент же тоже ищет билеты.
— Агент может принимать деньги, а метапоисковик нет. Когда человек, например, говорит, что купил свой билет на Aviasales, то с технической точки зрения это неверно. Потому что он купил билет у агента, куда его переправил Aviasales или любой другой метапоисковик. Деньги за билет тоже принял агент. Он несет за него ответственность. Corner — это метапоисковик.

У нас комиссии нет, потому что мы не зарабатываем на агентских комиссиях, как те же Skyscanner, Momondo. Мы зарабатываем на комиссии от Автоматизированных Систем Бронирования (АСБ, они же — GDS, global distribution system ), потому что в отличие от других мы делаем поиск билетов не через агентов, а напрямую через GDS. Это очень важно разделять. Агент нам ничего не платит, ваш заказ для него бесплатен.
У нас хорошая ставка, потому что то, как мы ищем билеты, никто никогда в мире не делал.

— Смелое заявление. В чем отличие, например, Corner от Skyscanner?
— Пусть теоретически Skyscanner подключен к трем агентам OneTwoTrip, Трипста, Тикетс.ру.
Skyscanner отправляет каждому запрос: «Дай мне цены Москва–Париж на 3 февраля.»
Итого, получается три запроса агентам.
Те в свою очередь делают точно такие же запросы на серверы GDS. Ну это чистой воды мультипликация нагрузки! А если агентов 15 и больше, то нагрузка возрастает до небес. Это же глупо!
Corner делает всего ОДИН запрос к GDS. Сравните скорость поиска у нас и у других. Будете не то, что удивлены. Вы поразитесь, как Corner мгновенно переходит на поисковую выдачу.
Мы, без ложной скромности, первыми в мире стали брать данные из GDS напрямую, а потом только опрашивать агентов:
«Агенты, у нас есть билеты Москва–Париж на 3 февраля и мы знаем их цену в GDS. За сколько вы их готовы выписать нашим пользователям?»

Полная версия интервью тут.

Open Source

image
Стандарты являются головной болью в тревеле. Если для авиабилетов и отелей со стандартами постепенно становится все лучше и лучше, то в Travel activities до сих пор нет ничего похожего на общие стандарты. Существует организация, которая всерьез занимается стандартами — это opentravel.org. Список компаний-членов этой организации впечатляет.

Список членов организации

Airline
Aer Lingus

Cruise Line
Uniworld River Cruises

Distributor/Distribution Services Provider
Amadeus
AxisData S.L.
CarTrawler
Expedia
Google
Orbitz
Pegasus Solutions, Inc.
Priceline
RoverPass
Sabre Holdings
TravelCLICK
Travelport

Educational Institution
IMHI, ESSEC Business School
University of Technology, Sydney

Ground Transportation Providers
SuperShuttle

Hotel Company
Best Western International, Inc.
Hyatt Corporation
InterContinental Hotels Group
Marriott International
Outrigger Hotels & Resorts

Industry Association
APEX/Convention Industry Council
ATTA (Adventure Travel Trade Association)
CASMA (Computerized Airline Sales/Marketing Assn)
Convention Industry Council
European Railway Agency
HEDNA (Hotel Electronic Distribution Network Assn)
HTNG (Hotel Technology Next Generation)
NGCOA (National Golf Course Owners Association)
TTI (Travel Technology Initiative)

Railway Company
AMTRAK

Rental Car Company
Avis Budget Group
DB Rent
Enterprise Holdings Inc.
Hertz Corporation

Technology/Solutions Provider
Adalte
AK Development Aps
Australian Tourism Data Warehouse
Campana Systems Inc.
Campground Automation Systems
Cvent
Cybage Software
Data Management
DataArt Solutions Inc.
DerbySoft, Inc.
e-Novate Pte Ltd
Fareoffice Car Rental Solutions AB
Fidelio Cruise Software GmbH
GATE7 Automated Reservation Systems
GuestCentric Systems
IBM Travel and Transportation
IBS Software Services Americas, Inc.
IDeaS — A SAS Company
Information System Associates
Jet Messaging Technologies AG
Lanyon
MicronNexus GmbH
Mindtree
OpenJaw Technologies
Passkey International
Protel Hotelsoftware, GmbH
Psand Limited
Rate-Highway
Reconline
Rezgo
SilverRail Technologies Inc.
SITA Inc.
Tavisca Solutions
Travel Centric Technology Ltd
VFM Leonardo

Tour Operator
Discover Hawaii Tours
TeamAmerica Inc

Travel Agency/Travel Services Provider
Kuoni

Список со ссылками тут.

Тravel-проекты, которые полностью open source

Практически у любой серьезной компании есть набор небольших open source проектов/библиотек, которые могут быть как узкоспециализированными, так и быть нацеленными на широкие массы. Вот некоторые из профилей таких компаний:

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

Немного подробнее про open source AirBnB


Список проектов open source от AirBnB.

Airpal — веб-приложение для работы с SQL (на базе Presto от Facebook).
создавалось с прицелом на пользователей, не являющихся техническими специалистами. Однако мы хотели дать им инструмент, позволяющий полноценно работать с массивами данных, а заодно помочь профессионалам повысить эффективность анализа.
Статья на Хабре
Airpal на GitHub

Airflow — платформа для управления документооборотом.
Airflow на GitHub

Aerosolve — система динамического ценообразования, основанная на машинном обучении. Как результат — повышение доходов хостеров на 20-40%.
Статья на Хабре Секреты алгоритма ценообразования Airbnb
Aerosolve на Github

image
Это программное обеспечение поможет собственникам жилья увеличить прибыль на 20-40%. Они смогут повышать стоимость аренды в высокий сезон и снижать тарифы при уменьшении спроса. Алгоритм PriceMethod основан на сборе данных и анализе информации по более чем 500 млн параметров.

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

Тем временем Airbnb ищет по миру талантливых программистов.
image По словам Майка Кёртиса (Mike Curtis), вице-президента по технологиям в Airbnb, который раньше занимал пост технологического директора в Facebook, открытие кода продуктов позволяет привлекать в компанию талантливых инженеров: «Мы хотим, чтобы в Airbnb работали лучшие технические специалисты мира. Сейчас сумасшедшая конкуренция за лучших инженеров, поэтому чем больше мы можем продемонстрировать, тем больше у нас шансов получить лучших специалистов». Майк добавил: «Если вы выкладываете что-то, что помогает двигаться компаниям вперёд, это вдохновляет людей, которым знакомы технические проблемы, решаемые нами в Airbnb. Возможно, так они подумают, что мы ждём их для работы у нас».

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


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

Для НТП был специально разработан АРI, с помощью которого эту информацию можно применить в собственных проектах на условиях использования открытых данных.

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

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

Для кого это будет полезно:

  • Стартапам, которые хотят получить сильного союзника в лице Ростуризма.
  • Тем, кто готов сделать трэвел‑стартап и ждёт знака свыше. Собственно, вот он, этот знак.
  • Специалистам в области разработки, маркетинга, дизайна и бизнес‑девелопмента, которые хотят узнать, как оно – работать в стартапе.

ссылка на оригинал статьи http://habrahabr.ru/post/271935/

sin 1° на калькуляторе

Калькулятор Casio
Важное уточнение — калькулятор обычный, без кнопки sin. Как в бухгалтерии или на рынке.



Простое решение

Первое, что приходит в голову — вот такое заклинание:

355 / 113 / 180 = MC M+ * = * MR / 6 +- = + MR =

Переведём эту путаную партитуру для калькулятора на более понятный язык bc. Он часто используется как калькулятор в командной строке UNIX-подобных операционных систем. Увидим примерно следующее:

  scale = 7   x = 355/113/180   x-x^3/6   .0174524 

Откуда это взялось

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

sin(x) ≅ x — x3/6

Перед подстановкой градус придётся перевести в радианы умножением на π и делением на 180°.

Отдельный приз полагается заметившим странные цифры 355 и 113. Их нашёл в наш китайский товарищ Цзу Чунчжи (祖沖之) ещё во времена династии Ци (479—502). Отношение 355/113 — это единственное приближение числа π рациональной дробью, которое короче десятичного представления аналогичной точности.

Интересное решение

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

Заведующий Самаркандской обсерваторией Гияс-ад-дин Джамшид ибн Масуд аль-Каши (غیاث الدین جمشید کاشانی‎‎) составил таблицы тригонометрических функций с точностью до 16-го знака ещё до 1429 года. В переводе с персидского на bc его заклинание применительно к нашей задаче выглядело примерно так:

  scale = 16    sin30 = .5   cos30 = sqrt(3)/2    sin45 = sqrt(2)/2   cos45 = sin45    sin75 = sin30*cos45+cos30*sin45   cos75 = sqrt(1-sin75^2)    cos36 = (1+sqrt(5))/4   sin36 = sqrt(1-cos36^2)    sin72 = 2*sin36*cos36   cos72 = sqrt(1-sin72^2)    (sin3 = sin75*cos72-cos75*sin72)   .0523359562429430    (x = sin3/3)   .0174453187476476   (x = (sin3+4*x^3)/3)   .0174523978055315   (x = (sin3+4*x^3)/3)   .0174524064267667   (x = (sin3+4*x^3)/3)   .0174524064372703   (x = (sin3+4*x^3)/3)   .0174524064372831   (x = (sin3+4*x^3)/3)   .0174524064372831 

Обратите внимание на то, что мы по-прежнему используем только сложение, вычитание, умножение, деление и квадратный корень. При желании все эти операции можно выполнить вообще на бумажке в столбик. Cчитать квадратный корень в столбик раньше даже учили в школе. Это занудно, но не очень сложно.

Что это за шаманство

Разберём магию аль-Каши по шагам.

  sin30 = .5   cos30 = sqrt(3)/2    sin45 = sqrt(2)/2   cos45 = sin45 

Синус и косинус 30° и 45° были известны ещё древним грекам.

  sin75 = sin30*cos45+cos30*sin45 

Налицо синус суммы углов 30° и 45°. Ещё до аль-Каши эту формулу вывел другой персидский астроном, Абуль-Вафа Мухаммад ибн Мухаммад ибн Яхья ибн Исмаил ибн Аббас аль-Бузджани.

  cos75 = sqrt(1-sin75^2) 

Пифагоровы штаны во все стороны равны.

  cos36 = (1+sqrt(5))/4   sin36 = sqrt(1-cos36^2) 

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

  sin72 = 2*sin36*cos36   cos72 = sqrt(1-sin72^2) 

Опять синус суммы и теорема Пифагора.

  (sin3 = sin75*cos72-cos75*sin72)   .0523359562429430 

Считаем синус разности 75° и 72° и получаем синус 3°.

Теперь можно разложить 3° на сумму трёх углов по 1°, но возникает заминка — получаем кубическое уравнение:

sin 3° = 3 x — 4 x3

где x = sin 1°. Решать кубические уравнения аналитически тогда ещё никто не умел.

Мудрый аль-Каши заметил, что можно выразить это уравнение в следующей форме:

f(x) = (sin 3° + 4 x3) / 3

и потом применить к f(x) метод простой итерации. Напоминаю, что в то время ни Ньютон, ни Рафсон ещё не родились.

  (x = sin3/3) 

Первое приближение.

  .0174453187476476   (x = (sin3+4*x^3)/3)   .0174523978055315   (x = (sin3+4*x^3)/3)   .0174524064267667   (x = (sin3+4*x^3)/3)   .0174524064372703   (x = (sin3+4*x^3)/3)   .0174524064372831   (x = (sin3+4*x^3)/3)   .0174524064372831 

Получаем 16 знаков после пяти итераций.

Как считает сам калькулятор

У пытливого читателя может возникнуть законный вопрос: как же считает значение синуса калькулятор, у которого есть такая кнопка?

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

Причём тут бомбардировщик Б-58

Придумал этот алгоритм Джек Волдер, который тогда работал в компании Конвэйр над навигационным вычислителем вышеупомянутого бомбардировщика.

Главное преимущество метода «цифра за цифрой» в том, что он использует только операции сложения и умножения на два (которое легко реализовать сдвигом влево).

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

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

#include <stdio.h> #include <math.h>  int main(int argc, char **argv) {   int bits = 32;   int cordic_one = 1 << (bits - 2);   printf("// Число с фиксированной точкой, соответствующее единице с плавающей точкой\n");   printf("static const int cordic_one = 0x%08x;\n", cordic_one);   printf("static const int cordic_table[] = {\n");   double k = 1;   for (int i = 0; i < bits; i++) {     printf("0x%08x, // 0x%08x * atan(1/%.0f) \n", (int)(atan(pow(2, -i)) * cordic_one), cordic_one, pow(2, i));     k /= sqrt(1 + pow(2, -2 * i));   }   printf("};\n");   printf("static const int cordic_k = 0x%08x; // %.16f * 0x%08x\n", (int)(k * cordic_one), k, cordic_one); } 

Заодно считается масштабирующий коэффициент cordic_k.

После этого посчитать пресловутый sin 1° можно так:

#include <stdio.h> #include <math.h>  // Число с фиксированной точкой, соответствующее единице с плавающей точкой static const int cordic_one = 0x40000000; static const int cordic_table[] = { 0x3243f6a8, // 0x40000000 * atan(1/1)  0x1dac6705, // 0x40000000 * atan(1/2)  0x0fadbafc, // 0x40000000 * atan(1/4)  0x07f56ea6, // 0x40000000 * atan(1/8)  0x03feab76, // 0x40000000 * atan(1/16)  0x01ffd55b, // 0x40000000 * atan(1/32)  0x00fffaaa, // 0x40000000 * atan(1/64)  0x007fff55, // 0x40000000 * atan(1/128)  0x003fffea, // 0x40000000 * atan(1/256)  0x001ffffd, // 0x40000000 * atan(1/512)  0x000fffff, // 0x40000000 * atan(1/1024)  0x0007ffff, // 0x40000000 * atan(1/2048)  0x0003ffff, // 0x40000000 * atan(1/4096)  0x0001ffff, // 0x40000000 * atan(1/8192)  0x0000ffff, // 0x40000000 * atan(1/16384)  0x00007fff, // 0x40000000 * atan(1/32768)  0x00003fff, // 0x40000000 * atan(1/65536)  0x00001fff, // 0x40000000 * atan(1/131072)  0x00000fff, // 0x40000000 * atan(1/262144)  0x000007ff, // 0x40000000 * atan(1/524288)  0x000003ff, // 0x40000000 * atan(1/1048576)  0x000001ff, // 0x40000000 * atan(1/2097152)  0x000000ff, // 0x40000000 * atan(1/4194304)  0x0000007f, // 0x40000000 * atan(1/8388608)  0x0000003f, // 0x40000000 * atan(1/16777216)  0x0000001f, // 0x40000000 * atan(1/33554432)  0x0000000f, // 0x40000000 * atan(1/67108864)  0x00000008, // 0x40000000 * atan(1/134217728)  0x00000004, // 0x40000000 * atan(1/268435456)  0x00000002, // 0x40000000 * atan(1/536870912)  0x00000001, // 0x40000000 * atan(1/1073741824)  0x00000000, // 0x40000000 * atan(1/2147483648)  }; static const int cordic_k = 0x26dd3b6a; // 0.6072529350088813 * 0x40000000  void cordic(int theta, int& s, int& c) {   c = cordic_k;   s = 0;   for (int k = 0; k < 32; ++k) {     int d = (theta >= 0) ? 0 : -1;     int tx = c - (((s >> k) ^ d) - d);     int ty = s + (((c >> k) ^ d) - d);     c = tx; s = ty;     theta -= ((cordic_table[k] ^ d) - d);   } }  int main(void) {   double alpha = M_PI / 180;   int sine, cosine;   cordic(alpha * cordic_one, sine, cosine);   printf("CORDIC:   %.8f\nExpected: %.8f\n", (double)sine / cordic_one, sin(alpha)); } 

Результат:

CORDIC:   0.01745240 Expected: 0.01745241 

Тут 32 итерации, поэтому осталась небольшая ошибка. Калькуляторы обычно используют 40 итераций.

ссылка на оригинал статьи http://habrahabr.ru/post/271889/

Роспотребнадзор разработал итоговый законопроект о защите покупателей от «недобросовестных» интернет-агрегаторов

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

Роспотребнадзор считает, что на российском рынке онлайн-ретейлинга есть «недобросовестные» магазины и агрегаторы. Они не раскрывают информацию о продавце, изготовителе продуктов, о самом товаре или услугах. Они получают деньги на счет как посредники между продавцами и покупателями, но при возникновении проблем к ответственности их привлечь практически невозможно. В июле ведомство предложило ввести возможность блокировки таких сайтов и рассматривать интернет-ретейлеров как самостоятельный субъект ответственности.

Интернет-магазины в прошлом году, в свою очередь, потребовали регулировать работу товарных агрегаторов — сайтов, которые сами не продают товары, но по запросу подбирают и сравнивают предложения разных онлайн-площадок. Примерно по такой схеме работают, например, «Яндекс.Маркет», Wikimart, «Товары@Mail.ru», Price.ru и eBay.

Весной 2014-го президент «М.Видео» Александр Тынкован обвинил «Яндекс.Маркет» в продаже контрафакта и заявил, что сервис должен нести ответственность за нарушение прав потребителей, напоминает РБК. «Яндекс» возражал, что работает, по сути, как рекламная площадка.

Если eBay или Amazon могут удалить подозрительный товар после жалобы правообладателя, то российские сервисы предлагают по каждому случаю обращаться в суд. В 2014 году оборот интернет-торговли в России вырос на 31%, до 713 миллиардов рублей, отмечал Роспотребнадзор со ссылкой на экспертные оценки.

В сентябре Российская ассоциация электронных компаний (РАЭК) в своем письме раскритиковала версию поправок Роспотребнадзора к закону «О защите прав потребителей». Основная проблема с «недобросовестными» ретейлерами, по мнению РАЭК, связана с работой не отечественных, а иностранных онлайн-площадок в России. Теперь Роскомнадзор доработал проект.

РБК описывает последние изменения законопроекта.

1. В пользу агрегаторов

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

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

2. Против агрегаторов

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

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

Предполагается, что новая версия поправок вступит в силу с 1 января 2017 года.

Законопроект в новой редакции невозможно будет применить на практике, так как он содержит «множество условий и оговорок», считает Ассоциация компаний интернет-торговли (АКИТ).

Как пишет исполнительный директор АКИТ Артем Соколов, «текущая версия [поправок] выводит значительную часть деятельности агрегаторов из-под государственного надзора». Не исключено, что в итоге агрегаторам разрешат ограничиваться ссылками на сайты продавцов вместо того, чтобы публиковать о них подробную информацию, беспокоятся в АКИТ.

Хотя новая редакция документа — это меньшее из зол, агрегаторы нововведениями тоже не вполне довольны, утверждает президент Национальной ассоциации дистанционной торговли (НАДТ) Александр Иванов. По его словам, потребители могут оказаться в сложной ситуации: если раньше они точно знали, что должны обращаться с претензиями к продавцу, то теперь у них не будет такой уверенности.

По его словам руководителя «Яндекс.Маркета» Павла Алешина, программы защиты покупателей уже есть практически у всех агрегаторов. Так, «Яндекс.Маркет», Wikimart, «Товары@Mail.ru» и их коллеги в декабре 2014-го запустили сайт для приема жалоб на поддельные товары nota-claim.ru: если товар действительно окажется контрафактом, его обещают убрать со всех сайтов участников объединения.

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

ссылка на оригинал статьи http://megamozg.ru/post/22270/

Экспериментальная версия PVS-Studio, поддерживающая C#

PVS-Studio for C/C++/C#
У нас появилась экспериментальная версия анализатора PVS-Studio, который умеет анализировать C#-проекты и который можно показать миру. Это не Release, и даже не Beta. Это просто текущая сборка PVS-Studio. Мы хотим как можно раньше начать получить отзывы от наших пользователей или потенциальных пользователей касательно поддержки C#. Поэтому предлагаем энтузиастам попробовать новую версию PVS-Studio на своих C#-проектах и рассказать нам о результатах, недостатках и высказать свои пожелания. Ах да, и конечно в статье будут описаны результаты проверки очередного проекта — SharpDevelop.

PVS-Studio

Сейчас одним из важных вопросов является: «Зачем делать очередной инструмент анализа кода для C#?».

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

Мы успешно создали и продолжаем развивать анализатор PVS-Studio для языка C/C++. В этом анализаторе реализовано много интересных и уникальных идей по выявлению разнообразных типов ошибок. Со временем стало понятно, что многие из реализованных диагностик никак не связаны с конкретным языком программирования. Не важно, какой язык вы используете. Всегда будут опечатки, ошибки из-за невнимательности или неудачного Copy-Paste.

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

Сейчас наша задача — начать как можно раньше получать отзывы от наших потенциальных пользователей. Полноценная версия анализатора PVS-Studio ещё не готова. Сейчас в ней мало диагностик (на момент написания статьи их было 36). Но эту версию можно установить и попробовать. И мы будем благодарны всем, кто это сделает. Нам важно убедиться, что мы вообще движемся в верном направлении и что анализатор в целом работоспособен. А у уж добавлять новые и новые диагностики мы будем очень быстро.

Итак, всем заинтересованным предлагаю скачать текущий вариант экспериментальной версии PVS-Studio по этой ссылке: http://files.viva64.com/beta/PVS-Studio_setup.exe.

Примечание. Со временем приведенная выше ссылка станет недействительной. Поэтому, если вы читаете эту статью по пришествию месяца или более с момента публикации, то предлагаем вам установить текущую версию дистрибутива: http://www.viva64.com/ru/pvs-studio-download/

Если читатель ещё не разу не пробовал PVS-Studio, то предлагаю ознакомиться со статьёй "PVS-Studio для Visual C++". Как вы видите, она ориентирована на C++, но на самом деле никакой разницы нет. С точки зрения интерфейса вообще почти нет никакой разницы, работаете вы с C++ или с C# проектами.

Чтобы отправить свои отзывы и пожелания, вы можете воспользоваться страницей обратной связи.

Проверка проекта SharpDevelop

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

Не вижу смысла изобретать велосипед. Теперь тем же методом я буду очаровывать C# программистов. И вот перед вами очередная статья про проверку открытого проекта SharpDevelop.

SharpDevelop — свободная среда разработки для C#, Visual Basic .NET, Boo, IronPython, IronRuby, F#, C++. Обычно используется как альтернатива Visual Studio .NET. Существует также форк на Mono/GTK+ — MonoDevelop.

Для нас важно, что проект написан полностью на C#. А значит мы можем его проверить экспериментальной версией PVS-Studio. В проекте 8522 файлов с расширением «cs», суммарный размер которых равен 45 мегабайт.

Наиболее подозрительные фрагменты кода

Фрагмент N1

public override string ToString() {   return String.Format("Thread Name = {1} Suspended = {2}",                        ID, Name, Suspended); }

Предупреждение PVS-Studio: V3025 Incorrect format. A different number of actual arguments is expected while calling ‘Format’ function. Expected: 2. Present: 3. Thread.cs 235

Переменная ID никак не используется. Возможно никакой настоящей ошибки здесь нет. Однако это место явно стоит проверить. Возможно здесь планировалось сформировать совсем иную строку.

Фрагмент N2

public override string ToString () {   return     String.Format ("[Line {0}:{1,2}-{3,4}:{5}]",                    File, Row, Column, EndRow, EndColumn, Offset); }

Предупреждение PVS-Studio: V3025 Incorrect format. A different number of actual arguments is expected while calling ‘Format’ function. Expected: 4. Present: 6. MonoSymbolTable.cs 235

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

[Line file.cs:{10,20}-{30,40}:7]

Но видимо он пропустил некоторые фигурные скобки. Поэтому получается, что ",2" и ",4" задаёт выравнивание полей, а вовсе не выводят значения переменных EndRow и EndColumn.

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

String.Format ("[Line {0}:{{1},{2}}-{{3},{4}}:{5}]",                File, Row, Column, EndRow, EndColumn, Offset);

Фрагмент N3

static MemberCore GetLaterDefinedMember(MemberSpec a, MemberSpec b) {   var mc_a = a.MemberDefinition as MemberCore;   var mc_b = b.MemberDefinition as MemberCore;    ....    if (mc_a.Location.File != mc_a.Location.File)     return mc_b;    return mc_b.Location.Row > mc_a.Location.Row ? mc_b : mc_a; }

Предупреждение PVS-Studio: V3001 There are identical sub-expressions ‘mc_a.Location.File’ to the left and to the right of the ‘!=’ operator. membercache.cs 1306

Здесь мы имеем дело с опечаткой. Я думаю, правильным вариантом будет следующее сравнение:

if (mc_a.Location.File != mc_b.Location.File)

Фрагмент N4

public WhitespaceNode(string whiteSpaceText,                       TextLocation startLocation) {   this.WhiteSpaceText = WhiteSpaceText;   this.startLocation = startLocation; }

Предупреждение PVS-Studio: V3005 The ‘this.WhiteSpaceText’ variable is assigned to itself. WhitespaceNode.cs 65

Красивая ошибка. Здесь статический анализатор показал свою главную суть. Он внимателен и в отличии от человека не устаёт. Поэтому он заметил опечатку. А вы её видите? Согласитесь, найти ошибку не просто.

Итак, опечатка в одной букве. Надо было написать "=whiteSpaceText". А написано "=WhiteSpaceText". В результате значение ‘WhiteSpaceText’ в классе остаётся неизменным.

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

Фрагмент N5

new public bool Enabled {   get { return base.Enabled; }   set {     if (this.InvokeRequired) {       base.Enabled = this.VScrollBar.Enabled =       this.hexView.Enabled =this.textView.Enabled =       this.side.Enabled = this.header.Enabled = value;     } else {       base.Enabled = this.VScrollBar.Enabled =       this.hexView.Enabled = this.textView.Enabled =       this.side.Enabled = this.header.Enabled = value;     }   } }

Предупреждение PVS-Studio: V3004 The ‘then’ statement is equivalent to the ‘else’ statement. Editor.cs 225

Очень подозрительно, что независимо от значения ‘this.InvokeRequired’ будут выполнены одни и те-же действия. У меня сильные подозрения, что строка «base.Enabled = …..» была скопирована. А потом в ней что-то забыли изменить.

Фрагмент N6, N7, N8, N9

public override void Run() {   ....   ISolutionFolderNode solutionFolderNode =     node as ISolutionFolderNode;    if (node != null)   {     ISolutionFolder newSolutionFolder =       solutionFolderNode.Folder.CreateFolder(....);     solutionFolderNode.Solution.Save();   .... }

Предупреждение PVS-Studio: V3019 Possibly an incorrect variable is compared to null after type conversion using ‘as’ keyword. Check variables ‘node’, ‘solutionFolderNode’. SolutionNodeCommands.cs 127

Хотели выполнить некоторые действия, если ‘node’ наследуется от интерфейса ‘ISolutionFolderNode’. Но проверили не ту переменную. Правильный вариант:

ISolutionFolderNode solutionFolderNode =   node as ISolutionFolderNode; if (solutionFolderNode != null) {

Кстати, это достаточно распространённый паттерн ошибки в C#-программах. Например, в проекте SharpDevelop встретилось ещё 3 таких ошибки:

  • V3019 Possibly an incorrect variable is compared to null after type conversion using ‘as’ keyword. Check variables ‘geometry’, ‘g’. PathHandlerExtension.cs 578
  • V3019 Possibly an incorrect variable is compared to null after type conversion using ‘as’ keyword. Check variables ‘oldTransform’, ‘tg’. ModelTools.cs 420
  • V3019 Possibly an incorrect variable is compared to null after type conversion using ‘as’ keyword. Check variables ‘node’, ‘solutionFolderNode’. SolutionNodeCommands.cs 104

Фрагмент N10

public override void VisitInvocationExpression(....) {   ....   foundInvocations = (idExpression.Identifier == _varName);   foundInvocations = true;   .... }

Предупреждение PVS-Studio: V3008 The ‘foundInvocations’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 211, 209. RedundantAssignmentIssue.cs 211

Очень подозрительное повторное присваивание. Возможно, второе присваивание написали в процессе отладки кода, а потом забыли про него.

Ошибка N11

public static Snippet CreateAvalonEditSnippet(....) {   ....   int pos = 0;   foreach (Match m in pattern.Matches(snippetText)) {     if (pos < m.Index) {       snippet.Elements.Add(....);       pos = m.Index;     }     snippet.Elements.Add(....);     pos = m.Index + m.Length;   }   .... }

Предупреждение PVS-Studio: V3008 The ‘pos’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 151, 148. CodeSnippet.cs 151

Ещё одно подозрительное повторное присваивание. Здесь или ошибка, или присваивание «pos = m.Index;» является лишним.

Фрагмент N12

.... public string Text { get; set; } .... protected override void OnKeyUp(KeyEventArgs e) {   ....   editor.Text.Insert(editor.CaretIndex, Environment.NewLine);   .... }

Предупреждение PVS-Studio: V3010 The return value of function ‘Insert’ is required to be utilized. InPlaceEditor.cs 166

Строки в языке C# являются неизменяемыми. Поэтому, если мы что-то делаем со строкой, результат нужно где-то сохранить. Однако про это легко забыть, как например это случилось здесь. Разработчик решил, что вызывая метод Insert() он что-то добавит к строке. Но это не так. Правильный вариант кода:

editor.Text =   editor.Text.Insert(editor.CaretIndex, Environment.NewLine);

Фрагмент N13, N14

public IEnumerable<PropertyMapping> GetMappingForTable(SSDL.EntityType.EntityType table) {   var value = GetSpecificMappingForTable(table);   var baseMapping = BaseMapping;   if (baseMapping != null)     value.Union(baseMapping.GetMappingForTable(table));   return value; }

Предупреждение PVS-Studio: V3010 The return value of function ‘Union’ is required to be utilized. MappingBase.cs 274

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

Метод-расширение ‘Union’, определённый для коллекций, реализующих интерфейс IEnumerable, позволяет получить пересечение двух множество. Однако, контейнер ‘value’ при этом не изменяется. Правильный вариант:

value = value.Union(baseMapping.GetMappingForTable(table));

Ещё одну такую ситуацию можно встретить здесь: V3010 The return value of function ‘OrderBy’ is required to be utilized. CodeCoverageMethodElement.cs 124

Фрагмент N15

Анализатор PVS-Studio пытается выявить ситуации, где программист мог что-то забыть сделать в switch(). Логика принятия решения, предупреждать или нет, достаточно сложная. Иногда получаются ложные срабатывания, иногда находятся явные ошибки. Рассмотрим одно из таких срабатываний.

Итак, в коде имеет место вот такое перечисление:

public enum TargetArchitecture {   I386,   AMD64,   IA64,   ARMv7, }

Местами используются все варианты этого перечисления:

TargetArchitecture ReadArchitecture () {   var machine = ReadUInt16 ();   switch (machine) {   case 0x014c:     return TargetArchitecture.I386;   case 0x8664:     return TargetArchitecture.AMD64;   case 0x0200:     return TargetArchitecture.IA64;   case 0x01c4:     return TargetArchitecture.ARMv7;   }   throw new NotSupportedException (); }

Однако есть и подозрительные места. Например, анализатор обратил моё внимание на следующий код:

ushort GetMachine () {   switch (module.Architecture) {   case TargetArchitecture.I386:     return 0x014c;   case TargetArchitecture.AMD64:     return 0x8664;   case TargetArchitecture.IA64:     return 0x0200;   }   throw new NotSupportedException (); }

Предупреждение PVS-Studio: V3002 The switch statement does not cover all values of the ‘TargetArchitecture’ enum: ARMv7. ImageWriter.cs 209

Как видите, не рассмотрен случай, если архитектурой является ARMv7. Я не знаю, ошибка это или нет. Но мне кажется, что это именно ошибка. Имя ARMv7 находится в конце перечисления, а значит добавлялось в последнюю очередь. В результате программист мог забыть поправить функцию GetMachine() и учесть эту архитектуру.

Фрагмент N15

void DetermineCurrentKind() {   .....   else if (Brush is LinearGradientBrush) {     linearGradientBrush = Brush as LinearGradientBrush;     radialGradientBrush.GradientStops =       linearGradientBrush.GradientStops;     CurrentKind = BrushEditorKind.Linear;   }   else if (Brush is RadialGradientBrush) {     radialGradientBrush = Brush as RadialGradientBrush;     linearGradientBrush.GradientStops =       linearGradientBrush.GradientStops;     CurrentKind = BrushEditorKind.Radial;   } }

Предупреждение PVS-Studio: V3005 The ‘linearGradientBrush.GradientStops’ variable is assigned to itself. BrushEditor.cs 120

Достаточно тяжелый фрагмент кода для чтения. И видимо поэтому в нем допущена ошибка. Скорее всего код писался методом Copy-Paste и в одно месте был неправильно изменён.

По все видимости вместо:

linearGradientBrush.GradientStops =   linearGradientBrush.GradientStops;

Должно было быть написано:

linearGradientBrush.GradientStops =   radialGradientBrush.GradientStops;

Запахи

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

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

Фрагмент кода с запахом N1

protected override bool CanExecuteCommand(ICommand command) {   ....   }   else if (command == DockableContentCommands.ShowAsDocument)   {     if (State == DockableContentState.Document)     {       return false;     }   }   ....   else if (command == DockableContentCommands.ShowAsDocument)   {     if (State == DockableContentState.Document)     {       return false;     }   }   .... }

Предупреждение PVS-Studio: V3003 The use of ‘if (A) {…} else if (A) {…}’ pattern was detected. There is a probability of logical error presence. Check lines: 773, 798. DockableContent.cs 773

Как видите, программа содержит два идентичных блока. Условие нижнего блока ‘if’ никогда не выполнится. Но на мой взгляд это не ошибка. Мне кажется, что просто случайно продублировали блок, и он является лишним. Тем не менее это место, которое стоит посмотреть и исправить.

Фрагмент кода с запахом N2

void PropertyExpandButton_Click(object sender, RoutedEventArgs e) {   ....   ContentPropertyNode clickedNode =     clickedButton.DataContext as ContentPropertyNode;   clickedNode = clickedButton.DataContext as ContentPropertyNode;   if (clickedNode == null)   .... }

Предупреждение PVS-Studio: V3008 The ‘clickedNode’ variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 105, 104. PositionedGraphNodeControl.xaml.cs 105

Код избыточный и его можно упростить до:

ContentPropertyNode clickedNode =   clickedButton.DataContext as ContentPropertyNode; if (clickedNode == null)

Фрагмент кода с запахом N3

IEnumerable<ICompletionData> CreateConstructorCompletionData(IType hintType) {   ....   if (!(hintType.Kind == TypeKind.Interface &&         hintType.Kind != TypeKind.Array)) {   .... }

Предупреждение PVS-Studio: V3023 Consider inspecting this expression. The expression is excessive or contains a misprint. CSharpCompletionEngine.cs 2392

Избыточный код. Выражение можно упростить:

if (hintType.Kind != TypeKind.Interface) {

Я могу продолжать, но, пожалуй, хватит. Все остальные «запахи» достаточно однообразны и похожи на уже перечисленные.

Заключение

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

PVS-Studio and C#

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

А если серьезно, то:

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


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Experimental version of PVS-Studio with C# support.

Прочитали статью и есть вопрос?

Часто к нашим статьям задают одни и те же вопросы. Ответы на них мы собрали здесь: Ответы на вопросы читателей статей про PVS-Studio, версия 2015. Пожалуйста, ознакомьтесь со списком.

ссылка на оригинал статьи http://habrahabr.ru/post/271993/

Анонс онлайн-курсов Технопарка, Техносферы и Технотрека на Stepic

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

Со временем количество курсов будет увеличено.

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

Алгоритмы и структуры данных

Для прохождения обучения требуются базовые навыки программирования на одном из популярных языков, например, C или C++. Курс содержит описание основных алгоритмов и структур данных. Вначале даются базовые понятия и оценка сложности, которые разбираются на примере следующих алгоритмов: «Вычисление чисел Фибоначчи», «Проверка числа на простоту», «Быстрое возведение в степень». Затем обсуждаются основные алгоритмы на массиве, линейный и бинарный поиск в массиве, структура данных «Динамический массив».

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

Всего за курс можно набрать 100 баллов. Сертификат выдается за 85 баллов. Сертификат с отличием — за 95 баллов.

Подготовительная программа по программированию на С/C++

Курс посвящён изучению и реализации основных принципов объектно-ориентированного и обобщённого программирования на языке С++. Будут рассмотрены следующие темы:

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

Многопоточное программирование на С/С++

Для прохождения обучения требуется знание C/C++ и умение обращаться с Unix-like операционными системами.Перед курсом предлагается пройти диагностический экзамен, который не оценивается, но позволяет понять, насколько вы готовы идти дальше. Курс состоит из семи двухнедельных учебных модулей:

  1. Контейнеры. Умные указатели. Аллокаторы.
  2. Сокеты Беркли. Мультиплексирование.
  3. Асинхронная работа с сетью.
  4. Процессы. Каналы. Сигналы.
  5. Очереди сообщений. Семафоры. Общая память.
  6. Потоки. Средства синхронизации.
  7. Параллельное программирование.

Каждый модуль оценивается в 100 баллов. Кроме того, есть финальный «экзамен», — собственный проект, — он оценивается в 300 баллов.

Чтобы получить сертификат, надо набрать 900. Для сертификата с отличием нужно 1000.

Hadoop. Система для обработки больших объемов данных

Требования:

  • Знание языков программирования не является обязательным, но очень желательно.
  • В курсе есть примеры программ, и в практических задачах нужно уметь читать и писать код. При этом используются Java и Python. Глубокого знания этих языков не требуется, и проблем не будет, если вы знаете только С++.
  • Нужно быть знакомым с Linux и уметь работать в командной строке.
  • Желательно хотя бы немного знать английский язык, т.к. в курсе встречаются термины и надписи на английском.

BigData, MapReduce, облачные вычисления, NoSQL. Все эти понятия стали популярными в последние годы. И все они связаны с распределенной обработкой больших объемов данных. Hadoop — одна из самых популярных opensource-систем для обработки больших объемов данных. Необходимость в таких системах растет с каждым годом — все больше компаний сталкиваются с проблемой растущего объема данных.

Facebook, Twitter, Yahoo!, Bing, Mail.Ru — это далеко не полный список компаний, которые используют Hadoop. Многие из них при этом активно участвуют в его развитии. И это неслучайно, т.к. именно большие интернет-компании первыми столкнулись с проблемой обработки больших объемов данных: как их надежно хранить, как обрабатывать, как получать быстрый доступ на их изменение. Сейчас Hadoop используется не только в интернет-компаниях, но и во многих других сферах, где возникает проблема с объемом данных (экономика, астрономия, биология, физика и т.д.)

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

Разработка веб-сервиса на Java (часть 1)

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

Курс построен на базе практического опыта разработки серверов для онлайновых игр и опыта прочтения лекций в Технопарке Mail.Ru в МГТУ им. Баумана. Автор курса постарался соблюсти баланс между академической последовательностью и практической полезностью материала. Курс состоит из двух частей, разделенных на 4 и 3 модуля соответственно. В каждом модуле разбирается одна большая тема:

1.1. Работа веб-сервера.
1.2. Авторизация пользователя.
1.3. Работа с базами.

2.1. Асинхронное взаимодействие с браузером.
2.2. Тестирование и нагрузка.
2.3. Работа с файлами.
2.4. Многопоточность.

Кроме того, студентам дается задание на практическую разработку кода приложения, использующего изученный материал. Успешное завершение курса даст вам: базовые основы разработки веб-приложения на Java, углубленное понимание затронутых в курсе тем ООП и проектирования; позволит вам сформировать пакет приложений, который вы сможете использовать в промышленной разработке.

Запись на обучение осуществляется через портал IT.Mail.Ru. Первые занятия начнутся совсем скоро!

ссылка на оригинал статьи http://habrahabr.ru/post/271971/