Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions learn/senior-web-dev/00-nang-luc-senior.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# L0 — Mô hình năng lực senior (thang đo)

Dùng file này để tự chấm. Chấm 0–3: 0 = chưa biết, 1 = từng dùng, 2 = giải thích được, 3 = từng dạy lại cho người khác.

## 0.1 Kỹ thuật: sâu một stack, rộng mọi tầng

Định nghĩa: với NestJS/Next.js/TS, bạn trả lời được "cái này hoạt động thế nào" ở mọi tầng (runtime → HTTP → DB). Không cần expert mọi thứ, cần không im lặng ở tầng nào.

Ví dụ tự test — chuỗi câu hỏi xuyên tầng:
```
"Câu await fetch() trong service Nest chạy ở đâu?"
→ TS: promise suspension (L2)
→ Node: microtask queue (L1)
→ HTTP: keep-alive socket (L3)
→ Prisma: pool connection (L9)
```
Trả lời được cả 4 = đạt 0.1.

## 0.2 Phán đoán trade-off

Định nghĩa: mỗi lựa chọn kỹ thuật nói được 2 cột: được gì / mất gì.

Ví dụ: JWT vs session
| | JWT | Session |
|---|---|---|
| Được | không cần lookup mỗi request | logout tức thì, revocation dễ |
| Mất | thu hồi khó (phải blacklist) | 1 round-trip Redis mỗi request |
Senior kết luận bằng ngữ cảnh: "internal tool, logout gấp → session. Mobile stateless → JWT + refresh rotation."

## 0.3 Hệ quả dài hạn

Định nghĩa: thấy quyết định hôm nay ở điểm 12–24 tháng.

Ví dụ: chọn `enum` trong Postgres:
- hôm nay: sạch, rẻ
- 1 năm sau: thêm giá trị = migration lock table lớn, rollback migration khó
- hệ quả: nhiều team chốt "enum ở app layer + check constraint" — quyết định dựa trên chi phí tương lai, không phải sự tiện hôm nay.

## 0.4 Communicate risk

Ba câu senior phải nói được và nói sớm:
1. "Con số này là estimate, sai số ±30%, em cần thêm X để siết lại."
2. "Em chưa làm cái này bao giờ. Em ước lượng dựa trên Y, nhưng cần người review."
3. "Cái này nổ to nếu Q tăng gấp 10. Đề xuất chặn bằng rate limit trước, refactor sau."

Ví dụ anti-pattern (mid hay làm): im lặng nhận deadline → 2 tuần trước release mới báo trễ. Senior báo rủi ro ở ngày thứ 2.

## 0.5 Nâng người khác

Đơn vị đo không phải "em giúp được nhiều người" mà là "codebase dễ vào hơn cho người sau".

Ví dụ review comment:
- ❌ mid: "Sai rồi, sửa đi."
- ✅ senior: "Guard này chạy DB query mỗi request cho route public. Route /health bị chậm theo. Đề xuất: bỏ guard khỏi public route, hoặc cache 60s.case."
Kèm: ADR + CONTEXT.md glossary (org bạn đang làm sẵn — dùng nó làm công cụ).

## 0.6 Ownership

Định nghĩa: vào = yêu cầu mơ hồ ("khách phàn nàn trang chậm"), ra = hệ thống chạy + có dashboard + có alert + có doc + người khác vận hành được không cần bạn.

Ví dụ checklist tự chấm cho 1 feature bạn đã làm:
- [ ] đo được effect bằng metric trước/sau
- [ ] có alert khi nó hỏng
- [ ] có runbook 10 dòng cho người khác oncall
- [ ] viết ADR cho quyết định không hiển nhiên

4/4 = 0.6 đạt.

---
Tự chấm xong: gửi kết quả cho tôi, tôi cắt bớt các tầng đã ≥2 trong LEARNING-PATH.
159 changes: 159 additions & 0 deletions learn/senior-web-dev/01-javascript-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# L1 — JavaScript runtime

## 1.1 Primitives vs object, tham chiếu vs giá trị

Primitives (string, number, boolean, null, undefined, symbol, bigint) copy theo giá trị. Object/array/function copy theo tham chiếu.

```js
const a = { n: 1 };
const b = a; // b trỏ cùng object
b.n = 2;
console.log(a.n); // 2 — không phải bản copy

const c = [1, [2]];
const d = [...c]; // shallow copy: d[1] vẫn cùng array với c[1]
d[1].push(3);
console.log(c[1]); // [2,3] — mutate xuyên qua shallow copy
```
Deep copy thật: `structuredClone(c)` (không clone được function/DOM node).

## 1.2 `this` — quyết định tại call site, không phải lúc khai báo

```js
function who() { return this; }
who(); // undefined (strict mode) / globalThis
const obj = { who };
obj.who(); // obj — call site là obj
const f = obj.who;
f(); // undefined — đã mất call site
const g = f.bind(obj);
g(); // obj — bind cố định
[1].map(obj.who); // window/undefined — map gọi hàm trần, không qua obj
```
Arrow function: `this` lấy từ scope lexical chứa nó — vì vậy arrow không dùng làm method cần `this` của object.

Closure ví dụ interview kinh điển:
```js
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)); // 3,3,3 — 1 biến i
for (let i = 0; i < 3; i++) setTimeout(() => console.log(i)); // 0,1,2 — mỗi loop 1 binding
```

## 1.3 Prototype chain

`class` là sugar. Bản chất: object này delegate lên object kia.

```js
const animal = { eats() { return 'yum'; } };
const dog = Object.create(animal);
dog.barks = () => 'woof';
dog.eats(); // 'yum' — tìm không thấy ở dog → leo lên animal
Object.getPrototypeOf(dog) === animal; // true
```
`instanceof` = đi ngược prototype chain tìm `prototype` của constructor. Vì vậy `class` kế thừa = nối chain, không copy method.

## 1.4 Scope & hoisting / TDZ

```js
console.log(x); // undefined — var được "hoist" nhưng chưa gán
var x = 1;
console.log(y); // ReferenceError — TDZ: let tồn tại trong scope nhưng chưa tới dòng khai báo
let y = 2;
```
TDZ tồn tại để bắt lỗi dùng biến trước khi khởi tạo — `var` không có bảo vệ đó = nguồn bug kinh điển.

## 1.5 Event loop: microtask thắng macrotask

```js
console.log('1');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
queueMicrotask(() => console.log('microtask'));
console.log('2');
// 1, 2, promise, microtask, timeout
```
Quy tắc: sau mỗi macrotask, drain HẾT microtask queue rồi mới sang việc tiếp. `setTimeout` là macrotask; `.then` là microtask.

## 1.6 Vì sao 1 thread chịu tải lớn

Vì I/O không chiếm thread: Node đăng ký socket với kernel (epoll), kernel báo khi có dữ liệu. Thread rảnh đi làm request khác.

Bug vì hiểu sai:
```js
app.get('/report', (req, res) => {
const data = fs.readFileSync('/big.csv'); // BLOCK cả process ~3s
res.send(data); // mọi request khác chờ 3s
});
// fix: await fs.promises.readFile(...) hoặc stream
```
`process.nextTick` chạy TRƯỚC cả microtask promise — dùng nội bộ Node, app code hiếm khi cần.

## 1.7 Libuv thread pool

`fs`, DNS, crypto (scrypt/bcrypt) KHÔNG chạy trên event loop — chúng mượn thread pool, mặc định 4.

Hệ quả interview: chạy bcrypt (CPU-bound) trên 20 req/s → pool 4 thread nghẽn → fs cũng chậm theo (chung pool). Fix: `UV_THREADPOOL_SIZE=16`, hoặc đẩy hashing sang worker/service riêng.

## 1.8 Async: error propagation & combinators

```js
// await trong loop = tuần tự CÓ Ý THỨC
for (const id of ids) results.push(await getUser(id)); // tổng = sum latency
await Promise.all(ids.map(id => getUser(id))); // tổng = max latency, nhưng 1 fail → fail hết
await Promise.allSettled(ids.map(id => getUser(id))); // không fail, tự xử lý từng result
```
```js
// unhandled rejection: promise không ai bắt
fetchUser(id).catch(handle); // đúng
try { await fetchUser(id); } catch(e) {} // đúng
fetchUser(id); // SAI — crash Node 15+ default
```

## 1.9 GC & memory leak kinh điển

GC mark-sweep: object không còn reference từ root (stack, global) mới bị thu dọn. Leak = bạn còn giữ reference mà không biết.

```js
// leak 1: listener không remove (thường gặp khi component re-render/register lặp)
bus.on('tick', handler); // mỗi lần setup thêm 1 handler
return () => bus.off('tick', handler); // cleanup mới là fixes

// leak 2: closure giữ object lớn
function makeCache() {
const huge = loadBigData(); // 500MB
return () => huge.version; // huge không được GC vì closure giữ
}

// leak 3: Map làm cache không eviction
cache.set(req.url, res); // không bao giờ xoá → fix: Map có TTL / lru-cache
```

## 1.10 JSON giới hạn & structuredClone

```js
JSON.parse(JSON.stringify({ d: new Date(), m: new Map(), fn: () => {} }));
// Date → string "2026-...", Map → {}, fn bị LOẠI BỎ. JSON không có kiểu này.
const cycle = {}; cycle.self = cycle;
JSON.stringify(cycle); // TypeError
structuredClone(cycle); // OK
```

## 1.11 Errors

```js
class DomainError extends Error {
constructor(msg, public code) { super(msg); this.name = 'DomainError'; }
}
try { throw new DomainError('out of stock', 'OUT_OF_STOCK'); }
catch (e) {
if (e instanceof DomainError) return handle(e.code); // lỗi nghiệp vụ: map code
throw e; // lỗi lạ: ném tiếp, đừng swallow
}
finally { release(); } // finally chạy kể cả có return trong try
```
Quy tắc senior: lỗi nghiệp vụ dự đoán được → Result/code; lỗi bất thường → exception + crash-loud + boundary transform ở L8.

**Check cuối tầng:** giải thích được vì sao
```js
for (const x of [1,2,3]) { await fetch(x); } // chậm tổng = 3 lần
```
và khi nào bạn CHỌN giữ tuần tự (rate limit phía server, transaction thứ tự).
137 changes: 137 additions & 0 deletions learn/senior-web-dev/02-typescript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# L2 — TypeScript

## 2.1 Structural typing, `unknown` vs `any` vs `never`

TS so sánh HÌNH DẠNG, không so tên. Object có đủ field thì nhận, không cần cùng "class".

```ts
type Point = { x: number; y: number };
const p: Point = { x: 1, y: 2, z: 3 }; // OK — thừa field khi gán trực tiếp mới bị chặn
declare const q: { x: number; y: number; z: number };
const r: Point = q; // OK — structural: q "to hơn" Point vẫn nhận

any // tắt type-check, lan sang mọi thứ chạm vào → cấm dùng
unknown // giá trị chưa biết: buộc phải narrow trước khi dùng
never // không có giá trị nào: return của function throw, exhaustive check
```
Dùng `never` để ép exhaustive:
```ts
function shapeArea(s: Circle | Square): number {
switch (s.kind) {
case 'circle': return Math.PI * s.r ** 2;
case 'square': return s.side ** 2;
default: { const _: never = s; return _; } // thêm Shape mới → compile error, không lọt
}
}
```

## 2.2 Narrowing & type predicate

```ts
type Result<T> = { ok: true; value: T } | { ok: false; error: string };

function unwrap<T>(r: Result<T>): T {
if (r.ok) return r.value; // sau if: r được narrow còn nhánh ok:true
throw new Error(r.error);
}

function isUser(x: unknown): x is { id: string } { // type predicate
return typeof x === 'object' && x !== null && 'id' in x;
}
```

## 2.3 Generics & conditional types

```ts
function last<T>(arr: T[]): T | undefined { return arr.at(-1); }

type Unwrap<T> = T extends Promise<infer U> ? Unwrap<U> : T; // recursive infer
type A = Unwrap<Promise<Promise<string>>>; // string

type First<T extends readonly unknown[]> = T extends [infer F, ...unknown[]] ? F : never;
```

## 2.4 Utility types — phải tự viết lại được

```ts
type MyPartial<T> = { [K in keyof T]?: T[K] };
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
type MyOmit<T, K extends keyof any> = MyPick<T, Exclude<keyof T, K>>;
type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T;
```
Interview hay hỏi: "Omit và Pick khác gì, viết Pick từ `keyof`".

## 2.5 Mapped & template literal types

```ts
type Keys<T> = keyof T;
type Val<T, K extends keyof T> = T[K]; // indexed access
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
type G = Getters<{ name: string }>; // { getName: () => string }
```

## 2.6 strict flags — từng flag giải thích được

- `strictNullChecks`: `string` không nhận `null`. Bug nó chặn: `user.profile.name` khi profile undefined.
- `noUncheckedIndexedAccess`: `arr[i]` là `T | undefined`. Chặn giả định array đủ dài.
- `exactOptionalPropertyTypes`: `?` nghĩa là "có thể không có key", KHÔNG phải "được gán undefined".
- Bật cả 4 trong tsconfig base của monorepo — org rule, interview thấy bạn bật = điểm.

## 2.7 interface vs type

```ts
interface Cfg { url: string }
interface Cfg { retries?: number } // merge tự động — type không làm được
type Cfg2 = { url: string };
// type Cfg2 = Cfg2 | string // recursive union: type làm được
```
Rule of thumb: public API của package = interface (người khác merge được); nội bộ = type.

## 2.8 ESM vs CJS

```jsonc
// package.json
{ "type": "module" } // .js = ESM; cần CJS thì đặt .cjs
```
```ts
import fs from 'node:fs'; // ESM
const fs = require('fs'); // CJS — không có trong "type":"module"
```
Dual package hazard: lib ship cả 2 bản, 2 instance của cùng class → `instanceof` fail. Senior đọc `exports` map trong package.json lib trước khi dùng.

## 2.9 tsconfig: 3 khóa không nhầm nhau

- `target` / `lib`: code sinh ra ES mấy / có sẵn type API nào (DOM, ES2022)
- `module`: cú pháp import/module nào được preserve
- `moduleResolution`: TÌM file trên đĩa bằng chiến lược nào (`bundler` cho Next, `node16` cho Nest ESM)

Project references: `composite: true` để build từng package độc lập + incremental.

## 2.10 Type erasure: type biến mất lúc chạy

```ts
enum Role { Admin } // runtime sinh object Role — tree-shaking khó, prefer const object
const as = data as User; // KHÔNG kiểm tra gì runtime. `as` không phải validator.
// Input thật từ HTTP: dùng zod/class-validator — org dùng class-validator pipeline (L8.5)
```

## 2.11 Declaration & augmentation

```ts
declare module 'fastify' { interface FastifyRequest { user?: User } } // thêm field vào lib
```

## 2.12 tsc vs transpile-only

SWC/esbuild chỉ XÓA type (nhanh, không đọc type). Type lỗi vẫn chạy được → CI bắt buộc chạy `tsc --noEmit` riêng, không tin "build pass".

## 2.13 Pattern thực chiến

```ts
type Brand<T, B> = T & { __brand: B };
type OrderId = Brand<string, 'OrderId'>;
function getOrder(id: OrderId) {}
getOrder('abc' as OrderId); // chỗ cast duy nhất: ranh giới DB/HTTP
```

**Check:** viết `type Paths<T>` sinh mọi đường `"user.address.city"` cho object lồng nhau, không tra Google.
Loading
Loading