Василий Половнёв

Разработчик, зануда, сооснователь Kursk Meetup.

Специализация — Рельсы, фронтенд, автоматизация тестирования и разработки.

Ушпешный блог Тестовый курс СЯУшки Почта Твитер

Проблемы с subject

subjectхелпер в Rspec для однострочных тестов. Бывает неявным и явным.

# Неявный subject
RSpec.describe User do
  # subject — User.new (described_class.new)
  it { is_expected.to validate_presence_of(:name) }

  # Такой тест разворачивается в:
  it "..." do
    expect(User.new).to validate_presence_of(:name)
  end
end

# Явный subject
RSpec.describe User do
  subject { described_class.new(role: "admin") }

  it { is_expected.to be_super_admin }
end

В однострочниках

subject здорово сокращает тесты валидаций и ассоциаций в Рельсах с Shoulda Matchers:

RSpec.describe ActivityLog do
  it { is_expected.to validate_presence_of(:user_name) }
  it { is_expected.to validate_presence_of(:event) }
  it { is_expected.to validate_presence_of(:ip) }

  it { is_expected.to belong_to(:user) }
end

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

RSpec.describe Alarm do
  describe "#snooze" do
    subject { described_class.new(at: time) }

    it { expect { subject.snooze }.to change { subject.at }.by(9.minutes)
  end
end

Не помогает и именованный subject:

RSpec.describe Alarm do
  describe "#snooze" do
    subject(:alarm) { described_class.new(at: time) }

    it { expect { alarm.snooze }.to change { alarm.at }.by(9.minutes)
  end
end

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

it { expect(...) } — плохо

Чтобы исправить ситуацию, разверните тест и добавьте описание:

# Ясно, что #snooze передвинет будильник на 9 минут вперед,
# без чтения проверки
it "pauses alarm for next 9 minutes" do
  expect {
    subject.snooze
  }.to change {
    subject.at
  }.by(9.minutes)
end
subject для проверок валидаций и ассоциаций в Рельсах — хорошо

В начале спеки

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

RSpec.describe Post do
  let(:author) { build(:author, name: "Melanie C") }
  subject { build(:post, author: author, title: "Awesome News") }

  describe "#slug" do
    it "generates slug from title" do
      expect(subject.slug).to eq "awesome-news"
      # Чтобы понять, почему slug именно такой,
      # придется вернуться в начало спеки и увидеть
      # title у поста
    end
  end

  describe "#author_name" do
    subject { post.author_name }

    context "with author" do
      it { is_expected.to eq "Melanie C" }
      # Чтобы понять, откуда взялась Мелани Си,
      # придется вернуться в начало спеки (и в 90-е, конечно)
    end

    context "without author" do
      let(:author) { nil }

      it { is_expected.to eq "Annoymouse" }
      # Чтобы понять, почему здесь Annoymouse, что
      # и где меняет author = nil,
      # придется вернуться в начало спеки
      # и пройтись по всем describe/let/context
    end
  end
end

Проблема в тестах выше в том, что информация важная для восприятия проверки далеко от нее. Читатель превращается в Шерлока Холмса и тратит время впустую, расследуя, что скрывает subject.

Глобальный subject — плохо

Чтобы поправить это, перенесите настройку ближе к проверке и оставьте только значащую информацию:

# Хорошо: информация нужная для восприятия проверки на месте
describe "#slug" do
  it "generates slug from title" do
    # Нам нет дела до автора, а вот title важен
    post = build(:post, title: "Awesome News")

    expect(post.slug).to eq "awesome-news"
  end
end
# Так себе: информация стала ближе к проверке,
# но чтобы понять смысл проверки,
# придется ее «скомпилировать» в голове
describe "#author_name" do
  context "with author" do
    let(:author) { build(:author, name: "Melanie C") }
    let(:post) { build(:post, author: author) }

    subject { post.author_name }

    it { is_expected.to eq "Melanie C" }
  end
end
# Лучше: информация стала ближе к проверке.
# Описание теста ясно дает понять, что проверяем
describe "#author_name" do
  context "with author" do
    let(:author) { build(:author, name: "Melanie C") }
    let(:post) { build(:post, author: author) }

    it "returns author's name" do
      expect(post.author_name).to eq "Melanie C"
    end
  end
end
# Хорошо: информация нужная для восприятия проверки на месте
describe "#author_name" do
  context "with author" do
    it "returns author's name" do
      author = build(:author, name: "Melanie C")
      post = build(:post, author: author)

      expect(post.author_name).to eq "Melanie C"
    end
  end
end
Держать информацию нужную для понимания проверки рядом с ней — хорошо

Дополнительное чтение

По хардкору: describe и context

describe и context объединяют связанные проверки и задают структуру теста.

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

Без структуры:

it "#accessible_projects return all system projects when user is admin"
it "returns no projects when user is not admin"
it "returns user full name with email"

Со структурой:

describe "#accessible_projects" do
  context "when user is admin" do
    it "returns all system projects"
  context "when user is NOT admin" do
    it "returns no projects"

describe "#user_name_with_email" do
  it "returns user full name with email in parens"

Когда и что использовать

Я использую describe для сущностей (кто, что?), а context для состояний (когда, с чем, с какими условиями?). Так легче ориентироваться в возможных ситуациях и исследовать условия.

# Так себе
context "associations"
context "#full_name"
describe "when user is admin"
describe "with valid params"

# Хорошо
describe "associations"
describe "#full_name"
context "when user is admin"
context "with invalid params"

Контексты начинаю со слов when, if, with, given. Если вижу их в описании проверки — вытаскиваю:

# Плохо: условие, описание ситуации
# (user is editor) тяжело заметить
it "returns only published posts when user is not editor"
it "returns published and draft posts when user is editor"

# Хорошо: ситуация явно обозначена контекстом
context "when user is editor" do
  it "returns published and draft posts"

context "when user is NOT editor" do
  it "returns only published posts"

Вложенные контексты начинаю с and, чтобы было понятно, что это не все условие:

context "when user is editor" do
  context "and post is published" do
    it "displays post views/visitors count"

Шпаргалка

describe <something>
context <when... / if... / with... / given...>

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

Стоит почитать

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

ЖЗЛ

Арнольд Шварценеггер. Вспомнить все: моя невероятно правдивая история

Зачем читать: чтобы посмотреть, насколько полной и насыщенной жизнью можно жить.

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

Теодор Драйзер. Финансист

Зачем читать: чтобы посмотреть на жизнь несгибаемого прагматика.

Джек Лондон. Время-не-ждет

О силе характера и бесполезности самоубийства.

Ральф Лейтон и Ричард Филлипс Фейнман. Вы, конечно, шутите мистер Фейнман

О задротстве и чувстве юмора.

Ричард Филлипс Фейнман. Какое ТЕБЕ дело до того, что думают другие?

Зачем читать: ради удивительной истории любви.

Работа

Джим Кэмп. Сначала скажите «нет»

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

Гэвин Кеннеди. Договориться можно обо всем

Зачем читать: чтобы порвать шаблоны, разученные в предыдущей книжке.

Карл Сьюэлл. Клиенты на всю жизнь

Зачем читать: чтобы научиться клиентскому сервису.

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

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

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

Лучшая в мире система клиентского сервиса является и самой простой: ДЕЛАЙ ТО, ЧТО ОБЕЩАЛ, И ДЕЛАЙ ЭТО С ПЕРВОГО РАЗА.

Майкл Гербер. Создание предприятия которое бы работало (П-Миф)

Зачем читать: чтобы познакомиться с Менеджером, Специалистом и Предпринимателем, живущим в вас.

Максим Батырев. 45 татуировок менеджера

Зачем читать: чтобы быстро прокачать внутреннего Менеджера.

Кармин Галло. iПрезентация

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

Сделывание

Брайан Моран и Майкл Леннингтон. 12 недель в году. Как за 12 недель сделать больше, чем другие успевают за 12 месяцев

Зачем читать: чтобы научиться планировать на квартал и неделю вперед. Стратегия.

Марк Форстер. Сделайте это завтра

Лучшая книга по планированию и «сделыванию» ежедневных задач. Тактика.

Дэвид Шварц. Искусство мыслить масштабно

Почаще напоминайте себе: «Лучше износиться, чем проржаветь».

Действие убивает страх! С другой стороны, колебание, нерешительность, отсрочка только усиливают его.

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

Программирование

Сенди Мэтц. Practical Object-Oriented Design in Ruby

Зачем читать: чтобы найти ключ к пониманию ООП, дизайну ПО и тестированию.

These benefits, however valid, are proxies for a deeper goal. The true purpose of testing, just like the true purpose of design, is to reduce costs. If writing, maintaining, and running tests consumes more time than would otherwise be needed to fix bugs, write documentation, and design applications tests are clearly not worth writing and no rational person would argue otherwise

Ищу стажеров (уже нашел)

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

Ищу четверых стажеров — Руби-разработчиков, у которых проблемы с тестами:

  • не понимают, что и как тестировать;
  • прочитали RSpec Book, не могут написать тест с нуля;
  • исправляют баг за 5 минут, потом 2 часа пишут для него тест;
  • пишут тесты, но не могут смотреть на них без боли.

За полтора месяца научу стажеров:

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

Для этого стажеры будут:

  • тратить 3 часа в неделю на теорию и домашние задания;
  • писать тесты на RSpec, Jasmine и Capybara;
  • переписывать тесты снова и снова после ревью.

Если написанное выше про вас, напишите мне письмо с рассказом о себе на vasily@polovnyov.ru.

Если нет, поделитесь постом в соцсетях. Вот смешная картинка:

По хардкору: что писать в комментарии к коммиту

У меня был коллега, который коммиты сопровождал одним и тем же комментарием, изредка играя с заглавными буквами. Его история изменений выглядела так:

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

Чтобы комментарии к коммитам были полезны и вам, и коллегам, договоритесь об их стиле и содержимом. А я пока поделюсь своим вариантом.


На каком языке писать

В коде вы работаете с Post, Blog, User. Вы мыслите ими и не задумываясь используете в речи: добавим юзеру аватарку, покажем популярные посты в блоге.

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

f0b4ac поправил верстку подвала статьи

«Статья». Это у нас что? Article, Post, BlogEntry? Если коммит на английском, проблем нет:

f0b4ac fix post footer markup
Пишите на английском

Что писать в комментарии

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

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

# Плохо: это мы и в диффе видим.
# Зачем добавляли-то? Что пытались поправить?
d77d9f add <div> wrap

# Хорошо: ясно, зачем сделаны изменения, какую проблему решали
d77d9f fix Firefox issue with flexbox padding
Не что сделано, а зачем

В каком времени

Я рассматриваю историю Гита как историю команд, приказов, приводящих репозиторий из одного состояния в другое. Поэтому пишу в повелительном наклонении. Чтобы было проще писать, использую шаблон-скороговорку:

# If applied, this commit will
mention Ubuntu installation instructions in README
В повелительном наклонении

ПРОТИП: если в комментарии есть and, скорее всего, в коммите несколько несвязанных изменений:

fix ... and update ...
introduce ... and correct ...

Дополнительное чтение

Дезодоранты и полезные комментарии

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

Смотрите:

def archive
  # unlock and destroy everything in project
  discussions.each(&:unlock!)
  todos.each(&:unlock!)
  discussions.destroy_all
  todos.destroy_all

  # assign read-only role to users
  users.each do |user|
    user.update_attribute(role: "client")
  end

  self.status = 38 # set status to archived
end

Чтобы сделать код понятнее, вытащите константу и несколько методов с вразумительными именами:

def archive
  clean_up_before_archiving
  reset_user_roles_to_read_only

  self.status = ARCHIVED
end


def clean_up_before_archiving
  discussions.each(&:unlock!)
  todos.each(&:unlock!)

  discussions.destroy_all
  todos.destroy_all
end

def reset_user_roles_to_read_only
  users.each(&:reset_role)
end

БА ДУМ ТСС! Отсюда правило:

Если код не понять без комментария, рефактори

Полезные комментарии

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

# Monkey patching Paperclip to disable built-in spoofing protection
# which gives many false-positive errors and prevents uploading of
# .xlsx, .caf, .msg files.
#
# For more details see:
#
# * http://robots.thoughtbot.com/prevent-spoofing-with-paperclip
# * https://github.com/thoughtbot/paperclip/issues/1456
# * https://github.com/thoughtbot/paperclip/issues/1530

module Paperclip
  class MediaTypeSpoofDetector
    def spoofed?
      false
    end
  end
end

Или объясняем, для чего здесь этот хак:

.is-visible & {
  // Adding transparent outline fixes jagged edges in Firefox.
  //
  // See: http://stackoverflow.com/a/9333891
  outline: 1px solid rgba(255, 255, 255, 0);
}

Наверняка бывают и другие типы полезных комментариев. Если вы их встречали, покажите-расскажите: vasily@polovnyov.ru.

###

Чтобы этот пост активнее шарили в Фейсбуке, прикладываю картинку-правило, заряженную на рефакторинг и быстрые тесты:

По хардкору: instance_double, verify_partial_doubles

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

Ситуация

Возьмем пример с Notifications:

class Notifications
  def as_json
    # Загружаем и отдаем последние новости
    # и уведомления с серверов Юрия Лозы по ХТТП
  end
end

class NotificationsController
  def index
    # Оборачиваем уведомления в ключ 'data'
    render json: { data: notifications.as_json }
  end

  def notifications
    Notifications.new
  end
end

Чтобы не обращаться к серверам Лозы в тестах, используем дублера:

describe "NotificationsController#index" do
  let(:notifications) do
    double(:datasource, as_json: { notifications: [] })
  end

  before do
    allow(Notifications)
      .to receive(:new)
      .and_return(notifications)
  end

  it "wraps notifications in 'data' key" do
    get :index, format: :json

    expect(json_response["data"].keys)
      .to have_key "notifications"
  end
end

Что произойдет, если мы переименуем Notifications#as_json в Notifications#to_json? Ничего. Мы останемся наедине с зеленым тестом, проверяющим бесполезного дублера.

instance_double

Чтобы не попадать в такую дебильную ситуацию, используйте instance_double:

describe 'NotificationsController#index' do
  let(:notifications) do
    instance_double(Datasource, as_json: { notifications: [] })
  end

  # ...
end

Такой дублер проверит свой интерфейс. Если метода нет или у него другие аргументы — красный тест.

Чтобы так же проверять моки и стабы, убедитесь, что verify_partial_doubles включен в spec_helper.rb.


Внимательный читатель со звездочкой наверняка заметил, что одного instance_double мало. Все верно. В RSpec есть похожие дублеры для классов, модулей и объектов: object_double и class_double.

По хардкору: дублеры, моки, стабы

Сегодня о тестах. Пост для тех, кто знаком с RSpec, но не понимает, что такое «мокать» и «застабить». Коротко, по делу и с примерами.

Дублер (test double)

Объект-каскадер, подменяющий реальный объект системы во время тестов:

describe NotificationsController do
  # NotificationsController загружает последние уведомления
  # со стороннего сервиса по HTTP
  # с помощью NotificationsDatasource.
  let(:datasource) do
    double(:datasource, as_json: { notifications: [] })
  end

  before do
    # Подменяем реальный NotificationsDatasource дублером,
    # чтобы не зависеть от внешнего сервиса в тестах:
    allow(NotificationsDatasource)
      .to receive(:new)
      .and_return(datasource)
  end

  describe "#index" do
    it "wraps notifications in 'data' key" do
      get :index, format: :json

      expect(json_response["data"].keys)
        .to have_key "notifications"
    end
  end
end

Стаб (stub)

Заглушка для метода или объекта, возвращающая заданное значение:

context "when attachment file is too large to email" do
  let(:max_file_size) { Attachment::MAX_FILE_SIZE }

  before do
    allow(attachment)
      .to receive(:file_size)
      .and_return(max_file_size + 1)
  end

  it "raises 'file is too large' error" do
    # ...
  end
end

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

Мок (mock)

Стаб с ожиданиями, которые RSpec проверит в конце теста:

context "when cloning process cannot be performed" do
  before do
    allow(doctor).to receive(:clone) { raise "can't clone" } # стаб
  end

  it "notifies airbrake" do
    expect(Airbrake).to receive(:notify) # мок
    # Rspec застабит `Airbrake.notify`
    # и в конце этого `it do...end` блока
    # проверит, был ли он вызван.
    # Если вызова не было — ошибка и красный тест.

    clone_poor_dolly
  end
end

Моки меняют порядок фаз в тесте. Вместо «Настройка — Испытание — Проверка» получается «Проверка+Настройка — Испытание». Если вам как и мне тяжело такое читать, используйте стаб с проверкой:

# мок
it "notifies airbrake" do
  expect(Airbrake).to receive(:notify) # проверка + настройка

  clone_poor_dolly # испытание
end

# стаб + проверка
it "notifies airbrake" do
  allow(Airbrake).to receive(:notify) # настройка

  clone_poor_dolly # испытание

  expect(Airbrake).to have_received(:notify) # проверка
end

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

Стоп-комментарии

Без стоп-комментариев код становится лаконичнее и информативнее. Этот пост о комментариях, которым не место в коде.

Закомментированный код отвлекает от чтения и исследования, увеличивает риск пропустить важное. Как пятиминутный рекламный блок, начавшийся на словах Вито Корлеоне: «…ты никогда не искал моей дружбы и ты боялся быть у меня в долгу».

// var fibonacci = function(n) {
//  if (n < 2){
//    return 1;
//  } else {
//    return fibonacci(n-2) + fibonacci(n-1);
//  }
// }

finallyMakeSomethingUseful()

Не предлагайте дружбу. Удаляйте без сожалений. Если что — возьмете из истории Гита.


Капитанские комментарии объясняют очевидное, описывают то, что и так ясно из кода.

# Associations
has_many :posts
has_many :comments
// Default Config
var defaultConfig = { showWordCount: true }

// close connection
connection.close()
// в _header.scss
// ========================
// HEADER
// ========================
#header { }

В них нет пользы и новой информации. Удаляйте без раздумий.


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

// TODO: fix this hack!
// TODO: move it somewhere else
// FIXME: this is not a good idea
// TODO: refactor this file

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

P. S. О «вонючих» и полезных комментариях — в следующем посте.


Еще по теме:

Сегодня я узнал

Мы каждый день узнаем новое: как инлайнить SVG в CSS, как использовать ES2015 в Gulpfile, где работает position: sticky. Накапливать такие знания публично — полезно: растет коллективный опыт. Кроме того, так проще заметить застой в профессиональном росте: перестали поступать новые знания → отстой, деградация, работа риэлтором.

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

Заодно посмотрите и подпишитесь на другие коллекции:

Три правила ревью

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

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


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

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

Первый ревьюер — ты

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

Отсюда и правило покруче: если что-то можно проверить автоматически, это должен делать робот, а не человек.

Синтаксис — роботам

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

  • понятно как и какую проблему решает пуллреквест?
  • гифка в пуллреквесте смешная?
  • в коде нет потенциальных багов?
  • от кода не пахнет (DRY, SRP, комментарии)?
  • имена и названия понятны?
  • код легко читается?
  • тесты на месте?
  • тестов достаточно, нет лишних?
  • по изменениям в тестах понятно, что изменилось в коде?
  • миграции и изменения в схеме совпадают?
  • у добавленных в БД полей есть нужные индексы и ограничения?
  • не торчит где-нибудь XSS или SQL инъекция?
  • не налажали с проверками доступа?
  • роуты в порядке?
  • нет жирных моделей, контроллеров и вьюх?
  • колбэки меняют только собственное состояние?
  • нет медленных участков (N+1 запросов, O(n^2) алгоритмов)?
Проверяй по списку

Если ваш список правил и проверок при ревью круче, поделитесь опытом, напишите: vasily@polovnyov.ru.


Еще по теме:

Управление временем

Еще год назад я был уверен, что тайм-менеджмент — это «наука» вроде супердиеты Елены Малышевой или двухчасового интенсива по продажам. Все поменялось, когда я увидел формулу:

Производительность = талант × самодисциплина

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

Если сомневаетесь, нужен ли вам тайм-менеджмент, поработайте неделю с RescueTime. Посмотрите, сколько времени уходит на работу, а сколько улетает впустую.

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

И 5 советов как не облажаться на старте.

  1. Забудьте про мотивацию. Главное — дисциплина.
  2. Выкрутите себе руки. Система должна быть сильнее вас.
  3. Найдите единомышленников. Отчитывайтесь перед друзьями или близкими за прошедшую неделю, делитесь планами на «год» и следующую неделю.
  4. Освободите голову. Записывайте и планируйте все от «разобрать Покет» и «купить 2 лампочки E17 25 ватт» до «server-side render в Фатеже» и «тестовый деплой с Ansible на Digital Ocean».
  5. Возьмите блокнот и ручку. Вычеркнуть сделанную задачу — удовольствие, которого нет в модных таск-трекерах.

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

Подростковое тестирование. Подборка

Подборка предназначена для разработчиков знакомых с Ruby и Rspec, но не до конца понимающих что и как тестировать. Для тех, кто прочитал Rspec Book, но не может написать тест с нуля. Для тех, кто исправляет баг за 5 минут, а потом 2 часа пишет для него тест. Для тех, кто прохавал жизнь с самого низа.

Если вы не знакомы с Ruby/Rspec, а статьи понять хочется, пройдите эти курсы:

Тестовая пирамида

Модульные тесты

Моки и стабы

UI, интеграционные, приемочные тесты

Дополнительно

Хороший доклад

На прошлой неделе я выступил с докладом об and в Ruby и прослушал 6 программистских докладов. Этот пост для меня и программистов, выступающих на митапах, демо и конференциях.


Хороший доклад — это полезный доклад. Если доклад бесполезен, ребята в зале достанут телефоны и полезут в твиттер.

Если вы начнете изучать Clojure, то сможете лучше ознакомиться с Emacs.

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

Короче, тут у нас Elastic Search, React, Flux и Docker. Все очень круто работает в продакшене пока мы попиваем картофельный смузи.

Ага, спасибо. Ты клевый. Когда фуршет?

Ну, я, короче, не знаю, что вам еще показать. Вот как-то так оно и работает.

Ясно-понятно. Спасибо, что украл всего час времени.

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

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

С CSS инбоксами программисты пишут CSS без стыда и боязни перед профессиональными верстальщиками.

Как бы вы не старались точно оценить задачу, все равно ошибетесь. Лучше переоценить задачу по времени, тогда потери будут минимальны. Недообещать > переобещать.

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

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

Хороший доклад — полезный доклад.

Stylus → PostCSS

Пока серьезные ребята только подумывают о переезде на PostCSS, я взял и перевел сайт (не этот, а другой) со Stylus на PostCSS.

У меня было 400 строк кода, 20 переменных, gulp, собирающий фронтенд, и целое множество правил всех сортов и расцветок, а также медиавыражения, псевдоклассы, миксины и вложенные селекторы. Не то что бы это был необходимый запас для сайта. Но если начал собирать фронтенд, становится трудно остановиться.

PostCSS — парсер, и работает с текстом похожим на CSS. Поэтому в первую очередь я переехал с SASS-подобного синтаксиса (значащие отступы, отсутствие точек с запятой и фигурных скобок) на SCSS-подобный. Stylus поддерживает оба синтаксиса, поэтому это делается до переезда на PostCSS:

// было
.footer
  padding: 1rem
/* стало */
.footer {
  padding: 1rem;
}

Теперь выкидываем Stylus, меняем расширение .styl файлов на .css и добавляем PostCSS. В первую очередь нужен postcss-import для поддержки @import, postcss-simple-vars для переменных и postcss-nested для вложенных селекторов:

postcssImport = require("postcss-import")
postcssVars = require("postcss-simple-vars")
postcssNested = require("postcss-nested")

preprocessors = [
  postcssImport(from: AppConfig.paths.mainStylesheet),
  postcssNested,
  postcssVars
]

gulp.task "stylesheets", ["clean:stylesheets"], ->
  gulp.src(AppConfig.paths.mainStylesheet)
    .pipe(postcss(preprocessors))

Объявление переменных придется поменять ( = на :):

// было
$text-color = rgba(0, 0, 0, .85);
/* стало */
$text-color: rgba(0, 0, 0, .85);

С миксинами сложнее. Есть postcss-mixins:

// было
clearfix() {
}

.footer {
  @include clearfix;
}
/* стало */
@define-mixin clearfix {
}

.footer {
  @mixin clearfix;
}

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

Для работы с цветом есть postcss-color-function:

// было
$footer-color = lighten($text-color, 10%);
$submit-shadow-color = darken($link-color, 20%);
/* стало */
$footer-color: color($(text-color) lightness(10%));
$submit-shadow-color: color($(link-color) blackness(+20%));

Вот и все. Уложился в час.


Следующий свой проект обязательно буду собирать с PostCSS вместо Stylus/LESS/SASS. Все, что нужно мне от препроцессоров, — вложенные селекторы, переменные, миксины и работа с цветом. Все это есть в PostCSS в виде крохотных подключаемых модулей.

В то же время нет безумных штук вроде @for, @if, @at-root, @each из SASS. PostCSS — это ограничения, в которых рождается хороший дизайн и чистый код.

Ну, и он быстрый:

# PostCSS
[21:42:10] Starting 'stylesheets'...
[21:42:10] Finished 'stylesheets' after 206 ms

# Stylus
[21:42:45] Starting 'stylesheets'...
[21:42:45] Finished 'stylesheets' after 325 ms

Дополнительное чтение:

CSS инбоксы

Давайте честно. 8 из 10 программистов стесняются, краснеют, потеют и устремляют взгляд в линолеум на полу, когда надо написать немного CSS. Чтобы избавиться от этого стресса, используйте CSS инбоксы.

Выделите комментарием отдельное место в конце файла со стилями (например, в application.css):

// -------------------------------------
//   Inbox
// -------------------------------------

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

Кроме того, инбоксы полезны и опытным ребятам. В инбоксах можно держать код «под вопросом»: здесь один или два компонента, мы еще это используем, почему здесь такой специфичный селектор? Когда ответы появятся, переработаете, перенесете и очистите инбокс.

Пожалейте программистов, заведите инбоксы в стилях.

К Оптимусу Прайму. Группировка CSS свойств

Привет еще раз, дорогой Оптимус.

В последнем письме я писал тебе о сортировке CSS свойств по алфавиту. Почему ты мне в ответ не пишешь? Я же твой самый большой и главный фанат. Мне нужен твой совет, слышишь?

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

  • позиционирование (position, top, …, z-index);
  • блочная модель (display, float, …, overflow);
  • размеры (width, height, margin, padding, …);
  • таблицы/списки (border-collapse, list-style, …);
  • визуальщина (transition, transform, cursor, text-transform, …);
  • цвета (opacity, color, border, background, …);
  • шрифт (font-*, line-height).

Вот так выглядит CSS, разбитый по этим группам:

.score {
  position: relative;
  z-index: 10;

  float: left;

  margin: 13px 0;
  padding: 14px 0;

  text-align: center;
  vertical-align: middle;

  background-color: #fff;
  border-radius: 20px;

  font-size: 12px;
  font-size: 16px;
  line-height: 14px;
}

Такой CSS удобно читать и редактировать. Связанные свойства (position и z-index, margin и padding, font-size и line-height) находятся рядом. Важные группы идут первыми: позиционирование, раскладка, размеры. Когда работаешь с одной конкретной группой свойств, легче заметить дублирующиеся: повторяющийся font-size бросается в глаза в блоке из 3 свойств.

Проблема здесь одна: нужно думать. Нужно понимать CSS, нужно знать, к какой группе относится свойство. Ребята, «лишь бы в Хроме работало», не смогут разбить свойства, которые не понимают, на группы, в которых не разбираются.

Оптимус, от тебя фанатеет вся Земля. Я все твои цитаты знаю наизусть. Но письма без ответа на меня наводят грусть. Что скажешь, как тебе такие группировки? Жду письма!

P. S. Ты обязательно должен мне ответить, потому что я твой верный фанат.

Объясни это

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

Смотрите:

3

<div class="axis">
  <span class="point point--scored_3">
    3
  </span>
</div>
.axis {
  position: relative;
  height: 20px;
  width: 200px;

  font-size: .75rem;
}

.axis:before { /* стили для полоски-шкалы */ }

.point {
  position: absolute;
  left: 0;

  display: block;
  height: 20px;
  width: 20px;
  margin-left: -10px;

  background: #ff0;
  border-radius: 100%;
  line-height: 20px;
  text-align: center;
}

.point--scored_3 {
  left: 60px;
}

20px, 10px, 60px, 200px — это не магические числа, которые удалось подобрать, чтобы эта штука хоть как-то работала в IE. Это странные числа, вызывающие вопросы. Как они связаны между собой? Почему оно работает? Что тут скрыто? Что я сломаю, если поменяю везде 20px на 30px?

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

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

$point-diameter: 20px;
$max-point-count: 10;

.axis {
  position: relative;
  height: $point-diameter;
  width: $max-point-count * $point-diameter;

  font-size: .75rem;
}

.point {
  position: absolute;
  left: 0;

  display: block;
  height: $point-diameter;
  width: $point-diameter;
  margin-left: -($point-diameter / 2);

  background: #ff0;
  border-radius: 100%;
  line-height: $point-diameter;
  text-align: center;
}

.point--scored_3 {
  left: 3 * $point-diameter;
}

Теперь зависимость величин от размера метки явно обозначена. Ось в длину 200px ($max-point-count * $point-diameter), чтобы вместить десять меток. Отрицательный отступ слева в половину метки нужен, чтобы отцентрировать ее. А высота строки в 20px ($point-diameter) центрирует текст метки по вертикали.

Кроме того, нам проще вносить изменения. Если окажется, что метки маленькие, а ось короткая, нам не придется исправлять кучу мест, связанных с этими размерами. Скорректируем одну переменную и вуаля!

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

К Оптимусу Прайму. Сортировка CSS свойств по алфавиту

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


Привет, дорогой Оптимус Прайм.

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

Ты, наверное, читал в интернете о CSS и знаешь, что они состоят из селекторов и наборов свойств:

селектор {
  свойство: значение;
  свойство: значение;
  свойство: значение;
}

Я никак не определюсь, в каком порядке свойства записывать.

Зачем им порядок? Затем, что порядок облегчает доступ к нужным свойствам и предотвращает ошибки:

.score {
  position: relative;
  float: left;
  margin: 13px 0;
  vertical-align: middle;
  background-color: #fff;
  border-radius: 20px;
  z-index: 10;
  font-size: 12px;
  text-align: center;
  line-height: 14px;
}

Чтобы найти z-index надо прочитать 6 строчек. Если дописать font-size: 16px в начало блока и не заметить font-size: 12px ниже, то размер шрифта будет 12 пикселей, а не 16.

Если свойства отсортировать по алфавиту, эти проблемы исчезнут. z-index будет в конце списка, а дублирующиеся свойства на соседних строчках:

.score {
  background-color: #fff;
  border-radius: 20px;
  float: left;
  font-size: 12px;
  font-size: 16px;
  line-height: 14px;
  margin: 13px 0;
  position: relative;
  text-align: center;
  vertical-align: middle;
  z-index: 10;
}

Так сортировать свойства просто: 10 из 10 моих знакомых знают английский алфавит; 10 из 10 пользователей Саблайма способны нажать F5, чтобы отсортировать блок свойств по алфавиту.

При такой сортировке не нужно думать. Вообще. Поэтому она отлично подходит пишущим, но не понимающим, CSS ребятам.

Что не так с сортировкой по алфавиту, чем она меня смущает? Она не помогает с чтением стилей: чтобы понять, как блок расположен, приходится читать все свойства.

Кроме того, сортировка по алфавиту затрудняет работу со связанными свойствами: width и height, position и left слишком далеко друг от друга. Придется скакать от строчки к строчке, чтобы переместить абсолютно спозиционированный блок.

Ну, все, я заканчиваю писать свое первое письмо. Что скажешь, Оптимус? Как тебе сортировка по алфавиту? Что использовали на Кибертроне?

P. S. Когда ты снова будешь сниматься в нормальном кино?

Два способа признаться в ненависти к человечеству

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

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

Уважаемый(ая), капитан-командор Васюша!

Извените за задержку …

Орфографические ошибки, «ый(ая)» замедляют восприятие, останавливают на себе внимание пользователя. Человек спотыкается при чтении и теряет свое время.

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

Не надо так. Хватит страданий.


Обязательно посмотрите: