Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «DRY (не повторюй себе)». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**DRY (Don't Repeat Yourself)** - кожен фрагмент логіки живе в одному місці. Змінив там - всі залежні місця оновились автоматично. Дублювання означає, що баг треба виправляти в кількох місцях, і хоча б одне будеш пропущено. ```javascript // WET: формат імені в двох місцях function greet(first, last) { return 'Hi, ' + first + ' ' + last; } function fullName(first, last) { return first + ' ' + last; } // DRY: одне місце для логіки імені function fullName(first, last) { return first + ' ' + last; } function greet(first, last) { return 'Hi, ' + fullName(first, last); } ``` **Ключове:** якщо скопіював логіку - витягни її в функцію або константу.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**DRY (Don't Repeat Yourself)** - кожен фрагмент логіки або знань у системі живе рівно в одному місці. ## Теорія ### TL;DR - Аналогія: один майстер-ключ від усіх дверей замість копій скрізь. Одна копія зламалась - все відкрите. - Головна перевага: зміни логіку один раз, і все що від неї залежить оновиться автоматично. - WET (Write Everything Twice) змушує оновлювати кілька місць одночасно - звідси розбіжності та баги. - Правило: якщо скопіював код або дані - винеси в функцію, константу або модуль. ### Швидкий приклад ```javascript // WET: формат імені в двох місцях // Виправив одне, забув інше = прихований баг function userFullName(first, last) { return first + ' ' + last; } function userGreeting(first, last) { return 'Hi, ' + first + ' ' + last + '!'; } // DRY: привітання використовує єдину логіку імені function userFullName(first, last) { return first + ' ' + last; } function userGreeting(first, last) { return 'Hi, ' + userFullName(first, last) + '!'; } // userGreeting('Alice', 'Smith') → 'Hi, Alice Smith!' ``` У WET-версії форматування імені в двох місцях. Виправив баг в одному, забув про друге. У DRY-версії одне місце для змін. ### WET проти DRY Дублювання коду змушує оновлювати кілька місць одразу. Пропустиш одне - отримаєш розбіжність. Наприклад, правило валідації пароля: 8 символів у формі, але 6 в API-обробнику. DRY централізує логіку: одна функція, одна константа, один модуль. Зміни там - все, що від неї залежить, оновиться автоматично. Протилежність DRY іноді називають WET (Write Everything Twice). Назва жартівлива лише наполовину. ### Коли виносити - Копіюєш схожу логіку між функціями або файлами? Витягни в спільну функцію. - Одне й те саме рядкове значення або число в кількох місцях? Перенеси в іменовану константу. - Однакова валідація у формі, API-обробнику та тесті? Перенеси в спільний утиліт. - Повторюваний шаблон API-запитів по всіх маршрутах? Оберни у фабричну функцію. - Конфігурація розкидана по файлах? Централізуй в одному конфігураційному об'єкті. ### Поведінка рантайму Компілятори на кшталт V8 або TypeScript виконують кожну копію продубльованої логіки незалежно. Баг у дубльованому коді спрацьовує в кожній копії окремо. Деякі правила ESLint (наприклад, `no-dupe-keys`) виявляють певні форми дублювання статично, але більшість стає помітною тільки в рантаймі, коли копії починають розходитися. ### Типові помилки **Помилка: надмірне застосування DRY до тривіального коду.** ```javascript // Безглузда абстракція - збільшує когнітивне навантаження без користі const twiceTwo = multiply(2, 2); ``` Абстрагуй тільки якщо логіка повторюється 3+ рази або справді складна. Дві схожі функції сьогодні можуть розійтися завтра. Передчасна абстракція ускладнить обидві. **Помилка: дублювання заради «читабельності».** ```javascript // WET: додати нову роль означає оновити три місця if (admin || moderator || owner) { ... } // DRY: одне місце для змін const canEdit = ['admin', 'moderator', 'owner'].includes(role); ``` Баги з правами доступу в продакшені часто виникають через те, що хтось додав роль в одному `if`, але забув про два інших. **Помилка: ігнорувати дублювання даних.** ```javascript // Неправильно: версія API захардкоджена в 5 різних файлах { name: 'API v1', version: '1.0' } // Правильно export const API_VERSION = '1.0'; ``` DRY стосується даних так само, як і коду. Оновлення версії повинне торкатися одного файлу. **Помилка: дві функції з однаковою назвою, але різною логікою в різних модулях.** ```javascript // utils.js містить validateEmail() // components/validateEmail.js містить іншу validateEmail() ``` Імпортуєш не той варіант - отримуєш неочевидні збої. Один модуль, barrel-exports - проблема зникає. ### Реальні проєкти - React: спільний хук `useAuth` між сторінками замість повтору логіки авторизації в кожному компоненті (патерн NextAuth.js). - Express: фабрика middleware `requireRole('admin')` замість перевірок ролей inline на кожному маршруті (патерн Passport.js). - Node.js: `path.join(__dirname, 'config')` визначається один раз і імпортується де потрібно. - Redux: централізовані action creators, де структура payload живе в одному файлі. - TypeScript: спільний інтерфейс `User` у моделях NestJS замість перевизначення в кожному файлі. Команди часто DRY-ять код, але залишають URL бази даних захардкодженим у 10 скриптах. Дублювання даних обходиться так само дорого, як і дублювання коду. ### Питання на співбесіді **Q:** Що є протилежністю DRY? **A:** WET (Write Everything Twice або «We Haven't Estimated Anything Totally»). Це код, де однакова логіка живе в кількох місцях. **Q:** Коли DRY шкодить більше, ніж допомагає? **A:** При передчасній абстракції. Є правило трьох: чекай мінімум трьох повторень перш ніж абстрагувати. Дві схожі функції можуть розійтися завтра, і примусова абстракція ускладнить обидві. **Q:** Як DRY пов'язаний з SOLID? **A:** DRY підтримує принцип єдиної відповідальності (SRP). Коли логіка живе в одному місці, це місце відповідає за неї і тільки за неї. **Q:** Як застосовувати DRY в мікросервісах, не створюючи зв'язності між сервісами? **A:** Чисті утиліти (схеми валідації, форматери дат) можна виносити в спільні пакети. Доменну логіку між bounded contexts не варто шарити - це створює зв'язність. Для доменних подій краще використовувати контракти або схеми подій, а не спільний код. Правильна відповідь на рівні senior згадує Domain-Driven Design і визнає, що дублювання між сервісами іноді прийнятне заради їхньої незалежності. ## Приклади ### Express.js: спільна функція для запитів до БД ```javascript // WET: шаблон запиту повторюється в кожному маршруті app.get('/users', authMiddleware, (req, res) => { db.query('SELECT * FROM users WHERE active=1').then(users => res.json(users)); }); app.get('/orders', authMiddleware, (req, res) => { db.query('SELECT * FROM orders WHERE active=1').then(orders => res.json(orders)); }); // DRY: одна функція для шаблону const fetchActive = (table) => db.query(`SELECT * FROM ${table} WHERE active=1`); app.get('/users', authMiddleware, (req, res) => fetchActive('users').then(res.json.bind(res))); app.get('/orders', authMiddleware, (req, res) => fetchActive('orders').then(res.json.bind(res))); ``` Додати новий маршрут - один рядок замість копіювання рядка запиту. Умова `WHERE active=1` визначена один раз. Змінив на `WHERE status='active'` - всі маршрути оновились. ### React: кастомний хук для повторної логіки запитів ```javascript // WET: логіка запиту скопійована між компонентами function UserList() { const [users, setUsers] = useState([]); useEffect(() => { fetch('/api/users').then(r => r.json()).then(setUsers); }, []); } // DRY: винесено в хук для повторного використання function useApiData(url) { const [data, setData] = useState([]); useEffect(() => { fetch(url).then(r => r.json()).then(setData); }, [url]); // url у масиві залежностей - пропустиш це → застарілі дані return data; } const users = useApiData('/api/users'); const products = useApiData('/api/products'); ``` Масив залежностей з `url` - місце де розробники помиляються найчастіше. Пропустиш його і хук буде запитувати початковий URL назавжди, навіть якщо `url` зміниться. Один правильно реалізований хук надійніший за п'ять розкиданих блоків `useEffect`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.