Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Как стать программистом и избежать детских ошибок: Рефакторинг на ходу


Посмотрим ещё раз на код, полученный в статье «Как я пишу очередную фичу». Там ещё не всё интересное исчерпано.

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

// Получаем JSON
$result file_get_contents(
    'http://twitter.com/status/user_timeline/'.
    $username.'.json?count='.$count)
if (!$result// Пустая строка для нас тоже не ответ
    Core::error('Cannot open Twitter');
                                            
// Извлекаем JSON
$result json_decode($resulttrue);
if (!is_array($result))
    Core::error('Twitter data corrupted');

сделать отдельную функцию. Например, RPC::getJSON($url).

Тут есть два правильных решения.

  1. Сделать это немедленно.
  2. Запомнить эту идею, но пока не реализовывать её.

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

Идём дальше и натыкаемся на то, что теперь нам надо программно искать в Гугле. И у него есть JSON API. Вот тут мы вспомним о нашей закладке в мозгу: добавив функцию сейчас, мы упрощаем код в двух местах! Это уже весомый плюс, как ни крути, поэтому функцию мы, наконец, создаём.

class RPC {

    function getJSON($url) {

        // Получаем JSON
        $result file_get_contents($url);
        if (!$result// Пустая строка для нас тоже не ответ
            Core::error('Cannot open '.$url);
                                            
        // Извлекаем JSON
        // Проверка результата спорная в данном случае,
        // но не хотелось бы сейчас углубляться – не о ней речь
        $result json_decode($resulttrue);
        if (!is_array($result))
            Core::error('JSON data from '.$url.' corrupted');
    }

}

Вот так теперь выглядит функция Твиттера:

class Twitter {

    function getRecent($username$count 1) {
        
        // Проверяем кэш
        if ($result = 
            Cache::read('Twitter\\'.$username.'\\'.$count))
            return $result;

        // Получаем JSON
        $result RPC::getJSON(
            'http://twitter.com/status/user_timeline/'.
            $username.'.json?count='.$count);
            
        // Сохряняем в кэш, чтобы не исчерпать лимит запросов
        Cache::write(
            'Twitter\\'.$username.'\\'.$count,
            $result,
            TWITTER_CACHE_TTL);
            
        return $result;
    }
    
}

Красота. А что с Google?

class Google {

    function search($query) {
        
        return RPC::getJSON(
            'http://ajax.googleapis.com/ajax/'.
            'services/search/web?v=1.0&q='.urlencode($query));
    }
    
}

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

Где рефакторинг?

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

К тому, как понимать слово «абстракция», я в будущем ещё вернусь.

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

Пример из жизни

Мне нужно извлечь из предыдущей версии проекта функцию formatTranscript.

Там она относилась к объекту, которого тут нет.

Функция требует дополнительных данных и сложных глобальных параметров, поэтому создаю файл format_transcript.js. Чтобы инкапсулировать эти данные, оформляю их в виде одноимённого файлу объекта FormatTranscript.

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

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

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

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

Удаляю файл formatter.js (побывавший ранее format_transcript.js). Спасибо за службу, ты тут больше не нужен.

Психологический момент

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

Ещё о getJSON

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

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

  1. Создаём отдельную функцию, скажем RPC::httpGET, которая будет работать через доступный механизм: file_get_contents (когда есть), curl или даже напрямую через сокеты.
  2. Вызываем в нашей среде редактирования окошко «Search in Files» и ищем все вызовы file_get_contents.
  3. Те из них, что использовались для работы с HTTP заменяем на вызов RPC::httpGET.

Этот выпуск Вы можете прокомментировать в Живом Журнале.

Задать вопрос

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


В избранное