Assertions
Understand how to use assertions for debugging and validation in Dart.
What is it?
Assertions are a debugging feature that allows you to validate assumptions about your code. They check if a condition is true, and if not, they throw an AssertionError. Assertions are only active in debug mode and are disabled in production builds.
Why does it exist?
Assertions exist to:
- Validate assumptions during development
- Catch bugs early
- Document expected conditions
- Provide safety checks in debug mode
- Enable defensive programming
- Help with debugging
Basic Assertions
Using assert()
The
assert()function checks a condition and throws anAssertionErrorif the condition is false.
void main() {
// Basic assertion
int age = 25;
assert(age > 0); // Passes - no output
// Assert with message
int temperature = -5;
assert(temperature >= 0, 'Temperature cannot be negative');
// Assert with computation
var list = [1, 2, 3];
assert(list.isNotEmpty);
print('Program continues');
}
// Output:
// Uncaught Error: Assertion failed: "Temperature cannot be negative"
// (in debug mode only)
What's happening here? -
assert()checks a boolean condition - If false, it throwsAssertionError- Optional message provides context - Assertions are active only in debug mode - In production, assertions are ignored
Assert with Custom Message
You can provide a custom message to help debug assertion failures.
void processUser(String name, int age, String email) {
// Validate with messages
assert(name.isNotEmpty, 'Name cannot be empty');
assert(age >= 0 && age <= 150, 'Age must be between 0 and 150');
assert(email.contains('@'), 'Email must contain @');
assert(email.contains('.'), 'Email must contain .');
print('User processed: $name, $age, $email');
}
void main() {
try {
processUser('', -5, 'invalid');
} catch (e) {
print('Caught: $e');
}
}
// Output:
// Caught: Assertion failed: "Name cannot be empty"
Assertion Use Cases
Validating Inputs
Assertions are useful for validating function inputs during development.
class Calculator {
int divide(int a, int b) {
// Assert can check conditions
assert(b != 0, 'Cannot divide by zero');
// Multiple assertions
assert(a >= 0, 'Dividend must be positive');
assert(b > 0, 'Divisor must be positive');
return a ~/ b;
}
double sqrt(double value) {
assert(value >= 0, 'Cannot sqrt negative number');
return math.sqrt(value);
}
int factorial(int n) {
assert(n >= 0, 'Cannot factorial negative number');
assert(n <= 20, 'Value too large for factorial');
if (n <= 1) return 1;
return n * factorial(n - 1);
}
}
void main() {
var calc = Calculator();
try {
calc.divide(10, 0);
} catch (e) {
print('Error: $e');
}
try {
calc.factorial(-5);
} catch (e) {
print('Error: $e');
}
}
// Output:
// Error: Assertion failed: Cannot divide by zero
// Error: Assertion failed: Cannot factorial negative number
Key insights: - Assertions validate function inputs - They catch programming errors early - Multiple assertions can be used - Clear messages help debugging
Documenting Assumptions
Assertions document assumptions and invariants in your code.
class Date {
final int year;
final int month;
final int day;
Date(this.year, this.month, this.day) {
// Documenting assumptions
assert(year > 0, 'Year must be positive');
assert(month >= 1 && month <= 12, 'Month must be between 1 and 12');
assert(day >= 1 && day <= 31, 'Day must be between 1 and 31');
}
bool isLeapYear() {
// Documenting logic assumptions
assert(year > 0, 'Year must be valid');
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
int daysInMonth() {
assert(month >= 1 && month <= 12, 'Month must be valid');
switch (month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
return 31;
case 4: case 6: case 9: case 11:
return 30;
case 2:
return isLeapYear() ? 29 : 28;
default:
// This should never happen
assert(false, 'Invalid month in daysInMonth');
return 0;
}
}
}
Assertions vs Exceptions
When to Use What
Assertions are for bugs, exceptions are for runtime conditions.
class BankAccount {
double _balance = 0;
// Assertion - checks programming error
void deposit(double amount) {
assert(amount >= 0, 'Amount must be non-negative');
_balance += amount;
}
// Exception - handles runtime condition
void withdraw(double amount) {
if (amount <= 0) {
throw ArgumentError('Amount must be positive');
}
if (amount > _balance) {
throw Exception('Insufficient funds');
}
_balance -= amount;
}
}
void main() {
var account = BankAccount();
// Assertion failure (bug)
// assert(account._balance >= 0, 'Balance should never be negative');
// Exception (runtime condition)
try {
account.withdraw(100);
} catch (e) {
print('Caught: $e');
}
}
Key insights: - Assertions find bugs during development - Exceptions handle runtime conditions - Assertions are removed in production - Exceptions remain in production
Assertions in Production
Why Assertions are Disabled
Assertions are disabled in production to improve performance.
// In debug mode
void debugExample() {
assert(1 == 2, 'This assertion fails in debug');
print('Debug mode: assertion would have failed');
}
// Production behavior
void productionExample() {
// Assertions are removed
// Equivalent to:
// (1 == 2) // No effect
// print('Production: assertions are ignored');
print('Production: assertions are ignored');
}
void main() {
debugExample();
productionExample();
}
// Debug output:
// Uncaught Error: Assertion failed: This assertion fails in debug
//
// Production output (if assertions disabled):
// Production: assertions are ignored
Real-World Example
Validation Pipeline
Using assertions in a validation pipeline.
class Order {
final String id;
final String customerId;
final List<OrderItem> items;
final DateTime createdAt;
Order(this.id, this.customerId, this.items, this.createdAt) {
// Validate constructor inputs
assert(id.isNotEmpty, 'Order ID cannot be empty');
assert(customerId.isNotEmpty, 'Customer ID cannot be empty');
assert(items.isNotEmpty, 'Order must have at least one item');
assert(createdAt.isBefore(DateTime.now()), 'Order cannot be in the future');
}
double calculateTotal() {
// Assert internal state
assert(items.isNotEmpty, 'Cannot calculate total for empty order');
var total = 0.0;
for (var item in items) {
total += item.price * item.quantity;
}
// Assertion for sanity check
assert(total > 0, 'Order total must be positive');
return total;
}
}
class OrderItem {
final String productId;
final double price;
final int quantity;
OrderItem(this.productId, this.price, this.quantity) {
assert(productId.isNotEmpty, 'Product ID cannot be empty');
assert(price > 0, 'Price must be positive');
assert(quantity > 0, 'Quantity must be positive');
}
}
void main() {
try {
var order = Order(
'',
'customer123',
[],
DateTime.now(),
);
} catch (e) {
print('Order creation failed: $e');
}
}
Best Practices
Use Assertions for Internal State
// Good: Validating internal state
class Counter {
int _count = 0;
void increment() {
assert(_count >= 0, 'Count should never be negative');
_count++;
}
void decrement() {
assert(_count > 0, 'Cannot decrement below zero');
_count--;
}
}
Use Assertions for Function Assumptions
// Good: Documenting assumptions
int getFirstElement(List<int> list) {
assert(list.isNotEmpty, 'List must not be empty');
return list[0];
}
Common Mistakes
Using Assertions for Runtime Errors
Wrong:
void process(String data) {
// Should be exception, not assertion
assert(data != null, 'Data cannot be null');
// Null check should be an exception
}
Correct:
void process(String data) {
if (data.isEmpty) {
throw ArgumentError('Data cannot be empty');
}
}
Relying on Assertions for Security
Wrong:
void sensitiveOperation(User user) {
// Assertions can be disabled in production!
assert(user.isAdmin, 'User must be admin');
}
Correct:
void sensitiveOperation(User user) {
if (!user.isAdmin) {
throw UnauthorizedException('Admin access required');
}
}
Summary
Assertions are powerful debugging tools that validate assumptions during development. Use them for catching bugs, not for handling runtime errors.
Next Steps
Now that you understand assertions, you've completed the Error Handling section! Continue to:
Did You Know?
- Assertions are disabled in production
- Use
assert()with a condition and optional message - Assertions help catch bugs early
- They're for debugging, not validation
- Assertions can be used anywhere
assert()throwsAssertionError- Use exceptions for runtime error handling