Dart: تحسين المشروع ونشره — خط أنابيب البيانات
آخر تحديث: 2026-08-26
الكود الذي يعمل ليس كافياً؛ الكود الذي يعمل بسرعة واستقرار وأمان هو تسليم المهندس. — تشارلي
1. ما ستتعلمه
- تحليل الأداء وتحسينه: توازن تحميل Isolate / ضغط Stream العكسي / تحسين الذاكرة
- تجميع AOT (
dart compile exe) وتخفيف المخرجات - تكوين CI/CD: GitHub Actions +
dart test+dart analyze - سير عمل النشر على pub.dev والوثائق (README / CHANGELOG / وثائق API)
- القبول النهائي لأليس: تقرير اختبار الإجهاد الشامل لمليون طلب
2. قصة مطور حقيقية
(1) نقطة الألم: يعمل لكن يعمل ببطء
عالج خط أنابيب البيانات 10 آلاف طلب في 0.3 ثانية فقط. عندما طبقت أليس اختبار إجهاد على مجموعة بيانات مكونة من مليون طلب، استغرق الأمر 45 ثانية. حدد بوب المشاكل: تقسيم Isolate غير متساوٍ (واحد فقط من 4 أنوية كان يعمل)، عدم وجود ضغط عكسي في Streams تسبب في ارتفاع الذاكرة إلى 2 جيجابايت، ومنتج 80 ميجابايت تم تجميعه في وضع التصحيح.
(2) الحل المحسّن
تقسيم Isolate الديناميكي وازن الحمل عبر 4 أنوية، إضافة الضغط العكسي إلى Streams أبقى الذاكرة تحت 200 ميجابايت، تجميع AOT قلل المخرجات إلى 15 ميجابايت، وأتمت CI/CD الاختبار والنشر.
flowchart LR A[الكود مكتمل] --> B[تحسين الأداء] B --> C[ضبط Isolate] B --> D[ضغط Stream العكسي] B --> E[تحليل الذاكرة] C --> F[تجميع AOT] F --> G[CI/CD] G --> H[GitHub Actions] H --> I[النشر على pub.dev] I --> J["v1.0.0"]
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
(3) الفوائد
- انخفض معالجة مليون طلب من 45 ثانية إلى 8 ثوانٍ
- انخفضت الذاكرة من 2 جيجابايت إلى 180 ميجابايت
- تقلصت مخرجات البناء من 80 ميجابايت إلى 15 ميجابايت
- أتمتة CI/CD تضمن الجودة مع كل commit
3. تحليل الأداء وتحسينه
(1) توازن تحميل Isolate
▶ مثال
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
: تقسيم ديناميكي مقابل ثابت
import 'dart:isolate';
// سيء: التقسيم الثابت - عمل غير متساوٍ
List<List<T>> staticChunk<T>(List<T> data, int chunks) {
final size = (data.length / chunks).ceil();
return [
for (var i = 0; i < chunks; i++)
data.sublist(i * size, ((i + 1) * size).clamp(0, data.length))
];
}
// جيد: سرقة العمل الديناميكية مع SendPort
class DynamicIsolatePool {
final int _poolSize;
var _pendingChunks = <List<Map<String, dynamic>>>[];
final _results = <AnalysisResult>[];
var _activeWorkers = 0;
late Completer<List<AnalysisResult>> _completer;
DynamicIsolatePool({int poolSize = 4}) : _poolSize = poolSize;
Future<List<AnalysisResult>> process(
List<Map<String, dynamic>> allOrders, {
int chunkSize = 50000,
}) async {
_pendingChunks = [
for (var i = 0; i < allOrders.length; i += chunkSize)
allOrders.sublist(i, (i + chunkSize).clamp(0, allOrders.length))
];
_completer = Completer<List<AnalysisResult>>();
_activeWorkers = 0;
final workers = <_Worker>[];
for (var i = 0; i < _poolSize && _pendingChunks.isNotEmpty; i++) {
final chunk = _pendingChunks.removeAt(0);
workers.add(await _spawnWorker(chunk));
_activeWorkers++;
}
return _completer.future;
}
Future<_Worker> _spawnWorker(List<Map<String, dynamic>> initialChunk) async {
final receivePort = ReceivePort();
final isolate = await Isolate.spawn(_workerEntry, receivePort.sendPort);
final sendPort = await receivePort.first as SendPort;
final responsePort = ReceivePort();
sendPort.send(_WorkMessage(data: initialChunk, responsePort: responsePort.sendPort));
responsePort.listen((result) {
_results.add(result as AnalysisResult);
_activeWorkers--;
if (_pendingChunks.isNotEmpty) {
final nextChunk = _pendingChunks.removeAt(0);
sendPort.send(_WorkMessage(data: nextChunk, responsePort: responsePort.sendPort));
_activeWorkers++;
} else if (_activeWorkers == 0) {
isolate.kill(priority: Isolate.immediate);
receivePort.close();
responsePort.close();
_completer.complete(_results);
}
});
return _Worker(isolate: isolate, sendPort: sendPort);
}
static void _workerEntry(SendPort mainSendPort) {
final receivePort = ReceivePort();
mainSendPort.send(receivePort.sendPort);
receivePort.listen((message) {
if (message is _WorkMessage) {
final orders = message.data.map((m) => Order(
id: m['id'] as String,
amount: (m['amount'] as num).toDouble(),
status: m['status'] as String,
category: m['category'] as String,
)).toList();
final result = const OrderAnalyzer().analyze(orders);
message.responsePort.send(result);
}
});
}
}
class _Worker {
final Isolate isolate;
final SendPort sendPort;
const _Worker({required this.isolate, required this.sendPort});
}
class _WorkMessage {
final List<Map<String, dynamic>> data;
final SendPort responsePort;
const _WorkMessage({required this.data, required this.responsePort});
}
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
| منطقة التحسين | قبل | بعد | التحسن |
|---|---|---|---|
| استراتيجية التقسيم | تقسيم متساوٍ ثابت | سرقة عمل ديناميكية | أسرع بنسبة 30% |
| حجم الجزء | 1 جزء/عامل | 50 ألف عنصر/جزء | الذاكرة للنصف |
| عدد العمال | ثابت 4 | متكيف مع أنوية CPU | أسرع على متعدد الأنوية |
(2) ضغط Stream العكسي
▶ مثال
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
: التحكم في الضغط العكسي
import 'dart:async';
class BackpressurePipeline {
final DataSource _source;
final OrderParser _parser;
final int _maxConcurrent;
BackpressurePipeline({
required DataSource source,
required OrderParser parser,
int maxConcurrent = 1000,
}) : _source = source,
_parser = parser,
_maxConcurrent = maxConcurrent;
Future<AnalysisResult> execute() async {
final orders = <Order>[];
var pending = 0;
final completer = Completer<void>();
final subscription = _source.readLines().listen(
(line) {
try {
orders.add(_parser.parse(line));
} on FormatException {
return;
}
pending++;
if (pending >= _maxConcurrent) {
subscription.pause();
}
},
onDone: () {
completer.complete();
},
onError: (e) {
completer.completeError(e);
},
);
await completer.future;
return const OrderAnalyzer().analyze(orders);
}
// Stream قابل للإيقاف مع التحكم في المخزن المؤقت
Stream<Order> streamWithBackpressure() async* {
var buffer = <Order>[];
await for (final line in _source.readLines()) {
try {
buffer.add(_parser.parse(line));
} on FormatException {
continue;
}
if (buffer.length >= _maxConcurrent) {
for (final order in buffer) {
yield order;
}
buffer = [];
}
}
for (final order in buffer) {
yield order;
}
}
}
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
| استراتيجية الضغط العكسي | الوصف | حالة الاستخدام |
|---|---|---|
| pause/resume | يوقف تدفق المصدر | المستهلك يعالج ببطء |
| حد المخزن المؤقت | مخزن مؤقت بحجم ثابت | استخدام ذاكرة قابل للتحكم |
| إسقاط الأحدث | يتجاهل البيانات التي تتجاوز الحد | فقدان محتمل في المراقبة في الوقت الفعلي |
(3) تحسين الذاكرة
▶ مثال
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
: تقنيات تحليل الذاكرة
import 'dart:math';
class MemoryOptimizer {
// استخدام iterables بدلاً من القوائم حيثما أمكن
static Iterable<Order> lazyParse(Iterable<String> lines, OrderParser parser) sync* {
for (final line in lines) {
try {
yield parser.parse(line);
} on FormatException {
continue;
}
}
}
// المعالجة بأجزاء لتحديد الذاكرة
static Future<AnalysisResult> processInChunks(
Stream<String> lines,
OrderParser parser, {
int chunkSize = 100000,
}) async {
var chunk = <Order>[];
var totalResult = _EmptyAnalysisResult();
await for (final line in lines) {
try {
chunk.add(parser.parse(line));
} on FormatException {
continue;
}
if (chunk.length >= chunkSize) {
final partial = const OrderAnalyzer().analyze(chunk);
totalResult = totalResult.merge(partial);
chunk = [];
}
}
if (chunk.isNotEmpty) {
final partial = const OrderAnalyzer().analyze(chunk);
totalResult = totalResult.merge(partial);
}
return totalResult.toAnalysisResult();
}
}
class _EmptyAnalysisResult {
int totalOrders = 0;
int completedOrders = 0;
double revenue = 0;
double tax = 0;
final Map<String, double> revenueByCategory = {};
_EmptyAnalysisResult merge(AnalysisResult other) {
totalOrders += other.totalOrders;
completedOrders += other.completedOrders;
revenue += other.revenue;
tax += other.tax;
for (final entry in other.revenueByCategory.entries) {
revenueByCategory.update(entry.key, (v) => v + entry.value, ifAbsent: () => entry.value);
}
return this;
}
AnalysisResult toAnalysisResult() => AnalysisResult(
totalOrders: totalOrders,
completedOrders: completedOrders,
revenue: revenue,
tax: tax,
total: revenue + tax,
revenueByCategory: Map.unmodifiable(revenueByCategory),
);
}
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
| تقنية التحسين | التأثير | حالة الاستخدام |
|---|---|---|
| Iterable كسول | بدون تحميل مسبق لجميع البيانات | تمرير البيانات عنصراً بعنصر من المصدر إلى المستهلك |
| المعالجة بأجزاء | استخدام ذاكرة ثابت | معالجة ملايين السجلات |
| دمج النتائج | نتائج وسيطة صغيرة | تجميع الإحصائيات بعد التقسيم |
4. تجميع AOT وتخفيف المخرجات
▶ مثال
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
: أوامر التجميع والتحسين
# بناء التصحيح (كبير، يتضمن معلومات التصحيح)
dart compile exe bin/data_pipeline.dart -o build/data_pipeline_debug
# بناء الإصدار (محسّن، أصغر)
dart compile exe bin/data_pipeline.dart -o build/data_pipeline \
--define=MODE=release
# التحقق من حجم الثنائي
ls -lh build/
# إزالة رموز التصحيح (تخفيض إضافي في الحجم)
# على Linux/macOS:
strip build/data_pipeline
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
| وضع التجميع | الأمر | حجم المخرجات | سرعة البدء |
|---|---|---|---|
JIT (dart run) |
dart run bin/main.dart |
0 (يشغل المصدر) | بطيء |
| AOT exe | dart compile exe |
~15 ميجابايت | سريع |
| AOT aot-snapshot | dart compile aot-snapshot |
~8 ميجابايت | الأسرع (يتطلب dartaotruntime) |
▶ مثال
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
: تكوين وقت الترجمة
// استيرادات شرطية لـ tree shaking
const _isRelease = bool.fromEnvironment('MODE.release');
void log(String message) {
if (!_isRelease) {
print('[DEBUG] $message');
}
}
// ستتم إزالة هذه الفئة بالكامل في وضع الإصدار
class DebugLogger {
void log(String message) {
if (!_isRelease) {
print('[DEBUG] ${DateTime.now()}: $message');
}
}
}
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
5. CI/CD — GitHub Actions
▶ مثال
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
: تكوين CI الكامل
# .github/workflows/ci.yml
name: DataPipeline CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
analyze-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
sdk: [stable, beta]
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
with:
sdk: ${{ matrix.sdk }}
- name: Install dependencies
run: dart pub get
- name: Verify formatting
run: dart format --output=none --set-exit-if-changed .
- name: Analyze code
run: dart analyze --fatal-infos
- name: Run tests
run: dart test --coverage=coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: coverage/lcov.info
build-and-release:
needs: analyze-and-test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
- name: Install dependencies
run: dart pub get
- name: Compile AOT binary
run: |
dart compile exe bin/data_pipeline.dart -o build/data_pipeline
- name: Run integration test
run: |
./build/data_pipeline analyze --source csv --input test/fixtures/sample.csv
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: data_pipeline-linux
path: build/data_pipeline
publish:
needs: build-and-release
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
- name: Install dependencies
run: dart pub get
- name: Publish to pub.dev
run: dart pub publish --force
env:
PUB_CREDENTIALS: ${{ secrets.PUB_CREDENTIALS }}
| مرحلة CI | الفحوصات التي يتم إجراؤها | الإجراء عند الفشل |
|---|---|---|
| التنسيق | dart format |
تنسيق تلقائي لـ PR |
| التحليل الثابت | dart analyze |
حظر الدمج |
| اختبارات الوحدة | dart test |
حظر الدمج |
| اختبار التكامل | ثنائي AOT شامل | حظر النشر |
| النشر | dart pub publish |
يتم تشغيله فقط بعلامة |
6. النشر على pub.dev
▶ مثال
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
: تكوين النشر في pubspec.yaml
name: data_pipeline
description: E-commerce data analytics CLI tool - process million-level orders with Stream + Isolate
version: 1.0.0
homepage: https://github.com/bob/datapipeline
repository: https://github.com/bob/datapipeline
issue_tracker: https://github.com/bob/datapipeline/issues
environment:
sdk: ^3.0.0
dependencies:
args: ^2.4.2
http: ^1.1.0
csv: ^6.0.0
dev_dependencies:
test: ^1.24.0
build_runner: ^2.4.0
json_serializable: ^6.7.0
executables:
data_pipeline: data_pipeline
▶ مثال
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
: CHANGELOG.md
# CHANGELOG
---
## ملاحظات الإصدار
### (1) 1.0.0 - 2025-01-15
#### الميزات
- دعم مصادر بيانات CSV و JSON
- خط أنابيب قائم على Stream مع ضغط عكسي
- Isolate Pool للمعالجة المتوازية
- CLI مع الأوامر الفرعية analyze/export/validate
- مطابقة الأنماط + بنية الصنف المختوم
### (2) الأداء
- معالجة مليون طلب في ~8 ثوانٍ (4 أنوية)
- استخدام الذاكرة تحت 200 ميجابايت لمليون سجل
### (3) الاختبار
- تغطية كود 95%
- اختبارات الوحدة + التكامل + E2E
▶ مثال
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
: قائمة مراجعة ما قبل النشر
# قائمة مراجعة ما قبل النشر
# 1. تشغيل جاف للنشر
dart pub publish --dry-run
# 2. التحقق من النقاط على pub.dev
dart pub publish --dry-run 2>&1 | grep -E "score|warning|error"
# 3. توليد وثائق API
dart doc .
# 4. التحقق من محتويات الحزمة
dart pub pack # ينشئ .tar.gz للفحص
# 5. النشر النهائي
dart pub publish
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
| خطوة النشر | الأمر | الغرض |
|---|---|---|
| تشغيل جاف | dart pub publish --dry-run |
التحقق من محتويات الحزمة |
| توليد الوثائق | dart doc . |
وثائق API |
| فحص الإصدار | pubspec.yaml version |
الإصدار الدلالي |
| النشر | dart pub publish |
الدفع إلى pub.dev |
7. توليد وثائق API
▶ مثال
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
: معايير تعليقات الوثائق
/// يحلل بيانات طلبات التجارة الإلكترونية وينتج نتائج إحصائية.
///
/// استخدم [OrderAnalyzer] كنقطة دخول أساسية لتحليل البيانات.
/// كون [taxRate] بناءً على المنطقة المستهدفة.
///
/// مثال:
/// ```dart
/// final analyzer = OrderAnalyzer(taxRate: 0.08);
/// final result = analyzer.analyze(orders);
/// print('Revenue: \$${result.revenue} USD');
/// ```
///
/// انظر أيضاً:
/// - [AnalysisResult] لبنية المخرجات
/// - [Order] لنموذج الإدخال
class OrderAnalyzer {
/// ينشئ محللاً بمعدل الضريبة [taxRate] المعطى.
///
/// الافتراضي 0.08 (معدل ضريبة US 8%).
/// استخدم معدلات خاصة بالمنطقة: US=0.08, EU=0.20, UK=0.15.
const OrderAnalyzer({this.taxRate = 0.08});
/// معدل الضريبة المطبق على الطلبات المكتملة.
final double taxRate;
/// يحلل قائمة [orders] ويُرجع إحصائيات مجمعة.
///
/// فقط الطلبات ذات الحالة 'completed' تُدرج في حسابات الإيرادات.
/// الطلبات ذات الحالات الأخرى تُعد في [AnalysisResult.totalOrders]
/// لكنها مستبعدة من الإيرادات.
AnalysisResult analyze(List<Order> orders) {
// ... التنفيذ
}
}
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
8. المثال الكامل: قياس الأداء
// ============================================
// قياس أداء خط أنابيب البيانات
// اختبار الإجهاد الشامل لأليس
// ============================================
import 'dart:async';
import 'dart:io';
class Benchmark {
final String name;
final Stopwatch _stopwatch;
Benchmark(this.name) : _stopwatch = Stopwatch();
Future<T> run<T>(Future<T> Function() action) async {
_stopwatch.reset();
_stopwatch.start();
final result = await action();
_stopwatch.stop();
print('$name: ${_stopwatch.elapsedMilliseconds}ms');
return result;
}
}
// توليد بيانات الاختبار
List<String> generateOrders(int count) {
final categories = ['Electronics', 'Clothing', 'Books', 'Home', 'Sports'];
final statuses = ['completed', 'completed', 'completed', 'pending', 'cancelled'];
final regions = ['US', 'EU', 'UK', 'JP'];
return List.generate(count, (i) {
final category = categories[i % categories.length];
final status = statuses[i % statuses.length];
final region = regions[i % regions.length];
final amount = (100 + (i * 37) % 5000).toDouble();
return 'ORD-${(i + 1).toString().padLeft(7, '0')},$amount,$status,$category,$region';
});
}
void main() async {
print('=== DataPipeline Performance Benchmark ===\n');
final sizes = [10000, 100000, 1000000];
for (final size in sizes) {
final label = size >= 1000000 ? '${size ~/ 1000000}M' : '${size ~/ 1000}K';
print('--- $label orders ---');
// توليد وحفظ في ملف مؤقت
final data = generateOrders(size);
final tempFile = File('${Directory.systemTemp.path}/bench_${label}.csv');
await tempFile.writeAsString(data.join('\n'));
// القياس 1: معالجة Stream
final streamResult = await Benchmark('Stream ($label)').run(() async {
final source = CsvSource(path: tempFile.path);
final parser = const CsvOrderParser();
final pipeline = Pipeline(source: source, parser: parser);
return pipeline.execute();
});
// القياس 2: معالجة بأجزاء موفرة للذاكرة
final chunkedResult = await Benchmark('Chunked ($label)').run(() async {
final source = CsvSource(path: tempFile.path);
return MemoryOptimizer.processInChunks(
source.readLines(),
const CsvOrderParser(),
chunkSize: 50000,
);
});
// التحقق من تطابق النتائج
final streamRev = streamResult.revenue.toStringAsFixed(2);
final chunkedRev = chunkedResult.revenue.toStringAsFixed(2);
print('Revenue match: ${streamRev == chunkedRev ? "PASS" : "FAIL"}');
print('Revenue: \$${streamRev} USD\n');
await tempFile.delete();
}
// القياس 3: فحص حجم ثنائي AOT
print('--- Build Artifacts ---');
final exe = File('build/data_pipeline');
if (await exe.exists()) {
final sizeKB = await exe.length() ~/ 1024;
print('AOT binary: ${sizeKB}KB');
} else {
print('AOT binary: not built (run: dart compile exe bin/data_pipeline.dart)');
}
print('\n=== Benchmark Complete ===');
print('Alice acceptance criteria: 1M orders < 10s, memory < 300MB, binary < 20MB');
}
// ---- stubs مطلوبة (مستوردة من الدروس السابقة) ----
sealed class DataSource {
const DataSource();
String get displayName;
Stream<String> readLines();
}
class CsvSource extends DataSource {
final String path;
const CsvSource({required this.path});
@override
String get displayName => 'CSV: $path';
@override
Stream<String> readLines() =>
File(path).openRead().transform(utf8.decoder).transform(const LineSplitter());
}
abstract class OrderParser {
const OrderParser();
Order parse(String raw);
}
class CsvOrderParser extends OrderParser {
const CsvOrderParser();
@override
Order parse(String raw) {
final parts = raw.split(',');
if (parts.length < 4) throw FormatException('Invalid row');
return Order(
id: parts[0].trim(),
amount: double.parse(parts[1].trim()),
status: parts[2].trim(),
category: parts[3].trim(),
region: parts.length > 4 ? parts[4].trim() : 'US',
);
}
}
class Order {
final String id;
final double amount;
final String status;
final String category;
final String region;
const Order({required this.id, required this.amount, required this.status, required this.category, this.region = 'US'});
double get tax => amount * 0.08;
}
class AnalysisResult {
final int totalOrders;
final int completedOrders;
final double revenue;
final double tax;
final double total;
final Map<String, double> revenueByCategory;
const AnalysisResult({required this.totalOrders, required this.completedOrders, required this.revenue, required this.tax, required this.total, required this.revenueByCategory});
}
class OrderAnalyzer {
const OrderAnalyzer();
AnalysisResult analyze(List<Order> orders) {
final completed = orders.where((o) => o.status == 'completed').toList();
final revenue = completed.fold<double>(0, (s, o) => s + o.amount);
final tax = completed.fold<double>(0, (s, o) => s + o.tax);
final byCategory = <String, double>{};
for (final o in completed) {
byCategory.update(o.category, (v) => v + o.amount, ifAbsent: () => o.amount);
}
return AnalysisResult(totalOrders: orders.length, completedOrders: completed.length, revenue: revenue, tax: tax, total: revenue + tax, revenueByCategory: byCategory);
}
}
class Pipeline {
final DataSource source;
final OrderParser parser;
const Pipeline({required this.source, required this.parser});
Future<AnalysisResult> execute() async {
final orders = <Order>[];
await for (final line in source.readLines()) {
try { orders.add(parser.parse(line)); } on FormatException { continue; }
}
return const OrderAnalyzer().analyze(orders);
}
}
class MemoryOptimizer {
static Future<AnalysisResult> processInChunks(Stream<String> lines, OrderParser parser, {int chunkSize = 50000}) async {
var chunk = <Order>[];
int totalOrders = 0, completedOrders = 0;
double revenue = 0, tax = 0;
final byCategory = <String, double>{};
await for (final line in lines) {
try { chunk.add(parser.parse(line)); } on FormatException { continue; }
if (chunk.length >= chunkSize) {
final r = const OrderAnalyzer().analyze(chunk);
totalOrders += r.totalOrders;
completedOrders += r.completedOrders;
revenue += r.revenue;
tax += r.tax;
for (final e in r.revenueByCategory.entries) {
byCategory.update(e.key, (v) => v + e.value, ifAbsent: () => e.value);
}
chunk = [];
}
}
if (chunk.isNotEmpty) {
final r = const OrderAnalyzer().analyze(chunk);
totalOrders += r.totalOrders;
completedOrders += r.completedOrders;
revenue += r.revenue;
tax += r.tax;
for (final e in r.revenueByCategory.entries) {
byCategory.update(e.key, (v) => v + e.value, ifAbsent: () => e.value);
}
}
return AnalysisResult(totalOrders: totalOrders, completedOrders: completedOrders, revenue: revenue, tax: tax, total: revenue + tax, revenueByCategory: byCategory);
}
}
> **الإخراج:** نفّذ محلياً في DartPad أو مع `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x. قد تختلف النتائج قليلاً مع إصدارات SDK.
9. تقرير قبول أليس
| معايير القبول | الهدف | الفعلي | الحالة |
|---|---|---|---|
| وقت معالجة مليون طلب | < 10 ثوانٍ | ~8 ثوانٍ | PASS |
| ذروة استخدام الذاكرة | < 300 ميجابايت | ~180 ميجابايت | PASS |
| حجم مخرجات AOT | < 20 ميجابايت | ~15 ميجابايت | PASS |
| تغطية الاختبار | > 90% | 95% | PASS |
dart analyze |
0 تحذيرات | 0 | PASS |
dart format |
100% | 100% | PASS |
| تغطية الوثائق | 100% من API العامة | 100% | PASS |
| نقاط pub.dev | > 120 | 140 | PASS |
أليس: "تم قبول DataPipeline v1.0! تم معالجة مليون طلب في 8 ثوانٍ، والذاكرة بقيت تحت 200 ميجابايت، ومخرجات بناء AOT مضغوطة، وأتمتة CI/CD مكتملة. جاهز للإصدار!"
❓ أسئلة شائعة
س: لماذا مخرجات بناء AOT كبيرة جداً؟ ج: تتضمن مخرجات AOT آلة Dart الافتراضية مبسطة + كود أصلي مترجم. استخدم
stripلإزالة رموز التصحيح وtree shakingلإزالة الكود غير المستخدم. بناء AOT نموذجي لـ Dart CLI هو 10-20 ميجابايت.
س: هل تكلفة إنشاء Isolate كبيرة؟ ج: يستغرق
Isolate.spawn~50-100 مللي ثانية، وIsolate.run~30 مللي ثانية. للمهام طويلة الأمد، استخدم Isolate Pool لإعادة استخدام العمال وتجنب الإنشاء/التدمير المتكرر.
س: كيف يعمل ضغط Stream العكسي مع Isolates معاً؟ ج: يقرأ الـ Isolate الرئيسي Stream ويخزنه مؤقتاً بحجم ثابت، ويوقف عند الامتلاء. تستهلك Isolates العامل البيانات المخزنة مؤقتاً وتُخبر الـ Isolate الرئيسي للاستئناف عند الانتهاء. إنه مشابه لنمط المنتج-المستهلك.
س: كيف يمكنني تحسين نقاط pub.dev الخاصة بي؟ ج: تأكد من: 1) pubspec.yaml كامل (homepage/repository/description)؛ 2) README.md مفصل؛ 3)
dart analyzeبدون تحذيرات؛ 4) تغطية الاختبار > 80%؛ 5) تعليقات الوثائق تغطي جميع واجهات API العامة.
س: كيف أخزن التبعيات مؤقتاً في GitHub Actions؟ ج: استخدم
actions/cacheلتخزين~/.pub-cacheمؤقتاً، مع المفتاح بناءً على hash لـpubspec.lock. التشغيل الأول يستغرق ~30 ثانية، إصابة ذاكرة التخزين المؤقت تستغرق ~3 ثوانٍ.
س: كيف أختبر الثنائي المجمع AOT في CI؟ ج: بعد
dart compile exe، شغّل./build/data_pipeline analyze --input test.csvمباشرة، مع التحقق من كود الخروج والمخرجات. هذا أقرب لبيئة الإنتاج منdart run.
📖 ملخص
- ثلاثي تحسين الأداء: تقسيم Isolate الديناميكي، التحكم في ضغط Stream العكسي، المعالجة بأجزاء لتحديد الذاكرة
- تجميع AOT يعطي CLI في Dart سرعة بدء على مستوى الأصلي؛
strip+tree shakingيخففان المخرجات - أتمتة CI/CD: تنسيق ← تحليل ← اختبار ← تجميع ← اختبار تكامل ← نشر
- النشر على pub.dev يتطلب pubspec.yaml كامل، و README، و CHANGELOG، ووثائق API
- قبلت أليس: 8 ثوانٍ لمليون طلب، 180 ميجابايت ذاكرة، 15 ميجابايت مخرجات بناء
📝 تمارين
- أساسي (صعوبة ⭐): أضف Makefile/سكربت لـ
dart compile exeإلى خط أنابيب البيانات، مما يتيح تجميع بناء الإصدار بنقرة واحدة وإخراج حجم الملف. - متوسط (صعوبة ⭐⭐): نفّذ آلية الإيقاف/الاستئناف لضغط Stream العكسي. تحقق بمعالجة بيانات 10 آلاف طلب: الذاكرة لا تزداد أثناء الإيقاف، وتستأنف المعالجة بشكل صحيح.
- تحدي (صعوبة ⭐⭐⭐): كون خط أنابيب GitHub Actions CI كامل: فحص التنسيق + التحليل الثابت + اختبار الوحدة + تجميع AOT + اختبار التكامل. ادفع إلى GitHub للتحقق من أن خط الأنابيب يمر.
← الدرس السابق | اكتملت الدورة — تهانينا على إنهاء جميع الدروس الـ 24!