Tư duy lập trình — input, xử lý, output
Mô hình input-processing-output, cách phân rã bài toán thành bước nhỏ, và 3 construct cơ bản: sequence, selection, iteration.
TL;DR: Mọi chương trình — dù đơn giản hay phức tạp — đều theo đúng một khung: nhận input, biến đổi qua processing, rồi tạo ra output. Đặt 3 câu hỏi "Input là gì? Processing làm gì? Output đi đâu?" trước khi viết một dòng code sẽ cho bạn 80% bản thiết kế. Bài toán lớn cũng chỉ là nhiều bước I-P-O nhỏ xếp lại. Mọi thuật toán được xây từ đúng 3 construct: sequence (tuần tự), selection (rẽ nhánh), iteration (lặp) — đây là định lý Böhm-Jacopini (1966). Và thói quen quan trọng nhất: nghĩ edge case trước khi code.
Trước khi học thêm syntax, bạn cần một khung tư duy để tiếp cận mọi bài toán lập trình. Không có khung này, bạn sẽ biết nhiều lệnh Java nhưng không biết bắt đầu từ đâu khi đứng trước một bài toán mới.
Bài này trang bị cho bạn mô hình đơn giản nhất mà lập trình viên giàu kinh nghiệm vẫn dùng mỗi ngày: input → processing → output.
1. Analogy — "Máy xay sinh tố"
Hình dung chiếc máy xay sinh tố:
- Nhét trái cây vào (input) — chuối, dâu, sữa
- Máy xay (processing) — xay nhuyễn, trộn đều
- Đổ ra cốc (output) — cốc sinh tố hoàn chỉnh
Mọi chương trình máy tính đều theo đúng 3 bước này. Dù là app điện thoại, trang web, hay hệ thống ngân hàng — tất cả đều nhận input, xử lý, rồi tạo ra output.
| Máy xay sinh tố | Chương trình (I-P-O) |
|---|---|
| Trái cây/sữa bỏ vào | Input (dữ liệu vào) |
| Lưỡi dao xay | Processing (xử lý/biến đổi) |
| Cốc sinh tố đổ ra | Output (kết quả ra) |
Máy xay sinh tố — input là trái cây, processing là lưỡi dao quay, output là cốc sinh tố. Mọi chương trình bạn viết là một dạng máy xay: nhận nguyên liệu, biến đổi, trả kết quả.
2. Mô hình I-P-O — ba câu hỏi đầu tiên là gì?
Khi đứng trước bất kỳ bài toán nào, hỏi ngay 3 câu:
flowchart LR inp["INPUT<br/>Du lieu dau vao"] proc["PROCESSING<br/>Bien doi du lieu"] out["OUTPUT<br/>Ket qua dau ra"] inp --> proc --> out
Câu 1 — Input là gì?
- User nhập từ bàn phím?
- Đọc từ file?
- Nhận từ API?
- Dữ liệu cứng trong code?
Câu 2 — Processing làm gì?
- Lọc, tìm kiếm?
- Tính toán, biến đổi?
- Sắp xếp, nhóm?
- Kiểm tra điều kiện?
Câu 3 — Output đi đâu?
- In ra console?
- Ghi vào file?
- Trả về cho người gọi?
- Hiển thị trên màn hình?
Trả lời 3 câu này trước khi viết một dòng code — bạn đã có 80% bản thiết kế.
3. Ví dụ 1 — Tính diện tích hình chữ nhật
Bài toán: Nhập chiều dài và chiều rộng, tính và in diện tích.
Phân tích I-P-O:
- Input: 2 số thực (chiều dài, chiều rộng) — người dùng nhập
- Processing: nhân 2 số với nhau
- Output: in kết quả ra console
Trước khi chạy đoạn code dưới, hãy đoán: nếu nhập chiều dài 5.0 và chiều rộng 3.5, output sẽ là gì?
import java.util.Scanner;
public class TinhDienTich {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in); // read input from keyboard
// --- INPUT ---
System.out.print("Nhap chieu dai: ");
double dai = sc.nextDouble(); // read decimal from user
System.out.print("Nhap chieu rong: ");
double rong = sc.nextDouble();
// --- PROCESSING ---
double dienTich = dai * rong; // calculate area
// --- OUTPUT ---
System.out.println("Dien tich: " + dienTich);
sc.close(); // release resources
}
}
Chạy thử:
Nhap chieu dai: 5.0
Nhap chieu rong: 3.5
Dien tich: 17.5
Scanner là class trong java.util — công cụ đọc input từ bàn phím (hoặc file). sc.nextDouble() đọc một số thập phân mà user nhập rồi nhấn Enter.
4. Ví dụ 2 — Chào user bằng tên
Bài toán: Hỏi tên người dùng, in lời chào cá nhân hoá.
Phân tích I-P-O:
- Input: tên (chuỗi) — người dùng nhập
- Processing: ghép chuỗi
"Xin chao, " + ten + "!" - Output: in ra console
Nếu người dùng nhập Nguyen Van An, output sẽ là gì?
import java.util.Scanner;
public class Chao {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// --- INPUT ---
System.out.print("Ban ten gi? ");
String ten = sc.nextLine(); // read entire line -- including spaces
// --- PROCESSING ---
String loiChao = "Xin chao, " + ten + "! Chuc ban hoc tot.";
// --- OUTPUT ---
System.out.println(loiChao);
sc.close();
}
}
Chạy thử:
Ban ten gi? Nguyen Van An
Xin chao, Nguyen Van An! Chuc ban hoc tot.
sc.nextLine() vs sc.nextDouble():
nextDouble()đọc một số, dừng lại trước EnternextLine()đọc cả dòng kể cả khoảng trắng, tiêu thụ cả ký tự Enter
5. Phân rã bài toán lớn
Bài toán thực tế không đơn giản như 2 ví dụ trên. Nhưng dù phức tạp đến đâu, bạn vẫn phân rã thành nhiều I-P-O nhỏ hơn, xử lý từng bước.
Ví dụ: Tính trung bình của danh sách số nguyên.
| Bước | Loại | Mô tả |
|---|---|---|
| Đọc N số từ user | Input | Scanner.nextInt() mỗi lần |
Cộng tất cả vào biến tong | Processing | Lặp và cộng dồn |
Chia tong cho N | Processing | Một phép tính |
| In kết quả | Output | System.out.println |
Nếu người dùng nhập 3 số: 4, 7, 10, output Trung binh sẽ là bao nhiêu? Và nếu không ép kiểu (double), kết quả thay đổi không?
import java.util.Scanner;
public class TinhTrungBinh {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// --- INPUT ---
System.out.print("Nhap so luong phan tu: ");
int n = sc.nextInt();
// --- PROCESSING (part 1) -- read and accumulate ---
int tong = 0;
for (int i = 1; i <= n; i++) {
System.out.print("Nhap phan tu " + i + ": ");
int soNguyen = sc.nextInt();
tong += soNguyen; // tong = tong + soNguyen
}
// --- PROCESSING (part 2) -- compute average ---
double trungBinh = (double) tong / n;
// --- OUTPUT ---
System.out.println("Trung binh: " + trungBinh);
sc.close();
}
}
Chú ý (double) tong / n: ép kiểu tong sang double trước khi chia để tránh integer division (chia lấy phần nguyên). 5 / 2 = 2 (int), nhưng (double)5 / 2 = 2.5.
6. Ba construct cơ bản của lập trình
Mọi thuật toán — dù đơn giản hay phức tạp — đều được xây từ 3 construct và chỉ 3:
Năm 1966, hai nhà khoa học máy tính chứng minh rằng bất kỳ thuật toán nào cũng có thể biểu diễn bằng đúng 3 construct: sequence, selection, iteration. Đây là nền tảng của mọi ngôn ngữ lập trình mệnh lệnh (imperative).
| Construct | Tiếng Việt | Trong Java | Dùng khi |
|---|---|---|---|
| Sequence | Tuần tự | Các câu lệnh từ trên xuống dưới | Luôn luôn — mặc định |
| Selection | Rẽ nhánh | if, else if, else, switch | Khi cần chọn giữa các trường hợp |
| Iteration | Lặp | for, while, do-while | Khi cần làm đi làm lại nhiều lần |
6.1 Sequence — tuần tự
// Lines run top-to-bottom in order
int a = 5; // step 1
int b = 10; // step 2
int c = a + b; // step 3 -- must come after steps 1 and 2
System.out.println(c); // step 4
Mặc định Java chạy tuần tự — dòng trước xong rồi mới đến dòng sau.
6.2 Selection — rẽ nhánh
Với diem = 75, đoạn code dưới sẽ in ra gì?
int diem = 75;
if (diem >= 90) {
System.out.println("Xuat sac");
} else if (diem >= 70) {
System.out.println("Kha"); // runs because 75 >= 70
} else {
System.out.println("Trung binh");
}
Selection cho phép chương trình đưa ra quyết định — chỉ chạy đoạn code phù hợp với điều kiện. Chi tiết sẽ có ở module câu lệnh điều kiện.
6.3 Iteration — lặp
Vòng lặp dưới sẽ in ra bao nhiêu dòng, và dòng cuối là số mấy?
// Print numbers from 1 to 5
for (int i = 1; i <= 5; i++) {
System.out.println(i); // runs 5 times
}
// Output: 1 2 3 4 5 (each number on its own line)
Iteration cho phép lặp đi lặp lại mà không cần viết code nhiều lần. Chi tiết về for, while sẽ có ở module vòng lặp.
7. Tư duy test — nghĩ edge case trước khi code
Một thói quen quan trọng của lập trình viên giỏi: trước khi viết code, nghĩ trước 2-3 input đặc biệt:
| Bài toán | Input bình thường | Edge case cần nghĩ |
|---|---|---|
| Tính diện tích | chiều dài = 5, rộng = 3 | Chiều dài = 0? Số âm? |
| Tính trung bình | 5 số dương | N = 0 (chia cho 0!)? Số âm? |
| Tìm max trong mảng | [3, 1, 4, 1, 5] | Mảng rỗng? Mảng 1 phần tử? |
| Đếm ký tự trong chuỗi | "hello" | Chuỗi rỗng ""? Chuỗi null? |
Vì sao quan trọng? Code chạy đúng với input bình thường là điều hiển nhiên. Code xử lý đúng edge case là thước đo của lập trình viên giỏi. Bug trong production thường đến từ những trường hợp "không ai nghĩ tới".
// Example: compute average with edge case guard
if (n == 0) {
System.out.println("Khong co phan tu nao de tinh trung binh.");
} else {
double trungBinh = (double) tong / n;
System.out.println("Trung binh: " + trungBinh);
}
8. Bài toán đầy đủ — kết hợp I-P-O
Dưới đây là ví dụ kết hợp tất cả: input từ user, xử lý có điều kiện, output rõ ràng.
Với input 85, chương trình in gì? Với input -5, kết quả thay đổi thế nào?
import java.util.Scanner;
public class PhanLoaiDiem {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// --- INPUT ---
System.out.print("Nhap diem (0-100): ");
int diem = sc.nextInt();
// --- PROCESSING + SELECTION ---
String xepLoai;
if (diem < 0 || diem > 100) {
xepLoai = "Diem khong hop le"; // edge case
} else if (diem >= 90) {
xepLoai = "Xuat sac";
} else if (diem >= 70) {
xepLoai = "Kha";
} else if (diem >= 50) {
xepLoai = "Trung binh";
} else {
xepLoai = "Yeu";
}
// --- OUTPUT ---
System.out.println("Xep loai: " + xepLoai);
sc.close();
}
}
Chạy thử:
Nhap diem (0-100): 85
Xep loai: Kha
Nhap diem (0-100): -5
Xep loai: Diem khong hop le
9. Áp dụng tư duy I-P-O
Nên làm:
- Vẽ I-P-O ra giấy trước khi gõ code — 1 phút vẽ tiết kiệm 30 phút debug
- Nghĩ edge case ngay từ đầu — số 0, số âm, chuỗi rỗng, mảng rỗng
- Đặt comment
// INPUT,// PROCESSING,// OUTPUTkhi code phức tạp - Kiểm tra từng phần — test input đúng chưa trước khi lo processing
Tránh:
- Nhảy thẳng vào code khi chưa hiểu output cần là gì
- Viết code dài rồi mới test — bug sẽ khó tìm hơn
- Bỏ qua edge case vì "chắc không ai nhập số âm đâu"
10. Luyện phân tích I-P-O
Hãy tự phân tích I-P-O cho 3 bài toán sau trước khi đọc gợi ý:
Bài 1: Sắp xếp danh sách tên theo thứ tự alphabet.
- Input: ?
- Processing: ?
- Output: ?
Bài 2: Tìm giá trị lớn nhất trong mảng số nguyên.
- Input: ?
- Processing: ?
- Output: ?
Bài 3: Đếm số lần ký tự 'a' xuất hiện trong một chuỗi.
- Input: ?
- Processing: ?
- Output: ?
Bài 1: Input = danh sách N tên (String), Processing = thuật toán sắp xếp (so sánh, đổi chỗ), Output = danh sách đã sắp xếp.
Bài 2: Input = mảng số nguyên, Processing = duyệt từng phần tử, giữ lại số lớn nhất tìm được, Output = 1 số nguyên.
Bài 3: Input = 1 chuỗi String, Processing = duyệt từng ký tự, đếm số ký tự bằng 'a', Output = 1 số nguyên (bộ đếm).
11. Deep Dive Oracle
Tài liệu tham khảo:
- java.util.Scanner — Java 21 API Docs — tài liệu đầy đủ Scanner: các method
nextInt,nextDouble,nextLine,hasNext... - Structured Program Theorem (Wikipedia) — bối cảnh học thuật của Böhm-Jacopini (1966): chứng minh 3 construct đủ để biểu diễn mọi thuật toán
- JLS §14 — Blocks and Statements — spec về các loại statement trong Java: if, for, while, switch...
Ghi chú thực tế: Scanner là class hay gặp khi mới học nhưng không dùng nhiều trong production (thường đọc input qua HTTP request, file, database thay vì bàn phím). Tuy nhiên, hiểu Scanner giúp bạn nắm khái niệm stream input/output — nền tảng để sau này làm việc với InputStream, BufferedReader, và Files.readAllLines.
12. Liên hệ các bài khác
- Hello World — bài trước viết chương trình in ra màn hình lần đầu; bài này mở rộng sang đọc input từ user với
Scannervà xây I-P-O đầy đủ. - Compile & Run — hiểu javac biên dịch code thành bytecode và JVM thực thi; giúp bạn hiểu vì sao
Scanner.nextLine()chờ input từstdinvà khi nào chương trình kết thúc. - Mini-challenge: in lịch tháng — bài tập áp dụng I-P-O tự lực: phân tích bài toán lịch tháng thành các bước sequence + iteration trước khi code.
13. Tóm tắt
- Mọi chương trình = Input → Processing → Output. Hỏi 3 câu này trước khi code.
Scannerđọc input từ bàn phím:nextInt(),nextDouble(),nextLine().- Bài toán lớn = nhiều bước I-P-O nhỏ xếp lại — phân rã từng phần.
- 3 construct cơ bản: sequence (tuần tự), selection (rẽ nhánh), iteration (lặp) — đủ để xây mọi thuật toán.
- Tư duy test: nghĩ edge case (số 0, số âm, chuỗi rỗng) trước khi viết code — thói quen của dev giỏi.
14. Tự kiểm tra
Q1Phân tích I-P-O cho bài toán: "Kiểm tra một số có phải số chẵn không". Input là gì, processing làm gì, output là gì?▸
n (từ bàn phím / tham số). Processing: tính n % 2; so sánh với 0. Output: true/false hoặc chuỗi "chẵn"/"lẻ". Lưu ý edge case: số âm vẫn hoạt động (-4 % 2 == 0), số 0 cũng được coi là chẵn theo toán học.Q2Vì sao cần (double) tong / n thay vì chỉ tong / n? Điều gì xảy ra nếu không ép kiểu?▸
(double) tong / n thay vì chỉ tong / n? Điều gì xảy ra nếu không ép kiểu?Nếu cả tong và n đều int, / là integer division: phần thập phân bị cắt. 7 / 2 == 3, không phải 3.5.
Ép kiểu (double) tong chuyển tong thành double trước, rồi JLS §5.6 tự promote n lên double → phép chia là double division → giữ phần thập phân.
Q3sc.nextLine() khác sc.nextInt() ở điểm nào? Khi nào dùng cái nào?▸
sc.nextLine() khác sc.nextInt() ở điểm nào? Khi nào dùng cái nào?nextInt() đọc 1 token số nguyên, dừng ở khoảng trắng và để lại ký tự
trong buffer. nextLine() đọc toàn bộ đến hết dòng (bao gồm
). Bẫy kinh điển: gọi nextInt() rồi nextLine() — nextLine() chỉ đọc được
còn sót, trả chuỗi rỗng. Fix: thêm sc.nextLine() "ăn" bỏ
giữa hai lần đọc, hoặc dùng nextLine() rồi Integer.parseInt.Q43 construct sequence/selection/iteration — cái nào mặc định luôn có? Cái nào chỉ xuất hiện khi cần?▸
if/switch) và iteration (for/while) chỉ xuất hiện khi bài toán yêu cầu rẽ nhánh hoặc lặp. Định lý Böhm-Jacopini (1966) chứng minh 3 construct này đủ để biểu diễn mọi thuật toán.Q5Bạn viết chương trình tính căn bậc hai. Edge case nào cần xử lý trước khi tính?▸
- Số âm → không có căn bậc hai thực;
Math.sqrt(-4)trảNaN. Cần validate hoặc dùngComplexnếu cho phép số phức. - Số 0 → kết quả 0, nhưng nhớ rằng 0 là biên.
- Input là
NaN/Infinitynếu đọc từ user (quaDouble.parseDouble). - Số rất lớn →
Math.sqrtvẫn ổn với double, nhưng nếu input là chuỗi quá lớn có thể overflow khi parse. - Input là ký tự / chuỗi rỗng →
NumberFormatException.
Bài tiếp theo: Compile & Run — javac, bytecode, vòng đời chương trình
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên