Вивчай
Домашнє завдання #15 · ES6+: let/const, template literals, arrow functions, destructuring, spread/rest, optional chaining
100 балів+15 бонусintermediate

Домашнє завдання #15: Рефакторинг легасі-коду на ES6+

Уяви: тобі дістався код від попереднього розробника твого продукту. Він працює — але написаний у стилі 2014 року: var, конкатенація плюсиками, function на кожному кроці. У реальній роботі таке трапляється постійно — розробники читають і супроводжують чужий код більше, ніж пишуть новий. Твоє завдання — рефакторинг: переписати код сучасно, не змінивши його поведінку.


Файлова структура

hw-15/
├── before.js   ← легасі-код (скопіюй фрагменти звідси, НЕ змінюй)
├── after.js    ← твій рефакторинг
└── README.md

Правило рефакторингу: after.js робить те саме, що before.js — ті ж результати, ті ж повідомлення в консолі. Змінюється тільки стиль коду.


Легасі-код: 6 фрагментів

Приклади — на дефолтній фільмотеці. Якщо твій продукт про інше — можеш замінити назви й дані під свою тему (книги, рецепти…), але набір конструкцій у кожному фрагменті має зберегтися.

Фрагмент 1: змінні та цикл

Що застосувати: let/const (жодного var).

var movieTitle = "Памфір";
var year = 2022;
var genres = ["драма", "кримінал"];
for (var i = 0; i < genres.length; i++) {
  console.log(genres[i]);
}

Фрагмент 2: склеювання рядків

Що застосувати: template literals (і для однорядкових, і для багаторядкового HTML).

var label = "Фільм «" + movieTitle + "» (" + year + "), жанри: " + genres.join(", ") + ".";

var cardHtml = "<div class=\"movie-card\">\n" +
  "  <h2>" + movieTitle + "</h2>\n" +
  "  <p>Рік: " + year + "</p>\n" +
  "</div>";

Фрагмент 3: анонімні функції

Що застосувати: arrow functions.

var ratings = [8.4, 6.1, 9.2, 7.5, 5.8];
var rounded = ratings.map(function(r) { return Math.round(r); });
var good = ratings.filter(function(r) { return r >= 7; });
var sum = ratings.reduce(function(acc, r) { return acc + r; }, 0);

setTimeout(function() {
  console.log("Каталог оновлено!");
}, 1000);

Фрагмент 4: об'єкти та розбирання

Що застосувати: shorthand properties, destructuring (об'єктів і масивів), rest у деструктуризації.

function createMovie(title, year, rating) {
  return { title: title, year: year, rating: rating };
}

function displayMovie(movie) {
  var title = movie.title;
  var year = movie.year;
  var rating = movie.rating;
  console.log(title + " (" + year + ") — рейтинг " + rating);
}

var watchlist = ["Дюна", "Опенгеймер", "Субстанція", "Анора"];
var next = watchlist[0];
var afterThat = watchlist[1];
var someday = watchlist.slice(2);

Фрагмент 5: параметри за замовчуванням, злиття, аргументи

Що застосувати: default parameters, spread, rest parameters.

function greetViewer(name) {
  name = name || "кіномане";
  console.log("Привіт, " + name + "!");
}

function mergeSettings(defaults, userSettings) {
  var result = {};
  for (var key in defaults) { result[key] = defaults[key]; }
  for (var key in userSettings) { result[key] = userSettings[key]; }
  return result;
}

function totalWatchTime() {
  var durations = Array.prototype.slice.call(arguments);
  return durations.reduce(function(a, b) { return a + b; }, 0);
}

Фрагмент 6: перевірки на існування

Що застосувати: optional chaining (?.), nullish coalescing (??).

function getDirectorCountry(movie) {
  if (movie && movie.director && movie.director.country) {
    return movie.director.country;
  }
  return "Невідомо";
}

function getPageSize(options) {
  var pageSize = options && options.pageSize !== null && options.pageSize !== undefined
    ? options.pageSize : 20;
  return pageSize;
}

Пастка: options.pageSize || 20 — НЕ те саме. Якщо передати pageSize: 0, оператор || підмінить його на 20, а ?? — ні. Саме тому тут потрібен nullish coalescing.


Бонус: повний рефакторинг (+15)

Функція аналітики каталогу — по суті, те саме, що ти робив з даними у ДЗ #11, тільки написане в кам'яному віці. Перепиши її, застосувавши всі вивчені фічі разом: const, arrow functions, деструктуризацію в параметрах колбеків, default parameter, optional chaining, shorthand, методи масивів замість циклу.

function processMovies(movies, minRating) {
  minRating = minRating || 7;
  var result = [];

  for (var i = 0; i < movies.length; i++) {
    var movie = movies[i];
    var title = movie.title;
    var rating = movie.rating;
    var country = movie.director && movie.director.country
      ? movie.director.country : "Невідомо";

    if (rating >= minRating) {
      result.push({
        title: title,
        rating: rating,
        country: country,
        status: rating >= 9 ? "шедевр" : "вартий уваги"
      });
    }
  }

  result.sort(function(a, b) { return b.rating - a.rating; });
  return result;
}

Поведінка має збігтися один в один — включно з тим, що оригінальний масив movies не змінюється (обережно з sort!).


Підказки

Рівень 1: напрямок думки
  • Йди фрагмент за фрагментом і після кожного запускай обидва файли (node before.js, node after.js) — вивід має збігатися
  • Питання для кожної змінної: «вона колись переприсвоюється?» Якщо ні — const, якщо так — let
  • Побачив function(...) { return ... } в одному рядку всередині map/filter/reduce — це кандидат на найкоротшу форму arrow function
Рівень 2: конкретні прийоми
  • Багаторядковий HTML — це один template literal з переносами рядків, лапки екранувати більше не треба
  • Витягування трьох полів з об'єкта — одна деструктуризація; а ще її можна робити прямо в параметрі функції
  • var first = arr[0]; var rest = arr.slice(2) — це деструктуризація масиву з ...rest
  • mergeSettings — це один вираз зі spread двох об'єктів у новий (порядок важливий: хто останній, той і переміг)
  • Array.prototype.slice.call(arguments) — застарілий трюк, який повністю замінюють rest parameters
  • У бонусі: filtermap → сортування копії масиву (spread у новий масив перед sort)
Рівень 3: перевір себе

Ознаки готового рефакторингу — пройдися чеклістом по after.js:

  • жодного var, анонімного function(, конкатенації рядків через +, arguments, Array.prototype.slice.call чи ручного for...in для копіювання об'єктів
  • жодного ланцюжка a && a.b && a.b.c — замість нього ?. і ??
  • поведінка: вивід node after.js посимвольно збігається з node before.js

Як здати

  1. Робота — у твоєму репозиторії курсу my-web-course, у папці hw-15/
  2. Мінімум 3 коміти з осмисленими повідомленнями (не "fix" і не "update") — зручно комітити по 2-3 фрагменти
  3. У папці — README.md: 2-3 речення, що зроблено і як запустити (node after.js)
  4. Скріншоти не потрібні — це консольне завдання
  5. Надішли посилання на репозиторій (можна одразу на папку hw-15)

Критерії оцінки

КритерійТипБали
Фрагмент 1: тільки let/const, доречний вибір між ними, жодного var в усьому after.js[код]10
Фрагмент 2: template literals замість конкатенації, включно з багаторядковим HTML[код]15
Фрагмент 3: arrow functions у map/filter/reduce/setTimeout, найкоротша доречна форма[код]15
Фрагмент 4: shorthand properties + деструктуризація об'єкта та масиву з rest[код]20
Фрагмент 5: default parameters, spread для злиття об'єктів, rest parameters замість arguments[код]20
Фрагмент 6: optional chaining + nullish coalescing (саме ??, не ||)[код]15
before.js присутній і не змінений; поведінка after.js ідентична[код]5
Бонус: processMovies переписано з усіма фічами, без мутації вхідного масиву[код]+15

Що далі

Звичка писати сучасний код закріпиться вже у ДЗ #16, де твій продукт уперше отримає дані з мережі. А в блоці TypeScript ти дізнаєшся, як компілятор сам підказує половину таких рефакторингів.