20-bo‘lim

Amaliy loyiha

Dokon omborini noldan qurish - turlar, tekshiruv, shartnoma, biznes mantig'i, sinovlar va qurish.

🕑 22 daqiqa o‘qish 📄 1 164 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. 1. Loyiha sozlamasi
  2. 2. Domen turlari
  3. 3. Tashqi ma'lumotni tekshirish
  4. 4. Tekshiruv sinovlari
  5. 5. Ombor shartnomasi
  6. 6. Biznes mantig'i
  7. 7. Xizmat sinovlari
  8. 8. Tur sinovlari
  9. 9. Dasturni ishga tushirish
  10. 10. To'liq sinov va qurish
  11. 11. Qat'iy sozlama ish beradi
  12. Butun darslikning xaritasi
  13. Xulosa

Endi darslikdagi hamma narsani bitta ishlaydigan loyihada birlashtiramiz.

Qurayotgan narsamiz - dokon ombori: mahsulot qo'shadi, tashqi ma'lumotni tekshiradi, sotuvni rasmiylashtiradi va qoldiqni yuritadi.

Har bir qadamda qaysi bob ishlayotganini ko'rsatib boramiz.

1. Loyiha sozlamasi #

JSON
{
  "name": "dokon",
  "type": "module",
  "private": true
}
JSON
{
  "compilerOptions": {
    "target": "es2022",
    "module": "esnext",
    "moduleResolution": "bundler",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}
Terminal
echo "sozlama tayyor"
Natija
sozlama tayyor

Bu - 12-bobdagi tavsiya etilgan sozlama. "type": "module" Node.js ga chiqish fayllarini ES moduli sifatida o'qishni aytadi.

2. Domen turlari #

TS
export type Mahsulot = {
  readonly id: number;
  nomi: string;
  narx: number;
  soni: number;
};

export type YangiMahsulot = Omit<Mahsulot, "id">;

export type Tolov =
  | { usul: "naqd"; berilgan: number }
  | { usul: "karta"; raqam: string };

export type Natija<T> =
  | { ok: true; qiymat: T }
  | { ok: false; xato: string };

export function yaxshi<T>(qiymat: T): Natija<T> {
  return { ok: true, qiymat };
}

export function yomon<T>(xato: string): Natija<T> {
  return { ok: false, xato };
}
Terminal
tsc --noEmit && echo "tsc: xato yo_q"
Natija
tsc: xato yo_q

Yigirma qatorda to'rtta bob ishladi:

YozuvBob
readonly id4 - obyekt turlari
Omit<Mahsulot, "id">13 - utility turlar
Tolov ajratilgan unioni6 - union
Natija<T> generigi9 - generiklar

YangiMahsulot - bazaga yozishdan oldingi holat: id ni baza beradi, shuning uchun uni chiqarib tashladik.

3. Tashqi ma'lumotni tekshirish #

TS
import { yaxshi, yomon } from "./turlar.js";
import type { Natija, YangiMahsulot } from "./turlar.js";

function obyektmi(x: unknown): x is Record<string, unknown> {
  return typeof x === "object" && x !== null && !Array.isArray(x);
}

export function tekshir(x: unknown): Natija<YangiMahsulot> {
  if (!obyektmi(x)) return yomon("obyekt kutilgan");

  const { nomi, narx, soni } = x;

  if (typeof nomi !== "string" || nomi.trim() === "") {
    return yomon("nomi bosh bolmasligi kerak");
  }
  if (typeof narx !== "number" || narx <= 0) {
    return yomon("narx musbat son bolishi kerak");
  }
  if (typeof soni !== "number" || !Number.isInteger(soni) || soni < 0) {
    return yomon("soni manfiy bolmagan butun son bolishi kerak");
  }

  return yaxshi({ nomi, narx, soni });
}
Terminal
tsc --noEmit && echo "tsc: xato yo_q"
Natija
tsc: xato yo_q

Bu - loyihaning chegarasi. Bu yerdan keyin hamma narsa turlangan, bu yergacha esa hech narsaga ishonilmaydi.

YozuvBob
x: unknown15 - hech narsaga ishonmaslik
x is Record<...>7 - tur predikati
typeof nomi !== "string"7 - toraytirish
import type11 - tur importi
"./turlar.js"11 - import yo'li

nomi.trim() === "" va narx <= 0 - turlar tuta olmaydigan tekshiruvlar. Tur string deydi, "bo'sh bo'lmagan satr" demaydi.

4. Tekshiruv sinovlari #

TS
import { test } from "node:test";
import assert from "node:assert/strict";
import { tekshir } from "./tekshir.js";

const holatlar: Array<[unknown, boolean]> = [
  [{ nomi: "Olma", narx: 12000, soni: 10 }, true],
  [{ nomi: "", narx: 12000, soni: 10 }, false],
  [{ nomi: "Olma", narx: -1, soni: 10 }, false],
  [{ nomi: "Olma", narx: 12000, soni: 1.5 }, false],
  ["mahsulot emas", false],
  [null, false],
];

for (const [kirish, kutilgan] of holatlar) {
  test(`tekshir: ${JSON.stringify(kirish)}`, () => {
    assert.equal(tekshir(kirish).ok, kutilgan);
  });
}
Terminal
node --import tsx --test src/tekshir.test.ts 2>&1 \
  | grep -E '^(ok|not ok|# (tests|pass|fail))'
Natija
ok 1 - tekshir: {"nomi":"Olma","narx":12000,"soni":10}
ok 2 - tekshir: {"nomi":"","narx":12000,"soni":10}
ok 3 - tekshir: {"nomi":"Olma","narx":-1,"soni":10}
ok 4 - tekshir: {"nomi":"Olma","narx":12000,"soni":1.5}
ok 5 - tekshir: "mahsulot emas"
ok 6 - tekshir: null
# tests 6
# pass 6
# fail 0

Array<[unknown, boolean]> - jadvalli sinov (19-bob). Kirish turi ataylab unknown: biz aynan noto'g'ri ma'lumotni sinamoqchimiz.

5. Ombor shartnomasi #

TS
import type { Mahsulot, YangiMahsulot } from "./turlar.js";

export type Ombor = {
  qoshish: (m: YangiMahsulot) => Promise<Mahsulot>;
  topish: (id: number) => Promise<Mahsulot | null>;
  hammasi: () => Promise<readonly Mahsulot[]>;
  yangilash: (
    id: number,
    ozgarish: Partial<YangiMahsulot>,
  ) => Promise<Mahsulot | null>;
};

export function xotiraOmbori(): Ombor {
  const yozuvlar = new Map<number, Mahsulot>();
  let keyingiId = 1;

  return {
    qoshish: async (m) => {
      const yangi: Mahsulot = { id: keyingiId, ...m };
      keyingiId += 1;
      yozuvlar.set(yangi.id, yangi);
      return yangi;
    },

    topish: async (id) => yozuvlar.get(id) ?? null,

    hammasi: async () => [...yozuvlar.values()],

    yangilash: async (id, ozgarish) => {
      const bor = yozuvlar.get(id);
      if (bor === undefined) return null;
      const yangi: Mahsulot = { ...bor, ...ozgarish };
      yozuvlar.set(id, yangi);
      return yangi;
    },
  };
}
Terminal
tsc --noEmit && echo "tsc: xato yo_q"
Natija
tsc: xato yo_q

Bu yerda ham bir nechta bob birga ishladi:

YozuvBob
qoshish: (m) => ... xossa yozuvi12 - metod emas, xossa
Partial<YangiMahsulot>13 - utility tur
Mahsulot | null6 - union
readonly Mahsulot[]4 - o'zgarmas massiv
Map<number, Mahsulot>9 - generik
?? null6 - nullish birlashtirish
async (m) => ... da m turlanmagan5 - kontekstli turlash

Xossa yozuvi ataylab tanlangan: 12-bobda ko'rganimizdek, metod yozuvi parametrlarni ikki tomonlama tekshiradi va teshik qoldiradi.

6. Biznes mantig'i #

TS
import { yaxshi, yomon } from "./turlar.js";
import type { Mahsulot, Natija, Tolov } from "./turlar.js";
import type { Ombor } from "./ombor.js";
import { tekshir } from "./tekshir.js";

export async function qoshish(
  ombor: Ombor,
  xom: unknown,
): Promise<Natija<Mahsulot>> {
  const t = tekshir(xom);
  if (!t.ok) return yomon(t.xato);
  return yaxshi(await ombor.qoshish(t.qiymat));
}

function tolovni(tolov: Tolov, jami: number): Natija<string> {
  switch (tolov.usul) {
    case "naqd":
      if (tolov.berilgan < jami) {
        return yomon(`kam berildi: ${tolov.berilgan} < ${jami}`);
      }
      return yaxshi(`naqd, qaytim ${tolov.berilgan - jami}`);
    case "karta":
      return yaxshi(`karta ${tolov.raqam}`);
    default: {
      const qolgan: never = tolov;
      return qolgan;
    }
  }
}

export async function sotish(
  ombor: Ombor,
  id: number,
  soni: number,
  tolov: Tolov,
): Promise<Natija<string>> {
  const m = await ombor.topish(id);
  if (m === null) return yomon(`mahsulot topilmadi: ${id}`);
  if (m.soni < soni) return yomon(`omborda yetarli emas: ${m.soni}`);

  const jami = m.narx * soni;
  const t = tolovni(tolov, jami);
  if (!t.ok) return t;

  await ombor.yangilash(id, { soni: m.soni - soni });
  return yaxshi(`${m.nomi} x${soni} = ${jami}, ${t.qiymat}`);
}
Terminal
tsc --noEmit && echo "tsc: xato yo_q"
Natija
tsc: xato yo_q

Diqqat qilinadigan uchta joy:

  • if (!t.ok) return yomon(t.xato); - shundan keyin t.qiymat ochiladi (6-bob, ajratilgan union);
  • const qolgan: never = tolov; - Tolov ga uchinchi usul qo'shilsa, bu qator darhol xato beradi (6-bob);
  • ombor: Ombor - haqiqiy baza emas, shartnoma qabul qilinadi. Shu tufayli sinov yozish oson (19-bob).
Loyiha qatlamlari: ishonch chegarasi qayerda Tashqi dunyo - ISHONILMAYDI JSON.parse, fetch, forma, process.env, fayl - hammasi unknown Turlar bu yerda hech narsani kafolatlamaydi tekshir.ts - CHEGARA unknown ichida, Natija<YangiMahsulot> tashqarida. Yagona o'tish nuqtasi. tur predikati Ichki kod - TURLANGAN xizmat.ts, ombor.ts - hamma qiymat aniq turga ega, any yo'q, as yo'q Bu yerda kompilyator to'liq kuchda ishlaydi Chegara qancha tor bo'lsa, ichkarida shuncha kam tekshiruv kerak bo'ladi.
Bitta tor chegara - qolgan hamma joyda to'liq ishonch

7. Xizmat sinovlari #

TS
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { xotiraOmbori } from "./ombor.js";
import { qoshish, sotish } from "./xizmat.js";
import type { Tolov } from "./turlar.js";

const naqd: Tolov = { usul: "naqd", berilgan: 40000 };
const karta: Tolov = { usul: "karta", raqam: "8600" };

describe("sotish", () => {
  it("naqd tolovda qaytim hisoblanadi", async () => {
    const o = xotiraOmbori();
    await qoshish(o, { nomi: "Olma", narx: 12000, soni: 10 });

    const r = await sotish(o, 1, 3, naqd);
    assert.equal(r.ok, true);
    assert.equal(
      r.ok ? r.qiymat : "",
      "Olma x3 = 36000, naqd, qaytim 4000",
    );
  });

  it("sotuvdan keyin soni kamayadi", async () => {
    const o = xotiraOmbori();
    await qoshish(o, { nomi: "Olma", narx: 12000, soni: 10 });
    await sotish(o, 1, 3, karta);

    const m = await o.topish(1);
    assert.equal(m?.soni, 7);
  });

  it("yetarli bolmasa rad etadi", async () => {
    const o = xotiraOmbori();
    await qoshish(o, { nomi: "Olma", narx: 12000, soni: 2 });

    const r = await sotish(o, 1, 5, karta);
    assert.equal(r.ok, false);
    assert.equal(r.ok ? "" : r.xato, "omborda yetarli emas: 2");
  });

  it("kam pul berilsa rad etadi", async () => {
    const o = xotiraOmbori();
    await qoshish(o, { nomi: "Anor", narx: 25000, soni: 5 });

    const r = await sotish(o, 1, 2, naqd);
    assert.equal(r.ok ? "" : r.xato, "kam berildi: 40000 < 50000");
  });

  it("mahsulot yoq bolsa rad etadi", async () => {
    const o = xotiraOmbori();
    const r = await sotish(o, 99, 1, karta);
    assert.equal(r.ok ? "" : r.xato, "mahsulot topilmadi: 99");
  });
});
Terminal
node --import tsx --test src/xizmat.test.ts 2>&1 \
  | grep -E '^(ok|not ok|# (tests|pass|fail))'
Natija
ok 1 - sotish
# tests 5
# pass 5
# fail 0

const naqd: Tolov = {...} - annotatsiya ataylab yozilgan. Usiz usul string ga kengayardi (14-bob) va Tolov ga mos kelmasdi.

r.ok ? r.qiymat : "" - r.qiymat ga faqat ok tekshirilgach kirish mumkin.

8. Tur sinovlari #

TS
import { tekshir } from "./tekshir.js";
import type { Mahsulot, YangiMahsulot } from "./turlar.js";

const a: YangiMahsulot = { nomi: "Olma", narx: 12000, soni: 1 };

// @ts-expect-error - YangiMahsulot da id yoq
const b: YangiMahsulot = { id: 1, nomi: "Olma", narx: 1, soni: 1 };

declare const m: Mahsulot;
// @ts-expect-error - id readonly
m.id = 2;

const r = tekshir({});
// @ts-expect-error - avval ok tekshirilishi kerak
r.qiymat;

console.log(a.nomi);
export {};
Terminal
tsc --noEmit && echo "tur sinovi otdi"
Natija
tur sinovi otdi

Uchta va'da yozib qo'yildi:

  • YangiMahsulot da id bo'lmasligi kerak;
  • Mahsulot.id o'zgartirilmasligi kerak;
  • Natija ni tekshirmasdan ishlatib bo'lmasligi kerak.

Kimdir readonly ni olib tashlasa yoki Omit ni buzsa, bu fayl TS2578 beradi (19-bob).

9. Dasturni ishga tushirish #

TS
import { xotiraOmbori } from "./ombor.js";
import { qoshish, sotish } from "./xizmat.js";
import type { Tolov } from "./turlar.js";

const ombor = xotiraOmbori();

const kirish: unknown[] = [
  { nomi: "Olma", narx: 12000, soni: 10 },
  { nomi: "Anor", narx: 25000, soni: 4 },
  { nomi: "", narx: 5000, soni: 1 },
  { nomi: "Uzum", narx: -1, soni: 1 },
  "mahsulot emas",
];

for (const x of kirish) {
  const r = await qoshish(ombor, x);
  console.log(r.ok ? `+ #${r.qiymat.id} ${r.qiymat.nomi}` : `- ${r.xato}`);
}

console.log("---");

const sotuvlar: Array<{ id: number; soni: number; tolov: Tolov }> = [
  { id: 1, soni: 3, tolov: { usul: "naqd", berilgan: 40000 } },
  { id: 2, soni: 2, tolov: { usul: "karta", raqam: "8600" } },
  { id: 1, soni: 99, tolov: { usul: "karta", raqam: "8600" } },
];

for (const s of sotuvlar) {
  const r = await sotish(ombor, s.id, s.soni, s.tolov);
  console.log(r.ok ? `+ ${r.qiymat}` : `- ${r.xato}`);
}

console.log("---");

for (const m of await ombor.hammasi()) {
  console.log(`#${m.id} ${m.nomi}: ${m.soni} dona`);
}

export {};
Terminal
tsx src/asosiy.ts
Natija
+ #1 Olma
+ #2 Anor
- nomi bosh bolmasligi kerak
- narx musbat son bolishi kerak
- obyekt kutilgan
---
+ Olma x3 = 36000, naqd, qaytim 4000
+ Anor x2 = 50000, karta 8600
- omborda yetarli emas: 7
---
#1 Olma: 7 dona
#2 Anor: 2 dona

Beshta kirishdan ikkitasi qabul qilindi, uchtasi aniq sabab bilan rad etildi. Hech qayerda istisno tashlanmadi.

kirish: unknown[] va sotuvlar: Array<{...}> - ikkalasida ham annotatsiya bor. Birinchisi ishonchsizlikni, ikkinchisi esa kengayishdan himoyani bildiradi.

10. To'liq sinov va qurish #

Terminal
node --import tsx --test "src/*.test.ts" 2>&1 \
  | grep -E '^(ok|not ok|# (tests|pass|fail))'
echo "--- qurish ---"
tsc
ls dist
echo "--- qurilgan dasturni ishga tushirish ---"
node dist/asosiy.js | tail -4
Natija
ok 1 - tekshir: {"nomi":"Olma","narx":12000,"soni":10}
ok 2 - tekshir: {"nomi":"","narx":12000,"soni":10}
ok 3 - tekshir: {"nomi":"Olma","narx":-1,"soni":10}
ok 4 - tekshir: {"nomi":"Olma","narx":12000,"soni":1.5}
ok 5 - tekshir: "mahsulot emas"
ok 6 - tekshir: null
ok 7 - sotish
# tests 11
# pass 11
# fail 0
--- qurish ---
asosiy.js
ombor.js
tekshir.js
tekshir.test.js
turlar.js
turlar.tur-sinov.js
xizmat.js
xizmat.test.js
--- qurilgan dasturni ishga tushirish ---
- omborda yetarli emas: 7
---
#1 Olma: 7 dona
#2 Anor: 2 dona

Qurilgan JavaScript tsx siz, oddiy node bilan ishladi.

Sinovlar dist/ ga tushib qolgan

ls dist da *.test.js fayllari ham ko'rinib turibdi - chunki include: ["src"] hammasini qamrab oldi.

Haqiqiy loyihada qurish uchun alohida sozlama yoziladi:

Natija
// tsconfig.build.json
{
  "extends": "./tsconfig.json",
  "exclude": ["src/**/*.test.ts", "src/**/*.tur-sinov.ts"]
}

Keyin tsc -p tsconfig.build.json. Tahrirlovchi esa asosiy tsconfig.json ni ishlatadi va sinovlarni ham tekshiradi.

11. Qat'iy sozlama ish beradi #

Loyihaga bitta yangi fayl qo'shib ko'ramiz:

TS
import { xotiraOmbori } from "./ombor.js";

const ombor = xotiraOmbori();
const hammasi = await ombor.hammasi();

const birinchi = hammasi[0];
console.log(birinchi.nomi);

export {};
Terminal
tsc --noEmit 2>&1 | fold -sw 74
rm -f src/xato.ts
Natija
src/xato.ts(7,13): error TS18048: 'birinchi' is possibly 'undefined'.

Ombor bo'sh bo'lishi mumkin, demak hammasi[0] undefined bo'lishi mumkin. Buni noUncheckedIndexedAccess tutdi - 12-bobda ko'rganimizdek, u strict ga kirmaydi.

Bu xato bo'lmasa, dastur bo'sh omborda yiqilardi.

Terminal
tsc --noEmit && echo "tsc: xato yo_q"
ls src
Natija
tsc: xato yo_q
asosiy.ts
ombor.ts
tekshir.test.ts
tekshir.ts
turlar.ts
turlar.tur-sinov.ts
xizmat.test.ts
xizmat.ts

Sakkizta fayl, any yo'q, as yo'q (bitta tur predikatidan tashqari), 11 ta sinov va to'liq qat'iy sozlama.

Butun darslikning xaritasi #

BobAsosiy fikr
1Turlar kompilyatsiyadan keyin yo'qoladi
2tsconfig.json bo'lmasa, bayroqlar e'tiborsiz qoladi
3const literal, let kengaygan tur beradi
4Ortiqcha maydon faqat literalda tekshiriladi
5void ga berish mumkin, undan olish mumkin emas
6Union maydonlarni kamaytiradi, & ko'paytiradi
7Predikat, ! va o'zgarish - uchta teshik
8interface birlashadi, type birlashmaydi
9Generik kirish turini chiqishga uzatadi
10private ish vaqtida yo'q, # bor
11Modulsiz fayl global sohada yashaydi
12Metod yozuvi strict da ham teshik qoldiradi
13Omit kalitni tekshirmaydi, Pick tekshiradi
14satisfies tekshiradi, turni almashtirmaydi
15any tarqaladi, unknown to'xtatadi
16declare module - tekshirilmaydigan va'da
17forEach(async ...) kutmaydi
18Ko'chirish bosqichma-bosqich, barglardan boshlab
19Turlar va sinovlar turli xatolarni tutadi
20Chegarani tor qiling, ichkarida ishoning
Bundan keyin nima o'rganish kerak
MavzuNima beradi
Zod yoki ValibotChegarani qo'lda yozmaslik
typescript-eslintno-floating-promises va boshqalar
Shartli turlar, inferMurakkab utility turlar
Loyiha havolalari (project references)Katta monorepo
Freymvork turlari (React, Express)Amaliy qo'llash

Eng foydalisi esa - o'z loyihangizdagi any larni birma-bir yo'qotish. Har bir yo'qotilgan any - kompilyator uchun ochilgan yangi hudud.

Xulosa #

  • Loyiha qat'iy tsconfig.json dan boshlanadi - keyinchalik qat'iylashtirish ancha qiyin.
  • Domen turlari bitta faylda yig'iladi va qolgan hamma joy ulardan foydalanadi.
  • Omit<Mahsulot, "id"> - "hali saqlanmagan yozuv" ni ifodalashning tabiiy yo'li.
  • Natija<T> istisno o'rniga xatoni turda ko'rsatadi va chaqiruvchini uni ishlashga majbur qiladi.
  • Tashqi ma'lumot faqat bitta joydan - chegaradan o'tadi; u yerda tur unknown.
  • Turlar tuta olmaydigan qoidalar (bo'sh satr, manfiy narx) ham o'sha chegarada tekshiriladi.
  • Ombor shartnoma sifatida beriladi, shuning uchun sinov uchun baza kerak emas.
  • never tekshiruvi unionga yangi a'zo qo'shilganda unutilgan joylarni topib beradi.
  • Tur sinovlari (@ts-expect-error) turlarning o'zi buzilmasligini kafolatlaydi.
  • noUncheckedIndexedAccess massivning bo'sh bo'lishi mumkin bo'lgan joyini topdi - u strict ga kirmaydi.
  • Qurilgan JavaScript hech qanday qo'shimcha vositasiz, oddiy node bilan ishlaydi.
  • Chegara qancha tor bo'lsa, ichkarida shuncha kam tekshiruv kerak bo'ladi.
Amaliy topshiriq
  1. Loyihani noldan qayta yozing va har bir bosqichda tsc --noEmit ni ishga tushiring.
  2. Tolov ga { usul: "payme"; telefon: string } qo'shing va kompilyator qaysi qatorlarni ko'rsatishini sanang.
  3. tekshir ga soni uchun yuqori chegara qo'shing va sinov yozing.
  4. Ombor ga ochirish(id: number): Promise<boolean> metodini qo'shing.
  5. Soxta omborni satisfies Ombor bilan yozib, sinovlarni undan foydalanadigan qiling.
  6. Mahsulot dan readonly ni olib tashlang va tur sinovi yiqilishini ko'ring.
  7. noUncheckedIndexedAccess ni o'chirib, 11-qadamdagi xatoning yo'qolishini tasdiqlang.
  8. tsconfig.build.json yozib, sinovlarni dist/ dan chiqarib tashlang.
  9. Xotira ombori o'rniga JSON faylga yozadigan omborni yozing - Ombor turi o'zgarmasligi kerak.
  10. Butun loyihani strict dan tashqari barcha qo'shimcha bayroqlar bilan qurib ko'ring va nechta yangi xato chiqqanini yozib qo'ying.

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.