Этот пост будет целиком и полностью посвящен книге «Refactoring to collections», написанной Adam Wathan.

Начну с краткого рассказа об авторе. Если вы следите за тем, что происходит в php-сообществе, то об Adam Wathan (далее Адам) вы точно должны были слышать. Он является ведущим подкаста Full Stack Radio, ведет блог о веб-разработке. Адам тесно связан с сообществом фреймворка Laravel. Периодически он выступает на конференциях, и я советую посмотреть видео с его выступления на LaraconEU 2015.

А теперь перейдем к самой книге. Если коротко, то автор предлагает отказаться от классического императивного обхода массива с помощью конструкций for и foreach в пользу использования коллекций. Сразу скажу, пугаться не надо, на самом деле автор немного лукавит. Всеми нами любимые for и foreach инкапсулируются методами коллекции. Да, все просто, никакой магии! Что касается коллекций, Адам предпочитает использовать готовое решений в виде коллекций из фреймворка Laravel. На самом деле, коллекции могут быть самописными или компонентами других фреймворков. Заменяя стандартный обход массивов методами типа map или reduce, мы делаем код заметно более выразительным и читаемым. На протяжении всей книги с помощью простых примеров кода автор пытается донести эту идею. И вы знаете? Я ему верю. Вместо одного большого куска лапшекода после рефакторинга мы получаем стройную pipeline конструкцию, в которой каждое действие отчуждаемо и понятно.

Приведу пример из бесплатной главы книги. Предположим, что мы хотим оценить недавнюю активность пользователя GitHub в баллах. Действия пользователя можно получить, сделав GET-запрос по адресу https://api.github.com/users/{username}/events. Ниже приведен пример json-ответа для данного запроса.

[
    {
        "id": "3898913063",
        "type": "PushEvent",
        "public": true,
        "actor": "adamwathan",
        "repo": "tightenco/jigsaw",
        "payload": { /* ... */ }
    },
    // ...
]

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

  1. PushEvent будет оцениваться в 5 баллов.
  2. CreateEvent будет оцениваться в 4 балла.
  3. IssuesEvent будет оцениваться в 3 балла.
  4. CommitCommentEvent будет оцениваться в 2 балла.
  5. Все остальные действия оцениваются в 1 балл.

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

<?php
function githubScore($username)
{
    // Grab the events from the API, in the real world you'd probably use
    // Guzzle or similar here, but keeping it simple for the sake of brevity.
    $url = "https://api.github.com/users/{$username}/events";
    $events = json_decode(file_get_contents($url), true);
    // Get all of the event types
    $eventTypes = [];
    foreach ($events as $event) {
        $eventTypes[] = $event['type'];
    }
    // Loop over the event types and add up the corresponding scores
    $score = 0;
    foreach ($eventTypes as $eventType) {
        switch ($eventType) {
            case 'PushEvent':
                $score += 5;
                break;
            case 'CreateEvent':
                $score += 4;
                break;
            case 'IssuesEvent':
                $score += 3;
                break;
            case 'CommitCommentEvent':
                $score += 2;
                break;
            default:
                $score += 1;
                break;
        }
    }
    return $score;
}

Я опущу промежуточные стадии рефакторинга и сразу перейду к его результату.

<?php
function githubScore($username)
{
    $url = "https://api.github.com/users/{$username}/events";

    $events = collect(json_decode(file_get_contents($url), true));

    return $events->pluck('type')->map(function ($eventType) {
        return collect([
            'PushEvent' => 5,
            'CreateEvent' => 4,
            'IssuesEvent' => 3,
            'CommitCommentEvent' => 2,
        ])->get($eventType, 1);
    })->sum();
}

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

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

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

Генерация пассов для Wallet(Passbook)

Пример генерации пасса для Wallet(Passbook). Читать дальше

Подкасты

Опубликовано 20.10.2015