15-bo‘lim
Refaktoring va texnik qarz
Texnik qarz turlari, refaktoring usullari, eski kod bilan ishlash va qachon qayta yozish kerak emasligi.
Ushbu bo‘lim mundarijasi
- Texnik qarz
- Qarz turlari
- Qarz reyestri
- Refaktoring nima?
- Asosiy refaktoring usullari
- Metodni ajratish
- O'zgaruvchi kiritish
- Shartni metodga ajratish
- Shartni polimorfizm bilan almashtirish
- Parametrlarni obyektga jamlash
- Sehrli sonni doimiyga
- Vaqtinchalik maydonni ajratish
- Eski kod bilan ishlash
- Xarakteristika testlari
- Bog'liqlikni uzish
- Qachon refaktoring qilish kerak?
- Qachon qayta yozmaslik kerak?
- Refaktoring va biznes
- Muntazam yondashuv
- Xulosa
Har bir loyihada kod vaqt o'tishi bilan yomonlashadi. Refaktoring - bu jarayonga qarshi turishning yagona yo'li.
Texnik qarz #
Ward Cunningham 1992-yilda taklif qilgan:
*"Tez yetkazish uchun tayyor bo'lmagan kod yozish - qarz olishga o'xshaydi. Qarz kichik bo'lsa, u ishlab chiqishni tezlashtiradi - agar u tezda qaytarilsa. Xavf esa qarz qaytarilmagan holatda paydo bo'ladi."*
Qarz turlari #
// Qarzni hujjatlashtiring
/**
* @todo TEXNIK QARZ
*
* Bu metod har bir mahsulot uchun alohida so'rov yuboradi (N+1).
* 500 mahsulotli katalogda sahifa 4 soniyada ochiladi.
*
* Sabab: reliz muddatiga yetishish uchun tez yechim tanlandi.
* Yechim: JOIN bilan bitta so'rovga aylantirish.
* Vazifa: #287
* Muddat: 2026-10-01 gacha
* Baholangan mehnat: 4 soat
*/
public function kataloginiOling(): array
{
// ...
}
Qarz reyestri #
# Texnik qarz reyestri
| ID | Tavsif | Ta'siri | Mehnat | Ustuvorlik | Vazifa |
|-----|--------|---------|--------|------------|--------|
| TQ-1 | Katalogda N+1 so'rov | Sahifa 4s ochiladi | 4 soat | Yuqori | #287 |
| TQ-2 | BuyurtmaServisi 800 satr | O'zgartirish xavfli | 3 kun | Yuqori | #291 |
| TQ-3 | To'lov moduli testsiz | Har o'zgarish xavf | 5 kun | O'rta | #295 |
| TQ-4 | PHP 8.0 (eskirgan) | Xavfsizlik yangilanishi yo'q | 2 kun | Yuqori | #301 |
| TQ-5 | jQuery 2.x | Yangi kutubxona qo'shib bo'lmaydi | 10 kun | Past | #310 |
Ko'rinmagan qarz to'lanmaydi.
Reyestrni backlogga qo'shing va har sprintda 10-20% vaqtni unga ajrating.
"Vaqt bo'lganda qilamiz" - hech qachon vaqt bo'lmaydi.
Refaktoring nima? #
Refaktoring - kodning tashqi xatti-harakatini o'zgartirmasdan, ichki tuzilmasini yaxshilash.
Muhim: agar xatti-harakat o'zgarsa - bu refaktoring emas, bu yangi imkoniyat yoki xato tuzatish.
Testsiz refaktoring - kodni buzish.
Ketma-ketlik:
- Mavjud xatti-harakatni testlar bilan qoplang
- Testlar yashil ekaniga ishonch hosil qiling
- Kichik o'zgarish kiriting
- Testlarni ishga tushiring
- 3-4 ni takrorlang
Asosiy refaktoring usullari #
Metodni ajratish #
// OLDIN
public function hisobotChiqaring(array $buyurtmalar): string
{
$natija = "SAVDO HISOBOTI\n";
$natija .= str_repeat('=', 50) . "\n";
$natija .= 'Sana: ' . date('d.m.Y') . "\n\n";
$jami = 0;
foreach ($buyurtmalar as $b) {
$jami += $b['summa'];
$natija .= sprintf("%-30s %10s so'm\n", $b['raqam'], number_format($b['summa']));
}
$natija .= str_repeat('-', 50) . "\n";
$natija .= sprintf("%-30s %10s so'm\n", 'JAMI:', number_format($jami));
$natija .= sprintf("%-30s %10s\n", 'Buyurtmalar:', count($buyurtmalar));
return $natija;
}
// KEYIN
public function hisobotChiqaring(array $buyurtmalar): string
{
return $this->sarlavha()
. $this->qatorlar($buyurtmalar)
. $this->yakun($buyurtmalar);
}
private function sarlavha(): string
{
return "SAVDO HISOBOTI\n"
. str_repeat('=', self::KENGLIK) . "\n"
. 'Sana: ' . date('d.m.Y') . "\n\n";
}
private function qatorlar(array $buyurtmalar): string
{
return implode('', array_map(
fn (array $b) => sprintf("%-30s %10s so'm\n", $b['raqam'], number_format($b['summa'])),
$buyurtmalar,
));
}
private function yakun(array $buyurtmalar): string
{
$jami = array_sum(array_column($buyurtmalar, 'summa'));
return str_repeat('-', self::KENGLIK) . "\n"
. sprintf("%-30s %10s so'm\n", 'JAMI:', number_format($jami))
. sprintf("%-30s %10s\n", 'Buyurtmalar:', count($buyurtmalar));
}
O'zgaruvchi kiritish #
// OLDIN
if ($buyurtma['summa'] > 500000 && $mijoz['daraja'] === 'oltin'
&& count($buyurtma['elementlar']) > 3) {
// ...
}
// KEYIN
$kattaBuyurtma = $buyurtma['summa'] > 500_000;
$imtiyozliMijoz = $mijoz['daraja'] === 'oltin';
$kopMahsulot = count($buyurtma['elementlar']) > 3;
if ($kattaBuyurtma && $imtiyozliMijoz && $kopMahsulot) {
// ...
}
Shartni metodga ajratish #
// OLDIN
if ($sana >= $bayramBoshi && $sana <= $bayramOxiri
&& !in_array($mahsulot['kategoriya'], ['aksiya', 'chegirma'])) {
$narx *= 0.85;
}
// KEYIN
if ($this->bayramChegirmasiQollanadimi($sana, $mahsulot)) {
$narx *= self::BAYRAM_CHEGIRMASI;
}
private function bayramChegirmasiQollanadimi(
DateTimeImmutable $sana,
array $mahsulot,
): bool {
if (!$this->bayramKunimi($sana)) {
return false;
}
return !in_array($mahsulot['kategoriya'], self::CHEGIRMASIZ_KATEGORIYALAR, true);
}
Shartni polimorfizm bilan almashtirish #
// OLDIN
public function narxHisoblang(string $tur, float $asos): float
{
return match ($tur) {
'oddiy' => $asos,
'ekspress' => $asos * 1.5 + 20000,
'tungi' => $asos * 2,
'bayram' => $asos * 2.5,
};
}
// KEYIN
interface Tarif
{
public function narx(float $asos): float;
}
final class OddiyTarif implements Tarif
{
public function narx(float $asos): float
{
return $asos;
}
}
final class EkspressTarif implements Tarif
{
private const QOSHIMCHA = 20_000;
public function narx(float $asos): float
{
return $asos * 1.5 + self::QOSHIMCHA;
}
}
Parametrlarni obyektga jamlash #
// OLDIN
public function qidiring(
?string $matn, ?int $kategoriya, ?int $narxDan, ?int $narxGacha,
?bool $mavjud, string $saralash, int $sahifa, int $chegara,
): array { }
// KEYIN
public function qidiring(QidiruvSorovi $sorov): QidiruvNatijasi { }
Sehrli sonni doimiyga #
// OLDIN
if ($urinishlar > 5) { }
if ($vaqt > 1800) { }
if ($hajm > 5242880) { }
// KEYIN
private const MAKS_URINISH = 5;
private const SESSIYA_MUDDATI = 30 * 60;
private const MAKS_FAYL_HAJMI = 5 * 1024 * 1024;
Vaqtinchalik maydonni ajratish #
// OLDIN - klass ba'zan to'liq bo'lmagan holatda
final class Hisobot
{
private ?array $malumot = null;
private ?float $jami = null;
public function yuklang(): void
{
$this->malumot = $this->repozitoriy->oling();
$this->jami = array_sum($this->malumot);
}
public function chiqaring(): string
{
// yuklang() chaqirilmagan bo'lsa - xato
return "Jami: {$this->jami}";
}
}
// KEYIN - obyekt har doim to'liq
final class Hisobot
{
private function __construct(
private readonly array $malumot,
private readonly float $jami,
) {}
public static function yarating(Repozitoriy $repozitoriy): self
{
$malumot = $repozitoriy->oling();
return new self($malumot, array_sum($malumot));
}
public function chiqaring(): string
{
return "Jami: {$this->jami}";
}
}
Eski kod bilan ishlash #
// Yo'naltiruvchi qatlam
final class BuyurtmaYonaltiruvchi
{
public function __construct(
private EskiBuyurtmaTizimi $eski,
private YangiBuyurtmaServisi $yangi,
private SozlamaBayroqlari $bayroqlar,
) {}
public function yarating(array $malumot): array
{
if ($this->bayroqlar->yoqilganmi('yangi_buyurtma_tizimi')) {
return $this->yangi->yarating($malumot);
}
return $this->eski->createOrder($malumot);
}
}
// 1-hafta: 1% foydalanuvchi
$bayroqlar->foizOrnating('yangi_buyurtma_tizimi', 1);
// 2-hafta: 10%
// 3-hafta: 50%
// 4-hafta: 100%
Muammo chiqsa - bir zumda orqaga qaytasiz.
Xarakteristika testlari #
// Eski kod nima qilishini bilmaymiz - avval yozib olamiz
public function test_eski_hisoblagich_hozirgi_natijalari(): void
{
$hisoblagich = new EskiNarxHisoblagichi();
// Ishga tushirib, haqiqiy natijalarni yozib oldik
$this->assertEquals(115_000, $hisoblagich->calc(100_000, 'A', 1));
$this->assertEquals(95_000, $hisoblagich->calc(100_000, 'B', 1));
$this->assertEquals(230_000, $hisoblagich->calc(200_000, 'A', 1));
$this->assertEquals(0, $hisoblagich->calc(0, 'A', 1));
}
Ular eski kod to'g'ri ishlashini tekshirmaydi. Ular xatti-harakat o'zgarmaganini tekshiradi.
Bu refaktoring uchun yetarli xavfsizlik to'ri.
Bog'liqlikni uzish #
// Testlab bo'lmaydigan kod
final class HisobotYaratuvchi
{
public function yarating(): string
{
$bugun = date('Y-m-d'); // vaqtga bog'liq
$malumot = (new PDO(/* ... */))->query(/* ... */); // bazaga bog'liq
return $this->format($malumot, $bugun);
}
}
// 1-qadam: bog'liqlikni metodga ajratish
final class HisobotYaratuvchi
{
public function yarating(): string
{
return $this->format($this->malumotOling(), $this->bugun());
}
protected function bugun(): string
{
return date('Y-m-d');
}
protected function malumotOling(): array
{
return (new PDO(/* ... */))->query(/* ... */)->fetchAll();
}
}
// Test uchun vorislik
final class SinovHisobotYaratuvchi extends HisobotYaratuvchi
{
protected function bugun(): string
{
return '2026-08-15';
}
protected function malumotOling(): array
{
return [['raqam' => 'B-001', 'summa' => 100_000]];
}
}
// 2-qadam: to'liq injeksiya
final class HisobotYaratuvchi
{
public function __construct(
private HisobotRepozitoriyi $repozitoriy,
private SoatInterfeysi $soat,
) {}
public function yarating(): string
{
return $this->format(
$this->repozitoriy->oling(),
$this->soat->bugun(),
);
}
}
Qachon refaktoring qilish kerak? #
| Vaqt | Izoh |
|---|---|
| Uchinchi takrorlanishda | Ikki marta chidang, uchinchisida abstraksiya |
| Yangi imkoniyat qo'shishdan oldin | Kodni tayyorlang, keyin qo'shing |
| Xato tuzatgandan keyin | Nima uchun xato bo'lganini tuzating |
| Kod tekshiruvidan keyin | Izohlar bo'yicha |
| Kodni tushunganingizda | Tushunchani kodda mustahkamlang |
- Reliz oldidan
- Testlar yo'q bo'lganda
- Katta o'zgarish bilan bir vaqtda
- Muddat juda qisqa bo'lganda
- Kodni tushunmasdan turib
Qachon qayta yozmaslik kerak? #
Joel Spolsky ning mashhur maqolasidan:
*"Eski kodni o'qish qiyin. Shuning uchun dasturchilar uni yomon deb o'ylaydi va qayta yozishni xohlaydi. Lekin o'sha 'chalkash' kodda yillar davomida topilgan yuzlab xato tuzatishlari yashiringan."*
Qayta yozishda nima bo'ladi:
- Yangi tizim 1-2 yil davomida tayyor bo'lmaydi
- Bu vaqtda raqobatchilar oldinga ketadi
- Eski xatolar qaytadan paydo bo'ladi
- Yangi tizimda o'z xatolari qo'shiladi
- Migratsiya kutilganidan murakkab chiqadi
Netscape kompaniyasi shu qarordan keyin bozorni yo'qotgan.
- Texnologiya butunlay eskirgan (qo'llab-quvvatlanmaydi)
- Talablar tubdan o'zgargan
- Tizim kichik (bir necha ming satr)
- Eski tizim ishlashda davom etadi (parallel)
- Bo'g'uvchi anjir naqshi bilan bosqichma-bosqich
Deyarli har doim bosqichma-bosqich refaktoring yaxshiroq.
Refaktoring va biznes #
YOMON:
"Bizga refaktoring uchun ikki hafta kerak."
→ "Nima uchun? Hozir ham ishlayapti-ku."
YAXSHI:
"Katalog moduli sekin ishlagani uchun har bir yangi filtr qo'shish
3 kun oladi. Uni tartibga solsak - 4 soat bo'ladi.
Bizda 8 ta filtr rejalashtirilgan:
Hozirgi holatda: 24 kun
Refaktoringdan keyin: 5 kun (2 kun refaktoring + 3 kun ish)
Ikkinchi filtrdan keyinoq foyda bera boshlaydi."
Biznes "toza kod" ni tushunmaydi, lekin vaqt va pul ni tushunadi.
Refaktoringni investitsiya sifatida taqdim eting: qancha sarflaymiz, qancha tejaymiz, qachondan boshlab.
Muntazam yondashuv #
Har sprintda:
- Vaqtning 15-20% texnik qarzga
- Kamida bitta reyestr elementi
- Skaut qoidasi: har fayl bir oz yaxshilanadi
Har chorakda:
- Qarz reyestrini qayta ko'rib chiqish
- Ustuvorliklarni yangilash
- Katta qarzlar uchun alohida vaqt
"Ikki sprint faqat refaktoring qilamiz" degan yondashuv odatda muvaffaqiyatsiz bo'ladi:
- Biznes bunga rozi bo'lmaydi (yangi imkoniyat yo'q)
- Katta o'zgarishlar xavfli
- Qarz yana to'plana boshlaydi
Muntazam kichik ulush ancha samaraliroq.
- Loyihangizda texnik qarz reyestrini tuzing (kamida 5 element).
- Har biriga ta'sir, mehnat va ustuvorlik bering.
- Eng uzun metodni toping va uni ajrating.
- Murakkab shartni nomlangan metodga chiqaring.
matchyokiswitchni polimorfizm bilan almashtiring.- 5 tadan ko'p parametrli metodni obyektga jamlang.
- 10 ta sehrli sonni doimiyga aylantiring.
- Eski kod uchun xarakteristika testlari yozing.
- Vaqtga bog'liq kodni
SoatInterfeysibilan ajrating. - Refaktoring taklifini biznes tilida yozing.
Xulosa #
- Texnik qarz - tez yechim uchun to'lanadigan kelajakdagi narx.
- Ataylab olingan va hujjatlashtirilgan qarz - normal biznes qarori.
- Qarzni reyestrda ko'rinadigan qiling, aks holda u to'lanmaydi.
- Refaktoring - xatti-harakatni o'zgartirmasdan tuzilmani yaxshilash.
- Ikki qalpoq: bir vaqtda yo imkoniyat qo'shing, yo refaktoring qiling.
- Refaktoring uchun testlar shart.
- Asosiy usullar: metodni ajratish, shartni nomlash, polimorfizm, parametrlarni jamlash.
- Eski tizim uchun bo'g'uvchi anjir naqshi va funksiya kalitlari.
- Xarakteristika testlari eski kodni xavfsiz o'zgartirish imkonini beradi.
- "Noldan yozamiz" - deyarli har doim xato qaror.
- Refaktoringni biznes tilida: qancha sarflaymiz, qancha tejaymiz.
- Har sprintda 15-20% vaqt - eng samarali yondashuv.
Keyingi bo'limda jamoaviy ish va versiya nazoratini ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.