8-bo‘lim

SOLID va toza kod

Beshta SOLID prinsipi, nomlash qoidalari, funksiyalar, izohlar va kod hidlari.

🕑 16 daqiqa o‘qish 📄 856 so‘z 👁 7 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. SOLID nima?
  2. S - Yagona javobgarlik
  3. O - Ochiq-yopiq
  4. L - Liskov o'rniga qo'yish
  5. I - Interfeysni ajratish
  6. D - Bog'liqlikni teskarilash
  7. Toza kod: nomlash
  8. Toza kod: funksiyalar
  9. Izohlar
  10. Kod hidlari
  11. Kod uslubi
  12. Bo'ysunuvchi qoida
  13. Xulosa

Kod bir marta yoziladi, lekin o'nlab marta o'qiladi. Shuning uchun o'qilishi oson kod yozish - professional mahoratning asosi.

SOLID nima? #

Beshta prinsipning bosh harflari. Ular obyektga yo'naltirilgan dizaynning asosini tashkil qiladi.

SOLID prinsiplari S Yagona javobgarlik Har bir klassning o'zgarishi uchun bitta sabab bo'lsin O Ochiq-yopiq Kengaytirishga ochiq, o'zgartirishga yopiq L Liskov o'rniga qo'yish Voris ota klass o'rniga muammosiz ishlatilishi kerak I Interfeysni ajratish Kichik maxsus interfeyslar katta universaldan yaxshiroq D Bog'liqlikni teskarilash Abstraksiyaga tayaning, aniq amalga oshirishga emas
Bu prinsiplar bir-birini to'ldiradi

S - Yagona javobgarlik #

PHP
// YOMON: klass to'rt ish qilyapti
class Foydalanuvchi
{
    public function __construct(
        public string $ism,
        public string $email,
    ) {}

    public function saqlang(): void
    {
        $pdo = new PDO(/* ... */);
        $pdo->prepare('INSERT INTO foydalanuvchilar ...')->execute();
    }

    public function emailYuboring(string $matn): void
    {
        mail($this->email, 'Xabar', $matn);
    }

    public function emailTogrimi(): bool
    {
        return filter_var($this->email, FILTER_VALIDATE_EMAIL) !== false;
    }

    public function jsonGaAylantiring(): string
    {
        return json_encode(['ism' => $this->ism, 'email' => $this->email]);
    }
}

Bu klass o'zgarishi uchun to'rtta sabab bor: baza sxemasi o'zgarsa, email provayderi o'zgarsa, validatsiya qoidasi o'zgarsa, JSON formati o'zgarsa.

PHP
// YAXSHI: har bir klassda bitta javobgarlik
final class Foydalanuvchi
{
    public function __construct(
        public readonly string $ism,
        public readonly Email $email,
    ) {}
}

final class Email
{
    public function __construct(public readonly string $qiymat)
    {
        if (!filter_var($qiymat, FILTER_VALIDATE_EMAIL)) {
            throw new NotogriEmail($qiymat);
        }
    }

    public function __toString(): string
    {
        return $this->qiymat;
    }
}

final class FoydalanuvchiRepozitoriyi
{
    public function __construct(private PDO $pdo) {}

    public function saqlang(Foydalanuvchi $foydalanuvchi): void
    {
        // ...
    }
}

final class FoydalanuvchiXabarnomasi
{
    public function __construct(private PochtaYuboruvchi $pochta) {}

    public function xushKelibsizYuboring(Foydalanuvchi $foydalanuvchi): void
    {
        // ...
    }
}
Javobgarlikni qanday aniqlash mumkin?

Klass nima qilishini bitta jumlada tasvirlang.

Agar jumlada "va" so'zi bo'lsa - ehtimol ikkita javobgarlik bor:

"Bu klass foydalanuvchini saqlaydi va email yuboradi."

Haddan tashqari bo'lmang

"Har bir metod uchun alohida klass" - bu ham xato.

Prinsip javobgarlik haqida, metodlar soni haqida emas. Foydalanuvchi klassida 10 ta metod bo'lishi mumkin - agar hammasi bitta javobgarlikka tegishli bo'lsa.

O - Ochiq-yopiq #

PHP
// YOMON: yangi tur qo'shish uchun klassni o'zgartirish kerak
class ChegirmaHisoblagichi
{
    public function hisoblang(Buyurtma $buyurtma, string $mijozTuri): float
    {
        if ($mijozTuri === 'oddiy') {
            return 0;
        }

        if ($mijozTuri === 'kumush') {
            return $buyurtma->jami() * 0.05;
        }

        if ($mijozTuri === 'oltin') {
            return $buyurtma->jami() * 0.10;
        }

        return 0;
    }
}
PHP
// YAXSHI: yangi tur = yangi klass
interface ChegirmaQoidasi
{
    public function mosKeladi(Mijoz $mijoz): bool;
    public function hisoblang(Buyurtma $buyurtma): float;
}

final class KumushChegirmasi implements ChegirmaQoidasi
{
    public function mosKeladi(Mijoz $mijoz): bool
    {
        return $mijoz->daraja() === MijozDarajasi::Kumush;
    }

    public function hisoblang(Buyurtma $buyurtma): float
    {
        return $buyurtma->jami() * 0.05;
    }
}

final class OltinChegirmasi implements ChegirmaQoidasi
{
    public function mosKeladi(Mijoz $mijoz): bool
    {
        return $mijoz->daraja() === MijozDarajasi::Oltin;
    }

    public function hisoblang(Buyurtma $buyurtma): float
    {
        return $buyurtma->jami() * 0.10;
    }
}

final class TugilganKunChegirmasi implements ChegirmaQoidasi
{
    public function mosKeladi(Mijoz $mijoz): bool
    {
        return $mijoz->tugilganKuni()->format('m-d') === date('m-d');
    }

    public function hisoblang(Buyurtma $buyurtma): float
    {
        return min($buyurtma->jami() * 0.15, 100000);
    }
}

final class ChegirmaHisoblagichi
{
    /** @param ChegirmaQoidasi[] $qoidalar */
    public function __construct(private array $qoidalar) {}

    public function hisoblang(Buyurtma $buyurtma, Mijoz $mijoz): float
    {
        $chegirmalar = [];

        foreach ($this->qoidalar as $qoida) {
            if ($qoida->mosKeladi($mijoz)) {
                $chegirmalar[] = $qoida->hisoblang($buyurtma);
            }
        }

        return $chegirmalar === [] ? 0.0 : max($chegirmalar);
    }
}

Endi "tug'ilgan kun chegirmasi" qo'shildi - ChegirmaHisoblagichi o'zgarmadi.

Amaliy talqin

"Yangi imkoniyat qo'shish uchun mavjud kodni tahrirlash kerakmi yoki yangi fayl qo'shish yetarlimi?"

Ikkinchi javob - prinsip bajarilgan degani.

L - Liskov o'rniga qo'yish #

PHP
// YOMON: klassik "kvadrat-to'rtburchak" muammosi
class Torburchak
{
    public function __construct(protected float $en, protected float $boy) {}

    public function enOrnating(float $en): void { $this->en = $en; }
    public function boyOrnating(float $boy): void { $this->boy = $boy; }
    public function yuza(): float { return $this->en * $this->boy; }
}

class Kvadrat extends Torburchak
{
    public function enOrnating(float $en): void
    {
        $this->en = $en;
        $this->boy = $en;      // kutilmagan xatti-harakat!
    }

    public function boyOrnating(float $boy): void
    {
        $this->en = $boy;
        $this->boy = $boy;
    }
}
PHP
function yuzaniTekshir(Torburchak $shakl): void
{
    $shakl->enOrnating(5);
    $shakl->boyOrnating(4);

    assert($shakl->yuza() === 20.0);
}

yuzaniTekshir(new Torburchak(1, 1));   // OK
yuzaniTekshir(new Kvadrat(1, 1));      // Xato: yuza 16
Meros - "is-a" munosabati emas

Matematikada kvadrat - to'rtburchak. Lekin dasturlashda meros xususiyat emas, xatti-harakat haqida.

Kvadratning xatti-harakati to'rtburchaknikidan farq qiladi, shuning uchun u voris bo'la olmaydi.

Yechim: o'zgarmas (immutable) obyektlar yoki umumiy interfeys:

PHP
interface Shakl
{
    public function yuza(): float;
}

final class Torburchak implements Shakl { /* ... */ }
final class Kvadrat implements Shakl { /* ... */ }
PHP
// YOMON: voris shartni kuchaytirdi
class FaylOmbori
{
    public function saqlang(string $nom, string $mazmun): void { /* ... */ }
}

class RasmOmbori extends FaylOmbori
{
    public function saqlang(string $nom, string $mazmun): void
    {
        if (!str_ends_with($nom, '.jpg')) {
            throw new InvalidArgumentException('Faqat JPG');   // yangi cheklov!
        }

        parent::saqlang($nom, $mazmun);
    }
}

FaylOmbori kutgan kod RasmOmbori bilan buziladi.

Liskov qoidalari

Voris klass:

  • Kirish shartlarini kuchaytirmasligi kerak
  • Chiqish shartlarini zaiflashtirmasligi kerak
  • Yangi istisnolar tashlamasligi kerak
  • Ota klass invariantlarini buzmasligi kerak

Amalda: agar parent o'rniga child qo'yganda kod buzilsa - prinsip buzilgan.

I - Interfeysni ajratish #

PHP
// YOMON: juda katta interfeys
interface Xodim
{
    public function ishlang(): void;
    public function ovqatlaning(): void;
    public function maoshOling(): float;
    public function tatilGaChiqing(): void;
    public function hisobotYozing(): string;
    public function kodYozing(): void;
    public function mijozBilanGaplashing(): void;
}

class Robot implements Xodim
{
    public function ishlang(): void { /* ... */ }
    public function ovqatlaning(): void
    {
        throw new BadMethodCallException('Robot ovqatlanmaydi');  // ???
    }
    // qolgan metodlar ham keraksiz
}
PHP
// YAXSHI: kichik maxsus interfeyslar
interface Ishlovchi
{
    public function ishlang(): void;
}

interface MaoshOluvchi
{
    public function maoshOling(): float;
}

interface KodYozuvchi
{
    public function kodYozing(): void;
}

interface HisobotYozuvchi
{
    public function hisobotYozing(): string;
}

final class Dasturchi implements Ishlovchi, MaoshOluvchi, KodYozuvchi
{
    // faqat kerakli metodlar
}

final class Menejer implements Ishlovchi, MaoshOluvchi, HisobotYozuvchi
{
    // faqat kerakli metodlar
}

final class Robot implements Ishlovchi, KodYozuvchi
{
    // maosh olmaydi, ovqatlanmaydi
}
Interfeys hajmi

Yaxshi interfeysda odatda 1-5 metod bo'ladi.

10 dan ko'p metodli interfeys - deyarli har doim bo'linishi kerak degan signal.

D - Bog'liqlikni teskarilash #

Bog'liqlik yo'nalishini o'zgartirish Oldin BuyurtmaServisi MysqlRepozitoriy Yuqori daraja quyi darajaga bog'liq Bazani almashtirish - servisni o'zgartirish Keyin BuyurtmaServisi RepozitoriyInterfeysi MysqlRepozitoriy Ikkalasi ham abstraksiyaga bog'liq
Interfeys - o'zgarishlarni to'xtatuvchi to'siq
PHP
// YOMON
final class BuyurtmaServisi
{
    private MysqlBuyurtmaRepozitoriyi $repozitoriy;

    public function __construct()
    {
        $this->repozitoriy = new MysqlBuyurtmaRepozitoriyi(
            new PDO('mysql:host=localhost;dbname=dokon', 'root', '')
        );
    }
}

// YAXSHI
final class BuyurtmaServisi
{
    public function __construct(
        private BuyurtmaRepozitoriyi $repozitoriy,
    ) {}
}
PHP
// Ilova ishga tushganda bir joyda ulanadi
$servis = new BuyurtmaServisi(
    new MysqlBuyurtmaRepozitoriyi($pdo)
);

// Testda
$servis = new BuyurtmaServisi(
    new XotiradaBuyurtmaRepozitoriyi()
);
Uchta injeksiya usuli
PHP
// 1. Konstruktor - eng yaxshi usul
public function __construct(private Logger $jurnal) {}

// 2. Setter - ixtiyoriy bog'liqliklar uchun
public function jurnalOrnating(Logger $jurnal): void { }

// 3. Metod parametri - bir martalik ishlatishda
public function saqlang(Buyurtma $b, Logger $jurnal): void { }

Konstruktor injeksiyasi ni afzal ko'ring: bog'liqliklar ochiq ko'rinadi va obyekt har doim to'liq holatda bo'ladi.

Toza kod: nomlash #

Nomlash qoidalari
  1. Maqsadni oshkor qiling: $d emas, $otganKunlar
  2. Talaffuz qilinadigan bo'lsin: $genymdhms emas, $yaratilganSana
  3. Qidirsa topiladigan bo'lsin: 7 emas, HAFTA_KUNLARI
  4. Turdan iborat prefiks kerak emas: $strIsm emas, $ism
  5. Klasslar - ot, metodlar - fe'l
  6. Bir tushuncha - bitta so'z: oling, qidiring, toping aralashmasin
PHP
// YOMON
$d = 5;
$list1 = [];
function proc($x, $y) { }
class DataManager { }
$flag = true;

// YAXSHI
$otganKunlar = 5;
$faolFoydalanuvchilar = [];
function buyurtmaJamisiniHisobla(array $elementlar, float $chegirma): float { }
class BuyurtmaRepozitoriyi { }
$tolovTasdiqlangan = true;
PHP
// Sehrli sonlar
if ($foydalanuvchi->yosh > 17 && $kunlar < 30) { }

// Nomlangan doimiylar
private const VOYAGA_YETGAN_YOSHI = 18;
private const SINOV_MUDDATI_KUNLAR = 30;

if ($foydalanuvchi->yosh >= self::VOYAGA_YETGAN_YOSHI
    && $kunlar < self::SINOV_MUDDATI_KUNLAR) { }

Toza kod: funksiyalar #

Funksiya qoidalari
  1. Kichik bo'lsin - 20 satrdan oshmasin
  2. Bitta ish qilsin
  3. Bitta abstraksiya darajasida bo'lsin
  4. Kam parametr - 3 tadan ko'p bo'lsa, obyektga jamlang
  5. Yon ta'sir bo'lmasin - nom aytmagan ishni qilmasin
  6. Mantiqiy bayroq parametri bo'lmasin
PHP
// YOMON: bayroq parametri
public function saqlang(Buyurtma $buyurtma, bool $xabarYuborilsinmi): void
{
    // ...
    if ($xabarYuborilsinmi) {
        $this->xabarnoma->yuboring($buyurtma);
    }
}

$servis->saqlang($buyurtma, true);   // true nima anglatadi?

// YAXSHI: ikkita aniq metod
public function saqlang(Buyurtma $buyurtma): void { }
public function saqlangVaXabarBering(Buyurtma $buyurtma): void { }
PHP
// YOMON: yashirin yon ta'sir
public function parolTekshir(string $login, string $parol): bool
{
    $foydalanuvchi = $this->repozitoriy->loginBoyicha($login);

    if ($foydalanuvchi && password_verify($parol, $foydalanuvchi->parolXesh)) {
        $this->sessiya->boshlang($foydalanuvchi);   // nom buni aytmagan!
        return true;
    }

    return false;
}
PHP
// YOMON: ichma-ich shartlar
public function buyurtmaBering(Mijoz $mijoz, Savat $savat): Buyurtma
{
    if ($mijoz->faolmi()) {
        if (!$savat->boshmi()) {
            if ($mijoz->manzilBormi()) {
                if ($this->ombor->yetarlimi($savat)) {
                    return $this->yarating($mijoz, $savat);
                } else {
                    throw new ZaxiraYetarliEmas();
                }
            } else {
                throw new ManzilYoq();
            }
        } else {
            throw new SavatBosh();
        }
    } else {
        throw new MijozFaolEmas();
    }
}

// YAXSHI: erta qaytish (guard clauses)
public function buyurtmaBering(Mijoz $mijoz, Savat $savat): Buyurtma
{
    if (!$mijoz->faolmi()) {
        throw new MijozFaolEmas($mijoz->id);
    }

    if ($savat->boshmi()) {
        throw new SavatBosh();
    }

    if (!$mijoz->manzilBormi()) {
        throw new ManzilYoq($mijoz->id);
    }

    if (!$this->ombor->yetarlimi($savat)) {
        throw new ZaxiraYetarliEmas();
    }

    return $this->yarating($mijoz, $savat);
}

Izohlar #

Yaxshi kod izohga muhtoj emas
PHP
// $i ni bittaga oshiramiz
$i++;

// Foydalanuvchi yoshi 18 dan katta bo'lsa
if ($foydalanuvchi->yosh > 18) { }

Bu izohlar hech nima qo'shmaydi. Ular eskiradi va yolg'on gapira boshlaydi.

Izoh o'rniga - yaxshi nom:

PHP
if ($foydalanuvchi->voyagaYetganmi()) { }
Foydali izohlar
PHP
// Nima uchun shunday qilinganini tushuntiradi
// To'lov provayderi ba'zan 200 qaytarib, keyin webhook orqali
// rad etadi. Shuning uchun holatni darhol "tasdiqlangan" qilmaymiz.
$buyurtma->holatOrnating(Holat::TolovKutilmoqda);

// Murakkab qoidani izohlaydi
// Soliq qonuni 2026-01-01 dan: 100 mln so'mdan ortiq buyurtmada
// QQS alohida hisoblanadi (Vazirlar Mahkamasi qarori 145-son)
if ($jami > 100_000_000) {
    $qqs = $this->qqsAlohidaHisoblang($jami);
}

// Ogohlantiradi
// DIQQAT: bu metod butun jadvalni bloklaydi.
// Faqat texnik xizmat oynasida chaqiring.
public function barchaNarxlarniQaytaHisoblang(): void { }

// TODO va FIXME
// TODO: kesh qo'shish kerak, hozir har so'rovda hisoblanmoqda (#142)

Kod hidlari #

Kod hidlari - muammo belgilari Hajm bilan bog'liq • Uzun metod (50+ satr) • Katta klass (500+ satr) • Ko'p parametr (5+) Takrorlanish • Nusxa-ko'chirilgan kod • Bir xil shartlar zanjiri • O'xshash klasslar Bog'liqlik • Uzun chaqiruvlar zanjiri • Boshqa klass ichiga kirish • Aylanma bog'liqlik Tushunarsizlik • Sehrli sonlar • Noaniq nomlar • O'lik kod
Hid - albatta xato emas, lekin ko'rib chiqish signali
PHP
// Hid: uzun chaqiruvlar zanjiri (Demeter qonuni buzilishi)
$shahar = $buyurtma->mijoz()->manzil()->shahar()->nomi();

// Yaxshiroq
$shahar = $buyurtma->yetkazishShahri();
PHP
// Hid: ma'lumot to'plami - birga yuradigan parametrlar
public function yarating(
    string $kocha, string $uy, string $shahar,
    string $viloyat, string $indeks,
): void { }

// Yaxshiroq
public function yarating(Manzil $manzil): void { }
PHP
// Hid: hasadgo'y funksiya - boshqa klass ma'lumotini ko'p ishlatadi
class HisobotYaratuvchi
{
    public function mijozTavsifi(Mijoz $mijoz): string
    {
        return $mijoz->ism() . ' ' . $mijoz->familiya()
             . ', ' . $mijoz->yosh() . ' yosh'
             . ', ' . $mijoz->shahar();
    }
}

// Yaxshiroq: metod o'z klassiga ko'chiriladi
class Mijoz
{
    public function tavsif(): string
    {
        return "{$this->ism} {$this->familiya}, {$this->yosh} yosh, {$this->shahar}";
    }
}

Kod uslubi #

JSON
{
    "scripts": {
        "lint": "php-cs-fixer fix --dry-run --diff",
        "fix": "php-cs-fixer fix",
        "analyse": "phpstan analyse --level=8"
    }
}
Formatlash haqida bahslashmang

Avtomatik formatlovchi o'rnating (php-cs-fixer, prettier, black) va uni CI ga qo'shing.

Kod tekshiruvida otstuplar haqida bahslashish - vaqtni behuda sarflash. Vosita buni hal qilsin.

YAML
# .editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4

Bo'ysunuvchi qoida #

Skaut qoidasi

"Lagerni topganingizdan ham tozaroq qoldiring."

Har safar faylni ochganingizda uni bir oz yaxshilang:

  • Bitta noaniq nomni tuzating
  • Bitta sehrli sonni doimiyga aylantiring
  • Bitta uzun metodni ikkiga bo'ling

Bir kunda katta refaktoring qilish shart emas. Kichik yaxshilanishlar yig'ilib, katta natija beradi.

Amaliy topshiriq
  1. Loyihangizdan to'rt ish qiladigan klass toping va uni bo'ling.
  2. if/elseif zanjirini Ochiq-yopiq prinsipiga muvofiq qayta yozing.
  3. Liskov prinsipi buzilgan meros misolini toping yoki yozing.
  4. 8 metodli interfeysni uchta kichik interfeysga bo'ling.
  5. new operatori bilan yaratilgan bog'liqlikni konstruktorga chiqaring.
  6. 10 ta yomon nomni yaxshi nomga o'zgartiring.
  7. Ichma-ich shartlarni erta qaytishga aylantiring.
  8. Bayroq parametrli metodni ikkita metodga bo'ling.
  9. Kodingizdagi 5 ta sehrli sonni doimiylarga aylantiring.
  10. .editorconfig va avtomatik formatlovchi o'rnating.

Xulosa #

  • S - har bir klassning o'zgarishi uchun bitta sabab bo'lsin.
  • O - yangi imkoniyat yangi fayl qo'shish bilan qo'shilsin.
  • L - voris ota klass o'rniga muammosiz ishlatilishi kerak.
  • I - kichik maxsus interfeyslar katta universaldan yaxshiroq.
  • D - abstraksiyaga tayaning, aniq amalga oshirishga emas.
  • Nom maqsadni oshkor qilsin; sehrli sonlarni doimiyga aylantiring.
  • Funksiya kichik bo'lsin va bitta ish qilsin.
  • Erta qaytish ichma-ich shartlardan yaxshiroq.
  • Bayroq parametri o'rniga ikkita aniq metod.
  • Izoh nima uchun ni tushuntirsin, nima qilinayotganini emas.
  • Kod hidlari - muammo belgilari, ularni sezishni o'rganing.
  • Skaut qoidasi: har safar kodni bir oz yaxshilab qoldiring.

Keyingi bo'limda UML bilan modellashtirishni 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.