Заметки Андрея Романова

Пишу о жизни, учёбе и работе

Ctrl + ↑ Later

Минутка рефакторинга №1

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

Задача:

Раз в t секунд последовательно выводить в консоль порции по n значений из массива, пока не будут выведены все элементы.

Исходное решение:

var myArr = ["Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua", "Ut", "enim", "ad", "minim", "veniam", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat"];
var i = myArr.length;
var x = 0;

var step = 10; // шаг смещения

function offset(x){
var y = x;
  while(x<y+step && x<i){
    console.log(myArr[x]);
    x++;
  };
};

function start(){
  offset(x);
  console.log('pause');
  x = x+step;
  if(x>i){ clearInterval(timer) };
};

var timer = setInterval(start, 1000);

Начнём с того, что тестовые данные можно сгенерировать динамически, благо значения элементов массива нам не важны:

// было
var myArr = ["Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua", "Ut", "enim", "ad", "minim", "veniam", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat"];

// стало
var myArr = Array.from({ length: 36 }, (value, index) => index);

Причёсываем код для лучшей читаемости (исправляем отступы и добавляем недостающие пробелы). Выносим интервал в отдельную переменную для гибкости. Интервал и количество выводимых значений за одну итерацию обозначаем прописными буквами, как константы. Количество выводимых за одну итерацию значений при этом переименовываем из step в ITEMS_PER_ITERATION. Заменяем var на const или let в зависимости от того, переприсваивается ли переменная в дальнейшем. Убираем точки с запятой после объявления функций, циклов и условий, они там не нужны. Переименовываем timer в intervalId, что точнее отражает суть значения. Избавляемся от микрооптимизации в виде сохранения длины myArr в переменную i — во-первых, название переменной никак не отражает её суть, во-вторых, преждевременная оптимизация лишь портит читаемость и усложняет код:

const ITEMS_PER_ITERATION = 10;
const INTERVAL = 1000;

const intervalId = setInterval(start, INTERVAL);
const myArr = Array.from({ length: 36 }, (value, index) => index);
let x = 0;

function offset(x) {
  const y = x;

  while(x < y + ITEMS_PER_ITERATION && x < myArr.length){
    console.log(myArr[x]);
    x++;
  }
}

function start() {
  offset(x);
  console.log('pause');

  x = x + ITEMS_PER_ITERATION;

  if (x > myArr.length) {
    clearInterval(intervalId);
  }
}

Сейчас есть две функции start() и offset(), с первого взгляда непонятно, что они делают. Самое ужасное — они обе изменяют переменную x. Но не всё так просто! На первый взгляд может показаться, что они изменяют одну и ту же переменную. Однако в функции offset() x — это аргумент, и он создаёт локальную переменную x. Такие переменные называются связанными — они непосредственно связаны с функцией, в которой они используются. Если бы у offset() не было аргументов, мы бы работали с внешней переменной x. Если переменная не связана, она называется свободной.

Итак, избавимся от излишней сложности со свободными и связанными переменными. Функция offset() по сути должна выводить в консоль ITEMS_PER_ITERATION элементов массива. Изменим её сигнатуру — аргумент x заменим на array, а саму функцию переименуем в logArrayItems. Ну и исправим функцию start(), чтобы она правильно работала с logArrayItems:

const ITEMS_PER_ITERATION = 10;
const INTERVAL = 1000;

const intervalId = setInterval(start, INTERVAL);
const myArr = Array.from({ length: 36 }, (value, index) => index);
let x = 0;

function logArrayItems(array) {
  array.forEach((item) => console.log(item));
}

function start() {
  logArrayItems(myArr.slice(x, x + ITEMS_PER_ITERATION));
  console.log('pause');

  x = x + ITEMS_PER_ITERATION;

  if (x > myArr.length) {
    clearInterval(intervalId);
  }
}

Теперь выглядит попроще, но всё далеко от идеала. Наше решение не выглядит целостным. Чтобы это исправить, обернём его в функцию logArrayItemsSequentially, которая будет принимать массив, элементы которого надо вывести в консоль, и настройки — количество выводимых за итерацию элементов и интервал в миллисекундах. Хорошей практикой считается обязательные параметры передавать в функцию как есть, а все опциональные параметры передавать в виде объекта (при этом опциональным параметрам нужно задать значения по умолчанию). Это позволяет сократить код при обычном использовании функции, но не потерять гибкость и оставить возможность переопределения настроек:

function logArrayItemsSequentially(array, options = {}) {
  const {
    itemsPerIteration = 10,
    interval = 1000,
  } = options;
  const intervalId = setInterval(start, interval);
  let x = 0;

  function logArrayItems(array) {
    array.forEach((item) => console.log(item));
  }

  function start() {
    logArrayItems(array.slice(x, x + itemsPerIteration));
    console.log('pause');

    x = x + itemsPerIteration;

    if (x > array.length) {
      clearInterval(intervalId);
    }
  }
}

const sampleArray = Array.from({ length: 36 }, (value, index) => index);

logArrayItemsSequentially(sampleArray/*, здесь могли быть переопределённые настройки */);

Отлично, теперь у нас есть целостное решение — функция, которую можно даже опубликовать на NPM (но лучше не надо). Снаружи её использование выглядит просто и понятно. Наведём порядок изнутри.

Для начала избавимся от функции logArrayItems — она используется лишь в одном месте:

// было
function logArrayItems(array) {
  array.forEach((item) => console.log(item));
}

function start() {
  logArrayItems(array.slice(x, x + itemsPerIteration));
  // ...
}

// стало
function start() {
  array.slice(x, x + itemsPerIteration).forEach((item) => console.log(item));
  // ...
}

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

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

Такое же поведение мы можем реализовать намного проще. Нам не нужны вложенные функции, счётчики и setInterval — нам нужны рекурсия и setTimeout! Тело функции logArrayItemsSequentially будет обрабатывать только одну итерацию, а переход к следующей итерации будет делаться рекурсивным вызовом logArrayItemsSequentially. Разберём решение:

function logArrayItemsSequentially(array, options = {}) {
  const {
    itemsPerIteration = 10,
    interval = 1000,
  } = options;
  // элементы, которые нужно вывести в консоль на текущей итерации
  const currentPortion = array.slice(0, itemsPerIteration);
  // оставшиеся элементы
  const tail = array.slice(itemsPerIteration);

  // сразу делаем вывод в консоль
  currentPortion.forEach((item) => console.log(item));
  console.log('pause');

  // если остаток не пустой, передаём его в следующую итерацию,
  // выполнение которой откладываем с помощью setTimeout
  if (tail.length) {
    setTimeout(() => logArrayItemsSequentially(tail, options), interval);
  }
}

const sampleArray = Array.from({ length: 36 }, (value, index) => index);

logArrayItemsSequentially(sampleArray/*, здесь могли быть переопределённые настройки */);

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

/**
 * Исходное решение
 */

var myArr = ["Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua", "Ut", "enim", "ad", "minim", "veniam", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat"];
var i = myArr.length;
var x = 0;

var step = 10; // шаг смещения

function offset(x){
var y = x;
 while(x<y+step && x<i){
   console.log(myArr[x]);
   x++;
 };
};

function start(){
	offset(x);
 console.log('pause');
 x = x+step;
 if(x>i){ clearInterval(timer) };
};

var timer = setInterval(start, 1000);

/**
 * Отрефакторенное решение
 */

function logArrayItemsSequentially(array, options = {}) {
  const {
    itemsPerIteration = 10,
    interval = 1000,
  } = options;
  const currentPortion = array.slice(0, itemsPerIteration);
  const tail = array.slice(itemsPerIteration);

  currentPortion.forEach((item) => console.log(item));
  console.log('pause');

  if (tail.length) {
    setTimeout(() => logArrayItemsSequentially(tail, options), interval);
  }
}

const sampleArray = Array.from({ length: 36 }, (value, index) => index);

logArrayItemsSequentially(sampleArray/*, здесь могли быть переопределённые настройки */);

Мой опыт работы в Рамблере

Так вышло, что с 10 октября по 23 ноября 2016 года я работал фронтенд-разработчиком в отделе рекламных технологий Рамблера.

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

Помимо меня в отделе было ещё семь фронтендеров: один джун, один тимлид и пять мидлов.

Я занимался поддержкой и развитием «Лета», системы для размещения баннеров на площадках RAMBLER&Co. Система была написана на ПХП, Реакте и Редаксе, ну и писалась она в довольно сжатые сроки (впрочем, так бывает всегда).

Про первый рабочий день

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

Бейдж-пропуск

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

Про работу

Нормально развернуть проект не получилось. Ну, формально получилось, но на деле локально не грузилось большинство картинок, потому что картинки хранились не в какой-нибудь общедоступной CDN, а непонятно где. Зачем-то прямо в репозитории хранились билд-файлы и композеровские пакеты, и всё это ломалось на каждый чих. С фронтендом всё тоже было не в порядке. В проекте не было Автопрефиксера! А команда, которая писала проект, никогда раньше не работала с Реактом и Редаксом, что вылилось в огромные компоненты длиной более чем в тысячу строк, в дебрях которых напрямую изменялось состояние и делались другие страшные вещи, о которых я рассказывать не буду, иначе вы сегодня не заснёте. Тестов почти не было (ну, была парочка на весь проект).

Кодревью чаще всего ограничивалось комментариями «ты здесь забыл убрать console.log». Сомневаюсь, что я писал настолько хороший код. В Иннове мой тимлид (спасибо, Антон) всегда оставлял комментарии не только про код, но и про решение в целом (архитектура, масштабируемость и все дела).

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

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

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

Про увольнение

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

Когда я сообщил о том, что собираюсь уходить, тимлид с техдиром долго уговаривали меня остаться и пытались понять, почему я ухожу. Я рассказал, что мне не понравилось и привёл в пример Яндекс и Авито, сказав, какие вещи там лучше. Кажется, я немного обидел техдира, когда он на вопрос «в Авито, по твоему мнению, специалисты опытнее наших?» получил ответ «да». Помимо прочего, мне предложили зарплату почти в полтора раза больше прежней, так что если вдруг захотите получать больше, сообщите начальнику, что собираетесь уволиться ;–).

Про условия работы в целом

Компания согласилась оплатить мой перелёт из Омска в Москву, что довольно неплохо. Правда по условиям трудового договора я был обязан вернуть деньги за билет, если проработаю в компании меньше года. В итоге выделенную на билет сумму просто вычли из моей последней зарплаты, ибо год я не проработал. Но и тут без фейлов не обошлось. Через какое-то время после увольнения мне позвонили и сказали, что вычли 10 000 ₽ (столько выделили на перелёт), а билет стоил 9 500 ₽, и, мол, мне нужно приехать в офис Рамблера и заполнить какую-то бумажку, чтобы получить обратно разницу.

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

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

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

Брендированные печеньки

«Кофе, чай, печеньки» — не ведитесь на это. Печеньки бывали только с утра и расходились они буквально за полчаса. Зато однажды печеньки были с логотипом компании! Что касается чая и кофе, на нашем этаже была кухня с двумя кофемашинами и двумя чайниками. Кофемашины периодически нужно было очищать от кофейной гущи, а ещё в них заканчивалась вода и нужно было самому доливать её туда из огромной бутыли, используемой обычно в кулерах. Здесь, конечно, можно сказать «да ты совсем зажрался», но я просто сравниваю с Яндексом или Авито, в них почему-то о таких вещах беспокоиться не приходится.

Где-то я слышал про бесплатные завтраки. Они оказались скидочной картой в соседнее кафе номиналом около 120 ₽ (точно не помню), на это можно было вроде как купить кашу и напиток. Эти карты были одноразовые, так что если вы желали завтракать каждый день, нужно было каждый день утром идти в другой корпус, чтобы получить карту.

Рабочее место Опенспейс

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

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

Митапы и конференции можно было посещать в счёт рабочего времени. Компания даже оплачивала билет на конференцию при обосновании необходимости посещения этой конференции.

Результаты

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

Чему учиться в 20** году, если вы — фронтенд-разработчик

  1. Изучайте разные парадигмы программирования (императивное, функциональное, логическое, автоматное программирование). Не нужно следовать только одной парадигме — каждая из них может оказаться полезной в зависимости от задачи.
  2. Научитесь писать (и пишите!) тесты. Автоматическое тестирование:
    — намного дешевле ручного;
    — помогает выявлять баги ещё на этапе разработки;
    — вселяет в вас уверенность в своём коде;
    — поможет вам убедиться, что вы ничего не сломали очередным рефакторингом.
  3. Не изучайте новые фреймворки и библиотеки; изучайте подходы, лежащие в их основе.
  4. Изучайте структуры данных и связанные с ними алгоритмы: списки, графы (в частности деревья), битовые карты, хеш-таблицы. Это расширит ваш кругозор и вы станете видеть более эффективные и простые способы решения ежедневных задач.
  5. Учитесь думать о задачах в мире бизнеса, а не в мире разработки. Помните о том, что программистам платят за решение задач бизнеса, а не за количество написанных строчек кода. Думайте о бизнесе, прежде чем переписывать проект с нуля на очередном модном фреймворке. Думайте о бизнесе, когда нужно определиться со списком поддерживаемых браузеров — если клиенты бизнеса пользуются IE8, ваша работа поддержать его, а не выводить надпись «Ваш браузер устарел, обновитесь!!!».

Универсальный совет — изучайте фундаментальные, проверенные временем подходы и приёмы, а не меняющиеся каждые n месяцев инструменты.

О состоянии потока

Людвиг Быстроновский в одной из своих лекций сказал, что состояние потока — зло.

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

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

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

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

Пока, 2016

Как водится, пора подводить итоги года.

Омск и военкомат

В начале года стартап, в котором я работал, развалился, и я стал искать новую работу в Омске. Меня позвали в местный Сбертех, и их служба безопасности попросила какую-то справку из военкомата. Проблема была в том, что я в военкомате на учёте не стоял и даже не имел приписного удостоверения. Делать нечего, я подготовился и ближе к концу мая пошёл в военкомат вставать на учёт. Это было ошибкой, потому что в то же время мне пришло письмо из Яндекса о том, что я прошёл отбор в ШРИ и меня ждут в июле в Москве. К счастью, мне каким-то непонятным образом удалось решить все дела с военкоматом примерно за месяц, и я освободился до конца июня.

Школа разработки интерфейсов

В начале года был анонсирован набор в ШРИ Яндекса. Я без особой надежды сделал тестовое задание, отправил его и благополучно забыл об этом. Ближе к лету мне прислали приглашение, и в июле я уже был в Москве. Яндекс любезно оплатил всем иногородним участникам проживание в хостеле, и это было круто в плане постоянного общения с другими ребятами. Вообще, общение и совместная работа — пожалуй, самые ценные вещи, которые были за эти три месяца. Сама учёба тоже была очень полезна — я, наконец, разобрался с некоторыми темами, которые постоянно откладывал на потом. На втором этапе мы полтора месяца делали свой продукт в команде с дизайнером и менеджером. Изначально я хотел выстроить правильный процесс разработки и делать кодревью, но сжатые сроки всё испортили и в итоге мы просто фигачили код, комментируя и обсуждая всё устно по ходу дела. Зато мы успели сделать законченный продукт, которым даже можно пользоваться, пусть и с некоторыми неудобствами.

Переезд в Москву

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

Работа в Авито

После выхода на работу в Рамблер я понял, что мои ожидания были чрезмерно завышены. Урок на будущее: прежде чем идти работать в какую-то компанию, стоит неформально пообщаться с кем-нибудь из её сотрудников. Я задумался о смене места работы, и тут мне очень удачно позвонили из Авито и пригласили на собеседование (а всё благодаря вам, Рашит и Яна, спасибо). Я прошёл все этапы (собеседования с руководителем отдела фронтенда, с тимлидами, с эйчаром) и в начале ноября получил офер, который с радостью принял. Забавно совпало, что из Рамблера я уволился в свой день рождения, 21 ноября. С 23 ноября я работаю в Авито, и ни одного плохого слова в адрес компании или сотрудников сказать не могу — всё круто!

Какие планы на 2017?

Пусть опубликованные здесь цели будут дополнительным стимулом к их выполнению:

— начну заниматься английским с преподавателем;
— переделаю приложение, сделанное в ШРИ, чтобы не было стыдно его показывать;
— оживлю Книжную полку фронтендера;
— пройду все курсы из профессии «Фронтенд-разработчик» на Хекслете;
— начну больше читать бумажные книги (хватит уже всё время в экраны пялиться) и, возможно, выкладывать сюда рекомендации книг;
— прорешаю СИКП хотя бы наполовину (попробую, но не уверен в этом).

Ctrl + ↓ Earlier