Hello our valued visitor, We present you the best web solutions and high quality graphic designs with a lot of features. just login to your account and enjoy ...
Hello our valued visitor, We present you the best web solutions and high quality graphic designs with a lot of features. just login to your account and enjoy ...
| رقم الخبر | عنوان الخبر | التفاصيل |
|---|---|---|
| 39,910 | تعبيرات لامدا (Lambda Expressions) في جافا |
يُخْزَّن البرنامج الفرعي (subroutine) بذاكرة الحاسوب على هيئة سِلسِلة نصية طويلة مُكوَّنة من الرقمين صفر وواحد، ولذلك فإنه لا يَختلف كثيرًا عن البيانات (data)، مثل الأعداد الصحيحة والسَلاسِل النصية والمصفوفات، والتي تُخْزَّن أيضًا بنفس الطريقة. ربما اِعتدت التفكير بكُلًا من البرامج الفرعية والبيانات على أساس أنهما شيئان مختلفان تمامًا، ولكن في الواقع يَتعامَل الحاسوب مع البرنامج الفرعي على أساس أنه مجرد نوع آخر من البيانات. تَسمَح حتى بعض اللغات البرمجية بالتَعامُل مع البرامج الفرعية بنفس الطريقة التي يَتعامَل معها الحاسوب، وهو ما أُضيف إلى الجافا على هيئة تعبيرات لامدا (lambda expressions) منذ الإصدار ٨. أصبحت تعبيرات لامدا أكثر شيوعًا ببرامج الجافا، وهي مفيدة في العموم، ولكن تَبرُز أهميتها بصورة أكبر ببرمجة واجهات المُستخدِم الرُسومية (GUI)، كالتَعامُل مع "أدوات تَحكُّم واجهة المُستخدَم الرسومية JavaFX (JavaFX GUI toolkit)"، وهو ما سنتناوله بالفصل السادس، كما سنَتَعرَّض لها مجددًا بنهاية الفصل الخامس، لذا يُمكِنك تَخطِّي هذا القسم إذا أردت إلى ذلك الحين. الدوال الكيانية (first-class functions)يُمكِننا كتابة دالة لحِسَاب قيمة مربع العدد، بحيث تَستقبِل ذلك العدد كمُعامِل صُّوري أو وهمي (dummy parameter)، كالتالي: static double square( double x ) { return x*x; }كالعادة، لابُدّ من تَخْصيص اسم لتلك الدالة، وفي تلك الحالة كان square، وعليه تُصبِح تلك الدالة جزءًا دائمًا من البرنامج (program)، وهو ما قد لا يَكُون مُلائمًا؛ بالأخص إذا كنت تَنوِي اِستخدَام الدالة مرة واحدة فقط. لامدا (lambda) هو أحد حروف الأبجدية الإغريقية (Greek alphabet)، والذي اِستخدَمه عالم الرياضيات ألونزو تشرتش (Alonzo Church) أثناء دراسته للدوال القابلة للحِسَاب (computable functions). على سبيل المثال، إذا كنا نُريد حِسَاب قيمة مربع عدد، وليكن x، فقد تَظُنّ أن الترميز x2 يُمثِل "دالة" تَحسِب مربع x، لكنه، في الواقع، ليس سوى تعبير (expression) يُمثِل "نتيجة" حِسَاب مربع x. قَدَّمَ ألونزو تشرتش (Alonzo Church) ترميز لامدا (lambda notation) والذي يُمكِن اِستخدَامه لتعريف دالة (function) بدون تَخْصيص اسم لها. بالتحديد اِستخدَم الترميز lambda(x).x2 (في الواقع يُستخدَم الحرف الإغريقي لامدا ذاته وليس الكلمة نفسها) للإشارة إلى "دالة x معطاة بواسطة x2". يُعدّ هذا الترميز دالة مجرَّدة (function literal)، أيّ تُمثِل قيمة من النوع "دالة (function)" بنفس الطريقة التي تُعدّ بها القيمة 42 عددًا صحيحًا مُجرَّدًا (integer literal) يُمثِل قيمة من النوع int. ينبغي أن تَدْفَعك الدوال المُجرَّدة (function literals) إلى التَفكير بالدوال (function) على أنها مُجرَّد نوع آخر من القيم، وعندما تَصِل إلى تلك المرحلة، فإنك ستَكُون قادرًا على تَطْبِيق نفس تلك الأشياء التي اعتدت إجرائها على القيم الآخرى على الدوال أيضًا، كإِسْناد دالة إلى مُتَغيِّر، أو تمرير دالة كمُعامِل (parameter) إلى برنامج فرعي (subroutine)، أو إعادة دالة كقيمة من برنامج فرعي، أو حتى إنشاء مصفوفة دوال (array of functions). تَسمَح بعض لغات البرمجة بالقيام بكل تلك الأشياء، ويُقال عندها أنها تُدعِّم "الدوال الكيانيَّة (first-class functions)" أو أنها تُعامِل الدوال بعدّها كائنات كيانيَّة (first-class objects). تُوفِّر لغة الجافا كل تلك الأشياء من خلال تعبيرات لامدا (lambda expressions)، والتي تَختلِف صيغتها عن الترميز الذي اِستخدَمه ألونزو تشرتش (Alonzo Church)، فهي الواقع، لا تَستخدِم حتى كلمة لامدا (lambda) على الرغم من مُسماها. تُكتَب دالة حِسَاب مربع العدد بتعبير لامدا (lambda expression) بلغة الجافا، كالتالي: x -> x*xيُنشِئ العَامِل -> تعبير لامدا، بحيث تَقَع المُعامِلات الوهمية أو الصُّوريّة (dummy parameter) على يسار العَامِل، بينما يَقَع التعبير (expression) المسئول عن حِسَاب قيمة الدالة على يمينه. قد ترى تعبير لامدا بالأعلى مُمرَّرًا كمُعامِل فعليّ (actual parameter) إلى برنامج فرعي، أو مُسنَد إلى مُتَغيِّر، أو مُعاد من دالة. إذًا، هل الدوال بلغة الجافا كيانيَّة (first-class)؟ لا تَتَوفَّر إجابة واضحة على هذا السؤال، لأن لغة الجافا لا تُدعِّم بعض الأشياء الآخرى المُتوفِّرة ضِمْن لغات برمجية آخرى. فمثلًا، في حين تستطيع إِسْناد تعبير لامدا المُعرَّف بالأعلى إلى مُتَغيِّر اسمه sqr، فإنك لن تستطيع بَعْدها اِستخدَام ذلك المُتَغيِّر كما لو كان دالة فعليّة، أيّ لا تستطيع كتابة sqr(42). لغة الجافا عمومًا هي لغة صارمة في تَحْدِيد النوع (strongly typed)، ولذلك لكي تَتَمكَّن من إِنشاء المُتَغيِّر sqr، فعليك التَّصْريح عنه أولًا وهو ما يَتضمَّن تَخْصيص نوعه، فيا تُرى ما هو ذلك النوع الذي يَتناسب مع قيمة هي عبارة عن دالة؟ تُوفِّر الجافا ما يُعرَف باسم واجهة نوع الدالة (functional interface) لهذا الغرض، وسنناقشها بَعْد قليل. ملاحظة أخيرة: على الرغم من ارتباط مصطلح الدالة (function) بتعبيرات لامدا (lambda expressions) بدلًا من مصطلحات مثل برنامج فرعي (subroutine) أو تابع (method)، فإنها في الواقع لا تَقْتصِر على تمثيل الدوال (functions)، وإنما يُمكِنها تمثيل أي برنامج فرعي (subroutines)، وبصورة عشوائية. واجهات نوع الدالة (functional interfaces)كي تَتَمكَّن من اِستخدَام برنامج فرعي (subroutine) معين، فلابُدّ أن تَعرِف كُلًا من اسمه، وعدد المُعامِلات (parameters) التي يَستقبِلها، وأنواعها، بالإضافة إلى نوع القيمة المُعادة (return type) من ذلك البرنامج الفرعي. تُوفِّر لغة الجافا ما يُعرَف باسم واجهات نوع الدالة (functional interface)، وهي أَشْبَه بالأصناف (class)، حيث تُعرَّف بملف امتداده هو .java كأي صَنْف، وتَتضمَّن تلك المعلومات المذكورة بالأعلى عن برنامج فرعي وحيد. انظر المثال التالي: public interface FunctionR2R { double valueAt( double x ); }تُصرِّح الشيفرة بالأعلى عن واجهة نوع الدالة FunctionR2R، والموجودة بملف يَحمِل اسم FunctionR2R.java. تُصرِّح تلك الواجهة عن الدالة valueAt، والتي تَستقبِل مُعامِل وحيد من النوع double، وتُعيد قيمة من النوع double. تَفرِض قواعد الصيغة تَخْصيص اسم للمُعامِل -x بالأعلى- على الرغم من أنه في الواقع ليس ذا أهمية هنا، وهو ما قد يَكُون مزعجًا نوعًا ما. ها هو مثال آخر: public interface ArrayProcessor { void process( String[] array, int count ); }تَتضمَّن لغة الجافا الكثير من واجهات نوع الدالة القياسية (standard functional interfaces) بصورة افتراضية. تُعدّ واجهة نوع الدالة Runnable واحدة من أهم تلك الواجهات، وأبسطها، والمُعرَّفة كالتالي: public interface Runnable { public void run(); }سنَستخدِم واجهات نوع الدالة (functional interfaces) الثلاثة بالأعلى للأمثلة التالية بهذا القسم. تُوفِّر لغة الجافا أيضًا ما يُعرَف باسم الواجهات (Interfaces)، والتي هي في الواقع أعم وأشمل، وربما أكثر تعقيدًا من واجهات نوع الدالة (functional interfaces). سنتعلَّّم المزيد عنها بالقسم ٥.٧. سنُقصِر حديثنا بهذا القسم على واجهات نوع الدالة (functional interfaces)؛ لارتباطها بموضوعنا الرئيسي: تعبيرات لامدا (lambda expressions). يُعدّ اسم واجهة نوع الدالة نوعًا (type)، تمامًا كالأنواع String و double، وكأيّ نوع، فإنه يُمكِن اِستخدَامه للتَّصْريح عن المُتَغيِّرات، والمُعامِلات، وكذلك لتَخْصيص نوع القيمة المُعادة (return type) من الدوال، ويُمكِن إِسْناد تعبيرات لامدا (lambda expression) كقيم للمُتَغيِّرات من ذلك النوع. في الواقع، تَتضمَّن واجهة نوع الدالة (functional interface) قالبًا (template) لبرنامج فرعي وحيد، وينبغي لتعبير لامدا (lambda expression) المُسنَد أن يَكُون مُتطابِقًا مع ذلك القالب. تعبيرات لامدا (lambda Expressions)يُعدّ أي تعبير لامدا (lambda expression) تمثيلًا لبرنامج فرعي مجهول الاسم (anonymous subroutine)، أيّ لا يَمتلك اسمًا، ولكنه في المقابل، وكأيّ برنامج فرعي، لديه قائمة من المُعامِلات الصُّوريّة (formal parameter list)، بالإضافة إلى التعريف (definition) نفسه، ويُكتَب عمومًا بالصيغة (syntax) التالية: ( <parameter-list> ) -> { <statements> }كما هو الحال مع البرامج الفرعية العادية، يُمكِن لقائمة المُعامِلات -بالأعلى- أن تَكُون فارغة، أو قد تتكوَّن من تَّصْريح عن مُعامِل (parameter declaration) وحيد أو أكثر، بحيث يُفصَل بين كل تَّصْريح والذي يليه بفاصلة (comma)، وبحيث يَتكوَّن كل تَّصْريح من نوع المُعامِل متبوعًا باسمه. في الواقع، تُبسَّط عادة تلك الصيغة وفقًا لمجموعة من القواعد: أولًا: يُمكِن حَذْف أنواع المُعامِلات إذا كان استنباطها من السِّياق مُمكِنًا. على سبيل المثال، إذا كان هناك تعبير لامدا مُعرَّف على أنه من النوع FunctionR2R، فإن مُعامِل التعبير لابُدّ وأن يَكُون من النوع double، ولذلك فإنه، في تلك الحالة، ليس من الضروري تَخْصيص نوع المُعامِل ضِمْن تعبير اللامدا. ثانيًا، يُمكِن حَذْف الأقواس () حول قائمة المُعامِلات (parameter list) إذا كانت مُكوَّنة من مُعامِل وحيد غَيْر مُحدَّد النوع. ثالثًا، يُمكِن حَذْف الأقواس {} الموجودة على الجانب الأيمن من العَامِل -> إذا كانت تَحتوِي على تَعْليمَة استدعاء برنامج فرعي (subroutine call) وحيدة. أخيرًا، إذا كان الجانب الأيمن من العَامِل -> مكتوبًا بالصيغة { return <expression>; }، فيُمكِن تبسيطها إلى التعبير وحَذْف أي شيء آخر. بفَرْض أننا نريد كتابة دالة لحِسَاب مربع القيم من النوع double، فإن نوع تلك الدالة هو من نفس نوع واجهة نوع الدالة FunctionR2R المُعرَّفة بالأعلى. الآن، إذا كان sqr مُتَغيِّرًا من النوع FunctionR2R، فإننا نستطيع أن نُسنِد تعبير لامدا (lambda expression) كقيمة لذلك المُتَغيِّر، ويُعدّ عندها ذلك التعبير تمثيلًا للدالة المطلوبة. يُمكِن القيام بذلك بأي من الصِيَغ التالية: sqr = (double x) -> { return x*x; }; sqr = (x) -> { return x*x; }; sqr = x -> { return x*x; }; sqr = x -> x*x; sqr = (double fred) -> fred*fred; sqr = (z) -> z*z;تُعرِّف تعبيرات لامدا (lambda expressions) الستة بالأعلى نفس الدالة بالضبط، وأُضيفت آخر تَعْليمَتين (statements) خصيصًا؛ للتأكيد على أن أسماء المُعامِلات غير مهمة، فهى بالنهاية مجرد مُعامِلات وهمية أو صُّوريّة (dummy parameters). لمّا كان المُصرِّف (compiler) على علم بكون المُتَغيِّر sqr من النوع FunctionR2R، ولأن النوع FunctionR2R يتطلَّب مُعامِلًا من النوع double، فقد تَمَكَّنا من حَذْف نوع المُعامِل double. يُمكِن اِستخدَام تعبيرات لامدا فقط ضِمْن السِّياقات التي يستطيع فيها المُصرِّف (compiler) استنباط نوعها، ولذلك لابُدّ من كتابة نوع المُعامِل إذا كان حَذْفه سيَتسبَّب بغموض نوع التعبير. لا يُعدّ المُتَغيِّر sqr -كما هو مُعرَّف بالأعلى- دالة (function) تمامًا، وإنما هو قيمة من النوع FunctionR2R، وبحسب ما هو مُخصَّص بتعريف الواجهة (interface definition) FunctionR2R، فإن ذلك المُتَغيِّر لابُدّ وأن يَحتوِي على دالة تَحمِل اسم valueAt. يمكن استدعاء تلك الدالة من خلال اسمها الكامل sqr.valueAt، فمثلًا يُمكِن كتابة sqr.valueAt(42) أو sqr.valueAt(x) + sqr.valueAt(y). إذا كانت قائمة مُعامِلات تعبير لامدا مُكوَّنة من أكثر من مُعامِل واحد، فإن الأقواس () المُحيطة بقائمة المُعامِلات (parameters list) لم تَعُدْ اختيارية. اُنظر المثال التالي، والذي يَستخدِم واجهة نوع الدالة ArrayProcessor، كما يُبيِّن طريقة كتابة تعبير لامدا عندما يكون تعريفها مُتعدِّد الأسطر (multiline definition): ArrayProcessor concat; concat = (A,n) -> { // الأقواس هنا مطلوبة String str; str = ""; for (int i = 0; i < n; i++) str += A[i]; System.out.println(str); }; // الفاصلة المنقوطة هنا ليست جزءًا من تعبير لامدا // وإنما تشير إلى انتهاء تعليمة الإسناد String[] nums; nums = new String[4]; nums[0] = "One"; nums[1] = "Two"; nums[2] = "Three"; nums[3] = "Four"; for (int i = 1; i < nums.length; i++) { concat.process( nums, i ); }مما سينتج عنه الخَرْج التالي: One OneTwo OneTwoThree OneTwoThreeFourتُصبِح الأمور أكثر تشويقًا عند تمرير تعبير لامدا كمُعامِل فعليّ (actual parameter)، وهو في الواقع الاِستخدَام الأكثر شيوعًا لتعبيرات لامدا. افترض أن لدينا الدالة التالية مثلًا: /** * احسب قيمة التعبير f(start) + f(start+1) + ... + f(end) * حيث f عبارة عن دالة تُستقبل كمعامل * قيمة المعامل end لابد أن تكون أكبر من أو تساوي start */ static double sum( FunctionR2R f, int start, int end ) { double total = 0; for (int n = start; n <= end; n++) { total = total + f.valueAt( n ); } return total; }لمّا كانت f من النوع FunctionR2R، فإن قيمة f عند n تُكتَب على الصورة f.valueAt(n). يُمكِن تمرير تعبير لامدا (lambda expression) كقيمة للمُعامِل الأول عند استدعاء الدالة sum، كالتالي: System.out.print("The sum of n squared for n from 1 to 100 is "); System.out.println( sum( x -> x*x, 1, 100 ) ); System.out.print("The sum of 2 raised to the power n, for n from 1 to 10 is "); System.out.println( sum( num -> Math.pow(2,num), 1, 10 ) );كمثال آخر، افترض أن لدينا برنامجًا فرعيًا (subroutine) ينبغي أن يُنفِّذ مُهِمّة (task) مُعطاة عدة مرات، يُمكِننا ببساطة تمرير تلك المُهِمّة كمُعامِل من النوع Runnable، كالتالي: static void doSeveralTimes( Runnable task, int repCount ) { for (int i = 0; i < repCount; i++) { task.run(); // نفذ المهمة } }نستطيع الآن طباعة السِلسِلة النصية "Hello World" عشر مرات باستدعاء التالي: doSeveralTimes( () -> System.out.println("Hello World"), 10 );تَتَكوَّن قائمة مُعامِلات (parameter list) تعبيرات لامدا من النوع Runnable من زوج فارغ من الأقواس ()؛ وذلك لتتماشى مع البرنامج الفرعي المُصرَّح عنه ضِمْن واجهة نوع الدالة Runnable. اُنظر المثال التالي: doSeveralTimes( () -> { int count = 5 + (int)(21*Math.random()); for (int i = 1; i <= count; i++) { System.out.print(i + " "); } System.out.println(); }, 100);على الرغم من أن الشيفرة بالأعلى قد تبدو معقدة نوعًا ما، إلا أنها، في الواقع، مُكوَّنة من تَعْليمَة وحيدة هي تَعْليمَة استدعاء البرنامج الفرعي doSeveralTimes. يَتَكوَّن مُعامِل البرنامج الفرعي الأول من تعبير لامدا (lambda expression) قد امتد تعريفه إلى عدة أسطر، أما مُعامِله الثاني فهو القيمة ١٠٠، وتُنهِي الفاصلة المنقوطة (semicolon) بنهاية آخر سطر تَعْليمَة استدعاء البرنامج الفرعي (subroutine call). اطلعنا على عدة أمثلة تُسنَد فيها تعبيرات لامدا (lambda expression) إلى مُتَغيِّرات، وآخرى تُمرَّر فيها تعبيرات لامدا كمُعامِلات فعليّة (actual parameter). تَبَقَّى الآن أن نراها مُستخدَمة كقيمة مُعادة (return value) من دالة. اُنظر المثال التالي: static FunctionR2R makePowerFunction( int n ) { return x -> Math.pow(x,n); }وبالتالي، ستُعيد makePowerFunction(2) قيمة من النوع FunctionR2R تَحسِب مربع قيمة المُعامِل المُمرَّر، بينما ستُعيد makePowerFunction(10) قيمة من النوع FunctionR2R تَحسِب قيمة المُعامِل المُمرَّر مرفوعًا للأس ١٠. يُوضِح هذا المثال أيضًا أن تعبيرات لامدا (lambda expression) تستطيع اِستخدَام مُتَغيِّرات آخرى إلى جانب مُعامِلاتها، مثل n في المثال بالأعلى. (في الواقع هناك بعض القيود لتَحْدِيد متى يُمكِن القيام بذلك). مراجع التوابع (Method References)لنفْترِض أننا نريد كتابة تعبير لامدا (lambda expression) من النوع FunctionR2R، بحيث يُمثِل دالة تَحسِب الجذر التربيعي. نستطيع كتابته على الصورة x -> Math.sqrt(x)، ولكننا إذا دققنا النظر، سنَجِدْ أن تعبير لامدا، في تلك الحالة تحديدًا، ليس سوى مجرد مُغلِّف (wrapper) بسيط للدالة Math.sqrt الموجودة بالفعل. ولهذا تُوفِّر الجافا ما يُعرَف باسم مَراجِع التوابع (method reference)؛ لاِستخدَامها في الحالات المشابهة (تَذَكَر أن "التابع [method]" هو مجرد كلمة آخرى للإشارة إلى "البرنامج الفرعي [subroutine]" بلغة الجافا). نستطيع مثلًا اِستخدَام المَرجِع التابعي Math::sqrt، والذي يُعدّ اختصارًا لتعبير اللامدا المذكور سلفًا، أينما أَمْكَن اِستخدَام تعبير اللامدا المناظر، كتمريره مثلًا كمُعامِل للدالة sum المُعرَّفة بالأعلى، كالتالي: System.out.print("The sum of the square root of n for n from 1 to 100 is "); System.out.println( sum( Math::sqrt, 1, 100 ) );فقط لو أَمْكَننا اِستخدَام الاسم Math.sqrt هنا بدلًا من الاستعانة بترميز (notation) جديد ::! في الواقع، الترميز Math.sqrt مُعرَّف بالفعل ضِمْن الصَنْف Math لكن للإشارة إلى مُتَغيِّر اسمه sqrt. يُمكِن عمومًا اِستخدَام مَراجِع التوابع (method reference) بدلًا من تعبيرات لامدا (lambda expression) التي يَقْتصِر دورها على اِستدعاء أحد التوابع الساكنة (static method) الموجودة مُسْبَّقًا. تُكتَب مَراجِع التوابع على الصورة التالية: <classname>::<method-name>لا يَقْتصِر هذا الترميز (notation) على التوابع الساكنة، أي تلك المُنتمية للأصناف (classes)، وإنما يَمْتَدّ إلى تلكم المُنتمية للكائنات (objects) كذلك. على سبيل المثال، إذا كان str متغير من النوع String، فإن str بطبيعة الحال سيَحتوِي على التابع str.length()، وفي هذه الحالة، يُمكِن اِستخدَام المَرجِع التابعي str::length كتعبير لامدا من واجهة نوع الدالة SupplyInt المُعرَّفة كالتالي: public interface SupplyInt { int get( ); }ترجمة -بتصرّف- للقسم Section 5: Lambda Expressions من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java. |
| 39,909 | القيم المعادة من الدوال في جافا |
يُطلَق على البرامج الفرعية (subroutine) التي تُعيد قيمة اسم "الدوال (function)"، والتي يُمكِنها أن تُعيد قيمة من نوع واحد مُحدَّد ضِمْن التعريف ويُسمى النوع المُعاد (return type) من الدالة. بإِمكَانك استدعاء الدوال (function call) في العموم بأيّ مكان يَتوقَّع فيه الحاسوب قيمة (value)، مثل الجانب الأيمن من تَعْليمَات الإِسْناد (assignment statement)، أو كمُعامِل فعليّ (actual parameter) بتَعْليمَات استدعاء البرامج الفرعية، أو كجُزء من تعبير (expression) أكبر. يُمكِن حتى اِستخدَام الدوال من النوع boolean، أي التي تُعيد قيمة من ذلك النوع، مَحل الشَّرْط (condition) ضِمْن تَعْليمَات مثل if و while و for و do..while. يُمكِن أيضًا اِستخدَام تَعْليمَة استدعاء دالة (function call)، كما لو كانت برنامجًا فرعيًا، أيّ كما لو أنها لا تُعيد قيمة. في تلك الحالة، يَتجاهَل الحاسوب القيمة المُعادة من البرنامج الفرعي، وهو ما يَكُون منطقيًا في بعض الأحيان. على سبيل المثال، تَقرأ الدالة TextIO.getln() سَطْرًا مُدْخَلًا مِنْ قِبَل المُستخدِم وتُعيده كقيمة من النوع String. عادة، يُسنَد السَطْر المُعاد من تلك الدالة إلى مُتَغيِّر؛ لكي يُستخدَم لاحقًا، كما في التَعْليمَة name = TextIO.getln();. لكن، يُمكِن اِستخدَام تلك الدالة ضِمْن تَعْليمَة استدعاء برنامج فرعي على الصورة TextIO.getln();، والتي، كالعادة، ستَستمِر بقراءة مُدْخَلات المُستخدِم إلى أن تَصِل إلى محرف العودة إلى بداية السطر (carriage return). لمّا لم تُسنَد القيمة المُعادة من تلك الدالة إلى مُتَغيِّر، أو تُستخدَم ضِمْن تعبير (expression)، فإنها تُستبعَد ببساطة، لذا يَكُون تأثير استدعاء البرنامج الفرعي في تلك الحالة هو مجرد قراءة بعض المُدْخَلات واِستبعَادها، وهو الشيء الذي قد تَرغَب به في بعض الأحيان. تعليمة returnبوصولك إلى هنا، فأنت قد تَعلَّمت طريقة استدعاء الدوال (functions) -التي تُعيد قيم فعليّة- مثل Math.sqrt() و TextIO.getInt()، لكنك لَمْ تَكتُب أيّ دالة خاصة بك بَعْد. في الواقع، ستبدو كتابة الدوال أمرًا مألوفًا بالنسبة لك؛ فهي بالنهاية مُشابهة تمامًا لصيغة كتابة أيّ برنامج فرعي (subroutine) عادي باستثناء ضرورة اِستخدَام التَعْليمَة return؛ لتَخْصِيص القيمة التي سيُعيدها البرنامج الفرعي (return value). تُكتَب تَعْليمَة return بالصيغة (syntax) التالية: return <expression>;يُمكِن اِستخدَام تَعْليمَة return بالأعلى داخل تعريف الدوال فقط (function definition)، ولابُدّ لنوع التعبير المَكْتُوب بَعْد التَعْليمَة أن يَتماشى مع النوع المُعاد (return type) المُخصَّص ضِمْن تعريف الدالة، أيّ لابُدّ أن يؤول ذلك التعبير إلى قيمة هي من نوع يُمكِن إِسْناده -بواسطة تَعْليمَة إِسْناد (assignment)- إلى النوع المُعاد (return type) من الدالة والمُخصَّص بتعريفها (definition). عندما يُنفِّذ الحاسوب تَعْليمَة return، فإنه يَحسِب قيمة التعبير المُخصَّص، ثم يُنهِي تَّنْفيذ الدالة، وأخيرًا، يُعيد قيمة التعبير لتُصبِح القيمة المُعادة (return value) من الدالة. اُنظر تعريف الدالة التالي على سبيل المثال: static double pythagoras(double x, double y) { return Math.sqrt( x*x + y*y ); }بفَرْض تَّنْفيذ الحاسوب لتَعْليمَة إِسْناد تَتضمَّن تَعْليمَة استدعاء الدالة بالأعلى، مثل totalLength = 17 + pythagoras(12,5);، فإنه سيُحاوِل تَحْصِيل قيمة تَعْليمَة الاستدعاء pythagoras(12,5)، لذلك سيبدأ بإِسْناد قيم المُعامِلات الفعليّة (actual parameters) المُمرَّرة، أيّ ١٢ و ٥، إلى المُعامِلات الصُّوريّة (formal parameters) x و y بتعريف الدالة، ثم سينتقل إلى تَّنْفيذ مَتْن الدالة (function body)، وبالتالي سيضطرّ إلى تَحْصِيل قيمة التعبير Math.sqrt(12.0*12.0 + 5.0*5.0) والتي تؤول إلى القيمة 13.0، ثم سيُعيد هذه القيمة لتَكُون القيمة المُعادة (return value) من الدالة، وعليه تُستبدَل القيمة 13.0 بتَعْليمَة استدعاء الدالة، فتصبح تَعْليمَة الإِسْناد وكأنها مَكْتُوبة على الصورة totalLength = 17+13.0. أخيرًا، تُضاف القيمة المُعادة (return value) إلى العدد 17 ثم تُخْزَن النتيجة 30.0 بالمُتَغيِّر totalLength. لاحظ أنه وبينما تُنفَّذ الدالة، فإنك قد تَتَمكَّن من تَحْصِيل القيمة المُفْترَض إعادتها منها، وفي تلك الحالة، تستطيع اِستخدَام تَعْليمَة return؛ لإعادة تلك القيمة مباشرة، ومِنْ ثَمَّ تَخَطِّي أية تَعْليمَات آخرى بمَتْن الدالة، دون الحاجة إلى الانتظار إلى نهاية المَتْن، أيّ أنه ليس من الضروري أن تَكُون تَعْليمَة return هي آخر تَعْليمَة بمَتْن الدالة، بل يُمكِن اِستخدَامها بأي مكان ضِمْن المَتْن. ولكن، لابُدّ دائمًا من إعادة قيمة ما من الدالة وذلك بجميع مسارات التَّنْفيذ المُحتمَلة التي قد تَتَّخذها الدالة. كما ذَكَرَنا مُسْبَّقًا، لابُدّ دائمًا من كتابة تَعْليمَة return مَتبوعة بتعبير (expression) ضِمْن تعريف أي دالة (function)؛ لأن الدوال دائمًا ما تُعيد قيمة. في المقابل، لا يُعدّ ذلك ضروريًا مع البرامج الفرعية من غير الدوال (non-function)، أي تلك التي تَستخدِم void ضِمْن تعريفها للتَّصْريح عن نوعها المُعاد، فهي في النهاية لا تُعيد قيمة، ومع ذلك يَظِلّ اِستخدَام تَعْليمَة return ضِمْن هذا السِّياق مُمكنًا، بحيث يَقْتصِر دورها على إنهاء تَّنْفيذ البرنامج الفرعي بمكان ما وسط المَتْن وإعادة التَحكُّم (return control) مرة آخرى إلى سَطْر الشيفرة المسئول عن استدعاء البرنامج الفرعي. تُكتَب تَعْليمَة return في تلك الحالة على الصورة return; بدون تعبير (expression). بالإضافة إلى ما سبق، قد تُستخدَم تَعْليمَة return أيضًا داخل حَلْقة تَكْرار (loop) لإنهاء كُلًا من الحَلْقة (loop) والبرنامج الفرعي الحاضن لها. بالمثل، قد تُستخدَم بتَعْليمَة switch، للخروج من كُلًا من تَعْليمَة switch والبرنامج الفرعي الحاضن لها، أيّ ستَجِدْ أحيانًا تَعْليمَة return مُستخدَمة ضِمْن سِّياقات اعْتَدْت فيها استخدام تَعْليمَة break. أمثلة على الدوال (functions)تَعرَّضنا لمسألة حِسَاب قيم عناصر مُتتالية الأعداد"3N+1" عدة مرات بما في ذلك القسم السابق. يُمكِننا تعريف دالة (function) بسيطة جدًا، تَستقبِل قيمة العنصر الحالي بالمُتتالية، ثم تُعيد قيمة عنصر المتتالية التالي. اُنظر الشيفرة التالية: static int nextN(int currentN) { if (currentN % 2 == 1) // إذا كان currentN فرديًا return 3*currentN + 1; // أعد تلك القيمة else return currentN / 2; // إذا لم يكن، أعد تلك القيمة }نُلاحِظ أن تعريف الدالة بالأعلى يَتضمَّن تَعْليمَتي return. ستُنفَّذ دائمًا تَعْليمَة return واحدة فقط بِغَضّ النظر عن عدد تلك التَعْليمَات المُستخدَمة ضِمْن التعريف. يُفضِّل بعض المبرمجين كتابة تَعْليمَة return وحيدة بنهاية الدالة، ويحاولوا القيام بذلك قدر الإمكان؛ خاصة وأن ذلك يُسهِل من العثور على تَعْليمَة return ضِمْن التعريف. فمثلًا، يُمكِننا إعادة كتابة الدالة nextN() -المُعرَّفة بالأعلى- مرة آخرى على النحو التالي: static int nextN(int currentN) { int answer; // متغير يحمل القيمة المعادة من الدالة if (currentN % 2 == 1) // إذا كان currentN فرديًا answer = 3*currentN+1; // أسند القيمة إلى المتغير else answer = currentN / 2; // إذا لم يكن، أسند القيمة إلى المتغير return answer; // لا تنسى أن تعيد المتغير }دَعْنَا الآن نَكتُب برنامجًا فرعيًا (subroutine) لحِسَاب قيم عناصر المُتتالية، لكن بالاعتماد على الدالة nextN هذه المرة. نظرًا لكَوْن الدالة nextN قصيرة نوعًا ما، فإن التَحْسِينات المُجراة على البرنامج الفرعي ليست ذا شأن بالموازنة مع نسخة البرنامج بالقسم ٤.٣. في المقابل، إذا كانت الدالة nextN() طويلة لكَوْنها مثلًا تَحسِب عملية معقدة، فعندها سيَكُون من المنطقي إخفاء ذلك التعقيد بداخل دالة منفصلة: static void print3NSequence(int startingValue) { int N; // أحد عناصر المتتالية int count; // عدد عناصر المتتالية N = startingValue; // أول قيمة بالمتتالية count = 1; System.out.println("The 3N+1 sequence starting from " + N); System.out.println(); System.out.println(N); while (N > 1) { // احسب قيمة عنصر المتتالية التالي باستدعاء الدالة nextN N = nextN( N ); count++; // أزد عدد عناصر المتتالية بمقدار الواحد System.out.println(N); // اطبع قيمة العنصر } System.out.println(); System.out.println("There were " + count + " terms in the sequence."); }اُنظر المثال التالي للدالة letterGrade، والتي تَستقبِل درجة عددية (numerical grade)، وتُحوِّلها إلى نظيرتها المحرفية (letter grade) بالاستعانة بمقياس درجات (grading scale) تقليدي: /** * أعد الدرجة المحرفية المناظرة للدرجة العددية الممررة للدالة كمعامل */ static char letterGrade(int numGrade) { if (numGrade >= 90) return 'A'; // 90 or above gets an A else if (numGrade >= 80) return 'B'; // 80 to 89 gets a B else if (numGrade >= 65) return 'C'; // 65 to 79 gets a C else if (numGrade >= 50) return 'D'; // 50 to 64 gets a D else return 'F'; } // نهاية الدالة letterGradeتستطيع الدوال أن تُعيد قيم من أي نوع، فمثلًا، نوع القيمة المُعادة (return type) من الدالة letterGrade() -بالأعلى- هو النوع char، أما الدالة المُعرَّفة بالمثال التالي، فإنها تُعيد قيمة (return value) من النوع boolean. اُنظر الشيفرة التالية، واحْرِصْ على قراءة التعليقات (comments)؛ لأنها تُوضِح بعض النقاط المثيرة للاهتمام: /** * تعيد هذه الدالة القيمة المنطقية true إذا كان N عدد أولي. * العدد الأولي هو عدد صحيح أكبر من العدد 1 * بشرط ألا يكون قابلًا للقسمة إلا على نفسه وعلى الواحد. * Nإذا كان لدى N أي قاسم D يتراوح بين الواحد والعدد * فسيكون لديه قاسم يتراوح بين العدد 2 والجذر التربيعي للعدد N * لذلك سوف نختبر قيم القواسم المحتملة بداية من العدد 2 * وحتى الجذر التربيعي للعدد N */ static boolean isPrime(int N) { int divisor; // عدد يحتمل أن يكون قاسما للعدد if (N <= 1) return false; // العدد الأولي لابد أن يكون أكبر من الواحد int maxToTry; // أكبر قيمة قاسم محتمل ينبغي اختبارها // ينبغي إجراء عملية التحويل بين الأنواع لأن Math.sqrt // تعيد قيمة من النوع double maxToTry = (int)Math.sqrt(N); // سنحاول قسمة N على الأعداد من 2 ووصولا الى قيمة maxToTry // إذا لم يكن N قابل للقسمة على أي عدد من تلك الأعداد // سيكون N عددا اوليا for (divisor = 2; divisor <= maxToTry; divisor++) { if ( N % divisor == 0 ) // إذا تمكن divisor من تقسم N return false; // إذًا N لا يمكن أن يكون عددًا أوليًا // لا حاجة للاستمرار } // إذا وصلنا إلى هنا، فإن N حتما هو عدد أولي // لأنه إذا لم يكن أوليًا، ستكون الدالة بالفعل قد انتهت // باستخدام تعليمة return الموجودة بالحلقة السابقة return true; // نعم N هو عدد أولي } // نهاية الدالة isPrimeنَسْتَعْرِض بالشيفرة التالية دالة آخرى، والتي تَستقبِل مُعامِل (parameter) من النوع String، وتُعيد قيمة (return value) من نفس النوع String. تَعْكِس تلك الدالة ترتيب محارف السِلسِلة النصية المُمرَّرة، وتُعيد نسخة مَعْكوسة منها، فمثلًا، النسخة المَعْكوسة من السِلسِلة النصية "Hello World" هي "dlroW olleH". يُمكِن توصيف الخوارزمية (algorithm) المطلوبة لعَكْسَ سِلسِلة نصية str على النحو التالي: ابدأ بسِلسِلة نصية فارغة (empty string)، ثم أَضِف إلى نهايتها كل محرف (character) موجود بالسِلسِلة النصية str، بحيث تبدأ بالمحرف الأخير من السِلسِلة str، وتعود للوراء حتى تَصِل إلى المحرف الأول. اُنظر الشيفرة التالية: static String reverse(String str) { String copy; // النسخة المعكوسة // الموضع الحالي من السلسلة النصية الممرة // يأخذ القيم بداية من طول السلسلة وحتى صفر int i; copy = ""; // ابدأ بسلسلة نصية فارغة for ( i = str.length() - 1; i >= 0; i-- ) { // أضف المحرف الحالي إلي نهاية النسخة المعكوسة copy = copy + str.charAt(i); } return copy; }لاحِظ أن بعض السَلاسِل النصية لا تتغَيَّر سواء قُرأت طردًا (forwards) أو عكسًا (backwards)، أي سواء قرأت من اليسار إلى اليمين أو العَكْس. يُعرَف ذلك باسم السِّياق المُتَناظِر أو القلب المستو (palindrome)، والذي يُمكِنك اختبار حُدُوثه على سِلسِلة نصية معينة word بالاستعانة بالدالة المُعرَّفة بالأعلى وباِستخدَام الاختبار if (word.equals(reverse(word))) تحديدًا. يقع كثير من المبتدئين، أثناء كتابتهم للدوال، بخطأ طباعة الإجابة بدلًا من إعادتها، وهو ما يُمثِل سوء فهم تام لماهية الدوال. إن مُهِمّة الدالة (function) ببساطة هو مجرد حِسَاب قيمة ما، ثم العودة مرة آخرى إلى سَطْر الشيفرة المسئول عن اِستدعاء الدالة، حيث ينبغي اِستخدَام القيمة المُعادة. قد يَطبَع ذلك السَطْر من الشيفرة تلك القيمة أو يُسنِدها إلى مُتَغيِّر أو يَستخدِمها ضِمْن تعبير (expression)، أو أي شيء آخر قد يَتطلَّبه البرنامج، المهم هنا هو أن تُدرِك أن أيًا كانت الطريقة التي سيُوظِّف بها المُستدعِي تلك القيمة المُعادة فهي بالنهاية أمر لا يَخُصّ الدالة. مسألة متتالية "3N+1"سنُحاوِل بالمثال الأخير من هذا القسم اِستخدَام الدالة nextN() التي عَرَفناها بالأعلى لتَعْديل البرنامج المسئول عن حِسَاب قيم عناصر المتتالية "3N+1". أُضيفَت بعض التَعْديلات الآخرى أيضًا، مثل طباعة قيم عناصر المتتالية بأعمدة، بحيث يَحتوِي كل سَطْر على ٥ عناصر فقط، مما سيُحسِن من هيئة الخَرْج الناتج عن البرنامج. تستطيع القيام بذلك ببساطة عن طريق الاحتفاظ بعدد العناصر المَطبوعة بالسَطْر الحالي، وبدء سَطْر خَرْج جديد عند وصول قيمة ذلك العدد إلى القيمة ٥. نُسِق الخَرْج بحيث تُصطَفَّ العناصر بأعمدة مُرتَّبة: input textio.TextIO; /** * برنامج يحسب قيم عناصر المتتالية 3N+1 ويطبعها. تُحدد قيمة * أول عنصر بالمتتالية من قبل المستخدم، ثم تُطبع القيم بأعمدة * بحيث يحتوي كل سطر على 5 عناصر فقط. * كما تطبع عدد عناصر المتتالية بالنهاية */ public class ThreeN2 { public static void main(String[] args) { System.out.println("This program will print out 3N+1 sequences"); System.out.println("for starting values that you specify."); System.out.println(); int K; // اقرأ مدخل المستخدم do { System.out.println("Enter a starting value;"); System.out.print("To end the program, enter 0: "); K = TextIO.getlnInt(); // اقرأ قيمة أول عنصر بالمتتالية if (K > 0) print3NSequence(K); // اعد تكرار الحلقة إذا كان k أكبر من الصفر } while (K > 0); } // نهاية main /** * يطبع print3NSequence قيم المتتالية 3N+1 إلى الخرج القياسي، * بحيث يكون أول عنصر بالمتتالية هو قيمة المعامل الممررة * كما يطبع أيضًا عدد عناصر المتتالية * قيمة المعامل startingValue الممررة ينبغي أن تكون عددًا صحيحا موجبا */ static void print3NSequence(int startingValue) { int N; // أحد عناصر المتتالية int count; // عدد العناصر حتى الآن int onLine; // عدد العناصر المطبوعة بالسطر الحالي N = startingValue; // ابدأ المتتالية بقيمة المعامل الممررة count = 1; // لدينا عنصر واحد بالمتتالية System.out.println("The 3N+1 sequence starting from " + N); System.out.println(); System.out.printf("%8d", N); onLine = 1; // هناك عدد مطبوع بالسطر الآن while (N > 1) { N = nextN(N); // احسب قيمة العنصر التالي count++; // أزد عناصر المتتالية بمقدار الواحد if (onLine == 5) { System.out.println(); // اطبع محرف العودة الى بداية السطر onLine = 0; // أعد ضبط عدد العناصر المطبوعة بالسطر } System.out.printf("%8d", N); // اطبع قيمة العنصر الحالي onLine++; // أزد قيمة عدد العنصر بالسطر بمقدار الواحد } System.out.println(); // انهي السطر الحالي من الخرج System.out.println(); // اطبع سطر فارغ System.out.println("There were " + count + " terms in the sequence."); } // نهاية print3NSequence /** * تحسب الدالة nextN قيمة عنصر المتتالية التالي وتعيده * بحيث تستقبل قيمة عنصر المتتالية الحالي كمعامل currentN */ static int nextN(int currentN) { if (currentN % 2 == 1) return 3 * currentN + 1; else return currentN / 2; } // نهاية الدالة nextN } // نهاية الصنف ThreeN2ينبغي أن تَقْرأ هذا البرنامج بتأنِّي وأن تُحاوِل اِستيعاب طريقة عَمَله. ترجمة -بتصرّف- للقسم Section 4: Return Values من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java. |
| 39,908 | المعاملات (parameters) في جافا |
إذا كان البرنامج الفرعي (subroutine) عبارة عن صندوق أسود، فإن المُعامِلات (parameter) هي ببساطة طريقة لتمرير بعض المعلومات إليه من العالم الخارجي، لذا فإنها تُعدّ جزءًا من واجهة (interface) البرنامج الفرعي، وتَسمَح لك بتَخْصيص طريقة عمل البرنامج الفرعي للتَكيُف مع موقف محدد. على سبيل المثال، إذا نظرنا لمنظم الحرارة كصندوق أسود، تتلخَّص مُهِمّته في الحفاظ على درجة حرارة معينة، فإن ذلك المنظم لديه مُعامِل، هو القرص، والذي يُستخدَم لتَخْصيص درجة الحرارة المطلوبة. سيُنفِّذ منظم الحرارة دائمًا نفس المُهِمّة، أيّ الحفاظ على درجة حرارة ثابتة، لكن قيمة درجة الحرارة تلك يُمكِن تَخْصيصها من خلال القرص. استخدام المعاملاتسنتَطَرَّق مجددًا لمسألة متتالية الأعداد "3N+1"، والتي قد تَعرَّضنا لها بالقسم الفرعي ٣.٢.٢. تُحسَّب قيم مُتتالية الأعداد "3N+1" وفقًا للقاعدة التالية: "إذا كان N عَدَدًا فرديًا، اِحسب حاصل ضربه في العَدَد ٣، ثُمَّ أَزِد قيمة حاصل الضرب بمقدار ١، أمَا إذا كان زوجيًا، اِحسب حاصل قِسمته على العَدَد ٢، بحيث تستمر بحِسَاب قيم عناصر مُتتالية الأعداد بنفس الطريقة حتى تُصبِح قيمة N مُساوِية للعَدَد ١. على سبيل المثال، إذا كانت قيمة N المبدئية تُساوِي ٣، سنَحصُل على مُتتالية الأعداد التالية: ٣، ١٠، ٥، ١٦، ٨، ٤، ٢، ١." اُكْتُب برنامجًا فرعيًا (subroutine) يَطبَع قيم مُتتالية الأعداد تلك. لاحِظ أن دور البرنامج هو طباعة متتالية الأعداد "3N+1" في العموم، أي أن القيم ذاتها التي سيَطبَعها لابُدّ وأن تعتمد على قيمة N المبدئية، ولهذا سنُمرِّر تلك القيمة كمُعامِل (parameter) للبرنامج الفرعي. يُمكِن كتابة البرنامج الفرعي كالتالي: static void print3NSequence(int startingValue) { int N; // أحد قيم عناصر المتتالية int count; // عدد عناصر المتتالية N = startingValue; // أول عنصر هو القيمة الممررة count = 1; // لدينا قيمة واحدة هي القيمة الممررة System.out.println("The 3N+1 sequence starting from " + N); System.out.println(); System.out.println(N); while (N > 1) { // إذا كان N عدد فردي if (N % 2 == 1) N = 3 * N + 1; else N = N / 2; count++; // أزد عدد عناصر المتتالية بمقدار الواحد System.out.println(N); // اطبع العنصر } System.out.println(); System.out.println("There were " + count + " terms in the sequence."); } // نهاية print3NSequenceتَتكوَّن قائمة المعاملات (parameter list) -بتعريف البرنامج الفرعي بالأعلى- من المُعامِل int startingValue، مما يَعنِي أن ذلك البرنامج الفرعي سيَستقبِل مُعامِلًا (parameter) وحيدًا من النوع العَدَدَي الصحيح int، وسيَتَمكَّنْ من اِستخدَام اسم ذلك المُعامِل بداخل المَتْن، بنفس الطريقة التي يَستخدِم بها اسم أيّ مُتَغيِّر آخر. لاحِظ أننا لم نُسنِد أي قيمة للمُعامِل أثناء عملية التعريف ذاتها، فهو يَحصُل على قيمته المبدئية من مصدر خارجي أثناء عملية الاستدعاء، لذا لابُدّ للمُستدعِي أن يُمرِّر قيمة معينة لذلك المُعامِل ضِمْن تَعْليمَة الاستدعاء (subroutine call statement)، وعندها فقط ستُسنَد إلى startingValue قبل تَّنْفيذ المَتْن. مثلًا، عندما يُنفِّذ الحاسوب تَعْليمَة استدعاء البرنامج الفرعي print3NSequence(17);، فإنه يُسنِد أولًا القيمة ١٧ إلى startingValue، ثم يُنفِّذ بَعْدها التَعْليمَات الموجودة بمَتْن ذلك البرنامج، والتي تَطبَع قيم مُتتالية الأعداد "3N+1" بداية من العدد ١٧. وبصورة أعم، إذا كان K مُتَغيِّرًا من النوع العَدََدَي الصحيح int، يُمكِن اِستخدَام التَعْليمَة print3NSequence(K); بهدف اِستدعاء البرنامج الفرعي، مما سيؤدي إلى إِسْناد قيمة المُتَغيِّر K إلى startingValue، ومِنْ ثَمَّ تَّنْفيذ مَتْن البرنامج الفرعي. يُمكِن اِستدعاء البرنامج الفرعي print3NSequence بواسطة أيّ برنامج فرعي -البرنامج main() أو غيره- مُعرَّف ضِمْن نفس الصَنْف المُتَضمِّن لتعريف البرنامج الفرعي print3NSequence. فمثلًا، يَطبَع البرنامج main() -بالمثال التالي- قيم عناصر المُتتالية "3N+1" أكثر من مرة وبقيم مبدئية مختلفة، والتي تُخصَّص من قِبَل المُستخدِم: public static void main(String[] args) { System.out.println("This program will print out 3N+1 sequences"); System.out.println("for starting values that you specify."); System.out.println(); int K; // اقرأ مدخل المستخدم do { System.out.println("Enter a starting value."); System.out.print("To end the program, enter 0: "); K = TextIO.getInt(); // اقرأ عنصر المتتالية الأول من المستخدم if (K > 0) // اطبع المتتالية print3NSequence(K); } while (K > 0); // إستمر إذا كانت قيمة k أكبر من الصفر } // نهاية mainتنبيه: لابُدّ أن يُعرَّف كُلًا من البرنامجين main و print3NSequence ضِمْن نفس الصَنْف؛ كي تتمكَّن من تَشْغِيل البرنامج بالأعلى. المعاملات الصورية (formal) والفعلية (actual)في الواقع، تُستخدَم كلمة "مُعامِل (parameter)" للإشارة إلى مفهومين، مُرتبطين نوعًا ما، لكنهما بالنهاية مختلفان. أولهما هو ذلك الذي نُشير إليه ضِمْن تعريفات البرامج الفرعية (definitions of subroutines)، مثل startingValue بالأعلى. والآخر هو ما نُشير إليه ضِمْن تَعْليمَات استدعاء البرامج الفرعية (subroutine call statements)، بحيث يُمرَّر إليها، مثل K بالتَعْليمَة print3NSequence(K);. يُطلَق على "المُعامِلات" من النوع الأول اسم المُعامِلات الصُّوريّة (formal parameters) أو المُعامِلات الوهمية (dummy parameters)، بينما يُطلَق على "المُعامِلات" من النوع الثاني اسم المُعامِلات الفعليّة (actual parameters) أو الوُسَطاء (arguments). عندما تَستدعِي برنامجًا فرعيًا، يُحصِّل الحاسوب قيم المُعامِلات الفعليّة بتَعْليمَة الاستدعاء، ثم يُسنِد تلك القيم إلى المُعامِلات الصُّوريّة المُصرَّح عنها بتعريف ذلك البرنامج الفرعي، وأخيرًا، يُنفِّذ مَتْن البرنامج الفرعي. المُعامِل الصُّوريّ (formal parameter) عبارة عن "اسم"، أو بتعبير آخر، مُعرِّف بسيط (simple identifier)، فهو في الواقع أشبه ما يَكُون بالمُتَغيِّر، وكأي مُتَغيِّر، لابُدّ أن يكون له نوع مثل int أو boolean أو String أو double[]. في المقابل، المُعامِل الفعلي (actual parameter) هو مجرد "قيمة" يُفْترَض اِسْنادها لمُتَغيِّر المُعامِل الصُّوريّ المُناظِر عند الاستدعاء الفعليّ للبرنامج الفرعي، لذا يُمكِن اِستخدَام أي تعبير (expression) طالما كان يَؤول إلى قيمة هي من نوع يمكن إِسْناده -بواسطة تَعْليمَة إِسْناد (assignment)- إلى نوع المُعامِل الصُّوريّ المُناظِر. فمثلًا، إذا كان لدينا مُعامِل صُّوريّ من النوع double، ونظرًا لإمكانية إِسْناد قيمة من النوع int إلى المُتَغيِّرات من النوع double، فإن قيمة المُعامِل الفعليّ المُمرَّرة لذلك المُعامِل الصُّوريّ يُمكِن أن تَكُون من النوع int. لاحِظ أنه من الضروري تَمرير مُعامِل فعليّ لكل مُعامِل صُّوريّ أثناء الاستدعاء الفعليّ للبرنامج الفرعي. اُنظر البرنامج الفرعي التالي كمثال: static void doTask(int N, double x, boolean test) { // تعليمات تنفيذ المهمة }تستطيع اِستدعاء البرنامج الفرعي بالأعلى باِستخدَام تَعْليمَة الاِستدعاء التالية: doTask(17, Math.sqrt(z+1), z >= 10);يُمكِن تَوصِيف ما يُنفِّذه الحاسوب -أثناء تَّنْفيذه لتَعْليمَة الاستدعاء بالأعلى- إلى تَعْليمَة الكُتلة (block statement) التالية: { int N; // خصص مساحة بالذاكرة للمعاملات الصورية double x; boolean test; // اسند القيمة 17 للمعامل الصوري الأول N = 17; // احسب قيمة التعبير واسندها للمعامل الصوري الثاني x = Math.sqrt(z+1); // احسب قيمة التعبير المنطقي واسندها للمعامل الصوري الثالث test = (z >= 10); // تعليمات تنفيذ المهمة }إلى جانب الاختلاف الجَلِيّ بحجم الشيفرة التي تَتَطلَّبها كِلا الطريقتين، هناك أيضًا اختلافات آخرى مُتعلقة بنِطاق (scope) المُتَغيِّرات، وكيفية التَعامُل في حالة وجود عدة مُتَغيِّرات أو مُعامِلات بنفس الاسم. قد تَكُون فكرة اِستخدَام مُعامِل (parameter) لتمرير بعض المعلومات إلى برنامج فرعي (subroutine) معين بديهية نوعًا ما، ولذلك فإن اِستدعاء التوابع الفرعية المُعرَّفة مُسْبَّقًا ليس بمشكلة، وهو ما يَختلِف تمامًا عن كتابة تعريف البرنامج الفرعي ذاته (subroutine definition)، والذي عادة ما يُرْبِك دَارسِي البرمجة المبتدئين، بالأخص ما يتعلق منها بكيفية عَمَل المُعامِلات. في الواقع، يَقع الكثيرون منهم في خطأ إِسْناد قيم إلى المُعامِلات الصُّوريّة (formal parameters) ببداية مَتْن البرنامج الفرعي، وهو ما يُمثِل سوء فهم تام لماهية مُعامِلات البرامج الفرعية؛ فعندما يُنفِّذ الحاسوب تَعْليمَة اِستدعاء برنامج فرعي معين، فإنه يُسنِد قيم المُعامِلات الفعليّة (actual parameters) المُمرَّرة بتَعْليمَة الاستدعاء إلى المُعامِلات الصُّوريّة، وذلك قَبْل البدء بتَّنْفيذ مَتْن البرنامج الفرعي، أيّ أنه حينما يبدأ الحاسوب بتَّنْفيذ ذلك المَتْن، تَكُون المُعامِلات الصُّوريّة قد هُيئت بالفعل بقيمها المبدئية المُمرَّرة، ولذلك لا معنى من بدء المَتْن بإِسْناد قيم إلى تلك المُعامِلات. تَذكَّر أن البرنامج الفرعي ليس مُستقلًا؛ فبالنهاية هو يُستدعَى بواسطة برنامج آخر (routine)، ولذا تَقع مسئولية تَوْفِير قيم مُعامِلاته على تَعْليمَة الاستدعاء، ومِنْ ثَمَّ على ذلك البرنامج المُستدعِي. التحميل الزائد (overloading)يَتَطلَّب استدعاء برنامج فرعي معين مَعرِفة بعض المعلومات عنه، والتي يُطلَق عليها اسم "بَصْمَة البرنامج الفرعي (subroutine's signature)". تَتَكوَّن تلك البَصْمَة من كُلًا من اسم البرنامج الفرعي، وعدد المُعامِلات الصُّوريّة (formal parameters) المُعرَّفة ضِمْن قائمة مُعامِلاته، بالإضافة إلى نوعها. على سبيل المثال، يُمكِنك كتابة بَصْمَة البرنامج الفرعي doTask المذكور بالأعلى كالتالي: doTask(int,double,boolean). لاحِظ أن بَصْمَة البرنامج الفرعي لا تَتضمَّن أسماء المُعامِلات، وهو في الواقع أمر يَسهُل تبريره؛ فليس هناك أيّ نَفْع من مَعرِفة أسماء المُعامِلات الصُّوريّة إذا كان كل غرضك هو مجرد اِستخدَام ذلك البرنامج الفرعي، ولذلك لا تُعدّ أسماء المُعامِلات جزءًا من واجهة البرنامج الفرعي (subroutine's interface). تَسمَح لغة الجافا باِستخدَام نفس الاسم لأكثر من برنامج فرعي داخل نفس الصَنْف، بشَّرْط اختلاف البَصْمَة (signatures) الخاصة بهم، فيما يُعرَف باسم "التحميل الزائد (overloading)" لاسم البرنامج الفرعي؛ حيث أصبح ذلك الاسم يَمتلك عدة معاني مختلفة. لا يَخلِط الحاسوب بين تلك البرامج الفرعية التي تَحمِل نفس الاسم، ويَستطيع تَّمييز أي منها تَرغَب باستدعائه، وذلك بالاعتماد على عدد المُعامِلات الفعليّة المُمرَّرة ضِمْن تَعْليمَة الاستدعاء، وأنواع تلك المُعامِلات. في الواقع، يَستخدِم الكائن (object) System.out التحميل الزائد (overloading)؛ حيث تَتَوفَّر له العديد من التوابع (methods) المختلفة، والتي تَحمِل جميعها الاسم println مع اختلاف بَصْمَاتها (signatures) بالتأكيد. اُنظر على سبيل المثال: println(int) println(double) println(char) println(boolean) println()يَعتمِد الحاسوب على نوع المُعامِل الفعليّ (actual parameter) المُمرَّر بتَعْليمَة الاستدعاء لمَعرِفة أي بَصْمَة من تلك البرامج الفرعية ترغب بتَّنْفيذها. فمثلًا، عند اِستخدَام التَعْليمَة System.out.println(17)، فإنه يَستدعِي البرنامج الفرعي ذو البَصْمَة println(int)، أما في حالة اِستخدَام التَعْليمَة System.out.println('A')، فإنه يَستدعِي البرنامج الفرعي ذو البَصْمَة println(char). تُعدّ جميع البرامج الفرعية بالأعلى مرتبطة نوعًا ما من الناحية الدلالية، فجميعها يقوم بالطباعة، وهو ما يُبرِّر تسميتها جميعًا بنفس الاسم، ولكن فيما يتعلق بالحاسوب، فإن طباعة عدد صحيح من النوع int تختلف تمامًا عن طباعة محرف من النوع char، والتي بدورها تختلف عن طباعة قيمة منطقية من النوع boolean، وهو ما يُبرِّر تعريف برنامج فرعي منفصل لكُلًا من تلك العمليات المختلفة. لا يُعدّ نوع القيمة المُعادة من البرنامج الفرعي (return type) جزءًا من البَصْمَة، ولهذا لا يُسمَح بتعريف برنامجين فرعيين بصَنْف معين إذا كان لهما نفس الاسم والبَصْمَة (signature)، حتى مع اختلاف نوع القيمة المُعادة منهما. فمثلًا، سيُؤدي تعريف البرنامجين الفرعيين التاليين بنفس الصَنْف إلى حُدوث خطأ في بناء الجملة (syntax error): int getln() { ... } double getln() { ... }وفي الواقع، هذا هو السبب وراء عدم تسمية جميع البرامج الفرعية المُعرَّفة ضِمْن الصَنْف TextIO، والمُستخدَمة لقراءة الأنواع المختلفة، بنفس الاسم getln()، وإنما يُستخدَم اسم مختلف لكل نوع مثل getlnInt() و getlnDouble()؛ لأن الصَنْف TextIO لا يُمكِن أن يَتَضمَّن أكثر من برنامج فرعي دون مُعامِلات، ويَحمِل نفس الاسم getln. أمثلة لبرامج فرعيةتَتكوَّن البرمجة باستخدام البرامج الفرعية من شقّين، الأول هو تصميم البرنامج (program design)، أيّ تَقسِّيم مُهِمّة البرنامج (program) إلى مَهَامّ فرعية (subtasks) صغيرة بحيث يُسنَد كل منها إلى برنامج فرعي (subroutine)، أما الشقّ الآخر، فيَتعلَّق بعملية كتابة تعريف تلك البرامج الفرعية الصغيرة، والتي هي مسئولة عن تَّنْفيذ المَهَامّ الفرعية. سنقوم الآن بكتابة عدة أمثلة لبعض من تلك البرامج الفرعية الصغيرة، بينما سنُعود إلى مناقشة موضوع تصميم البرامج (program design) بالقسم ٤.٧. أول مثال هو كتابة برنامج فرعي يَستقبِل عددًا صحيحًا موجبًا كمُعامِل، ثم يَحسِب جميع قواسم (divisors) هذا العدد، ويَطبَعها. يُكتَب أيّ برنامج فرعي بالصيغة (syntax) التالية: <modifiers> <return-type> <subroutine-name> ( <parameter-list> ) { <statements> }تَتلخَّص كتابة أي برنامج فرعي (subroutine) في مَلْئ هذه الصيغة، لذا دَعْنَا نقوم بذلك لمسألة حِسَاب القواسم. أولًا، تَنُصّ المسألة على اِستقبال البرنامج الفرعي لمُعامِل (parameter) وحيد من النوع int، كما تُبيِّن المُهِمّة المطلوب تَّنْفيذها بواسطة التَعْليمَات المَكًتوبة بمَتْن ذلك البرنامج. ثانيًا، لمّا كان حديثنا في الوقت الراهن مقصورًا على البرامج الفرعية الساكنة (static subroutines)، فإننا سنَستخدِم المُبدِّل static بالتعريف. ثالثًا، قد نُضيف مُبدِّل وصول (access modifier) بالتعريف، مثل public أو private، ولكن نظرًا لعدم النَصّ بذلك صراحة ضِمْن نَّصّ المسألة، فلن نَستخدِم أيًا منهما. رابعًا، لم تَنُصّ المسألة على وجود أي قيمة مُعادة، ولذلك سيَكُون النوع المُعاد (return type) هو void. أخيرًا، لمّا لم تُحدِّد المسألة كُلًا من اسم البرنامج الفرعي (subroutine)، وأسماء المُعامِلات الصُّوريّة (formal parameter) صراحةً، فإننا سنَضَطرّ لاختيار تلك الأسماء بأنفسنا، ولذلك سيُستخدَم N كاسم للمُعامِل، و printDivisors كاسم للبرنامج الفرعي. اُنظر تعريف البرنامج الفرعي: static void printDivisors( int N ) { <statements> }ينبغي لنا الآن كتابة مجموعة التَعْليمَات التي ستُكوِّن مَتْن البرنامج (routine body)، وهو ليس أمرًا صعبًا. تَذكَّر فقط أن المُعامِل N سيَكُون لديه قيمة بالفعل ببداية تَّنْفيذ المَتْن. يُمكِن كتابة تَوصِيف الخوارزمية كالتالي: "لكل عدد يُحتمَل أن يَكُون قَاسِمًا D، أيّ بدايةً من الواحد ووصولًا للعَدَد N، إذا تَمكَّن العدد D من تَقسِّيم العَدَد N تَقسِّيمًا مُتعادلًا، اِطبَع قيمة D." تُصبح الخوارزمية كالتالي بعد ترجمتها إلى لغة الجافا: /** * اطبع قواسم N * بفرض أن N هو عدد صحيح موجب */ static void printDivisors( int N ) { int D; System.out.println("The divisors of " + N + " are:"); for ( D = 1; D <= N; D++ ) { if ( N % D == 0 ) // إذا نجح D في تقسيم N تقسيما متعادلا System.out.println(D); } }يُمثِل التعليق (comment)، المُضاف قَبْل تعريف البرنامج الفرعي (subroutine definition)، ما يُعرَف باسم المواصفة الاصطلاحية للبرنامج (subroutine contract)، والمُكوَّنة من: بَيان هدف البرنامج الفرعي، بالإضافة إلى التَّصْريح عن أيّ اِفتراضات ينبغي مَعرفِتها قَبْل اِستخدَام ذلك البرنامج. مثلًا، في المثال بالأعلى، ذُكِر أن N ينبغي أن يَكُون عددًا صحيحًا موجبًا، ولذلك ينبغي لمُستدعِي البرنامج الفرعي التَأكُّد من تَوْفِية ذلك الفَرْض. لنَفْحَص مثالًا آخر، تَنُصّ المسألة على التالي: "اُكتب برنامجًا فرعيًا خاصًا private اسمه printRow، بحيث يَستقبِل ذلك البرنامج كُلًا من المُعامِلين: ch من النوع char، و N من النوع int. يَتلخَّص دور ذلك البرنامج بطباعة المحرف ch عدد N من المرات وذلك بسَطْر نصي مُنفصِل." بخلاف المسألة السابقة، نَصَّت هذه المسألة صراحةً على كُلًا من اسم البرنامج الفرعي وأسماء المُعامِلات، كما نَصَّت على ضرورة كَوْنه خاصًا، أيّ ينبغي اِستخدَام مُبدِّل الوصول private، ولذلك نحن مقيدين نوعًا ما فيما يتعلق بالسطر الأول من تعريف البرنامج الفرعي (subroutine definition). أخيرًا، مُهِمّة البرنامج بسيطة جدًا، ولذا يَسهُل كتابة مَتْن البرنامج الفرعي. اُنظر شيفرة البرنامج بالكامل: /** * اطبع المحرف `ch` عدد `N` من المرات وذلك بسَطْر نصي مُنفصِل * إذا كان N أقل من أو يساوي الصفر، اطبع سطرًا فارغًا */ private static void printRow( char ch, int N ) { int i; for ( i = 1; i <= N; i++ ) { System.out.print( ch ); } System.out.println(); }لَمْ تُصَرِّح المواصفة الاصطلاحية (contract) للبرنامج بالأعلى عن وجود أية افتراضات فيما يَخُص المُعامِل N، ولكنها في المقابل أوْضَحَت طريقة استجابة البرنامج الفرعي لجميع الحالات المُمكِنة، بما فيها الحالة غَيْر المُتوقَّعة من كَوْن N <= 0. لنَفْحَص مثالًا آخر، ولكن في هذه المرة، سنُوضِح كيف يُمكِن لبرنامجين فرعيين أن يتفاعلا. تحديدًا، سنَكتُب برنامجًا فرعيًا يَستقبِل مُعامِلًا من النوع String، بحيث يَطبَع كل محرف بالسِلسِلة النصية (string) المُمرَّرة ٢٥ مرة وبسَطْر مُنفصِل. ينبغي أن تَستعين بالبرنامج الفرعي printRow() الذي كتبناه للتو لطباعة ذلك الخَرْج. في هذا المثال، لمّا لم تُحدِّد المسألة كُلًا من اسم البرنامج الفرعي (subroutine)، وأسماء المُعامِلات الصُّوريّة (formal parameter) صراحةً، فإننا سنضطر لاختيار تلك الأسماء، ولذلك سيُستخدَم str كاسم للمُعامِل، وprintRowsFromString كاسم للبرنامج الفرعي. يُمكِن كتابة تَوصِيف الخوارزمية كالتالي: "لكل مَوْضِع i بالسِلسِلة النصية str، اِستدعِي البرنامج الفرعي printRow(str.charAt(i),25)؛ لطباعة سَطْر الخَرْج." تُصبِح الخوارزمية كالتالي بَعْد ترجمتها إلى لغة الجافا: /** * لكل محرف بالسلسلة النصية، اطبع سطر مكون من 25 نسخة من ذلك المحرف */ private static void printRowsFromString( String str ) { int i; for ( i = 0; i < str.length(); i++ ) { printRow( str.charAt(i), 25 ); } }نستطيع الآن استدعاء البرنامج الفرعي printRowsFromString المُعرَّف بالأعلى داخل البرنامج main() كالتالي: public static void main(String[] args) { String inputLine; // السطر النصي المدخل من قبل المستخدم System.out.print("Enter a line of text: "); inputLine = TextIO.getln(); System.out.println(); printRowsFromString( inputLine ); }لاحِظ أنه من الضروري تَضْمِين تعريف البرامج الثلاثة main() و printRowsFromString() و printRow() بنفس الصَنْف. على الرغم من كَوْن البرنامج بالأعلى عديم الفائدة نوعًا ما، لكنه على الأقل يُبيِّن طريقة اِستخدَام البرامج الفرعية. يُمكِنك الإطلاع على البرنامج كاملًا بالملف RowsOfChars.java. المصفوفات كمعاملبالإضافة إلى إمكانية تمرير المُعامِلات من الأنواع البسيطة (primitive types) إلى البرامج الفرعية، يُسمَح أيضًا بتمرير المُعامِلات من أنواع المصفوفة (array types)، ما يَعنِي تمرير مصفوفة كاملة من القيم (المُتَغيِّرات إذا شِئنا الدقة) من خلال مُعامِل وحيد. لنَكتُب، على سبيل المثال، برنامجًا فرعيًا يَستقبِل مُعامِلًا من نوع المصفوفة int[]؛ ثم يَطبَع جميع الأعداد الصحيحة الموجودة بها، بحيث يُفصَل بين كل عدد والذي يَليه فاصلة (comma)، وبحيث تُحاط جميع تلك الأعداد بزوج من الأقواس []، اُنظر تعريف البرنامج الفرعي: static void printValuesInList( int[] list ) { System.out.print('['); int i; for ( i = 0; i < list.length; i++ ) { if ( i > 0 ) System.out.print(','); System.out.print(list[i]); } System.out.println(']'); }لكي نَستدعِي البرنامج الفرعي بالأعلى، سنحتاج إلى مصفوفة فعليّة (actual). تُنشِئ الشيفرة التالية مصفوفة أعداد صحيحة (array of ints)، وتُمرِّرها كوسيط (argument) للبرنامج الفرعي: int[] numbers; numbers = new int[3]; numbers[0] = 42; numbers[1] = 17; numbers[2] = 256; printValuesInList( numbers );سنَحصُل على الخَرْج [42,17,256]. وسطاء سطر الأوامر (command-line arguments)لمّا كان البرنامج (routine) main يَستقبِل مُعامِل مصفوفة من النوع String[]، كان لزامًا على مُستدعِيه -أيّ النظام (system) في هذه الحالة- أن يُمرِّر مصفوفة سَلاسِل نصية (array of String) فعليّة (actual) كقيمة لذلك المُعامِل الصُّوريّ (formal parameter). لذا لابُدّ أن يَحصُل النظام على قيم السَلاسِل النصية بتلك المصفوفة بطريقة ما، فما الذي يعنيه ذلك؟ وكيف سيَتحصَّل النظام على تلك القيم؟ في الواقع، تَتَكوّن تلك القيم من وُسَطاء سطر الأوامر (command-line arguments)[*] المُمرَّرين إلى الأمر المُستخدَم لتَشْغِيل البرنامج، وبالتالي يُسنِد النظام قيم هؤلاء الوُسَطاء إلى مصفوفة سَلاسِل نصية (array of strings)، ثم يُمرِّرها إلى البرنامج main(). [*] يستطيع المُستخدِم عمومًا تَشْغِيل أحد البرامج من خلال كتابة أمر (command) معين بواجهة سَطْر الأوامر (command-line interface). يَتَكوّن الأمر عمومًا من اسم البرنامج المطلوب تَشْغِيله، ولكن يُمكِن أيضًا كتابة مُدْخَلات إضافية ضِمْن الأمر. تُسمَى تلك المُدْخَلات الإضافية باسم وُسَطاء سَطْر الأوامر (command-line arguments). فمثلًا، إذا كان اسم برنامج معين هو myProg، تستطيع تَشْغِيله باِستخدَام الأمر التالي: java myProgولكن، في هذه الحالة، لا يوجد أي وُسَطاء (arguments)، في المقابل، تستطيع تمرير وسيط واحد أو أكثر باِستخدَام الأمر التالي: java myProg one two threeفي المثال بالأعلى، تُمثِل السَلاسِل النصية "one"، و "two"، و "three" قيم الوُسَطاء، ولذلك سيُسنِد النظام هذه السَلاسِل النصية إلى مصفوفة من النوع String[]، ثم يُمرِّرها كمُعامِل إلى البرنامج main(). على سبيل المثال، تَطبَع الشيفرة التالية قيمة أي وسيط (argument) أَدْخَله المُستخدِم: public class CLDemo { public static void main(String[] args) { System.out.println("You entered " + args.length + " command-line arguments"); if (args.length > 0) { System.out.println("They were:"); int i; for ( i = 0; i < args.length; i++ ) System.out.println(" " + args[i]); } } // نهاية main } // نهاية الصنف CLDemoإذا لم يُخصِّص المُستخدِم أي وسيط (arguments) بأمر تَشْغِيل البرنامج، فإن المُعامِل args سيَكُون عبارة عن مصفوفة فارغة، طولها (length) يُساوي الصفر. عمليًا، يُستخدَم وُسَطاء سَطْر الأوامر عادةً بهدف تمرير أسماء بعض الملفات إلى البرنامج. فمثلًا، يَنسَخ البرنامج التالي محتويات ملف نصي معين إلى ملف نصي آخر، ولذلك فإنه يَستخدِم الصَنْف TextIO ضِمْن حَلْقة تَكْرار، بحيث يَقْرأ سَطْرًا واحدًا من الملف الأصلي في كل مرة، ثم يَنسَخُه إلى الملف الآخر. يَستمِر في القيام بذلك حتى يَصِل إلى نهاية الملف، والتي يُستدَلّ عليها من القيمة المنطقية المُعادة من الدالة (function) TextIO.eof(). input textio.TextIO; /** * يتطلب ذلك الأمر وسيطين، هما أسماء الملفات * الأول ينبغي أن يكون اسم ملف موجود * والثاني ينبغي أن يكون اسم الملف الذي سينسخ البرنامج إليه بيانات الملف الأول * سيقوم البرنامج بنسخ محتويات الملف الأول إلى الملف الثاني * تحذير: إذا كان الملف الثاني موجود عند تشغيل البرنامج، ستتم الكتابة على البيانات السابقة * يعمل هذا البرنامج مع الملفات النصية فقط */ public class CopyTextFile { public static void main( String[] args ) { if (args.length < 2 ) { System.out.println("Two command-line arguments are required!"); System.exit(1); } TextIO.readFile( args[0] ); // افتح الملف الأصلي بهدف القراءة TextIO.writeFile( args[1] ); // افتح الملف المراد النسخ إليه int lineCount; // عدد الأسطر المنسوخة lineCount = 0; while ( TextIO.eof() == false ) { // اقرأ سطر من الملف الأصلي وانسخه للملف الآخر String line; line = TextIO.getln(); TextIO.putln(line); lineCount++; } System.out.printf( "%d lines copied from %s to %s%n", lineCount, args[0], args[1] ); } }تُنفَّذ معظم البرامج حاليًا من خلال واجهة مُستخدِم رسومية (GUI environment)، ولذلك لم يَعُدْ وُسَطاء سَطْر الأمر (command-line arguments) بنفس ذات الأهمية في الوقت الحالي. مع ذلك ما يزال البرنامج بالأعلى مثالًا جيدًا لبيان كيفية اِستخدَام مُعامِلات المصفوفة (array parameters) على الأقل. التبليغ عن الاعتراضات (exceptions)ذَكَرَنا، منذ قليل، مصطلح المواصفة الاصطلاحية للبرنامج (subroutine contract)، والذي يُوضِح وظيفة البرنامج الفرعي في حالة تمرير قيم صالحة -وفقًا لما هو مُوضَّح بنفس المواصفة الاصطلاحية- لجميع المُعامِلات الخاصة به. تَتَبقَّى الحاجة إلى مَعرِفة كيفية تَصرُّف البرنامج الفرعي في حالة تمرير قيم غَيْر صالحة للمُعامِلات. تَعرَّضنا بالفعل لعدة أمثلة بالقسم ٣.٧، والتي تُبلِّغ فيها البرامج الفرعية عن اِعتراضات (throwing exceptions) في حالة تمرير قيم غَيْر صالحة، فمثلًا، تَنُصّ المواصفة الاصطلاحية للبرنامج الفرعي المَبنِى مُسْبَّقًا (built-in) Double.parseDouble على ضرورة أن يَكُون المُعامِل المُمرَّر إليها عبارة عن تمثيل نصي (string representation) لعَدََدَ من النوع double. إذا كانت قيمة المُعامِل المُمرَّرة مُتماشية مع ذلك الشَّرْط، سيُحوِّل البرنامج الفرعي السِلسِلة النصية (string) المُمرَّرة إلى قيمتها العَدَدية المكافئة، أما إذا لم تَكُن مُتماشية معها، سيُبلِّغ البرنامج الفرعي عن اِعتراض (exception) من النوع NumberFormatException. تُبلِّغ الكثير من البرامج الفرعية عن اعتراض من النوع IllegalArgumentExceptions في حالة تمرير قيم غَيْر صالحة لمُعامِلاتها، وهو ما قد تَرغب في اتباعه بالبرامج الفرعية الخاصة بك. أيّ اعتراض (exception) هو بالنهاية عبارة عن كائن (object)، ستَضطرّ إلى إنشائه قَبْل التَبْليغ عنه باِستخدَام التَعْليمَة throw. سنتناول كيفية القيام بذلك تفصيليًا بالفصل الخامس، ولكن، إلى ذلك الحين، تستطيع اِستخدَام الصيغة (syntax) التالية لتَعْليمَة throw؛ للتَبْليغ عن اِعتراض من النوع IllegalArgumentException تحديدًا: throw new IllegalArgumentException( <error-message> );تَتَكوّن رسالة الخطأ بالأعلى من سِلسِلة نصية (string) تَشرَح الخطأ المُكْتشَف بينما تُستخدَم new لإنشاء كائن الاعتراض (exception object). كل ما عليك القيام به هو فَحْص قيم المُعامِلات المُمرَّرة؛ لمَعرِفة ما إذا كانت صالحة أم لا، ثم التَبْليغ عن اعتراض باِستخدَام التَعْليمَة بالأعلى إذا لم تَكُن صالحة. على سبيل المثال، لابُدّ أن تَكُون قيمة المُعامِل المُمرَّرة إلى البرنامج الفرعي print3NSequence -المُعرَّف ببداية هذا القسم- عددًا صحيحًا موجبًا، لذلك يُمكِننا، في حالة انتهاك هذا الشَّرْط، تَعْدِيل تعريف البرنامج الفرعي (subroutine definition) بالصورة التالية؛ وذلك لكي نَتَمَكَّن من التَبْليغ عن الاِعتراض: static void print3NSequence(int startingValue) { if (startingValue <= 0) // إذا حدث انتهاك للمواصفة الاصطلاحية throw new IllegalArgumentException( "Starting value must be positive." ); // بقية البرنامج الفرعي مثلما بالأعلىإذا كانت القيمة المُمرَّرة إلى المُعامِل startingValue غَيْر صالحة، فستُنهِي تَعْليمَة throw بالأعلى البرنامج الفرعي فورًا بدون تَّنْفيذ بقية المَتْن، كما قد ينهار (crash) البرنامج بالكامل في حالة عدم اِلتقاط ذلك الاِعتراض (catching exception) بمكان آخر ضِمْن البرنامج باِستخدَام التَعْليمَة try..catch بحيث يُستدعَى البرنامج الفرعي print3NSequence داخل الجزء try، كما ناقشنا بالقسم ٣.٧. المتغيرات العامة (global) والمحلية (local)كملاحظة أخيرة بهذا القسم، يُمكِننا القول أننا نستطيع الآن اِستخدَام ثلاثة أنواع مختلفة من المُتَغيِّرات داخل أيّ برنامج فرعي، هي كالتالي: أولًا، المُتَغيِّرات المحليّة (local variables) والتي يُصرَّح عنها داخل البرنامج الفرعي. ثانيًا، أسماء المُعامِلات الصُّوريّة (formal parameter) ضِمْن تعريف البرنامج الفرعي. ثالثًا، المُتَغيِّرات الأعضاء الساكنة (static member variables)، والتي يُصرَّح عنها خارج البرنامج الفرعي. تُعدّ المُتَغيِّرات المحليّة (local variables) من صميم أعمال البرنامج الفرعي الداخلية، وليس لها أيّ اتصال مع ما هو خارج البرنامج الفرعي. تُمرِّر المُعامِلات الصُّوريّة (formal parameter) قيم خارجية إلى البرنامج الفرعي أثناء الاستدعاء، ولكنها مع ذلك تَتَصرَّف بطريقة مشابهة للمُتَغيِّرات المحليّة بمجرد بدء تَّنْفيذ البرنامج الفرعي. يَعنِي ذلك أنه في حالة حُدوث تَغْيِير بقيمة أحد المُعاملات الصُّوريّة (من النوع الأوَّليّ [primitive type] تحديدًا) أثناء تَّنْفيذ البرنامج الفرعي، فإن ذلك التَغْيِير لا يُؤثِر نهائيًا على بقية البرنامج. لاحِظ أنه في حالة كان المُعامِل الصُّوريّ عبارة عن مصفوفة أو كائن (object)، فإن الأمور تُصبِح أكثر تعقيدًا كما سنرى لاحقًا. أخيرًا، بخلاف المُتَغيِّرات المحليّة (local variables) المُعرَّفة داخل البرامج الفرعية، فإن المُتَغيِّرات العامة (global variables) تُعرَّف خارج أي برنامج فرعي، ولذا فإنها تُعدّ مستقلة عنها، كما أنها قابلة للاِستخدَام ضِمْن أجزاء عديدة من البرنامج (program). في الواقع، يُمكِن اِستخدَام المُتَغيِّر العام (global variable) بأيّ مكان داخل الصَنْف المُُعرَّف بداخله، بل يُمكِن استخدامه بأي صنف آخر طالما لم يُصرَّح عنه باِستخدَام المُبدِّل private. لاحظ أنه إذا حَدَثَ تغيير على مُتَغيِّر عام (global variable) داخل برنامج فرعي معين، فإن تأثير ذلك التغيير يمتد إلى ما هو أبعد من مجرد ذلك البرنامج الفرعي الذي أَحَدَث التغيير، وهو ما قد تعرضت له بالفعل بالمثال الأخير بالقسم السابق؛ حيث عَدَّلت إحدى البرامج الفرعية قيم المُتَغيِّرات العامة gamesPlayed و gamesWon، ثم طُبعت بواسطة برنامج فرعي آخر هو main(). إذا اِستخدَمت مُتَغيِّرًا عامًا (global variable) ضِمْن برنامج فرعي معين، فإن ذلك البرنامج الفرعي يُصبِح قادرًا على الاتصال مع بقية البرنامج (program) فيما يُشبه الاتصال السري (back-door communication)، ولهذا نستطيع القول أن المُتَغيِّرات العامة قد أصبحت بذلك جزءًا من واجهة البرنامج الفرعي (subroutine's interface). يُعدّ الاتصال السري عمومًا أقل وضوحًا وصراحةً من ذلك المُقام باستخدام المُعامِلات، ولهذا فإنه قد يَتَسبَّب بكسر القاعدة التي تَنُصّ على ضرورة كَوْن واجهة (interface) "الصندوق الاسود (black box)" صريحة وسهلة الفهم. قد يَدْفَعك ذلك إلى الظّنّ بأن اِستخدَام المُتَغيِّرات العامة داخل البرامج الفرعية أمر سيئ عمومًا، ولكن هذا غَيْر صحيح، فهناك على الأقل سببًا واحدًا جيدًا قد يَدْفَعك للقيام بذلك: مثلًا إذا فكرت بالصَنْف (class) بكَوْنه نوعًا من "الصندوق الاسود (black box)"، يُصبِح من المعقول تمامًا السماح للبرامج الفرعية الواقعة ضِمْن ذلك الصندوق بالاتصال سريًا مع بعضها البعض (back-door communication) خاصة إذا ساهم ذلك بتبسيط الصَنْف ككل من الخارج. لهذا يَنصَح الكاتب بعدم اتخاذ أيّ موقف عدائي مُطْلَق من اِستخدَام المُتَغيِّرات العامة (global variables) داخل البرامج الفرعية، فقط عليك أن تُفكِر مَلِيًّا قَبْل اِستخدَام مُتَغيِّر عام (global variable) داخل برنامج فرعي للتَأكُّد مما إذا كان ذلك ضروريًا. ترجمة -بتصرّف- للقسم Section 3: Parameters من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java. |
| 39,907 | البرامج الفرعية والمتغيرات الساكنة في جافا |
تَسمَح بعض اللغات البرمجية -بل غالبيتها في الواقع- بتعريف البرامج الفرعية (subroutines) بصورة مستقلة خارج أي صَنْف، لكن تختلف لغة الجافا عنهم في تلك النقطة، حيث لابُدّ أن يُعرَّف أيّ برنامج فرعي (subroutine) بلغة الجافا ضِمْن صَنْف (class). الهدف من الصَنْف عمومًا هو تجميع البرامج الفرعية والمُتَغيِّرات المُرتبطة معًا ضِمْن وحدة واحدة. نظرًا لأن البرامج المكتوبة بلغة الجافا عادة ما تَستخدِم عددًا ضخمًا من البرامج الفرعية المَكْتوبة بواسطة مبرمجين مختلفين، فإن احتمالية الخلط بين أسمائها يَكُون كبيرًا، لذا حَرَص مُصمِّمي الجافا على وَضع تقييد حازم على الطرق التي يُمكِن بها تسمية الأشياء، ومن هنا كانت ضرورة تعريف البرامج الفرعية وتجميعها ضِمْن أصناف (classes) لها اسم وبالتبعية تجميع تلك الأصناف ضِمْن حزم (packages) لها اسم أيضًا كما سنرى لاحقًا. تُميز لغة الجافا بشكل واضح بين البرامج الفرعية الساكنة (static) وغَيْر الساكنة (non-static). يُمكِن لأي صَنْف أن يَتضمَّن كِلا النوعين، ولكن كيفية اِستخدَامهما مختلفة تمامًا. تُعدّ البرامج الفرعية الساكنة (static subroutine) عمومًا أسهل في الفهم، فببساطة أي برنامج فرعي ساكن هو عضو (member) ينتمي للصنف ذاته المُعرَّف بداخله. في المقابل، تعريف البرامج الفرعية غَيْر الساكنة (non-static subroutine) موجود فقط ضِمْن الصَنْف حتى تَتَمكَّن كائنات (objects) ذلك الصنف -عند إنشائها- من اِستخدَام تلك البرامج الفرعية، وتُصبِح عندها تلك البرامج أعضاء بتلك الكائنات (objects). يُمكِن تطبيق ذلك الاختلاف على المُتَغيِّرات (variables) الساكنة وغَيْر الساكنة بنفس الكيفية، بل وعلى أي شيء آخر قد يَقع ضمن تعريف الأصناف (class definition). سنتناول في هذا الفصل كُلًا من البرامج الفرعية الساكنة والمُتَغيِّرات الساكنة فقط، وسننتقل بالفصل التالي إلى البرمجة كائنية التوجه (object-oriented programming) وعندها سنستطيع مناقشة الأعضاء غَيْر الساكنة. عادة ما يُطلَق مصطلح "التابع (method)" على البرامج الفرعية (subroutines) المُعرَّفة ضِمْن صَنْف، وهو الاسم الذي يُفضِّله غالبية مبرمجي الجافا مع أن المصطلحان عمومًا مترادفان خاصة فيما يَتعلَّق بلغة الجافا. ستَجِدْ أيضًا البعض يَستخدِم مصطلحات آخرى للإشارة إلى البرامج الفرعية مثل "الإجراء (procedure)" أو "الدالة (function)". يُفضِّل الكاتب الاستمرار باِستخدَام المصطلح الأعم "البرنامج الفرعي (subroutine)" ضِمْن هذا الفصل للإشارة إلى البرامج الفرعية الساكنة على الأقل، ولكنه سيبدأ في اِستخدَام مصطلح "التابع (method)" من حين لآخر، كما أنه سيَستخدِم مصطلح "الدالة (function)" فقط للإشارة إلى البرامج الفرعية التي تَحسِب قيمة وتُعيدها مع أن بعض اللغات البرمجية الآخرى تَستخدِم ذلك المصطلح للإشارة إلى البرامج الفرعية (subroutines) بمفهومها الأعم. تعريف البرامج الفرعيةينبغي أن يُعرَّف (define) أيّ برنامج فرعي (subroutine)، تَنوِي اِستخدَامه، بمكان ما بالبرنامج، بحيث يَتضمَّن ذلك التعريف (subroutine definition) كُلًا من الآتي: اسم البرنامج الفرعي (subroutine name)، والمعلومات اللازمة لاستدعائه (subroutine call)، وأخيرًا، الشيفرة الفعليّة المطلوب تَّنْفيذها مع كل عملية استدعاء. يُكتَب تعريف البرنامج الفرعي (subroutine definition) بالجافا بالصياغة التالية: <modifiers> <return-type> <subroutine-name> ( <parameter-list> ) { <statements> }قد تكون الصيغة بالأعلى مألوفة بالنسبة لك؛ فقد تَعرَّضنا بالفعل خلال الفصول السابقة لبعض البرامج الفرعية، مثل البرنامج main() المُعرَّف بأيّ برنامج، والبرنامج drawFrame() ببرامج التحريكة (animation) بالقسم ٣.٩. ومع ذلك، لاِستيعاب ما تَعنيه تلك الصيغة تفصيليًا، سنحتاج إلى مُناقشتها باستفاضة، وهو ما سنفعله بغالبية هذا الفصل. أولًا، تُكوِّن التَعْليمَات -الواقعة بين القوسين المعقوصين {}- مَتْن البرنامج الفرعي (subroutine body)، وتُمثِل الجزء التَّنْفيذي (implementation) أو الداخلي "للصندوق الأسود (black box)" كما ذَكَرنا بالقسم السابق. يُنفِّذ الحاسوب تلك التَعْليمَات عند إجراء عملية اِستدعاء لذلك البرنامج (أو التابع [method]). لاحظ أن تلك التَعْليمَات قد تَتكوَّن من أي تَعْليمَة قد تَعرَّضنا لها خلال الفصلين الثاني والثالث. ثانيًا، تُكتَب المُبدِّلات ببداية التعريف، وهي عبارة عن كلمات تُستخدَم لضَبْط بعض خاصيات البرنامج الفرعي، مثل ما إذا كان ساكنًا (static) أم غير ساكن (non-static). تُوفِّر لغة الجافا عمومًا ستة مُبدِّلات (modifiers) يُمكنك اِستخدَامها، ولقد تَعرَّضنا لاثنين منها فقط حتى الآن، وهي المُبدِّل الساكن static، والمُبدِّل العام public. ثالثًا، يُستخدَم النوع المُعاد لتَخْصِيص نوع القيمة المُعادة من البرنامج الفرعي. تحديدًا، إذا كان البرنامج الفرعي بالأساس هو عبارة عن دالة (function) تَحسِب قيمة معينة، عندها قد تَستخدِم أي نوع، مثل String أو int أو حتى أنواع المصفوفة مثل double[]. أما إذا كان البرنامج الفرعي ليس بدالة، أيّ لا يُعيد قيمة، فعندها تُستخدَم القيمة الخاصة void، والتي تُشير إلى عدم وجود قيمة مُعادة، أيّ أنها إِما أن تَكُون فارغة (empty) أو غَيْر موجودة. سنتناول الدوال (functions)، والأنواع المُعادة (return types) تفصيليًا بالقسم ٤.٤. وأخيرًا، قائمة المُعامِلات الخاصة بالتابع (method). تُعدّ المُعامِلات جزءًا من وَاجهة البرنامج الفرعي (subroutine interface)، وتُمثِل المعلومات المُمرَّرة (passed) إليه من الخارج، والتي قد يَستخدِمها لحِسَاب بعض العمليات الداخلية الخاصة به. فمثلًا، بفَرْض وجود الصَنْف Television، والذي يَتضمَّن التابع changeChannel(). في تلك الحالة، يُفْترَض أن يُطرَح سؤالًا تلقائيًا عن رقم القناة التي تُريد من البرنامج الفرعي الانتقال إليها، وهنا يأتي دور المُعامِلات (parameter)؛ حيث يُمكِن اِستخدَام مُعامِل للإجابة على مثل هذا السؤال، ولمّا كان رقم القناة هو عدد صحيح، فسيَكُون نوع ذلك المُعامِل (parameter type) هو int، ولهذا يُمكِنك التَّصْريح (declaration) عن التابع changeChannel() كالتالي: public void changeChannel(int channelNum) { ... }يُشير التَّصْريح بالأعلى إلى أن التابع changeChannel() لديه مُعامِل يَحمِل اسم channelNum من النوع العددي الصحيح int. لاحظ أن قيمة المُعامِل channelNum غير مُتوفِّرة حتى الآن، وإنما تَتوفَّر عند عملية الاستدعاء الفعليّ للتابع الفرعي، على سبيل المثال: changeChannel(17);. قد تتكوَّن قائمة المُعامِلات (parameter list) -بتعريف البرنامج الفرعي- من تَّصْريح عن مُعامِل (parameter declaration) واحد أو أكثر، وذلك على الصورة ، بحيث يَتكوَّن كل تَّصْريح من مُعامِل وحيد، كما يُفصَل بين كل تَّصْريح والذي يليه باستخدام فاصلة (comma). على سبيل المثال، في حالة أردت التَّصْريح عن مُعامِلين من النوع double، فستضطر إلى كتابة double x, double y وليس double x, y. لاحِظ أن قائمة المُعامِلات قد تَكُون فارغة أيضًا. سنتناول المُعامِلات (parameters) تفصيليًا بالقسم التالي. ها هي التعريفات الخاصة ببعض البرامج الفرعية (subroutine definitions)، ولكن بدون الجزء التَّنْفيذي (implementation) منها، أي بدون التَعْليمَات الفعليّة التي تُعرِّف ما تُنفِّذه تلك البرامج الفرعية: public static void playGame() { // المبدلات هي public و static // النوع المعاد هو void // اسم البرنامج الفرعي هو playGame // قائمة المعاملات فارغةً } int getNextN(int N) { // لا يوجد مبدلات // النوع المعاد هو int // اسم البرنامج الفرعي هو getNextN // قائمة المعاملات تتضمن معامل اسمه N من النوع int } static boolean lessThan(double x, double y) { // المبدلات هي static // النوع المعاد هو boolean // اسم البرنامج الفرعي هو lessThan // قائمة المعاملات تتضمن معاملين x و y كلاهما من النوع double }بالمثال الثاني بالأعلى، لمّا كان تعريف التابع getNextN لا يَتضمَّن المُبدل static، فإنه يُعدّ بصورة افتراضية تابعًا غَيْر ساكن (non-static method)، ولذلك لن نَفْحصه بهذا الفصل! اُستخدِم المُبدل public بالمثال الأول، والذي يُشير إلى إِمكانية استدعاء التابع (method) من أي مكان بالبرنامج، حتى من خارج الصَنْف (class) الذي عُرِّف فيه هذا التابع. في المقابل، يَتوفَّر المُبدل private، والذي يُشير إلى إمكانية استدعاء التابع من داخل نفس الصَنْف فقط. يُطلَق على كُلًا من المُبدلين public و private مُحدِّدات الوصول أو مُبدِّلات الوصول (access specifiers/access modifier). يَتوفَّر مُبدل وصول (access modifier) أخير، هو protected، والذي ستتضح أهميته عندما ننتقل إلى البرمجة كائنية التَوجه (object-oriented programming) بالفصل الخامس. في حالة عدم تخصيص مُحدِّد وصول لتابع معين، فإنه، وبصورة افتراضية، يُصبِح قابلًا للاستدعاء من أيّ مكان بالحزمة (package) التي تَتضمَّن صَنْفه، ولكن ليس من خارج تلك الحزمة. سنُناقش الحزم (packages) خلال هذا الفصل، تحديدًا بالقسم ٤.٦. يَتَّبِع البرنامج main()، المُعرَّف بأي برنامج، نفس قواعد الصيغة (syntax rules) المُعتادة لأي برنامج فرعي: public static void main(String[] args) { ... }بفَحْص التعريف بالأعلى، تَجد أن المُبدلات المُستخدَمة هي public و static، أما النوع المُعاد فهو void، أما اسم البرنامج الفرعي فهو main، وأخيرًا، قائمة المعاملات هي String[] args، أيّ أن نوع المُعامِل المُمرَّر هو نوع المصفوفة String[]. إذا كنت قد قرأت الفصول السابقة، فلديك بالفعل الخبرة الكافية لكتابة الجزء التَّنْفيذي من البرنامج الفرعي (implementation of a subroutine). في هذا الفصل، سنتعلَّم كتابة التَعرِيف بالكامل بما في ذلك جزء الواجهة (interface). استدعاء البرامج الفرعيةيُعدّ تعريف البرنامج الفرعي (subroutine definition) بمثابة إعلام للحاسوب بوجود ذلك البرنامج الفرعي وبالوظيفة التي يُؤديها، لكن يُرجئ التَّنْفيذ الفعليّ للبرنامج الفرعي إلى حين استدعائه (call). يَنطبِق ذلك حتى على البرنامج (routine) main()، والذي يَستدعيه النظام (system) -لا أنت- عند تَّنْفيذه للبرنامج (program) ككل. تُستخدَم، مثلًا، تَعْليمَة استدعاء البرنامج الفرعي (subroutine call) التالية؛ لاستدعاء التابع playGame() المذكور بالأعلى: playGame();يُمكن عمومًا كتابة تَعْليمَة الاستدعاء بالأعلى بأيّ مكان داخل نفس الصَنْف (class) الذي عَرَّف التابع playGame()، سواء كان ذلك بالتابع main()، أو بأيّ برنامج فرعي آخر. علاوة على ذلك، لمّا كان التابع playGame() مُعرَّف باِستخدَام المُبدل public، وهو ما يَعنِي كَوْنه تابعًا عامًا، فإنه من الممكن لأيّ صَنْف آخر استدعائه أيضًا، ولكن ينبغي أن تُعلِم الحاسوب، في تلك الحالة، باسم التابع كاملًا أثناء الاستدعاء، أيّ بذكر الصَنْف الذي ينتمي إليه ذلك التابع. ولأن التابع playGame() مُعرَّف باِستخدَام المُبدل static، وهو ما يَعنِي كَوْنه تابعًا ساكنًا، فإن اسمه الكامل يَتضمَّن اسم الصنف ذاته المُعرَّف بداخله. على سبيل المثال، إذا كان التابع playGame() مُعرَّف بالصَنْف Poker، تُستخدَم التَعْليمَة التالية لاستدعائه من خارج هذا الصَنْف: Poker.playGame();يُعلِم اسم الصَنْف -بالأعلى- الحاسوب بأيّ صَنْف ينبغي له أن يَجِد التابع. بالإضافة إلى ذلك، يُساعدنا وجود اسم الصَنْف أثناء الاستدعاء على التمييز بين التابع Poker.playGame() وأي توابع اخرى لها نفس الاسم ومُعرَّفة بأصناف آخرى، مثل Roulette.playGame() أو Blackjack.playGame(). تُكتَب عمومًا تَعْليمَة استدعاء أي برنامج فرعي ساكن static (static subroutine call) بالجافا بالصيغة التالية إذا كان البرنامج الفرعي المُستدعَى مُعرَّفًا بنفس الصَنْف (class): <subroutine-name>(parameters);أو كالتالي إذا كان البرنامج الفرعي مُعرَّفًا بصَنْف آخر: <class-name>.<subroutine-name>(parameters);يَختلف ذلك عن التوابع غَيْر الساكنة (non-static methods) -سنتناولها لاحقًا-، والتي تنتمي إلى كائنات (objects) وليس أصناف (classes)، ولهذا يُستدعَى ذلك النوع من التوابع من خلال الكائنات (objects) ذاتها لا من خلال أسماء الأصناف. لاحظ أنه في حين يُمكِن لقائمة المُعامِلات (parameter list) أن تَكُون فارغة (empty)، كما هو الحال بالمثال playGame()، فإن كتابة الأقواس (parentheses) بتَعْليمَة الاستدعاء ما تزال ضرورية حتى مع كَوْن ما بينها فارغًا. أما في حالة تخصيص مُعامِل (parameter) أو أكثر بقائمة المُعامِلات (parameter list) بالتعريف الخاص ببرنامج فرعي ما (subroutine definition)، فينبغي عمومًا أن يَتطابَق عدد المُعامِلات المُمرَّرة أثناء استدعاء ذلك البرنامج الفرعي مع العَدَدَ المُخصَّص بذلك التعريف، كما لابُدّ بطبيعة الحال أن تَتطابَق أنواع تلك المُعامِلات المُمرَّرة بتَعْليمَة الاستدعاء مع نوعها المُناظِر بنفس ذلك التعريف. البرامج الفرعية بالبرامجسنُعطي الآن مثالًا عما قد يبدو عليه البرنامج (program) عند تَضمُّنه لبرنامج فرعي آخر غَيْر البرنامج main(). سنَكتُب تحديدًا برنامجًا للعبة تخمين، يَختار فيه الحاسوب عددًا عشوائيًا بين العددين ١ و ١٠٠، ثُمَّ يُحاول المُستخدِم تخمين ذلك العدد، ليُخبره الحاسوب بَعْدها عما إذا كان تخمينه أكبر أو أقل أو يُساوِي العَدَد الصحيح، وبحيث يَفوز المُستخدِم بالمباراة إذا تَمكَّن من تخمين العدد الصحيح خلال ٦ تخمينات كحد أقصى. أخيرًا، يَستطيع المُستخدِم اختيار الاستمرار بلعب مباراة إضافية بنهاية كل مباراة. لمّا كانت كل مباراة هي بالنهاية مُهِمّة مُترابطة مُفردة، كان بديهيًا كتابة برنامج فرعي playGame() بهدف لعب مباراة تخمين واحدة مع المُستخدِم. سيَستخدِم البرنامج main() حَلْقة تَكْرار (loop)، والتي ستَستدعِي ذلك البرنامج الفرعي مع كل مرة يختار فيها المُستخدِم الاستمرار بلعب مباراة إضافية. نحتاج الآن إلى تصميم البرنامج الفرعي playGame() وكتابته، وفي الواقع، يُصمَّم أي برنامج فرعي بنفس طريقة تَصْميم البرنامج main()، أيّ نبدأ بكتابة توصيف للخوارزمية (algorithm)، ثم نُطبِق التَصْميم المُتدرج (stepwise refinement). اُنظر الخوارزمية التالية لبرنامج لعبة التخمين مكتوبًا بالشيفرة الوهمية: // اختر عددًا عشوائيًا Pick a random number // طالما لم تنته المباراة while the game is not over: // اقرأ تخمين المستخدم Get the user's guess // اخبر المستخدم عما إذا كان تخمينه صحيحًا أم أكبر أم أقل Tell the user whether the guess is high, low, or correct.يُعدّ الاختبار "طالما لم تنته المباراة" معقدًا نوعًا ما؛ وذلك لأن المباراة قد تنتهي لسببين: إما لأن تخمين المُستخدِم كان صحيحًا أو لوصوله للحد الأقصى من عدد التخمينات المُمكنة، أيّ ٦. أحد أسهل الطرائق للقيام بذلك هو اِستخدَام حَلْقة تَكْرار لا نهائية while (true) تحتوي على تَعْليمَة break؛ بهدف إِنهاء الحَلْقة في الوقت المناسب. لاحِظ أننا سنحتاج إلى الاحتفاظ بعَدَد تخمينات المُستخدِم؛ حتى نَتمكَّن من إِنهاء المباراة في حالة وصول المُستخدِم للحد الأقصى من التخمينات. تُصبِح الخوارزمية كالتالي بعد إِجراء التعديلات: // أسند قيمة عشوائية بين 1 و 100 إلى المتغير computersNumber Let computersNumber be a random number between 1 and 100 // اضبط قيمة العداد المستخدم لعدّ عدد تخمينات المستخدم Let guessCount = 0 // استمر بتنفيذ الآتي while (true): // اقرأ تخمين المستخدم Get the user's guess // أزد قيمة العداد بمقدار الواحد Count the guess by adding 1 to guess count // إذا كان تخمين المستخدم صحيحًا if the user's guess equals computersNumber: // بلغ المستخدم بفوزه بالمباراة Tell the user he won // اخرج من الحلقة break out of the loop // إذا وصل العداد للحد الأقصى 6 if the number of guesses is 6: // بلغ المستخدم بخسارته للمباراة Tell the user he lost // اخرج من الحلقة break out of the loop // إذا كان كان تخمين المستخدم أقل من العدد if the user's guess is less than computersNumber: // بلغ المستخدم بكَوْن التخمين أقل من العدد Tell the user the guess was low // إذا كان كان تخمين المستخدم أعلى من العدد else if the user's guess is higher than computersNumber: // بلغ المستخدم بكَوْن التخمين أكبر من العدد Tell the user the guess was highيُستخدَم التعبير (int)(100 * Math.random()) + 1 لاختيار عدد عشوائي يقع بين العددين ١ و ١٠٠. اُنظر الشيفرة التالية بلغة الجافا، والتي تَتضمَّن تعريف البرنامج playGame() بعد التَّصْريح عن المُتَغيِّرات (variable declarations): static void playGame() { int computersNumber; // العدد العشوائي int usersGuess; // إحدى تخمينات المستخدم int guessCount; // عدد تخمينات المستخدم computersNumber = (int)(100 * Math.random()) + 1; guessCount = 0; System.out.println(); System.out.print("What is your first guess? "); while (true) { usersGuess = TextIO.getInt(); // اقرأ تخمين المستخدم guessCount++; if (usersGuess == computersNumber) { System.out.println("You got it in " + guessCount + " guesses! My number was " + computersNumber); break; // انتهت المباراة بفوز المستخدم } if (guessCount == 6) { System.out.println("You didn't get the number in 6 guesses."); System.out.println("You lose. My number was " + computersNumber); break; // انتهت المباراة بخسارة المستخدم } // بلغ المستخدم عما إذا كان تخمينه أكبر أم أصغر من العدد if (usersGuess < computersNumber) System.out.print("That's too low. Try again: "); else if (usersGuess > computersNumber) System.out.print("That's too high. Try again: "); } System.out.println(); } // end of playGame()الآن، وبعد انتهائنا من كتابة تعريف البرنامج الفرعي بالأعلى، فإننا سنحتاج إلى معرفة المكان الذي يُفْترَض أن نضع به هذا التعريف؟ ينبغي أن نضعه عمومًا ضِمْن نفس الصَنْف المُتضمِّن للبرنامج main()، ولكن ليس داخل البرنامج main ذاته؛ فمن غَيْر المسموح كتابة برنامج فرعي ضِمْن آخر (nested). لمّا كانت لغة الجافا لا تَشترِط أيّ ترتيب معين للبرامج الفرعية المُعرَّفة ضِمْن نفس الصَنْف، فيُمكِنك وضع تعريف playGame() قَبْل البرنامج main() أو بَعْده. لاحِظ أن البرنامج main() سيَستدعِي البرنامج الفرعي playGame()، وهو ما يَعنِي مُجرد تَضمُّنه لتَعْليمَة استدعاء (call statement) لذلك البرنامج الفرعي، لا تَضمُّنه لتعريفها الكامل (definition). يتبقَّى لنا الآن كتابة البرنامج main، وهو ما قد تراه أمرًا بغاية السهولة خاصة مع رؤيتنا لكثير من الأمثلة المشابهة مُسْبَّقًا. سيبدو البرنامج كاملًا كالتالي مع مُراعاة إِمكانية إضافة المزيد من التعليقات (comments): import textio.TextIO; public class GuessingGame { public static void main(String[] args) { System.out.println("Let's play a game. I'll pick a number between"); System.out.println("1 and 100, and you try to guess it."); boolean playAgain; do { playGame(); // اِستدعي البرنامج الفرعي لإجراء مباراة System.out.print("Would you like to play again? "); playAgain = TextIO.getlnBoolean(); } while (playAgain); System.out.println("Thanks for playing. Goodbye."); } // نهاية main static void playGame() { int computersNumber; // العدد العشوائي int usersGuess; // إحدى تخمينات المستخدم int guessCount; // عدد تخمينات المستخدم computersNumber = (int)(100 * Math.random()) + 1; guessCount = 0; System.out.println(); System.out.print("What is your first guess? "); while (true) { usersGuess = TextIO.getInt(); // اقرأ تخمين المستخدم guessCount++; if (usersGuess == computersNumber) { System.out.println("You got it in " + guessCount + " guesses! My number was " + computersNumber); break; // انتهت المباراة بفوز المستخدم } if (guessCount == 6) { System.out.println("You didn't get the number in 6 guesses."); System.out.println("You lose. My number was " + computersNumber); break; // انتهت المباراة بخسارة المستخدم } // بلغ المستخدم عما إذا كان تخمينه أكبر أم أصغر من العدد if (usersGuess < computersNumber) System.out.print("That's too low. Try again: "); else if (usersGuess > computersNumber) System.out.print("That's too high. Try again: "); } System.out.println(); } // نهاية البرنامج الفرعي playGame } // نهاية الصنف GuessingGameاِستغرِق الوقت الكافي لقراءة شيفرة البرنامج بالأعلى، وحَاول اِستيعاب طريقة عملها. رُبما تَكُون قد لاحَظت أن تَقسيم البرنامج إلى تابعين (methods) قد جَعل البرنامج عمومًا أسهل في القراءة، وربما حتى في الكتابة حتى مع كَوْنه بسيطًا، أما إذا لم تَلحَظ ذلك، فحاول إقناع نفسك به في الوقت الحالي. المتغيرات الأعضاء (Member Variables)قد تَتضمَّن الأصناف (classes) أعضاء (members) آخرى، غير البرامج الفرعية، كالمُتَغيِّرات (variables)، فيُمكِن لأي صَنْف التَّصْريح عن مُتَغيِّر ما (variable declaration)، ولا يُقصَد بذلك تلك المُتَغيِّرات المُعرَّفة داخل برنامج فرعي معين، والمَعروفة باسم المُتَغيِّرات المحليّة (local variable)، وإنما تلك المُعرَّفة بمكان يقع خارج أيّ برنامج فرعي ضِمْن الصَنْف. تُعرَف تلك المُتَغيِّرات باسم المُتَغيِّرات العامة (global variable) أو المُتَغيِّرات الأعضاء (member variables)؛ وذلك لكَوْنهم أعضاء (members) ضِمْن الصنف (class). مثلما هو الحال مع البرامج الفرعية، يُمكِن لأي مُتَغيِّر عضو (member variable) أن يُعرَّف بعدّه عضوًا ساكنًا (static) أو عضوًا غير ساكن (non-static). سنقتصر في هذا الفصل على الساكن منها. بدايةً، ينتمي أي مُتَغيِّر عضو، مُعرَّف باِستخدَام المُبدل static، إلى الصَنْف ذاته المُعرَّف بداخله -لا إلى كائنات ذلك الصَنْف-، فعندما يُحَمِّل مُفسر الجافا (Java interpreter) صَنْف معين، فإنه يُخصِّص مساحة بالذاكرة لكل مُتَغيِّر عضو ساكن ضِمْن ذلك الصَنْف. تُعدِّل أي تَعْليمَة إِسْناد (assignment) إلى واحد من تلك المُتَغيِّرات، وبغض النظر عن مكانها بالبرنامج، من محتوى نفس المساحة بالذاكرة، وكذلك يَلج (access) أي تعبير (expression) يَتضمَّن واحدًا من تلك المُتَغيِّرات، وبغض النظر عن مكانه بالبرنامج، إلى نفس المساحة بالذاكرة ويُعيد نفس القيمة، أيّ تتشارك البرامج الفرعية الساكنة المُعرَّفة بصَنْف ما قيم المُتَغيِّرات الأعضاء الساكنة المُعرَّفة ضِمْن نفس ذلك الصَنْف، فيُمكِن لبرنامج فرعي مُعين ضَبْط قيمة مُتَغيِّر عضو ساكن ما، بحيث يَستخدِمها برنامج فرعي آخر، وهو ما يَختلِف عن المُتَغيِّرات المحليّة المُعرَّفة (local variable) داخل أحد البرامج الفرعية؛ حيث تَتوفَّر فقط بينما يُنفَّذ ذلك البرنامج الفرعي، ثم لا يَعُدْ بالإمكان الوُلوج إليها (inaccessible) من خارج ذلك البرنامج الفرعي. تتشابه تَعْليمَة التَّصْريح (declaration) عن مُتَغيِّر عضو (member variable) مع تلك المَسئولة عن التَّصْريح عن أي مُتَغيِّر محليّ تقليدي باستثناء شيئين. الأول هو وقوع التَّصْريح عن المُتَغيِّر العضو بمكان خارج أي برنامج فرعي (مع ذلك ما يزال التَّصْريح ضِمْن الصَنْف نفسه)، والثاني هو إمكانية اِستخدَام المُبدلات (modifiers) مثل static و public و private ضِمْن التَّصْريح. يقتصر هذا الفصل على الأعضاء الساكنة فقط، ولهذا ستَتضمَّن أي تَعْليمَة تَّصْريح عن مُتَغيِّر عضو (member variable) المُبدل static، وربما قد يُستخدَم أيًا من المُبدلين public أو private. على سبيل المثال، انظر التالي: static String usersName; public static int numberOfPlayers; private static double velocity, time;إذا لم تَستخدِم المُبدل private ضِمْن تَعْليمَة التَّصْريح عن مُتَغيِّر عضو ساكن معين، فإنه يُعامَل افتراضيًا كعضو عام public، أي يُمكن الوُلوج إليه بأي مكان سواء من داخل الصَنْف المُعرَّف به أو من خارج ذلك الصنف. ولكن لاحِظ أنك ستحتاج إلى اِستخدَام مُعرِّف (identifier) مُركَّب على الصورة . عند محاولة الإشارة إليه من خارج الصَنْف. فمثلًا، يحتوي الصَنْف System على مُتَغيِّر عضو ساكن عام اسمه هو out، لذلك تستطيع الإشارة إلى ذلك المُتَغيِّر باِستخدَام System.out بأيّ صَنْف خاص بك. مثال آخر هو المُتَغيِّر العضو الساكن العام Math.PI بالصَنْف Math. مثال أخير، وبفَرْض أن لدينا الصَنْف Poker، والذي يُصَرِّح عن مُتَغيِّر عضو ساكن عام، وليَكُن numberOfPlayers، فإنه يُمكِن الإشارة إلى ذلك المُتَغيِّر داخل الصَنْف Poker ببساطة باِستخدَام numberOfPlayers، في المقابل، يُمكِن الإشارة إليه باِستخدَام Poker.numberOfPlayers من خارج الصَنْف. والآن، لنضيف عدة مُتَغيِّرات أعضاء ساكنة إلى الصَنْف GuessingGame الذي كتبناه مُسْبَّقًا بهذا القسم: أولًا، المُتَغيِّر العضو gamesPlayed بهدف الاحتفاظ بعَدَدَ المباريات التي لعبها المُستخدِم إجمالًا. ثانيًا، المُتَغيِّر العضو gamesWon بهدف الاحتفاظ بعَدَدَ المباريات التي كَسَبها المُستخدِم. يُصَرَّح عن تلك المُتغيرات كالتالي: static int gamesPlayed; static int gamesWon;ستَزداد قيمة المُتَغيِّر gamesPlayed بمقدار الواحد دائمًا مع كل عملية اِستدعاء للبرنامج playGame()، بينما ستَزداد قيمة المُتَغيِّر gamesWon بمقدار الواحد في حالة فوز المُستخدِم بالمباراة فقط، ثم تُطبَع قيمة المُتَغيِّرين بنهاية البرنامج main(). لمّا كان ضروريًا لكِلا البرنامجين الفرعيين playGame() و main() أن يَلجا إلى نفس قيمتي المُتَغيِّرين، فإنه يَستحِيل اِستخدَام المُتَغيِّرات المحليّة للقيام بنفس الشيء؛ ففي الواقع، يَقتصِر الولوج إلى قيمة مُتَغيِّر محليّ معين على برنامج فرعي وحيد، هو البرنامج الفرعي الذي عَرَّفه، وضِمْن نفس الاستدعاء؛ حيث تُسنَد قيمة جديدة للمُتَغيِّرات المحليّة (local variables) مع كل عملية استدعاء للبرنامج الفرعي الشامل لها، وهو ما يَختلِف عن المُتَغيِّرات العامة (global variables)، والتي تَحتفِظ بنفس قيمها بين كل استدعاء والاستدعاء الذي يَليه. تُهيَئ (initialized) المُتَغيِّرات الأعضاء تلقائيًا بقيم افتراضية بعد التَّصْريح عنها، وهو ما يَختلِف عن المُتَغيِّرات المحليّة ضِمْن البرامج الفرعية، والتي لابُدّ من إِسْناد قيمة لها صراحةً قَبْل اِستخدَامها. تلك القيم الافتراضية هي ذاتها القيم الافتراضية لعناصر المصفوفات، فتُسنَد القيمة صفر افتراضيًا إلى المُتَغيِّرات العددية، بينما تُسنَد القيمة false للمُتَغيِّرات من النوع boolean، في حين يُسنَد المحرف المقابل لقيمة ترميز اليونيكود (Unicode code) \u0000 للمُتَغيِّرات من النوع المحرفي char، أما القيمة الافتراضية المبدئية للكائنات (objects)، كالسَلاسِل النصية من النوع String، فهي القيمة الفارغة null. لمّا كان المُتَغيِّرين gamesPlayed و gamesWon من النوع int، فإنهما يُهيَئا أتوماتيكيًا إلى القيمة المبدئية صفر، وهو ما تَصادَف أن يَكُون القيمة المبدئية المناسبة لمُتَغيِّر يُنوَى اِستخدَامه كعَدَّاد (counter). مع ذلك، إذا لم تُناسبك القيمة المبدئية الافتراضية أو حتى إذا كنت تُريد مُجرد التَّصْريح عن نفس القيمة المبدئية لكن بصورة أكثر وضوحًا، فما يزال بإمكانك إِجراء عملية إِسْناد إلى أي من تلك المُتَغيِّرات ببداية البرنامج main(). اُنظر نسخة البرنامج GuessingGame.java بَعْد التعديل: import textio.TextIO; public class GuessingGame2 { static int gamesPlayed; // العدد الإجمالي للمباريات static int gamesWon; // عدد المباريات التي فاز فيها المستخدم public static void main(String[] args) { gamesPlayed = 0; gamesWon = 0; // لا فائدة فعلية من ذلك لأن الصفر هو القيمة الافتراضية System.out.println("Let's play a game. I'll pick a number between"); System.out.println("1 and 100, and you try to guess it."); boolean playAgain; do { playGame(); // استدع البرنامج الفرعي للعب مباراة System.out.print("Would you like to play again? "); playAgain = TextIO.getlnBoolean(); } while (playAgain); System.out.println(); System.out.println("You played " + gamesPlayed + " games,"); System.out.println("and you won " + gamesWon + " of those games."); System.out.println("Thanks for playing. Goodbye."); } // end of main() static void playGame() { int computersNumber; // العدد العشوائي int usersGuess; // إحدى تخمينات المستخدم int guessCount; // عدد تخمينات المستخدم gamesPlayed++; // أزد العدد الإجمالي للمباريات computersNumber = (int)(100 * Math.random()) + 1; guessCount = 0; System.out.println(); System.out.print("What is your first guess? "); while (true) { usersGuess = TextIO.getInt(); // اقرأ تخمين المستخدم guessCount++; if (usersGuess == computersNumber) { System.out.println("You got it in " + guessCount + " guesses! My number was " + computersNumber); gamesWon++; break; // انتهت المباراة بفوز المستخدم } if (guessCount == 6) { System.out.println("You didn't get the number in 6 guesses."); System.out.println("You lose. My number was " + computersNumber); break; // انتهت المباراة بخسارة المستخدم } // بلغ المستخدم عما إذا كان تخمينه أكبر أم أصغر من العدد if (usersGuess < computersNumber) System.out.print("That's too low. Try again: "); else if (usersGuess > computersNumber) System.out.print("That's too high. Try again: "); } System.out.println(); } // نهاية البرنامج الفرعي playGame } // نهاية الصنف GuessingGame2بالمناسبة، لم يُستخدَم أي من المُبدلين public و private مع البرامج الفرعية أو المُتَغيِّرات الساكنة static بالأعلى، فما الذي يَعنيه ذلك؟ في الواقع، إذا لم يُخصَّص أي مُبدل وصول (access modifier) لمُتَغيِّر عام (global variable) أو لبرنامج فرعي (subroutine)، فإنه يُصبِح قابلًا للوصول بأيّ مكان يقع ضِمْن حزمة (package) الصَنْف الحاضن له، ولكن ليس بأي حزم آخرى. تقع الأصناف التي لا تُصَرِّح عن وجودها ضِمْن حزمة معينة بالحزمة الافتراضية (default package)، لذا تستطيع جميع الأصناف ضِمْن الحزمة الافتراضية -وهو ما يَشمَل أغلبية الأصناف بهذا الكتاب- الوصول إلى كِلا المُتَغيِّرين gamesPlayed و gamesWon وكذلك استدعاء البرنامج الفرعي playGame(). مع ذلك، يُعدّ اِستخدَام المُبدل private أثناء التَّصْريح عن كُلًا من المُتَغيِّرات الأعضاء والبرامج الفرعية عمومًا واحدًا من الممارسات الجيدة إلا إذا كان هناك سببًا يَدعوك لمخالفة ذلك، كما يُفضَّل تَجَنُّب اِستخدَام الحزمة الافتراضية (default package). ترجمة -بتصرّف- للقسم Section 2: Static Subroutines and Static Variables من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java. |
| 39,906 | الصناديق السوداء: البرامج الفرعية في جافا |
تستطيع عمومًا أن تقتطع مجموعة من التَعْليمَات (instructions) المسئولة عن إنجاز مُهِمّة واحدة مُحدَّدة، وتُضمِّنها معًا تحت اسم معين، ومِنْ ثَمَّ، تستطيع التَعامُل مع تلك المُهِمّة كوحدة واحدة مهما بَلغت درجة تعقيدها، يُطلق على تلك الوحدات اسم البرنامج الفرعي (subroutine). والآن، لَمْ يَعُدْ هناك أيّ داعي للقلق بشأن أيّ من تلك الخطوات التي قد يَكُون الحاسوب مضطرًا لتَّنْفيذها أثناء إنجازه لمُهِمّة معينة، وإنما تحتاج فقط لتَذَكُّر اسم البرنامج الفرعي (subroutine) الخاص بتلك المُهِمّة؛ لكي تتَمكَّنْ من استدعائه (call). تُعدّ البرامج الفرعية عمومًا أحد أهم الأدوات الأساسية لتبسيط المسائل المعقدة. في الواقع، البرامج الفرعية (subroutine) هي أشبه ما تَكُون بـ"صندوق أسود (black box)"؛ لا يُمكِنك رؤية ما بداخله، أو بتعبير أدق، لا ترغب برؤيته؛ لأنك إذا فعلت، فستكون مُضطرًا للتعامل مع ذلك الكم الهائل من التعقيد الذي يُفْترَض لذلك البرنامج الفرعي إخفائه بالأساس. على الجانب الآخر، لا يُمكِن أن يَقْتصِر ذلك الصندوق على عالمه الداخلي، وإلا سيكون عديم الفائدة، بل ينبغي له التَفاعُل مع العالم الخارجي، ولهذا فإنه يحتاج إلى ما يُشبه الواجهة (interface) التي تَسمَح بتَفاعُل ما بداخل الصندوق مع ما هو خارجه. على سبيل المثال، قد يُوفِّر صندوق أسود مادي عدة أزرار قابلة للضغط، أو قرص قابل للضبط، أو عدة فتحات لتمرير المعلومات جيْئةَ وذَهابًا. يحاول الصندوق عمومًا التخلص من التعقيد بإخفائه، ولذلك فإن القاعدة الأولى لأيّ صندوق أسود هي كالتالي: "ينبغي لواجهة (interface) أيّ صندوق أسود أن تَكُون مُبسَّطة، وسَهلة الفهم، ومُعرَّفة بصورة جيدة." هل توجد صناديق سوداء ضِمْن عالمنا الذي نعيش فيه؟ في الواقع، إنها تُحيط بك من كل جانب: تلفازك، وسيارتك، وهاتفك المحمول، وثلاجتك هي مجرد أمثلة قليلة. على سبيل المثال، تَتَكوَّن واجهة (interface) التلفاز من عدة عناصر هي مفتاحي التَشْغِيل والغَلْق، وجهاز التحكم -ولا تنس بالطبع وضع قابس الكهرباء-، والتي تستطيع من خلالها غَلْق التلفاز، وتَشْغِيله، أو تَغْيِير القناة، أو ضَبْط الصوت، وذلك بدون أيّ فهم لكيفية عَمَل تلك الأشياء. يَنْطَبِق نفس الشيء على هاتفك المحمول على الرغم من كَوْن واجهته أكثر تعقيدًا بعدّة مراحل. يُسمَى الجزء الداخلي من الصندوق الأسود بالتَّنْفيذ (implementation)، والذي قد يَتمثَل في صورة مجموعة الالكترونيات داخل عُدَّة التلفاز أو في صورة شيفرة البرنامج الفرعي الفعليّة المسئولة عن تَّنْفيذ مهمة البرنامج، وعليه تَكُون القاعدة الثانية لأيّ صندوق أسود كالتالي: "لا حاجة لمَعرِفة أيّ شيء عن الجزء التَّنْفيذي (implementation) الخاص بصندوق أسود لكي تَتَمكَّنْ من اِستخدَامه، وإنما يَكفيك مَعرِفة واجهته (interface)." تستطيع عمومًا إعادة كتابة الجزء التَّنْفيذي (implementation) لبرنامج فرعي معين طالما لم تتأثر واجهته بذلك التَغْيِير. على سبيل المثال، عندما تغَيَّرت عُدَّة التلفاز الداخلية من اعتمادها على الصمامات المُفْرغَة (vacuum tubes) إلى الترانزستور (Transistor)، لَمْ يَكُن هناك داعي لمَعرِفة أي شيء عن ذلك أو مَعرِفة حتى ما قد يَعنِيه، وبالمثل، يُمكِن إعادة كتابة الجزء الداخلي لأيّ برنامج فرعي؛ ربما بهدف كتابة شيفرة أكثر كفاءة، وذلك بدون إِحداث أي تَغْيِير على البرامج المُستخدِمة لذلك البرنامج الفرعي (subroutine). تَحَصُّلك على صندوق أسود يَعنِي بالضرورة أن شخصًا ما كان قد صمم تَّنْفيذه (implementation) وبناه، وفي الواقع، يَستفيد كُلًا من المُنفِّذ (implementor) والمُستخدِم من فكرة وجود صندوق أسود، فبالنهاية، يُمكِن اِستخدَام ذلك الصندوق بعَدََدَ لا محدود من المواضع المختلفة، والتي لا يحتاج المُنفِّذ (implementor) أن يَعلم عنها شيئًا، وإنما يَقْتصِر دوره على التأَكُّد من أن ذلك الصندوق يُنفِّذ مُهِمّته المُوكلة إليه تَّنْفيذًا صحيحًا، مع تَوْفِيره لواجهة (interface) سليمة تَسمَح للصندوق بالتَفاعُل مع العالم الخارجي. وعليه تَكُون القاعدة الثالثة لأيّ صندوق أسود كالتالي: "لا يحتاج مُنفِّذ صندوق أسود معين مَعرِفة أيّ شيء عن تلك الأنظمة الأكبر التي ربما قد تُوظِّف ذلك الصندوق داخلها." يُمكِن القول أن أيّ صندوق أسود يُقسِّم العالم إلى جزئين: الجزء الداخلي أو التَّنْفيذي (implementation) والجزء الخارجي، بحيث تَقع الواجهة (interface) على الحد الفاصل بينهما وتربطهما معًا. لاحِظ أن الواجهة (interface) ليست مجرد رابط مادي بين صندوق معين وعالمه الخارجي، وإنما تَتضمَّن أيضًا تَوصِيفًا (specification) لما يُنجزه الصندوق بالإضافة إلى تبيان واضح لطريقة اِستخدَام تلك الواجهة. فمثلًا، لا يكفي أن تقول أن التلفاز يُوفِّر مفتاح تَشْغِيل، وإنما لابُدّ من تَحْدِيد إمكانية اِستخدَام ذلك المفتاح لأغراض تَشْغِيل التلفاز وغَلْقه. دَعْنَا نُعيد صياغة ما تَعنيه الواجهة (interface) بمصطلحات علم الحاسوب (computer science). تَتَكوَّن واجهة أيّ برنامج فرعي (subroutine) من مُكوّنين رئيسيين، الأول هو تَوصِيف صياغي أو نَحوِي (syntactic specification)، والآخر تَوصِيف دلالي (semantic specification). يخبرك الأول بالكيفية التي يُسْتَدعى (call) بها ذلك البرنامج الفرعي، بينما يُحدِّد الثاني المُهِمّة (task) المُوكَلة لذلك البرنامج والتي يُفْترَض له إنجازها. لابدّ من مَعرِفة التوصيف الصياغي (syntactic specification) لبرنامج فرعي معين؛ وذلك لاستدعائه استدعاءً سليمًا، بينما لابُدّ من مَعرِفة توصيفه الدلالي (semantic specification)؛ وذلك لفهم الغرض منه، واِستخدَامه بطريقة فعالة. عادةً ما يُستخدَم مصطلح المواصفة الاصطلاحية للبرنامج الفرعي (contract of subroutine) للإشارة إلى مُكوّني الواجهة -الصياغي والدلالي- معًا. تقول المواصفة الاصطلاحية (contract) لأيّ برنامج فرعي: "ها هو ما ينبغي القيام به لإستدعائي، وها هو ما سأُنْجِزُه بالمقابل". ينبغي للتعليقات (comments) المكتوبة لبرنامج فرعي معين أن تُبرز مُواصفته الاصطلاحية بصورة واضحة، وهو في الواقع ما لا يَحدُث عمليًا، مما يُؤدي إلى انزعاج المبرمجين الذين قد يكونوا مُضطرّين لاِستخدَام ذلك البرنامج الفرعي. سأنتقل ببقية هذا الفصل من مجرد الحديث عن الفكرة العامة للصندوق الأسود والبرامج الفرعية إلى تفاصيل كتابة تلك البرامج بلغة الجافا وكيفية اِستخدَامها. ولكن حَاوِل دومًا اِستحضار تلك الأفكار والمبادئ العامة بذهنك؛ لأنها تُبرز الغرض الأساسي من وجود البرامج الفرعية، وتُرشدك لكيفية اِستخدَامها. سيَكُون ذلك أكثر وُضوحًا بوصولنا للقسم ٤.٧ حيث سنناقش البرامج الفرعية كأداة لتطوير البرامج. تَذكَّر دائمًا أن البرامج الفرعية (subroutines) ليست الصناديق السوداء الوحيدة بعالم البرمجة. على سبيل المثال، يُعدّ الصنف (class) صندوقًا أسودًا، يَتكوَّن من جزئين، أحدهما عام (public) يُمثِل واجهته (interface)، والآخر خاص (private) يُمثِل تَّنْفيذه (implementation) الخَفي. تَنْطَبِق مبادئ الصناديق السوداء عمومًا على كُلًا من الأصناف (classes) والبرامج الفرعية (subroutines). ترجمة -بتصرّف- للقسم Section 1: Black Boxes من فصل Chapter 4: Programming in the Large I: Subroutines من كتاب Introduction to Programming Using Java. |
| 39,905 | آبل توضح تفاصيل استخدام بيانات الخصوصية على جميع تطبيقات iOS |
مع إطلاق شركة آبل تحديث نظام الآيفون iOS 14.3 حدثت الشركة سياسية الخصوصية في متجر تطبيقاتها App Store عبر وسم التطبيقات بعلامة توضح طريقة استخدام بيانات المستخدم الملتقطة عبرها. حيث كانت الشركة قد أعلنت عن خطتها إطلاق الميزة الجديدة تحت مسمى “وسم التغذية” للإفصاح عن الخصوصية في يونيو الماضي خلال مؤتمر المطورين العالمي WWDC؛ فيما قامت الشهر الماضي بالكشف عن ضرورة تقديم المطورين بيانات التعقب في تطبيقاتهم مع حلول الثامن من ديسمبر الجاري وإلا سيفقدوا القدرة على تحديث تطبيقاتهم. وأوضحت الشركة أن علامة توضيح بيانات الخصوصية ستكون إلزامية لكل التطبيقات على منصات أنظمتها المختلفة؛ iPadOS وmacOS وwatchOS وtvOS ويجب أن يتم تحديثها مع إيفاد المطورين أي تحديث جديد على التطبيقات؛ كما سيطبق الأمر ذاته على تطبيقات الشركة نفسها. وفي حالة التطبيقات الأصلية من الشركة والتي لا تملك صفحة منتح على متجر App Store مثل تطبيق الرسائل مثلًا؛ فقد صرحت الشركة أن علامة الخصوصية التي تشمل المعلومات ستتوفر على الويب كما هو الحال مع جميع المنتجات والتطبيقات الأخرى التي ستعتمد معها الشركة نفس الآلية. أما بالنسبة لعملية تصنيف البيانات التي ستتبعها الشركة في تطبيق استراتيجيتها الجديدة فسيكون هناك ثلاث مجموعات تنطوي تحتها البيانات التفصيلية؛ الأولى “بيانات تستخدم لتعقبك” والثانية “بيانات مرتبطة بك” والثالثة ” بيانات غير مرتبطة بك”. كل مجموعة منها ستعبر عن مدى دقة وحساسية بيانات الخصوصية التي يتم جمعها عن المستخدم من التطبيقات المختلفة على متاجر آبل. المصدر التدوينة آبل توضح تفاصيل استخدام بيانات الخصوصية على جميع تطبيقات iOS ظهرت أولاً على عالم التقنية. |
| 39,904 | وصول تحديث iOS 14.3 مع ميزة التصوير ProRAW وحل مشكلة إشعارات الرسائل |
أطلقت آبل أخر تحديثات نظام الآيفون عبر الإصدار iOS 14.3 الذي جاء مع الحل للعديد من المشكلات ودعم العديد من الميزات أيضًا؛ وعلى رأسها ميزة التصوير ProRAW المخصصة لنسختي آيفون 12 برو و12 برو ماكس. كما شمل التحديث تحسينات على تطبيق آبل TV ودعم خدمة Fitness Plus “فيتنس بلس” واشتراكاتها بالإضافة لدعم سماعة الرأس الجديدة AirPods Max. وبالنسبة لميزة التصوير ProRAW فمن المعلوم أن الآيفون يلتقط صورًا بصيغة RAW لكن دون دعم المزايا التي توفرها عملية التقاط الصورة عبر تطبيق الكاميرا الرئيسي من تحرير ومعالجة محوسبة تزيد من جمالية الصورة. حيث تم تصميم ProRAW لمعالجة الصورة بشكلها النمطي والاستفادة من تركيبة التحرير أثناء عملية معالجة الصورة الأولية مع مرونة ما بعد المعالجة في صيغة RAW؛ حيث خصت الشركة آيفون 12 برو و12 برو ماكس فقط للاستفادة من هذه الميزة بعد الترقية لنسخة iOS 14.3. على غرار التحسينات السابق ذكرها جلبت آبل مع التحديث حلًا لمشكلة عدم وصول إشعارات الرسائل الجديدة؛ حيث عاني مستخدمي الآيفون مع نسخة النظام السابقة من خلل في وصول الإشعارات سواء على مستوى الرسائل القصيرة SMS أو عبر iMessage. فيما كان لسيري “Siri” نصيبًا في الإصدار الجديد من النظام بدعم التعرف على مئات الأصوات المتنوعة من الحيوانات والأدوات الموسيقية إلى المركبات، فيما تعمل الميزة على آيفون وآيباد وهوم بود. وجاء مع iOS 14.3 تحسينات أخرى شملت تسهيل تخصيص تطبيقات الواجهة دون الحاجة لتطبيق الاختصارات، والقدرة على تصوير فيديو 25 إطار في الثانية بالإضافة لتحديث شريط البحث في Apple TV ودعم تطبيق الصحة بمجموعة جديدة من ميزات تعقب التمارين. المصدر
التدوينة وصول تحديث iOS 14.3 مع ميزة التصوير ProRAW وحل مشكلة إشعارات الرسائل ظهرت أولاً على عالم التقنية. |
| 39,870 | سيسكو تعلن عن ثلاثة أجهزة Webex جديدة لتمكين العودة الآمنة إلى المكتب |
وسعت شركة سيسكو سلسلة منتجات منصتها للتعاون Webex عبر الإعلان عن ثلاثة أجهزة تعاونية جديدة كليًا والتي صممت لدعم العاملين عن بُعد وتمكين العودة الآمنة إلى المكتب. والأجهزة الثلاثة الجديدة التي ستنضم إلى مجموعة أجهزة Webex الذكية هي: Webex Desk Camera و Webex Desk Hub و Webex Desk. هذا وقد أصبح من الواضح أن مستقبل العمل ومكان العمل الذكي سيشمل مزيج من التفاعلات في المكتب وعن بُعد، والذي يُعرف باسم بيئة العمل الهجينة. ومن المهم مع تبلور بيئة العمل الهجينة أن تتوفر تجارب رقمية ذكية وسلسة سواء في المنزل أو المكتب وما بينهما. وتوفر الأجهزة الجديدة التي أعلنت عنها سيسكو إضافةً إلى بقية مجموعة منتجاتها الذكية هذه التجارب المتميزة. وفي هذا الإطار، قالت ريم أسعد، نائب الرئيس التنفيذي لدى شركة سيسكو في الشرق الأوسط وأفريقيا: “بدأ عملائنا بمشاركة رغبتهم المعنية بإعادة تصور أماكن العمل، والجميع بات يتجه نحو نهج العمل الهجين. وعليه قمنا في سيسكو بتطوير منتجات جديدة: Webex Desk Camera و Webex Desk Hub ليتم استخدامهم في كل من البيت والمكتب، بالإضافة إلى Webex Desk Hub الأول من نوعه والذي يمكن من التمتع بتجارب عمل مخصصة ضمن المساحات المشتركة”
معززة بالمزايا الذكية التي تعرفونها في مجموعة أجهزة الفيديو لدينا مثل: التعرف على الوجه ومقاييس الإشغال، وتعد كاميرا Webex Desk Camera مثالية للمنزل أو المكتب. ومع ميزة هي الأولى في القطاع ولا تتوفر بأي كاميرا USB أخرى، يمكنكم الآن كتم صوت الميكروفون وإعادته بإيماءة بسيطة دون لمس الحاسوب. وبدعم من منصة Webex كبقية الأجهزة في مجموعتنا، يستطيع فريق تقنية المعلومات إدارة Webex على نطاق واسع كي لا يفوتكم أي تحديث وليكون الدعم متوفر على الدوام. مما يعني أنه حتى في مكان العمل المتطور باستمرار، سيتمكن فريق تقنية المعلومات من تلبية احتياجات القوى العاملة أينما كانوا.
بدأت العديد من المؤسسات بإعادة التفكير في مساحات مكاتبها وإعادة تكوينها، وقد تعني العودة إلى المكتب مساحة أقل للمكاتب والمزيد من المكاتب التي يستخدمها موظفون متعددون في أوقات مختلفة. ومع Webex Desk Hub الجديد سيصبح تحويل أي مكتب متاح لمساحتكم الشخصية أسهل بكثير. وهذه الفئة الجديدة من الأجهزة قادرة على دعم اجتماعات الفيديو والمكالمات الهاتفية بجودة مذهلة، كما تتيح أيضاً إقران وشحن وتوصيل جميع الأجهزة التي تعتمدون عليها في الإنتاجية والتعاون مثل الكاميرا وسماعة الرأس والشاشة والحاسوب المحمول والجوال. وهي معززة بالذكاء لتوفير تجربة متناغمة تمكنكم من إنجاز مهامكم بسهولة. وبالإضافة إلى ذلك، يتيح لكم Webex Desk Hub حجز مكتب باستخدام الحاسوب المحمول أو شارتِكم أو الهاتف المحمول للتحقق من هويتكم. وهذا الجهاز ليس جهاز عادي لتشارك مساحة المكاتب بأوقات مختلفة؛ وإنما تم تصميم Webex Desk Hub ليمنح المستخدمين طريقة جديدة للعمل. وهو سهل الاستخدام والإدارة أيضاً، فيجمع Webex Desk Hub البيانات البيئية والإشغال وحالة استخدام المكتب في الوقت الفعلي للمساعدة في إدارة مساحة المكتب الجديدة. ويمكن استخدامه لعرض اللافتات الرقمية التي تعرض إشعارات لتمكين إعادته الآمنة.
تمامًا مثل جهاز Webex Desk Pro، يتيح لكم Webex Desk تعزيز التفاعل مع جمهوركم باستخدام الشرائح أو مقاطع الفيديو كخلفية لكم أثناء تقديمكم لعروضكم، وهي ميزة تدعى “مقدم العروض الغنية”. وتحول هذه التقنية تجربتكم من خلال تمكينكم من الاتصال في الاجتماعات والمشاركة في الإنشاء والحفاظ على الإنتاجية سواء كنتم في المنزل أو المكتب. وتمتلك سعر تنافسي كونها جهاز واحد بميزات ذكية وغنية مثل إلغاء الضجيج والخلفيات الافتراضية وخاصية التعرف على الوجه. ويمكن استخدامه كشاشة رئيسية والاتصال بأي خدمة اجتماعات. ومن السهل جداً تغيير إعداداته بفضل الكاميرا والميكروفون ومكبرات الصوت عالية الجودة، دون الحاجة إلى فريق تقنية المعلومات أو المعرفة التقنية المسبقة. ويصبح جاهز للعمل في غضون دقائق.
التدوينة سيسكو تعلن عن ثلاثة أجهزة Webex جديدة لتمكين العودة الآمنة إلى المكتب ظهرت أولاً على عالم التقنية. |
| 39,847 | اختيار العتاد والبرامج في العالم الرقمي |
سيقدم هذا الفصل أهمّ الأساليب المتبعة حاليًا في صناعة البرمجيات والعتاد، وكيفية الاختيار والتفاضل بينها. كما سيشرح أهمية تحديثات البرمجيات بالإضافة إلى تقديم عددٍ من البرمجيات البديلة المفيدة للمستخدم. ما بين البرمجيات المفتوحة والمغلقةكما ذكرنا في فصل المفاهيم التأسيسية، فإنّ البرمجيات المغلقة (Closed-Source Software) هي تلك التي لا تسمح لك بتعديل ورؤية ومشاركة الشِفرة المصدرية للبرنامج، بل تتطلب موافقتك على شروط استخدام معيّنة (End-User License Agreement) قبل أن تتمكن من استخدام البرنامج. بينما البرمجيات المفتوحة (Open-Source Software) هي تلك التي تسمح لك بتعديل ورؤية ومشاركة الشِفرة المصدرية للبرنامج دون قيودٍ بصورة عامّة (إلّا القيود المتعلّقة بأحد تراخيص البرمجيات المفتوحة، مثل أنّه عليك ذكر أسماء المطورين الأصليين وتوزيع برنامجك المُشتَق كذلك تحت نفس الرخصة… إلخ). الكثير من البرمجيات التي تراها حولك هي مفتوحة المصدر، مثل متصفّح فيرفكس للويب ونظام لينكس وتوزيعاته، وبرنامج عارض الفيديو VLC وغيرها الكثير من البرامج. نواة نظام أندرويد الموجود على هاتفك مفتوحة المصدر فهي نواة نظام لينكس نفسها. مهما كان تصنيف البرمجيات التي تستعملها، عليك أن تحاول دومًا الاعتماد على البرمجيات المفتوحة، للأسباب التالية:
هذا لا يعني بالطبع أنّ كل البرمجيات المغلقة لها بدائل أفضل منها من البرمجيات المفتوحة، ولكن إذا تساوت لديك المزايا فحينها عليك بالطبع ترجيح المفتوح المصدر منها. اختيار العتادالعتاد (Hardware) في عصرنا الحالي قصّة مؤسفة. معظمه يأتي من شركات صينية أو أمريكية أو أوروبية، ولا يمكنك أنت كمستخدم عربي التوقّف عن الشراء منهم فليس لدينا بديل عربي مناسب لهذه الأجهزة، وبالتالي لا يمكنك أن تضمن كفرد أنّ العتاد الذي تشتريه خالٍ من برمجيات التجسس والمراقبة. نعم، يمكن للعتاد كذلك أن يحتوي برمجيات تجسس محمّلة مسبقًا. قامت شركة لينوفو (Lenovo) الشهيرة مثلًا سنة 2015م بشحن مئات الآلاف من الحواسيب المحمولة التي تحوي برمجيات تجسس محمّلة مسبقًا داخل الولايات المتّحدة [1]. كما تُكتشف على مدار الشهور العشرات من الشركات الصينية التي تبيع الهواتف المحمولة الرخيصة على أنّها كانت تشحن برمجيات تجسس داخل تلك الهواتف كذلك. أبرزها كان ما اكتُشف سنة 2016م عن 700 مليون هاتف أندرويد قادم من الصين مع برمجيات تجسس خبيثة ترسل كل بيانات المستخدمين إلى الصين كل 72 ساعة [2]. هل عرفت الآن لماذا تلك الهواتف رخيصة؟ تأتي المشكلة الأكبر بخصوص العتاد عند الهواتف المحمولة؛ تأتي جميع الهواتف بنظام تشغيل محمّل مسبقًا ولا يمكنك استبداله (مثل أندرويد وiOS). الكثير منها يسمح لك بعمل ما يعرف بالصلاحيات المطلقة "رووت" (Root) للجهاز حيث يصبح بإمكانك امتلاك كامل الصلاحيات عليه ثم حذف نظام التشغيل الحالي وتثبيت نظام آخر مثلًا، لكن جميع الشركات تقول لك أنّك ستفقد ضمان الجهاز بمجرّد قيامك بهذه الخطوة، فتظل مجبورًا على استخدام نظام التشغيل القديم المليء بالبرمجيات التي لا تعرفها ولا تعرف ماذا تفعل على نظامك. هناك جزءٌ كبير من الهواتف المحمولة التي لا تسمح لك بعمل رووت للجهاز حتّى. هذا بالنسبة للمستخدمين، لكن المشكلة موجودة حتّى بالنسبة للشركات، فقد نشرت بلومبيرغ تقريرًا سنة 2019م عن قطعة عتاد صغيرة خبيثة ترسل بيانات حساسة للصين داخل معالجات أحد الشركات الصينية والتي تُصدَّر لتعمل داخل مراكز البيانات لشركات مثل أمازون وآبل في أمريكا [3]. علّقت كلّ الشركات أن التقرير غير صحيح بل وتدخلت وكالة الأمن القومي الأمريكية (NSA) لتقول أنّ التقرير عارٍ عن الصحّة. لكن وإن كان التقرير خاطئًا إلّا أنّه دفع المتخصصين في المجال للتأكيد على سهولة هذا النوع من الهجمات وإمكانيته، وأنّ الولايات المتحدة نفسها كانت تمارسه على مدار عقود للتجسس على الدول الأخرى [4]. ملخّص الكلام هو أنّه لا يمكنك كمستخدم التأكد تمامًا أنّ عتادك لا يوجد به قطعة من هذه القطع. وعليك أنت كمستخدم أن تقرر أي درجة من الحماية والخصوصية تريد أن تمتلك من ناحية العتاد. لكن ما لا يُدرك كلّه لا يترك جلّه. إليك بعض النصائح المتعلّقة بشراء العتاد:
بسبب كل ما سبق من انتهاكات الخصوصية من ناحية العتاد، ظهرت مؤخّرًا الكثير من الشركات لتتخصص في بناء عتادٍ يحترم خصوصية المستخدم بصورة افتراضية. يكون سعر هذا العتاد عادةً مرتفع الثمن مقارنةً بغيره، لكن إن كنتَ مهتمًا حقًا بتأمين نفسك إلى أقصى صورة ممكنة فحينها قد تحب الشراء من أحدها. هنا نذكر بعضها.
يستعمل معظم القرّاء غالبًا نظام التشغيل ويندوز (Windows) من شركة مايكروسوفت (Microsoft) على أجهزة حواسيبهم، لكن هذا ليس أفضل خيارٍ موجود في الساحة من أجل الأمان الرقمي والخصوصية. عليك بدايةً أن تعلم أنّه يمكنك حذف نظام التشغيل الحالي على أجهزة حواسيبك وتثبيت أيّ نظامٍ بديلٍ تريده. لدى مختلف أنظمة التشغيل مميزات وعيوب مختلفة وتطورها شركات ومؤسسات مختلفة حول العالم. ويندوز مثلًا يُطَوّر من طرف شركة مايكروسوفت وحدها، وكذلك ماك من طرف شركة آبل، أمّا نظام لينكس فهو يأتي على شكل توزيعات (Distributions) يطورها أفرادٌ وشركات مختلفة من حول العالم. هناك الكثير من أنظمة التشغيل الأخرى وليست هذه فقط الموجودة بالساحة، لكن هذه هي أشهرها ولا ننصح باستخدام غيرها. إننا ننصح باستخدام أحد توزيعات نظام لينكس عوضًا عن ذلك، وهذا للأسباب التالية:
هناك توزيعات أخرى بالطبع مثل فيدورا، أوبن سوزا، أرتش لينكس وغيرها. لكننا لا نستحسن البدء معها لكونها تتطلب خبرةً أكثر في استخدام نظام لينكس. ستحتاج تعلّم العديد من المعلومات عن نظام لينكس قبل الانتقال إليه؛ مثلًا عليك أن تعلم أنّ برمجيات ويندوز (كل شيء بصيغة .exe) لا تعمل على لينكس، وبالتالي عليك البحث عن بدائل لتلك البرمجيات. يمكنك تثبيت نظام لينكس على نفس القرص الصلب بجانب ويندوز لتجرّبه إن أردت قبل الانتقال إليه بصورة كاملة. ننصحك بزيارة قسم لينكس على أكاديمية حسّوب وموقع لينكس اليوم وتصفّح المقالات المكتوبة هناك للمزيد من المعلومات عن لينكس. كلّ ما سبق مفيدٌ إن قررت الانتقال إلى نظام لينكس. لكننا ندرك أنّ الكثير من القرّاء لن يقدروا على هذا التحوّل أو يريدوه، وبالتالي هناك بعض النقاط لأخذها بعين الحسبان إن قررت البقاء على استخدام ويندوز:
بخصوص نظام ماك (macOS) من شركة آبل؛ هو نظامٌ يأتي افتراضيًا على أجهزة الشركة فقط ولا يأتي مع أجهزة شركاتٍ أخرى، فنسبة استخدامه ضئيلة بسبب ذلك. وهو مبنيٌ على يونكس (Unix) ومغلق المصدر. تمتلك شركة آبل تحكّمًا كاملًا به وكذلك ببيانات المستخدمين وحساباتهم فخدمات آبل المختلفة مدموجةٌ فيه بصورة جذرية. لا ننصح بشراء منتجات آبل ولا استخدام أنظمتها، فهي مغلقة المصدر وتمنع المستخدم من التحكّم بأجهزته وتثبيت ما يشاء عليها. لكنّها جيّدة من ناحية حماية المستخدمين من الأطراف الثالثة (3rd-party)، حيث تدعم خدمات التشفير وتستخدمها بكثرة في منتجاتها، كما أنّها لا تبيع البيانات للمعلنين وتمتلك سياسات خصوصية واضحة تخبر المستخدم كيفية التعامل مع بياناته. وهي أفضل من مايكروسوفت في هذا المجال، وهذا لا يعني أنّ ويندوز لا يمكن ضبطه ليكون آمنًا من هذه الناحية كذلك، لكنّ الإعدادات الافتراضية هي ما يهمّ. يُمكن تأمين كلّ الأنظمة بصورة قويّة من ناحية المبدأ، لكن ما يهم هو طريقة تعاملها مع بيانات وملفّات المستخدم وهل تأتي بإعدادات حفظ الخصوصية مفعلةً افتراضيًا أم لا، وهل تجبره على نوعٍ معيّن من الممارسات بعقله اللاواعي أم تجعله مدركًا لِمَا يحصل على نظام تشغيله. لا يمكن الحكم على نظام تشغيل معيّن أنّه أأمن نظام تشغيل على الإطلاق مثلًا، لكن هناك أنظمة يمكن تأمينها بسهولة أكثر من غيرها وهناك أنظمة مفتوحة وأخرى مغلقة، ومنها ما يحترم الخصوصية وحقّ المستخدم في التصرّف بعتاده ومنها ما لا يحترم ذلك. فمثلًا حتّى لو استخدمت خدمات التشفير بصورة جيدة من شركة آبل على نظام macOS واستخدمت خيارات الحماية الأساسية، فسيظل عرضةً للاختراق إمّا عبر الثغرات الأمنية الغير مكتشفة بعد أو البرمجيات الخبيثة التي قد تصل حاسوبك بطريقةً ما. لا يوجد نظام مؤمَّن مئة بالمئة. وهذا جليٌ في قضية آبل الشهيرة مع وكالة الأمن الفيدرالي الأميركية سنة 2016م، حيث طلبت المحكمة من آبل فتح التشفير لجهاز الآيفون الخاصّ بأحد مرتكبي الجرائم، لكنّها رفضت التعاون معهم، ورغم أنّه نظريًا كان من المستحيل على الـFBI أن يفتحوا الهاتف إلّا بمساعدةٍ من آبل إلّا أنّهم أعلنوا أنّهم قد تمكّنوا من فتحه آنذاك بمساعدة شركةٍ خارجية [5]. وهذا لتفهم أنّه حتى أكثر التقنيات أمانًا كالتشفير يُمكن كسرها ليس عبر كسر التشفير نفسه أو التقنية نفسها مثلًا، بل عبر كسر طريقة استخدام وتضمين ذاك التشفير أو التقنية (Implementation). اختيار متصفّح الويبأفضل متصفّح ويب يجمع بين احترام الخصوصية والأداء وسهولة الاستخدام هو فيرفكس (Firefox). ننصح ألّا يستعمل المستخدمون متصفّح كروم (Google Chrome) من شركة جوجل إن كانوا مهتمين حقًا بخصوصيتهم. متصفّح كروم من جوجل مغلق المصدر (هو في الواقع مبني على متصفّح كروميوم (Chromium) المفتوح المصدر التابع لجوجل كذلك، لكنّ كروم نفسه مغلق المصدر) وبالتالي لا أحد يعلم ما الموجود داخله سوى مطوروه. لكن هذه ليست ربع المشكلة ولا حتّى ثُمنها، بل المشكلة الحقيقية هي في البيانات والأساليب التي يتّبعها كروم افتراضيًا [1]:
ننصح من أجل كلّ هذه الأسباب باستخدام فيرفكس:
هناك متصفّحات أخرى توفّر خصوصية أكبر من فيرفكس كذلك، مثل Ungoogled Chromium (وهو متصفّح كروم لكن دون خدمات جوجل تمامًا) وننصح به لمن يريد نفس أداء كروم لكن بخصوصية أكبر، وهناك كذلك Tor (يأتي بنظام وصول مجهول إلى الإنترنت). إذا كنت تريد متصفحًا مفتوح المصدر وبنفس واجهة ومميزات كروم (بل حتّى نفس المحرّك) ويتمتع بالخصوصية والأمان بنفس الوقت فيمكنك تجريب متصفّح Brave، وهو متصفّح مبني على كروميوم مع إعدادات خصوصية قوية افتراضيًا بالإضافة إلى بعض التقنيات المتطورة لدعم صنّاع المحتوى والمواقع التي تزورها عبر العملات الرقمية وغير ذلك. يحميك Brave حتّى من تتبع بصمة الإصبع افتراضيًا. البدائل مفتوحة المصدر للبرمجيات الشهيرةإليك جدولًا مفيدًا بالبرمجيات الشهيرة مغلقة المصدر، وما يقابلها من معسكر البرمجيات المفتوحة. يمكنك زيارة مواقع هذه البرمجيات وتحميلها وتثبيتها على جهازك لرؤية ما إذا كانت تناسب استعمالك اليومي أم لا. اسم التصنيف البرامج المغلقة البدائل المفتوحة برامج المكتب مايكروسوفت أوفيس
CorelDraw Adobe Illustrator
Cinema 4D Lightwave 3D SolidWorks
PowerDVD
Skype
التحديثات مهمّة جدًا لأي مستخدم مهتم بالأمان الرقمي والخصوصية. تأتي تحديثات البرمجيات عادةً لإصلاح المشاكل اﻷمنية أو تقديم مميزاتٍ جديدة، وعدم تثبيت المستخدم لها على حاسوبه سيجعله عرضةً للثغرات الأمنية. حوالي 75% من كل الثغرات التي يستخدمها المخترقون حول العالم (إحصائيات الربع الأول من 2020م) كانت ثغراتٍ متعلّقة بحزمة مايكروسوفت أوفيس مثلًا [1]. تسمح هذه الثغرات للمخترقين بوضع شِفرات خبيثة داخل ملفّات المستندات وإرسالها إلى المستخدمين المُراد اختراقهم، والذين يقومون للأسف بفتحها دون وعي مما يسمح للمخترقين بالتحكّم بكامل أنظمتهم أو سرقة بياناتٍ حساسة لهم. والأمر لا يقتصر على رسائل التصيّد (Phishing) التي تأتي من المخترقين بصورة مباشرة، بل يمكن مثلًا أن يقوم أحد المخترقين باختراق حساب صديقك أو زميلك في العمل مثلًا، ثم يُرسل لك أحد هذه الملفّات وتظن أنت أنّه لا بأس بفتحه فهو قادمٌ من صديقك، فتُخترَق أنت كذلك عبر هذه الطريقة. عليك الحفاظ على نظاّمك مُحدّثًا دومًا من أجل هذا، وسواءٌ كنت تستخدم ويندوز أو ماك أو لينكس. تأكّد كل أسبوع على الأقل أنّ نظامك وبرمجياتك جميعها محدّثةٌ إلى آخر إصدارٍ منها، وخصوصًا متصفّحات الويب، فمتصفّحات الويب هي بوابتك الأساسية للحصول على البيانات من الجهات الأخرى واستعمالك لمتصفّح ويب قديم سيعرّضك للاختراق. ولكن المستخدمين لا يستمعون إلينا للأسف. فعلى سبيل المثال كانت أحد الثغرات التي أُصلِحت في مايكروسوفت أوفيس سنة 2017م ما تزال هي واحدة من أكثر 10 ثغرات استخدامًا لاختراق الناس في 2020م [2]، وهو ما يعني أنّ المستخدمين لا يقومون بتحديث برمجياتهم بالصورة المطلوبة. لا تكن مثلهم! ختامًالا تكن مثل هؤلاء المستخدمين الذين لا يبالون بنوعية البرمجيات التي يثبّتونها على أنظمتهم ولا يهتمون بتحديثها. قد تشكّل كل هذه العوامل خطرًا عليك في المستقبل إن لم تضبطها بصورةٍ صحيحة. وضبطها ليس بذاك التعقيد فكلّ ما عليك فعله هو اختيار البرمجيات الجيّدة بدايةً، ثمّ متابعة تحديثها بصورة مستمرة، فقط هذا هو كلّ الأمر. |
| 39,846 | الوعي في العالم الرقمي |
سيشرح هذا الفصل أهمية الوعي في الأمان الرقمي والحفاظ على الخصوصية، ولماذا هو أهم شيء قد تمتلكه أن أردت الدخول في هذا المجال، الأمان الرقمي والحفاظ على الخصوصية. كما سيشرح بعض النصائح النظرية للحصول على مستوىً أعلى من الأمان. مفاهيم أساسية للوعيالوعي صفة غير موضوعية لا يوجد تعريفٌ مشترك لها. لكن يمكن تعريف الوعي - في هذا المجال - بصورة عامّة أنّه الأسلوب الذي يتبعه المُستخدم في كل تصرّفاته في العالم الرقمي ليضمن حفاظه على أمانه وخصوصيته بالشكل الذي يريده ويرتضيه. المستخدم الواعي هو من يتبع مجموعة من الإرشادات والقواعد والأساليب المدروسة بصورة صحيحة أثناء استخدامه للأجهزة الرقمية، والمستخدم غير الواعي هو من لا يبالي بذلك. الوعي مَلَكة من الصعب تعلّمها، وهذا لأنّه ليس شيئًا يمكن شرحه كبرنامج أو إضافة متصفّح مثلًا، بل هو أسلوب تفكير وتحليل للمعطيات، فهو مشتقٌ من ذكاء الإنسان وقدرته على التفكير. ولا يُمكن الإشارة للوعي بصورة مباشرة وأن يُقال: هذا هو الوعي فتعلّموه، بل على المرء أن يتعلمه بنفسه ويبنيه مع الزمن. وهو أهم وسيلة دفاع ليمتلكها المُستخدم أثناء قيامه بأيّ عملٍ رقمي، ولهذا جعلناه في مقدّمة هذه السلسلة وقبل الفصول التطبيقية الأخرى، لأنّ كل تلك الأساليب العملية لن تجديك نفعًا إن لم تمتلك المعارف والمهارات والخبرات التي تؤهلك للقيام بها على أكمل وجه، ثم متابعة القيام بها. ما قد يغذّي خزّان الوعي للقارئ هو أن يبحث في المصادر المتوفّرة على الشبكة عن مواضيع متفرقة في علوم الحاسوب؛ كيف يعمل الحاسوب والشبكات والهواتف والأنظمة المختلفة، وما هي آخر الأخبار في مجال الأمان الرقمي والخصوصية، وما هي آخر الطرق التي استعملها المخترقون ووكالات التجسس للتنصت على المستخدمين… وغير ذلك من المواضيع المتعلّقة بالمجال. يصبح القارئ تدريجيًا واعيًا بكل هذه الأشياء المحيطة به مع مرور الوقت. إليك جملة من النصائح العامة حول أشياء يجب عليك فعلها أو تجنبها من أجل الأمان الرقمي. هذه النصائح مجرّد خطوط عامة لأمور متكررة الحدوث، وليست تفصيلية:
ما لا يدركه الكثير من الناس عندما يشاركون أي شيء على مواقع التواصل - بل وحتّى غيرها من المواقع - أنّ معظمها تشترط إعطاء حقوق ملكية فكرية كاملة من طرفك لتلك المنصّة. فيس بوك مثلًا ينصّ على ذلك بوضوح في شروط الاستخدام الخاصّة به [1]: اقتباسعلى وجه التحديد، عندما تقوم بمشاركة محتوى محمي بموجب حقوق الملكية الفكرية أو نشره أو تحميله على أو في منتجاتنا أو بأي طريقة ذات صلة بمنتجاتنا، فإنك بذلك تمنحنا ترخيصًا دوليًا غير حصري، قابلاً للنقل، وقابلاً للترخيص من الباطن، وغير محفوظ الحقوق، لاستضافة المحتوى، واستخدامه، وتوزيعه، وتعديله، وتشغيله، ونسخه، وتقديمه أو عرضه على العامة، وترجمته، وإنشاء أعمال مشتقة منه (بما يتوافق مع إعدادات الخصوصية والتطبيق الخاصة بك). وذلك يعني أنه، على سبيل المثال، إذا قمت بمشاركة صورة على فيسبوك، فإنك بذلك تمنحنا إذنًا يسمح لنا بالحق في تخزينها ونسخها ومشاركتها مع الآخرين (ونكرر، بما يتوافق مع إعداداتك) مثل موفري الخدمات الذين يدعمون خدمتنا أو غير ذلك من منتجات فيسبوك التي تستخدمها. وتنتهي صلاحية هذا الترخيص بمجرد حذف المحتوى الخاص بك من أنظمتنا. تستعمل الشركات الأخرى مثل جوجل الصور التي ترفعها كبياناتٍ لتدريب أنظمة الذكاء الصناعي الخاصّة بها، حيث تُستعمل صورك من أجل تدريبها على التقاط بعض العناصر أو التعرّف عليها، مما يساعد جوجل في تقديم خدمات تجارية لاحقًا للشركات الأخرى [2]: اقتباسمن خلال تسليم أو نشر أو عرض المحتويات، فإنك تمنح Google ترخيصاً دائماً وغير قابلٍ للنقض وفي كل أنحاء العالم وبدون رسوم وغير حصري بإعادة إنتاج وتكييف وتعديل وترجمة ونشر وإنجاز علناً وعرض علناً وتوزيع أي من المحتويات التي تسلمها أو تنشرها أو تعرضها في أو من خلال الخدمات. وهذا الترخيص هو فقط لغرض تمكين Google من عرض وتوزيع وترويج الخدمات ويمكن سحبه بالنسبة لخدمات معينة كما هو محدد ضمن الشروط الإضافية لتلك الخدمات. مشاركتك على موقع Reddit مثلًا تعطي الحق لكل المستخدمين - وليس فقط مدراء موقع Reddit - بأن يستخدموا محتواك ويعدّلوه ويعيدوا مشاركته بأي طريقةٍ شاؤوا طالما أنهم يشيرون إلى مساهمتك الأصلية ولا يستعملونها بصورة تجارية. كل المنصّات الرقمية تطلب منك إذنًا شبيهًا عندما تقوم باستخدامها، والمشكلة هي أنّ المستخدم غالبًا ما يوافق على شروط الاستخدام دون أن يقرأ المكتوب فيها. هذا بالنسبة لتعاملك من ناحية الشركات الموفّرة للخدمات، لكن من ناحية الأفراد فالأمر أصعب، لأنّ الأفراد قادرون على جمع معلوماتك وصورك، وحفظها في مجلّدٍ على أجهزتهم الشخصية وعدم إخبار أي أحد بذلك. ولن تعرف حتّى من هم ولماذا يحتفظون ببياناتك عندهم. لأجل هذا عليك اعتبار كل ما ترفعه على الشبكة من بيانات وملفّات صار منتشرًا عند كل الناس، ولا رجعة فيه. لذلك فكّر مرتين قبل أن ترفع صورك الشخصية أو تعلن عن آرائك الفكرية والسياسية في أي مكان، فلا تدري متى يخرج أحدهم بها ليحاول استخدامها ضدك. شيء مرعب ما يمكنني معرفته عنكدعونا نستعرض ما يمكننا كأفراد معرفته عن بعضنا البعض عبر المعلومات التي ننشرها على الشبكة. ما الحسابات والخدمات الرقمية التي يستخدمها أي مستخدم معاصر اليوم؟ لا بد من أن يستخدم بريدًا إلكترونيًا، وحساب فيس بوك وربما حساب تويتر أو حسابات على مواقع اجتماعية أخرى. ما الذي يمكنني معرفته عنك عبر حسابك على فيس بوك؟ يمكنني رؤية المنشورات العامّة التي تنشرها من نصوص وصور وفيديوهات، كما يمكنني غالبًا رؤية قائمة أصدقائك، والناس الذي يعلّقون عندك ويتفاعلون مع منشوراتك بالإعجابات والتعليقات والمشاركات. كان يمكنني ألّا أرّى شيئًا من هذا إن استخدمت إعدادات الخصوصية المناسبة، لكن للأسف معظم المستخدمين لا يستخدمونها ويتركون كل شيءٍ ليكون مكشوفًا للعموم. الآن إليك بعض الأشياء التي يمكنني معرفتها عنك عبر فيس بوك:
عبر تجميع كل هذه المعلومات مع بعضها البعض، يمكنني بناء ملف كامل حولك ومعرفة تفاصيل كنتَ لا تظن أن أيّ إنسانٍ غريبٍ عنك قد يعرفها. وقد تُستعمل هذه المعلومات لتعقّبك أو إبداء الأذية لك أو لاستغلالها ضدّك في نشاطاتٍ مختلفة من الحياة اليومية. كلّ ما سبق كان عبر استخدام فيس بوك لوحده، لكن كلما ازدادت المنصّات والخدمات التي تستخدمها على الشبكة، ازدادت معها قدرة الآخرين على معرفة المزيد من المعلومات حولك. وأهم هذه المعلومات هي بريدك الإلكتروني. يظن الكثير من الناس أنّه لا مشكلة في نشر بريده الإلكتروني على العلن، فبالنهاية ما الذي سيفعلون به؟ إنّه مجرد عنوان لاستقبال الرسائل! وهذا للأسف الشديد غير صحيح بالمرّة. عنوان بريدك الإلكتروني سيكشف كامل هويتك الرقمية إن استعملته على أي منصّة رقمية تنشره. على سبيل المثال وهذا من تجربتي الشخصية، حيث كانت الجامعات في تركيا مثلًا تنشر ملفّات PDF بقوائم المقبولين والمرفوضين لديها كل سنة. وللأسف لا يمنعون فهرسة هذه الملفّات من قبل محركات البحث، فكانت هذه الملفّات تظهر بسهولة عند البحث عن اسم الشخص أو بريده الإلكتروني في جوجل، فتظهر لك كل الجامعات التركية التي أرسل لها صاحب البريد الإلكتروني هذا طلب قبولٍ لديها! ستظهر كل الخدمات الأخرى التي تعرض بريدك الإلكتروني للعلن عند البحث عنها في أي محرك بحث. وهكذا يُمكن لأي شخص أن يحصّل المزيد من المعلومات عنك. ومن الأشياء المهم ملاحظتها حول البريد الإلكتروني هو أنّه عبر إزالة اسم البريد وعلامة @ منه (أي عبر البحث عن testuser بدلًا من [email protected]) ستظهر المزيد من النتائج عن معظم الناس في محرّكات البحث، وهذا لأن الكثير من الناس في الغالب يستخدمون نفس اسم المستخدم الخاص ببريدهم الإلكتروني كاسم مستخدم كذلك لحساباتهم على فيس بوك وتويتر وغيرها من الخدمات الأخرى. وهذا ما يسبب سهولة العثور عليها جميعًا في نفس عملية البحث. تويتر قصّة أخرى. إذا كان لديك حساب على تويتر فيمكن لأي إنسان أن يستعرض التغريدات التي قمتَ أنت بالإعجاب بها، أو قمتَ بالرد عليها من حسابك على تويتر مباشرةً. يُمكن كذلك عبر ميّزة البحث المتقدم في تويتر أن يستعرض أي إنسان ردودك على ردود حسابات معيّنة؛ فإذا كنت تتفاعل بكثرة مع حساب معيّن مثلًا وقد لاحظ الشخص الذي يبحث عن معلوماتك هذا، فحينها يمكنه أن يقرر رؤية ردودك على تغريدات ذاك المستخدم بالتحديد. وكما الأمر في كلّ المنصات الاجتماعية، حيث لا يمكنك منع الآخرين من الحديث عنك. ربّما قام حساب الجامعة الرسمي التي تدرس فيها مثلًا أو حساب الشركة التي تعمل فيها أو حساب لأي شخص آخر بنشر صورة تكونُ موجودًا فيها ويَذكر اسمك أيضًا. فهكذا تصبح صورك ومعلوماتك متوفرة بيد الآخرين دون أن يكون لك يد في الموضوع. وبمجرّد البحث عن اسمك في أي محرّك بحث فستظهر معلوماتك. هناك أيضًا محرّكات بحث متخصصة للبحث عن أشخاص أو بيانات معينة لهم، على عكس محركات البحث العامّة مثل جوجل وBing مثلًا، فلا تظن أنّه بمجرد عدم العثور على نتائج عنك على جوجل فإنه لا يوجد شيءٌ عنك كذلك. عليك الحرص كذلك على التعليقات والمقالات التي تنشرها في الشبكة، وخصوصًا التعليقات. يظن البعض أن التعليقات التي يكتبها على مواقع الإنترنت سواءٌ العربية منها أم الأجنبية لا يمكن الوصول إليها سوى عبر المقال نفسه، لكن محرّكات البحث تؤرشفها كذلك. ولهذا فإنّ كل التعليقات التي تدلي بها على المدونات والموسوعات والمواقع الإلكترونية الأخرى… كلّها ستكون ظاهرة عبر البحث عن اسمك. سنتحدث في الفصل اللاحق عن ضرورة استخدام أسماء وهمية في بعض المنصّات وعدم استخدام الاسم الحقيقي. لكن نريد التنويه بصورة سريعة هنا إلى أن كل الحسابات التي تستخدمها على الشبكة وتستعمل فيها نفس الاسم هي حسابات عليك أن تعتبرها بيد كل الناس الذين تراهم في الشارع حولك. لأنّهم جميعًا قادرون على الوصول إليها عبر مجرد البحث عن اسمك بالإنجليزية أو العربية أو أي لغة أخرى. هوية الإنترنت الوهميةمن الأمور التي يقوم الكثير من الناس بها هي أنهم يفصلون بين هويّاتهم المختلفة على الإنترنت. فتجدهم عندما يتصفّحون مواقع مثل Reddit أو حتى فيس بوك وتويتر، يستعملون أسماء وبيانات وهمية لا تعبّر عن هويتهم الحقيقية. بعضهم يفصل بين الحياة المهنية والترفيهية، وبعضهم يستعمل أسماءً وهمية للحديث في مواضيع جدلية في بعض الأماكن وغير ذلك. عليك أنت أن تقرر كذلك ما نوعية الهوية التي تريد استخدامها على الإنترنت؟ الهوية الوهمية تمنحك خصوصية وأمانًا أكبر، فالآن اسمك صار وهميًا ولا أحد يعرف من أنت (باستثناء الدولة التي تعيش بها بالطبع، لأنّك تستعمل مزوّد خدمة الإنترنت الخاص بها، ما لم تقم بإجراءات للتخلص من ذلك، وباستثناء المنصّة أو الخدمة التي تتصفحها فهي لديها عنوان الآي بي الحقيقي الخاص بك، ويمكن لهذين الاثنين أن يتعاونا لكشف هويتك). إنّك بالطبع تخسر الكثير عندما تشارك على الإنترنت بأسماء وهمية (إنشاء علاقات مع الآخرين باسمك الحقيقي، ونسب مساهماتك لك أنت ونشر اسمك بين الناس.. إلخ)، لكن فكّر في عواقب ما تنشره كذلك وهل من المناسب أن يلتصق باسمك وهويتك الحقيقية أم لا؟ إن كان الجواب لا، فحينها عليك استخدام هوية وهمية، والقيام بعددٍ من الإجراءات الأخرى كذلك لحماية نفسك. تذكّر أن الاسم الوهمي لا يحميك لا من الدولة التي تعيش بها ولا من صاحب الخدمة أو الموقع الذي تزوره. وهذا لأنّ:
لن نشرح في هذه السلسلة طريقة استخدام VPN. لكن يمكنك ببساطة البحث عن VPN مناسب لك على الإنترنت، وحاول دومًا أن يكون مقابل اشتراك مدفوع، فالمجاني منها غير مناسب. تقييم المخاطر والرغبة في الحمايةعليك الآن أن تتخذ قرارًا حول درجة الأمان والخصوصية التي تريد الحفاظ عليها. هل تريد مثلًا ألّا يكون هناك أي معلومة أو صورة عنك على الإنترنت على الإطلاق؟ هل تريد أن تفصل حياتك المهنية عن حياتك الترفيهية وتستعمل أسماء وهمية مختلفة؟ هل تخاف من نشر مقالٍ ما لأيّ سببٍ من الأسباب؟ هل أنت على وشك الدخول للسياسة حيث كل معلومة بسيطة عنك قد تُستخدم ضدك في المستقبل من المنافسين؟ الكل عليه محاولة الحفاظ على أمانه الرقمي، لكن إلى أي درجة؟ هذا يختلف بالطبع حسب حالتك. وهذا مهم لأن طرق الحماية والتأمين ستختلف كذلك، وكلّما أردت حماية وخصوصية أعلى، كانت التكاليف من وقت وجهد وصعوبة في الاستخدام أعلى وأكثر. ومن المهم كذلك أن تحدد ضد من تريد الحماية؟ هل تريد حماية نفسك من الحكومة، أم الشركات الأجنبية التي تستعمل خدماتها مثل فيس بوك وجوجل، أم من الأفراد والمخترقين الخارجيين، أم من أصحاب المواقع التي تزورها، أم من ماذا بالتحديد؟ من الذي يزعجك ويخيفك من بين هؤلاء؟ لجعل الموضوع أكثر بساطةً، فكّر بهدوء ثمّ أجب عن الأسئلة التالية:
إجابتك على الأسئلة السابقة مهمة وأنت تستعرض فصول هذه السلسلة، حيث سيجب عليك أن تقرر بنفسك: "هل هذا شيء أريد تطبيقه أم لست بحاجته في حالتي"؟ تذكّر دومًا أن الخصوصية والأمان لا يأتيان بالمجان دون مجهود أو تعب. ختامًاالوعي مهم جدٌا في كل نشاطاتك التي ستقوم بها عبر الشبكة، وهو أوّل طبقة حماية وأمان لك أمام العالم الخارجي. تذكّر أنّ هذه النصائح كانت لوضعك على بداية الطريق فقط، أمّا الباقي، من تعلّم المزيد من المفاهيم وتجنّب الأخطاء الشائعة هو عليك أنت. |