Chuyển tới nội dung chính

1.10 — Mini case study

Tóm tắt

Một bài toán CRM có thật và đủ nhỏ để làm trọn: tính hoa hồng cho nhân viên bán hàng theo bậc doanh số. Giá trị của bài này không nằm ở code — nó chỉ khoảng 30 dòng — mà ở quá trình đi từ đề bài mơ hồ tới code chạy đúng. Đề bài ban đầu có ba chỗ không rõ mà nếu không hỏi lại, bạn sẽ viết ra thứ chạy được nhưng tính sai tiền. Và điều quan trọng nhất về mặt nghề nghiệp: bốn trong năm trường hợp biên không được nhắc trong đề bài — biết tự tìm ra chúng là khác biệt giữa code chạy trên máy mình và code chạy được trên production.

Mục tiêu bài học​

Sau bài này bạn có thể:

  • Phát hiện chỗ mơ hồ trong một đề bài nghiệp vụ.
  • Dùng bảng quyết định để làm rõ quy tắc.
  • Tự tìm trường hợp biên không được nhắc tới.
  • Viết code đọc được cho một quy tắc nhiều nhánh.

Nội dung bài học​

1.10.1 — Đề bài​

"Tính hoa hồng cho nhân viên bán hàng. Dưới 100 triệu thì 2%, từ 100 đến 500 triệu thì 5%, trên 500 triệu thì 8%. Nhân viên thử việc chỉ được một nửa."

Nghe rõ ràng, nhưng có ba chỗ mơ hồ cần hỏi lại trước khi viết dòng code nào:

Chỗ mơ hồHai cách hiểuChênh lệch
"Từ 100 đến 500"Có bao gồm 100 và 500 không?Đúng mốc thì lệch tiền
Doanh số 600 triệu8% của toàn bộ hay 8% chỉ phần vượt 500?Rất lớn
"Một nửa"Nửa của tiền hoa hồng hay nửa của tỷ lệ?Ra cùng kết quả — nhưng phải xác nhận

Chỗ mơ hồ thứ hai là quan trọng nhất. Với doanh số 600 triệu:

Cách 1 (luỹ tiến — phần vượt):  100tr x 2% + 400tr x 5% + 100tr x 8% = 30 triệu
Cách 2 (toàn bộ theo bậc): 600tr x 8% = 48 triệu

Chênh 18 triệu cho một nhân viên. Đoán sai ở đây là sai tiền thật, và lỗi sẽ chỉ bị phát hiện khi có người thắc mắc bảng lương.

Giả sử sau khi hỏi lại, câu trả lời là: luỹ tiến theo phần vượt, mốc tính là >= 100 và > 500, và thử việc giảm một nửa số tiền cuối cùng.

1.10.2 — Bảng quyết định​

Viết quy tắc thành bảng trước khi viết code — nó phơi bày chỗ thiếu rõ hơn văn xuôi:

Doanh số (triệu)Phần áp dụngTỷ lệ
0 – 99,99Toàn bộ2%
100 – 500Phần từ 100 tới 5005%
Trên 500Phần vượt 5008%

Nhân viên thử việc: nhân kết quả cuối với 0,5.

Kiểm chứng bảng bằng ba ví dụ tự tính tay trước khi viết code:

80 trieu   -> 80 x 2%                              = 1,6 trieu
300 trieu -> 100x2% + 200x5% = 12 trieu
600 trieu -> 100x2% + 400x5% + 100x8% = 30 trieu

1.10.3 — Code​

public static decimal CalculateCommission(decimal sales, bool isProbation)
{
if (sales < 0)
throw new ArgumentException("Doanh số không được âm", nameof(sales));

const decimal Tier2Threshold = 100_000_000m;
const decimal Tier3Threshold = 500_000_000m;

decimal commission = 0m;

// Bậc 1: phần từ 0 đến 100 triệu
decimal tier1 = Math.Min(sales, Tier2Threshold);
commission += tier1 * 0.02m;

// Bậc 2: phần từ 100 đến 500 triệu
if (sales > Tier2Threshold)
{
decimal tier2 = Math.Min(sales, Tier3Threshold) - Tier2Threshold;
commission += tier2 * 0.05m;
}

// Bậc 3: phần vượt 500 triệu
if (sales > Tier3Threshold)
{
decimal tier3 = sales - Tier3Threshold;
commission += tier3 * 0.08m;
}

return isProbation ? commission * 0.5m : commission;
}

Bốn quyết định trong code này đáng giải thích:

decimal, không phải double. Đây là tiền. double là số thực nhị phân và không biểu diễn chính xác các giá trị thập phân — 0.1 + 0.2 cho ra 0.30000000000000004. Với tiền, sai số đó tích luỹ thành lệch sổ sách.

Hằng số có tên. MocBac2 đọc được, 100_000_000m rải trong code thì không. Dấu gạch dưới phân tách nhóm chữ số là cú pháp C# hợp lệ và giúp đọc số lớn.

Math.Min thay vì lồng if. So sánh với cách viết lồng nhau:

// Khó đọc hơn, và dễ sai biên
if (sales <= 100_000_000m) { ... }
else if (sales <= 500_000_000m) { ... }
else { ... }

Math.Min diễn đạt thẳng ý "lấy phần nằm trong bậc này", nên ít chỗ để sai hơn.

Kiểm tra đầu vào ngay đầu hàm. Doanh số âm là lỗi lập trình hoặc dữ liệu hỏng — phát hiện sớm tốt hơn là trả về một con số vô nghĩa.

1.10.4 — Năm trường hợp biên​

Đề bài chỉ nhắc một trong năm. Bốn cái còn lại bạn phải tự nghĩ ra — và đây chính là kỹ năng phân biệt người mới với người có kinh nghiệm.

CalculateCommission(0, false)              // 0 — không có doanh số
CalculateCommission(100_000_000m, false) // ĐÚNG mốc — 2 triệu (không phải 5 triệu)
CalculateCommission(500_000_000m, false) // ĐÚNG mốc — 22 triệu
CalculateCommission(-1000, false) // Âm — phải ném ngoại lệ
CalculateCommission(0, true) // Thử việc doanh số 0 — 0, không phải lỗi

Kiểm chứng hai mốc bằng tay:

100.000.000 -> 100tr x 2% + 0 x 5%           = 2.000.000
500.000.000 -> 100tr x 2% + 400tr x 5% = 22.000.000

Quy tắc tổng quát: với mọi hàm có ngưỡng, luôn kiểm tra đúng tại ngưỡng, ngay dưới và ngay trên ngưỡng. Lỗi lệch một đơn vị ở ranh giới là loại bug phổ biến nhất trong code có điều kiện (bài 1.3).

1.10.5 — Kiểm chứng bằng test​

[Theory]
[InlineData(0, false, 0)]
[InlineData(80_000_000, false, 1_600_000)]
[InlineData(100_000_000, false, 2_000_000)] // đúng mốc
[InlineData(300_000_000, false, 12_000_000)]
[InlineData(500_000_000, false, 22_000_000)] // đúng mốc
[InlineData(600_000_000, false, 30_000_000)]
[InlineData(600_000_000, true, 15_000_000)] // thử việc
public void CalculateCommission_ReturnsExpected(decimal sales, bool isProbation, decimal expected)
{
Assert.Equal(expected, CalculateCommission(sales, isProbation));
}

[Fact]
public void CalculateCommission_Throws_WhenSalesNegative()
{
Assert.Throws<ArgumentException>(() => CalculateCommission(-1000, false));
}

Mỗi giá trị mong đợi trong bảng này đã được tính tay trước, không phải lấy từ kết quả chạy chương trình. Đó là điểm mấu chốt: nếu bạn chạy code rồi chép kết quả vào test, test chỉ khẳng định "code làm đúng những gì code đang làm" — nó không kiểm tra được gì.

1.10.6 — Rà lại code của bạn​

Danh sách rà soát bài toán nghiệp vụ

  • •Đã hỏi lại mọi chỗ mơ hồ trước khi viết code.
  • •Quy tắc được viết thành bảng quyết định trước.
  • •Đã tự tính tay vài ví dụ để kiểm chứng cách hiểu.
  • •Dùng decimal cho tiền, không dùng double.
  • •Số có ý nghĩa được đặt tên thành hằng số.
  • •Đầu vào không hợp lệ được kiểm tra ngay đầu hàm.
  • •Đã kiểm tra đúng tại ngưỡng, ngay dưới và ngay trên ngưỡng.
  • •Giá trị mong đợi trong test được tính tay, không chép từ kết quả chạy.

Bài tập áp dụng​

Bài 1 — Đổi quy tắc​

Sửa hàm sang cách hiểu thứ hai (toàn bộ doanh số nhân tỷ lệ của bậc cao nhất) và so sánh kết quả với 600 triệu.

Tiêu chí hoàn thành: bạn có hai hàm cùng chạy được và một bảng so sánh, và chỉ ra được cách hiểu thứ hai tạo ra một vấn đề mà cách hiểu thứ nhất không có.

Gợi ý và lời giải — Bài 1

Gợi ý. So sánh không chỉ ở mốc 600 triệu. Hãy thử ngay dưới và ngay trên mỗi ngưỡng.

Lời giải — cách hiểu thứ hai:

public static decimal TinhHoaHongToanBo(decimal doanhSo, bool thuViec)
{
if (doanhSo < 0)
throw new ArgumentException("Doanh số không được âm", nameof(doanhSo));

decimal tyLe = doanhSo switch
{
< 100_000_000m => 0.02m,
<= 500_000_000m => 0.05m,
_ => 0.08m,
};

var hoaHong = doanhSo * tyLe;
return thuViec ? hoaHong * 0.5m : hoaHong;
}

Bảng so sánh hai cách hiểu:

Doanh sốLuỹ tiến (cách 1)Toàn bộ (cách 2)Chênh lệch
80 triệu1,6 triệu1,6 triệu0
99 triệu1,98 triệu1,98 triệu0
100 triệu2,0 triệu5,0 triệu+3,0 triệu
300 triệu12,0 triệu15,0 triệu+3,0 triệu
500 triệu22,0 triệu25,0 triệu+3,0 triệu
501 triệu22,08 triệu40,08 triệu+18,0 triệu
600 triệu30,0 triệu48,0 triệu+18,0 triệu

Và đây là vấn đề mà cách hiểu thứ hai tạo ra — nhìn hai hàng liền nhau:

Doanh số 500 triệu -> hoa hồng 25,0 triệu
Doanh số 501 triệu -> hoa hồng 40,08 triệu

Bán thêm 1 TRIỆU -> hoa hồng tăng 15,08 TRIỆU
Và chiều ngược lại còn tệ hơn:
99.999.999 đồng -> hoa hồng 1.999.999 đồng
100.000.000 đồng -> hoa hồng 5.000.000 đồng

Chênh 1 ĐỒNG doanh số -> chênh 3 TRIỆU hoa hồng

Đây gọi là "vách đá" ở ngưỡng, và nó gây ra ba hậu quả thật:

1. Nhân viên có động cơ thao túng con số.

Cuối tháng đang ở 498 triệu -> cố ép thêm một đơn nhỏ bằng mọi giá
-> kể cả giảm giá sâu, vì 2 triệu doanh số đổi lấy 15 triệu hoa hồng
-> công ty LỖ vì chính chính sách của mình

2. Nó không công bằng theo cách khó giải thích.

Hai nhân viên: một người 499 triệu, một người 501 triệu
-> chênh 0,4% doanh số
-> chênh 82% hoa hồng

3. Và nó làm mọi tranh chấp về số liệu trở nên gay gắt.

Với cách luỹ tiến: một đơn hàng bị tính nhầm -> lệch vài trăm nghìn
Với cách toàn bộ: cùng đơn đó, nếu nó đẩy qua ngưỡng -> lệch hàng chục triệu

Cách hiểu thứ nhất không có vấn đề này:

499 triệu -> 21,95 triệu
500 triệu -> 22,00 triệu
501 triệu -> 22,08 triệu
Hàm LIÊN TỤC: doanh số tăng một chút thì hoa hồng tăng một chút.
Không có chỗ nào nhảy vọt.

Đây chính là lý do gần như mọi hệ thống thuế và hoa hồng trong thực tế dùng cách luỹ tiến — không phải vì nó dễ tính hơn, mà vì nó không tạo ra vách đá.

Và một mẹo kiểm chứng nhanh, dùng được cho mọi hàm có ngưỡng:

[Theory]
[InlineData(100_000_000)]
[InlineData(500_000_000)]
public void Khong_co_buoc_nhay_tai_nguong(decimal moc)
{
var duoi = TinhHoaHong(moc - 1, false);
var tai = TinhHoaHong(moc, false);
var tren = TinhHoaHong(moc + 1, false);

(tai - duoi).Should().BeLessThan(1_000,
"chênh 1 đồng doanh số không được làm hoa hồng nhảy vọt");
(tren - tai).Should().BeLessThan(1_000);
}

Test này chạy với hàm luỹ tiến thì xanh, với hàm "toàn bộ theo bậc" thì đỏ — và nó biến một tính chất của nghiệp vụ thành một thứ kiểm chứng tự động được.


Bài 2 — Thêm ràng buộc​

Thêm quy tắc "hoa hồng tối đa 100 triệu mỗi tháng" và viết test cho mốc đó.

Tiêu chí hoàn thành: bạn xác định được thứ tự áp dụng giữa trần và hệ số thử việc, và giải thích được vì sao thứ tự đó phải hỏi lại thay vì tự quyết.

Gợi ý và lời giải — Bài 2

Gợi ý. Với nhân viên thử việc, "trần 100 triệu" nghĩa là gì? Trần trước khi chia đôi, hay sau?

Lời giải — hai cách, hai kết quả khác nhau:

// Cách A: trần TRƯỚC, rồi mới chia đôi
public static decimal TinhHoaHongA(decimal doanhSo, bool thuViec)
{
var hoaHong = TinhLuyTien(doanhSo);
hoaHong = Math.Min(hoaHong, TranMoiThang); // trần 100 triệu
return thuViec ? hoaHong * 0.5m : hoaHong; // rồi chia đôi
}

// Cách B: chia đôi TRƯỚC, rồi mới áp trần
public static decimal TinhHoaHongB(decimal doanhSo, bool thuViec)
{
var hoaHong = TinhLuyTien(doanhSo);
if (thuViec) hoaHong *= 0.5m; // chia đôi trước
return Math.Min(hoaHong, TranMoiThang); // rồi trần 100 triệu
}

Với nhân viên chính thức, hai cách giống hệt nhau. Với thử việc thì không:

Doanh sốHoa hồng thôCách A (trần trước)Cách B (chia trước)
1 tỷ62 triệu31 triệu31 triệu
2 tỷ142 triệu50 triệu71 triệu
5 tỷ382 triệu50 triệu100 triệu
Cách A: trần thực tế cho nhân viên thử việc là 50 TRIỆU
Cách B: trần thực tế cho nhân viên thử việc là 100 TRIỆU

Chênh nhau tới 50 triệu cho một người.

Vì sao phải hỏi lại thay vì tự quyết:

Câu chữ "hoa hồng tối đa 100 triệu mỗi tháng" KHÔNG nói rõ áp cho
số nào — số trước hay sau khi giảm cho thử việc.

Cả hai cách đều đọc xuôi tai. Cả hai đều có lập luận hợp lý:
A: "trần là trần của chính sách hoa hồng, áp cho mọi người như nhau"
B: "trần là trần của SỐ TIỀN NHẬN, nên áp lên con số cuối cùng"

Chọn bừa -> 50% khả năng sai -> và sai ở đây là sai tiền lương.

Đây đúng là bài học của mục 1.10.1: chỗ mơ hồ phải hỏi trước khi viết dòng code nào. Bài tập này chỉ thêm một chỗ mơ hồ nữa vào cùng một yêu cầu.

Giả sử câu trả lời là cách A. Test cho mốc đó:

public class HoaHongTests
{
private const decimal Tran = 100_000_000m;

[Fact]
public void Chinh_thuc_khong_vuot_tran()
=> TinhHoaHong(5_000_000_000m, thuViec: false).Should().Be(Tran);

[Fact]
public void Thu_viec_bi_tran_TRUOC_khi_chia_doi()
=> TinhHoaHong(5_000_000_000m, thuViec: true).Should().Be(Tran * 0.5m);

[Theory]
[InlineData(1_000_000_000)] // hoa hồng 62 triệu — chưa chạm trần
[InlineData(1_800_000_000)] // hoa hồng 126 triệu — vượt trần
public void Khong_bao_gio_vuot_tran(decimal doanhSo)
{
TinhHoaHong(doanhSo, false).Should().BeLessThanOrEqualTo(Tran);
TinhHoaHong(doanhSo, true).Should().BeLessThanOrEqualTo(Tran);
}

[Fact]
public void Tim_dung_diem_cham_tran()
{
// Doanh số nào làm hoa hồng đúng bằng 100 triệu?
// 100tr×2% + 400tr×5% + x×8% = 100tr -> x = 975 triệu
// => doanh số = 500 + 975 = 1.475 triệu
TinhHoaHong(1_475_000_000m, false).Should().Be(Tran);
TinhHoaHong(1_474_000_000m, false).Should().BeLessThan(Tran);
TinhHoaHong(1_476_000_000m, false).Should().Be(Tran);
}
}

Test cuối là test đáng giá nhất, vì nó buộc bạn tính ra điểm chạm trần:

Không tính ra được điểm đó -> bạn chưa thật sự hiểu hàm mình viết
Tính ra được -> bạn có một mốc kiểm tra chính xác, không phải "một số lớn"

Và nó bắt được một lỗi tinh vi: nếu ai đó đổi tỷ lệ bậc 3 từ 8% sang 10%, điểm chạm trần dịch từ 1.475 sang 1.280 triệu — test 1_474 sẽ đỏ, buộc người sửa phải nhìn lại con số.

Và một câu hỏi nữa mà ràng buộc mới làm lộ ra:

"Tối đa 100 triệu MỖI THÁNG"

-> hàm hiện tại không biết gì về tháng. Nó nhận một con số doanh số.
-> nếu một nhân viên có nhiều lần tính trong tháng thì sao?
-> trần áp cho từng lần, hay cho tổng cả tháng?

Câu này không trả lời được bằng cách sửa hàm — nó đòi hỏi biết hàm được gọi như thế nào. Và đó là một dấu hiệu tốt: một ràng buộc mới thường kéo theo một câu hỏi về ngữ cảnh rộng hơn, không chỉ về công thức.


Bài 3 — Tìm chỗ mơ hồ​

Lấy một yêu cầu nghiệp vụ thật trong công việc của bạn và liệt kê mọi chỗ có thể hiểu theo hai cách.

Tiêu chí hoàn thành: bạn có danh sách kèm hậu quả bằng số cho mỗi chỗ mơ hồ, và xếp hạng chúng theo mức độ nghiêm trọng.

Gợi ý và lời giải — Bài 3

Gợi ý. Với mỗi chỗ mơ hồ, hãy tính ra hai kết quả bằng một ví dụ cụ thể. Con số chênh lệch cho biết nó có đáng hỏi không.

Lời giải — sáu loại từ ngữ hay gây mơ hồ:

LoạiVí dụHai cách hiểu
Khoảng"từ 100 đến 500"Có bao gồm hai đầu không?
Thời gian"trong 30 ngày"30 ngày làm việc hay 30 ngày lịch? Tính từ lúc nào?
Phủ định"khách chưa mua hàng"Chưa bao giờ, hay chưa mua trong kỳ này?
Liên từ"khách VIP và ở Hà Nội"Cả hai điều kiện, hay một trong hai?
Mặc định"nếu không chọn thì lấy mặc định"Mặc định là gì, và ai đặt?
Làm tròn"chia đều cho 3 người"Phần lẻ về ai?

Ví dụ áp dụng cho một yêu cầu thật:

"Gửi email nhắc cho khách hàng chưa phản hồi sau 3 ngày. Không gửi vào cuối tuần. Mỗi khách tối đa 3 lần nhắc."

## Chỗ mơ hồ — tính năng nhắc khách hàng

| # | Chỗ mơ hồ | Cách 1 | Cách 2 | Hậu quả nếu chọn sai |
|---|---|---|---|---|
| 1 | "sau 3 ngày" tính từ đâu? | Từ lúc tạo lead | Từ lần liên hệ cuối | Lệch vài ngày, khách nhận nhắc quá sớm hoặc quá muộn |
| 2 | "3 ngày" là ngày gì? | Ngày lịch | Ngày làm việc | Lead tạo thứ năm: nhắc chủ nhật hay thứ ba — lệch 2 ngày |
| 3 | "chưa phản hồi" nghĩa là gì? | Không có email trả lời | Không có bất kỳ hoạt động nào | Khách đã gọi điện vẫn bị nhắc — gây khó chịu |
| 4 | "không gửi cuối tuần" | Hoãn sang thứ hai | Bỏ qua lượt đó | Bỏ qua = khách không bao giờ được nhắc nếu rơi vào T7 |
| 5 | "tối đa 3 lần" tính theo gì? | Mỗi lead 3 lần | Mỗi khách 3 lần | Khách có 5 lead: 15 email hay 3 email |
| 6 | Múi giờ nào? | Giờ máy chủ (UTC) | Giờ Việt Nam | Email gửi lúc 7 giờ sáng hay 2 giờ chiều |

### Xếp hạng theo mức độ nghiêm trọng

**Nghiêm trọng — hỏi trước khi viết code:**
- #5: chênh **5 lần** số email. Khách có 5 lead nhận 15 email trong một tuần
-> nhiều khả năng bị báo cáo là spam, ảnh hưởng uy tín tên miền gửi
- #3: gửi nhắc cho khách vừa gọi điện hôm qua -> khách khó chịu, và
nhân viên bán hàng mất uy tín với khách của mình

**Trung bình — nêu giả định, xác nhận sau:**
- #2: lệch tối đa 2 ngày
- #4: ảnh hưởng khoảng 2/7 số lead

**Nhẹ — chọn mặc định hợp lý và ghi lại:**
- #1: giả định "từ lần liên hệ cuối"
- #6: giả định giờ Việt Nam, gửi trong giờ hành chính

Ba điều làm danh sách này hữu ích, thay vì thành một danh sách dài vô ích:

1. Mỗi chỗ mơ hồ có HẬU QUẢ BẰNG SỐ, không chỉ mô tả.

Kém: "#5 có thể hiểu theo hai cách"
Tốt: "#5 chênh 5 lần số email — khách 5 lead nhận 15 thay vì 3"

Con số quyết định người đọc có coi trọng câu hỏi hay không.

2. Có xếp hạng, nên người trả lời biết phải ưu tiên gì.

Gửi 6 câu hỏi ngang hàng -> người nhận trả lời qua loa, hoặc không trả lời
Gửi 2 câu "chặn" + 4 câu "đã giả định" -> dễ trả lời, và thường được trả lời ngay

3. Chỗ nhẹ được ghi là GIẢ ĐỊNH, không phải câu hỏi.

"Tôi giả định tính từ lần liên hệ cuối, giờ Việt Nam. Nếu khác, cho tôi biết."

-> không chặn việc làm
-> nhưng vẫn ghi lại, nên sáu tháng sau không ai phải đoán vì sao code như vậy

Cách kiểm chứng rẻ nhất: viết ba ví dụ cụ thể và hỏi "kết quả đúng là gì?"

Thay vì hỏi: "3 ngày là ngày làm việc hay ngày lịch?"

Hãy hỏi: "Lead tạo lúc 16:00 thứ Năm, khách không phản hồi.
Email nhắc gửi lúc nào?
a) Chủ nhật 16:00
b) Thứ Hai 09:00
c) Thứ Ba 09:00"

Người không quen nói chuyện kỹ thuật trả lời câu hỏi thứ hai dễ hơn nhiều — và câu trả lời của họ xác định luôn cả #2 lẫn #4 trong một lần.

Và một dấu hiệu cho biết bạn đã liệt kê đủ:

Viết được bộ test từ danh sách đó mà KHÔNG phải đoán thêm điều gì
-> danh sách đã đủ

Còn phải đoán -> vẫn còn chỗ mơ hồ chưa liệt kê

Đây là cùng một ý tưởng với bảng quyết định ở mục 1.10.2: viết quy tắc ra dạng có cấu trúc trước khi viết code, vì cấu trúc phơi bày chỗ thiếu rõ hơn văn xuôi.

Tự kiểm tra​

Câu hỏi thường gặp

Vì sao phải hỏi lại trước khi viết code?

Vì cùng một đề bài có thể hiểu theo nhiều cách cho ra kết quả rất khác nhau. Trong ví dụ này, hai cách hiểu về bậc luỹ tiến chênh nhau mười tám triệu cho một nhân viên, và lỗi chỉ bị phát hiện khi có người thắc mắc bảng lương.

Bảng quyết định giúp gì so với đọc đề bài?

Nó buộc bạn liệt kê đủ mọi khoảng giá trị và phơi bày chỗ thiếu. Một đoạn văn xuôi có thể bỏ sót ranh giới mà không ai nhận ra, còn bảng thì lộ ngay.

Vì sao dùng decimal cho tiền chứ không dùng double?

Vì double là số thực nhị phân, không biểu diễn chính xác giá trị thập phân, nên các phép cộng tích luỹ sai số. Với tiền, sai số đó dẫn tới lệch sổ sách.

Vì sao Math.Min dễ đọc hơn if lồng nhau?

Vì nó diễn đạt thẳng ý lấy phần nằm trong bậc này, thay vì bắt người đọc theo dõi nhiều nhánh điều kiện. Ít nhánh hơn cũng nghĩa là ít chỗ để sai hơn.

Quy tắc kiểm tra ngưỡng là gì?

Với mọi hàm có ngưỡng, luôn kiểm tra đúng tại ngưỡng, ngay dưới và ngay trên ngưỡng. Lỗi lệch một đơn vị ở ranh giới là loại bug phổ biến nhất trong code có điều kiện.

Vì sao không nên chép kết quả chạy vào test?

Vì khi đó test chỉ khẳng định code làm đúng những gì code đang làm, kể cả khi điều đó sai. Giá trị mong đợi phải được tính tay từ đề bài, độc lập với cài đặt.

Kết luận​

Ba điều đáng nhớ nhất:

  1. Đề bài mơ hồ là chuyện bình thường. Hỏi lại rẻ hơn tính sai tiền rất nhiều.
  2. Bảng quyết định trước, code sau. Bảng phơi bày chỗ thiếu mà văn xuôi giấu đi.
  3. Bốn trong năm trường hợp biên không có trong đề bài. Tự tìm ra chúng là kỹ năng cốt lõi.

Tham khảo​

Điều hướng​