Khắc phục sự cố trong Aiken
Bài học này hướng dẫn cách debug và xử lý các lỗi thường gặp trong Aiken.
Mục tiêu học tập
- Đọc hiểu error messages
- Debug với trace
- Xử lý lỗi compilation phổ biến
- Best practices để tránh bugs
Các loại lỗi
┌─────────────────────────────────────────────────────────────┐
│ ERROR TYPES │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Compilation │ → Lỗi cú pháp, type mismatch │
│ └─────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ Test Fail │ → Logic sai, assertion fail │
│ └─────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ Runtime │ → expect fail, division by zero │
│ └─────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ On-chain │ → Script execution fail │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Lỗi Compilation
Type Mismatch
// ❌ Lỗi: Type mismatch
fn add_wrong(a: Int, b: ByteArray) -> Int {
a + b // Error: Cannot add Int and ByteArray
}
Error message:
Error: Type mismatch
┌── lib/main.ak:3:3
│
3 │ a + b
│ ^^^^^ expected `Int`, got `ByteArray`
Fix:
// ✅ Đúng
fn add_correct(a: Int, b: Int) -> Int {
a + b
}
Missing Pattern
// ❌ Lỗi: Non-exhaustive pattern match
type Status {
Active
Pending
Completed
}
fn describe(status: Status) -> ByteArray {
when status is {
Active -> "Active"
Pending -> "Pending"
// Missing: Completed
}
}
Error message:
Error: Non-exhaustive pattern match
┌── lib/main.ak:8:3
│
8 │ when status is {
│ ^^^^^^^^^^^^^^^ missing pattern: Completed
Fix:
// ✅ Đúng
fn describe(status: Status) -> ByteArray {
when status is {
Active -> "Active"
Pending -> "Pending"
Completed -> "Completed"
}
}
Unknown Variable
// ❌ Lỗi
fn calculate() -> Int {
let x = 10
x + y // Error: Unknown variable 'y'
}
Fix:
// ✅ Đúng
fn calculate() -> Int {
let x = 10
let y = 20
x + y
}
Import Error
// ❌ Lỗi: Module not found
use nonexistent/module
Fix:
- Kiểm tra đường dẫn file
- Đảm bảo module tồn tại
- Kiểm tra dependencies trong aiken.toml
Lỗi Test
Assertion Failed
test test_fails() {
1 + 1 == 3 // False -> Test FAIL
}
Output:
┍━ main ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
│ FAIL [mem: 1234, cpu: 5678] test_fails
│
│ ↪ 1 + 1 == 3
│ │
│ └─ expected: True
│ got: False
Debug với trace
test test_with_debug() {
let a = 10
trace @"a = 10"
let b = 20
trace @"b = 20"
let sum = a + b
trace @"sum calculated"
// Debug intermediate values
sum == 30
}
Chạy với trace:
aiken check --trace-level verbose
Trace operator (?)
test test_trace_on_fail() {
let a = 10
let b = 20
// ? sẽ trace khi expression là False
(a > 0)? // Sẽ trace nếu a <= 0
(b > 0)? // Sẽ trace nếu b <= 0
(a + b == 30)? // Sẽ trace nếu sum != 30
}
Lỗi Runtime
Expect Failure
fn unsafe_unwrap(opt: Option<Int>) -> Int {
expect Some(value) = opt // Fail nếu None!
value
}
test test_expect_fail() fail {
unsafe_unwrap(None) // Runtime error
True
}
Fix với if/is:
fn safe_unwrap(opt: Option<Int>) -> Int {
if opt is Some(value) {
value
} else {
0 // Default value
}
}
Division by Zero
fn divide(a: Int, b: Int) -> Int {
a / b // Fail nếu b == 0!
}
Fix:
fn safe_divide(a: Int, b: Int) -> Option<Int> {
if b == 0 {
None
} else {
Some(a / b)
}
}
Debug Strategies
1. Isolate the Problem
// Chia nhỏ logic để debug
fn complex_validation(data: Data) -> Bool {
let step1 = validate_structure(data)
trace @"Step 1 done"
let step2 = validate_values(data)
trace @"Step 2 done"
let step3 = validate_signatures(data)
trace @"Step 3 done"
step1 && step2 && step3
}
2. Print Intermediate Values
use aiken/cbor
fn debug_value(label: String, value: a) -> a {
trace label
trace cbor.diagnostic(value)
value
}
fn calculate() -> Int {
let a = debug_value(@"a", 10)
let b = debug_value(@"b", 20)
let sum = debug_value(@"sum", a + b)
sum
}
3. Test Edge Cases
// Test boundary conditions
test test_empty_list() {
process_list([]) == 0
}
test test_single_element() {
process_list([1]) == 1
}
test test_negative() {
process_list([-1, -2, -3]) == -6
}
test test_large_numbers() {
process_list([1_000_000_000, 2_000_000_000]) == 3_000_000_000
}
4. Property-Based Testing
use aiken/fuzz
// Tìm edge cases tự động
test prop_reverse_length(xs: List<Int> via fuzz.list(fuzz.int())) {
list.length(reverse(xs)) == list.length(xs)
}
Error Handling Patterns
Result Type
type Result<a, e> {
Ok(a)
Err(e)
}
fn safe_operation(input: Int) -> Result<Int, ByteArray> {
if input < 0 {
Err("Negative input not allowed")
} else {
Ok(input * 2)
}
}
fn use_result() {
when safe_operation(-5) is {
Ok(value) -> value
Err(msg) -> {
trace msg
0
}
}
}
Validation Chain
fn validate_all(data: Data) -> Result<Data, ByteArray> {
validate_step1(data)
|> and_then(validate_step2)
|> and_then(validate_step3)
}
fn and_then(result: Result<a, e>, f: fn(a) -> Result<b, e>) -> Result<b, e> {
when result is {
Ok(value) -> f(value)
Err(e) -> Err(e)
}
}
Hoàn thành Part 1
Chúc mừng! Bạn đã hoàn thành Part 1: The Aiken Foundation. Bạn đã học:
- Cài đặt và sử dụng Aiken CLI
- Cấu trúc dự án và modules
- Biến, hằng số và các kiểu dữ liệu
- Control flow và functions
- Data serialization
- Unit testing và troubleshooting
Tiếp theo, chúng ta sẽ chuyển sang Part 2: Cardano Architecture để hiểu kiến trúc blockchain Cardano.