السلسة الذهبية في المواضيع العلمية [ لماذا يجب أن نعتني بفهم شفراتنا ؟ ]
بسم الله الرحمن الرحيم
السلام عليكم و رحمة الله وبركاته
مرة أخرى نعود مع سلسلتنا العتيقة [ السلسلة الذهبية في المواضيع العلمية ] ليس بموضوع جديد بل إستكمالاً و تعزيزاً لموضوع سبق و الذي كان بعنوان ( كيف تحل مشاكلك بنفسك ؟ ) .. فقد عبر لي البعض عن رغبتهم بالمزيد من الامثلة تحفيزاً للقدرات وشحناً للهمم .. و ربما كان من الافضل في رأيي لو غيرنا الموضوع قليلاً تعميماً للفائده بإذن الله تعالى
موضوعنا اليوم ينصب في التعمق في بعض الحلول البرمجية و المقارنة بين الشفرات وتحسين الإداء العام لإكوادنا مع مراعة الفهم العميق للفروقات بين الشفرات و سنحاول تسليط الضوء بشكل لن اقول مفصلاً بالكامل و لكني سأقول مفصلاً بقدر إعانه الله لنا سبحانه و تعالى على العديد من الأمثلة البسيطة ...
مشكلة عدم توفر المعلومة و توظيف الخبرات
في ايامنا هذه اصبح غريبا ان يقول المرء ان المعلومة غير متوفر لديه بالشكل الكامل او ان هناك نقصاً في الموراد المعلوماتية خاصة مع نعمة الله الذي منّ بها على عباده في ايامنا هذه وهي ( الإنترنت ) ... لهذا فالمشكلة دائماً تكون في عدم اتاحة المجال للعقل البشري لمحاولة الإسترسال في استحظار الحلول و تعويده على شيء من الكسل و حب الإجابات السريعة و التي لا يوجد لها ما يبررها ( إلا إن كان المرء مقيداً بإتمام عمله في فترة زمنية معينة شارفت على الإنقضاء ) اما ما سوى ذلك فمن الجميل ان نستبدل كلمتنا المشهورة التي نستخدمها عند كل مشكلة برمجية ( لا أعرف الحل ! ) فنجعلها ( سأحاول أن اوجد حلاً ) ... بتغييرك الطفيف للكلمات السابقة يجعل باب الامل ينفتح من جديد و نعطي الفرصة لرؤوسنا كي تجرب حظها في إبتكار حلول قد تكون جميلة وقد تكون فظيعة (بائسة) غير ان الرائع بالتأكيد هو مجرد المحاولة ... دعونا نضرب مثالاً.
لنفرض مثلاً انني احتجت في أحد برامجي الى معرفة إسم او نوع النظام الذي يعمل عليه برنامجي ( Windows 95,98,Me,NT,2000, XP ) كي اقوم بأشياء معينة تترتب على نوع النظام ...
لتأدية هذا الأمر ينبغي عليك ان تحيط علماً ببعض دوال الـ API كي تعرف نظامك المستخدم وقد تكون شفرتك كالتالي :
Private Declare Function GetVersionExA Lib "kernel32" (lpVersionInformation As OSVERSIONINFO) As Integer Private Type OSVERSIONINFO dwOSVersionInfoSize As Long dwMajorVersion As Long dwMinorVersion As Long dwBuildNumber As Long dwPlatformId As Long szCSDVersion As String * 128 End Type Function WindowsName() As String Dim OS As OSVERSIONINFO OS.dwOSVersionInfoSize = Len(OS) OS.szCSDVersion = Space(128) Call GetVersionExA(OS) If OS.dwPlatformId = 1 Then ' 95,98 Or Me Select Case OS.dwMinorVersion Case 0: WindowsName = "Windows 95" Case 10: WindowsName = "Windows 98" Case 90: WindowsName = "Windows Me" End Select ElseIf OS.dwPlatformId = 2 Then ' NT,2000 Or XP Select Case OS.dwMajorVersion Case 3: WindowsName = "Windows NT 3.51" Case 4: WindowsName = "Windows NT 4.0" Case 5: WindowsName = IIf(OS.dwMinorVersion, "Windows XP", "Windows 2000") End Select End If End Function
في شفرتنا السابقة استخدمت انا دالة الـ API الخاصة بهذا الأمر وهي GetVersionExA و بالتالي فإن الكود سيعمل بإذن الله تعالى .. و لكن اذا صادف اننا لم نكن نعلم بهذه الدالة الخاصة بهذا العمل فهنا ينبغي علي محاولة الإستنتاج المنطقي للحل.
يعني دعونا نحاول القيام بالسابق بشكل مختلف و بالتالي فإن طريقة تفكيرنا في الحل لابد و ان تختلف قليلاً .. و إن ركزنا قليلاً سنجد ان الوينذوز طالما احتفظ ببياناته في مسجل النظام او الـ Registry و بالتالي فإننا لو اعرناه شيئاً من الإهتمام لعرفنا ان هناك مفاتيح في الريجستري تحتوي على إسم نظام التشغيل و بالتالي يمكن بسهوله ان نقوم بإستغلال ذلك في حل مشكلتنا السابقة
فبعلمنا ان الوينذوز يتحفظ بإسمه ( اسم نظام التشغيل ) في احد مفتاحين اما المفتاح :
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ProductName
في حالة الانظمة 95 او 98 او ME ... او في المفتاح :
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductName
في حالة احد الانظمة NT, 2000, XP وبالتالي ينبغي مراعه ذلك في تصميمنا للحل ... و يمكن اقتراح الحل التالي :
Function WinRegistry() As String
Dim Shl As Object
On Error Resume Next
Set Shl = CreateObject("WScript.Shell")
WinRegistry = Shl.regRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\" & _
"Windows\CurrentVersion\ProductName")
If Err.Number <> 0 Then
WinRegistry = Shl.regRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\" & _
"Windows NT\CurrentVersion\ProductName")
End If
End Functionحيث تقوم الدالة السابقة بمحاولة القراءة من المفتاح الأول ( الخاص بالانظمة 95, 98, ME ) فإن حصل أي خطاء اي فان الـ Err.Number يحتفظ بقيمة الخطأ الذي حصل في الكود و بالتالي فان لم تكن القيمة هذه تساوي صفراً ( اي انه يوجد خطأ ) فمن الطبيعي عندها معرفة ان هذا المفتاح الذي حاولنا قرأته غير موجود اصلاً و بالتالي يمكن محاولة قرأة المفتاح الآخر وهو ما تقوم به الدالة السابقة.
ما ينبغي التركيز عليه اننا حتى و ان لم نكن نعرف دالة الـ API المسماه GetVersionExA فقد استطعنا بعون الله تعالى ان نوظف معلومتنا البسيطة عن الريجستري لحل الموضوع ..
و لكن دعنا نكون اكثر تشاؤماً و نفترض انه حتى تلك المعلومة عن الريجستري لم تكن بحوزتنا فما الذي ينبغي علينا القيام به للخروج من المأزق و الحصول على اسم نظام التشغيل ؟!!
عند هذه النقطة ينبغي ان نتوقف قليلاً و نشرب كوباً من الشاي مع الإستمرار بتقليب الامور في رؤوسنا عل الله ان يفتح علينا بشيء آخر جديد ...
نعم قد يخطر في بال احدنا ان نستفيد من تقنية الـ WMI إختصاراً لـ Windows Management Instrumentation و هي عبارة عن مجموعة من الفئات التي يمكنك بواسطتها معرفة الكثير من المعلومات عن نظامك الحالي او أي انظمة اخرى يمكن الوصول اليها عن طريق الشبكة مع توفر الصلاحيات اللازمة لذلك ... و يهمنا في هذا الأمر هو الفئة المسماه Win32_OperatingSystem ومن إسمها يمكن التنبؤ بانها تختص بمعلومات عن نظام التشغيل و بهذا يمكن ان نستخدم هذه في دالة بسيطة ننشئها قد تكون كالتالي :
Function WinWMI() As String
Dim WMI, System As Object
Set WMI = GetObject("WinMgmts://localhost").InstancesOf("Win32_OperatingSystem")
For Each System In WMI
WinWMI = System.Caption
Exit Function
Next
End Functionوبهذا نكون قد إستخدمنا طريقة اخرى مفيدة لحل هذه المسئلة البسيطة بحمد الله وتوفيقه سبحانه
لكن ما يشغلني الآن هل جميعنا يعرف هذه التقنية ( WMI ) اظن انه مهما بلغت درجة معرفتنا و إطلاعاتنا فلابد و أن شخص بيننا ينظر الى الكود السابق نظرته الاولى.
اذاً دعونا الآن نفترض اكثر الأمور سواءً حتى نخرج بفائدة واحدة ( مهما قلت درجة معرفتك فقط حــــــــــــــــــــــــاول ) و لنتخيل ان بيننا من ليست لديه أي إحاطة سوى بعلوماته عن نظام التشغيل DOS فقط ... بالطبع الى جانب معلوماته في الفيجوال بيسك ( لايمكن ابداً افتراض انه لايعرف الفيجوال ! لانه لن يكون قد كتب عنوان موقعنا هذا اصلاً و لم يكن ليصل الى هذه الصفحة التي نقرأها الآن ) ...
و بالتالي هل على شخص كهذا ان يفقد الامل نظراً لقدم معلوماته ( بالطبع لا ) ينبغي اولاً ان لا ييئس ابداً و ثانياً أن لا ينسى بأن يعمل تحديث لهذه المعلومات لاحقاً بإذن الله تعالى
المقصود هنا وعودة الى الوراء قليلاً و الى أيام الـ DOS فالكثيرين منا طالما استخدموا الامر Ver و المختص بمعرفة إصدار النظام المستعمل ... و بالتالي فما الذي يمنع استخدام هذا الامر من خلال الفيجوال بيسك لمعرف إسم الوينذوز !
طبعاً ليس هناك أي مانع بإذن الله تعالى و لكن ربما كان هناك عائق وهو ان اوامر الـ DOS تكتب مخرجاتها الى شاشة الدوز السوداء و بالتالي وجب معرفة كيفية استحظار هذه المعلومات الى برنامجنا ... و بما ان موضوعنا هذا غرضه الرئيسي هو الإستفادة بشتى الطرق فلا يوجد ما يمنع ان نحيد عنه قليلاً و نحدث ببضعه سطور عن كيفية توجيه المخرجات في الدوز
اذا قمت مثلاً بكتابة الامر التالي في شاشة الدوز :
Dir
فان المخرجات ستكون هي اسماء و بعض المعلومات عن الملفات و المجلدات في المجلد الحالي وجميع هذه البيانات يتم عرضها في الشاشة السوداء و لكننا اذا اردنا توجيه هذه المخرجات الى هذف معين ( لنقل ملف نصي اسمه data.txt ) فمن المفروض عندها ان نكتب التالي :
Dir >> c:\data.txt
بعد تنفيذك للأمر السابق فانك ستلاحظ ان النظام قد انشىء لك ملف على المسار C:\data.txt و فيه جميع البيانات التي كان ينبغي ان تعرض على الشاشة غير انها تم عرضها داخل الملف المذكور
و بالتالي ففي حالتنا نحن يمكن ان نستخدم الأمر ver بنفس الطريقة بحيث نقوم بتوجيه المخرجات الى ملف نصي مؤقت ثم نقوم باستخراج البيانات من داخل الملف الى داخل برنامجنا .. يعني في جهازي مثلاً اذا قمت بطباعة الامر VER في شاشة الدوز فاني النتاج هو :
Microsoft Windows XP [Version 5.1.2600]
و اذا استخدمت مفهوم توجيه المخرجات بحيث اكتب السطر التالي :
ver >> c:\data.txt
في الدوز فان نفس العبارة السابقة سوف يتم تخزينها في الملف data.txt وهذا بيت القصيد ..
بقي ان نعرف ان الملفات التنفيذية المسئولة عن تشغيل اوامر الدوز هي الملفين Cmd و Command حيث انه يتم استخدام الامر Command في الانظمة 95 ,98 ,ME بينما الامر Cmd في انظمة الـ NT بحيث يمكن تنفيذ الامر الذي تريده عن طريق الدالة Shell
فمثلاً لتنفيذ الامر Dir من داخل الفيجوال بيسك قم بالتالي :
Shell "Cmd /c Dir",vbNormalFocus
او بالطبع في الانظمة الاخرى
Shell "Command /c Dir",vbNormalFocus
واذا اردنا توجيه المخرجات للامر بحيث تصب في ملف نصي يمكن ان نكتب التالي :
Shell "Cmd /c Dir>>c:\data.txt", vbNormalFocus او Shell "Command /c Dir>>c:\data.txt", vbNormalFocus
ولكي لا نزعج المستخدم بظهور الشاشة امامه بحيث يتم تنفيذ العمل دون علمه يمكن اخفاء شاشة الدوز بالشكل التالي :
Shell "Command /c Dir>>c:\data.txt", vbHide او Shell "Cmd /c Dir>>c:\data.txt", vbHide
اذا الحل المقترح هنا هو ان نقوم باستخدام الامر ver مع توجيه المخرجات الى ملف نصي ثم فتح هذا الملف النصي و استخراج البيانات منه يلي ذلك القيام بحذف الملف بعد قرأه محتوياته و عرضها اخيراً على المستخدم الذي لن يعلم بما كان يدور في الخفاء ... و الشفرة التي اقترحها مثل :
Function WinDOS() As String
Dim X As Long, S As String
On Error Resume Next
If Dir("C:\Ver.txt") <> "" Then Kill "C:\Ver.txt"
Call Shell("Cmd /c VER>>C:\Ver.txt", vbHide)
If Err.Number <> 0 Then Call Shell("Command.Com /c VER>>C:\Ver.txt", vbHide)
Wait:
Do
DoEvents
Loop Until Len(Dir("C:\Ver.txt"))
DoEvents
X = FreeFile
Open "C:\Ver.txt" For Input As X
Line Input #X, S
Line Input #X, S
Close X
If S = "" Then GoTo Wait
Kill "C:\Ver.txt"
WinDOS = Left$(S, InStr(S, "[") - 1)
End Functionسوف انهي هنا هذا المثال الطويــــــــــــــــــــــــــــــــــــــــــــــل على أمل ان لايكون الملل قد تسرب الى نفس القارىء و اكتفي بالقول حبذا لو استغل كلاً منا معلوماته مهما قلت في البحث عن الحل ثم لابأس بعد ذلك ان يستعين بأي احد عند انقطاع الحيلة
ايهما افضل الاكواد الطويلة او الشفرات القصيرة التي تؤدي نفس العمل
يخطىء الكثير في تقييم الكود عن طريق عدد السطور المستخدمة خاصة اذا كانت المخرجات واحدة ! ... حيث ان كفاءة الكود تقاس دائماً بالزمن المستغرق و اداء الكود و استخدامه لموارد النظام و الإحاطة بكافة الإحتمالات المتوقعة والكثير الكثير من التفاصيل و يأتي اخيراً عدد السطور اذا تساوت جميع النقاط السابقة .. فمثلا في مثالنا السابق وضعنا سوياً اربعة حلول قد تكون جميلة و لكن اذا اردنا التقييم فمن افضلها ؟!
في الواقع ان الافضل هو الكود الاول نتيجة انه يستخدم دالة الـ API التي تم تعريفها في الملف kernel32.dll وهو احد الملفات الرئيسية التي يعتمد عليها الوينذوز و بالتالي فانك تضمن توفرها في جميع الانظمة ضمنياً اي ان شفرتك ستعمل بإذن الله في اغلب الاحوال !
الحل الثاني و المعتمد على الريجستري في الواقع هو حل ظريف ايضاً غير انه يعتمد على على قيمة موجودة في الريجستري و نعلم ان تغيير قيم الريجستري هو امر هين سواءً بالكود او يدوياً فلو ان احدهم عدل القيمة الموجودة في الريجستري بحيث تصبح "arabTeam2000 Operating System" فانك سوف تجد ان الدالة تعيد لك قيمة مفادها ان نظام الوينذوز المستخدم لديك هو نظام تشغيل الفريق العربي !! ( مبارك النظام الجديد شباب ) ... وهذا و إن كان شيئاً نادراً حدوثه ان يقوم احدهم بتعديل هذه القيمة و لكنه ايضاً سبب وجيه في فشل الدالة في عملها و بالتالي فانه احد عيوب الدالة
الحل الثالث يعتبر الاول من حيث عدد السطور البرمجية القليلة ( و التي ليست مقياس كما اتفقنا سابقاً ) و لكنه سيء جداً من ناحية الموثوقية و ذلك ان تقنية الـ WMI هي تقنية ضمنية موجودة في الانظمة ME, NT, 2000, XP غير انها ليست كذلك في الانظمة 98 و 95 و ينبغي ان يتم تحميلها اولاً على النظام حتى تعمل شفرتنا بشكل سليم ! ... و بهذا فان هذا العيب القاتل يضيف نقطة سوداء كبيرة الى تقييمنا لهذا الحل
و اخيراً الاجابة الاقدم و الاخيرة ( الاعتماد على الامر Ver في الـ DOS ) وهي اجابة تعمل بشكل جيد على الرغم من كونها ليست عملية كفاية من حيث اعتمادها برامج خارجية حيث ان الامر Shell يشغل برامج خارج نطاق الفيجوال بيسك و بالتالي فالكثير من الاخطاء يمكن حدوثها خارج الفيجوال بيسك قد تؤدي الى فشل العملية منها مثل عدم امكانية الكتابة الى القرص الصلب لأي سبب من الاسباب او أي خطأ غير متوقع يعيق إنشاء الملف المؤقت الذي قمنا بتوجيه مخرجات الامر Ver اليه سيؤدي بالتالي الى فشل دالتنا .. كما انه من غير المستحب ابداً اعتماد شفراتك على اوامر بهذا القدم ( الامر Ver ) فلا يعلم احدنا الى أي مدى سيبقى دعم هذا الامر موجوداً .. فربما ثم التخلي عنه في وقت قريب و الله سبحانه و تعالى اعلى و اعلم !
الى اي مدى يفيدنا تحليل الشفرات
لا اظن ان بيننا من يقول انه من غير المجدي ان يتم دراسة شفرتنا وتحليلها للوصول الى افضل طريقة ولكن الذي يختلف من شخص الى آخر هو تقدير اهمية القيام بذلك .... فلو انه تم طلب تصميم برنامج من احدنا فأنه يسعى بالمقام الاول ان ينتج اكوادا تقوم بالمطلوب منها و لكن كثيراً ما يتم إغفال عملية التأكد من كون هذه الاكواد التي طلبناها هي افضل اكود تقوم بذلك !
دعونا نتسلى بمثال بسيط ... فلكي نملىء قائمة ( ListBox ) بعناصر كثيرة ( لنقل 25000 ) عنصر فاننا سوف نكتب كود كالتالي :
Dim I As Long For I = 1 To 25000 List1.AddItem I Next
فإن هذه العملية هي عملية بسيطة ولكنها تأخد وقت طويل حيث ان جهازي ( الذي هو بمواصفات 256MB رام .. و معالج بسرعة 800MHz ) فقد استغرقت عملية ملىء القائمة بـ 25000 عنصر وقت مقداره ( 9 ثواني كاملة ! ) ... وهو رقم مهول للغاية فمن أين لك بمستخدم لبرنامجك ( في هذا الزمان السريع للغاية ) يمتلك طول بال لهذا الحد !
و لكي تختبر سرعة الكود السابق في برنامجك عدل الكود كالتالي :
Dim I As Long Dim T As Single T = Timer For I = 1 To 25000 List1.AddItem I Next MsgBox Round(Timer - T)
و الان كيف يمكننا ان نحسن الكود السابق بحيث نقتصد في وقت مستخدم برنامجنا ( و نقنعه بعدم رمي البرنامج في سلة المحذوفات بعد اول إستخدام ) .... في الواقع إن القيام بذلك يتم بفهم عمل مربع القائمة جيداً ! ... فلو انك اضفت السطرين التالين الى البرنامج :
Dim I As Long Dim T As Single T = Timer List1.Visible = False For I = 1 To 25000 List1.AddItem I Next List1.Visible = True MsgBox Round(Timer - T, 1)
السطران الذي اضفناهما ( List1.Visible = False & List1.Visible = True ) جعلا الزمن المتطلب لملىء القائمة ينخفض من 9 ثواني إلى أقل من ثانية واحدة !
و السبب هنا يعود الى عمليات التحديث ... إذ انه بعد كل عملية إضافة بإستخدام AddItem فإن مربع القائمة يقوم بتحديث نفسه ( Refersh ) بحيث يظهر العنصر الجديد وايضاً لتغيير طول شريط التمرير ( Scroll Bar ) مع إزدياد طول القائمة ..... و هذا كله يحتاج الى وقت فليس من الهين ان تقوم بـ 25000 علمية تحديث امام عيني المستخدم ... و بالتالي فإنه بجعل القيمة Visible تساوي False قبل الإضافة الى القائمة و ارجاعها الى True بعد الإنتهاء يقوم بإخفاء القائمة و بالتالي فلن يضطر الفيجوال بيسك الى إعادة تحديث القائمة إلا مرة واحدة فقط عندما نقوم بإظهار القائمة مرة أخرى عن طريق Visible = True ...
يظهر بوضوح ان ابسط التعديلات في الكود قد تؤدي الى نتائج غير متوقعة على اداء البرامج التي نصممها
ولو ان في نفسك شيئاً من حب الإستزادة من الامثلة دعني و إياك نجرب المثال التالي :
لو اننا احتجنا الى تكوين دالة بسيطة تقوم بعمل سلسلة نصية مكونة من اسم شخص متبوعاً برقم متسلسل لعدد كبير من المرات ( لنقل 25000 مرة ) يعني مثلاً :
Rgheed1,Rgheed2,Rgheed3,.....,Rgheed24999,Rgheed25000
و لتنفيذ هذه المهمة البسيطة للغاية يمكن إستخدام كود كالتالي :
Dim I As Long Dim T As Single Dim S As String T = Timer For I = 1 To 25000 S = S & "Rgheed" & I & "," Next MsgBox Round(Timer - T, 1)
وهو كود جميل و بسيط وقام بالمطلوب منه بشكل جيد و نتج عنه متغير S فيه سلسلة حرفية تحتوي على النص المطلوب تماماً ... و لكن الشيء المقزز فعلاً هو الوقت الذي استغرقته لدالة السابقة للقيام بهذه المهمة حيث أنه وبإستخدام جهازي نفسه ( ذي المواصفات المذكورة اعلاه ) كان الوقت المستهلك هو 138 ثانية ( أي ما يزيد عن الدقيقتين بقليل ! ) وهو زمن اقل ما يمكن ان يوصف به هو ( فضيـــــــــــــــــــــــــــــــــــــــــــــع ) و لا اخفيك أني هممت أن اقوم بإيقاف البرنامج قبل ان ينهي عمله نتيجة للمل الذي اصابني من انتظاره .
و إن كان الكود في المثال الذي سبق مثالنا الأخير قد استغرق 9 ثواني ( فقط ) ومع ذلك فلم نحتمله وقمنا بتحسينه ليصبح اقل من ثانية واحدة فإننا هنا ( يجب ) ان نحسن مثالنا هذا ولا ندعه بالصورة التي هو عليها الآن
و للقيام بذلك يمكن اتباع طرق مختلفة كلاً بحسب الطريقة التي سنفكر بها ... و دعني هنا اقترح و احدة و هي ان نعدل الكود قليلاً ليصبح :
Dim I As Long Dim T As Single Dim S As String Dim Temp(1 To 25000) As String T = Timer For I = 1 To 25000 Temp(I) = "Rgheed" & I Next S = Join(Temp, ",") MsgBox Round(Timer - T, 1)
الكود السابق لم يقم بتقليل وقت الإنتظار و لكن يمكنك القول بأنه ( قضى ) على الإنتظار و جعل الزمن الكلي لإداء العملية السابقة وللحصول على متغير يحتوي على تلك السلسلة هو اقل حتى من ربع ثانية او على وجه الدقة ( 0.1 من الثانية ! ) فأنت لا تكاد تشغل الكود حتى ينتهي من اداء عمله !
و ما قمت به هنا امر معروف منذ زمن طويل فهو تعديل بسيط للغاية سيفكر به كل من اعطى الامر لحظات من الإهتمام والتركيز للبحث عن تحسين مناسب .. ولكي نفهم الفرق بين الكودين ينبغي اولاً ان نعرف العمل الذي يقوم به الكود الأول
إذ انه يقوم بتعديل قيمة المتغير S لعدد 25000 مرة و هذا ليس سيئاً في المراحل الأولى التي يكون فيها حجم المتغير S صغيراً ولكنه يعتبر كارثه حقيقة عندما يزداد حجم المتغير تدريجاً إذ ان كل دورة من الدورات الـ 25000 تزيد حجمه بمقدار اقله 8 بايت ( وهو ناتج عن طول الكلمة Rgheed وهو 6 حروف بالاضافة الى طول الرقم الذي يليها واخيرا طول الفاصلة وهو 1 ) واقصى حجم للزيادة هو 12 بايت ... و بحسبة بسيطة يظهر لنا ان طول المتغير يزداد من الصفر وحتى يصبح 282 كليوبايت ( او مايساوي 288894 بايت ) وهو عدد مهول للغاية اذا ما فهمنا انه سوف تتم معالجته لاكثر من عشرين ألف مرة ( 25000 ) . يعني بحسبة بسطية فأن الخمسة الف مرة الاخيرة من الدوران و اذا افترضنا ان حجم المتغير سيكون فيها متوسطاً حوالي 250 كليوبايت فانك ستجبر برنامج في الخمسة الف دورة الاخيرة على معالجة اكثر من 1.2 جيجا بايت من البيانات !! وهذا في اواخر الخمسة ألف دورة من الدورات الخمسة والعشرين ألف دورة !
لهذا جاءت فكرة الكود الثاني و التي تقوم اولاً بحجز مصفوفة نصية فيها 25000 عنصر بالشكل التالي :
Dim Temp( 1 To 25000) As String
ثم نقوم في كل دورة من الدورات بتعيين قيمة لأحد هذه العناصر ( من عناصر المصفوفة ) و بالتالي فهي جميعاً عناصر صغيرة جداً لايزيد حجم اكبرها عن الـ 11 بايت ... كما ان مقدار الذاكرة يتم حجزة مرة واحدة عند تعريف المصفوفة ... وبعد الإنتهاء من تعيين قيم العناصر جميعاً ... قمنا بتجميعها في متغير نصي واحد S عن طريق الدالة Join التي تقوم بالتجميع
و بالتالي فان التعامل مع الذاكرة في الحالة الثانية كان كفوءً للغاية بعكس الكود الأول الذي يقوم بتغيير حجم المتغير S لآلاف المرات مع الإزدياد الرهيب لحجم المتغير ...بينما اكتفت الطريقة الثانية بالتعامل مع المتغير S ذو الحجم الكبير مرة واحدة فقط ( عند التجميع ) بينما مرات الدوران الكثيرة كانت تتعامل مع متغيرات صغيرة جداً ( هي عناصر المصفوفة ) .
و بالتالي فقد تم اختصار ما يزيد عن الـ 137 ثانية وذلك بزيادة سطران فقط ! في الكود مع اختلاف طريقة تفكيرنا في الحل ... ولك ان تجرب تغيير عدد الدروات بدلاً من 25000 لترى الفروقات
خاتمة
اتمنى بالفعل ان نخرج من هذا المقال بحرص شديد على العناية اكثر بفهم اكوادنا البرمجية لتحسين اداءها بإذن الله تعالى
السلام عليكم و رحمة الله و بركاته




