8-bo‘lim
SOLID va toza kod
Beshta SOLID prinsipi, nomlash qoidalari, funksiyalar, izohlar va kod hidlari.
Ushbu bo‘lim mundarijasi
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.
S - Yagona javobgarlik #
// 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.
// 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
{
// ...
}
}
Klass nima qilishini bitta jumlada tasvirlang.
Agar jumlada "va" so'zi bo'lsa - ehtimol ikkita javobgarlik bor:
"Bu klass foydalanuvchini saqlaydi va email yuboradi."
"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 #
// 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;
}
}
// 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.
"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 #
// 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;
}
}
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
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:
interface Shakl
{
public function yuza(): float;
}
final class Torburchak implements Shakl { /* ... */ }
final class Kvadrat implements Shakl { /* ... */ }
// 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.
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 #
// 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
}
// 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
}
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 #
// 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,
) {}
}
// Ilova ishga tushganda bir joyda ulanadi
$servis = new BuyurtmaServisi(
new MysqlBuyurtmaRepozitoriyi($pdo)
);
// Testda
$servis = new BuyurtmaServisi(
new XotiradaBuyurtmaRepozitoriyi()
);
// 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 #
- Maqsadni oshkor qiling:
$demas,$otganKunlar - Talaffuz qilinadigan bo'lsin:
$genymdhmsemas,$yaratilganSana - Qidirsa topiladigan bo'lsin:
7emas,HAFTA_KUNLARI - Turdan iborat prefiks kerak emas:
$strIsmemas,$ism - Klasslar - ot, metodlar - fe'l
- Bir tushuncha - bitta so'z:
oling,qidiring,topingaralashmasin
// 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;
// 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 #
- Kichik bo'lsin - 20 satrdan oshmasin
- Bitta ish qilsin
- Bitta abstraksiya darajasida bo'lsin
- Kam parametr - 3 tadan ko'p bo'lsa, obyektga jamlang
- Yon ta'sir bo'lmasin - nom aytmagan ishni qilmasin
- Mantiqiy bayroq parametri bo'lmasin
// 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 { }
// 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;
}
// 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 #
// $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:
if ($foydalanuvchi->voyagaYetganmi()) { }
// 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 #
// Hid: uzun chaqiruvlar zanjiri (Demeter qonuni buzilishi)
$shahar = $buyurtma->mijoz()->manzil()->shahar()->nomi();
// Yaxshiroq
$shahar = $buyurtma->yetkazishShahri();
// 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 { }
// 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 #
{
"scripts": {
"lint": "php-cs-fixer fix --dry-run --diff",
"fix": "php-cs-fixer fix",
"analyse": "phpstan analyse --level=8"
}
}
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.
# .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 #
"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.
- Loyihangizdan to'rt ish qiladigan klass toping va uni bo'ling.
if/elseifzanjirini Ochiq-yopiq prinsipiga muvofiq qayta yozing.- Liskov prinsipi buzilgan meros misolini toping yoki yozing.
- 8 metodli interfeysni uchta kichik interfeysga bo'ling.
newoperatori bilan yaratilgan bog'liqlikni konstruktorga chiqaring.- 10 ta yomon nomni yaxshi nomga o'zgartiring.
- Ichma-ich shartlarni erta qaytishga aylantiring.
- Bayroq parametrli metodni ikkita metodga bo'ling.
- Kodingizdagi 5 ta sehrli sonni doimiylarga aylantiring.
.editorconfigva 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.
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.