Это отличное ката для начинающих, если вы только начинаете изучать JavaScript. Или, возможно, вы хотите разогреться для некоторых технических тестов; если да, то это классический вопрос на собеседовании. Мы будем использовать разработку через тестирование (TDD), что опять-таки отлично подходит для демонстрации на собеседовании.

Есть 2 немного разных FizzBuzz — один, который создает массив чисел до выбранного числа, и другой, в котором вы вводите число, и оно подтверждает, является ли это просто числом, «Fizz», «Buzz» или «FizzBuzz».

В этом посте будет подробно рассмотрен первый. Учитесь вместе со мной, пока я занимаюсь рефакторингом и исследую TDD, и если у вас есть какие-либо предложения или улучшения, пожалуйста, не стесняйтесь комментировать. Если вам понравился этот пост, вы можете дать 50 хлопков 👏, что поможет другим найти его ☺️ подписка также поможет алгоритму и позволит вам перейти к большему количеству контента. писать.

Что такое ката FizzBuzz?

Выводить целые числа (числа) от 1 до N (например, N может быть… 100)
Выводить «Fizz», если целое число делится на 3
Выводить «Buzz», если целое число делится на 5
И выведите «FizzBuzz», если целое число делится и на 3, и на 5.

Это будет выглядеть примерно так: учитывая число 15, будет возвращено:
1, 2, Шипение, 4,Жужжание, Шипение, 7, 8,Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz.

Обратите внимание, что 15 будет FizzBuzz, так как оно делится и на 3, и на 5.

Шаг 1: Критерии принятия и вопросы

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

Извлеките из ката как можно больше подсказок.

Вызывает ли это какие-либо вопросы, которые мы должны прояснить?

Вывести целые числа от 1 до N

N не указан в ката, поэтому это будет аргумент, который мы передаем в функцию. Это может быть любое число…

  • Следовательно, нужно уточнить, всегда ли N положительное число? Может ли N быть отрицательным числом? На этом этапе стоит спросить интервьюера, так как это повлияет на тесты, которые мы пишем.

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

Кроме того, это список чисел, образующих предсказуемый шаблон (например, +1 каждый раз), поэтому цикл было бы неплохо изучить это решение.

Выведите «Fizz», если целое число делится на 3

Полезно знать — нам нужно знать, как подтвердить, что число полностью делится на 3. Подробнее об этом позже (подсказка: по модулю!)

Выведите «Buzz», если целое число делится на 5

Обратите внимание, насколько похоже это требование на предыдущее — может быть, мы можем повторно использовать некоторую логику?

И «FizzBuzz», если целое число делится и на 3, и на 5.

Вы заметили закономерность, когда нас просят добавить «Buzz» к «Fizz»? Удобно, что он не запрашивает, скажем, слово «лимонад», если оно делится на 3 и 5. То, что он запрашивает «FizzBuzz», намекает на потенциальный подход — обратите внимание, что он объединяет заданные слова по предыдущим критериям приемлемости.

Шаг 2: Давайте подумаем о тестах

Кент Бек лучше всего сказал об этом в «Разработке через тестирование на примере»:

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

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

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

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

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

Итак, давайте начнем с малого, с основ, а затем построим — давайте не будем сразу беспокоиться о «Шиззе» и «Баззе», первое, что нужно передать в N и получить список обратно.

Когда задано число N, он возвращает массив от 1 до числа N.
Стоит подумать, что, возможно, мы просто сначала передаем число «2» и ожидаем массив [1, 2]

Теперь мы можем начать думать о «Fizz» и т. д.

Если число в массиве делится на 3, выведите «Fizz»
Тогда мы можем построить это, передав, скажем, «3», и ожидать [1, 2, «Fizz ”]

Если число в массиве делится на 5, выведите «Buzz»
Теперь мы можем передать 5 и ожидать [1, 2, «Fizz», 4, «Buzz»]

И наконец -

Если число в массиве делится на 3 и 5, выведите «FizzBuzz»
Способ вычислить первое число, которое делится на 3 и 5, состоит в том, чтобы на самом деле умножить 3 x 5 = 15. Если бы мы перешли в 15 к нашему FizzBuzz, мы бы ожидали:
[1, 2, "Fizz", 4,"Buzz","Fizz ”,7, 8,"Шипение","Базз",11, "Шипение" , 13, 14,"FizzBuzz"]. Однако мы, вероятно, хотим выйти за рамки этого, а также протестировать 30. Подробнее об этом позже.

Большой совет для младших разработчиков

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

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

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

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

Шаг 3: Настройте свой проект JavaScript и тестируйте Jest с нуля.

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

Следуйте этому руководству здесь.

Как только вы это сделаете — вперед!

Шаг 4: Давайте напишем первый тест TDD

Когда задано число N, он возвращает массив от 1 до числа N.

Я бы сказал, что очень важно продемонстрировать TDD, если вы участвуете в техническом тестировании, и иметь возможность рассказать о причинах, почему вы это делаете. Причин много, но вот основные из них:

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

Вот как подходить к каждому тесту TDD, и обязательно говорите об этом на собеседовании:

🔴 Красный — напишите тест, который не проходит (мы еще не написали код для его прохождения).
🟢 Зеленый — затем напишите код, чтобы сделать пройдите тест, напишите быстро, это может быть грязно.
♻️ Рефакторинг — приведите в порядок написанный вами код и убедитесь, что он все еще проходит.

Тогда расслабьтесь и наслаждайтесь зелеными галочками прохождения тестов. ✅ Ааааа. 😎

Вам может быть интересно, с какой стати сначала писать неудачный тест (красный этап)? Что ж, представьте, что вы случайно написали тест, который давал ложноположительный результат (например, прохождение теста), несмотря на то, что у вас нет правильного кода решения — или вообще кода! Тест будет возвращать положительный результат теста (зеленая галочка), когда он не должен, даже если ваш код не обязательно правильный или даже не существует. Поэтому это не лучший тест. Помните, что ваш тест предназначен для того, чтобы убедиться, что ваш код правильный.

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

В нашем тестовом файле мы всегда будем стремиться сначала написать тест,переднаписанием нашего кода. Вот почему он тестируется. Это начнется с «красного» сценического теста.

В нашем тестовом файле давайте код:

// fizzbuzz.test.js
const fizzBuzz = require("../src/fizzbuzz")
describe("fizzBuzz tests", () => {
test("When given a number, N, it returns an array from 1 to the number N", () => {
// this is where we'll write our test
}}

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

Теперь давайте рассмотрим, как мы напишем в нем тест.

Шаг 5: Организуйте Act Assert

Упорядочить, Действовать, Утвердить — хороший подход, когда речь идет о TDD. Мне также нравится настраивать переменные для ожидаемого и фактического — я думаю, что это делает его более читабельным. Что вы думаете?

Помня о первых критериях приемлемости (AC), которые мы составили, вот как проходит А-А-А.

// fizzbuzz.test.js file
const fizzBuzz = require("../src/fizzbuzz");
describe("fizzBuzz tests", () => {
test("When given a number, N, it returns an array from 1 to the number N", () => {
// ARRANGE THE SETUP FOR THE TEST
let N = 2;
let expected = [1, 2]
// ACT - CALL YOUR METHODS!
let actual = fizzBuzz(N);
// ASSERT WHAT YOU WANT TO HAPPEN
expect(actual).toEqual(expected);
}); // (slip in the next test here - more on that later) 
});

Давайте просто поговорим об этой строке:

ожидать(фактическое).быть(ожидаемое)

Что это на самом деле означает? Давайте прочитаем в разбивке:

Вывод fizzBuzz(2) (фактически то, что мы собираемся сделать) должен быть таким же, как мы ожидаем — то есть [1, 2] .

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

Теперь, когда все настроено, это дает нам возможность приступить к написанию кода; мы знаем, чего ожидать от результата кода. Нажмите «Сохранить» то, что вы сделали в тестовом файле.

Затем без дальнейших церемоний введите npm test в своем терминале, и вы увидите, что он выдает ошибку при запуске теста. Это нормально — это импорт пустой функции fizzBuzz. Поздравляем — вы только что прошли красную стадию теста 1! Теперь займемся зеленым :)

Шаг 6: Давайте напишем первый фрагмент кода, чтобы покрасить город в зеленый цвет.

В VS Code вы сможете перейти к файлу fizzbuzz.js и открыть его. Давайте нарисуем базовую функцию fizzBuzz, если у вас ее еще нет:

// fizzbuzz.js file 
const fizzBuzz = () => {
// our function will go in here
}
module.exports = fizzBuzz

Помните, наш тест помог нам начать работу — хороший TDD!:

«Когда задано число N, он возвращает массив от 1 до числа N»

Итак, мы знаем, что передаем N:

const fizzBuzz = (N) => {
// our function will go in here
}
module.exports = fizzBuzz

Мы знаем, что хотим перебирать числа по 1 за раз, двигаясь вверх. Цикл for кажется хорошим оружием. У нас есть все необходимые для этого компоненты:

  • Мы хотим, чтобы он начинался с 1, поэтому мы можем написать i = 1
  • N будет последним числом, на котором он остановится, поэтому мы можем написать i ≤ N
  • Мы хотим подняться на 1 число за раз, поэтому мы можем написать i++
const fizzBuzz = (N) => {
for (i = 1; i <= N; i++) {
console.log(i)
}
}
fizzBuzz(2)
module.exports = fizzBuzz

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

  • Запустите тесты, выполняющие npm test, в терминале, когда я нахожусь в корневой папке проекта — это покажет мне, что тест ожидает, зеленым цветом, а что он получает красным.
  • Я могу использовать console.log, чтобы увидеть, что возвращает функция — обратите внимание, что я делаю это внутри цикла for и увижу это напечатанным в терминале.
  • Я также могу вызвать функцию в нижней части файла и передать ей число, как показано в строке 21 ниже fizzBuzz(2). Я могу запустить это, набрав node fizzbuzz.js — это запустит файл JavaScript без запуска набора тестов.

Попробуйте эти 3 и посмотрите, какие результаты вы получите.

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

Я вижу, что мой console.log печатает 1 и 2, так что теперь я могу пойти дальше и поместить его в массив.

Давайте изменим код:

// fizzbuzz.js
const fizzBuzz = (N) => {
let array = []
for (i = 1; i <= N; i++) {
array.push(i)
}
return array
}
module.exports = fizzBuzz
  • Я создал пустой массив вне цикла let array = []. Слишком часто я случайно создавал его внутри цикла, что, если бы мы это сделали, просто означало бы, что массив сбрасывается обратно в [] каждый раз, когда цикл проходит.
  • Я изменил console.log(i) на array.push(i), что будет добавлять i каждый раз, когда цикл повторяется, в массив. Помните, что i — это число, и оно будет меняться каждый раз — поэтому я начну с 1, затем 2 и так далее, поскольку мы делаем i++ каждый раз, когда цикл повторяется.
  • Снова вне цикла я добавил массив возврата, который будет запущен после завершения цикла. Это вернет мой прекрасный массив, в который были вставлены все числа! И это также должно остановить нас от получения «неопределенного» — ура!

Теперь мы можем снова запустить наш первый тест с npm test. Бум! Теперь тест проходит. Зеленая галочка привет 👏

Можем ли мы провести рефакторинг? Подумайте. На данном этапе единственное, что бросается в глаза, — это лишние комментарии в тестовом файле, так что давайте удалим их и приведем в порядок к следующему тесту.

Шаг 7: Тест 2 входящий

Если число в массиве делится на 3, выведите «Физз»

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

// fizzbuzz.test.js
const fizzBuzz = require("../src/fizzbuzz");
describe("fizzBuzz tests", () => {
test("When given a number, N, it returns an array from 1 to the number N", () => {
let N = 2;
let expected = [1, 2];
let actual = fizzBuzz(N);
expect(actual).toEqual(expected);
});
test("If the number in the array is divisible by 3, print 'Fizz'", () => {
// ARRANGE
// ACT
// ASSERT
});
});

Круто — вот как бы я попробовал то, что мы изначально наметили:

const fizzBuzz = require("../src/fizzbuzz");
describe("fizzBuzz tests", () => {
test("When given a number, N, it returns an array from 1 to the number N", () => {
let N = 2;
let expected = [1, 2];
let actual = fizzBuzz(N);
expect(actual).toEqual(expected);
});
test("If the number in the array is divisible by 3, print 'Fizz'", () => {
// ARRANGE
let N = 3;
let expected = [1, 2, "Fizz"];
// ACT
let actual = fizzBuzz(N);
// ASSERT
expect(actual).toEqual(expected);
});
});

Запустите npm test, теперь у нас должен быть 1 пройденный тест (тест 1) и 1 не пройденный (красный этап этого теста).

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

Если число в массиве делится на 3, выведите «Физз»

  • Как мы можем подтвердить, что передаваемое число делится на 3?

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

10/3 = 3 раза, с 1 остатком (не делится)
9/3 = 3 раза, без остатка (делится на 3!)
8/3 = 2 раз, с остатком 2 (не делится)
7 / 3 = 2 раза, с 1 остатком (не делится)
6 / 3 = 2 раза, с без остатка (делится на 3!)

Итак, вы видите закономерность? Числа, которые делятся на 3, не имеют остатка при делении на 3.

Следовательно, мы можем выдвинуть гипотезу:

Доказывая, что число не имеет остатка при делении на 3, это означает, что мы нашли число, которое действительно делится на 3.

Как бы мы написали это в коде? Ну, есть оператор, который идеально подходит для установления, есть остаток или нет. Этот оператор по модулю имеет красивый знак %.

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

10 % 3 = 1

Таким образом, я читаю это выражение так: всякий раз, когда я вижу знак процента, мой мозг читает его как '10, деленное на [число после знака %, например, 3], и остается остаток*по модулю!* (сказано в стиле абракадабры).

10 % 3 = 1 … иначе «10 разделить на 3 дает остаток… по модулю 1!»

Как работать по модулю в коде

Что я обычно делаю, так это bash в небольшой console.log:

Затем запустите node fizzbuzz.js, чтобы запустить файл кода. Это должно вернуть это в консоль:

Выделим числа, которые делятся только на 3 без остатка, используя наш друг по модулю. Почему? Потому что мы хотим, чтобы они все превратились в "Физз"!

Сначала напишем в псевдокоде:

// если число при делении на 3 имеет остаток 0… return Fizz

Теперь займемся кодом! Помните, что === означает «строго равно».

if(N % 3 === 0) {
return “Fizz”
}

Давайте пока подключим логику 🧠

  • Числа, разделенные на 3, у которых остаток 0, полностью делятся на 3 ✅
  • Modulo — отличный способ проверить, есть ли у числа остаток ✅
  • if (число % 3 === 0) удалит те числа, которые делятся на 3 и не имеют остатка 📣

Давайте поместим это вместе с нашим кодом. То, что я сделал здесь, находится внутри цикла, в основном, скажем, проверьте, делится ли это число i на 3 — если это так, нажмите «Fizz» в массив. Если это не так, перейдите к «еще» — нажмите число (i).

// fizzbuzz.js 
const fizzBuzz = (N) => {
let array = [];
for (i = 1; i <= N; i++) {
if (i % 3 === 0) {
array.push("Fizz");
} else {
array.push(i);
}
}
return array;
};
module.exports = fizzBuzz

!важный момент по объему тестов

Обратите внимание, что я помещаю array.push("Fizz"), а не возвращаю "Fizz". Это связано с тем, что return приведет к преждевременному завершению функции, когда она достигнет этого возврата.

Подумайте о последствиях этого. Если бы я поставил return «Fizz» и установил тест с N равным 3, это означало бы, что тест будет пройден — он напечатает [1, 2, «Fizz»]. Однако, если я поставлю N равным 6, тест завершится ошибкой, так как он напечатает только [1, 2, «Шипение»] — когда он достигает 3, он возвращается и выходит из функции.

Что мы можем извлечь из этого? Убедитесь, что у вас есть тесты широкого диапазона, а не слишком узкого масштаба.

Следуя этому совету, я знаю, что мой окончательный тест будет выполняться в диапазоне до 30. Предположим, что я скромно поместил return «Fizz» в свой код, тесты 1, 2 и 3 пройдут. Но когда я затем запускаю окончательный тест, ожидая массив до 30 элементов — тест 4 завершится неудачей — возвращая только [1, 2, «Шипение»]. Тогда это поможет мне обнаружить ошибку. Вот почему мы любим тесты. 🎉

В качестве дополнительной линии — если вам это нравится, попробуйте и поиграйте с:

  • Вместо этого вставьте этот код:
// fizzbuzz.js 
const fizzBuzz = (N) => {
let array = [];
for (i = 1; i <= N; i++) {
if (i % 3 === 0) {
return "Fizz"
} else {
array.push(i);
}
}
return array;
};
module.exports = fizzBuzz
  • Запуск теста с прохождением N as 3 и ожиданием [1, 2, «Шипение»] — тест должен пройти.

Теперь подумайте об альтернативе:

  • Представьте, если вы запустите тот же тест, но с N равным 6 — что вы настроите для своего теста? [1, 2, "шипение", 4, 5, "шипение"]?

С ошибкой «возврата Fizz» в нашем коде тест с N = 6 завершится неудачей и пометит ошибку кода «возврата Fizz», поскольку он не дойдет до 6, он дойдет только до [1, 2 , «Шипение»] до окончания цикла.

Представьте, что вы сохранили тест N as 6 и исправили свой код так, чтобы тест прошел [1, 2, «Шипение», 4, 5, «Шипение»].

В следующем фрагменте кода, который вы реализуете, он заменит числа, делящиеся на 5, на «Buzz». Затем наш тест N as 6 перешел бы от прохождения к провалу ❌, поскольку этот тест затем начал бы получать [1, 2, «Шипение», 4, «Жужжание», «Шипение»], но ожидал [1, 2, «Шизз», 4, 5, «Шизз».

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

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

Итак, давайте подведем итоги того, что доказывают наши тесты:

Тест 1: создается массив чисел, увеличивающихся на 1, от 1 до N — тестирование [1, 2] с N равным 2 — 👇 не идеально, хотелось бы протестировали более широкий диапазон, но я опасаюсь, что любые другие тесты не сработают, когда мы начнем вводить Fizzes и Buzzes. ✅ Но 1, 2 показывает, что он возвращает массив, и число увеличивается до N.

Тест 2: числа, делящиеся на 3, печатают «шипение», где N равно 3 — проверка [1, 2, «шипение»] — опять же, на этом этапе нельзя расширяться. не сталкиваясь с проблемами «Buzz», поскольку мы еще не доказали это.

Тест 3.Число, делящееся на 5, выводит "Buzz" — на данный момент мы доказали, что число, делимое на 3, выводит "Fizz", так что это нормально, если в строке есть "Fizz". подведены итоги — тестирование [1, 2, «Шипение», 4, «Шипение», «Шипение», 7, 8, «Шипение», «Шипение»]. У нас нет большего в рамках ката, например. нет числа, делящегося на 7, которое должно печатать «Мангуст», поэтому мы в порядке, чтобы иметь более длинный поводок в этом тесте. Этот тест можетфактически дойти до N как 14, но мы все равно должны помнить, что у нас есть еще одна кривая с «FizzBuzz», которая будет применяться к номеру 15. .

Тест 4.Наконец, мы проверяем, что числа, делящиеся на 3 и 5, производят "FizzBuzz". На этом этапе мы можем охватить пункты 1, 2 и 3 в тестировании, как мы их доказали — так что это должен быть тест, который является долгосрочным и более широким по охвату (например, мы могли бы перейти к 30 или больше, если мы хотим ). В конечном итоге это позволило бы выявить ошибки, которые предыдущие тесты могли не показать, например ошибку возврата «Fizz».

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

Круто, надеюсь, хорошая пища для размышлений! Итак, с тестом 2 как N вместо 3 и убедившись, что у вас есть код как array.push("Fizz") — вы можете сохранить свою работу и запустить нпм тест. Вы должны пройти еще один тест! Отлично, теперь мы сделали Red-Green… самое время для рефакторинга, если это возможно. На данном этапе, опять же, выглядит неплохо. Мы продолжим и, вероятно, проведем рефакторинг после третьего теста.

Шаг 8: Воспользуйтесь той же идеей для «Buzz».

Если число в массиве делится на 5, выведите «Buzz»

Мы уже заметили, что это очень похоже на предыдущий тест, но для «Жужжания» и кратных 5. Подражание — это величайшая форма лести, так что без лишних слов… (и давайте на этот раз увеличим N, учась на примере последний тест и сага о возвращении "Физза" — как уже упоминалось, это может дойти до 14. Мы останемся на 10, потому что, причины).

... test code from above...
test("If the number in the array is divisible by 5, print 'Buzz'", () => {
// ARRANGE 
let N = 10;
let expected = [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz"];
// ACT
let actual = fizzBuzz(N);
// ASSERT
expect(actual).toBe(expected);
});  
});

Запустив npm test, терминал сообщает нам, что он ожидал «Buzz», на самом деле он получил 5 и 10. Это хорошо, потому что мы находимся в красной фазе.

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

// если число при делении на 5 не имеет остатка… return Fizz

if(N % 5 === 0) {
return “Buzz”
}

Зеленая фаза подробнее

Когда вы пишете код, особенно на зеленом этапе — он может быть сколь угодно запутанным и многословным — цель состоит в том, чтобы быстро найти решение. Не переусердствуйте. И не беспокойтесь о рефакторинге, как бы заманчиво это ни было — это на этапе рефакторинга. Да, DRY (Don’t Repeat Yourself) может вылететь в окно, если вы этого захотите! 😮

Так что, если бы это был я, вбивающий код, я бы сделал это, чтобы пройти тест:

const fizzBuzz = (N) => {
let array = [];
for (i = 1; i <= N; i++) {
if (i % 3 === 0) {
array.push("Fizz");
} else if (i % 5 === 0) {
array.push("Fizz");
} else {
array.push(i);
}
}
return array;
};
module.exports = fizzBuzz

Хорошо! npm test теперь должен дать нам 3 пройденных теста — ух ты!

Подробнее о рефакторинге

Теперь мы находимся в лучшем положении для рефакторинга. Я готов убрать некоторые скобки в первую очередь — мы можем удалить { }, если поместим код в строку с условием — это означает, что это больше не блок кода, поэтому не нужно {}.

Не забудьте перезапустить npm test, чтобы убедиться, что вы не подделали код при рефакторинге. Ооо, разве это уже не выглядит лучше?!

Верно, амигос, мы переходим к последнему тесту. Это сочная, которую вы все ждали!

Шаг 9: Окончательный обратный отсчет (ошибаетесь, тест!)

Если число в массиве делится на 3 и 5, выведите «FizzBuzz»

Это самое интересное для записи ожидаемых значений, я избавлю вас от хлопот:

// fizzbuzz.test.js
test("If the number in the array is divisible by 3 and 5, print 'FizzBuzz'", () => {
// ARRANGE
let N = 30;
let expected = [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz", 16, 17, "Fizz", 19, "Buzz", "Fizz", 22, 23, "Fizz", "Buzz", 26, "Fizz", 28, 29, "FizzBuzz"]
// ACT
let actual = fizzBuzz(N);
// ASSERT
expect(actual).toEqual(expected);
});
});

Вот теперь становится интересно. Итак, первое, что вы могли бы сделать, я, конечно, сделал, когда только учился, это — строка, которая в основном говорит, что если это делится на 3 и делится на 5, нажмите «FizzBuzz».

// fizzbuzz.js
const fizzBuzz = (N) => {
let array = [];
for (i = 1; i <= N; i++) {
if (i % 3 === 0) array.push("Fizz");
else if (i % 5 === 0) array.push("Buzz");
else if (i % 3 === 0 && i % 5 === 0) array.push("FizzBuzz")
else array.push(i);
}
return array;
};
module.exports = fizzBuzz;

Это не работает. Если мы передаем 15 этой функции, JavaScript сначала нажмет на if (i % 3 === 0) array.push("Fizz") и скажет: "О, да, 15 делится на 3, поэтому мы напечатаем " Физз».

Давайте переключим его и поместим наш новый оператор вверху:

// fizzbuzz.js
const fizzBuzz = (N) => {
let array = [];
for (i = 1; i <= N; i++) {
if (i % 3 === 0 && i % 5 === 0) array.push("FizzBuzz")
else if (i % 3 === 0) array.push("Fizz");
else if (i % 5 === 0) array.push("Buzz");
else array.push(i);
}
return array;
};
module.exports = fizzBuzz;

Теперь это будет работать при запуске npm test, потому что теперь он проверяет, делится ли N сначала на 15, затем на 3, затем на 5. Теперь мы готовы к нескольким вариантам рефакторинга, как вы можете видеть здесь. много повторений.

Рефакторинг — пикантная штука

Честно говоря, первое, что бросается в глаза, почему бы не убрать оператор && и не изменить его на 15?

// fizzbuzz.js
const fizzBuzz = (N) => {
let array = [];
for (i = 1; i <= N; i++) {
if (i % 15 === 0) array.push("FizzBuzz")
else if (i % 3 === 0) array.push("Fizz");
else if (i % 5 === 0) array.push("Buzz");
else array.push(i);
}
return array;
};
module.exports = fizzBuzz;

Это может быть вполне респектабельным решением в техническом тесте. Однако есть много способов снять шкуру с кошки. Готовы ли вы исследовать немного дальше? Помните, что Fizz и Buzz могут объединяться в FizzBuzz — это не ошибка; мы можем использовать это как подсказку. Итак, вот одно предложение, которое я получил из этого превосходного видео Тома Скотта. Главный реквизит для этого!

Итак, как нам исследовать нашу подсказку к конкатенации?

  • Этот маленький барсук прямо здесь приходит на ум +=, что в основном означает, что он берет исходное значение переменной слева и добавляет к нему значение того, что находится справа. Вот что вы могли бы сделать с FizzBuzz, например:
let a = "Fizz";
let b = "Buzz";
let c = (a += b);
console.log(c) // will give you "FizzBuzz"

Итак, как нам развить это мышление дальше? Ну, я думаю, что мы пытаемся убрать строку if (i % 15 === 0) array.push("FizzBuzz"). По крайней мере, кажется, что мы можем использовать конкатенацию, чтобы добавить «Buzz» к «Fizz», вместо того, чтобы сразу объявлять «FizzBuzz». Так что бы это оставило нас?

Вернемся к предыдущей версии нашего кода.. хм…

// fizzbuzz.js
const fizzBuzz = (N) => {
let array = [];
for (i = 1; i <= N; i++) {
if (i % 3 === 0) array.push("Fizz");
else if (i % 5 === 0) array.push("Buzz");
else array.push(i);
}
return array;
};
module.exports = fizzBuzz;

Переменные могут сделать нас более динамичными

Итак, глядя на это, я теперь думаю, хорошо, помещать «Fizz» в массив с помощью array.push(«Fizz») очень жестко. Это окончательно.

Переменные позволяют нам быть более динамичными. Вместо того, чтобы помещать «Fizz» в массив, почему бы нам не отправить «Fizz» в новую переменную.

Новая переменная, которая может оставаться «открытой» для других дополнений, например. += «Жужжание».

Как только цикл завершится, мы можем поместить эту новую переменную в массив.

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

Звучит нормально, не так ли? Давайте попробуем…

  • Давайте создадим эту новую переменную как пустую строку «», точно так же, как наш массив также является пустым массивом. Обратите внимание, что эта новая переменная внутри цикла по причинам, упомянутым выше.
  • Давайте изменим array.push("Fizz") на newVariable += "Fizz"
  • В том же духе, давайте так же с Buzz, чтобы быть newVariable += «Buzz»
// fizzbuzz.js
const fizzBuzz = (N) => {
let array = [];
for (i = 1; i <= N; i++) {
let newVariable = ""
if (i % 3 === 0) newVariable += "Fizz";
else if (i % 5 === 0) newVariable += "Buzz";
else array.push(i);
}
return array;
};
module.exports = fizzBuzz;

Итак, мы покрыли наши делимые части. Согласно этой теории, если появится число 15, оно сначала удовлетворит if (i % 3 === 0) и добавит «Fizz» к newVariable. Тогда он будет удовлетворять if (i % 5 === 0), а затем добавить «Buzz» дополнительно к newVariable, таким образом, делая newVariable равным «FizzBuzz». Так что это хорошее начало!

Что, если оно не делится на 3 или 5, например. это 7? Думаю, это следующая часть логики, над которой нам нужно работать. Для этого давайте удалим else array.push(i), так как это быстро становится избыточным — мы не будем помещать i в массив, поскольку мы реализовали нашу новую переменную, чтобы она была более гибкой и расширяемой. Мы будем помещать newVariable в массив, когда все будет сказано и сделано.

Итак, по этому совету нам действительно нужно обратить внимание на newVariable. Если цикл проходит дальше (i % 3 === 0) и (i % 5=== 0), а newVariable по-прежнему остается нетронутым как « », (например, если мы передаем его в 7), затем он подтверждает нам, что число, передаваемое в , не делится ни на 3, ни на 5. Это будет означать, что newVariable — это просто i — число в цикле. После того, как мы сформировали newVariable таким, каким он должен быть, newVariable — это то, что мы помещаем в массив.

Это псевдокод — теперь давайте его закодируем!

// fizzbuzz.js
const fizzBuzz = (N) => {
let array = [];
for (i = 1; i <= N; i++) {
let newVariable = ""
if (i % 3 === 0) newVariable += "Fizz";
if (i % 5 === 0) newVariable += "Buzz";
if (newVariable === "") newVariable = i;
array.push(newVariable)
}
return array;
};
module.exports = fizzBuzz;

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

Итак, теперь перевернуть это с ног на голову еще больше ... мы готовы ?!

Я просматривал комментарии к видео Тома Скотта и увидел комментарий Алексиса Манделиаса:

Создайте карту (объект в js), которая сопоставляет кратные (3, 5) со строками ("шипение", "жужжание"). Затем для каждого числа просмотрите каждый элемент на карте и, если ключ является кратным, добавьте значение в строку.

Ооооо, мне нравится, как это звучит! Готовы попробовать это?

Интерполяционные таблицы и скобки

Итак, я думаю, что Алексис имел в виду то, что мы могли бы создать объект, у которого есть ключи в виде чисел, на которые мы делим, и значения, являющиеся строками, известными как «таблица поиска».

const lookup = {
3: "Fizz",
5: "Buzz"
}

Следующая часть тогда, как мы это делаем?

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

Тогда давайте попробуем… да, мы будем делать цикл внутри цикла, но это нормально!

  • Наш существующий цикл for будет проходить через числа от 1 до N, увеличиваясь с шагом i++.
  • Затем наш новый цикл будет отвечать за просмотр ключей таблицы поиска, например, он будет проверять «3» и «5». Мы хотим, чтобы он зацикливал таблицу поиска для каждого нового значения i, потому что мы хотим проверить, есть ли совпадение между «i» и ключом в таблице поиска. Следовательно, новый цикл должен существовать внутри цикла for, чтобы он мог работать с каждым новым значением «i».
  • Если есть совпадение между «i» и ключом в таблице поиска, мы можем использовать значение таблицы поиска для этого совпадения, чтобы добавить к нашей новой переменной.
// fizzbuzz.js
const lookup = {
3: "Fizz",
5: "Buzz"
}
let array = [];
for (i = 1; i <= N; i++) {
let newVariable = ""
for (let key in lookup) {
      if (i % key === 0) { newVariable += lookup[key] }
    }
if (newVariable === "") newVariable = i;
array.push(newVariable)
}
return array;
};
module.exports = fizzBuzz;
  • Мы добавили нашу прекрасную таблицу поиска вне цикла. Из-за этого мы можем удалить жестко закодированные биты для if (i % 3 === 0) newVariable += «Fizz» и if (i % 5 === 0 ) newVariable += «Buzz»потому что мы, по сути, сохранили значения того, что нужно печатать в таблице поиска.
  • Мы сохранили в нашей новой переменной — это все еще то, чем мы будем манипулировать и помещать в массив, чтобы оно не менялось.
  • Мы добавляем цикл for…in, который будет использоваться внутри цикла for.
  • Мы сохраняем в нашем if (newVariable === "") newVariable = i; проверьте в конце, что не будет обрабатываться таблицей поиска.

Для… в цикле

Это выглядит сложно, но на самом деле это очень просто и отлично подходит для набора инструментов. Давайте пройдёмся по нему вместе. Также, если у вас есть минутка, прочтите Документацию Mozilla.

for (пусть ключ в поиске)
Это в основном говорит «для каждой записи в объекте поиска» — я установил слово как «ключ», но это может быть что угодно, «элемент» , «запись» в поиске — я просто нахожу «ключ» полезным, потому что он сохраняет ясность.

Ключ будет, например, "3", а значением будет "Шипение". Другой ключ будет «5», а значение будет «Buzz».

Цикл for in будет перебирать (то есть по одному) ключи в объекте поиска, чтобы увидеть, есть ли какие-либо совпадения с заданными нами условиями. Таким образом, он проверит 3, 5 и все остальное, что мы можем добавить в таблицу поиска.

«для каждого ключа в поиске»…

const lookup = {
3: "Fizz",
5: "Buzz"
}
for (let key in lookup)

тогда… если «i» после деления на ключ оставляет в остатке 0 (помните наш модуль fun ранее?), то мы знаем, что «i» полностью делится на ключ. например, если i равно 5, оно полностью делится на ключ 5, поэтому мы можем затем объединить значение в таблице поиска с новой переменной.

if (i % key === 0) { newVariable += lookup[key] }

Итак, подождите, что это за lookup[key]?

Это скобочное обозначение, или то, что Mozilla называет аксессорами свойств, что на самом деле имеет большой смысл. Это эквивалент записи через точку, например. вы можете написать lookup.3 (что даст Fizz или lookup.5 (что даст Buzz). Но по мере повторения ключей, например. , это проверка 3, это проверка 5, это проверка любых других, которые мы добавляем — проще использовать метод доступа к свойствам, который напрямую обращается к значениям этих ключей, и на самом деле не заботится о том, как эти ключи называются. это lookup[key], который будет печатать значения, это эквивалент lookup.3 (= Fizz) или lookup.5 (= Buzz).

Обозначение скобок — это не то, что сразу щелкнет, если вы чем-то похожи на меня, но поиграйте с этим.

Итак, что же все это значит?! Это был хороший рефакторинг?

Если мы оглянемся на наш код, то увидим, что теперь он потенциально намного более расширяемый. Допустим, мы хотели добавить в Mongoose вывод для любых чисел, кратных 2. Если число делится на 2, 3 и 5, мы должны получить число 30, печатающее «MongooseFizzBuzz».

const lookup = {
2: "Mongoose",
3: "Fizz",
5: "Buzz"
}
let array = [];
for (i = 1; i <= N; i++) {
let newVariable = ""
for (let key in lookup) {
      if (i % key === 0) { newVariable += lookup[key] }
    }
if (newVariable === "") newVariable = i;
array.push(newVariable)
}
return array;
};
console.log(fizzBuzz(60));
module.exports = fizzBuzz;

Давайте запустим console.log(fizzBuzz(60)), нажмем node fizzbuzz.js и посмотрим, как горит мир.

Цель рефакторинга

Я оставлю вас с мимолетной мыслью из комментариев к видео Тома Скотта, это из ciknay —

Какова цель вашего рефакторинга?

Есть ли у вас цель — заставить его использовать меньше памяти или сделать его более адаптируемым для будущего использования?

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

Надеюсь, вам понравился этот пост так же, как и мне! Если да, поддержите нас подпиской! Вы также можете поставить до 50 хлопков, что поможет другим найти ее. 👏

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

Хорошего дня и удачного кодирования.

@lucyironmonger в твиттере