Giao diện
↑ R1 · ← Types & Missingness · → Environments & Scoping
🎯 Mục tiêu
Mục tiêu của bài này không phải “thuộc cú pháp if/for”, mà là viết code R có thể kiểm thử, tái sử dụng, và debug nhanh. Trong production, đó là thứ quyết định bạn có ngủ ngon hay không.
1) Control flow: dùng đúng chỗ
1.1 if/else: điều kiện phải là TRUE/FALSE thật sự
if chỉ nhận 1 giá trị logical. NA sẽ crash, vector logical sẽ bị lấy phần tử đầu (dễ bug).
r
flag <- NA
if (flag) "ok" # Error
x <- c(TRUE, FALSE)
if (x) "ok" # Warning + dùng x[1]Guardrails thực dụng:
r
if (isTRUE(flag)) {
"ok"
} else {
"not ok"
}1.2 for: tốt cho orchestration, không phải để “tính toán thay vectorization”
for hợp cho:
- gọi API theo batch,
- đọc nhiều file,
- orchestrate pipeline,
- log progress.
Không hợp để làm toán element-wise khi bạn có thể dùng vectorization. Lý do: code dài, dễ state leak, khó test.
1.3 while: chỉ dùng khi điều kiện dừng rõ và có “safety cap”
while dễ tạo infinite loop. Trong production, luôn có giới hạn:
r
max_iters <- 1000L
i <- 0L
while (i < max_iters) {
i <- i + 1L
}
stopifnot(i == max_iters)⚠️ Cạm bẫy
Đừng viết while(TRUE) nếu không có timeout/backoff/giới hạn số lần retry.
2) Vectorization vs loops: tiêu chí chọn
Vectorization không phải “thần chú”, nó là trade-off.
2.1 Khi nên vectorize
- Transform theo cột (per-element computation) → vectorize.
- Filter/mutate/summarise dạng bảng → ưu tiên style “pipeline”.
2.2 Khi nên loop
- Mỗi iteration là một “unit of work” có I/O (API, file, DB).
- Cần early stop/break rõ ràng theo điều kiện.
- Cần retry/backoff.
Rule of thumb: logic thuần (pure) → vectorize; orchestration/I/O → loop.
3) Functions: thiết kế để “pure-ish” và test được
“Pure-ish” nghĩa là:
- function nhận input rõ ràng (args),
- trả output rõ ràng (return),
- side-effects (đọc/ghi file, set seed, set options) đặt ở entrypoint.
3.1 Input validation: fail fast
Validation ở đầu function giúp giảm bug lan truyền.
r
assert_has_cols <- function(df, required_cols) {
stopifnot(is.data.frame(df))
stopifnot(is.character(required_cols))
missing_cols <- setdiff(required_cols, names(df))
if (length(missing_cols) > 0) {
stop(paste("Missing columns:", paste(missing_cols, collapse = ", ")))
}
invisible(TRUE)
}3.2 Return contract: nói rõ kiểu và shape
Return contract tối thiểu:
- trả về kiểu gì (vector/list/data.frame)?
- nếu data.frame: có cột nào, có bao nhiêu dòng (có thể thay đổi không)?
- có thể trả length 0 không?
Ví dụ function trả về data.frame đã chuẩn hóa:
r
normalize_events <- function(events_df) {
assert_has_cols(events_df, c("user_id", "event_time", "event_type"))
events_df[order(events_df$user_id, events_df$event_time), , drop = FALSE]
}3.3 Side-effects: phân tầng rõ
Tách code thành 3 lớp:
- Pure logic:
R/(functions) - Orchestrator:
scripts/(I/O, args, logging, seed) - Presentation:
reports/(render/plots, ít logic)
4) Mẫu “ship-ready”: script gọi functions
Mẫu entrypoint tối thiểu (tư duy, không bắt buộc cấu trúc thư mục):
r
args <- commandArgs(trailingOnly = TRUE)
input_path <- args[[1]]
output_path <- args[[2]]
events_df <- read.csv(input_path)
events_df <- normalize_events(events_df)
write.csv(events_df, output_path, row.names = FALSE)Điểm quan trọng:
- Orchestrator xử lý đường dẫn, I/O.
- Logic nằm trong function để test.
5) Checklist: để code “test được”
✅ Checklist triển khai
Khi viết function
- Input explicit, không đọc global state
- Validate args, fail fast
- Return contract rõ (kiểu + shape)
- Không đổi
options()/timezone/locale bên trong (hoặc đổi thì reset)
Khi viết loop/orchestration
- Có max retries/timeout
- Log context (file/batch id)
- Dễ resume (idempotent output nếu có thể)