-Поиск по дневнику

Поиск сообщений в rss_rss_hh_new

 -Подписка по e-mail

 

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 51

Habrahabr/New








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/new/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-new-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

В поисках оптимальной диеты методом линейного программирования

Воскресенье, 01 Октября 2017 г. 21:27 + в цитатник
Scorobey сегодня в 21:27 Разработка

В поисках оптимальной диеты методом линейного программирования

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

    Должна ли диета быть экономной?


    Для поддержания нормальной жизнедеятельности человеку необходимо потреблять в день не менее 118 г белков, 56 г жиров, 500 г углеводов и 28 г минеральных солей. Эти питательные вещества содержатся в разных количествах и разных пищевых продуктах.

    В таблице приведено количество питательных веществ в различных продуктах в г/кг и цена этих продуктов за 1 кг. Необходимо составить дневной рацион, содержащий минимальную суточную норму питательных веществ при минимальной их стоимости.



    Обозначив через: Х1 –количество мяса; Х2- количество рыбы; Х3- количество молока; Х4- количество масла; Х5- количество сыра; Х6- количество крупы; Х7- количество картофеля, потребляемых человеком в день. Можем составить уравнение общей стоимости F питания в день:
    F=333*X1+308*X2+52*X3+400*X4+450*X5+56*X6+25*X7

    Нам нужно найти минимум F.
    Суммарное количество белков в рационе человека должно быть не меньше 118 г. Отсюда,
    180*X1+190*X2+30*X3+10*X4+260*X5+130*X6+21*X7>=118

    Такие же неравенства составляем для жиров, углеводов и солей. Имеем:
    20*X1+3*X2+40*X3+865*X4+310*X5+30*X6+2*X7>=56
    50*X3+6*X4+20*X5+650*X6+200*X7>=500
    9*X1+10*X2+7*X3+12*X4+60*X5+20*X6+10*X7>=28

    Решим задачу на Python


    from cvxopt.modeling import variable, op
    import time
    start = time.time()
    x = variable(7, 'x')
    z=(333*x[0] + 308*x[1] +52* x[2] +400*x[3] +450*x[4] +56* x[5]+20*x[6])
    mass1 =(- (180*x[0] + 190*x[1] +30* x[2] +10*x[3] +260*x[4] +130* x[5]+21*x[6]) <= -118)
    mass2 =(- (20*x[0] + 3*x[1] +40* x[2] +865*x[3] +310*x[4] +30* x[5]+2*x[6]) <= -56)
    mass3 =(- (50* x[2] +6*x[3] +20*x[4] +650* x[5]+200*x[6]) <= -500) 
    mass4 =(- (9*x[0] + 10*x[1] +7* x[2] +12*x[3] +60*x[4] +20* x[5]+10*x[6]) <= -28)
    x_non_negative = (x >= 0)    
    problem =op(z,[mass1,mass2,mass3,mass4 ,x_non_negative])
    problem.solve(solver='glpk')  
    problem.status
    print("Результат:")
    print(round(1000*x.value[0],1),'-грамм мяса, затраты -',round(x.value[0]*333,1),'руб.')
    print(round(1000*x.value[1],1),'-грамм рыбы, затраты -',round(x.value[1]*308,1),'руб.')
    print(round(1000*x.value[2],1),'-миллилитров молока, затраты -',round(x.value[2]*52,1),'руб.')
    print(round(1000*x.value[3],1),'-грамм масла, затраты -',round(x.value[3]*400,1),'руб.')
    print(round(1000*x.value[4],1),'-грамм сыр, затраты -',round(x.value[4]*450,1),'руб.')
    print(round(1000*x.value[5],1),'-грамм крупы, затраты -',round(x.value[5]*56,1),'руб.')
    print(round(1000*x.value[6],1),'-грамм картофеля, затраты -',round(x.value[6]*25,1),'руб.')
    print(round(problem.objective.value()[0],1),"- стоимость рациона одного человека в день")
    stop = time.time()
    print ("Время :",round(stop-start,3))
    


    Следует отметить некоторые особенности написания программы с использованием модуля cvxopt. Modeling: все переменные сохраняются в списках, а индексы списков начинаются не с 1, а с 0; в условиях, которые записываются в виде нестрогих неравенств должно быть установлено ограничение сверху, поэтому, для перехода от ограничения снизу, обе части неравенств умножены на -1.

    Результат:


    0.0 -грамм мяса, затраты — 0.0 руб.
    0.0 -грамм рыбы, затраты — 0.0 руб.
    0.0 -миллилитров молока, затраты — 0.0 руб.
    38.0 -грамм масла, затраты — 15.2 руб.
    -0.0 -грамм сыр, затраты — -0.0 руб.
    679.3 -грамм крупы, затраты — 38.0 руб.
    1395.9 -грамм картофеля, затраты — 34.9 руб.
    81.1 — стоимость рациона одного человека в день
    Время: 0.09

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

    Должна ли диета быть калорийной?


    Для определённости предположим, что в качестве исходных продуктов рассмотрим хлеб, мясо, сыр, бананы, огурцы, помидоры, виноград – всего семь продуктов. В качестве питательных веществ – белки, жира, углеводы.

    Калорийность одной весовой единицы каждого из продуктов следующая: c1=2060, c2=2430, c3=3600, c4=890, c5=140, c6=230, c7=650.

    Содержание питательных веществ в продуктах питания поместим в следующую таблицу.



    Минимальная суточная потребность человека в питательных веществах следующая: в белках b1=100, в жирах b2=70, в углеводах b3=400.

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

    Обозначив через: x1 –количество хлеба; x2- количество мяса; x3- количество сыра; x4- количество бананов; x5- количество огурцов; x6- количество помидоров; x7- количество винограда, потребляемых человеком в день в килограммах.

    Можем составить уравнение общей калорийности F питания в день:
    F=2060*x1 + 2430*x2 +3600* x3+890*x4 +140*x5 +230* x6+650*x7

    Нам нужно найти минимум F.
    Суммарное количество белков в рационе человека должно быть не меньше 100 г. Отсюда
    61*x1+ 220*x2 +230* x3 +15*x4 +8*x5 +11* x6+6*x7 >=100

    Такие же неравенства составляем для жиров и углеводов. Имеем:
    12*x1 +172*x2 +290* x3+1*x4 +1*x5 +2* x6+2*x7 >=70
    420*x1 +0*x2 +0* x3 +212*x4+26*x5 +38* x6+155*x7 >=400

    Решим задачу на Python


    from cvxopt.modeling import variable, op
    import time
    start = time.time()
    x = variable(7, 'x')
    z=(333*x[0] + 308*x[1] +52* x[2] +400*x[3] +450*x[4] +56* x[5]+20*x[6])
    mass1 =(- (180*x[0] + 190*x[1] +30* x[2] +10*x[3] +260*x[4] +130* x[5]+21*x[6]) <= -118)
    mass2 =(- (20*x[0] + 3*x[1] +40* x[2] +865*x[3] +310*x[4] +30* x[5]+2*x[6]) <= -56)
    mass3 =(- (50* x[2] +6*x[3] +20*x[4] +650* x[5]+200*x[6]) <= -500) 
    mass4 =(- (9*x[0] + 10*x[1] +7* x[2] +12*x[3] +60*x[4] +20* x[5]+10*x[6]) <= -28)
    x_non_negative = (x >= 0)    
    problem =op(z,[mass1,mass2,mass3,mass4 ,x_non_negative])
    problem.solve(solver='glpk')  
    problem.status
    print("Результат:")
    print(round(1000*x.value[0],1),'-грамм хлеба')
    print(round(1000*x.value[1],1),'-грамм мяса')
    print(round(1000*x.value[2],1),'-грамм сыра')
    print(round(1000*x.value[3],1),'-грамм бананов')
    print(round(1000*x.value[4],1),'-грамм огурцов')
    print(round(1000*x.value[5],1),'-грамм помидоров')
    print(round(1000*x.value[6],1),'-грамм винограда')
    print(round(problem.objective.value()[0],1),"-Калорийность рациона одного человека в день")
    stop = time.time()
    print ("Время :",round(stop-start,3))
    


    Результат:


    0.0 -грамм хлеба
    211.5 -грамм мяса
    109.4 -грамм сыра
    1886.8 -грамм бананов
    0.0 -грамм огурцов
    0.0 -грамм помидоров
    0.0 -грамм винограда
    2587.1 -килокалорий -калорийность
    рациона одного человека в день
    Время: 0.06

    Анализ результатов решения показывает, что для удовлетворения суточной потребности в питательных веществах (белки, жиры, углеводы), следует использовать 211 грамм мяса баранины, 109 грамм сыра, 1887 грамм бананов, совсем отказаться от хлеба, огурцов, помидоров и винограда. При этом общая калорийность найденной оптимальной диеты будет равна 2587 килокалорий, что вполне соответствует малоактивному образу жизни без существенных физических нагрузок. Напомним, что согласно медицинским данным, энергетические затраты работников умственного труда (программисты, юристы, врачи, педагоги, бухгалтера) лежат в пределах 3000 килокалорий.

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

    Что можно предложить заинтересованному читателю?


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

    Решение задачи на Python (только как пример)


    from cvxopt.modeling import variable, op
    import time
    start = time.time()
    x = variable(7, 'x')
    z=( x[0] + x[1] +x[2] +x[3] +x[4] +x[5]+x[6])
    mass1 =(- (61*x[0] + 220*x[1] +230* x[2] +15*x[3] +8*x[4] +11* x[5]+6*x[6]) <= -100)
    mass2 =(- (12*x[0] +172*x[1] +290* x[2] +1*x[3] +1*x[4] +2* x[5]+2*x[6]) <= -70)
    mass3 =(- (420*x[0] +0*x[1] +0* x[2] +212*x[3] +26*x[4] +38* x[5]+155*x[6]) <= -400)
    mass4 =(-( 2060*x[0] + 2430*x[1] +3600* x[2] +890*x[3] +140*x[4] +230* x[5]+650*x[6]) <= -3000)
    x_non_negative = (x >= 0)    
    problem =op(z,[mass1,mass2,mass3, mass4,x_non_negative])
    problem.solve(solver='glpk')  
    problem.status
    print("Результат:")
    print(round(1000*x.value[0],1),'-грамм хлеба')
    print(round(1000*x.value[1],1),'-грамм мяса')
    print(round(1000*x.value[2],1),'-грамм сыра')
    print(round(1000*x.value[3],1),'-грамм бананов')
    print(round(1000*x.value[4],1),'-грамм огурцов')
    print(round(1000*x.value[5],1),'-грамм помидоров')
    print(round(1000*x.value[6],1),'-грамм винограда')
    print(round(problem.objective.value()[0],1),"-килограмм-общая масса продуктов из \n рациона одного человека в день")
    stop = time.time()
    print ("Время :",round(stop-start,3))
    


    Результат:


    952.4 -грамм хлеба
    0.0 -грамм мяса
    288.4 -грамм сыра
    0.0 -грамм бананов
    0.0 -грамм огурцов
    0.0 -грамм помидоров
    0.0 -грамм винограда
    1.2 -килограмм-общая масса продуктов из
    рациона одного человека в день
    Время: 0.051

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

    Вместо вывода


    Несмотря на выявленные недостатки рассмотренной математической модели задачи об оптимальной диете, найденное оптимальное решение в точности соответствует исходной постановке задачи. Это свидетельствует о достаточно высокой точности решения задач линейного программирования при помощи библиотеки cvxopt. Modeling Python.
    Интерфейс программы настолько простой и наглядный, что не требует каких-либо дополнительных навыков. Достаточно скачать и установить последнюю версию Python, например, с сайта [1], а библиотеку cvxopt с сайта [2]. Затем создать файл с расширением py и поместить в него любую из приведенных в статье программ, предварительно модифицировав её под свою задачу с новой функцией цели и ограничениями.

    Ссылки


    1. Python.
    2. Windows binaries for python.
    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/339088/


    Метки:  

    [Перевод] ggplot2: как легко совместить несколько графиков в одном, часть 3

    Воскресенье, 01 Октября 2017 г. 20:07 + в цитатник
    qc-enior сегодня в 20:07 Разработка

    ggplot2: как легко совместить несколько графиков в одном, часть 3

    • Перевод
    • Tutorial
    Эта статья шаг за шагом покажет, как совместить несколько ggplot-графиков на одной или нескольких иллюстрациях, с помощью вспомогательных функций, доступных в пакетах R ggpubr, cowplot и gridExtra. Также опишем, как экспортировать полученные графики в файл.

    Cмешиваем таблицу, текст и ggplot2-графики


    В этом разделе покажем, как вывести таблицу и текст вместе с графиком. Будем использовать набор данных iris.

    Начнем с создания таких графиков:
    1. график плотности переменной Sepal.Length. Функция R: ggdensity()ggpubr]
    2. сводная таблица, содержащая описательные статистики (среднее, стандартное отклонение, т.д.) Sepal.Length
      • Функция R для вычисления описательных статистик: desc_statby()ggpubr]
      • Функция R для создания таблицы с текстом: ggtexttable()ggpubr]
    3. абзац текста. Функция R: ggparagraph()ggpubr]

    Завершим, скомбинировав все три графика с помощью функции ggarrange()ggpubr].
    # График плотности "Sepal.Length"
    #::::::::::::::::::::::::::::::::::::::
    density.p <- ggdensity(iris, x = "Sepal.Length", 
                           fill = "Species", palette = "jco")
    # Вывести сводную таблицу Sepal.Length
    #::::::::::::::::::::::::::::::::::::::
    # Вычислить описательные статистики по группам
    stable <- desc_statby(iris, measure.var = "Sepal.Length",
                          grps = "Species")
    stable <- stable[, c("Species", "length", "mean", "sd")]
    # График со сводной таблицей, тема "medium orange" (средний оранжевый)
    stable.p <- ggtexttable(stable, rows = NULL, 
                            theme = ttheme("mOrange"))
    # Вывести текст
    #::::::::::::::::::::::::::::::::::::::
    text <- paste("iris data set gives the measurements in cm",
                  "of the variables sepal length and width",
                  "and petal length and width, respectively,",
                  "for 50 flowers from each of 3 species of iris.",
                 "The species are Iris setosa, versicolor, and virginica.", sep = " ")
    text.p <- ggparagraph(text = text, face = "italic", size = 11, color = "black")
    # Разместить графики на странице
    ggarrange(density.p, stable.p, text.p, 
              ncol = 1, nrow = 3,
              heights = c(1, 0.5, 0.3))


    Добавляем графический элемент в ggplot


    Для добавления таблиц, графиков или других элементов на основе таблиц в рабочую область ggplot есть функция annotation_custom()ggplot2]. Упрощенный формат:
    annotation_custom(grob, xmin, xmax, ymin, ymax)

    • grob: внешний графический элемент для отображения
    • xmin, xmax: x-расположение в координатах (горизонтальное расположение)
    • ymin, ymax: y-расположение в координатах (вертикальное расположение)

    Помещаем таблицу в ggplot


    Используем графики density.p и stable.p, созданные в предыдущем разделе.
    density.p + annotation_custom(ggplotGrob(stable.p),
                                  xmin = 5.5, ymin = 0.7,
                                  xmax = 8)


    Помещаем диаграмму рассеивания в ggplot


    1. Создаем диаграмму разброса для y = “Sepal.Width” по x = “Sepal.Length” из набора данных iris. Функция R: ggscatter() [ggpubr].
    2. Создаем отдельно диаграмму рассеивания переменных х и у с прозрачным фоном. Функция R: ggboxplot() [ggpubr].
    3. Преобразуем диаграмму рассеивания в графический объект под названием “grob” в терминологии Grid. Функция R: ggplotGrob() [ggplot2].
    4. Поместим grob-ы диаграммы рассеивания внутрь диаграммы разброса. Функция R: annotation_custom() [ggplot2].

    Поскольку помещенная внутрь диаграмма рассеивания накладывается в нескольких точках, для нее используется прозрачный фон.
    # Диаграмма разброса по группам ("Species")
    #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    sp <- ggscatter(iris, x = "Sepal.Length", y = "Sepal.Width",
                    color = "Species", palette = "jco",
                    size = 3, alpha = 0.6)
    # Диаграммы рассеивания переменных x/y
    #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    # Диаграмма рассеивания переменной x
    xbp <- ggboxplot(iris$Sepal.Length, width = 0.3, fill = "lightgray") +
      rotate() +
      theme_transparent()
    # Диаграмма рассеивания переменной у
    ybp <- ggboxplot(iris$Sepal.Width, width = 0.3, fill = "lightgray") +
      theme_transparent()
    # Создать внешние графические объекты
    # под названием “grob” в терминологии Grid
    xbp_grob <- ggplotGrob(xbp)
    ybp_grob <- ggplotGrob(ybp)
    # Поместить диаграммы рассеивания в диаграмму разброса
    #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    xmin <- min(iris$Sepal.Length); xmax <- max(iris$Sepal.Length)
    ymin <- min(iris$Sepal.Width); ymax <- max(iris$Sepal.Width)
    yoffset <- (1/15)*ymax; xoffset <- (1/15)*xmax
    # Вставить xbp_grob внутрь диаграммы разброса
    sp + annotation_custom(grob = xbp_grob, xmin = xmin, xmax = xmax, 
                           ymin = ymin-yoffset, ymax = ymin+yoffset) +
      # Вставить ybp_grob внутрь диаграммы разброса
      annotation_custom(grob = ybp_grob,
                           xmin = xmin-xoffset, xmax = xmin+xoffset, 
                           ymin = ymin, ymax = ymax)



    Добавляем фоновое изображение в ggplot2-графики


    Импортировать фоновое изображение. Используйте или функцию readJPEG() [в пакете jpeg], или функцию readPNG() [в пакете png] в зависимости от формата фоновой картинки.

    Чтобы протестировать пример ниже, убедитесь, что пакет png установлен. Можно его установить, используя команду install.packages(“png”).
    # Импорт картинки
    img.file <- system.file(file.path("images", "background-image.png"),
                            package = "ggpubr")
    img <- png::readPNG(img.file)

    Скомбинировать ggplot с фоновой картинкой. Функция R: background_image()ggpubr].
    library(ggplot2)
    library(ggpubr)
    ggplot(iris, aes(Species, Sepal.Length))+
      background_image(img)+
      geom_boxplot(aes(fill = Species), color = "white")+
      fill_palette("jco")


    Изменим прозрачность заливки в диаграммах разброса заданием аргумента alpha. Значение должно быть в промежутке [0, 1], где 0 — полная прозрачность, а 1 — отсутствие прозрачности.
    library(ggplot2)
    library(ggpubr)
    ggplot(iris, aes(Species, Sepal.Length))+
      background_image(img)+
      geom_boxplot(aes(fill = Species), color = "white", alpha = 0.5)+
      fill_palette("jco")


    Еще один пример, накладывание карты Франции на ggplot2:
    mypngfile <- download.file("https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/France_Flag_Map.svg/612px-France_Flag_Map.svg.png", 
                               destfile = "france.png", mode = 'wb') 
    img <- png::readPNG('france.png') 
    ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
      background_image(img)+
      geom_point(aes(color = Species), alpha = 0.6, size = 5)+
      color_palette("jco")+
      theme(legend.position = "top")



    Располагаем графики на нескольких страницах


    Если у вас длинный список ggplot-ов, скажем, n=20, возможно, вы захотите упорядочить их, разместив на нескольких страницах. Если на странице будет 4 графика, для 20 понадобится 5 страниц.

    Функция ggarrange()ggpubr] предоставляет удобное решение, чтобы расположить несколько ggplot-ов на нескольких страницах. После задания аргументов nrow и ncol функция ggarrange() автоматически рассчитывает количество страниц, которое потребуется, чтобы разместить все графики. Она возвращает список упорядоченных ggplot-ов.

    Например, код ниже
    multi.page <- ggarrange(bxp, dp, bp, sp,
                            nrow = 1, ncol = 2)

    возвращает две страницы с двумя графиками на каждой. Можно вывести каждую страницу вот так:
    multi.page[[1]] # Вывести страницу 1
    multi.page[[2]] # Вывести страницу 2

    Упорядоченные графики можно экспортировать в pdf-файл с помощью функции ggexport()ggpubr]:
    ggexport(multi.page, filename = "multi.page.ggplot2.pdf")

    PDF-файл

    Многостраничный вывод можно получить и с функцией marrangeGrob()gridExtra].
    library(gridExtra)
    res <- marrangeGrob(list(bxp, dp, bp, sp), nrow = 1, ncol = 2)
    # Экспорт в pdf-файл
    ggexport(res, filename = "multi.page.ggplot2.pdf")
    # Интерактивный вывод
    res

    Вложенное взаиморасположение с ggarrange()


    Упорядочим графики, созданные в предыдущих разделах.
    p1 <- ggarrange(sp, bp + font("x.text", size = 9),
                    ncol = 1, nrow = 2)
    p2 <- ggarrange(density.p, stable.p, text.p, 
                    ncol = 1, nrow = 3,
                    heights = c(1, 0.5, 0.3))
    ggarrange(p1, p2, ncol = 2, nrow = 1)


    Экспорт графиков


    Функция R: ggexport()ggpubr].

    Сначала создадим список из 4 ggplot-ов, соответствующих переменным Sepal.Length, Sepal.Width, Petal.Length и Petal.Width в наборе данных iris.
    plots <- ggboxplot(iris, x = "Species",
                       y = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width"),
                       color = "Species", palette = "jco"
                       )
    plots[[1]]  # Вывести первый график
    plots[[2]]  # Вывести второй график и т.д.

    После можно экспортировать отдельные графики в файл (pdf, eps или png) (один график на странице). Можно упорядочить графики (2 на страницу) при экспорте.

    Экспорт отдельных графиков в pdf (по одному на странице):
    ggexport(plotlist = plots, filename = "test.pdf")

    Упорядочьте и экспортируйте. Задайте nrow и ncol, чтобы вывести несколько графиков на одной странице:
    ggexport(plotlist = plots, filename = "test.pdf",
             nrow = 2, ncol = 1)
    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/339090/


    Метки:  

    Классический 2д квест или как прошли наши два года разработки. Часть 3

    Воскресенье, 01 Октября 2017 г. 18:34 + в цитатник
    MaikShamrock сегодня в 18:34 Разработка

    Классический 2д квест или как прошли наши два года разработки. Часть 3

      Снова привет всем, в этой, третьей, части будет рассказ о том как программировался «Swordbreaker The Game», почему был выбран тот или иной фреймворк, подробности под катом.





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


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

      Как было рассказано ранее проект пережил следующие вехи развития:
      • Был создан прототип на бумаге в виде диздока, были определены платформы, и примерный геймплей
      • Были созданы прототипы на различных игровых движках с целью определить какой из них подойдёт лучше всего для реализации идей
      • Была создана демо-версия для предварительной оценки игры со стороны коммьюнити
      • Игра была реализована для основных платформ (Windows, Android OS)
      • Игра была реализована для вторичных платформ (Linux, Mac, iOS)


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

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

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

      Лирическое отступление завершено, про денежную сторону этого вопроса я думаю поговорим в следующей части, а теперь собственно к коду. =]

      На тот момент когда всё началось (начало 2014) жизнь в интернете происходила под лозунгами а-ля «Флеш умирает, что же делать, а давайте переходить на html5, но он однако какой-то сырой…», также на горизонтах лихо проносились андроиды выпуская всё новые версии системы, охватить хотелось конечно всё и сразу, но для начала были определены две платформы – Android и Windows (в виде Steam Greenlight, если пройдём конечно), соответственно нужен был кроссплатформенный движок. На тот момент были следующие кандидаты на которых я попробовал реализовать базовый уровень – Construct2, Libgdx, Unity3d, ImpactJS, Cocos2d, еще какая-то пара Flash-движков, AndEngine, CoronaSDK. Из всей кучи более-менее адекватными по совместимости, документации и простоте освоения были только два – это LibGDX и CoronaSDK, в Unity3d на тот момент работа с 2д графикой делалась набором костылей (но пару демок я написал и на нём, но по сравнению с теми двумя он не был таким удобным).

      Как вообще выбрать движок для начального прототипирования игры? Заметьте, что для прототипа, именно прототипа, когда нужно составить виденье будущей игры, подходит фактический любой движок, который вы сумеете освоить хотя бы за пару недель и начать уже-то что-то на нём писать. LibGDX в этом плане имел хорошую wiki-документацию, отличный форум, с живым коммьюнити, а его авторов Mario Zechner написал даже две книги по программированию игр, с использованием LibGDX, (ищите на packtpub) всё это довольно сильно помогало на начальном этапе, ведь опыта программирования игр у меня не было. Примерно такая же ситуация с CoronaSDK – простая и доступная документация, сравнительно богатый для 2d набор API, язык программирования Lua – простой в освоении, но с большим количеством нюансов.


      Первая демка, с тестовыми кнопками, собранная на CoronaSDK, шрифт тестовый, кнопки тоже

      Сначала я вообще думал писать на CoronaSDK, но постоянная работа со сборщиком на стороне сервера, компиляция, потом загрузка на Android (всё это не быстро), да и довольно “жидкая” структура языка lua, чем то напоминающее javascript, заставили продолжить освоение движка LibGDX.

      Собственно LibGDX был хорош всем. Во первых там был хороший туториал который позволял понять основной игровой цикл, были демки (около 100 шт.) с различными функциями API, которые можно было запустить и посмотреть, всё это дело собиралось на Windows и делало это очень быстро, плюс при компиляции на Android проект выглядел 100% также (у Corona бывали глюки, на эмуляторе так, а на телефоне по-другому). К тому же это была Java – мейстримовый ООП язык программирования, надо сказать очень удобный для реализации проектов любого уровня, и практически такой же как C#, на котором я писал (и пишу) на работе. К тому же это было всё управлением Android Studio (создатели Resharper и кучи других инструментов), которая была также крайне удобной IDE для разработки.

      Собственно какие выводы я из всего этого сделал – если вы только начинающий в разработке игр, берите тот движок, который наиболее приемлем для вас с точки зрения простоты освоения, да, я понимаю, что выбрав бы допустим Unity3d я бы освоил более мощный инструмент, но в целом для игры, как для программного продукта, важно – сама геймплейная составляющая, на чём она будет реализована – дело вторичное, все движки примерно одинаково работают с 2д и 3д, поэтому выбирайте то, что подходит именно вам (может быть по опыту ЯП, может по удобству), единственный критерий на данном этапе – скорость разработки прототипа.


      Найди AdMob на фотографии

      Описывать сам процесс программирования для игры наверно нет смысла, для LibGDX всё доступно расписано в Wiki, хотя последнее время проект немного подзатих, но думаю для начинающих я бы порекомендовал его для “пробы пера”.

      Итак на LibGDX мы доехали до выпуска демо-версии, здесь сыграло важную роль то, что у движка было:
      • Лёгкая интеграция с рекламным API, можно было легко и без проблем встроить рекламные модули AdMob в проект с использованием готовых API.
      • Это была фактически родная для Android – Java
      • Собственно запуск демо прошёл фактически без всяких технических проблем, были пару вопросов почему-то криво формировалось отображение, но в целом – всё нормально.
      • Далее начался марафон до релиза основной версии игры.
      • На всём этом долгом пути очень полезными были следующие вещи:
      • Android Studio – которая ну очень удобная штука со всеми фишками рефакторинга, подсветки синтаксиса, быстрых переходов, отладки и.т.д. JetBrains – one love! Всё это позволяло удобно управлять кодом и быстро находить ошибки экономя время и нервы.
      • Структура проектов в LibGDX – так получилось, что данный фреймворк отлично спроектирован под создание 2д игр, почти под всё API были либо Demo либо статьи в Wiki, так что проблем технического характера было мало, также порадовала локализация, которая тоже имела свой API. На финальном этапе интеграции в Steam, также оказалось, что есть свободные компоненты для реализации Steam API в игре, аналогично было и с google play services. В целом LibGDX был этаким фреймворком наподобие стандартной библиотеки .NET с кучей готовых классов из которых ты можешь лепить всё, что тебе требуется, там есть практически всё (в рамках OpenGL ES, наверно =])
      • Моральная Санина поддержка, особенно когда мы переписывали диалоги и тестировали всё это дела на баги. =]


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

      Постепенно правя последние баги мы подошли к релизу в Steam, запуск Android версии решено было отложить, поскольку там было пара технических моментов и мы всё никак не могли решить делать ли её бесплатной с рекламой или paid-app. Подготовка к запуску в Steam заняла тоже своём время, нужно было заполнит много информации – личные данные, ачивки, подготовить материалы (как оказалось нужны картинки на ачивки, значки, смайлики для чата, обои для профилей пользователей), а также протестировать работу API в игре. Steam вообще в этом плане очень удобная платформа, море документации, которая доступна после регистрации (и которая не подлежит разглашению o_O), делаю процесс довольно простым, к тому же, как я уже говорил, были готовые библиотеки для LibGDX. Вобщем пару раз нас отклоняла модерация из-за несоответствия материалов, но таки мы прошли и стартовали! :D

      Тут процесс перетёк из состояния «разработка» в состояние «поддержка», и посыпались первые долгожданные «баги» — у некоторых неправильно отрабатывал FitViewport, у кого-то терялись сохранения, кому-то не давались ачивки, вобщем у кого что болит. Часть багов была оперативно поправлена, а часть… увы, но с некоторыми я так и не смог разобраться до сих пор.


      Типичная отладка

      Впрочем у большинства игроков игра шла стабильно, появилась первая статистика, скриншоты, отзывы, и какое-никакое моральное удовлетворение от проделанной работы. Ура мы стали разработчиками! :D

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


      Карта замка, прототип, в начале мы хотели соединить всё это стрелочками и при прохождении чтобы отдельные области замка становились светлыми


      Карта всей игры

      Вобщем это были напряженные деньки предфинальной полировки почти готового продукта. Спустя некоторое время была создана версия для Android, и запущена в виде paid-app, трансформация благодаря функционалу LibGDX произошла довольно безболезненно, все функции для if-else условий настройки интерфейса там были, поэтому часть управления была подстроена под пальцевый тач и всё закрутилось-завертелось.

      Оставалось еще одно незаконченное дело – это версия для Mac, а именно для iOS, вообще Android для paid-приложения показал себя довольно хорошо, была надежда, что iOS версия будет как минимум не хуже.

      Сейчас уже не помню в чём конкретно была причина, по которой я решил версию для iOS писать не на LibGDX, а на CoronaSDK, но вроде это были какие-то нюансы в совместимости. Был также куплен Mac, для того, чтобы пройти регистрацию на портале и для тестирования на виртуалке.
      Достаточно быстро, хотя и не без проблем с Lua игра была переписана и запускалась на эмуляторе вполне хорошо, сказались некоторые отличия в компонентах, что-то пришлось оптимизировать, что-то переделать, но в целом движок с возложенной на него задачей справился, плюс CoronaSDK обладает хорошим и шустрым эмулятором. Место Android Studio заменил Sublime Text под который у CoronaSDK существует плагин, получилось что-то наподобие IDE, в котором было тоже довольно удобно. В целом у CoronaSDK плюсом и минусом является язык – это скриптовый Lua с динамической типизацией, к которой не сразу привыкаешь и иногда путаешься в областях видимости, определении переменных, и.т.д., постепенно к этому привыкаешь, всё становится таблицей в которую можно затолкать всё – начиная от данных и заканчивая функциями, в целом тут есть своя логика, но отлаживать всю эту чертовщину – не очень то круто. Коронка тоже имеет богатый API в котором есть много всего для создания 2д игр, очень проста в освоении, содержит много примеров в самой документации, плюс к этому есть плагины для встраивания монетизации, и прочих сервисов (некоторые платные). Для создания простеньких игрушек под телефоны – самое то, для чего-то более крупного, я бы наверно запутался в всём этом балагане, при отсутствии нормальной IDE.


      Три тысячи чертей (на самом деле триста мечеломов) и одна маленькая сценка!

      Запуск iOS версии прошёл стабильно, написав и протестировав всё под Windows на эмуляторе, осталось заполнить регистрационную информацию, сделать билд и запустить сборку на iOS. Profit! :D

      На этом в принципе всё, основные моменты вроде рассказал, в следующей части Саня расскажет про поддержку игры после релиза, какие пиар-акции проводились, и что это всё итоге дало, плюс расскажем про монетизацию.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/339092/


      Метки:  

      Liquibase: пример автоматизированного наката изменений на реляционную БД

      Воскресенье, 01 Октября 2017 г. 17:32 + в цитатник
      akk0rd87 сегодня в 17:32 Разработка

      Liquibase: пример автоматизированного наката изменений на реляционную БД

        Вместо предисловия


        Статья будет интересна тем, кто хоть раз задумывался о вопросе наката изменений (патча) на реляционную БД. Статья не будет интересна тем, кто уже освоил и использует Liquibase. Главной целью данной статьи является указание ссылки на репозиторий с примером использования. В качестве примера я выбрал накат sample-схемы HR на БД Oracle (список всех поддерживаемых БД) — любой желающий может скачать себе репозиторий и поиграться в домашних условиях. Желание продемонстрировать пример вызвано обсуждением этого вопроса на ресурсе sql.ru.


        Что такое Liquibase


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


        Почему я использую Liquibase


        Мой выбор остановился на этом инструменте, так как:
        1) Инструмент отслеживает, какие changeset-ы уже были применены к данному экземпляру БД и накатывает только те, которые еще не накатывались и какие нужно еще донакатить. Если в процессе наката применение какого-либо изменения упало с ошибкой, то, после устранения причины вы перезапускаете накат и Liquibase продолжает выполнение с того changeset-а, на котором остановился.
        2) Возможность выставить changeset-у атрибуты runOnChange и runAlways существенно упрощает управление изменениями, в частности, recreatable-объектов.
        3) Свойство context позволяет выполнять/не выполнять changeset-ы в зависимости от текущего окружения (например, не запускать юнит-тесты на проде).


        Это был не полный список фич.


        Репозиторий


        Он здесь. В нем приведены "hard" (таблицы, индексы, ограничения целостности) и "soft" (триггеры, процедуры, представления) объекты, changeset-ы с тегами sql и sqlFile, c атрибутами runOnChange и runAlways и без.


        Чего нет в репозитории


        Ввиду отсутствия необходимости в репозитории нет таких полезных фич/шагов, которые я обычно использую в своих проектах:


        • Preconditions — позволяют задавать условие выполнения changeset-a;
        • Компилирование объектов схемы в конце наката. В Oracle это dbms_utility.compile_schema(user, false);
        • Запуск юнит-тестов.
        Original source: habrahabr.ru (comments, light).

        https://habrahabr.ru/post/339084/


        Метки:  

        Swift Generics: cтили для UIView и не только #2

        Воскресенье, 01 Октября 2017 г. 17:15 + в цитатник
        iWheelBuy сегодня в 17:15 Разработка

        Swift Generics: cтили для UIView и не только #2

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


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


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


          typealias Decoration = (T) -> Void

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


          let decoration: Decoration = { (view: UIView) -> Void in
              view.backgroundColor = .white
          }
          let view = UIView()
          decoration(view)

          Композиция декораций


          Используя оператор сложения и соблюдая порядок применения декораций можно получить механизм композиции декораций:


          func +(lhs: @escaping Decoration, rhs: @escaping Decoration) -> Decoration {
              return { (value: T) -> Void in
                  lhs(value)
                  rhs(value)
              }
          }

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


          Decoration + Decoration = Decoration
          Decoration + Decoration = Decoration
          Decoration + Decoration = нельзя

          Создание декораций


          Главным неудобством при создании декорации было написание кода самой конструкции декорации. Приходилось писать тип декорации, замыкание, тип класса внутри замыкания… Чаще всего это заканчивалось CTRL+C, CTRL+V.


          Чтобы выйти из ситуации и генерировать замыкание через автокомплит была написана универсальная функция, которая принимала тип объекта:


          func decor(_ type: T.Type, closure: @escaping Decoration) -> Decoration {
              return closure
          }

          Использовалось это следующим образом:


          let decoration = decor(UIView.self) { (view) in
              view.backgroundColor = .white
          }

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


          error: variable used within its own initial value
          let decoration = decoration(UIView.self) { (view) in

          Более удачным решением стало создание универсальной static функции:


          protocol Decorable: class {}
          
          extension NSObject: Decorable {}
          
          extension Decorable {
          
              static func decoration(closure: @escaping Decoration) -> Decoration {
                  return closure
              }
          }

          Создавать декорирующее замыкание в итоге можно следующим образом:


          let decoration = UIView.decoration { (view) in
              view.backgroundColor = .white
          }

          Состояние


          class MyView: UIView {
          
              var isDisabled: Bool = false
              var isFavorite: Bool = false
              var isSelected: Bool = false
          }

          Чаще всего сочетание подобных переменные применяется лишь для того, чтобы изменить стиль конкретного UIView.


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


          struct MyViewState: OptionSet, Hashable {
          
              let rawValue: Int
          
              init(rawValue: Int) {
                  self.rawValue = rawValue
              }
          
              static let normal = TextPlaceholderState(rawValue: 1 << 0)
          
              static let disabled = TextPlaceholderState(rawValue: 1 << 1)
              static let favorite = TextPlaceholderState(rawValue: 1 << 2)
              static let selected = TextPlaceholderState(rawValue: 1 << 3)
          
              var hashValue: Int {
                  return rawValue
              }
          }

          Применять можно следующим образом:


          class MyView: UIView {
          
              var state: MyViewState = .normal
          }
          
          let view = MyView()
          view.state = [.disabled, .favorite]
          view.state = .selected

          В прошлой публикации была введена обобщенная структура, которая имеет указатель на экземпляр класса, к которому будут применяться декорации.


          struct Style {
          
              let object: T
          }

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


          extension Style where T: Decorable {
          
              var state: AnyHashable? {
                  get {
                      //
                  }
                  set {
                      //
                  }
              }
          }

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


          class Holder {
          
              var state = Optional.none
          }
          
          var KEY: UInt8 = 0
          
          extension Decorable {
          
              var holder: Holder {
                  get {
                      if let holder = objc_getAssociatedObject(self, &KEY) as? Holder {
                          return holder
                      } else {
                          let holder = Holder()
                          let policy = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
                          objc_setAssociatedObject(self, &KEY, holder, policy)
                          return holder
                      }
                  }
              }
          }

          Теперь обобщенная структура Style может сохранять состояние через ассоциированный с объектом Holder класс.


          extension Style where T: Decorable {
          
              var state: AnyHashable? {
                  get {
                      return object.holder.state
                  }
                  set(value) {
                      object.holder.state = value
                  }
              }
          }

          Хранение декораций


          Если можно хранить состояние стиля, то точно так же можно хранить декорации для разных состояний. Это достигается путем создания словаря декораций [AnyHashable: Decoration], ассоциированного с объектом декорации.


          class Holder {
          
              var state = Optional.none
              var states = [AnyHashable: Decoration]()
          }

          Чтобы добавлять декорации в словарь введем функцию:


          extension Style where T: Decorable {
          
              func prepare(state: AnyHashable, decoration: @escaping Decoration) {
                  object.holder.states[state] = decoration
              }
          }

          Использовать можно следующим образом:


          let view = MyView()
          view.style.prepare(state: MyViewState.disabled) { (view) in
              view.backgroundColor = .gray
          }
          view.style.prepare(state: MyViewState.favorite) { (view) in
              view.backgroundColor = .yellow
          }

          Применение декораций


          После наполнения словаря декораций, при изменении состояния стиля, следует применить соответствующую декорацию из словаря. Этого можно добиться немного изменив реализацию сеттера состояния стиля:


          extension Style where T: Decorable {
          
              var state: AnyHashable? {
                  get {
                      return object.holder.state
                  }
                  set(value) {
                      let holder = object.holder
                      if let key = value, let decoration = holder.states[key] {
                          object.style.apply(decoration)
                      }
                      holder.state = value
                  }
              }
          }

          Применяться декорация будет следующим образом:


          let view = MyView()
          // подготовка декораций
          view.style.state = .selected

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


          extension Style where T: Decorable {
          
              func prepare(state: AnyHashable, decoration: @escaping Decoration) {
                  let holder = object.holder
                  holder.states[state] = decoration
                  if state == holder.state {
                      object.style.apply(decoration)
                  }
              }
          }

          Анимации?


          Если внутри применяемой декорации содержится что-то, что можно анимировать,...


          When positive, the background of the layer will be drawn with
          rounded corners. Also effects the mask generated by the
          'masksToBounds' property. Defaults to zero. Animatable.

          open var cornerRadius: CGFloat

          … то изменения стиля объекта внутри анимационного блока приведет к соответствующим анимациям:


          UIView.animate(withDuration: 0.5) {
              view.style.state = .selected
          }

          Заключение


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


          pod 'Style'
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/339048/


          Метки:  

          160-терабитный трансатлантический кабель Marea закончен

          Воскресенье, 01 Октября 2017 г. 16:55 + в цитатник
          it_man сегодня в 16:55 Разработка

          160-терабитный трансатлантический кабель Marea закончен

            Три компании — Microsoft, Facebook и Telxius — совместно проложили оптоволоконный кабель под названием Marea через Атлантический океан: из американской Вирджинии в испанский Бильбао. Пропускная способность Marea — 160 Тбит/с. Это самый высокопроизводительный трансатлантический кабель на сегодняшний день.

            Длина кабеля равняется 6600 километрам, а средняя глубина пролегания составляет 3,35 километра. Marea проложили меньше чем за два года, тогда как стандартный срок для подобных проектов составляет около пяти лет.


            / Flickr / Ministerio TIC Colombia / CC

            Первый провод, который люди проложили через океан, — трансатлантический телеграфный кабель. Первую попытку предприняли в 1857 году, но кабель порвался.

            5 августа 1858 года был проложен кабель между островами Валентия и Ньюфаундленд, но уже в сентябре он вышел из строя. Долговременную связь между Европой и Америкой обеспечил лишь кабель, проложенный в 1866 году.

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


            / Flickr / Steve Song / CC

            В 2016 году группа компаний, среди которых была Google, закончила прокладывать кабель FASTER из США в Японию. По нему можно передавать до 60 Тбит данных в секунду — на момент запуска он был самым быстрым.

            Формально FASTER остается самым быстрым кабелем и сейчас — использовать Marea начнут только в начале 2018 года. Полностью свой потенциал он раскроет в 2025 году. Ожидается, что к этому времени общемировое потребление трафика вырастет в восемь раз.

            В условиях такого роста новый кабель нужен Microsoft и Facebook, чтобы обеспечивать стабильную работу своих сервисов. Президент Microsoft Брэд Смит (Brad Smith) уже высказался о важности Marea:

            «Marea проложили вовремя. Через трансатлантические кабели проходит на 55% больше данных, чем через кабели Тихого океана. И на 40% больше, чем по кабелям, соединяющим США и Латинскую Америку.

            Безусловно, поток данных через Атлантический океан будет расти, а Marea обеспечит необходимое качество соединения для США, Испании и других стран».

            Еще одна причина, по которой компании инициировали проект, — природные катаклизмы. В 2006 году на острове Тайвань произошло семибалльное землетрясение, из-за чего были повреждены восемь кабелей, соединяющих остров с Китаем. Чтобы их восстановить, понадобилось 11 кораблей и 49 дней. А ураган Сэнди в 2012 году оставил без связи Восточное побережье США. С этого момента в Microsoft решили повысить отказоустойчивость трансатлантических соединений. Получается, что как раз Сэнди объединил Facebook и Microsoft.

            «Мы постоянно встречались с представителями Facebook на различных мероприятиях и поняли, что пытаемся решить одну и ту же проблему. Поэтому мы объединились и улучшили трансатлантическую сеть, спроектировав новый кабель», — рассказал Фрэнк Рей (Frank Ray), руководитель инфраструктурного направления облачных решений.

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

            Сообщество понимает, что подводные кабели не лишены недостатков, поэтому ведет разработку альтернативных методов соединения. Марк Цукерберг предлагает использовать дронов, компания AT&T — электрические сети, а некоторые ученые — квантовую телепортацию. Однако эти проекты находятся на ранних этапах своего развития или существуют пока лишь на бумаге.



            P.S. Наши дайджесты на Хабре:


            P.P.S. О чем еще мы пишем в нашем корпоративном блоге:

            Original source: habrahabr.ru (comments, light).

            https://habrahabr.ru/post/338622/


            Метки:  

            Дайджест интересных материалов для мобильного разработчика #223 (25 сентября — 1 октября)

            Воскресенье, 01 Октября 2017 г. 16:16 + в цитатник
            EverydayTools сегодня в 16:16 Разработка

            Дайджест интересных материалов для мобильного разработчика #223 (25 сентября — 1 октября)

              Подводим итоги очередной недели мобильным дайджестом. В нем мы рассказываем про разработку прибыльной игры школьниками, про iOS-релизы в Badoo, про новые правила App Store и победителей Google Play, иллюзии скорости и движения, анонимность и многое другое.



              Разработка прибыльной Android игры двумя школьниками + Продолжение

              В данной статье хочу рассказать вам об опыте разработки одной простенькой игры под Android, которая многократно окупилась еще на стадии разработки!

              Процесс релиза iOS-приложений в Badoo

              Меня зовут Михаил Булгаков, и я работаю в команде релиз-инженеров Badoo. В этом посте я расскажу о том, как происходят релизы iOS-приложений с момента «У меня есть готовый бинарь» до момента «После нас хоть потоп», и, конечно, как это делаем мы в Badoo.

              Как работает Android, часть 3

              В этой статье я расскажу о компонентах, из которых состоят приложения под Android, и об идеях, которые стоят за этой архитектурой.

              Дайджест доступен и в виде рассылки. Подписаться вы можете тут (у нас уже 2000+ подписчиков!).

              iOS

              (+24) Больше сюрпризов от Apple: обновленные правила размещения на App Store
              (+13) iOS+Kotlin. Что можно сделать сейчас
              (+8) Опционалы в Swift
              Podlodka #26: реактивный стиль программирования
              Stack AR: первая игра на основе ARKit, возглавившая топ App Store
              Полный разбор дизайна iOS 11: Apple все еще внимательны к деталям?
              image Первое React Native приложение: от «Hello World» до App Store
              image Отладка Swift с LLDB
              image Как уйти из колледжа и стать iOS-фрилансером
              image Управление разными средами в Swift-проекте
              image Руководство по ARKit для новичков
              image Чистая Swift архитектура
              image В Xcode 9 цвета можно добавлять в каталог ассетов
              image Измерение времени компиляции в Xcode 9
              image React Native Game Center: интеграция Game Center в React Native
              image ButtonProgressBar: прогресс бар в кнопке
              image Detect.Location: история посещения мест по фотографиям
              image LifetimeTracker: отслеживание ключевых проблем прямо во время разработки

              Android

              (+11) Делаем MitM с помощью openssl на Android
              (+11) Библиотека Reamp: обезболивающее для ваших Android-приложений
              (+11) Реактивные приложения с Model-View-Intent. Часть 2: View и Intent
              (+8) 30 новых ресурсов для android-разработчика (лето 2017)
              (+4) CSV-библиотека Adaptive Table Layout
              Лучшие приложения Google Play за пять лет
              image Android Dev Подкаст. Выпуск 43. Обзор Devfest Siberia 2017
              Победители Google Play Indie Games Festival
              Создание макета в ConstraintLayout: нормативы, барьеры и цепочки
              В Android O изменился файл хранения паролей от от wi-fi сетей
              Как парсить XML на Android
              image RxJava: делаем креш-логи лучше
              image Многопотоковый рендеринг на Android с Litho и Infer
              image Flutter: от дизайна до приложения
              image Использование шрифтов с Support Library 26
              image Android Architecture Components: тестирование ViewModel LiveData
              image Наслаждение тулбаром
              image Воссоздаем “Бутылочку” на Android
              image Используем buildSrc для кастомной логики сборок Gradle
              image Как улучшить быстродействие Android Studio на машине с малым объемом памяти
              image Frames: готовое приложение с обоями
              image Tutorial View: простая организация туториалов
              image Croller: круглый контрол

              Разработка

              (+89) Иллюзия движения
              (+72) Иллюзия скорости
              (+21) Как мы за неделю создали чат-бота и подружили его с веб-приложением
              (+19) Learnopengl. Урок 3.3 — Класс 3D-модели
              (+15) Oblique frustum. Внутри скошенной пирамиды видимости
              (+11) Зачем в 2017 году писать свой движок для мобильных игр?
              (+10) Как Алексей Моисеенков дошел до Prisma и пошел дальше
              (+9) Как довести первый проект до конца. Часть 2. Мифы, ошибки и провалы
              (+8) «Нормальный у нас такой UX. UX? Не до этого нам, у нас тут сроки поджимают!» Снимаем мантию — моя интерпретация
              (+6) Как сделать gif-анимацию для Behance и Dribbble?
              Почему WebAssembly значительно изменит веб
              Как обучаются и растут junior-разработчики в BBC
              Inkdrop: как я создал редактор, зарабатывающий $1300 в месяц
              Jovo: open source фреймворк для разработки кроссплатформенных голосовых приложений
              “А что, так можно было?”: Рейтинг разработчиков будет пересмотрен
              Как tbh превратило анонимность во благо
              Мобильное Облако Mail.Ru внедрило технологии компьютерного зрения
              image Mission-driven интерфейс
              image Мобильная типографика
              image Как получить работу в продуктовом или UX дизайне без портфолио
              image Вопросы и ответы по Code Review
              image Лучший кодинг через тестирование
              image Понимаем Progressive Web App: стоят ли они всей шумихи?
              image Как неинтуитивный пользовательский интерфейс может создать превосходный пользовательский опыт
              image 19 альтернатив Parse в 2017 году

              Аналитика, маркетинг и монетизация

              (+4) Мобильные приложения: что такое предпраздничный сезон-2017 и как заработать на нем максимум?
              Игры жанра match3: как разработать такую игру и сделать её лучше
              Tune проводит увольнения
              Топ рекламных платформ для привлечения установок
              Фрод со сбросом DeviceID: новая угроза в мобильном маркетинге
              image Три стадии мобильного маркетинга
              image Аналитика против атрибуции — Работа с несоответствием установок

              Устройства, IoT, AI

              (+20) Тайм-менеджмент для кинестетиков
              «Путь будущего»: бывший главный инженер Uber создал религию ИИ
              LG представила смартфон с отпугивателем комаров
              Amazon выпускает новые колонки и умные часы
              Какие языки вам стоит изучить для Data Science?
              image Microsoft запускает новые инструменты машинного обучения

              <- Предыдущий дайджест. Если у вас есть другие интересные материалы или вы нашли ошибку — пришлите, пожалуйста, в почту.
              Original source: habrahabr.ru (comments, light).

              https://habrahabr.ru/post/339082/


              [Из песочницы] Как легализовать торговлю игровыми предметами

              Воскресенье, 01 Октября 2017 г. 15:11 + в цитатник
              Hyperevolution сегодня в 15:11 Маркетинг

              Как легализовать торговлю игровыми предметами

              image

              Объем российского рынка онлайн-игр по итогам 2016 года составил 56,7 млрд руб. (по данным Mail.Ru Group), общемировой объем рынка онлайн-игр в 2016 году — $99,6 млрд (по данным Newzoo), но только 6% пользователей умеют зарабатывать на внутриигровых предметах. Игровые пространства предлагают активно покупать амуницию, камуфляж, прогресс персонажа, но легально продать кому-то свою коллекцию ты не можешь, как не можешь и вывести деньги из игры, инвестировав время, скилы и удачу.

              Имея большой опыт в игровой индустрии (Suntechsoft, Esforce, Na’Vi’ и др.), мы придумали, как объединить все игры и платформы и дать геймерам возможность в один клик продавать, обменивать или оценивать виртуальные предметы. Сервис DMarket будет работать на базе блокчейн, а все операции внутри маркетплейса можно будет осуществлять с помощью встроенных токенов и умных контрактов.

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

              От виртуального — к реальному


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

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

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

              Рассмотрим крупнейших игроков на глобальном рынке внутриигровых предметов.

              Steam


              Ежедневное количество активных пользователей: 12 853 536 (по состоянию на 15.07.2017);
              Количество игр: + — 14 398 (по состоянию на 15.07.2017).

              image

              Платформа Steam была выпущена в 2003 году. За последние 4 года она завоевала огромную популярность среди игрового сообщества, прибыль в 2016 — $3,5 млрд. Steam первым обратил внимание на торговлю внутриигровыми предметами. Правда, вся полученная прибыль удерживается внутри платформы, возможности обналичить деньги нет. Более того, возможность торговать игровыми предметами ограничена и представлена всего в нескольких играх (CS:GO, Dota2, TF2, PUBG, H1Z1).

              Преимущества Steam:

              • крупнейший рынок для продажи видеоигр и модов;
              • темп продаж увеличивается, и каталог игр стремительно расширяется. На сегодняшний день активировано почти 370 млн платных игр;
              • Steam предлагает дополнительные сервисы для игроков, моддеров и разработчиков игр: торговля предметами, бета/альфа релизы, предварительная продажа игр, реестр предметов, обработка платежей и проверка кредитных карт.

              Недостатки Steam:

              • технологии и серверы Steam централизованы и имеют полный контроль над доступностью каждого отдельного товара или элемента в своем каталоге. Это приводит к мошенничеству в сфере безопасности (учетной записи);
              • большинство игр в Steam не поддерживают внутриигровую торговлю. Только три игры оказывают значительное влияние на общий оборот внутриигровых предметов (CS:GO, Dota2, TF2);
              • пользователи Steam не имеют реального влияния на развитие платформы;
              • Steam не позволяет своим пользователям обналичивать виртуальную валюту платформы;
              • Steam взимает высокую комиссию за торговлю игровыми предметами (до 12%);
              • Steam не поддерживает мультиплатформенную торговлю и торговлю на разных игровых движках.

              Opskins


              Opskins – это площадка для торговли внутриигровыми предметами, которая была запущена в 2014 году. Opskins сохраняет безопасность как покупателей, так и продавцов, выступая в качестве сторонней платформы. Деньги можно вывести на счет PayPal, банковский счет, биткойн-кошелек, карту предоплаты Visa и т.д.

              image

              Сервис применяет API Steam, позволяющий торговать внутриигровыми предметами. На данный момент Opskins является крупнейшей в мире платформой для торговли скинами с 10 млн уникальных пользователей в месяц.

              Преимущества Opskins:

              • оборот за І и ІІ кварталы 2017 года составил около $250 млн;
              • одно из самых крупных сообществ по продаже скинов (более 10 млн пользователей каждый месяц);
              • обналичивание реальных денег.

              Недостатки Opskins:

              • не имеет официальной поддержки Steam (большое количество случаев мошенничества с аккаунтами);
              • высокая комиссия за транзакции и вывод средств (до 10%);
              • поддерживает всего несколько игр Steam;
              • в большой степени зависит от Steam из-за использования API-интерфейса этого сервиса.

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

              В начале статьи мы говорили о 2,15 млрд геймеров. Именно такое количество людей во всем мире играет в игры на разных платформах. Если бы все они получили возможность торговать своими виртуальными раритетами, ежегодный оборот игрового контента в денежном выражении превысил бы $450 млрд.

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

              Второй важный момент — интерес разработчиков и издателей игр. Мы не уводим пользователей от покупки непосредственно внутри игр в сторону покупок у частных лиц, мы создаем сообщество, в котором продажа и обмен предметов будет только частью процесса. Благодаря блокчейн, будет сохраняться история каждого виртуального артефакта, можно будет проследить, кто владел раньше условным автоматом, кто был первым покупателем, где он использовался. Это создает историю каждой вещи, и из безликого инструмента для достижения игровых целей она превращается в предмет коллекционирования. Пользователи будут продавать одни предметы и покупать другие, а потом возвращаться в игру, чтобы использовать полученные возможности. Таким образом увеличится время, которое игрок проводит внутри игры, жизнь каждого предмета и жизненный цикл самой игры.
              Original source: habrahabr.ru (comments, light).

              https://habrahabr.ru/post/339074/


              Метки:  

              [Из песочницы] Информационная безопасность в АСУ ТП: вектор атаки преобразователи интерфейсов

              Воскресенье, 01 Октября 2017 г. 13:29 + в цитатник
              2younda сегодня в 13:29 Разное

              Информационная безопасность в АСУ ТП: вектор атаки преобразователи интерфейсов

              Размышление


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

              Выступал там один парень с докладом об ИБ в АСУ ТП, рассказывал о том, что на протяжении длительного времени эволюция систем автоматизации шла параллельным курсом с развитием ИТ систем и что с течением времени для управления системами автоматизации стали применяться системы, построенные на современных ИТ технологиях. И что в настоящее время сопряжение технологической сети с корпоративной сетью необходимо для управления производством так и для администрирования всей системы целиком. Вектор атак с систем ИТ перешел на производственные и промышленные сети. Рассказывал о заводе где-то в германии на который удалось произвести атаку и сорвать технологический процесс в результате чего литейное производство встало и понесло огромные убытки, а литейный цех пришлось разбирать и еще много страшных историй. Ну в общем доклад не произвел нужного(пугающего) впечатления. Присутствующие на данном мероприятии начали задавать вопросы и обсуждать," мол кому мы нужны маленькие компании с минимальными оборотами денег, что мол есть ли реальные подтверждения инцидентов с атаками на предприятия у нас в России".

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

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

              Немного теории


              Рассмотрим нетипичное направление атаки на преобразователи интерфейсов, т.к. много информации об атаках на ПК, ПЛК и системы в целом, но мало кто задумывался что почти на каждом производстве имеются все различные системы устройств, датчиков, приборов и есть проблема совместимости различных видов устройств. Существует огромное разнообразие устройств различного вида и назначения, большое количество компаний, занимающихся их производством, разные стандарты, большей частью не совместимые между собой. Выходом из этой ситуации служат преобразователи интерфейсов. Все они применяются для подключения устройств с интерфейсом RS-232/422/485, а это системы сбора данных, регистраторов, контроллеров, датчиков, терминалов и многого другого. Но мало просто соединить порты необходимо установить связь на программном уровне, что является более сложной задачей. Разные стандарты устройств способны передавать данные по различным технологиям. Унифицировать протоколы привести передаваемые данные к единому виду с помощью преобразователей не получиться, но адаптировать вид данных передаваемых между различными частями системы различными протоколами, чтобы они были успешно приняты и расшифрованы элементом, использующий другой протокол возможно.

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

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

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

              К практике


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

              Есть в сети такой очень интересный ресурс www.shodan.io позволяет найти устройства в глобальной сети интернет, к которым есть доступ и увидеть где они находятся. Выполним простое сканирование с вводом в поисковой строке Moxa Nport. Смотрим и удивляемся тому, что происходит в мире. Стоит учитывать, что некоторые организации направляют письменный запрет на сканирование их подсети в компанию www.shodan.io.

              В общем случаи мы нашли 7260 устройств по запросу Moxa Nport во всем мире. Дальше изучаем как можно попасть на устройства к которым открыт доступ. Замечаем, что в большинстве своем 22, 23, 80, 443 порты открыты и через них можно спокойно подключиться к устройствам.



              Тайвань



              Польша



              Липецк



              Китай



              Казань



              Тайвань



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



              Попадаем на устройство и смотрим все что нужно, убеждаемся, что есть права администратора.





              Можем как просмотреть всю информацию, так и изменить ее.



              Некоторые пользователь устанавливают нестандартные пароль и закрывают 80 порт, но www.shodan.io все равно есть возможность просмотра полезной информации и по другим портам которая может быть использована для развития вектора атаки. Название устройство, его статус, мак адреса, SSL certificate его версию и многое другое.



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



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



              Но это только начало, используем стандартные пары ( а таких устройств нашлось не мало и в России, Чехии, Польше, Италии, Австралии, и во многих других странах).



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









              И смена паролей и перезагрузка, в общем неограниченные права администратора.

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

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

              Проходим на ics-cert.us-cert.gov и в поисковике просто указываем интересующее нас оборудование и имеем официальный список признанных уязвимостей от компании MOXA и этот список разнообразный.



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

              Появление проблем является следствием недостаточной защиты систем АСУ ТП в общем. К примеру преобразователи поставляются без установленного пароля или со стандартным заводским паролем и учетной записью (CVE-2016-2286). Как и многие устройства для систем АСУ, что позволяет любому пользователю получить доступ правами администратора к устройству через TCP/80(HTTP) и TCP/23(Telnet). В настоящее время как утверждает производитель нет информации о случаях активной эксплуатации ошибок. И производитель рекомендовал в свое время просто отключать порты TCP/80(HTTP) и TCP/23(Telnet).



              CVE-2016-0875 Позволяет удаленному пользователю повысить свои привилегии и получить доступ к конфигурационным и лог файлам с помощью специально сформулированного URL.

              CVE-2016-087 удаленный атакующий может вызвать отказ в обслуживании путем отправки специально сформулированного запроса.

              CVE-2016-0877 связана с PING функцией, а ее эксплуатация может привести к утечкам данных.

              CVE-20164500 затрагивает встраиваемые компьютеры uc7408 lx plus позволяет удаленному авторизированному пользователю перепрашивать устройства и вызывать отказ в работе.

              CVE-016-8717 эксперты обнаружили в устройствах MOXA жестко закодированные учетные данные которые позволяют войти в недокументированный аккаунт.



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

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

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

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

              Выводы


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

              — Нижний уровень- уровень датчиков и исполнительных механизмов
              — Средний уровень- уровень промышленных контроллеров
              — Верхний уровень- уровень промышленных серверов и сетевого оборудования
              — Операторский уровень –уровень оператора и диспетчерских станций

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


              Процент атакованных промышленных компьютеров по месяцам в России за 2016 год

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

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

              P.S. В результате данного исследования ни одно устройство не пострадало. И данный эксперимент преследовал исследовательские цели (показать реальность атак и их простоту).
              Original source: habrahabr.ru (comments, light).

              https://habrahabr.ru/post/339066/


              Метки:  

              Предсказание про Стива Джобса от 31 сентября 1994 года

              Воскресенье, 01 Октября 2017 г. 12:20 + в цитатник
              MagisterLudi сегодня в 12:20 Разное

              Предсказание про Стива Джобса от 31 сентября 1994 года

                Фантазия, искусство, сарказм и троллинг — это прикрытие, благодаря которому будущее нашептывает свои мысли настоящему. Вот почему:

                1. Морган Робертсон в 1898 году публикует роман «Тщетность, или Гибель Титана». В книге описывается крушение огромного лайнера под названием «Титан», считавшемся непотопляемым, однако затонувшим в Атлантическом океане после столкновения с айсбергом. Через 14 лет, в 1912, затонул "Титаник", при тех же обстоятельствах. Даже месяц совпал и количество шлюпок.

                2. В 1996 году Чак Паланик публикует «Бойцовский клуб», где описывает «очень крупную компанию», производящую некачественные автомобили, убивающие людей, и по экономическим причинам не отзывает брак, а просто откупается от пострадавших. В 2000 году это происходит наяву, с компрометацией NASA и вмешательством ЦРУ. Детали расследования тут — Toyota: 81 514 нарушений в коде.

                3. «Шутка» Гая Кавасаки про Стива Джобса, опубликованная в 1994 году (за 9 лет до этого, в 1985, Стива выперли из Apple), в точности сбылась в 1996 году. Подробнее под катом.

                image


                Стив Джобс возвращается в «Apple» в должности CEO.


                Проницательность отца Macintosh вновь на службе делу. Шпиндлер в должности COO.

                Данный пресс-релиз появился в ленте новостей в понедельник 31 сентября 1994 г. в 8:32 утра.
                ДЛЯ НЕМЕДЛЕННОЙ ПУБЛИКАЦИИ. Контакты: Regis Makaha, «Apple Computer, Inc.»; (408) 996-1010, AppleLink: uni.wish.

                КУПЕРТИНО, Калифорния — 31 сентября, 1994 г. — Совет директоров «Apple Computer, Inc.» сегодня объявил о том, что Стив Джобс, сооснователь «Apple», которого выкинул Джон Скaлли в 1986 г., вернется в компанию в должности главного исполнительного и технического директора.

                Стив Джобс в настоящее время является президентом «NeXT, Inc.». В своей новой должности он будет нести ответственность за общее руководство и стратегию «Apple Computer». Майкл Шпиндлер оставит пост главного исполнительного директора и будет назначен руководителем административной службы компании.

                С Джобсом, сооснователем «Apple» и отцом Macintosh, в компанию вернется то дальновидное лидерство, позволившее ей создать три из четырех стандартов ПК (Apple II, Macintosh и Windows). Кроме того, благодаря его опыту в «NeXT», компания сможет вернуть себе забытое чувство скромности.

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


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

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

                Учитывая широко известную щедрость Стива Джобса к сотрудникам былого отдела Macintosh, представитель «Odwalla Juices» сказал: «Пора нашей высочайшей доходности пришлась на то время, когда Стив управлял отделом Macintosh. Мы предвидим потрясающие возможности вновь войти в корпоративный сегмент рынка свежевыжатого сока». Схожим образом отреагировал представитель самоуправляемой «United Airlines»: «Когда мы узнали эту новость, мы связались с Боингом и удвоили наш заказ на места первого класса».

                Билл Гейтс, исполнительный директор «Microsoft», со своей стороны поддержал возвращение Стива: «Macintosh всегда был и будет играть большую роль в нашем бизнесе программного обеспечения, а «Apple» — в исследованиях и разработке продуктов Microsoft. Мы верим, что Стив оживит работу «Apple» в области системного программного обеспечения и обеспечит нас пиратскими целями на ближайшие 10-20 лет. Ожидаем увидеть примеры мудрости Стива в наших новых версиях Windows, под кодовым названием Cleveland.

                Совет директоров «Apple» также объявил о покупке активов «NeXT» на сумму 200 млн. долл. США. Основная цель — обернуть объектно-ориентированную операционную систему NeXTSTEP различными версиями операционных системы Macintosh. Джон Уорнок, исполнительный директор «Adobe Systems», дал следующий комментарий: «С возвращением Стива, мы предвидим тесные отношения с Apple, подразумевая невероятно щедрые выплаты роялти за использование Display PostScript».

                Помимо исходного кода NeXTSTEP другим соображением компании «Apple» о покупке «NeXT» была возможность получить от «Canon» 4,000,000 картриджей для лазерных принтеров, и появлением Росса Перо в 30-минутном рекламном ролике про PowerBook с анимированными флипчартами QuickTime.

                Расположенная в Купертино (Калифорния) компания «Apple Computer, Inc.» (NASDAQ-тикер: AAPL) занимается разработкой, производством и сбытом слишком многих моделей ПК, ряда персональных интерактивных ЭВМ и пакетов ClarisWorks. Аппаратное обеспечение Apple продается для использования в настольных издательских системах, в настольных издательских системах и в настольных издательских системах. Признанный пионер в области инноваций (в отличие от имитаторов) в сфере персональных компьютеров, «Apple» продает свою продукцию в более чем 120 странах мира, при этом оказывая техническую поддержку менее чем в 10 из них.

                P.S.


                «Многие люди просили меня опубликовать ту вымышленную заметку, которую я написал в 1994 г., о том, что «Apple» покупает «NeXT», а Стив возвращается в «Apple». Возрождению интереса способствовал тот факт, что она упоминается в биографии Стива Джобса. Эта заметка появилась в ноябрьском номере интернет-журнала «Macworld» 1994 года. По правде говоря, я и забыл, что написал ее. Что ж, надеюсь, вам понравится!»
                — Гай Кавасаки


                Источник — holykaw.alltop.com/1994-spoof-apple-press-release-about-steves-r
                Original source: habrahabr.ru (comments, light).

                https://habrahabr.ru/post/339032/


                Метки:  

                МУК становится дистрибутором Oracle

                Воскресенье, 01 Октября 2017 г. 12:09 + в цитатник
                Muk сегодня в 12:09 Разное

                МУК становится дистрибутором Oracle

                  Группа компаний МУК и корпорация Oracle, ведущий мировой производитель программного обеспечения и серверных решений, объявили о подписании дистрибуторского контракта на территории Азербайджана, Армении, Грузии, Кыргызстана, Таджикистана, Туркменистана и Узбекистана.
                  image


                  Являясь VAD-дистрибутором, группа компаний МУК активно развивает партнерский канал в регионах, предоставляя партнерам дополнительные возможности для улучшения и расширения своего бизнеса с Oracle. Это и совместные маркетинговые активности, и помощь в обучении персонала компаний партнеров – учебный центр МУК является Authorized Oracle University Reseller. Компания также предоставляет возможность использования оборудования для проведения тестирований и демонстраций заказчикам. Стоит отметить возможности регистрации и защиты проектов, а также помощь в работе над комплексными проектами и технические консультации.

                  Одним из флагманских продуктов Oracle является высокопроизводительная СУБД Oracle Database. В сфере инженерных систем Oracle наиболее известны платформа Exadata Database Machine – кластер серверов для управления базами данных, а также программно-аппаратный комплекс Database Appliance, комплекс для работы с большими данными Big Data Appliance, система Exalogic Elastic Cloud и решение для консолидации баз данных и приложений SuperCluster.

                  Помимо этого, в портфеле Oracle присутствуют защищенные серверы на собственной архитектуре SPARC, обеспечивающие безопасность данных без ущерба для производительности. Также компания предлагает системы хранения данных All-Flash, NAS, SAN и ленточные системы Tape Storage.

                  Одно из приоритетных направлений Oracle – облачные сервисы. Компания предлагает заказчикам комплексное интегрированное облако Oracle Cloud, которое помогает организациям внедрять инновации, повышать адаптируемость, снижать затраты и упрощать ИТ-системы. Организациям, которым необходимо внутреннее частное облако, Oracle предоставляет комплексный портфель платформенных и инфраструктурных решений. В портфеле компании доступны управляемые облачные услуги для бизнес-приложений, ПО промежуточного уровня, СУБД и оборудования. Заказчикам доступны новые облачные программы «Bring Your Own License to PaaS» и «Universal Credits», которые снижают ТСО за счет повышения автоматизации и гибкости. Для всех облачных сервисов Oracle характерны низкая стоимость, надежность, производительность, приверженность стандартам, совместимость и безопасность.

                  Таким образом, МУК получает возможность предложить своим партнерам и заказчикам полный спектр решений систем управления базами данных, связующего программного обеспечения и бизнес-приложений Oracle.
                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/339064/


                  Метки:  

                  MobX — управление состоянием без боли. Лекция в Яндексе

                  Воскресенье, 01 Октября 2017 г. 11:20 + в цитатник
                  Leono сегодня в 11:20 Разработка

                  MobX — управление состоянием без боли. Лекция в Яндексе

                    Недостаток зависимостей в веб-приложении приводит к ошибкам в интерфейсе, избыток — снижает производительность. Руководитель отдела разработки интерфейсов Яндекса Азат razetdinov показывает, как библиотека MobX помогает отслеживать минимальный набор изменений и поддерживать консистентность состояния приложений, а также знакомит с инструментом mobx-state-tree, который позволяет совместить всё лучшее из MobX и Redux.




                    — Меня зовут Азат Разетдинов, я представляю персональные сервисы Яндекса: Почту, Диск, Календарь, Паспорт, управление аккаунтом. Хотел бы рассказать про управление состоянием веб-приложения без боли.

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

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

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

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

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

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

                    Если вы давно во фронтенде, то, наверное, знакомы с таким паттерном, как ручная подписка на изменения.

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

                    Если вспомнить старые фреймворки, это выглядело примерно так в псевдокоде.

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

                    У этого подхода две проблемы.

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

                    Но у этого подхода тоже есть проблема.

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

                    Что здесь произошло? Товарищ обновил свою аватарку, а она обновилась не везде. Оказалось, большая аватарка поменялась, а маленькие аватарки в твитах не подписались на изменения аватарки пользователя и не получили это изменение, не обновили себя. Это самый большой минус, который есть в ручной подписке.

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

                    Чтобы это показать, нужно объяснить, как это устроено изнутри.

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

                    Давайте объявим такой класс — Person, человек. И объявим три поля, и пометим их декоратором observable. Имя, фамилия и кличка.

                    Когда мы говорим про MobX, очень полезно проводить аналогию с Excel.

                    Observable-поля — это просто исходные данные в ячейках.

                    Они позволяют остальным концепциям следить за изменением себя.

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

                    В данном случае мы просто конкатинируем имя и фамилию через пробел.

                    Если проводить аналогию с Excel, это ячейка с формулой. Кажется, пока все просто.

                    Этот не тот action, который вы, наверное, знаете из Redux, но он очень похож.

                    В терминах MobX action — просто некая функция. Здесь это метод, но это необязательно. Action не обязан быть методом класса, он может быть в любом месте приложения, главное, чтобы он был помечен декоратором action. Внутри этой функции вы можете изменять observable-поля, которые вы пометили ранее.

                    Пока все понятно, метод устанавливает nickName.

                    Теперь начинается магия.

                    Самая главная концепция MobX — это реакции.

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

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

                    Самая простая реакция — функция autorun из библиотеки MobX.

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

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

                    Autorun, как только мы его вызываем, сразу первый раз запускает нашу функцию, которую мы передали в аргументе.

                    При выполнении этой функции он обращается к observable-полям, в данном случае первым делом к nickName. Здесь срабатывает магия MobX: на самом деле, когда мы объявляли observable, вместо обычного поля был объявлен getter для этого поля.

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

                    Когда у меня что-то изменится, мне нужно этого слушателя уведомить об этом изменении. NickName пустой, поэтому дальше идет обращение к Person fullName. У нас происходит подписка на изменение этого поля. FullName является computed-полем, это getter, который внутри себя обращается к полям firstName и lastName.

                    На этом выполнение функции заканчивается, и в этот момент MobX знает, что функция, которую мы передали в autorun, зависит от четырех полей: nickName, fullName, firstName, lastName.

                    Дерево зависимостей выглядит так. Любое изменение observable-полей в первом столбце запустит заново выполнение autorun.

                    Допустим, мы решили задать нашему человечку кличку Васек.

                    Этот метод, который является action, совершает внутри себя операцию присваивания.

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

                    Autorun получает уведомление, что что-то изменилось, надо заново перезапуститься. Запускает выполнение функции, обращается к полю nickName.

                    На сей раз оно уже не пустое. На этом выполнение функции прекращается.

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

                    До тех пор, пока не изменится nickName, autorun вообще будет игнорировать любые изменения полей firstName и lastName, потому что код устроен таким образом, что пока nickName не пустой, до поля fullName дело даже не дойдет никогда.

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

                    Autorun — не единственный пример реакции. Есть реакция observer. Это helper для React.

                    Если наш пример переписать в виде React-компонента, он будет выглядеть примерно так.

                    Мы используем декоратор observer. Напомню: можно использовать обычные обертки здесь. Внутри метода render мы обращаемся сначала к nickName. Если он пустой, тогда уже к fullName. Ровно та же логика. Единственное, при использовании observer мы не выполняем функцию autorun, а вместо этого он при любом изменении полей, на которые мы подписаны, запускает переадресовку вашего компонента.

                    Автоматическая подписка компонентов плюс observer позволяет кардинально минимизировать количество перерисовок React-компонентов.

                    Есть часто наблюдаемый код, когда имеется какой-то флаг, который мы проверяем в самом начале метода render. Если он не выполняется, мы просто возвращаем null. Здесь очень помогает магия React. До тех пор, пока изменения у нас false, изменения любых полей, которые используются ниже, где написано много кода, observer будет игнорировать. Но как только флаг загорится, во время очередного перерендера он выполнит очередной код и подпишется на изменения полей, которые там используются.

                    Если React экономит нам операции с домом, то MobX экономит нам операции с виртуальным домом. Чем меньше перерисовок даже в виртуальном доме, тем быстрее наше приложение.

                    Расскажу об еще одной оптимизации, которая встроена в MobX, — кэшировании computed.

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

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

                    Но если мы задаем поле nickName, и autorun отписывается от fullName, в этот момент fullName понимает, что у него больше не осталось подписчиков, выкидывает кэш, который потом собирается через garbage collector и работает просто как обычный геттер.

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

                    Небольшой пример того, как можно работать с асинхронными данными при таком подходе.

                    Можно руками запускать метод load, запускать флаг isLoading True или False, но у MobX есть такой хелпер, который называется fromPromise.

                    Мы объявляем некое поле, заворачиваем асинхронную операцию в хелпер fromPromise, и в этом поле появляется два сабполя — state и value.

                    В React-компоненте можно сначала проверять, что state pending. Тогда мы показываем какой-то loading. Если fullfilled, тогда обращаемся к полю value и рисуем наш компонент дальше.

                    Итого, плюсы MobX.

                    Уже слышу вопрос из зала. Я этого человечка называю Reduxman, это человек, который написал много кода на Redux. Какой вопрос он задает?


                    А как же netability? Это что же, у вас можно методами прямо полями модели менять? Ну ни фига себе.

                    А как же time travel? Мне же нужны не модели с методами, а простые plain JavaScript-объекты, чтобы можно было с их помощью делать undo, redo и прочие вкусные штуки.

                    Как же мой любимый devtools, к которому я уже привык, чтобы можно было делать реплей действий, которые производил пользователь?

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

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

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

                    Когда я начал думать, чем Redux отличается от MobX, у меня возникла такая аналогия.



                    Все любили этот мультик? А чем отличаются мультики, которые смотрит молодое поколение? Они вот такие.



                    Знаете, в чем разница? «Том и Джерри» рисовали таким образом, брали кадры и каждый рисовали по отдельности.

                    Ничего не напоминает? Immutable store в Redux-приложении. Каждый раз есть какой-то отпечаток, который мы руками конструируем, используем для этого библиотеку immutable или Object.assign или spread operator. Каждый раз мы дорисовываем руками состояние приложения на текущий момент. Если нужно откатиться, мы берем и обратно откатываем. Это все круто, только очень много кода получается. Я не люблю писать код, я люблю его удалять. Код — это зло. Самый быстрый код — это тот, который не выполняется.

                    А новые мультики рисуют вот так.



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

                    То, что мы руками пытаемся работать с immutable-данными, — это необязательно. Immutable-состояние нашего приложения — это еще один вид, еще одно представление, еще одно отображение. Можно использовать живую модель, просто каждый раз в любой момент времени получить его плоскую проекцию.
                    Давайте покажу, как это сделать.

                    Авторы MobX написали такую отдельную штуку.

                    Это уже более opinionated-подход, который диктует вам, как писать приложение, но взамен дает много плюшек.

                    Давайте напишем небольшой store, объявим класс Todo, для этого используется хелпер types, у которого есть метод model. Пока он пустой.

                    Добавим title.

                    Здесь мы объявляем, что это строка.

                    Добавим опциональное булево поле isCompleted. Кстати, здесь есть возможность написать это короче. Если вы присваиваете какой-то примитив, то mobx-state-tree понимает, что это опциональное примитивное поле с дефолтным значением.

                    Добавим reference. Это означает, что в folder будет лежать id какого-то другого объекта, но при создании модели mobx-state-tree по этому id достанет этот объект из некоего store и поставит его в этом поле. Пример я покажу чуть позже.


                    Чтобы вся магия работала, нам нужно объявить класс Folder, у которого обязан быть id с типом types.identifier. Это как раз для того, чтобы связывать ссылки с объектами store по идентификатору.

                    Объявим главный рутовый TodoStore, в котором будет два массива: todos и folders. Здесь можно видеть, как используется types.array, передаем в качестве аргумента класс, и MobX понимает, что это массив instance этого класса.

                    Если мы объявляем геттер, он автоматически становится computed из терминологии MobX, как мы смотрели раньше. Здесь у меня есть геттер completedTodos, который просто возвращает список всех выполненных todo. Он кэшируется, и пока есть хоть один подписчик, он всегда возвращает закэшированное значение. Не бойтесь так писать, писать сложные выражения, все это будет закэшировано.

                    Вот так создаются экшены. Первый объект в декларации — свойства и computed, во втором объекте перечислены экшены. Здесь и не надо их уже объявлять, mobx-state-tree по умолчанию считает, что все, что вы передаете вторым объектом, — экшены.

                    Давайте попробуем создать store. У нас есть данные, допустим, они пришли с сервера, видите, они в нормализованном виде, у нас в folder лежит 1, а в списке folders есть объект с идентификатором 1.

                    Создаем, используем.

                    Первая строчка — все нормально, я использую поле title объекта todo.

                    Во второй строчке уже магия: поскольку folder объявлен как reference, то MobX при создании модели автоматически, в первую очередь, положил folder в массив folders, а в моделях todo по ссылке, по идентификатору, добавил ссылку на этот объект. То, где в Redux мы бы писали селектор, здесь работает из коробки. Можно спокойно обращаться ко вложенным полям ваших ссылок, ваших референсов. И это работает, это очень удобно писать в компонентах без всяких селекторов и прочих map state to props.

                    Мы собрали какую-то 3D-модель. Давайте попробуем запустить ее. Камера, мотор.

                    Для начала попробуем получить обратно данные, которые мы в модель положили. Для этого есть хелпер getSnapshot. Передаем туда модель, получаем snapshot в виде обычного JS-объекта, как все редаксмены любят. Получил и получил, но у меня же модель постоянно меняется, как мне подписаться на изменения?

                    Очень просто: есть хелпер onSnapshot, который позволяет подписаться на изменение любого поля в модели, при этом в качестве параметра он всегда передает новый snapshot, который он генерирует, но не просто так, иначе было бы глупо каждый раз новый объект генерировать. Он так же, как React, использует immutable.

                    Если какие-то части менялись, он их реиспользует, запускает механизм structural sharing.

                    Для изменившихся создает новые объекты.

                    Как сделать time travel? Мы гуляем по истории и хотим какой-то snapshot применить к модели. Есть хелпер applySnapshot, передать модель, передать и snapshot. Он сравнивает то, что вы передали, и то, что сейчас в модели, берет диф и обновляет только те части, которые изменились.

                    При этом он реиспользует модели, если у них совпадают идентификаторы. Если в модели лежит какой-то folders с id = 1, в snapshot тоже передается folders с id = 1. Он не пытается его перезатереть, а просто обновляет данные самого folder, если они изменились.

                    Инстанция внутри модели не перезатирается, если вы правильно задали идентификаторы.

                    Пожалуй, самая яркая иллюстрация того, как работают живые модели и snapshots.

                    Есть живая модель, и мы в любой момент времени можем снять с нее snapshot.

                    Наконец, бонус, специально для редаксменов. Есть для адаптера для работы с Redux. Если у вас уже есть большое приложение, написанное в Redux style, то вы можете переписать только store и из mobx-state-tree store получить reduxStore просто методом asReduxStore.

                    Если вы привыкли работать с ReduxDevtools, можно просто использовать хелпер connectReduxDevtools, передать туда модель, store в виде mobx-state-tree, и все будет работать.

                    Старые добрые ООП-модели вместо immutable-структур. Вообще-то, когда мы от них отказывались, кажется, мы выкинули ребенка вместе с водой. Они вообще-то были удобные, когда у вас есть данные и методы для работы с ними.

                    Живые ссылки вместо селекторов. Вы можете вкладывать модели друг в друга сколько угодно, делать референсы и работать просто через точку. Todo.folder.parent и так далее, как хотите. При этом, когда вы будете сериализовывать, все будет автоматически обратно сериализовываться в нормализованный вид.

                    Дешевое получение снэпшотов всего дерева с реиспользованием частей, которые не изменились. Применение снэпшотов с реконсайлингом, прямо как в React. Если объекты совпадают по идентификаторам, они будут реиспользованы. И — адаптеры для Redux Store и Redux Devtools.

                    Как сказал Дэниел Эрвикер, MobX — это как React, только для данных. Здесь есть несколько ссылок, которые вы можете потом посмотреть:


                    На этом спасибо.
                    Original source: habrahabr.ru (comments, light).

                    https://habrahabr.ru/post/339054/


                    Метки:  

                    Чтение на выходных: 17 независимых блогов по математике, алгоритмам и языкам программирования

                    Воскресенье, 01 Октября 2017 г. 09:35 + в цитатник
                    it_man сегодня в 09:35 Разработка

                    Чтение на выходных: 17 независимых блогов по математике, алгоритмам и языкам программирования

                      Сегодня мы подготовили очередную [наш IaaS-дайджест и материалы по структурированию кода] подборку полезных источников. На этот раз мы решили изучить тематическую ветку на Hacker News и рассказали о блогах, которые могут хорошо дополнить чтение «Хабра».

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

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

                      / Flickr / home thods / CC BY



                      Математика и алгоритмы





                      Сам автор блога Брайан Хэйс называет себя счастливчиком, которому удалось посвятить свою жизнь изучению математики и информатики. В своем блоге автор делится накопленным опытом (более 350 постов), а также пишет о биологии и физике, решает задачи и делится своим опытом работы с изображениями. С 1973-го по 1984-й год Брайан писал статьи для Scientific American – одного из старейших научно-популярных журналов США, который издается с 1845 года. Публикации Брайана выходили в «Computer Language» и «American Scientist». В 2005 году автор блога опубликовал книгу о промышленных объектах «Infrastructure: A Guide to the Industrial Landscape», а осенью 2017 года выйдет его новая книга «Foolproof, and Other Mathematical Meditations».




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




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




                      Автор блога, о котором мы рассказали выше, очень рекомендует всем блог Кевина Кнадсона. И пусть Кевин давно не обновлял свой блог (последняя публикация датируется 2015 годом), зато он успешно реализует проект под названием «My favorite theorem», в рамках которого он записывает подкасты в формате бесед с математиками со всех уголков мира. Рекомендуем к прослушиванию.




                      Практически каждый из упомянутых выше блоггеров советует почитать «Discrete Analysis». Авторы журнала утверждают, что он совсем не похож на другие издания о математике. Отличия они видят в выдержанном тематическом фокусе и удобной подаче материала.




                      Если вы хотите расширить свои знания в области квантовой механики и высшей математики, Дэн Пипони поможет вам в этом. В своем блоге автор старается уйти от нудных объяснений и использует примеры из обычной жизни, подкрепляя их схемами. Сам автор разбирает примеры на Haskell, однако, материалы по линейной алгебре, логике и многим другим темам, интересно почитать, даже если вы работаете с другими языками. В профиле автора на Google+ или в его Twitter можно узнать о жизни автора, найти посты с интересными ссылками на статьи (например, о проверке гипотез с помощью P-значения).


                      / Flickr / home thods / CC BY



                      Языки программирования





                      Блог, который с 2005 года ведет Майк Эш, особенно приглянется тем, кто пишет на Swift. В блоге вы найдете руководства, разборы ошибок и заметки с тематических семинаров. Майк регулярно отвечает на вопросы читателей и составил целую книгу из еженедельных Q&A-постов. Она называется «The Complete Friday Q&A: Volume I».




                      Руководства на Python, Haskell и Clojure от Эли Бендерски. Этот проект начался в качестве персонального блога в 2003 году и перерос в технологический журнал, с помощью которого автор «прокачивает» свои знания и делится открытыми проектами.




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




                      Карин Майер, разработчик ПО, пишет о Clojure, искусственном интеллекте и робототехнике. Кстати, совсем недавно в мае 2017 автор блога опубликовала книгу «Code Shifter», которую написала вместе со своей дочерью. Книга описывает приключения 12-летней девочки Элизы, обладающей способностью изменять код одним только прикосновением. Во время этих приключений Элиза вместе с друзьями учится программировать и познает свои сильные стороны.




                      Фред Херберт, автор книги «Изучай Erlang во имя добра» (Learn You Some Erlang for Great Good!) в своем блоге разбирает ошибки и делится руководствами на Erlang.




                      Блог польского разработчика Бартоломея Филипека (Bartlomiej Filipek, он же Bartek) будет интересен тем, кто ищет регулярно обновляющийся ресурс о С++. Бартек начал писать код, когда ему было 14, поэтому за спиной у него немалый опыт работы в самых разных областях: от разработки ПО до геймдева. В блоге можно найти посты об оптимизации, практические советы, последние стандарты и много чего еще. Например, вот эта статья Бартека была признана одной из лучших на англоязычной тематической площадке за январь 2016.




                      Название блога говорит само за себя. Внутри вы найдете руководства для написания чистого кода на С++. Автор блога, Джонатан Боккара (Jonathan Boccara), делится своим 5-летним опытом программирования на С++, и методами обучения сотрудников.




                      Блог Роберта Харпера, профессора кафедры теории и практики вычислительных машин и систем Университета Карнеги-Меллон, будет интересен тем, кто хотел бы погрузиться в теорию языков программирования. Особенно много информации можно найти по SML и grid-вычислениям. Профессор Харпер также является автором нескольких книг, среди которых «The Definition of Standard ML» и «Practical Foundations for Programming Languages».




                      В блоге Криса Дала вы сможете познакомиться с самыми различными языками вроде Pony, Vodka или Haskell. Помимо этого в своем блоге Крис разбирается с ОС, браузерами и сетевыми технологиями. Обычно он проводит детальный анализ того или иного предмета, сопровождая его кодом и пояснениями, а также подкрепляет свое мнение ссылками на полезные ресурсы. В блоге около трехсот публикаций, накопленных за 12 лет.




                      Название этого блога плавно эволюционировало из «Inside 245s». До этого он назывался: Inside T5, Inside P4, Inside 1712B, Inside 2214, Inside 233, Inside 374, Inside 206-105, Inside 214-1E и Inside 245-5D. Автор этого блога, Эдвард З. Ян (Edward Z. Yang), эволюционировал вместе с ним. Сейчас он — аспирант Стэнфордского университета и автор нескольких публикаций. Больше всего блоггер интересуется Haskell (например, в блоге есть иллюстрированное введение в работу на Haskell).




                      В «стране Ruby» вы найдете свежие новости, туториалы и мнения экспертов об одном из самых молодых и популярных языков программирования. Ресурс обновляется несколько раз в день — это одна из лучших тематических лент о Ruby. Автор проекта Джонатан Рочкинд (Jonathan Rochkind) регулярно публикует посты в своем блоге о Ruby и веб-разработке, а апдейты Rubyland также доступны и в Twitter.



                      P.S. Наши дайджесты:


                      P.P.S. О чем еще мы пишем в нашем корпоративном блоге:

                      Original source: habrahabr.ru (comments, light).

                      https://habrahabr.ru/post/338624/


                      Метки:  

                      DevOps приходит к нам домой? Домашний Minecraft server в Azure с применением современных DevOps практик

                      Суббота, 30 Сентября 2017 г. 23:04 + в цитатник
                      Dronopotamus вчера в 23:04 Разработка

                      DevOps приходит к нам домой? Домашний Minecraft server в Azure с применением современных DevOps практик

                      • Tutorial
                      Эта статья НЕ про майнкрафт. Эта статья про Azure и про современные подходы к деливери ПО.

                      Если вы просто хотите развернуть себе сервер майнкрафт — спросите гугл «minecraft server hosting» — будет и проще и дешевле.

                      А вот если вы хотите посмотреть на реальный кейс использования подходов Infrastructure as Code, Configuration as Code применительно к неадаптированному к Azure ПО на примере майнкрафта — то добро пожаловать

                      Roadmap


                      Azure Stack огромен и хочется потыкать в разные его части, поэтому эта статья будет всяко не одна.

                      План статей пока видится такой:

                      • Автоматизированный деплой и настройка сервера с применением Azure CLI 2 и Powershell DSC
                      • Поддержка конфигурации сервера в консистентном состоянии с применением Azure Automation
                      • Автоматизированное обновление сервера на новую версию
                      • Получение логов майнкрафта, анализ и отображение с применением Azure Stream Analytics и Power BI
                      • Организация active/passive кластера с управлением через Azure Automation
                      • ...

                      Но конечно процесс появления новых статей может быть остановлен отсутствием вашего интереса )

                      Disclaimer


                      • Я не it инженер, не devops инженер, не игрок в майнкрафт. Поэтому если вы шарите и вам кажется, что я всё делаю не правильно — то скорее всего так оно и есть — напишите в коментах как надо.
                      • Все примеры к статье 100% рабочие, но поставляются «как есть». Если у вас что-то не работает — почините это самостоятельно или забейте.

                      Итак.

                      Автоматизированный деплой и настройка сервера с применением Azure CLI 2 и Powershell DSC


                      О задаче


                      Задача не синтетическая. Сервер попросили развернуть дети и он реально будет использоваться. Меньше всего я хочу его постоянно чинить и поднимать. Ещё меньше я хочу терять данные при обновлениях. Поэтому требования к нему вполне себе prod-like )

                      Общий подход


                      • 100% автоматизация.
                      • Относимся к скриптам как к коду
                      • Соблюдаем базовые требования конфигурационного управления к деливери процессу на prod окружения

                      Все пункты буду пояснять по ходу дела

                      О ПО


                      В качестве сервера я взял официальный сервер minecraft.net/en-us/download/server. Беглое изучение выявило, что:

                      • Сервер представляет из себя jar файл и требует для запуска java
                      • Для запуска ему требуется рядом с собой файл eula.txt с определённым содержимым
                      • Сервер конфигурируется с помощью файла server.properties который тоже должен лежать рядом
                      • Сервер слушает 25565 порт по протоколу TCP
                      • Данные (карту) сервер хранит в папочке World рядом с собой (и это будет слегка проблемой)
                      • Сервер умеет данные апгрейдить, если они созданы предыдущей версией (и это сильно упростит жизнь при апгрейде)
                      • Логи хранятся в папочке Logs рядом

                      Инфраструктура


                      Применим самую базовую схему:

                      одна Resource Group c:

                      • Network Security Group (будут открыты порты 25565 (minecraft) и 3389 (rdp)
                      • VNET c 1 subnet
                      • NIC и статический public IP c DNS именем
                      • 1 VM под Windows Server 2016 core + 2 Managed Disk (OS + Data)
                      • Storage account и 1 container для Blob storage

                      image

                      Данные будем хранить отдельно от виртуальной машины на отдельном managed disk и линковать в папочку World рядом с jar'кой с помощью Symlink (я писал выше, что расположение папочки world не настраивается и должно быть всегда рядом с jar).

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

                      Сервер будем конфигурировать на основании:

                      • Дистрибутива (jar с сервером + jre + начальные данные), который будет предварительно загружаться в Blob storage
                      • Конфигурации (Powershell DSC), которая также будет предварительно загружаться в Blob storage

                      Я там выше обещал следования некоторым требованиям конфигурационного управления — вот первое:

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

                      Именно поэтому мы и дистрибутив и конфигурацию будем предварительно собирать из артефактов в интернетах и перепубликовывать в Blob Storage.

                      Таким образом, алгоритм процедуры развертывания следующий:

                      • Создать новую Resource Group, Storage Account и Storage Container
                      • Сформировать дистрибутив (jar с сервером + jre + начальные данные) и загрузить его в Blob Storage
                      • Создать всю остальную инфраструктуру в Resource Group
                      • Сформировать DSC конфигурацию и загрузить её в Blob Storage
                      • Сконфигурировать сервер на основании конфигурации и дистрибутива

                      Исходники


                      • Храним на github
                      • Соблюдаем какую-нибудь внятную стратегию бранчевания и релизного цикла (ну например gitflow и релиз каждую статью :))
                      • Используем azure cli 2 и bash там, где это возможно (к слову, в этой статье удалось совсем избежать использования powershell api. А вот в следующей это уже не получится)

                      Реализация шагов процедуры развертывания


                      1. Создать новую Resource Group, Storage Account и Storage Container


                      реализация тривиальная, просто вызовы cli

                      initial_preparation.sh
                      az configure --defaults location=$LOCATION group=$GROUP
                      
                      echo "create new group"
                      az group create -n $GROUP
                      
                      echo "create storage account"
                      az storage account create -n $STORAGE_ACCOUNT --sku Standard_LRS
                      STORAGE_CS=$(az storage account show-connection-string -n $STORAGE_ACCOUNT)
                      export AZURE_STORAGE_CONNECTION_STRING="$STORAGE_CS"
                      
                      echo "create storage container"
                      az storage container create -n $STORAGE_CONTAINER --public-access blob


                      2. Сформировать дистрибутив (jar с сервером + jre + начальные данные) и загрузить его в Blob Storage


                      Скачиваем сервер и карту, jre берём с текущего компьютера и всё пакуем в zip
                      структура дистрибутива:

                      • .jar
                      • jre
                      • initial_world — папка с начальной картой

                      prepare_distr.sh
                      mkdir $DISTR_DIR
                      cd $DISTR_DIR
                      
                      echo "download minecraft server from official site"
                      curl -Os https://s3.amazonaws.com/Minecraft.Download/versions/1.12.2/minecraft_server.$MVERSION.jar 
                      
                      echo "copy jre from this machine"
                      cp -r "$JRE" ./jre
                      
                      echo "create ititial world folder"
                      mkdir initial_world
                      cd initial_world
                      
                      echo "download initial map"
                      curl -Os https://dl01.mcworldmap.com/user/1821/world2.zip 
                      unzip -q world2.zip
                      cp -r StarWars/* .
                      rm -r -f StarWars
                      rm world2.zip
                      cd ../
                      
                      echo "create archive (zip utility -> https://ranxing.wordpress.com/2016/12/13/add-zip-into-git-bash-on-windows/)"
                      cd ../
                      zip -r -q $DISTR_ZIP $DISTR_DIR 
                      rm -r -f $DISTR_DIR
                      


                      3. Сформировать DSC конфигурацию и загрузить её в Blob Storage


                      DSC конфигурация представляет собой один ps1 файл (о структуре его — позже), который будет запускаться на сервере и его конфигурировать.

                      Но скрипты в ps1 файле имеют депенды на внешние модули, которые не будут установлены на сервер автоматически.

                      Поэтому конфигурацию надо передать на сервер не просто как ps1 файл, а как архив с:

                      • ps1 файлом
                      • Всеми зависимыми модулями, сложенными в одноименных папочках рядом с ps1 файлом

                      Зависимые модули можно просто скачать curl'ом как nuget пакет с репозитория powershell gallery и разархивировать в нужное место.

                      Также можно использовать и команду powershell Save-Module — которая включает эти два шага
                      Обратите внимание, что версии зависимых модулей и в ps1 файле и в скриптах формирования архива с конфигурацией указаны и совпадают.

                      И тут мы опять следуем требованиям очередного правила конфигурационного управления:

                      Все зависимости должны быть точно определены и ресолвиться всегда однозначно

                      prepare_config.sh
                      echo "prepare server configuration"
                      curl -s -L -o configuration/xPSDesiredStateConfiguration.zip "https://www.powershellgallery.com/api/v2/package/xPSDesiredStateConfiguration/7.0.0.0"
                      curl -s -L -o configuration/xNetworking.zip "https://www.powershellgallery.com/api/v2/package/xNetworking/5.1.0.0"
                      curl -s -L -o configuration/xStorage.zip "https://www.powershellgallery.com/api/v2/package/xStorage/3.2.0.0"
                      cd configuration
                      
                      unzip -q xPSDesiredStateConfiguration.zip -d xPSDesiredStateConfiguration
                      rm -r xPSDesiredStateConfiguration.zip
                      unzip -q xNetworking.zip -d xNetworking
                      rm -r xNetworking.zip
                      unzip -q xStorage.zip -d xStorage
                      rm -r xStorage.zip
                      
                      zip -r -q ../$CONFIG_ZIP . *
                      rm -r -f xPSDesiredStateConfiguration
                      rm -r -f xNetworking
                      rm -r -f xStorage
                      cd ../


                      4. Создать всю остальную инфраструктуру в Resource Group


                      реализация тривиальная, просто вызовы cli:

                      iaas_preparation.sh
                      
                      echo "create network security group and rules"
                      az network nsg create -n $NSG
                      az network nsg rule create --nsg-name $NSG -n AllowMinecraft --destination-port-ranges 25556 --protocol Tcp --priority 100
                      az network nsg rule create --nsg-name $NSG -n AllowRDP --destination-port-ranges 3389 --protocol Tcp --priority 110
                      
                      echo "create vnet, nic and pip"
                      NIC_NAME=minesrvnic
                      PIP_NAME=minepip
                      SUBNET_NAME=servers
                      az network vnet create -n $VNET --subnet-name $SUBNET_NAME
                      
                      az network public-ip create -n $PIP_NAME --dns-name $DNS --allocation-method Static
                      az network nic create --vnet-name $VNET --subnet $SUBNET_NAME --public-ip-address $PIP_NAME -n $NIC_NAME 
                      
                      echo "create data disk"
                      DISK_NAME=minedata
                      az disk create -n $DISK_NAME --size-gb 10 --sku Standard_LRS
                      echo "create server vm"
                      az vm create -n $VM_NAME --size $VM_SIZE --image $VM_IMAGE \
                                      --nics $NIC_NAME \
                                      --admin-username $VM_ADMIN_LOGIN --admin-password $VM_ADMIN_PASSWORD \
                                      --os-disk-name ${VM_NAME}disk --attach-data-disk $DISK_NAME                


                      5. Сконфигурировать сервер на основании конфигурации и дистрибутива


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

                      DSC extension кормится конфигом в котором указан URL до архива с конфигурацией и значения параметров

                      server_configuration.sh
                      echo "prepare dsc extension settings"
                      cat MinecraftServerDSCSettings.json | envsubst > ThisMinecraftServerDSCSettings.json
                      
                      echo "configure vm"
                      az vm extension set \
                         --name DSC \
                         --publisher Microsoft.Powershell \
                         --version 2.7 \
                         --vm-name $VM_NAME \
                         --resource-group $GROUP \
                         --settings ThisMinecraftServerDSCSettings.json
                      rm -f ThisMinecraftServerDSCSettings.json


                      О Powershell DSC


                      Ну это некоторая надстройка над powershell, которая позволяет декларативно описывать требуемое состояние компьютера.

                      DSC конфигурация анализируется на целевой машине, строится диф относительно текущей конфигурации этой машины, а затем происходит приведение конфигурации к нужной. Ближайший аналог powershell DSC в «opensource» мире — Ansible.

                      Реализация DSC конфигурации MinecraftServer


                      Структурно конфигурация состоит из набора шагов, которые выполняются в определенном порядке (с учётом зависимостей). Пробежимся по всем:

                      xRemoteFile DistrCopy
                      {
                         Uri = "https://$accountName.blob.core.windows.net/$containerName/mineserver.$minecraftVersion.zip"
                         DestinationPath = "$mineHome.zip"
                         MatchSource = $true
                      }
                      

                      Декларирует, что на компьютере должен лежать zip с дистрибутивом сервера по пути DestinationPath, если его там не лежит — он будет скачен по адресу Uri:

                      Archive UnzipServer 
                      {
                         Ensure = "Present"
                         Path = "$mineHome.zip"
                         Destination = $mineHomeRoot
                         DependsOn = "[xRemoteFile]DistrCopy"
                         Validate = $true
                         Force = $true
                      }
                      

                      Декларирует, что на компьютере в папке Destination должен лежить разархивированный архив с дистрибутивом сервера если его там не лежит (или если состав файлов отличается от того что в архиве) — они будут добавлены:

                      File CheckProperties
                      {
                         DestinationPath = "$mineHome\server.properties"
                         Type = "File"
                         Ensure = "Present"
                         Force = $true
                         Contents = "....."
                      }
                      
                      File CheckEULA
                      {
                         DestinationPath = "$mineHome\eula.txt"
                         Type = "File"
                         Ensure = "Present"
                         Force = $true
                         Contents = "..."				
                         DependsOn = "[File]CheckProperties"
                      }
                      

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

                      xWaitForDisk WaitWorldDisk
                      {
                         DiskIdType = "Number"
                         DiskId = "2"   
                         RetryIntervalSec = 60	
                         RetryCount = 5
                         DependsOn = "[File]CheckEULA"
                      }
                      
                      xDisk PrepareWorldDisk
                      {
                         DependsOn = "[xWaitForDisk]WaitWorldDisk"
                         DiskIdType = "Number"
                         DiskId = "2"
                         DriveLetter = "F"
                         AllowDestructive = $false
                      }
                      
                      xWaitForVolume WaitForF
                      {
                         DriveLetter      = 'F'
                         RetryIntervalSec = 5
                         RetryCount       = 10
                         DependsOn = "[xDisk]PrepareWorldDisk"
                      }
                      

                      Тут мы декларируем о том, что managed disk с данными должен быть инициализирован в OS и ему должна быть присвоена буква F:

                      File WorldDirectoryExists
                      {
                         Ensure = "Present" 
                         Type = "Directory"
                         Recurse = $true
                         DestinationPath = "F:\world"
                         SourcePath = "$mineHome\initial_world"
                         MatchSource = $false
                         DependsOn = "[xWaitForVolume]WaitForF"    
                      }
                      

                      Декларируем о том, что на диске F должна присутствовать директори world (с миром). Если её там нет — мы считаем что происходит деплой впервые и нам надо эту папку создать, взяв за основу карту из initial_world дистрибутива:

                      Script LinkWorldDirectory
                      {
                         DependsOn="[File]WorldDirectoryExists"
                         GetScript=
                         {
                            @{ Result =  (Test-Path "$using:mineHome\World") }
                         }
                         SetScript=
                         {
                            New-Item -ItemType SymbolicLink -Path "$using:mineHome\World" -Confirm -Force -Value "F:\world"                
                         }
                         TestScript=
                        {
                            return (Test-Path "$using:mineHome\World")
                         }
                      }
                      

                      Это кастомный шаг — проверяем, что у нас в папке с сервером есть папка World. Если её нету — линкуем её с диска F. Script работает следующим образом — если TestScript вернул false — вызывается SetScript. Иначе не вызывается:

                      Script EnsureServerStart
                      {
                         DependsOn="[Script]LinkWorldDirectory"
                         GetScript=
                         {
                            @{ Result = (Get-Process -Name java -ErrorAction SilentlyContinue) } 
                         }
                         SetScript=
                         {
                            Start-Process -FilePath "$using:mineHome\jre\bin\java" -WorkingDirectory "$using:mineHome" -ArgumentList "-Xms512M -Xmx512M  -jar `"$using:mineHome\minecraft_server.$using:minecraftVersion.jar`" nogui"                
                         }
                         TestScript=
                         {
                            return (Get-Process -Name java -ErrorAction SilentlyContinue) -ne $null
                         }
                      }
                      

                      Ещё один кастомный шаг. Проверяем, что запущен java процесс с сервером ). Если не запущен — запускаем:

                      xFirewall FirewallIn 
                      { 
                         Name = "Minecraft-in"             
                         Action = "Allow" 
                         LocalPort = ('25565')
                         Protocol = 'TCP'
                         Direction = 'Inbound'
                      }
                      
                      xFirewall FirewallOut 
                      { 
                         Name = "Minecraft-out"             
                         Action = "Allow" 
                         LocalPort = ('25565')
                         Protocol = 'TCP'
                         Direction = 'Outbound'
                      }

                      И последние шаги — открываем 25565 порт.

                      Эпилог


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

                      Всем спасибо за внимание.

                      Как всё это запустить из Windows
                      Нам нужен будет


                      Запуск процедуры ролаута

                      git clone https://github.com/AndreyPoturaev/minecraft-in-azure
                      cd minecraft-in-azure
                      git checkout v1.0.0
                      export MINESERVER_PASSWORD=
                      export MINESERVER_DNS=
                      export MINESERVER_STORAGE_ACCOUNT=
                      az login
                      . rollout.sh
                       

                      Результат:

                      image
                      Original source: habrahabr.ru (comments, light).

                      https://habrahabr.ru/post/339034/


                      Метки:  

                      История создания синхронизатора часов DCF77

                      Суббота, 30 Сентября 2017 г. 21:11 + в цитатник

                      Метки:  

                      [Из песочницы] Работа с ресурсами, или как я пропихивал @Cleanup

                      Суббота, 30 Сентября 2017 г. 19:36 + в цитатник
                      vtarasoff вчера в 19:36 Разработка

                      Работа с ресурсами, или как я пропихивал @Cleanup

                      Это вымышленная история, и все совпадения случайны.

                      Наконец-то команда разработки компании Unknown Ltd. выпустила релиз вовремя. Руководитель отдела разработки Эндрю, системный архитектор Юг и простой рядовой разработчик Боб собрались на планирование.

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

                      Все уселись поудобнее и начали обсуждать предстоящий план. Боб сразу обратил внимание на задачу по переработке генерации документов. Суть задачи заключалась в том, что генерируемые документы состоят из схемы и настроек генерации и непосредственно самого документа. При сохранении в БД документ сериализуется в XML, конвертируется в поток байт и сжимается, а потом все стандартненько — помещается в колонку типа BLOB. Когда нужно отобразить в системе или выгрузить документ, то все повторяется с точностью да наоборот, и вуаля, документик красуется на экране клиента. Вот так, все просто. Но, как известно, дьявол кроется в мелочах. Чтобы заново сгенерировать документ, если необходимо изменить настройки, то приходится целиком загружать весь документ из БД, хотя содержимое его совершенно не нужно. Ай-я-яй. Обсудили задачу. Пришли к выводу, что Бобу предстоит сделать следующее:

                      • удалить ненужные сущности, изменить существующие, создать новые, чтобы разделить схему с настройками и сами данные документа
                      • написать миграцию, которая создаст новую таблицу для хранения схемы и данных, в которой будут две колонки, одна типа CLOB для хранения XML схемы, и другая типа BLOB для хранения по-прежнему сжатого XML с данными

                      На том и порешили. Перешли к обсуждению остальных вопросов.

                      Прошел час или полтора.

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

                      public class MigratorV1  {
                      	
                      	private Connection conn; // Injected
                      	private SAXParser xmlParser; // Injected
                      	private XMLOutputFactory xmlFactory; // Injected
                      	
                          public void migrate() throws Exception {
                          	PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
                          	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
                          	
                          	ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data");
                          	
                          	while (oldIdResult.next()) {
                          		long id = oldIdResult.getLong(1);
                          		
                          		selectOldContent.setLong(1, id);
                          		
                              	ResultSet oldContentResult = selectOldContent.executeQuery();
                              	oldContentResult.next();
                              	
                              	Blob oldContent = oldContentResult.getBlob(1);
                          		Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
                          		
                          		StringWriter newSchemeWriter = new StringWriter();
                          		XMLStreamWriter newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter);
                          		
                          		ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
                          		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
                          		XMLStreamWriter newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8");
                          		
                          		xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
                          			// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
                          		});
                          		
                          		String newScheme = newSchemeWriter.toString();
                          		byte[] newData = newDataOutput.toByteArray();
                          		
                          		StringReader newSchemeReader = new StringReader(newScheme);
                          		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
                          		
                          		insertNewContent.setLong(1, id);
                          		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
                          		insertNewContent.setBlob(3, newDataInput, newData.length);
                          		
                          		insertNewContent.executeUpdate();
                          	}
                          }
                      }
                      

                      Чтобы воспользоваться мигратором, клиентский код должен создать или каким-либо образом заинжектить мигратор и вызвать у него метод migrate(). Вот и все.

                      Кажется что-то не так, подумал Боб. Ну конечно, он же забыл освободить ресурсы. Представьте себе, что если у клиента на продакшене порядка несколько сотен тысяч документов, и мы не освобождаем ресурсов. Боб быстренько починил проблему:

                      public class MigratorV2  {
                      	
                      	private Connection conn; // Injected
                      	private SAXParser xmlParser; // Injected
                      	private XMLOutputFactory xmlFactory; // Injected
                      	
                          public void migrate() throws Exception {
                          	try (
                      			PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
                      	    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
                      	    	
                      	    	ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data");
                          	){
                          		while (oldIdResult.next()) {
                              		long id = oldIdResult.getLong(1);
                              		
                              		selectOldContent.setLong(1, id);
                              		
                              		try (ResultSet oldContentResult = selectOldContent.executeQuery()) {
                              			oldContentResult.next();
                                      	
                              			String newScheme;
                              			byte[] newData;
                              			
                              			Blob oldContent = null;
                              			try {
                              				oldContent = oldContentResult.getBlob(1);
                              				
                              				try (
                              					Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
                              					
                              					StringWriter newSchemeWriter = new StringWriter();
                              					
                          						ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
                                          		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
                              				){
                              					XMLStreamWriter newSchemeXMLWriter = null;
                              					XMLStreamWriter newDataXMLWriter = null;
                              					try {
                              						newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter);
                                              		newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8");
                                              		
                                              		xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
                                              			// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
                                              		});
                              					} finally {
                              						if (newSchemeXMLWriter != null) {
                                      					try {
                                      						newSchemeXMLWriter.close();
                                      					} catch (XMLStreamException e) {}
                                      				}
                              						if (newDataXMLWriter != null) {
                                      					try {
                                      						newDataXMLWriter.close();
                                      					} catch (XMLStreamException e) {}
                                      				}
                              					}
                              					
                              					newScheme = newSchemeWriter.toString();
                                          		newData = newDataOutput.toByteArray();
                              				}
                              			} finally {
                              				if (oldContent != null) {
                              					try {
                              						oldContent.free();
                              					} catch (SQLException e) {}
                              				}
                              			}
                              			
                              			try (
                              				StringReader newSchemeReader = new StringReader(newScheme);
                                      		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
                                  		){
                                  			insertNewContent.setLong(1, id);
                                      		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
                                      		insertNewContent.setBlob(3, newDataInput, newData.length);
                                      		
                                      		insertNewContent.executeUpdate();
                                  		}
                              		}
                              	}
                          	}
                          }
                      }
                      

                      О ужас! Подумал Боб. Как теперь в этом во всем разобраться? Этот код сложно понять не только другому разработчику, но и мне, если я вернусь к нему, предположим через месяц, чтобы что-то исправить или добавить. Надо декомпозировать, подумал Боб, и разбил независимые части кода на методы:

                      public class MigratorV3  {
                      	
                      	private Connection conn; // Injected
                      	private SAXParser xmlParser; // Injected
                      	private XMLOutputFactory xmlFactory; // Injected
                      	
                      	@RequiredArgsConstructor
                      	private static class NewData {
                      		final String scheme;
                      		final byte[] data;
                      	}
                      	
                      	private List loadIds() throws Exception {
                      		List ids = new ArrayList<>();
                      		
                      		try (ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data")) {
                      			while (oldIdResult.next()) {
                      				ids.add(oldIdResult.getLong(1));
                      			}
                      		}
                      		
                      		return ids;
                      	}
                      	
                      	private Blob loadOldContent(PreparedStatement selectOldContent, long id) throws Exception {
                      		selectOldContent.setLong(1, id);
                      		
                      		try (ResultSet oldContentResult = selectOldContent.executeQuery()) {
                      			oldContentResult.next();
                      			
                      			return oldContentResult.getBlob(1);
                      		}
                      	}
                      	
                      	private void oldContentToNewData(Reader oldContentReader, StringWriter newSchemeWriter, GZIPOutputStream newZippedDataOutput) throws Exception {
                      		XMLStreamWriter newSchemeXMLWriter = null;
                      		XMLStreamWriter newDataXMLWriter = null;
                      		try {
                      			newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter);
                          		newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8");
                          		
                          		xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
                          			// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
                          		});
                      		} finally {
                      			if (newSchemeXMLWriter != null) {
                      				try {
                      					newSchemeXMLWriter.close();
                      				} catch (XMLStreamException e) {}
                      			}
                      			if (newDataXMLWriter != null) {
                      				try {
                      					newDataXMLWriter.close();
                      				} catch (XMLStreamException e) {}
                      			}
                      		}
                      	}
                      	
                      	private NewData generateNewDataFromOldContent(PreparedStatement selectOldContent, long id) throws Exception {
                      		Blob oldContent = null;
                      		try {
                      			oldContent = loadOldContent(selectOldContent, id);
                      			
                      			try (
                      				Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
                      				
                      				StringWriter newSchemeWriter = new StringWriter();
                      				
                      				ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
                              		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
                      			){
                      				oldContentToNewData(oldContentReader, newSchemeWriter, newZippedDataOutput);
                      				
                      				return new NewData(newSchemeWriter.toString(), newDataOutput.toByteArray());
                      			}
                      		} finally {
                      			if (oldContent != null) {
                      				try {
                      					oldContent.free();
                      				} catch (SQLException e) {}
                      			}
                      		}
                      	}
                      	
                      	private void storeNewData(PreparedStatement insertNewContent, long id, String newScheme, byte[] newData) throws Exception {
                      		try (
                      			StringReader newSchemeReader = new StringReader(newScheme);
                          		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
                      		){
                      			insertNewContent.setLong(1, id);
                          		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
                          		insertNewContent.setBlob(3, newDataInput, newData.length);
                          		
                          		insertNewContent.executeUpdate();
                      		}
                      	}
                      	
                          public void migrate() throws Exception {
                          	List ids = loadIds();
                          	
                          	try (
                      			PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
                      	    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
                          	){
                          		for (Long id : ids) {
                          			NewData newData = generateNewDataFromOldContent(selectOldContent, id);
                          			storeNewData(insertNewContent, id, newData.scheme, newData.data);
                          		}
                          	}
                          }
                      }
                      

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

                      Вроде стало немного лучше. Но Бобу взгрустнулось. Почему XMLStreamWriter и Blob не реализуют AutoСloseable, подумал он и начал написал обертки:

                      public class MigratorV4  {
                      	
                      	private Connection conn; // Injected
                      	private SAXParser xmlParser; // Injected
                      	private XMLOutputFactory xmlFactory; // Injected
                      	
                      	@RequiredArgsConstructor
                      	private static class NewData {
                      		final String scheme;
                      		final byte[] data;
                      	}
                      	
                      	@RequiredArgsConstructor
                      	private static class SmartXMLStreamWriter implements AutoCloseable {
                      		final XMLStreamWriter writer;
                      
                      		@Override
                      		public void close() throws Exception {
                      			writer.close();
                      		}
                      	}
                      	
                      	@RequiredArgsConstructor
                      	private static class SmartBlob implements AutoCloseable {
                      		final Blob blob;
                      
                      		@Override
                      		public void close() throws Exception {
                      			blob.free();
                      		}
                      	}
                      	
                      	private List loadIds() throws Exception {
                      		List ids = new ArrayList<>();
                      		
                      		try (ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data")) {
                      			while (oldIdResult.next()) {
                      				ids.add(oldIdResult.getLong(1));
                      			}
                      		}
                      		
                      		return ids;
                      	}
                      	
                      	private Blob loadOldContent(PreparedStatement selectOldContent, long id) throws Exception {
                      		selectOldContent.setLong(1, id);
                      		
                      		try (ResultSet oldContentResult = selectOldContent.executeQuery()) {
                      			oldContentResult.next();
                      			
                      			return oldContentResult.getBlob(1);
                      		}
                      	}
                      	
                      	private void oldContentToNewData(Reader oldContentReader, StringWriter newSchemeWriter, GZIPOutputStream newZippedDataOutput) throws Exception {
                      		try (
                      			SmartXMLStreamWriter newSchemeXMLWriter = new SmartXMLStreamWriter(xmlFactory.createXMLStreamWriter(newSchemeWriter));
                      			SmartXMLStreamWriter newDataXMLWriter = new SmartXMLStreamWriter(xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8"));
                      		){
                      			xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
                      				// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
                      			});
                      		}
                      	}
                      	
                      	private NewData generateNewDataFromOldContent(PreparedStatement selectOldContent, long id) throws Exception {
                      		try (
                      			SmartBlob oldContent = new SmartBlob(loadOldContent(selectOldContent, id));
                      			
                      			Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.blob.getBinaryStream()));
                      				
                      			StringWriter newSchemeWriter = new StringWriter();
                      			
                      			ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
                          		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
                      		){
                      			oldContentToNewData(oldContentReader, newSchemeWriter, newZippedDataOutput);
                      			return new NewData(newSchemeWriter.toString(), newDataOutput.toByteArray());
                      		}
                      	}
                      	
                      	private void storeNewData(PreparedStatement insertNewContent, long id, String newScheme, byte[] newData) throws Exception {
                      		try (
                      			StringReader newSchemeReader = new StringReader(newScheme);
                          		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
                      		){
                      			insertNewContent.setLong(1, id);
                          		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
                          		insertNewContent.setBlob(3, newDataInput, newData.length);
                          		
                          		insertNewContent.executeUpdate();
                      		}
                      	}
                      	
                          public void migrate() throws Exception {
                          	List ids = loadIds();
                          	
                          	try (
                      			PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
                      	    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
                          	){
                          		for (Long id : ids) {    			
                          			NewData newData = generateNewDataFromOldContent(selectOldContent, id);
                          			storeNewData(insertNewContent, id, newData.scheme, newData.data);
                          		}
                          	}
                          }
                      }
                      

                      Были написаны две обертки SmartXMLStreamWriter и SmartBlob, которые автоматически закрывали XMLStreamWriter и Blob в try-with-resources.

                      А если у меня появятся еще ресурсы, которые не реализуют AutoCloseable, то мне снова придется писать обертки? Боб обратился за помощью к Югу. Юг немного покумекав выдал оригинальное решение, используя возможности Java 8:

                      public class MigratorV5  {
                      	
                      	private Connection conn; // Injected
                      	private SAXParser xmlParser; // Injected
                      	private XMLOutputFactory xmlFactory; // Injected
                      	
                      	@RequiredArgsConstructor
                      	private static class NewData {
                      		final String scheme;
                      		final byte[] data;
                      	}
                      	
                      	private List loadIds() throws Exception {
                      		List ids = new ArrayList<>();
                      		
                      		try (ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data")) {
                      			while (oldIdResult.next()) {
                      				ids.add(oldIdResult.getLong(1));
                      			}
                      		}
                      		
                      		return ids;
                      	}
                      	
                      	private Blob loadOldContent(PreparedStatement selectOldContent, long id) throws Exception {
                      		selectOldContent.setLong(1, id);
                      		
                      		try (ResultSet oldContentResult = selectOldContent.executeQuery()) {
                      			oldContentResult.next();
                      			
                      			return oldContentResult.getBlob(1);
                      		}
                      	}
                      	
                      	private void oldContentToNewData(Reader oldContentReader, StringWriter newSchemeWriter, GZIPOutputStream newZippedDataOutput) throws Exception {
                      		XMLStreamWriter newSchemeXMLWriter;
                      		XMLStreamWriter newDataXMLWriter;
                      		try (
                      			AutoCloseable fake1 = (newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter))::close;
                      			AutoCloseable fake2 = (newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8"))::close;
                      		){
                      			xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
                      				// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
                      			});
                      		}
                      	}
                      	
                      	private NewData generateNewDataFromOldContent(PreparedStatement selectOldContent, long id) throws Exception {
                      		Blob oldContent;
                      		try (
                      			AutoCloseable fake = (oldContent = loadOldContent(selectOldContent, id))::free;
                      			
                      			Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
                      				
                      			StringWriter newSchemeWriter = new StringWriter();
                      			
                      			ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
                          		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
                      		){
                      			oldContentToNewData(oldContentReader, newSchemeWriter, newZippedDataOutput);
                      			return new NewData(newSchemeWriter.toString(), newDataOutput.toByteArray());
                      		}
                      	}
                      	
                      	private void storeNewData(PreparedStatement insertNewContent, long id, String newScheme, byte[] newData) throws Exception {
                      		try (
                      			StringReader newSchemeReader = new StringReader(newScheme);
                          		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
                      		){
                      			insertNewContent.setLong(1, id);
                          		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
                          		insertNewContent.setBlob(3, newDataInput, newData.length);
                          		
                          		insertNewContent.executeUpdate();
                      		}
                      	}
                      	
                          public void migrate() throws Exception {
                          	List ids = loadIds();
                          	
                          	try (
                      			PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
                      	    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
                          	){
                          		for (Long id : ids) {    			
                          			NewData newData = generateNewDataFromOldContent(selectOldContent, id);
                          			storeNewData(insertNewContent, id, newData.scheme, newData.data);
                          		}
                          	}
                          }
                      }
                      

                      Да-да-да, именно, что вы и подумали: он воспользовался возможностью передачи метода. Код получился просто ужасный. Но зато не нужно писать обертки, подумал Боб и заплакал.

                      И тут он обратил внимание на аннотацию, которую так активно уже использовал: @RequiredArgsConstructor. Эврика! В библиотеке Lombok есть аннотация @Cleanup, которая как раз и рождена для того, чтобы утешить потерявшего всякие надежды Java программиста. Она на этапе компиляции добавляет в байт-код try-finally и автоматически добавляет код безопасного закрытия ресурсов. Более того, она умеет работать с любым методом освобождения ресурсов, будь то close(), free() или какой-нибудь другой, главное ей об этом подсказать (хотя она и сама умная и выругается, если не нашла подходящего метода).

                      И Боб переписал проблемные места с использованием @Cleanup:

                      public class MigratorV6  {
                      	
                      	private Connection conn; // Injected
                      	private SAXParser xmlParser; // Injected
                      	private XMLOutputFactory xmlFactory; // Injected
                      	
                      	@RequiredArgsConstructor
                      	private static class NewData {
                      		final String scheme;
                      		final byte[] data;
                      	}
                      	
                      	private List loadIds() throws Exception {
                      		List ids = new ArrayList<>();
                      		
                      		try (ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data")) {
                      			while (oldIdResult.next()) {
                      				ids.add(oldIdResult.getLong(1));
                      			}
                      		}
                      		
                      		return ids;
                      	}
                      	
                      	private Blob loadOldContent(PreparedStatement selectOldContent, long id) throws Exception {
                      		selectOldContent.setLong(1, id);
                      		
                      		try (ResultSet oldContentResult = selectOldContent.executeQuery()) {
                      			oldContentResult.next();
                      			
                      			return oldContentResult.getBlob(1);
                      		}
                      	}
                      	
                      	private void oldContentToNewData(Reader oldContentReader, StringWriter newSchemeWriter, GZIPOutputStream newZippedDataOutput) throws Exception {
                      		@Cleanup XMLStreamWriter newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter);
                      		@Cleanup XMLStreamWriter newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8");
                      		
                      		xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
                      			// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
                      		});
                      	}
                      	
                      	private NewData generateNewDataFromOldContent(PreparedStatement selectOldContent, long id) throws Exception {
                      		@Cleanup("free") Blob oldContent = loadOldContent(selectOldContent, id);
                      		
                      		try (
                      			Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
                      				
                      			StringWriter newSchemeWriter = new StringWriter();
                      			
                      			ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
                          		GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
                      		){
                      			oldContentToNewData(oldContentReader, newSchemeWriter, newZippedDataOutput);
                      			return new NewData(newSchemeWriter.toString(), newDataOutput.toByteArray());
                      		}
                      	}
                      	
                      	private void storeNewData(PreparedStatement insertNewContent, long id, String newScheme, byte[] newData) throws Exception {
                      		try (
                      			StringReader newSchemeReader = new StringReader(newScheme);
                          		ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
                      		){
                      			insertNewContent.setLong(1, id);
                          		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
                          		insertNewContent.setBlob(3, newDataInput, newData.length);
                          		
                          		insertNewContent.executeUpdate();
                      		}
                      	}
                      	
                          public void migrate() throws Exception {
                          	List ids = loadIds();
                          	
                          	try (
                      			PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
                      	    	PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
                          	){
                          		for (Long id : ids) {    			
                          			NewData newData = generateNewDataFromOldContent(selectOldContent, id);
                          			storeNewData(insertNewContent, id, newData.scheme, newData.data);
                          		}
                          	}
                          }
                      }
                      

                      Довольный найденным элегантным, и главное, из коробки, решением Боб сделал долгожданный коммит и отдал код на ревью.

                      Ничто не предвещало беды. Но неприятности всегда подстерегают нас за углом. Коммит не прошел ревью, Юг и Эндрю отнюдь не одобрили @Cleanup. Всего два места, где используются не AutoCloseable ресурсы, говорили они. Какой профит это нам даст? Нам не нравится эта аннотация! Как мы будем дебажить код в случае чего? И все в таком духе. Боб безжалостно отбивался, но все попытки были тщетны. И тогда он предпринял еще попытку доказать удобство и выкатил следующий код:

                      public class MigratorV7  {
                      	
                      	private Connection conn; // Injected
                      	private SAXParser xmlParser; // Injected
                      	private XMLOutputFactory xmlFactory; // Injected
                      	
                          public void migrate() throws Exception {
                          	@Cleanup PreparedStatement selectOldContent = conn.prepareStatement("select content from old_data where id = ?");
                          	@Cleanup PreparedStatement insertNewContent = conn.prepareStatement("insert into new_data (id, scheme, data) values (?, ?, ?)");
                          	
                          	@Cleanup ResultSet oldIdResult = conn.createStatement().executeQuery("select id from old_data");
                          	
                          	while (oldIdResult.next()) {
                          		long id = oldIdResult.getLong(1);
                          		
                          		selectOldContent.setLong(1, id);
                          		
                          		@Cleanup ResultSet oldContentResult = selectOldContent.executeQuery();
                              	oldContentResult.next();
                              	
                              	@Cleanup("free") Blob oldContent = oldContentResult.getBlob(1);
                              	@Cleanup Reader oldContentReader = new InputStreamReader(new GZIPInputStream(oldContent.getBinaryStream()));
                          		
                              	@Cleanup StringWriter newSchemeWriter = new StringWriter();
                              	@Cleanup XMLStreamWriter newSchemeXMLWriter = xmlFactory.createXMLStreamWriter(newSchemeWriter);
                          		
                              	ByteArrayOutputStream newDataOutput = new ByteArrayOutputStream();
                              	@Cleanup GZIPOutputStream newZippedDataOutput = new GZIPOutputStream(newDataOutput);
                              	@Cleanup XMLStreamWriter newDataXMLWriter = xmlFactory.createXMLStreamWriter(newZippedDataOutput, "utf-8");
                          		
                          		xmlParser.parse(new InputSource(oldContentReader), new DefaultHandler() {
                          			// Usage of schemeXMLWriter and dataXMLWriter to write XML into String and byte[]
                          		});
                          		
                          		String newScheme = newSchemeWriter.toString();
                          		byte[] newData = newDataOutput.toByteArray();
                          		
                          		@Cleanup StringReader newSchemeReader = new StringReader(newScheme);
                          		@Cleanup ByteArrayInputStream newDataInput = new ByteArrayInputStream(newData);
                          		
                          		insertNewContent.setLong(1, id);
                          		insertNewContent.setCharacterStream(2, newSchemeReader, newScheme.length());
                          		insertNewContent.setBlob(3, newDataInput, newData.length);
                          		
                          		insertNewContent.executeUpdate();
                          	}
                          }
                      }
                      

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

                      Но он снова не нашел поддержки. Как ни бился он о стену — стена оказалась прочней. И он сдался. В продакшен в итоге выкатили код MigratorV5 — тот самый, где так неуклюже используются возможности Java 8.

                      Эпилог.

                      Конечно, код который был приведен, далек от идеала, и его еще причесывать, какие-то вещи можно переписать совсем по-другому, например, с помощью шаблонного кода. Последний вариант является вообще воплощением процедурного стиля программирования, что не очень хорошо (но при этом понятно при чтении сверху-вниз). Но суть не в этом. @Cleanup — классная аннотация, которая помогает именно в таких моментах, когда мы не можем воспользоваться try-with-resources, она избавляет нас от излишней вложенности блоков кода одних в другие, если мы не разбиваем операции на методы. Ей не нужно увлекаться, но если необходимо, то почему нет?
                      Original source: habrahabr.ru (comments, light).

                      https://habrahabr.ru/post/339046/


                      Метки:  

                      Как довести первый проект до конца. Часть 2. Мифы, ошибки и провалы

                      Суббота, 30 Сентября 2017 г. 19:25 + в цитатник


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

                      И сейчас мы рассмотрим мифы и ошибки, на которых я споткнулся. И, хотя игру хвалили многие, она на момент написания статьи получила только 61 загрузку!

                      Ну вот как так может быть? Читать дальше ->

                      https://habrahabr.ru/post/339044/


                      Метки:  

                      [CppCon 2017] Бьёрн Страуструп: Изучение и преподавание современного C++

                      Суббота, 30 Сентября 2017 г. 19:13 + в цитатник
                      Fil сегодня в 19:13 Разработка

                      [CppCon 2017] Бьёрн Страуструп: Изучение и преподавание современного C++

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


                        Это первое вступительное видео. Оно не такое интересное для меня, но пропустить тоже не мог, это же Страуструп. Далее, текст от его лица. Заголовки взяты из слайдов.





                        Disclaimer: весь дальнейший текст — достаточно краткий пересказ, являющийся результом работы моего восприятия, и то, что я посчитал "водой" и проигнорировал, могло оказаться важным для вас. Иногда выступление было таким: "(важная мысль 1)(минута воды)(важная мысль 2)". Эти две мысли плавно перетекали друг в друга, а у меня получались довольно резкие скачки. Где можно сгладил, но посчитал нецелесообразным полностью причесывать текст, на это бы потребовалось много времени.


                        Вступление


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


                        Мы все учителя и мы все ученики


                        Зададимся вопросом кого мы учим, чему, зачем и как. Нужно делать это лучше. Я не критикую кого-то в частности, но чувствую, что мы должны делать это лучше. Не все из нас преподаватели, но тем не менее постоянно возникают случаи, когда мы занимаемся обучением. Например, рассказываем коллегам о последних фичах или даем советы. Общаемся на StackOverflow, Reddit, ведем блоги и т.д. Но нужно давать хорошие советы. Советы, которые двигают мир вперед.


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


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


                        Обучение программированию


                        Не нужно фокусироваться на языковых фичах. Например, вы встречали примеры в которых объясняется проблема приведения signed short к unsigned int [рассказывается о преподавании языка в общем, а не об особенностях C++]. Это неинтересно и можно увидеть в отладчике или прочитать в руководстве. Учите так, чтобы такая проблема не появлялась.


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


                        Одна из встечающихся проблем обучения C++ — то что язык изучается сам по себе, отдельно от библиотек. Вектор на 697 странице, sort через 100 страниц. Это учит, что stl скучная, сложная фигня. И в то же время: свой Linked List или Hash table это круто, круче чем stl.


                        Не будьте слишком умными


                        [в выступлении автор использует слово clever с негативным оттенком, что-то вроде человека, который пытается казаться быть умным]


                        Люди которые хотят и требуют "самое последнее" часто не знают основ. Пересмотрите основы.


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


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


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


                        Обучение программированию


                        Если изучать только сам язык, то попав в реальность можно просто "утонуть".
                        Используйте различные инструменты. Не только компилятор и учебник, но и IDE, отладчики, системы контроля версий, профилировщики, модульное тестирорвание, статические анализаторы, онлайн компиляторы. Интрументы должны быть современными (иногда получаю вопросы по Turbo C++ 4.0 :( )


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


                        Язык это не только синтаксис


                        Как мы часто учим? Объясняем язык плюс немного стандартную библиотеку. Без всякой графики, пользовательского интерфейса, веба, электронной почты, баз данных… И многие ученики считают, что C++ скучный бесполезный язык. Но это же не так, ведь такие вещи как браузеры, СУБД, САПР и прочие пишутся на C++. Перед началом лекции потратьте 5 минут о практическом применении.


                        Мы должны сделать лучше


                        Нам, сообществу C++, очень важно упростить начало работы, возможность пользоваться "прямо сейчас".


                        Как программирование похоже на занятие фотографией?


                        Как пользователи в различных отраслях разделяются на группы? Приведем пример с фотографией. Результат зависит от оборудования и от пользователя. Лично я новичок в фотографии. Большинство возможностей профессиональной фотокамеры будут для меня бесполезными. Она много весит, дорого стоит. Для нее существует множество аксессуаров в которых можно утонуть. Но с ее помощью можно делать превосходные фотографии, если потратить много времени на обучение. Аналогично существует много людей, которые не могут использовать разнообразные фичи языка и библиотеки.


                        Массовый рынок


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


                        Как-то во время преподавания мне было нужно, чтобы у студентов была установлена библиотека GUI. Оказалось, что установить одну и ту же библиотеку на студенческие Mac, Linux, Windows, весьма болезненно.


                        Языку нужна "система"


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


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


                        Какими должны быть основные дистрибутивы?


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


                        Модули помогут


                        База:


                        import bundle.entry_level; //Для новичков
                        import bundle.enthusiast_level; //Для продвинутых
                        import bundle.professional_level; //Для профессионалов

                        Расширения (которые не входят в базу):


                        import grahics.2d;
                        import grahics.3d;
                        import professional.graphics.3d;
                        import physlib.linear_algebra;
                        import boost.XML;
                        import 3rd_party.image_filtering;

                        Нужны хорошие пакетные менеджеры и системы сборки


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


                        > download gui_xyz
                        > install gui_xyz

                        Или эквивалетным способом, например в IDE:


                        import gui_xyz; //в коде

                        Современный C++


                        Мое видение современного C++ (как обычно):


                        • Статическая типобезопасность, хорошо определенные интерфейсы.
                        • Безопасность ресурсов (конструкторы/деструкторы, RAII).
                        • Абстракции без накладных расходов.
                        • Инкапсуляция, инварианты.
                        • Обобщенное программирование, шаблоны.
                        • Простота для большинства разработчиков, сложность скрыта в библиотеках.

                        Меняться трудно


                        Современный C++ это не C, Java, C++98 и не тот язык, на которым вы программировали 10 лет назад. Инерция — враг хорошего кода. Преподаватели, оправдывая неиспользование современных стандартов, говорят, что "мы так не делаем", "это не вставить в мою учебную программу", "может быть через 5 лет". У студентов появляется большее доверие к интернету, чем к преподавателям. Некоторые считают, что они умнее преподавателей, и иногда они правы. У меня стабильно каждый год на курсе были студенты, абсолютно убежденные, что они умнее меня в программировании. В этих частных случаях, я обоснованно уверен, что оне не правы [смех в зале].


                        Что такое современный С++?


                        • Лучшие практики, использующие текущий стандарт ISO С++
                        • Стремление к типо- и ресурснобезопасному коду

                        Для реализации этого 2 года назад был открыт проект C++ Core Guidelines. Он дает конкретные ответы на вопросы. У него много много участников, включая Microsoft и Red Hat.


                        Примеры кода


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


                        Улучшение кода


                        Всегда объясняйте причины. Например:


                        //1
                        int max = v.size();
                        for(int i = 0; i < max; ++i)
                        
                        //2
                        for (auto x : v)

                        Почему 2 лучше чем 1? Пример 2 явно показывает намерение, v может быть изменен без переписывания цикла, и менее подвержен ошибкам. Следует заметить, что 1 предоставляет более гибкие возможности. Но ведь goto еще более универсален, и поэтому мы избегаем его.


                        • I.4: Делайте интерфейсы точными и строготипизированными

                        [I.4 означает пункт из Core Guidelines]


                        void blink_led1(int time_to_blink) //Плохо - неясный тип
                        
                        void blink_led2(milliseconds time_to_blink) //Хорошо
                        
                        void use()
                        {
                          blink_led2(1500); //Ошибка: какая единица измерения?
                          blink_led2(1500ms);
                          blink_led2(1500s); //Ошибка: неверная единица измерения
                        }

                        [Здесь milliseconds какой-то простой тип не из библиотеки Chrono, поэтому последняя строчка приводит к ошибке. Ниже по тексту описано обобщение типа для единицы измерения, взятого из Chrono. Если интересно, можете почитать мое описание этой библиотеки]


                        template
                        void blink_led(duration time_to_blink)
                        {
                          auto ms_to_blink = duration_cast(time_to_blink);
                        }
                        
                        void use()
                        {
                          blink_led(2s);
                          blink_led(1500ms);
                        }

                        • ES.20: Всегда инициализируйте объект
                        • F.21: Для возврата нескольких значений из функций предпочитайте использовать tuple, структуру (или structured binding).

                        Error_code err; //неинициализировано: потенциальная проблема
                        //...
                        Channel ch = Channel::open(s, &err); //out-параметр: потенциальная проблема
                        if(err) { ... }
                        
                        Лучше:
                        auto [ch, err] = Channel::open(s) //structured binding
                        if(err) ...

                        А должен ли этот код использовать возврат двух параметров?


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

                        auto ch = Channel::open(s);

                        Лучше? Да, если неуспешное открытие было предусмотрено в программе.


                        Улучшение кода: не будьте слишком умными


                        Слово "умный" в контексте использования C++ — ругательное. Найдите баг:


                        istream& init_io()
                        {
                            if(argc > 1)
                                return *new istream { argv[1], "r" };
                            else
                                return cin;
                        }

                        • P.8: Не допускайте утечки
                        • R.11: Избегайте прямого вызова new и delete
                        • R.4: Raw-ссылка (T&) должны быть невладеющей

                        Комментирование


                        • P.1: Выражайте идеи прямо в коде
                        • NL.1: Не говорите в комментариях, что и так ясно видно в коде
                        • NL.2: Выражайте намерения в комментариях
                        • NL.3: Поддерживайте комментарии в актуальном состоянии

                        //Плохо
                        auto x = m * v1 + vv //Перемножение m с v1 и прибавление vv
                        
                        //Хорошо
                        void stable_sort(Sortable& c)
                        //cортирует "c" согласно порядку, задаваемым "<"
                        //сохраняет исходный порядок равных элементов (определяемыми "==")
                        {
                            //...несколько строк нетривиального кода
                        }

                        Philosophy rules


                        Я рекомендую вам отправиться на github и почитать раздел Philosophy rules, содержащий основные концепции.


                        Core guidelines


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


                        Сейчас разрабатываются 2 открытых проекта: анализатор, для проверки провил Core Guidelines, и библиотека GSL — guidelines support library (реализация от Microsoft).


                        No Garbage Collector!


                        Вопросы? Комментарии?


                        Нет, я расказал далеко не все про обучение. Лишь едва царапнул поверхность.


                        Вопросы из зала


                        [У Страуструпа есть сверхспособность отвечать по 5 минут на простые вопросы, поэтому я очень сильно сократил его ответы да и сами вопросы тоже]


                        Core Guidelines слишком всеобъемлющие. как учить?

                        Не нужно читать всё. Прочитать введение, затем раздел с философией. Не нужно искать правило, правило само найдет вас.


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

                        Да, нужны.


                        Вы говорили про 3 дистрибутива C++. Кто должен этим заниматься?

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


                        Моя дочь учится в колледже и мы вместе делали проект термостата. Так вот, для того, чтобы получить температуру и отобразить на экране, потребовался целый семестр изучения C++. Что вы думаете по этому поводу?

                        Да, есть такая проблема. С модулями будет лучше.


                        Нужно ли преподавать программирование как общий предмет, так же как математику

                        Я не компетентен в этом вопросе.


                        mmatrosov: вы говорили о том, что в обучении нужно пользоваться билиотеками. Не будет ли такого, что новое поколение программистов не будет знать основ?

                        Зависит от цели. Я учу студентов как реализовать вектор, они должны знать об указателях, но не каждому нужно реализовывать lock-free код.

                        Original source: habrahabr.ru (comments, light).

                        https://habrahabr.ru/post/339036/


                        Метки:  

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

                        Суббота, 30 Сентября 2017 г. 17:06 + в цитатник
                        Noobariouse сегодня в 17:06 Разработка

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

                          К посту: habrahabr.ru/post/338596

                          Это пост благодарности. В нем мы не будем писать много букв и приводить кучу сложных графиков. Мы хотим просто поблагодарить всех тех, кто лайкнул, оставил комментарий, да и вообще просто прочитал предыдущий пост. Также мы немного расскажем о том, как Хабрахабр сделал нас знаменитыми.

                          image

                          Вступление


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

                          Что насчет рекламы?


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

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

                          После этого статью моментально подхватило около 20 сайтов-агрегаторов новостей, которые очень быстро разнесли материал по интернету. Статья появилась в таких новостных гигантах, как Яндекс.Дзен и Flipboard, а также на многих других сайтах.

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

                          Что там с Google Play?


                          Также у нас была большая проблема с отзывами — их не было видно на главной странице игры, так как большинство игроков оставляли отзывы еще при бета-тестировании игры. А отзывы бета-теста на релизной версии приложения не видны. Из-за этого вид страницы на Google Play не привлекал новых пользователей, так как у них складывалось впечатление, что игра полнейший ноунейм. И тут участливые хабравчане тоже не остались в стороне.

                          Мы сидели и читали отзывы, которые приходили с Хабра каждые 5 минут примерно с таким видом:


                          Надо отметить, что у игры возрос средний рейтинг и перевалил за 4,5 звезды.
                          Из-за этого загрузки, а соответственно и показатели игры в общем рейтинге Гугла начали стремительно расти вверх.
                          К сравнению: если на момент публикации игры, мы на старте попали на 280 место в ТОПе новых приложений и на 260 место в ТОПе новых игр, то сейчас мы уже занимаем места в первой половине сотни в обоих топах.

                          Что в итоге? Наши планы на будущее.


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

                          Скажем сразу, что если у нас что-то и получится с созданием своего бизнеса, то мы 100% будем делать его в России, так как мы тут выросли. И мы очень благодарны нашей стране, которая дала нам образование, а также много-много возможностей. Ну и вообще, нам тут просто очень нравится.
                          И сейчас, во время сложной экономической ситуации в стране, нам кажется, что пришло самое время её отблагодарить, сделать что-то полезное в ответ. Мы хотим сделать то, что поможет России в такой непростой ситуации, возможно, даже привлечет в нее иностранных инвесторов.

                          Непосредственно благодарность!


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

                          Спасибо вам огромное!


                          image
                          Original source: habrahabr.ru (comments, light).

                          https://habrahabr.ru/post/339030/


                          Метки:  

                          [Из песочницы] Как запустить Java-приложение с несколькими версиями одной библиотеки в 2017 году

                          Суббота, 30 Сентября 2017 г. 16:07 + в цитатник
                          CyberSoft сегодня в 16:07 Разработка

                          Как запустить Java-приложение с несколькими версиями одной библиотеки в 2017 году

                          Как запустить Java-приложение с несколькими версиями одной библиотеки в 2017 году


                          КДПВ, ничего интересного


                          Хочу поделиться решениями одной проблемы, с которой мне пришлось столкнуться, плюс исследование данного вопроса в контексте Java 9.


                          дисклеймер

                          Писатель из меня ещё тот (пишу в первый раз), поэтому закидывание вкусными помидорами с указанием причин только приветствуется.
                          Сразу договоримся, что статья не годится в качестве руководства по:


                          • Java 9
                          • Elasticsearch
                          • Maven

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


                          Представим себе простую ситуацию: разворачиваем кластер Elasticsearch и загружаем в него данные. Мы пишем приложение, которое занимается поиском в этом кластере. Поскольку постоянно выходят новые версии Elasticsearch, мы привносим в кластер новые проблемы фичи с помощью rolling upgrade. Но вот незадача — в какой-то момент у нас сменился формат хранимых данных (например, чтобы максимально эффективно использовать какую-то из новых фич) и делать reindex нецелесообразно. Нам подойдёт такой вариант: ставим новый кластер на этих же машинах — первый кластер со старой схемой данных остаётся на месте только для поиска, а поступающие данные загружаем во второй с новой схемой. Тогда нашему поисковому компоненту потребуется держать на связи уже 2 кластера.


                          Наше приложение использует Java API для общения с кластером, а это значит, что оно тянет в зависимостях сам Elasticsearch. Стоит отметить, что вместе с 5-ой версией вышел и Rest Client, избавляющий нас от таких проблем (а также от удобного API самого Elasticsearch), но мы переместимся во времени на момент релиза 2-ой версии.


                          Рассмотрим возможные решения на примере простого приложения: поиск документа в 2-х кластерах Elasticsearch 1.7 и 2.4. Код доступен на гитхабе, и повторяет структуру данной статьи (отсутствует только OSGi).


                          Перейдём к делу. Создадим Maven-проект следующей структуры:


                          +---pom.xml
                          +---core/
                          |   +---pom.xml
                          |   +---src/
                          |       +---main/
                          |       |   +---java/
                          |       |   |   +---elasticsearch/
                          |       |   |       +---client/
                          |       |   |           +---SearchClient.java
                          |       |   |           +---Searcher.java
                          |       |   +---resources/
                          |       +---test/
                          |           +---java/
                          +---es-v1/
                          |   +---pom.xml
                          |   +---src/
                          |       +---main/
                          |       |   +---java/
                          |       |   |   +---elasticsearch/
                          |       |   |       +---client/
                          |       |   |           +---v1/
                          |       |   |               +---SearchClientImpl.java
                          |       |   +---resources/
                          |       +---test/
                          |           +---java/
                          +---es-v2/
                              +---pom.xml
                              +---src/
                                  +---main/
                                  |   +---java/
                                  |   |   +---elasticsearch/
                                  |   |       +---client/
                                  |   |           +---v2/
                                  |   |               +---SearchClientImpl.java
                                  |   +---resources/
                                  +---test/
                                      +---java/

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


                          • core — здесь находится вся логика приложения;
                          • можно (и нужно) вынести интерфейсы для взаимодействия с Elasticsearch в отдельный модуль;
                          • es-v1 — реализация интерфейса для Elasticsearch 1.7.5;
                          • es-v2 — реализация интерфейса для Elasticsearch 2.4.5.

                          Модуль core содержит класс Searcher, который является "испытателем" наших модулей es-v1 и es-v2:


                          public class Searcher {
                              public static void main(String[] args) throws Exception {
                                  List clients = Arrays.asList(
                                          getClient("1"),
                                          getClient("2")
                                  );
                                  for (SearchClient client : clients) {
                                      System.out.printf("Client for version: %s%n", client.getVersion());
                                      Map doc = client.search("test");
                                      System.out.println("Found doc:");
                                      System.out.println(doc);
                                      System.out.println();
                                  }
                                  clients.forEach(SearchClient::close);
                              }
                          
                              private static SearchClient getClient(String desiredVersion) throws Exception {
                                  return null; // см. далее
                              }
                          }

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


                          Взглянем на одну из реализаций, вторая почти идентична:


                          public class SearchClientImpl implements SearchClient {
                              private final Settings settings = ImmutableSettings.builder()
                                      .put("cluster.name", "es1")
                                      .put("node.name", "es1")
                                      .build();
                          
                              private final Client searchClient = new TransportClient(settings)
                                      .addTransportAddress(getAddress());
                          
                              private InetSocketTransportAddress getAddress() {
                                  return new InetSocketTransportAddress("127.0.0.1", 9301);
                              }
                          
                              @Override
                              public String getVersion() {
                                  return Version.CURRENT.number();
                              }
                          
                              @Override
                              public Map search(String term) {
                                  SearchResponse response = searchClient.prepareSearch("*")
                                          .setQuery(QueryBuilders.termQuery("field", term))
                                          .execute()
                                          .actionGet();
                                  if (response.getHits().getTotalHits() > 0) {
                                      return response.getHits().getAt(0).getSource();
                                  } else {
                                      return null;
                                  }
                              }
                          
                              @Override
                              public void close() {
                                  searchClient.close();
                              }
                          }

                          Тоже всё просто: текущая версия, зашитая в Elasticsearch, и поиск по полю field во всех индексах (*), возвращающий первый найденный документ, если есть.


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


                          Может быть, Class.forName?


                          Даже если вы не знаток Java, наверняка слышали, что там властвует ClassLoader. Он не позволит нам совершить задуманное, если оставить по умолчанию, поэтому такое решение влоб не сработает:


                          private static SearchClient getClient(String desiredVersion) throws Exception {
                              String className = String.format("elasticsearch.client.v%s.SearchClientImpl", desiredVersion);
                              return (SearchClient) Class.forName(className).newInstance();
                          }

                          Соберём, запустим и увидим результат… вполне неопределённый, например, такой:


                          Exception in thread "main" java.lang.IncompatibleClassChangeError: Implementing class
                              at java.lang.ClassLoader.defineClass1(Native Method)
                              at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
                              at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
                              at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
                              at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
                              at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
                              at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
                              at java.security.AccessController.doPrivileged(Native Method)
                              at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
                              at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
                              at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
                              at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
                              at java.lang.Class.forName0(Native Method)
                              at java.lang.Class.forName(Class.java:348)
                              at elasticsearch.client.Searcher.getClient(Searcher.java:28)
                              at elasticsearch.client.Searcher.main(Searcher.java:10)

                          Хотя мог и ClassNotFoundException бросить… или ещё чего...


                          Так как URLClassLoader найдёт и загрузит первый попавшийся класс с заданным именем из заданного набора jar-файлов и директорий, это будет необязательно требуемый класс. В данном случае эта ошибка возникает из-за того, что в списке class-path библиотека elasticsearch-2.4.5.jar идёт до elasticsearch-1.7.5.jar, поэтому все классы (которые совпадают по имени) будут загружены для 2.4.5. Поскольку наш Searcher сначала пытается загрузить модуль для Elasticsearch 1.7.5 (getClient("1")), URLClassLoader загрузит ему совсем не те классы...


                          Когда загрузчик классов имеет в своём распоряжении пересекающиеся по имени (а значит и по именам файлов) классы, такое его состояние называют jar hell (или class-path hell).


                          Свой ClassLoader


                          Становится очевидным, что модули и их зависимости нужно разнести по разным загрузчикам классов. Просто создаём URLClassLoader на каждый модуль es-v* и указываем каждому свою директорию с jar-файлами:


                          private static SearchClient getClient(String desiredVersion) throws Exception {
                              String className = String.format("elasticsearch.client.v%s.SearchClientImpl", desiredVersion);
                              Path moduleDependencies = Paths.get("modules", "es-v" + desiredVersion);
                              URL[] jars = Files.list(moduleDependencies)
                                      .map(Path::toUri)
                                      .map(Searcher::toURL)
                                      .toArray(URL[]::new);
                              ClassLoader classLoader = new URLClassLoader(jars); // parent = app's class loader
                              return (SearchClient) classLoader.loadClass(className).newInstance();
                          }

                          Нам нужно собрать и скопировать все модули в соответствующие директории modules/es-v*/, для этого используем плагин maven-dependency-plugin в модулях es-v1 и es-v2.


                          Соберём проект:


                          mvn package

                          И запустим:


                          сент. 29, 2017 10:37:08 ДП org.elasticsearch.plugins.PluginsService 
                          INFO: [es1] loaded [], sites []
                          сент. 29, 2017 10:37:12 ДП org.elasticsearch.plugins.PluginsService 
                          INFO: [es2] modules [], plugins [], sites []
                          Client for version: 1.7.5
                          Found doc:
                          {field=test 1}
                          
                          Client for version: 2.4.5
                          Found doc:
                          {field=test 2}

                          Бинго!


                          (1.7 не сработает под JRE 9)

                          если не пропатчить JvmInfo, о чём упоминается ниже в пересборке Elasticsearch 1.7.


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


                          1. Invoke findLoadedClass(String) to check if the class has already been loaded.
                          2. Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead.
                          3. Invoke the findClass(String) method to find the class.

                          То есть в этом случае будут загружены классы Elasticsearch из core, а не es-v*. Присмотревшись к порядку загрузки, видим обходной вариант: написать свой загрузчик классов, который нарушает этот порядок, поменяв местами шаги 2 и 3. Такой загрузчик сможет загрузить не только отдельно свой модуль es-v*, но и увидит классы из core.


                          Напишем свой URLClassLoader, назовём его, например, ParentLastURLClassLoader:


                          public class ParentLastURLClassLoader extends URLClassLoader {
                              ...
                          }

                          и переопределим loadClass(String,boolean), скопировав код из ClassLoader и убрав всё лишнее:


                          @Override
                          protected Class c = findLoadedClass(name);
                                  if (c == null) {
                                      try {
                                          if (getParent() != null) {
                                              c = getParent().loadClass(name);
                                          }
                                      } catch (ClassNotFoundException e) {
                                      }
                          
                                      if (c == null) {
                                          c = findClass(name);
                                      }
                                  }
                                  if (resolve) {
                                      resolveClass(c);
                                  }
                                  return c;
                              }
                          }

                          Меняем местами вызовы getParent().loadClass(String) и findClass(String) и получаем:


                          @Override
                          protected Class c = findLoadedClass(name);
                                  if (c == null) {
                                      try {
                                          c = findClass(name);
                                      } catch (ClassNotFoundException ignored) {
                                      }
                          
                                      if (c == null) {
                                          c = getParent().loadClass(name);
                                          if(c == null) {
                                              throw new ClassNotFoundException(name);
                                          }
                                      }
                                  }
                                  if (resolve) {
                                      resolveClass(c);
                                  }
                                  return c;
                              }
                          }

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


                          Загрузчик написан, теперь используем его, заменив URLClassLoader в методе getClient(String):


                          ClassLoader classLoader = new URLClassLoader(jars);

                          на ParentLastURLClassLoader:


                          ClassLoader classLoader = new ParentLastClassLoader(jars);

                          и запустив приложение снова, видим тот же результат:


                          сент. 29, 2017 10:42:41 ДП org.elasticsearch.plugins.PluginsService 
                          INFO: [es1] loaded [], sites []
                          сент. 29, 2017 10:42:44 ДП org.elasticsearch.plugins.PluginsService 
                          INFO: [es2] modules [], plugins [], sites []
                          Client for version: 1.7.5
                          Found doc:
                          {field=test 1}
                          
                          Client for version: 2.4.5
                          Found doc:
                          {field=test 2}

                          ServiceLoader API


                          В Java 6 добавили класс java.util.ServiceLoader, который предоставляет механизм загрузки реализаций по интерфейсу/абстрактному классу. Этот класс тоже решает нашу проблему:


                          private static SearchClient getClient(String desiredVersion) throws Exception {
                              Path moduleDependencies = Paths.get("modules", "es-v" + desiredVersion);
                              URL[] jars = Files.list(moduleDependencies)
                                      .map(Path::toUri)
                                      .map(Searcher::toURL)
                                      .toArray(URL[]::new);
                              ServiceLoader serviceLoader = ServiceLoader.load(SearchClient.class, new URLClassLoader(jars));
                              return serviceLoader.iterator().next();
                          }

                          Всё очень просто:


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

                          Чтобы ServiceLoader увидел реализации интерфейсов, нужно создать файлы с полным именем интерфейса в директории META-INF/services:


                          +---es-v1/
                          |   +---src/
                          |       +---main/
                          |           +---resources/
                          |               +---META-INF/
                          |                   +---services/
                          |                       +---elasticsearch.client.spi.SearchClient
                          +---es-v2/
                              +---src/
                                  +---main/
                                      +---resources/
                                          +---META-INF/
                                              +---services/
                                                  +---elasticsearch.client.spi.SearchClient

                          и написать полные имена классов, реализующих этот интерфейс, на каждой строке: в нашем случае по одному SearchClientImpl на каждый модуль.
                          Для es-v1 в файле будет строка:


                          elasticsearch.client.v1.SearchClientImpl

                          и для es-v2:


                          elasticsearch.client.v2.SearchClientImpl

                          Также используем плагин maven-dependency-plugin для копирования модулей es-v* в modules/es-v*/. Пересоберём проект:


                          mvn clean package

                          И запустим:


                          сент. 29, 2017 10:50:17 ДП org.elasticsearch.plugins.PluginsService 
                          INFO: [es1] loaded [], sites []
                          сент. 29, 2017 10:50:20 ДП org.elasticsearch.plugins.PluginsService 
                          INFO: [es2] modules [], plugins [], sites []
                          Client for version: 1.7.5
                          Found doc:
                          {field=test 1}
                          
                          Client for version: 2.4.5
                          Found doc:
                          {field=test 2}

                          Отлично, требуемый результат снова получен.


                          Для упомянутого хардкорного случая придётся выносить интерфейс SearchClient в отдельный модуль spi и использовать следующую цепочку загрузчиков:


                          core:  bootstrap -> system
                          spi:   bootstrap -> spi
                          es-v1: bootstrap -> spi -> es-v1
                          es-v2: bootstrap -> spi -> es-v2

                          т.е.


                          1. создаём отдельный загрузчик для spi (в parent кидаем null — будет использован bootstrap-загрузчик),
                          2. загружаем им spi (интерфейс SearchClient),
                          3. затем создаём по загрузчику на каждый модуль es-v*, у которых родительским будет загрузчик для spi,
                          4. ...
                          5. PROFIT!

                          Модули OSGi


                          Признаюсь сразу и честно — мне не доводилось сталкиваться с фреймворками OSGi (может оно и к лучшему?). Взглянув на маны для начинающих у Apache Felix и Eclipse Equinox, они больше походят на контейнеры, в которые (вручную?) загружают бандлы. Даже если есть реализации для встраивания, это слишком громоздко для нашего простого приложения. Если я не прав, выскажите обратную точку зрения в комментариях (да и вообще хочется увидеть, что его кто-то использует и как).


                          Я не стал углубляться в этом вопросе, т.к. в Java 9 модули теперь из коробки, которые мы сейчас и рассмотрим.


                          Нативные модули в Java?


                          На прошлой неделе релизнулась 9-ая версия платформы, в которой главным нововведением стали модуляризация рантайма и исходников самой платформы. Как раз то, что нам надо!


                          Hint: Для того, чтобы использовать модули, нужно сначала скачать и установить JDK 9, если вы ещё этого не делали.


                          Здесь дело осложняет только способность используемых библиотек запускаться под девяткой в качестве модулей (на самом деле, я просто не нашёл способа в IntelliJ IDEA указать class-path вместе с module-path, поэтому далее мы всё делаем в контексте module-path).


                          Как работает модульная система


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


                          Кроме упомянутых модулей, есть ещё слои, содержащие их. При старте приложения создаётся слой boot, в который загружаются все указанные в --module-path модули, из которых состоит приложение, и их зависимости (от модуля java.base автоматически зависят все модули). Другие слои могут быть созданы программно.


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


                          Сами модули изолированы друг от друга и по умолчанию их пакеты и классы в них не видны другим модулям. Дескриптор модуля (им является module-info.java) позволяет указать, какие пакеты может открыть каждый модуль и от каких модулей и их пакетов зависят они сами. Дополнительно, модули могут объявлять о том, что они используют некоторые интерфейсы в своей работе, и могут объявлять о доступной реализации этих интерфейсов. Эта информация используется ServiceLoader API для загрузки реализаций интефрейсов из модулей.


                          Модули бывают явными и автоматическими (типов больше, но мы ограничимся этими):


                          • Явные модули описываются явно файлом-дескриптором module-info.class в корне jar-архиа (модуль или модуляризованный jar),
                          • Автоматические модули — это библиотеки без дескриптора, помещённые в module-path; в качестве модулей такого типа предполагается использовать существующие немодуляризованные библиотеки, используемые в старом добром class-path.

                          Теперь этой информации будет достаточно, чтобы применить её на нашем проекте:


                          1. В boot-слое у нас будет только модуль core. Если в нём будут находиться модули es-v*, то приложение не запустится из-за конфликтующих транзитивных модулей elasticsearch.shaded.
                          2. Класс Searcher будет вручную загружать модули es-v* в отдельные дочерние слои со своим загрузчиком классов, используя ServiceLoader API.

                          Всё так просто?...


                          Package Hell


                          Модулям не разрешено иметь пересекающиеся имена пакетов (по крайней мере в одном слое).


                          Например, есть некая библиотека, которая предоставляет какое-то API в публичном классе Functions. В этой библиотеке есть класс Helpers с пакетной областью видимости. Вот они:


                          com.foo.bar
                              public class Functions
                              class Helpers

                          На сцену выходит вторая библиотека, которая предоставляет которая дополняет функционал первой:


                          com.foo.baz
                              public class Additional

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


                          com.foo.baz
                              public class Additional
                          com.foo.bar
                              public class AccessorToHelpers

                          Поздравим себя — мы только что создали себе проблему разделения пакетов (split package) с точки зрения модульной системы. Что можно сделать с такими библиотеками? Нам предлагают оставить такие библиотеки в class-path и дотянуться до них из модулей, используя автоматические модули в качестве моста. Но мы не ищем лёгких путей, поэтому используем другой вариант: докладём в библиотеку все его зависимости и получим один единственный jar-архив (известный под названиями fat jar и uber jar), его-то и можно использовать в module-path как автоматический модуль, минуя class-path. Проблемой может стать сборка такого all-in-one jar.


                          Инструкция по пересборке Elasticsearch

                          Elasticsearch активно использует доступ к package-private методам/полям для доступа к некоторому функционалу Lucene. Чтобы использовать его в виде автоматического модуля, сделаем из него uber jar и установим в локальный репозиторий под именем elasticsaerch-shaded для дальнейшего использования в нашем проекте.


                          Собираем Elasticsearch 1.7


                          В первых версиях проект приложения представляет из себя единственный Maven-модуль, поэтому здесь проблем особо не возникнет: нужно поправить pom.xml и некоторые классы, если собираем 8-кой.


                          Клонируем репозиторий в какую-нибудь директорию, чекаутим тег v1.7.5 и начинаем править:


                          • В проекте уже используется maven-shade-plugin, поэтоу для сборки uberjar потребуется закомментировать включение некоторых пакетов, чтобы включались все:


                          и желательно в оригинале, без перемещений:



                          • Придётся убрать Groovy (ломает загрузку из-за такой неоднозначности), а также логгеры (для них нет конфигов, JUL будет работать прекрасно по умолчанию), добавив сразу за закоментированным узлом :

                          
                              org.codehaus.groovy:groovy-all
                              org.slf4j:*
                              log4j:*
                          

                          • Выключим вырезание неиспользуемых классов — плагин не знает о ServiceLoader/Reflection API:


                          • И добавим склеивание сервис-файлов с классами реализаций для ServiceLoader API в узел плагина:

                          
                              
                          

                          • С pom.xml закончили, осталось устранить UnsupportedOperationException, которое кидает java.lang.management.RuntimeMXBean#getBootClassPath. Для этого найдём такую строку в классе JvmInfo:

                          info.bootClassPath = runtimeMXBean.getBootClassPath();

                          и оборнём её в "правильные":


                          if (runtimeMXBean.isBootClassPathSupported()) {
                              info.bootClassPath = runtimeMXBean.getBootClassPath();
                          } else {
                              info.bootClassPath = "";
                          }

                          Эта информация используется всего лишь для статистики.


                          Готово, теперь можно собрать jar:


                          $ mvn package

                          и после компиляции и сборки получим требуемый elasticsearch-1.7.5.jar в директории target. Теперь его нужно установить в локальный репозиторий, например, под именем elasticsearch-shaded:


                          $ mvn install:install-file \
                          > -Dfile=elasticsearch-1.7.5.jar \
                          > -DgroupId=org.elasticsearch \
                          > -DartifactId=elasticsearch-shaded \
                          > -Dversion=1.7.5 \
                          > -Dpackaging=jar \
                          > -DgeneratePom=true

                          Теперь этот артефакт можно использовать как автоматический модуль в нашем Maven-модуле es-v1:


                          
                              
                                  org.elasticsearch
                                  elasticsearch-shaded
                                  1.7.5
                              
                              ...
                          

                          Собираем Elasticsearch 2.4


                          Откатим локальные изменения и зачекаутим тег v2.4.5. Начиная со 2-ой версии проект разбит на модули. Нужный нам модуль, выдающий elasticsearch-2.4.5.jar — модуль core.


                          • Первым делом уберем снапшот, нам нужен релиз:

                          $ mvn versions:set -DnewVersion=2.4.5

                          • Теперь поищем, настроен ли где shade-плагин… и натыкаемся на такую доку:

                          Shading and package relocation removed



                          Elasticsearch used to shade its dependencies and to relocate packages. We no longer use shading or relocation.
                          You might need to change your imports to the original package names:
                          • com.google.common was org.elasticsearch.common
                          • com.carrotsearch.hppc was org.elasticsearch.common.hppc
                          • jsr166e was org.elasticsearch.common.util.concurrent.jsr166e
                            ...

                          Нам придётся добавить shade-плагин заново в модуль core, добавив в настройках трансформер сервис-файлов и исключение логгеров:


                          
                              org.apache.maven.plugins
                              maven-shade-plugin
                              
                                  
                                      package
                                      
                                          shade
                                      
                                  
                              
                              
                                  
                                      
                                  
                                  
                                      
                                          org.slf4j:*
                                          log4j:*
                                      
                                  
                              
                          

                          • Уберём зависимость com.twitter:jsr166e (там используется sun.misc.Unsafe, которого в 9-ке "нет") у модуля core:


                          и сменим импорты com.twitter.jsr166e на java.util.concurrent.atomic.


                          • Плагин animal-sniffer-maven-plugin задетектит изменение на предыдущем шаге (в 7-ке нет jsr166e), убираем:


                          Готово, теперь проделываем те же шаги по сборке и установке, что и для es-v1, с небольшими отличиями:


                          • поменять версию на 2.4.5,
                          • достаточно собрать только модуль core:

                          $ mvn clean package -pl org.elasticsearch:parent,org.elasticsearch:elasticsearch -DskipTests=true

                          Модули в нашем проекте


                          После того, как мы выяснили, что используемые нами библиотеки способны работать в модульной системе Java, сделаем из наших Maven-модулей явные Java-модули. Для этого нам потребуется в каждой директории с исходниками (src/main/java) создать файлы module-info.java и в них описать взаимоотношения между модулями.


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


                          // имя модуля - elasticsearch.client.core
                          module elasticsearch.client.core {
                              // очевидно, нужно открыть пакет с интерфейсом,
                              // чтобы он был доступен для модулей
                              exports elasticsearch.client.spi; 
                              // и этим модулям скажем, что мы используем интерфейс SearchClient
                              // для динамической загрузки ServiceLoader'ом
                              uses elasticsearch.client.spi.SearchClient;
                          }

                          Для модулей es-v1 и es-v2 будет похожее описание:


                          • они используют модуль elasticsearch.client.core, т.к. в нём находится интерфейс,
                          • каждый использует автоматический модуль Elasticsearch требуемой версии,
                          • и каждый модуль говорит, что он предоставляет реализацию для интерфейса SearchClient.

                          Итого имеем для es-v1:


                          // имя модуля - elasticsearch.client.v1
                          module elasticsearch.client.v1 {
                              // в core лежит SearchClient
                              requires elasticsearch.client.core;
                              // этот автоматический
                              requires elasticsearch.shaded;
                              // говорим модулю core, что у нас есть реализация его интерфейса
                              provides elasticsearch.client.spi.SearchClient with elasticsearch.client.v1.SearchClientImpl;
                          }

                          Для es-v2 почти всё тоже самое, только в именах должна фигурировать версия v2.


                          Теперь как загрузить такие модули? Ответ на этот вопрос есть в описании класса ModuleLayer, который содержит небольшой пример загрузки модуля с ФС. Предположив, что модули es-v* находятся каждый всё в тех же директориях modules/es-v*/, можно написать примерно такую реализацию:


                          private static SearchClient getClient(String desiredVersion) throws Exception {
                              Path modPath = Paths.get("modules", "es-v" + desiredVersion);
                              ModuleFinder moduleFinder = ModuleFinder.of(modPath);
                              ModuleLayer parent = ModuleLayer.boot();
                              Configuration config = parent.configuration().resolve(moduleFinder, ModuleFinder.of(), Set.of("elasticsearch.client.v" + desiredVersion));
                              ModuleLayer moduleLayer = parent.defineModulesWithOneLoader(config, Thread.currentThread().getContextClassLoader());
                              ServiceLoader serviceLoader = ServiceLoader.load(moduleLayer, SearchClient.class);
                              Optional searchClient = serviceLoader.findFirst();
                              if (searchClient.isPresent()) {
                                  return searchClient.get();
                              }
                              throw new Exception("Module 'elasticsearch.client.v" + desiredVersion + "' not found on " + modPath);
                          }

                          ModuleLayer#defineModulesWithManyLoaders нам здесь не подойдёт, так как у нас получатся совсем изолированные модули и наши es-v* не смогут увидеть свои зависимости.


                          Итак, теперь нужно собрать все модули. Для компиляции потребуется плагин maven-compiler-plugin поновее, на данный момент последняя версия — 3.7.0:


                          
                              org.apache.maven.plugins
                              maven-compiler-plugin
                              3.7.0
                          

                          Укажем Java 9 для исходников:


                          
                              1.9
                              1.9
                          

                          И не забываем про maven-dependency-plugin, поэтому пересоберём модули:


                          $ mvn clean package

                          Теперь можно запустить, и видим такой же вывод:


                          сент. 29, 2017 10:59:01 ДП org.elasticsearch.plugins.PluginsService 
                          INFO: [es1] loaded [], sites []
                          сент. 29, 2017 10:59:04 ДП org.elasticsearch.plugins.PluginsService 
                          INFO: [es2] modules [], plugins [], sites []
                          Client for version: 1.7.5
                          Found doc:
                          {field=test 1}
                          
                          Client for version: 2.4.5
                          Found doc:
                          {field=test 2}

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


                          Конец


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


                          • построить свой велосипед с ClassLoader — может подойти там, где сильно хочется проблем не хочется тащить библиотеки и разбивать на модули (можно извернуться и сделать всё в одном),
                          • использовать ServiceLoader API — если не мешают META-INF/services,
                          • использовать готовые сторонние библиотеки, которые мы не рассматривали (например) — всё удобство готового и протестированного кода и подросшего бандла
                          • использовать мощную систему модулей Java 9 — если переход позволителен или вообще проект начинается с чистого листа.

                          P.S. Желаю всем джавистам удачного перехода на Java 9!

                          Original source: habrahabr.ru (comments, light).

                          https://habrahabr.ru/post/339026/


                          Метки:  

                          Поиск сообщений в rss_rss_hh_new
                          Страницы: 1437 ... 1167 1166 [1165] 1164 1163 ..
                          .. 1 Календарь