15-bo‘lim

Refaktoring va texnik qarz

Texnik qarz turlari, refaktoring usullari, eski kod bilan ishlash va qachon qayta yozish kerak emasligi.

🕑 14 daqiqa o‘qish 📄 881 so‘z 👁 6 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Texnik qarz
  2. Qarz turlari
  3. Qarz reyestri
  4. Refaktoring nima?
  5. Asosiy refaktoring usullari
  6. Metodni ajratish
  7. O'zgaruvchi kiritish
  8. Shartni metodga ajratish
  9. Shartni polimorfizm bilan almashtirish
  10. Parametrlarni obyektga jamlash
  11. Sehrli sonni doimiyga
  12. Vaqtinchalik maydonni ajratish
  13. Eski kod bilan ishlash
  14. Xarakteristika testlari
  15. Bog'liqlikni uzish
  16. Qachon refaktoring qilish kerak?
  17. Qachon qayta yozmaslik kerak?
  18. Refaktoring va biznes
  19. Muntazam yondashuv
  20. Xulosa

Har bir loyihada kod vaqt o'tishi bilan yomonlashadi. Refaktoring - bu jarayonga qarshi turishning yagona yo'li.

Texnik qarz #

Texnik qarz - moliyaviy qarz kabi Vaqt Ishlab chiqish tezligi qarz to'lanmasa muntazam refaktoring bilan farq "Foiz" - har bir yangi imkoniyat sekinroq qo'shiladi
Qarz to'lanmasa, foizi kuchayib boradi
Metafora

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 #

Texnik qarz kvadranti Ehtiyotsiz va ataylab "Bizda dizaynga vaqt yo'q" Eng yomon holat Ehtiyotkor va ataylab "Hozir tez chiqaramiz, oqibatini bilamiz" Maqbul - agar qaytarilsa Ehtiyotsiz va tasodifiy "Qatlamlar nima?" Bilim yetishmasligi Ehtiyotkor va tasodifiy "Endi qanday qilish kerakligini tushundik" Tabiiy va foydali Ehtiyotsiz Ehtiyotkor Ataylab Tasodifiy
Ataylab olingan va hujjatlashtirilgan qarz - normal biznes qarori
PHP
// 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 #

MARKDOWN
# 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 |
Qarzni ko'rinadigan qiling

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? #

Aniq ta'rif

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.

Ikki qalpoq metaforasi Imkoniyat qo'shish • Yangi test yoziladi • Yangi kod qo'shiladi • Xatti-harakat o'zgaradi Tuzilmani o'zgartirmaymiz Refaktoring • Yangi test yozilmaydi • Tuzilma yaxshilanadi • Xatti-harakat o'zgarmaydi Imkoniyat qo'shmaymiz Bir vaqtda faqat bitta qalpoqni kiying
Ikkalasini aralashtirsangiz, xato qayerdan kelganini bilmaysiz
Refaktoring uchun testlar shart

Testsiz refaktoring - kodni buzish.

Ketma-ketlik:

  1. Mavjud xatti-harakatni testlar bilan qoplang
  2. Testlar yashil ekaniga ishonch hosil qiling
  3. Kichik o'zgarish kiriting
  4. Testlarni ishga tushiring
  5. 3-4 ni takrorlang

Asosiy refaktoring usullari #

Metodni ajratish #

PHP
// 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;
}
PHP
// 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 #

PHP
// 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 #

PHP
// 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 #

PHP
// 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,
    };
}
PHP
// 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 #

PHP
// 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 #

PHP
// 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 #

PHP
// 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 #

Bo'g'uvchi anjir naqshi 1-bosqich Eski tizim 2-bosqich Eski Yangi yo'naltiruvchi 3-bosqich Yangi tizim Yangi imkoniyatlar yangi tizimda, eski asta-sekin ko'chiriladi Har qanday paytda tizim ishlaydi - "katta portlash" yo'q
Eski tizimni bosqichma-bosqich almashtirish usuli
PHP
// 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);
    }
}
Funksiya kalitlari bilan bosqichma-bosqich
PHP
// 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 #

PHP
// 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));
}
Bu testlar "to'g'ri" emas - ular "hozirgi"

Ular eski kod to'g'ri ishlashini tekshirmaydi. Ular xatti-harakat o'zgarmaganini tekshiradi.

Bu refaktoring uchun yetarli xavfsizlik to'ri.

Bog'liqlikni uzish #

PHP
// 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);
    }
}
PHP
// 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]];
    }
}
PHP
// 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? #

To'g'ri paytlar
VaqtIzoh
Uchinchi takrorlanishdaIkki marta chidang, uchinchisida abstraksiya
Yangi imkoniyat qo'shishdan oldinKodni tayyorlang, keyin qo'shing
Xato tuzatgandan keyinNima uchun xato bo'lganini tuzating
Kod tekshiruvidan keyinIzohlar bo'yicha
Kodni tushunganingizdaTushunchani kodda mustahkamlang
Noto'g'ri paytlar
  • 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? #

"Noldan yozamiz" - deyarli har doim xato

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:

  1. Yangi tizim 1-2 yil davomida tayyor bo'lmaydi
  2. Bu vaqtda raqobatchilar oldinga ketadi
  3. Eski xatolar qaytadan paydo bo'ladi
  4. Yangi tizimda o'z xatolari qo'shiladi
  5. Migratsiya kutilganidan murakkab chiqadi

Netscape kompaniyasi shu qarordan keyin bozorni yo'qotgan.

Qayta yozish oqlanadigan holatlar
  • 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 #

Natija
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 tilida gapiring

Biznes "toza kod" ni tushunmaydi, lekin vaqt va pul ni tushunadi.

Refaktoringni investitsiya sifatida taqdim eting: qancha sarflaymiz, qancha tejaymiz, qachondan boshlab.

Muntazam yondashuv #

Natija
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
"Refaktoring sprinti" ishlamaydi

"Ikki sprint faqat refaktoring qilamiz" degan yondashuv odatda muvaffaqiyatsiz bo'ladi:

  1. Biznes bunga rozi bo'lmaydi (yangi imkoniyat yo'q)
  2. Katta o'zgarishlar xavfli
  3. Qarz yana to'plana boshlaydi

Muntazam kichik ulush ancha samaraliroq.

Amaliy topshiriq
  1. Loyihangizda texnik qarz reyestrini tuzing (kamida 5 element).
  2. Har biriga ta'sir, mehnat va ustuvorlik bering.
  3. Eng uzun metodni toping va uni ajrating.
  4. Murakkab shartni nomlangan metodga chiqaring.
  5. match yoki switch ni polimorfizm bilan almashtiring.
  6. 5 tadan ko'p parametrli metodni obyektga jamlang.
  7. 10 ta sehrli sonni doimiyga aylantiring.
  8. Eski kod uchun xarakteristika testlari yozing.
  9. Vaqtga bog'liq kodni SoatInterfeysi bilan ajrating.
  10. 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.

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.