История про шляпу или словесный рефакторинг

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

Шляпных дел мастер

Джону нужна была вывеска для магазина. Изначально он составил такую:

Джон Томпсон, шляпных дел мастер, изготавливает и продает шляпы за наличный расчет

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

Теперь вывеска гласила: "Джон Томпсон продает шляпы".

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

John Thompson

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

Прочитать

[Rails] Exception handling #2

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

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

module ApiHelper  
  def require_auth
    head :forbidden unless current_client
  end

  def current_client
    @client ||= Client.find_by_api_token(params[:api_token])
  end
end  

Но вместе с ним в заголовках вернется и Content-Type, который не всегда должен быть markdown/html.

Content-Type: markdown/html; charset=utf-8\r\nStatus: 403 Forbidden  

Это сломает вашего клиента, который к примеру написан с использованием Weary.

Правильный способ

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

Переделаем немного наш модуль:

def require_auth  
  raise ApplicationController::Forbidden unless current_client
end  

В главном ApplicationController пропишем

class ApplicationController < ActionController::Base  
  class Forbidden < StandardError; end
end  

в config/application.rb добавляем:

config.action_dispatch.rescue_responses.merge!(  
  'ApplicationController::Forbidden' => :forbidden
)

routes.rb:

match "/403" => "errors#forbidden"  

Добавляем написанный в предыдущей заметке errors_controller метод forbidden

def forbidden  
  @body_msg = "Access Denied"
  render status: :forbidden
end  

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

Content-Type: application/json; charset=utf-8\r\nStatus: 403 Forbidden  
{ "message": "Access Denied" }

То, что нужно! Таким образом можно обработать любую исключительную ситуацию и возвращать разные сообщения и статусы в нужных местах. Просто добавьте исключение, обработчик и пропишите в роутах и application.rb нужное исключение, создайте вью с правильным форматом ответа и радуйтесь жизни)

UPD: в комментариях добавили про 4ые рельсы и матч, вношу поправку, если вы пишите на 4х, то в роутах должно быть: routes.rb:

match "/403" => "errors#forbidden", via: :get  

Прочитать

[Отзыв] Р. Мартин "Чистый код"

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

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

  1. Главы про многопоточность! Однозначная 5, много примеров проблем и пути их решения:

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

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

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

Ссылка на книгу:

"Чистый код: создание, анализ и рефакторинг. Библиотека программиста" Роберт Мартин - ISBN 978-5-496-00487-9"Чистый код: создание, анализ и рефакторинг. Библиотека программиста" Роберт Мартин - ISBN 978-5-496-00487-9

Прочитать

Decorator vs Strategy, Composite, Presenter

Очень понравилась статья Dan Croak из ThoughtBot, про сравнение паттернов, которые часто путают( Декоратор, Презентер, Стратегия, Композиция). По сути этот пост это перевод статьи для себя.

Decorator

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

Пример:

coffee = Coffee.new  
Sugar.new(Milk.new(coffee)).cost  

или

coffee = Coffee.new  
coffee.extend Milk  
coffee.extend Sugar  
coffee.cost  

Вопрос в чем же отличие декоратора от остальных вышеперечисленных паттернов?

Strategy

GoF:

  • Декоратор меняет наружность
  • Стратегия меняет внутренности

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

Пример:

class Coffee  
  def initialize(brewing_strategy = DripBrewingStrategy.new)
    @brewing_strategy = brewing_strategy
  end

  def brew
    @brewing_strategy.brew
  end
end

Coffee.new(SteepBrewingStrategy.new)  

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

Composite

  • Декоратор это композиция с одним объектом
  • Декоратор не предназначен для аггрегации объектов

Пример из ActivePresenter:

class SignupPresenter < ActivePresenter::Base  
  presents :user, :account
end  

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

Presenter

Самый непонятный из паттернов:

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

Определения паттерна Презентер нет в книги GoF. Истоки использования данного термина в rails сообществе идут от статьи Jay Fields 2007 года. По большому счету, единственное что делает презентер презентером, так это его "представительная" составляющая.

class HumanizedStat  
  def initialize(component)
    @component = component
  end

  def to_s
    # большое выражение, которое обычно хранят в модели
  end
  # хелпер методы необходимы для #to_s
  # которым возможно следовало бы находиться в app/helpers
end  

В этом примере Презентер очень похож на Декоратор, но тем не менее не попадает под определение Gang of Four.

Однако функциональность полностью относится к представлению объекта, то можно считать данную конструкцию презентером и сложить её в app/presenters.

Прочитать

Вопросы на собеседовании #5

Как поменять местами 2 переменные без использования третьей?

Пусть нам даны 2 переменные a=3 и b=2. Нужно получить a=2 и b=3.

Способ №1

a = a + b  
b = a - b  
a = a - b  

Недостатоки: В случае когда переменные a и b типа int можно выйти за границы этого типа.

Способ №2

С помощью сложения по модулю 2 (XOR).

a = a ^ b # a = 1  
b = a ^ b # b = 3  
a = a ^ b # a = 2  

Матрица состояний:

a b xor
0 0 0
0 1 1
1 0 1
1 1 0

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

Прочитать