Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Refaktoring nima
- 2.1 Ta'rif
- 2.2 Ikki shlyapa
- 2.3 Sikl
- 3. Kod hidlari katalogi
- 4. Asosiy usullar
- 4.1 O'zgaruvchi ajratish
- 4.2 Ichiga qo'yish
- 4.3 Qayta nomlash va sehrli qiymat
- 4.4 switch o'rniga polimorfizm
- 5. Testlar himoyasida
- 5.1 Avval testlar, keyin refaktor
- 5.2 Muharrir yordami
- 5.3 Testlar ushlamaydigan joylar
- 6. Ko'p uchraydigan xatolar
- 6.1 Refaktor bilan "tuzatish"
- 6.2 Saqlangan ma'lumot nomini o'zgartirish
- 6.3 Noto'g'ri umumlashtirish
- 6.4 Katta qadam
- 7. Mashqlar
- 1-mashq (oson): Hidni toping
- 2-mashq (o'rta): Test bilan refaktor
- 3-mashq (qiyin): Takrorlanuvchi switch
- 4-mashq: Vazifalar qadami — beshta hid, besh commit
- 8. Real ishda
- Xulosa
- Manbalar
Code smell va refactoring: kod hidlarini tanish va testlar himoyasida xavfsiz qadamlar
Qisqacha: Kod hidi (code smell) — xato emas, lekin kelajakdagi xato yoki og'ir o'zgarishdan darak beruvchi belgi: takror kod, sehrli satr, uzun funksiya, begona modul ishiga aralashish. Refaktoring — kodning ichini yaxshilash, tashqi xulqini o'zgartirmasdan. U kichik qadamlar bilan qilinadi: bitta o'zgarish → testlar (
npm test) → commit. Testlar yiqilsa — o'zgarish qaytariladi, kod "tuzatilmaydi".
Bu darsda
- Eng ko'p uchraydigan kod hidlarini nomi bilan taniy olasiz.
- Asosiy refaktoring usullarini qo'llaysiz: funksiya va o'zgaruvchi ajratish, qayta nomlash, ichiga qo'yish, sehrli qiymatni o'zgarmasga chiqarish.
- Takrorlanuvchi
switchni polimorfizm bilan almashtirasiz. - Refaktorni testlar himoyasida, har biri alohida commit bo'lgan kichik qadamlar bilan qilasiz.
vazifalardagi beshta hidni besh commit bilan tuzatasiz — 57 test har qadamda yashil.
Oldin bilishingiz kerak: Toza kod: nomlash, Toza kod: funksiyalar, izohlar va tuzilma, Birinchi avtomatik test: node:test va assert, Meros: extends va super.
1. Nega bu kerak?
Jasur aka vazifani tahrirlash maydonini boshqacha bezashni so'radi. Sardor CSS'da #tahrir-maydon ni o'zgartirmoqchi, buning uchun avval HTML'dagi id'ni tahrir-kiritma qilmoqchi. vazifalar da qidiradi va "tahrir-maydon" satrini olti joyda, ikki modulda topadi: render.js da elementni yaratish va qidirish, asosiy.js da Enter tugmasini tekshirish va saqlash. Bittasini unutsa — tahrir jimgina buziladi: Enter ishlamaydi yoki null.value xatosi chiqadi.
Kod ishlaydi. Xato yo'q. Lekin uni o'zgartirish xavfli va sekin. Bu — kod hidi (code smell). Atamani Kent Beck va Martin Fowler ommalashtirgan: muzlatgichdan kelgan hid kabi — nima buzilganini hali bilmaysiz, lekin ichkariga qarash vaqti kelgan.
O'tgan darsda vazifalar ga 57 ta test yozdik. Endi ular himoyasida hidlarni tuzatamiz. Ilova foydalanuvchi uchun hech qanday o'zgarmaydi — o'zgaradigani kodning ichi.
2. Refaktoring nima
2.1 Ta'rif
Refaktoring (refactoring) — dastur tashqaridan qanday ishlasa, shunday qoldirib, uning ichki tuzilishini yaxshilash. "Tashqaridan" — foydalanuvchi ko'radigan hamma narsa: ekran, xabarlar, serverga ketadigan so'rovlar, saqlangan ma'lumot.
Refaktoring — qayta yozish emas. Qayta yozish: eski kodni tashlab, yangisini noldan boshlash. Refaktoring: ishlayotgan koddan har safar bitta kichik qadam, har qadamdan keyin dastur yana ishlaydi. Uyni buzib qayta qurish va xonama-xona ta'mirlash farqi kabi: ta'mir paytida ham oila uyda yashaydi.
2.2 Ikki shlyapa
Kent Beck'ning o'xshatishi bor (Fowler kitobi orqali mashhur): dasturchida ikki "shlyapa" bor. Bir paytda faqat bittasini kiyasiz: yo yangi imkoniyat qo'shasiz (xulq o'zgaradi, yangi test yoziladi), yo refaktor qilasiz (xulq o'zgarmaydi, testlar o'zgarmaydi). Ikkalasini aralashtirsangiz, test yiqilganda sababini topa olmaysiz: refaktor buzdimi yoki yangi kod?
vazifalar da shuning uchun branch nomlari har xil: Unicode regex darsida fix/teglar-regex — xulq o'zgardi, xato tuzatildi. Nomlash va funksiyalar darslarida refactor/… — xulq o'zgarmadi.
2.3 Sikl
Har refaktoring qadami bir xil tartibda:
flowchart LR
A["npm test — yashil"] --> B["bitta kichik o'zgarish"]
B --> C["npm test"]
C -->|yashil| D["commit"]
C -->|qizil| E["git restore — qaytarish"]
D --> A
E --> ADiqqat qiling: qizil bo'lsa, xatoni tuzatmaymiz, o'zgarishni qaytaramiz (git restore .) va kichikroq qadam bilan qayta urinamiz. Qadam kichik bo'lgani uchun yo'qotish ham kichik — bir necha daqiqa. Katta qadamda esa soatlab debugging qilasiz.
Tekshirib ko'ring: Sardor bitta commit'da
api.jsdagi takrorni yig'di va yangiqayta urinishimkoniyatini qo'shdi. Test yiqildi. Nega bu yomon tanlov edi?
Javob
U ikki shlyapani birga kiydi. Endi yiqilgan testning sababi ikki joyda bo'lishi mumkin: refaktor xulqni buzdimi yoki yangi imkoniyat eski testga to'g'ri kelmaydimi? Avval refaktor (testlar o'zgarmay yashil), commit; keyin yangi imkoniyat (yangi test bilan), alohida commit.
3. Kod hidlari katalogi
Fowler'ning "Refactoring" kitobida (2-nashr, 2018) 24 ta hid bor. Har birining nomi bor — jamoada "bu yerda feature envy bor" deyish "bu funksiya negadir boshqa modulning ishini qilyapti" deb uzoq tushuntirishdan tezroq. Eng ko'p uchraydiganlari:
| Hid | Belgisi | Davosi |
|---|---|---|
| Sirli nom (mysterious name) | nomdan ma'no chiqmaydi: oxiri, top(i) |
qayta nomlash |
| Takror kod (duplicated code) | bir xil qatorlar bir necha joyda | funksiya ajratish |
| Uzun funksiya (long function) | ishini "va"siz ayta olmaysiz | funksiya ajratish |
| Uzun parametrlar ro'yxati | 4 va undan ko'p parametr, true, false |
sozlama obyekti |
| Sehrli qiymat (magic literal) | "tahrir-maydon", 8000 izohsiz |
nomli o'zgarmas |
| Hid | Belgisi | Davosi |
|---|---|---|
| Feature envy (begona ishga havas) | funksiya o'z modulidan ko'ra boshqa modul ma'lumotini ko'proq ishlatadi | funksiyani o'sha modulga ko'chirish |
| Shotgun surgery (sochma o'zgarish) | bitta o'zgarish uchun ko'p faylga tegish kerak | bilimni bir joyga yig'ish |
Takrorlanuvchi switch |
bir xil switch (tur) bir necha funksiyada |
polimorfizm |
| Global yoki o'zgaruvchan ma'lumot | hamma joydan o'zgartiriladigan obyekt | o'zgartirishni bitta joyga yig'ish |
| Izohlar (deodorant sifatida) | murakkab kodni izoh "xushbo'ylashtiradi" | kodni aniqlashtirish, izohni o'chirish |
Ikki narsani yodda tuting. Birinchisi — hid signal, hukm emas. Ba'zan takror ataylab qoldiriladi: ikki o'xshash qator boshqa-boshqa sabab bilan o'zgarishi mumkin. Ikkinchisi — "takror" deganda qatorlar emas, bilim takrorlanishi muhim. Filtrlar ro'yxati ikki joyda yozilgan bo'lsa, yangi filtr qo'shilganda ikkalasini ham o'zgartirish kerak — bu xavfli takror.
4. Asosiy usullar
4.1 O'zgaruvchi ajratish
Murakkab ifodaga nom berish — eng kichik refaktoring. Bron: guests — mehmonlar soni, hour — soat, onTerrace — ayvondami:
const booking = { guests: 6, hour: 21, onTerrace: true };
if (booking.onTerrace && (booking.hour >= 21 || booking.guests > 5)) {
console.log("Ayvonga issiq choy va adyol tayyorlang");
}Shart nimani bildiradi? Nom bilan:
const booking = { guests: 6, hour: 21, onTerrace: true };
const isChillyEvening = booking.hour >= 21;
const isLargeGroup = booking.guests > 5;
if (booking.onTerrace && (isChillyEvening || isLargeGroup)) {
console.log("Ayvonga issiq choy va adyol tayyorlang");
}isChillyEvening — "kechki salqinmi", isLargeGroup — "katta guruhmi". Konsolda (ikkalasida bir xil):
Ayvonga issiq choy va adyol tayyorlangFunksiya ajratish (extract function) — xuddi shu g'oya, faqat kattaroq bo'lak uchun. Uni funksiyalar darsida closeOrder da qildik.
4.2 Ichiga qo'yish
Teskari usul ham bor: ichiga qo'yish (inline). Funksiya yoki o'zgaruvchi hech narsa qo'shmasa, u faqat sakrash joyi:
function getPrice(dish) {
return dish.price;
}
const total = getPrice(plov) + getPrice(tea);dish.price o'zi yetarlicha aniq. getPrice ("narxni ol") ni olib tashlab, plov.price + tea.price deb yozamiz. Refaktoring har doim ham "ko'proq funksiya" emas — maqsad aniqlik.
4.3 Qayta nomlash va sehrli qiymat
Ikkalasini oldingi ikki darsda ko'rdik: nomlash — oxiri → sinovParametri; sehrli raqam — 8000 → KUTISH_MS. Bugun vazifalar dagi qolgan sehrli qiymatlarni tuzatamiz.
4.4 switch o'rniga polimorfizm
«Bahor» buyurtmasi uch joyga bo'lishi mumkin: zal, ayvon, yetkazib berish. Xizmat haqi (serviceFee) va tayyorlanish vaqti (prepTime) joyga (place) bog'liq. Joy nomlari — ma'lumot, shuning uchun o'zbekcha qoldi. Sardorning kodi:
function serviceFee(order) {
switch (order.place) {
case "zal":
return 0;
case "ayvon":
return (order.amount * 5) / 100;
case "yetkazish":
return 15000;
}
}
function prepTime(order) {
switch (order.place) {
case "zal":
case "ayvon":
return 20;
case "yetkazish":
return 50;
}
}
for (const place of ["zal", "ayvon", "yetkazish"]) {
const order = { place, amount: 100000 };
console.log(place, serviceFee(order), prepTime(order));
}Konsolda:
zal 0 20
ayvon 5000 20
yetkazish 15000 50Hid — takrorlanuvchi switch: bir xil switch (order.place) ikki funksiyada. To'rtinchi joy ("olib ketish") qo'shilsa, hamma switch ni topib, har biriga case qo'shish kerak. Bittasini unutsangiz — funksiya undefined qaytaradi va hech kim sezmaydi.
Polimorfizm (polymorphism) — har xil klasslar bir xil nomli metodga ega bo'lib, har biri uni o'zicha bajaradi. Chaqiruvchi qaysi klass ekanini bilmaydi — u shunchaki serviceFee() ni chaqiradi. class sintaksisi va meros darslaridagi bilimlar yetarli:
const TERRACE_PERCENT = 5;
const DELIVERY_FEE = 15000; // so'm
class HallOrder {
serviceFee() {
return 0;
}
prepMinutes() {
return 20;
}
}
class TerraceOrder extends HallOrder {
serviceFee(amount) {
return (amount * TERRACE_PERCENT) / 100;
}
}
class DeliveryOrder {
serviceFee() {
return DELIVERY_FEE;
}
prepMinutes() {
return 50;
}
}
const PLACES = {
zal: new HallOrder(),
ayvon: new TerraceOrder(),
yetkazish: new DeliveryOrder(),
};
for (const place of ["zal", "ayvon", "yetkazish"]) {
const kind = PLACES[place];
console.log(place, kind.serviceFee(100000), kind.prepMinutes());
}Konsolda:
zal 0 20
ayvon 5000 20
yetkazish 15000 50Natija bir xil. Endi har joyning hamma qoidasi bitta klassda yashaydi (HallOrder — zal, TerraceOrder — ayvon, DeliveryOrder — yetkazish). Yangi joy — yangi klass va PLACES ("joylar") ga bitta qator. switch faqat bir joyda qoldi — PLACES jadvalida. Ayvon zaldan meros oladi: tayyorlanish vaqti bir xil, faqat xizmat haqi boshqa.
Bitta ehtiyot chorasi: PLACES[place] ga foydalanuvchidan kelgan matn berilsa, PLACES["constructor"] ham "topiladi" — u har obyektda prototipdan keladi (Prototip va prototip zanjiri). Tekshiruvni Object.hasOwn(PLACES, place) bilan qiling. vazifalar da aynan shu holat bor — Vazifalar qadamida ko'rasiz.
Polimorfizm har doim kerak emas. switch bitta bo'lsa va kamdan-kam o'zgarsa — u yaxshi. Hid — takrorlanuvchi switch.
Tekshirib ko'ring: Birinchi variantda "olib ketish" joyini qo'shdingiz, lekin faqat
serviceFeegacaseyozdingiz.prepTime({ place: "olib ketish" })nima qaytaradi?
Javob
undefined. Hech bir case mos kelmadi, switch tugadi va funksiya return siz tugadi. Xato tashlanmaydi — ekranda "undefined daqiqa" chiqadi. Polimorfik variantda esa yangi klass prepMinutes ni yozmasa, chaqiruvda darhol TypeError: kind.prepMinutes is not a function chiqadi — xato ko'rinadi.
5. Testlar himoyasida
5.1 Avval testlar, keyin refaktor
Refaktordan oldin o'zingizga savol bering: men buzsam, buni nima ushlaydi? Javob "hech narsa" bo'lsa — avval test yozing. Biz o'tgan darsda vazifalar ga aynan shuning uchun xulqni qotiruvchi testlar qo'shdik: api.js qaysi manzilga qaysi metod bilan so'rov yuborishi, eksport matnining har bo'sh joyi, xato matnlari. Bugun api.js dagi takrorni yig'ganimizda, aynan shu testlar "manzil o'zgarmadi" deb kafolat beradi.
5.2 Muharrir yordami
VS Code ko'p refaktoringni o'zi xavfsiz bajaradi. Kodni belgilab, Ctrl+Shift+R (yoki o'ng tugma → Refactor…) bosing:
- Extract to function — belgilangan qatorlardan funksiya yasaydi, kerakli parametrlarni o'zi topadi;
- Extract to constant — ifodani
constga chiqaradi; - Move to a new file — funksiyani yangi modulga ko'chirib, importlarni yangilaydi;
- Rename Symbol (
F2) — nomlash darsida ko'rdik.
Vosita qo'lda qilishdan xavfsizroq, lekin baribir npm test ni ishga tushiring: vosita satr ichidagi nomni yoki brauzerdagi xulqni bilmaydi.
5.3 Testlar ushlamaydigan joylar
Bizning testlarimiz Node'da ishlaydi. asosiy.js va render.js (DOM bilan ishlaydigan modullar) Node'da import ham qilinmaydi — ular uchun hali test yo'q. Shuning uchun DOM'ga tegadigan refaktordan keyin brauzerda tekshiruv/ssenariy.md qadamlarini ham bajaramiz. Bu bo'shliqni yozib qo'yish va boshqarishni keyingi darsda ko'ramiz.
6. Ko'p uchraydigan xatolar
6.1 Refaktor bilan "tuzatish"
Refaktor paytida g'alati xulqqa duch keldingiz: masalan, qisqa hisobotda "so'm" so'zi yo'q. Uni shu yerda tuzatsangiz — bu endi refaktor emas. Tuzatish kerak bo'lsa — uni yozib qo'ying va alohida commit'da, testi bilan qiling.
6.2 Saqlangan ma'lumot nomini o'zgartirish
localStorage kaliti "vazifalar:v3" ham satr — "sehrli qiymat"ga o'xshaydi. Lekin uni "vazifalar:v3.1" ga o'zgartirsangiz, har foydalanuvchining saqlangan ro'yxati yo'qoladi: ilova eski kalitni endi o'qimaydi. Saqlangan ma'lumot, URL, server API — bular "tashqi xulq". Ularning nomi refaktorda o'zgarmaydi.
6.3 Noto'g'ri umumlashtirish
Ikki o'xshash funksiyani bitta qilib, ichiga if (turi === …) qo'shish — yangi hid (flag argument). Takrorni yo'qotish yaxshi, agar ikki joy bitta sabab bilan o'zgarsa. Aks holda biroz takror noto'g'ri abstraksiyadan arzonroq.
6.4 Katta qadam
"Butun asosiy.js ni bir kunda qayta tuzaman" — ertasi kuni yarmi buzilgan va qayerdan boshlanganini hech kim bilmaydi. Bitta hid — bitta commit.
7. Mashqlar
1-mashq (oson): Hidni toping
Har parchada qaysi hid bor? Hid nomini katalogdan toping.
// a)
if (state.network === "oflayn") { … } // 4 ta faylda xuddi shu satr
// b)
function save(list, render, broadcast, key, timeout) { … }
// c)
function checkBooking(booking) {
// ...
db.save(booking); // tekshiruv paytida saqlab ham qo'yadi
}Yechim
a) Sehrli satr va shotgun surgery: "oflayn" nomi o'zgarsa, to'rt faylga tegish kerak. Davosi — bitta o'zgarmas (yoki isOffline() funksiyasi). b) Uzun parametrlar ro'yxati; render ("chizish") va broadcast ("tarqatish") flag'ga o'xshaydi. Davosi — sozlama obyekti yoki ikki alohida funksiya. c) Yashirin yon ta'sir (nom yolg'on gapiradi) — funksiyalar darsidagi "so'rov va buyruqni ajratish".
2-mashq (o'rta): Test bilan refaktor
Quyidagi deliveryPrice ("yetkazish narxi") funksiyasiga avval ikkita test yozing (yashil bo'lsin). Keyin sehrli qiymatlarni o'zgarmasga chiqaring va takrorni yo'qoting. Testlar yashil qolsin.
function deliveryPrice(km) {
if (km <= 3) {
return 10000;
}
return 10000 + (km - 3) * 2000;
}Yechim
import { test } from "node:test";
import assert from "node:assert/strict";
const BASE_PRICE = 10000; // so'm
const BASE_KM = 3; // shu masofagacha narx o'zgarmaydi
const PRICE_PER_KM = 2000;
function deliveryPrice(km) {
const extraKm = Math.max(0, km - BASE_KM);
return BASE_PRICE + extraKm * PRICE_PER_KM;
}
test("3 km gacha — boshlang'ich narx", () => {
assert.equal(deliveryPrice(2), 10000);
assert.equal(deliveryPrice(3), 10000);
});
test("har qo'shimcha km — 2 000", () => {
assert.equal(deliveryPrice(4), 12000);
assert.equal(deliveryPrice(10), 24000);
});Testlar refaktordan oldin eski funksiya bilan yozildi va o'tdi; keyin yangi funksiya bilan ham o'tdi — tests 2, pass 2. Chegarani (3 km) ham tekshirdik: <= ni yo'qotganda Math.max aynan shu joyni to'g'ri saqlaydimi — test javob beradi. 10000 ikki marta yozilgan edi — endi bitta BASE_PRICE.
3-mashq (qiyin): Takrorlanuvchi switch
dishIcon ("taom belgisi") va dishLabel ("taom tavsifi") bir xil switch ni takrorlaydi. Uni har taom turi uchun bitta obyektdan iborat jadvalga aylantiring (klass shart emas — oddiy obyektlar ham polimorfizmning sodda shakli). Noma'lum tur "?" va "Noma'lum" qaytarsin. Konsol chiqishi o'zgarmasin.
function dishIcon(type) {
switch (type) {
case "issiq":
return "🍲";
case "ichimlik":
return "🍵";
default:
return "?";
}
}
function dishLabel(type) {
switch (type) {
case "issiq":
return "Issiq taom";
case "ichimlik":
return "Ichimlik";
default:
return "Noma'lum";
}
}
for (const type of ["issiq", "ichimlik", "constructor"]) {
console.log(dishIcon(type), dishLabel(type));
}Konsolda:
🍲 Issiq taom
🍵 Ichimlik
? Noma'lumYechim
const DISH_TYPES = {
issiq: { icon: "🍲", label: "Issiq taom" },
ichimlik: { icon: "🍵", label: "Ichimlik" },
};
const UNKNOWN = { icon: "?", label: "Noma'lum" };
function getDishType(type) {
return Object.hasOwn(DISH_TYPES, type) ? DISH_TYPES[type] : UNKNOWN;
}
for (const type of ["issiq", "ichimlik", "constructor"]) {
const { icon, label } = getDishType(type);
console.log(icon, label);
}Konsolda:
🍲 Issiq taom
🍵 Ichimlik
? Noma'lum"constructor" ataylab qo'shilgan: DISH_TYPES["constructor"] oddiy obyektda Object funksiyasini qaytaradi va icon undefined bo'lardi. Object.hasOwn faqat o'z kalitlarini tan oladi. Yangi tur — jadvalda bitta qator, ikki funksiyani qidirish shart emas.
4-mashq: Vazifalar qadami — beshta hid, besh commit
Branch: refactor/kod-hidlari. Bu safar besh commit, har biri bitta hid. Har commit'dan oldin npm test — 57/57.
| # | Hid | Qayerda | Tuzatish |
|---|---|---|---|
| 1 | Sehrli satr | "tahrir-maydon" — 6 joyda, 2 modulda |
render.js: TAHRIR_MAYDONI_ID, tahrirMaydoni() |
| 2 | Feature envy | asosiy.js tahrirniSaqla render'ning DOM ishini qilardi |
render.js tahrirXatosiniKorsat(xabar) |
| 3 | Takrorlangan bilim | filtrlar ro'yxati ikki joyda | marshrut.js export const FILTRLAR |
| 4 | Sehrli qiymatlar | paket versiyasi 1 — 3 joyda; tab xabari "ozgardi" — 2 joyda |
PAKET_VERSIYASI, OZGARDI |
| 5 | Takror kod | api.js dagi 4 yozuv metodi |
ichki yoz(yol, metod, tana, sozlama) |
Commitlar tarixi shunday ko'rinadi:
gitGraph
commit id: "test: node:test"
branch refactor/kod-hidlari
commit id: "1 tahrir-maydon"
commit id: "2 feature envy"
commit id: "3 FILTRLAR"
commit id: "4 versiya, OZGARDI"
commit id: "5 yoz()"
checkout main
merge refactor/kod-hidlari1-commit: sehrli satr. render.js ga:
// Tahrir maydoni faqat tahrir paytida bor — har safar qidiriladi
export const TAHRIR_MAYDONI_ID = "tahrir-maydon";
export function tahrirMaydoni() {
return document.getElementById(TAHRIR_MAYDONI_ID);
}Olti joyda satr o'rniga o'zgarmas yoki funksiya: kiritma.id = TAHRIR_MAYDONI_ID;, const kiritma = tahrirMaydoni();, asosiy.js da event.target.id !== TAHRIR_MAYDONI_ID. Izohga e'tibor bering: element nega bir marta olinib, o'zgaruvchida saqlanmaydi — u har tahrirda qaytadan yaratiladi. Endi Sardorning id'ni o'zgartirish ishi — bitta qator.
Commit: refactor: "tahrir-maydon" sehrli satri o'zgarmasga chiqarildi
2-commit: feature envy. asosiy.js dagi tahrirniSaqla xato bo'lganda maydonga aria-invalid qo'yar, fokusni qaytarar va e'lon qilardi — bu render.js ning ishi (DOM). Oldin:
// asosiy.js, tahrirniSaqla() ichida
if (xato !== null) {
kiritma.setAttribute("aria-invalid", "true");
kiritma.focus();
elonQil(xato);
return;
}Keyin — ish o'z moduliga ko'chdi, xatoniKorsat va importXatosiniKorsat qatoriga:
// render.js
export function tahrirXatosiniKorsat(xabar) {
const kiritma = tahrirMaydoni();
kiritma.setAttribute("aria-invalid", "true");
kiritma.focus();
elonQil(xabar);
}
// asosiy.js, tahrirniSaqla() ichida
if (xato !== null) {
tahrirXatosiniKorsat(xato);
return;
}Commit: refactor: tahrir xatosini ko'rsatish render.js ga ko'chdi (feature envy)
3-commit: takrorlangan bilim. Qaysi filtrlar borligini ikki joy bilardi: marshrut.js dagi FILTRLAR ro'yxati va asosiy.js dagi tekshiruv — u render'ning bosh xabarlar jadvalini (boshMatnlar) ishlatardi. Yangi filtr qo'shilsa — ikkala joyni ham o'zgartirish kerak edi. git diff dan:
-const FILTRLAR = ["hammasi", "faol", "bajarilgan"];
+// Filtrlarning yagona ro'yxati: URL ham, tugmalar ham shunga qaraydi
+export const FILTRLAR = Object.freeze(["hammasi", "faol", "bajarilgan"]); function filtrniTanla(filtr) {
- // Faqat boshMatnlar ning O'Z kalitlari — "constructor" filtr emas
- if (!Object.hasOwn(boshMatnlar, filtr)) {
+ // Oq ro'yxat: data-filtr ni DevTools'da o'zgartirish mumkin
+ if (!FILTRLAR.includes(filtr)) {
return;
}boshMatnlar endi eksport qilinmaydi — u yana faqat render.js ning ichki ishi. Xulq bir xilmi? Object.hasOwn(boshMatnlar, x) va FILTRLAR.includes(x) aynan bir xil uchta qiymatni qabul qiladi, "constructor" ikkalasida ham rad etiladi. marshrut.test.js dagi ?filtr=constructor → hammasi testi buni tasdiqlaydi.
Commit: refactor: filtrlar ro'yxati bitta joyda — marshrut.js FILTRLAR
4-commit: sehrli qiymatlar. paket.js da:
// Eksport shakli o'zgarsa — versiya oshadi, eski matnlar rad etiladi
const PAKET_VERSIYASI = 1;Endi eksportMatni da { versiya: PAKET_VERSIYASI, vazifalar }, paketniOqi da xom.versiya !== PAKET_VERSIYASI, xato matni ham undan: `Versiya ${xom.versiya} o'qilmaydi — faqat ${PAKET_VERSIYASI}.`. sinxron.js da:
// Xabar turi: yuboruvchi ham, qabul qiluvchi ham shu nomni ishlatadi
export const OZGARDI = "ozgardi";Yuboruvchi (tarqat({ tur: OZGARDI, … })) va qabul qiluvchi (xabar?.tur !== OZGARDI) endi bitta nomga qaraydi — biri xato yozilsa, tablar sinxroni jimgina to'xtab qolmaydi. paket.test.js dagi "Versiya 2 o'qilmaydi — faqat 1." testi matn o'zgarmaganini isbotlaydi.
Commit: refactor: paket versiyasi va tab xabari turi o'zgarmasga chiqarildi
5-commit: takror kod. api.js ning to'rtta yozuv metodi (qosh, yangila, ochir, almashtir) har birida manzilga sinovParametri qo'shar va { ...sozlama, metod, tana } yasardi. Oldin, ikkitasi:
qosh: (matn, sozlama) => sorovYubor(manzil + sinovParametri, {
...sozlama, metod: "POST", tana: { matn },
}),
ochir: (id, sozlama) =>
sorovYubor(`${manzil}/${id}${sinovParametri}`, {
...sozlama, metod: "DELETE",
}),Keyin — takror bitta ichki funksiyada:
// Yozuv so'rovlari (o'qishdan farqli) sinov rejimini oladi
function yoz(yol, metod, tana, sozlama) {
return sorovYubor(manzil + yol + sinovParametri, {
...sozlama, metod, tana,
});
}Metodlar endi bir qatorli:
qosh: (matn, sozlama) => yoz("", "POST", { matn }, sozlama),
yangila: (id, ozgarish, sozlama) =>
yoz(`/${id}`, "PATCH", ozgarish, sozlama),
ochir: (id, sozlama) => yoz(`/${id}`, "DELETE", undefined, sozlama),
almashtir: (vazifalar, sozlama) =>
yoz("", "PUT", { vazifalar }, sozlama),Bitta nozik joy: ochir ga tana: undefined ketadi, oldin esa tana umuman yo'q edi. Farq bormi? sorovniTayyorla tana !== undefined deb tekshiradi — ikkala holatda ham tana yuborilmaydi. Buni taxmin qilmaymiz: api.test.js dagi "qosh, yangila, ochir, almashtir — metod, manzil, tana" testi DELETE da tana null (yo'q) ekanini va har so'rovning aniq manzilini tekshiradi. U 57/57 da o'tdi.
Commit: refactor: api.js yozuv metodlaridagi takror yoz() ga yig'ildi
Ataylab qilinmagan. localStorage kalitlari allaqachon o'zgarmas (SAQLASH_KALITI, SERVER_KALITI_NOMI). "vazifalar:v3" qiymati esa o'zgarmaydi — "Ko'p uchraydigan xatolar" bo'limidagi sabab bilan.
Tekshiruv. Har commit'dan keyin npm test — tests 57 · pass 57 · fail 0. Beshinchisidan keyin brauzerda to'liq ssenariy: qo'shish, belgilash, tahrir (Enter va Esc), xato matni bilan tahrir, filtrlar va «Orqaga», ikki tab, ?muvaffaqiyatsiz=1 da qaytarish. Hammasi avvalgidek. Jami diff: 6 fayl, +53 −35.
Yechim (buyruqlar)
git switch -c refactor/kod-hidlari
# 1-hid: o'zgartiring, keyin
npm test
git commit -am "refactor: \"tahrir-maydon\" sehrli satri \
o'zgarmasga chiqarildi"
# 2-hid
npm test
git commit -am "refactor: tahrir xatosini ko'rsatish render.js \
ga ko'chdi (feature envy)"
# 3-hid
npm test
git commit -am "refactor: filtrlar ro'yxati bitta joyda — \
marshrut.js FILTRLAR"
# 4-hid
npm test
git commit -am "refactor: paket versiyasi va tab xabari turi \
o'zgarmasga chiqarildi"
# 5-hid
npm test
git commit -am "refactor: api.js yozuv metodlaridagi takror \
yoz() ga yig'ildi"
git push -u origin refactor/kod-hidlari
gh pr create --fill
gh pr merge --mergegit commit -am — -a kuzatilayotgan (avval commit qilingan) hamma o'zgargan fayllarni o'zi qo'shadi; yangi fayl yo'q, shuning uchun git add shart emas. PR — bitta, ichida besh commit: ko'rib chiquvchi har hidni alohida o'qiydi. Biror commit muammo chiqarsa, faqat uni qaytarish mumkin (git revert).
8. Real ishda
- Har kunlik ish. Dasturchi vaqtining katta qismi yangi kod yozishga emas, mavjud kodni o'zgartirishga ketadi. Refaktoring — alohida "loyiha" emas, har vazifa ichidagi kichik odat: "kelganingdan tozaroq qoldir".
- Kod ko'rib chiqishda hid nomlari umumiy til: "shotgun surgery bo'lyapti", "bu feature envy, render'ga ko'chiring". Nomini bilish — tez kelishish.
- Vositalar. VS Code va WebStorm refaktorlarni avtomatik qiladi; ESLint ba'zi hidlarni (
no-duplicate-case,complexity,max-depth) ushlaydi; SonarQube kabi xizmatlar butun loyihaning "hid xaritasi"ni chiqaradi. - Intervyu. "Code smell nima, misol keltiring", "Refaktoring va qayta yozish farqi", "Refaktordan oldin nima qilasiz?" — middle darajasiga o'tishda ko'p so'raladi. To'g'ri javobning yarmi — "avval testlar".
Xulosa
- Kod hidi — xato emas, xavf belgisi: sirli nom, takror, uzun funksiya, sehrli qiymat, feature envy, shotgun surgery, takrorlanuvchi
switch. - Refaktoring — tashqi xulqni saqlab ichki tuzilmani yaxshilash; qayta yozish emas. Ikki shlyapa: refaktor va yangi imkoniyat — alohida.
- Usullar: o'zgaruvchi va funksiya ajratish, ichiga qo'yish, qayta nomlash, o'zgarmasga chiqarish,
switcho'rniga polimorfizm (Object.hasOwnbilan). - Sikl: yashil → kichik qadam →
npm test→ commit; qizil — qaytarish, tuzatish emas. - Saqlangan ma'lumot kaliti, URL, API — tashqi xulq, refaktorda o'zgarmaydi.
Keyingi dars: Legacy kod va texnik qarz bilan ishlash — testsiz modullar bilan nima qilish, "tikuv" (seam) va TEXNIK-QARZ.md faylida qarzni ko'rinadigan qilish.
Manbalar
- Martin Fowler, "Refactoring: Improving the Design of Existing Code", 2-nashr, 2018 — 3-bob "Bad Smells in Code", refaktoring katalogi (refactoring.com/catalog)
- Kent Beck, "Implementation Patterns", 2007; Fowler, "Two Hats" — martinfowler.com
- VS Code hujjatlari: "Refactoring" — code.visualstudio.com/docs/editor/refactoring
- Node.js hujjatlari: "Test runner" — nodejs.org/api/test.html
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!