• Map как замена циклу

    Я нашел, что отказ от циклов в пользу map улучшает код сразу по нескольким критериям, а заодно вправляет мозги. Код с мапами короче, меньше подвержен ошибкам, его легче поддерживать.

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

    Map принимает функцию и коллекцию. Функция обрабатывает один элемент. Результат map – коллекция результатов функции на множестве входных данных.

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

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

    Меньше кода

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

    results = map(do_stuff, my_data_list)
    

    и

    results = []
    for x in my_data_list:
        res = do_stuff(x)
        results.append(res)
    

    Очевидно, первый вариант короче и не засоряет пространство лишними переменными. Строку с промежуточным результатом res можно опустить, но тогда следующая строка results.append(do_stuff(x)) станет сложнее. Две операции на одну строку – не желательно.

    Сдвиг кода и логические прыжки

    Цикл – это дополнительный сдвиг кода. ООП уже дает сдвиги для классов и методов, а тут еще циклы. При этом нужно бегать глазами к переменным-спискам, объявленным до цикла. Map не делает сдвига и создает переменные по требованию.

    Декларативный код безопасней

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

    Дополнительный уровень абстракции

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

    Если у нас неизменяемые коллекции и транзакционная память, интерпретатор раскидает задачу по ядрам. Имея 1000 элементов и 4 ядра, получим по 250 итераций на одно ядро. Описать это циклом – нетривиальная задача. Насколько я знаю, Clojure и некоторые реализации Haskell параллелят map при заданных флагах компиляции.

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

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

    url_list = ["http://foo1.com", "http://foo2.com", ...]
    
    def get_data(url):
        return requests.get(url, timeout=5).json()
    
    data_list = map(get_data, url_list)
    

    Теперь напишем свой map с использованием тредов:

    from multiprocessing.dummy import Pool
    
    def map_threaded(func, *seq):
        pool = Pool(processes=16)
        return pool.map(func, *seq)
    
    data_list = map_threaded(get_data, url_list)
    

    Всего лишь поменяли map на map_threaded, но какая большая разница в реализации! Был цикл, а стали треды, но сигнатуры одинаковые. Теперь на всю операцию понадобится время, равное самому долгому запросу.

    (Да, это пул тредов не смотря на название модуля.)

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

    Мощь Гугла – это комбинация функций Map и Reduce. Инженеры Гугла дробят задачи на подзадачи и эффективно исполняют их на тысячах серверов. Технология работает в масштабах планеты. Под капотом может быть скучный ООП-код на Java, но Map и Reduce – абстракция высочайшего порядка. Благодаря им Гугл стал тем, что есть сейчас.

    Контроль за разрастанием цикла

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

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

    Бывает, мы добавляем костыли в цикл, потому что на носу релиз, и возиться некогда. Часто в ревью мы видим пару строчек:

      for item in items:
    +     if not item.check_foo():
    +         continue
      ...
    

    которые ни у кого не вызывают подозрений. Ну, пропускаешь шаг, все понятно.

    Это плохо.

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

    Отрефакторить цикл, в котором полно if, continue, try/catch очень трудно. Все завязано на глобальные списки, объявленные до входа в цикл. Оператор continue я считаю дурным тоном. Мы должны отделять котлеты от мух до входа в итерацию.

    Map исключает условия и пропуск элементов. Целевая функция не может сказать “этот обработать не могу, дай-ка лучше следующий”. Список следует предварительно очистить функцией filter.

    Неверное прохождение слоев бизнес-логики

    Мы часто работаем со списками объектов. Типичная задача – извлечь данные из базы и выполнить для каждого ряд действий. Например, действие1, действие2 и действие3.

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

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

    Повышенные требования к коду

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

    Не каждый готов дать эту гарантию. Часто мы видим такое:

    result = []
    for item in items:
        try:
            data = process_item(item)
            res = process_data(data)
            result.append(res)
        except Exception as e:
            logger.except(e)
            continue
    

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

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

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

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

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

  • Записи докладов с четвертой встречи любителей рефакторить

    Провели четвертую встречу!

    Роман Гребенников выступил с докладом о распределенных системах:

    Евгений Рыжков рассказал о тонкостях манипуляций:

    Слайды

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

  • Идиотизм вокруг NPM

    Как и другие разработчики, я с интересом наблюдаю за скандалом вокруг экосистемы Node.js и пакетного менеджера NPM.

    Если вы не в курсе дела, смотрите ссылки в конце поста. А я напомню, что у Node-разработчиков прошлая неделя прошла в боли и страданиях. Программист Азер Кочулу удалил из NPM свои 100500 пакетов, том числе один аж на 11 строк. Без него не смогли собраться React, Babel и куча всего.

    Азер удалил код потому, что администрация NPM в одностороннем порядке передала права на пакет Kik от Азера к представителям одноименной компании. Якобы юристы Kik запугали патентами и торговой маркой.

    Развязка: Азер перенес все пакеты в Гитхаб, администрация NPM запретила удалять пакеты по истечении 24 часов в момента публикации. Азер на коне, он весь такой Д’Артаньян в белых перчатках, в Твиттере пафосные картинки.

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

    Имеем три стороны – Азер, администрация NPM и юристы компании Kik. Кто неправ в этой истории?

    Первые два. Объясню с конца.

    Юристы Kik действуют в своих интересах. Они стараются расширить экспансию вверенного им продукта. Совершенно естественно, что их цель – захватить имя Kik разных его проявлениях.

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

    Азер обозвал их мудаками и послал на хуй (как было, так и перевел). А в начале написал “Ха-ха”. Кто-то сомневается в неадекватности пациента? Взрослый же человек, просто откажи, зачем оскорблять людей?

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

    Кто-то думает, что NPM зассали перед патентом на торговую марку “Kik”, а на самом деле все просто – одни из основателей NPM работал в Kik раньше. Это банальный блат, работает не только в России.

    Поэтому администрация NPM – тоже мудаки.

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

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

    Администрация NPM, к их чести, вынесла правильный урок. Теперь запрещено удалять пакеты, с момента публикаций которых прошло больше 24 часов. Это правильно, потому что если разработчик выложил код, то значит, он предлагает его другим, верно? Иначе зачем выкладывать, ставь из приватного репозитория.

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

    Давайте окинем быстрым взглядом 250 пакетов Азера. Двести пятьдесят, Карл, как пишут в интернете.

    • run-paralelly – 11 комитов, 46 строк.
    • left-pad, из-за которого сломался интернет. 11 комитов, 11 значимых строк. 660 звезд.
    • flat-glob – 6 комитов, 49 строк. Прогресс.
    • get-object-path – 9 комитов, 21 строка.
    • rnd – 1 комит, 8 строк.
    • random-color – 7 комитов, 8 строк.
    • route-map – 6 комитов, 55 строк.

    Ну ты понел.

    В интернете точно пошутили, что чуваку нужен не Гитхаб, а Твиттер. Каждый твит – новый пакет. Большая часть уместится в 140 символов, если переменные укоротить. А “тяжелые” проекты на 50 строк и больше декомпозировать и подключить зависимостями.

    Блин, у него декларация package.json занимает больше места, чем код.

    Что мешает этому Азеру создать пакет azer-tools и наполнять его утилитами? Принцип “один пакет – одна функция” это не глупость, a рак мозга. Я не видел такого ни в одном другом языке.

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

    А ведь я помню поток статей в духе “Node.js спасет мир” пять лет назад. 2016 на дворе, а в сообществе сканалы на уровне Дома-2.

    На закуску – статья Теда Дзюбы “Node.Js Is Cancer”. На Хабре был перевод.

    Ссылки:

  • Что значит код как данные

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

    Предположим, кто-то осваивает Лисп и делает задачки вроде факториала и рекурсии. Частенько приходится записывать числовые выражения вроде (x + y) * (a / b). В Лиспе пишут польской нотацией, то есть выражение выглядит так:

    (* (+ x y)
       (/ a b))
    

    Не очень привычно. Было бы здорово, думает студент, написать функцию, которая принимает выражение как удобно мне, а возвращала то, что нужно Лиспу. Начинает писать функцию expr, что-то вроде (expr (x + y) * (a / b)).

    Ничто не предвещает беды. Но вот облом: Лисп, как и другие ЯП, вычисляет аргументы функции до входа в нее. А выражение (x + y)... ошибочно с точки зрения и семантики, и синтаксиса, поэтому даже до вызова функции expr дело не дойдет. Что же делать?

    Помогут макросы.

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

    Короткими словами, макросу можно скормить полную дичь: крестики-нолики, математическое выражение, кусок кода на любом языке, asii-арт. Внутри макроса это станет списком. Задача макроса – перестроить дичь в правильный список, который вычислит Лисп.

    Именно в этот момент срабатывает срабатывает принцип код-как-данные. Когда мы пишем (expr (x + y) * (a / b)), для нас это код. Но внутри макроса это список! Нулевой элемент списка – открывающая скобочка, первый – символ x, затем символ плюсика, и так до последней закрывающей скобочки.

    Рассмотрим макрос для вычисления выражений из двух операндов. Он вычислит простейшие вещи вроде 3 + 2, 2 * 10, -3 / 2:

    (defmacro expr [v1 op v2]
      `(~op ~v1 ~v2))
    
    (expr 2 + 2)
    >>> 4
    (expr 2 / 2)
    >>> 1
    

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

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

    PS: Кроме Лиспа, я знаю только один язык со схожим поведенем. Это Tcl (Тикль) – в котором, грубо говоря, все является строкой. Тикль до сих пор популярен на производстве: заводах, CAD-системах. Нефтяные платформы Shell работают под управлением системы, написанной на Тикле.

  • О техзадании

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

    Теперь длинное объяснение.

    Что такое ТЗ? Это документ, в котором бизнес-требования описаны техническим языком. Упоминаются протоколы, стандарты, меры безопасности. Четко описаны процессы обмена данными.

    Когда программист просит ТЗ, он не понимает, что требует невозможного. У заказчика не может быть правильно составленного технического документа. Все, что у него есть – это обрывки идей будущего бизнеса.

    Задача программиста – помочь оформить бизнес-идеи в техническую форму. Заказчик не может сделать это сам. Он не знает разницу между HTTP и HTTPS, GET и POST, и чем стандартная авторизация отличается от OAuth 2.0.

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

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

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

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

    Описанное выше можно изобразить на графике. Вот как протекает сотрудничество во времени. Программист думает:

    |ТЗ       |код       |код        |код     |код      |код         |готово
    >------------------------------------------------------------------------>
                                     время
    

    На самом деле:

    |вопросы    |вопросы   |вопросы   |ТЗ     |код    |код   |код     |готово
    >------------------------------------------------------------------------>
                                     время
    

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

    А вот что будет, если следовать первому графику.

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

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

    • Заказчик: Форма авторизации как-то не очень…
    • Программист: Все сделано по техзаданию.
    • Заказчик: Да, но нет авторизации через соцсети.
    • Программист: Этого не было в ТЗ.
    • Заказчик: Не было, потому что сейчас это везде, я думал, вы догадаетесь.
    • Программист: Вы не отразили это техзадании.
    • Заказчик: Что вы все с техзаданием! У нас будет меньше пользователей!
    • Программист (мысленно): Вот мудак, не знает, что хочет.
    • Заказчик (думает): Послал же бог дурака. Только по бумажке работает, мозга ноль.

    И так до следующей встречи. С новым заказчиком и исполнителем. Драмы и плач на форумах.

    Выводы

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

  • Деструктивный синтаксис в функциях

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

    Деструктивный синтаксис пришел, как все лучшее, из мира функционального программирования. В Питоне он известен как “распаковка кортежа” на составные переменные.

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

    Простой пример:

    point = (1, 2)
    x, y = point
    

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

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

    Например, работаем с точками. Есть две точки, нужно вычислить декартово расстояние. Решение в лоб:

    point1 = (1, 2)
    point2 = (3, 4)
    
    def distance(p1, p2):
        x1, y1 = p1
        x2, y2 = p2
        ...
    
    print distance(p1, p2)
    

    Хорошо, но лишние строки на распаковку. Решение в ООП-стиле:

    class Point(object):
    
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        def distance(self, other):
            # use self.x, self.y, other.x, other.y
            # to refer variables
            ...
    
    p1 = Point(1, 2)
    p2 = Point(3, 4)
    print p1.distance(p2)
    

    Лишние строки на класс. К тому же, я забыл добавить метод __str__ и во время дебага увижу что-то вроде <object Point at 0x523fw523> вместо чисел. Матерясь, полезу дописывать метод и только увеличу энтропию.

    Самая простая, и потому лучшая реализация:

    point1 = (1, 2)
    point2 = (3, 4)
    
    def distance((x1, y1), (x2, y2)):
        ...
    
    print distance(p1, p2)
    

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

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

    Деструктивный синтаксис следует принципу “явное лучше неявного”. Он избавляет от долгих описаний вроде “аргумент permission – это кортеж, где первый элемент то, второй се, третий…”. Сигнатура с распаковкой скажет сама за себя.

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

    data = {'foo': 1, 'bar': 2, 'baz': 3}
    process = lambda (key, val): 'key: %s, value: %d' % (key, val)
    print map(process, data.iteritems())
    >>> ['key: baz, value: 3', 'key: foo, value: 1', 'key: bar, value: 2']
    

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

  • О нищенском мышлении

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

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

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

    Экономия свойственна не только бедным. Приведу в пример известных людей. Билл Гейтц очень неприхотлив в одежде. Он как-то признался, что каждая вещь в его гардеробе не дороже 10 долларов. Стив Джобс не вылезал из водолазки и пары джинс. У Обамы дешевые часы. У Марка Закерберга бюджетное авто.

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

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

    На мой взгляд, нищенское мышление – это такой ход мыслей, когда человек остается бедным бесконечно долгое время.

    С этим определением все становится на места. Но каким образом мыслит нищий человек? Какие ошибки он совершает?

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

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

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

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

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

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

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

    Второе – это кредиты. Они тянут на финансовое дно и в перспективе сделают бедным любого. Кредит для малоимущего – это как доза наркоману со стажем.

    Сегодня, когда население России стремительно нищает, как грибы после дождя открываются сервисы микрозаймов. Это займы 600% годовых и распри с коллекторами. Царапины на машинах, перерезанные провода, поджог колясок. Люди на все это идут добровольно.

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

    Когда иду по городу, вспоминаю Ильфа и Петрова. В уздном городе N были только цирюльни и погребальные конторы, словно граждане рождались за тем, чтобы побриться и умереть. А в крупных городах люди живут чтобы брать кредиты, терпеть беспредел коллекторов и становиться банкротами.

    Мы с Аленой поженились рано и первое время жили, так сказать, скромно. У нее зарплата 8 тысяч, а у меня, бывало, еще меньше. Почти все уходило на еду и редкие развлечения. При этом мы стабильно откладывали 2-3 тысячи. В год набегало 25. Раз в несколько месяцев покупали технику: микроволновку, фотоаппарат. И когда нам советовали кредит, крутили пальцем у виска. Мы что, миллионеры, платить двойную стоимость?

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

    Вот что такое нищенское мышление. Давить его из себя беспощадно.

  • Что такое RAML и как он помогает проекту

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

    Предположим, несколько сервисов сообщаются по HTTP API. Авторизация, история операций, бизнес-транзакции. Сервисы принимают и отдают JSON, сигнализируют об ошибках правильными статусами ответа. Нет пользователя – 404, нет прав – 403, кривые данные – 400. Классический рест.

    Как составить документацию? Нужно перечислить все урлы. Указать правила авторизации, заголовки, как правильно их составить. Для каждого урла перечислить методы GET, POST, etc. Описать структуру входных данных. Структуру ошибочных ответов. Добавить человеческое описание каждого метода. И все это поддерживать в актуальном состоянии.

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

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

    RAML решает эти проблемы. Название означает REST API Markup Language. Технически это YAML-документ с набором фич. Стандарт задает жесткую структуру для описания рестовых апих. Разработчики держат этот файл в гите и наполняют вместе с кодом. Во время билда особая утилита переводит документ в формат HTML.

    Перечислю достоинста технологии:

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

    2. Формат YAML. Это лучший формат для конфигураций. Краток, удобен для чтения. Поддерживает основные типы. Имеет списки, словари. Поддерживает многострочные данные. Разрешает комментарии. Не падает из-за лишней запятой на конце, как JSON.

    3. Поддержка markdown. Описание к каждой сущности пишется в маркдауне. Это самый простой язык разметки. Очень подходит для вставки кода, простейшей структуры (заголовки, подзаголовки).

    4. Инклуды. Большой файл легко декомпозировать на составные части и подключать по частям директивой include. Более того, инклуд поощрается стандартом, чтобы не дублировать сущности. Например, любой list-метод (список сущностей методом GET) принимает параметры limit и offset. Создаем отдельный файл с их описанием, примерами. Подключаем к нужным методам инклуд, компилируем. На выходе у каждого метода идентичное описание этих параметров.

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

    5. Утилиты и экспорт. Документацию в RAML легко обрабатывать машинным способом, потому что это стандарт, протокол. Уже существует пачка утилит для работы с форматом. Визуальные редакторы, генерация кода по заданным урлам, плагины к редакторам и ИДЕ, и конечно, тулза для экспорта в markdown и HTML.

    6. JSON-схемы. Схема – лучший способ описать структуру данных. Она заменит долгие списки и абзацы человеческого языка. Схема декларативна – сложные структуры могут быть представлены как ссылки на схемы проще. RAML поддерживает инклуд схем.

    7. Поддержка HTTP/REST. Стандарт знает все о заголовках и статусах ответа. Красиво, когда документация покажет не только тело ответа, но и статус с описанием, примеры заголовков.

    Существует два версии формата: 0.8 и 1.0. Вторая предлагает больше полей и возможностей для описания. Я пока что пользуюсь 0.8.

    Рассмотрим простой пример. Пусть есть простая апиха. Вот что мы напишем в заголовке файла example.raml:

    #%RAML 0.8
    title: Some Title
    version: v3
    baseUri: https://example.com/api/v1/
    

    Добавим вводную документацию. Для каждого раздела укажем подзаголовок. Ниже:

    documentation:
     - title: Main Title
       content: |
         common notes on this document.
    
     - title: Authorization
       content: |
         Some text to describe HTTP headers to access data.
         ~~~
         code goes here...
         ~~~
    

    Задекларируем правила авторизации. У нас в проекте это обычная базовая авторизация по протоколу HTTPS. Стандарт RAML знает, что это такое:

    securitySchemes:
      - Basic Authentication:
          type: Basic Authentication
    

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

    Начнем описывать урлы:

    /api/v1/user:
      securedBy: [Basic Authentication]
      get:
        description: |
          Retrieves a list of users.
          ### Syntax
          ~~~
          GET /api/v1/user/
          Host: {host}
          Authorization: {authorization}
          ~~~
        queryParameters:
          !include user_list_params.raml
        responses:
          200:
            body:
              application/json:
                example: !include examples/user_list_response.json
                schema: !include schemas/user_list_response.json
    

    Все просто. Мы декларируем апиху, которая отдает список юзеров. Доступ защищен базовой авторизацией. В описании пример запроса. Файл user_list_params.raml содержит парамеры командной строки: лимит, смещение, сортировка.

    Схема ответа может быть большой, поэтому инклуд. В файле examples/user_list_response.json лежит схема, примерно такая:

    {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "additionalProperties": false,
        "definitions": {
            "item": {
                "additionalProperties": false,
                "properties": {
                    "id": {
                        "minimum": 0,
                        "type": "integer"
                    },
                    "name": {
                        "maxLength": 64,
                        "type": "string"
                    }
                },
                "required": [
                    "id",
                    "name"
                ],
                "type": "object"
            }
        },
        "properties": {
            "count": {
                "minimum": 0,
                "type": "integer"
            },
            "results": {
                "items": {
                    "$ref": "#/definitions/item"
                },
                "type": "array"
            }
        },
        "required": [
            "count",
            "results"
        ],
        "type": "object"
    }
    

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

    Дальше документ очень удобно развивать. Добавлять методы (POST, PUT, DELETE), ссылаться на уже определенные структуры, выносить повторы в инклуды.

    Скомпилируем документ в файл. Утилита raml2html написана на node.js и ставиться через npm. Компилим:

    raml2html example.raml > example.html
    

    Не нравится дефолтный шаблон? Можно передать свой:

    raml2html -t custom-templates/template.nunjucks -i example.raml -o example.html
    

    В результате получим HTML-документ (UPD документ по ссылке удалили).

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

  • Третья встреча любителей глобоко порефакторить

    Состоялась третья встреча!

    Денис Ковалев дал вредные советы программистам. С комментариями, почему это вредно.

    Слайды

    Наш гость Евгений Рыжков поведал о спорных моментах в манифесте аджайла.

    Слайды

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

    Планируем четвертую встречу. Хотите выступить? Шлите запрос в ФБ мне, Денису или Юре.

  • Как проходить собеседование

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

    Текст о себе

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

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

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

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

    Протоколы, алгоритмы, БД

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

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

    Не опаздывать

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

    Однажды кандидат опоздал на час и рассказал, что, оказывается, на дороге пробки. ВНЕЗАПНО, да. Выглядел он как школьник, который прогулял диктант и говорит, что болела рука. Собеседование не прошел.

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

    Подготовьте окружение

    Если проходите собеседование удаленно, подготовьте рабочее место.

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

    Запустите тормозную ИДЕ заранее, чтобы не тратить 20 секунд впустую.

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

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

    Не перебивать, не холиварить

    Категорически запрещено перебивать того, кто задает и принимает ответ. И точка.

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

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

    В конце концов, ваше мнение о ПХП, Хаскеле и ООП ничего не меняет.

    Будьте на равных с собеседником

    Собеседование – это переговоры. В переговорах стороны по определению равны. Это значит каждая сторона имеет право задавать вопросы и говорить “нет”.

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

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

    Топорное “не знаю” с последующей паузой разражает. Я вижу, что не знаешь, но как ты будешь действовать? Какие задашь вопросы? Или будешь тупить за компом, молча срывая сроки?

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

    Не болтайте лишнего

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

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

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

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

    Если вы без опыта

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

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

    Вот и все, что нужно кандидату для успешного прохождения интервью!

Страница 31 из 49