مشروع: أداة ملاحظات مستندة إلى الملفات
مشروع: أداة ملاحظات مستندة إلى الملفات
كلّ مفهوم تمّ تدريسه في هذا الدرس — Path، وFiles، والتدفقات ذات المخزن المؤقت، وتعبير try-with-resources، وترميز المحارف — يتقاطع في هذا المشروع الختامي. ستبني أداة ملاحظات صغيرة لكنها كاملة تعمل من سطر الأوامر، تحفظ الملاحظات في ملف نصّي، وتدعم السرد والإضافة والحذف بالفهرس والبحث. الهدف ليس مجرّد جعلها تعمل، بل اتخاذ قرارات تصميميّة واعية وفهم المقايضات الكامنة وراء كلّ منها.
ما تفعله الأداة
- add — إضافة ملاحظة جديدة إلى الملف.
- list — عرض جميع الملاحظات مع فهارسها المبدوءة بالرقم 1.
- delete <index> — حذف الملاحظة عند الفهرس المحدّد.
- search <term> — عرض كل ملاحظة تحتوي على مصطلح البحث (بصرف النظر عن حالة الأحرف).
تُخزَّن الملاحظات بمعدّل سطر واحد لكلّ ملاحظة في ~/.notes/notes.txt. هذا الملف الوحيد هو "قاعدة البيانات" بأكملها.
هيكل المشروع
نضع كل شيء في ملف واحد للإيجاز؛ في مشروع حقيقي ستقسّم NoteRepository وNoteService وMain إلى فئات منفصلة.
user.home؟ تخزين البيانات في المجلد الرئيسي للمستخدم (~/.notes/) عرف معروف في UNIX/Linux/macOS يفصل البيانات الشخصية عن التطبيق ذاته. كما أنه لا يتطلّب صلاحيات مرتفعة، ويستمر صالحًا حتى بعد إعادة تثبيت التطبيق.
إنشاء مجلد التخزين عند الحاجة
Files.createDirectories غير مؤثّر التكرار (idempotent) — تُنشئ كل جزء ناقص في المسار وتنجح صامتةً إذا كان المسار موجودًا بالفعل. افضّل دائمًا استخدامها على Files.createDirectory حين لا تضمن وجود المجلد الأب.
إضافة ملاحظة
استخدم Files.writeString مع StandardOpenOption.APPEND لإضافة سطر دون قراءة الملف كاملًا. هذه العملية O(1) من حيث الذاكرة بصرف النظر عن عدد الملاحظات الموجودة.
Files.writeString تحميلًا زائدًا يأخذ Charset. الاعتماد على مجموعة المحارف الافتراضية للمنصّة فخّ لنقل الكود — ملف كُتب على Windows (Cp1252) قد لا يُقرأ على خادم Linux (UTF-8). ثبّت StandardCharsets.UTF_8 في كل مكان.
سرد الملاحظات
تحمّل Files.readAllLines كل سطر إلى List<String>. لملف ملاحظات هذا مقبول؛ إذا كان الملف قد يبلغ جيجابايتات فانتقل إلى Files.lines() (تدفق كسول) عوضًا عن ذلك.
حذف ملاحظة بالفهرس
لا توجد طريقة فعّالة لحذف سطر من منتصف ملف نصّي في الموضع مباشرةً — أنظمة الملفات لا تدعم تقليص منطقة دون إعادة كتابة كل ما يليها. النمط المعياري هو اقرأ الكل، احذف، اكتب الكل. لأداة ملاحظات هذا مقبول؛ لسجل ضخم بجيجابايتات ستستخدم تنسيق تخزين مختلفًا.
FileChannel.lock() أو قاعدة بيانات حقيقية.
البحث في الملاحظات
استخدم واجهة التدفقات مع Files.readAllLines() للبحث. يُطبع الناتج بالفهارس الأصلية من الملف حتى يتمكّن المستخدم من حذف الملاحظة بالرقم لاحقًا.
تجميع الأجزاء — تشغيل الأداة
تجميع وتشغيل من الطرفية:
قرارات التصميم والمقايضات
- نص عادي مقابل ثنائي: النص العادي قابل للقراءة البشرية، يُنسخ احتياطيًا ببساطة بـ
cp، ويمكن البحث فيه بـ grep. العيب أن فواصل الأسطر داخل الملاحظة ستفسد التنسيق — ومن هنا جاء التعقيم فيadd(). - الإلحاق مقابل إعادة الكتابة عند الإضافة: الإلحاق سريع وآمن عند انقطاع الكهرباء (الأسطر الموجودة غير متأثرة). إعادة كتابة الملف كاملًا عند كل إضافة أبطأ وأكثر خطورة للملفات الكبيرة.
- قراءة الكل عند الحذف: أمر لا مفرّ منه مع الملفات المسطّحة. المقايضة موثّقة صراحةً — هذا النمط مقبول لمئات الملاحظات؛ لآلاف ستنتقل إلى تنسيق كـ SQLite عبر JDBC.
- UTF-8 في كل مكان: تمرير
StandardCharsets.UTF_8صراحةً لكل قراءة وكتابة يجعل الأداة قابلة للنقل بصرف النظر عن مجموعة المحارف الافتراضية للـ JVM. - لا حاجة لـ try-with-resources هنا:
Files.readAllLinesوFiles.writeStringوFiles.writeهي توابع مريحة تفتح القناة الأساسية وتستخدمها وتغلقها داخليًا. إذا نزلت إلىBufferedReaderأوFileWriterمباشرةً، فـ try-with-resources إلزامي.
الخلاصة
جمع هذا المشروع كل أدوات الدرس: Path وFiles للوصول الاصطلاحي عبر NIO.2، والتعامل الصريح مع ترميز UTF-8، وStandardOpenOption.APPEND للكتابة الفعّالة، ونمط قراءة-تعديل-كتابة للحذف في الموضع، والبحث المستند إلى الحلقات. القرارات التصميمية — النص العادي، سطر واحد لكل ملاحظة، التخزين في المجلد الرئيسي — مقصودة وكل منها موثّقة بمقايضتها. هذا النوع من التفكير هو ما يميّز قاعدة كود قابلة للصيانة عن واحدة تعمل فحسب.