Домашнє завдання #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)— це деструктуризація масиву з...restmergeSettings— це один вираз зі spread двох об'єктів у новий (порядок важливий: хто останній, той і переміг)Array.prototype.slice.call(arguments)— застарілий трюк, який повністю замінюють rest parameters- У бонусі:
filter→map→ сортування копії масиву (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
Як здати
- Робота — у твоєму репозиторії курсу
my-web-course, у папціhw-15/ - Мінімум 3 коміти з осмисленими повідомленнями (не "fix" і не "update") — зручно комітити по 2-3 фрагменти
- У папці —
README.md: 2-3 речення, що зроблено і як запустити (node after.js) - Скріншоти не потрібні — це консольне завдання
- Надішли посилання на репозиторій (можна одразу на папку
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 ти дізнаєшся, як компілятор сам підказує половину таких рефакторингів.