<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x644;&#x63A;&#x629; Go</title><link>https://academy.hsoub.com/programming/go/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x644;&#x63A;&#x629; Go</description><language>ar</language><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x642;&#x648;&#x627;&#x644;&#x628; Templates &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648; Go</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-templates-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r2207/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_12/---Templates----Go.png.3b4bfdaaf8576fab381071e30dcd0d12.png" /></p>
<p>
	إذا كنا بحاجة إلى عرض البيانات بتنسيقات منظمة مثل التقارير النصية أو صفحات <a href="https://academy.hsoub.com/programming/html/%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-html-r1702/" rel="">HTML</a>، فإن قوالب <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a> توفر حلًا فعالًا. تتضمن مكتبة لغة جو القياسية حزمتين تسمحان لأي برنامج مكتوب في هذه اللغة بتقديم البيانات بطريقة منسقة بدقة، وهما <a href="https://pkg.go.dev/text/template" rel="external nofollow"><code>text/template</code></a> و <a href="https://pkg.go.dev/html/template" rel="external nofollow"><code>html/template</code></a>.
</p>

<p>
	يمكننا باستخدام هذه الحزم إنشاء قوالب نصية وتمرير البيانات فيها لتصيير render مستندات مصممة خصيصًا لمتطلباتنا. توفر القوالب المرونة في التكرار على البيانات باستخدام الحلقات وتطبيق المنطق الشرطي لتحديد محتوى ومظهر كل عنصر. نستكشف في هذا المقال كيفية استخدام كلتا حزم القوالب. نستخدم في البداية حزمة <code>text/template</code> لإنشاء تقرير نصي عادي من خلال الاستفادة من الحلقات والعبارات الشرطية والدوال المخصصة. نستخدم بعد ذلك <code>html/template</code> لتصيير مستند HTML مع ضمان الحماية ضد الثغرات الأمنية في إدخال التعليمات البرمجية.
</p>

<h2 id="">
	المتطلبات الأولية
</h2>

<p>
	1لمتابعة هذا المقال التعليمي، سنحتاج إلى:
</p>

<ul>
	<li>
		إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a> لإعداده.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
	<li>
		إنشاء <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%A8%D9%86%D9%89-structs-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1976/" rel="">البنى Structs في لغة جو</a> و<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-methods-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1977/" rel="">تعريف التوابع في لغة جو</a>.
	</li>
</ul>

<h2 id="1texttemplate">
	الخطوة 1- استيراد حزمة text/template
</h2>

<p>
	لنفترض أننا نريد إنشاء تقرير بسيط عن بيانات الكلاب التي لدينا. تنسيق التقرير المطلوب هو كما يلي:
</p>

<pre class="ipsCode">---
Name:  Jujube

Sex:   Female (spayed)

Age:   10 months

Breed: German Shepherd/Pitbull

---
Name:  Zephyr

Sex:   Male (intact)

Age:   13 years, 3 months

Breed: German Shepherd/Border Collie
</pre>

<p>
	نحتاج لإنشاء هذا التقرير باستخدام حزمة <code>text/template</code> إلى استيراد الحزمة اللازمة وإعداد المشروع. يتألف التقرير من نص ثابت في القالب (العناصر على اليسار) وبيانات ديناميكية نمررها إلى القالب لتقديمها (على اليمين). يمكن تخزين القوالب مثل متغيرات من النوع <code>string</code> ضمن الشيفرة أو ملفات منفصلة خارج الشيفرة. تحتوي القوالب على نص ثابت معياري متداخل مع عبارات شرطية <code>else</code> و <code>if</code> وعبارات التحكم في التدفق (الحلقات) واستدعاءات الدوال، وكلها محاطة داخل أقواس معقوصة <code>{{. . .}}</code>. أخيرًا يمكننا إنشاء المستند النهائي من خلال توفير البيانات للقالب كما في المثال أعلاه.
</p>

<p>
	ننتقل الآن إلى مساحة العمل الخاصة بنا "go env GOPATH" وننشئ مجلدًا جديدًا لهذا المشروع، ثم ننتقل إليه. يمكن إجراء ذلك من خلال التعليمات التالية بالترتيب:
</p>

<pre class="ipsCode">$ cd `go env GOPATH`
$ mkdir pets
$ cd pets
</pre>

<p>
	باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">محرر نانو nano أو أي محرر آخر تريده</a>، نفتح ملفًا جديدًا يسمى "pets.go":
</p>

<pre class="ipsCode">$ nano pets.go
</pre>

<p>
	ونضع فيه التعليمات التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4991_6" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"text/template"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تنتمي الشيفرة السابقة إلى الحزمة <code>main</code>، وتتضمّن الدالة <code>main</code> التي تسمح بتنفيذ الشيفرة باستخدام الأمر <code>go run</code>. تستورد الشيفرة حزمتين، هما: <code>text/template</code> من مكتبة جو القياسية، والتي نستخدمها لكتابة القالب وعرضه، وحزمة <code>os</code> للتفاعل مع نظام التشغيل من خلال الدوال التي توفرها.
</p>

<p>
	بذلك تكون الأمور جاهزة لبدء كتابة المنطق اللازم لإنشاء التقرير المطلوب باستخدام حزمة <code>text/template</code>.
</p>

<h2 id="2">
	الخطوة 2- إنشاء بيانات القالب
</h2>

<p>
	بدايةً يجب أن يكون لدينا بعض البيانات لتمريرها إلى القالب، لذا سنعرّف بنيةً تسمى <code>Pet</code> تمثل خصائص حيوان أليف. تحتفظ هذه البنية ببيانات كل كلب في التقرير.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4991_8" style=""><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
type </span><span class="typ">Pet</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln">   string
    </span><span class="typ">Sex</span><span class="pln">    string
    </span><span class="typ">Intact</span><span class="pln"> </span><span class="kwd">bool</span><span class="pln">
    </span><span class="typ">Age</span><span class="pln">    string
    </span><span class="typ">Breed</span><span class="pln">  string
</span><span class="pun">}</span></pre>

<p>
	نُنشئ أيضًا شريحةً من <code>Pet</code> لتخزين معلومات كلبين:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4991_10" style=""><span class="pln">func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    dogs </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="typ">Pet</span><span class="pun">{</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">   </span><span class="str">"Jujube"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">    </span><span class="str">"Female"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Intact</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Age</span><span class="pun">:</span><span class="pln">    </span><span class="str">"10 months"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Breed</span><span class="pun">:</span><span class="pln">  </span><span class="str">"German Shepherd/Pitbull"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">   </span><span class="str">"Zephyr"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">    </span><span class="str">"Male"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Intact</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Age</span><span class="pun">:</span><span class="pln">    </span><span class="str">"13 years, 3 months"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Breed</span><span class="pun">:</span><span class="pln">  </span><span class="str">"German Shepherd/Border Collie"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="com">// end main</span></pre>

<p>
	عرّفنا البنية <code>Pet</code> بحقول تمثل الخصائص المختلفة للحيوان الأليف. نستخدم هذه البنى للاحتفاظ ببيانات كل كلب في التقرير. تتضمن الحقول: اسم الحيوان الأليف <code>Name</code> وجنس الحيوان الأليف <code>Sex</code> وقيمة منطقية تشير إلى ما إذا كان الحيوان الأليف سليم <code>Intact</code> وعمر الحيوان الأليف <code>Age</code> والسلالة <code>Breed</code>.
</p>

<p>
	أنشأنا داخل الدالة <code>main</code> شريحة <code>Pet</code> باسم <code>dogs</code> وملأناها بنموذجين من كلاب مختلفة. الكلب الأول يُسمّى <code>Jujube</code> والكلب الثاني <code>Zephyr</code>. من المهم ملاحظة أنه في سيناريو العالم الحقيقي يمكن جلب بيانات القالب من قاعدة بيانات أو الحصول عليها من واجهة برمجة تطبيقات خارجية أو توفيرها من خلال إدخال المستخدم، لكن هنا أدخلنا البيانات يدويًا.
</p>

<p>
	يمكننا الآن المتابعة إلى الخطوة التالية لكتابة القالب وعرضه.
</p>

<h2 id="3">
	الخطوة 3- تنفيذ وعرض بيانات القالب
</h2>

<p>
	حان الوقت الآن لاستكشاف كيفية استخدام حزمة <code>text/template</code> لتوليد مستند من قالب، ولكي نتأكد من أن الأمور تعمل بنجاح، سننشئ ملف قالب فارغ ثم نمرّر البيانات إلى القالب لتنفيذه. على الرغم من أن النموذج الأولي لن يعرض سوى النص "Nothing here yet"، إلا أنه سيكون بمثابة نقطة بداية لتوضيح دوال الحزمة <code>text/template</code>.
</p>

<p>
	ننشئ ملف باسم <code>pets.tmpl</code> بالمحتوى التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5770_7" style=""><span class="typ">Nothing</span><span class="pln"> here yet</span><span class="pun">.</span></pre>

<p>
	نحفظ القالب ونخرج من المحرر. في حالة المحرر نانو nano، نضغط على المفتاحين "CTRL + X" ثم المفتاح "Y" و "ENTER" لتأكيد التغييرات. نضيف الآن مقتطف الشفرة التالي داخل <code>main</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4991_12" style=""><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
    var tmplFile </span><span class="pun">=</span><span class="pln"> </span><span class="pun">“</span><span class="pln">pets</span><span class="pun">.</span><span class="pln">tmpl</span><span class="pun">”</span><span class="pln">
    tmpl</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> </span><span class="kwd">template</span><span class="pun">.</span><span class="typ">New</span><span class="pun">(</span><span class="pln">tmplFile</span><span class="pun">).</span><span class="typ">ParseFiles</span><span class="pun">(</span><span class="pln">tmplFile</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        panic</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> tmpl</span><span class="pun">.</span><span class="typ">Execute</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="typ">Stdout</span><span class="pun">,</span><span class="pln"> dogs</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        panic</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="com">// main نهاية الدالة </span></pre>

<p>
	صرّحنا ضمن الدالة <code>main</code> عن المتغير <code>tmplFile</code> وأسندنا له القيمة <code>“pets.tmpl”</code>، والتي تمثل اسم ملف القالب. استخدمنا بعد ذلك الدالة <code>template.New</code> لإنشاء قالب من <code>template</code>، مع تمرير <code>tmplFile</code> اسمًا للقالب. استدعينا بعد ذلك <code>ParseFiles</code> في القالب الذي أنشأناه حديثًا، مع تمرير <code>tmplFile</code> مثل ملف لتحليله. تربط هذه الخطوة ملف القالب بالقالب.
</p>

<p>
	تحققنا بعد ذلك من أية أخطاء حدثت أثناء تحليل القالب. نلتقط الخطأ في حالة حدوثه، وتنتج لدينا حالة هلع panic في البرنامج.
</p>

<p>
	الآن لتنفيذ القالب نستدعي التابع <code>Execute</code>، ونمرر له وسيط أول <code>os.Stdout</code> ليكون وجهة الخرج ووسيط ثان <code>dogs</code> ليمثّل البيانات الممررة إلى القالب. يمثل <code>os.Stdout</code> (أو أي شيء آخر يحقق الواجهة <code>io.Writer</code>، أي ملف مثلًا) الخرج القياسي الذي سيطبع في هذه الحالة التقرير المُنشأ على الطرفية.
</p>

<p>
	سيؤدي تنفيذ القالب في هذه المرحلة إلى عرض النص المذكور أنفًا، وذلك لأننا لا نستخدم بيانات ديناميكية في القالب.
</p>

<p>
	ستكون الشيفرة كاملة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4991_14" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"text/template"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">Pet</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln">   string
    </span><span class="typ">Sex</span><span class="pln">    string
    </span><span class="typ">Intact</span><span class="pln"> </span><span class="kwd">bool</span><span class="pln">
    </span><span class="typ">Age</span><span class="pln">    string
    </span><span class="typ">Breed</span><span class="pln">  string
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    dogs </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="typ">Pet</span><span class="pun">{</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">   </span><span class="str">"Jujube"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">    </span><span class="str">"Female"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Intact</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Age</span><span class="pun">:</span><span class="pln">    </span><span class="str">"10 months"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Breed</span><span class="pun">:</span><span class="pln">  </span><span class="str">"German Shepherd/Pitbull"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">   </span><span class="str">"Zephyr"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">    </span><span class="str">"Male"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Intact</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Age</span><span class="pun">:</span><span class="pln">    </span><span class="str">"13 years, 3 months"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Breed</span><span class="pun">:</span><span class="pln">  </span><span class="str">"German Shepherd/Border Collie"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    var tmplFile </span><span class="pun">=</span><span class="pln"> </span><span class="pun">“</span><span class="pln">pets</span><span class="pun">.</span><span class="pln">tmpl</span><span class="pun">”</span><span class="pln">
    tmpl</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> </span><span class="kwd">template</span><span class="pun">.</span><span class="typ">New</span><span class="pun">(</span><span class="pln">tmplFile</span><span class="pun">).</span><span class="typ">ParseFiles</span><span class="pun">(</span><span class="pln">tmplFile</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        panic</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> tmpl</span><span class="pun">.</span><span class="typ">Execute</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="typ">Stdout</span><span class="pun">,</span><span class="pln"> dogs</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        panic</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="com">// main نهاية الدالة </span></pre>

<p>
	لنُشغّل ملف البرنامج "pets.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run pets.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Nothing here yet.
</pre>

<p>
	لا يطبع البرنامج البيانات حتى الآن، ولكن على الأقل يعمل بطريقة صحيحة. لنكتب الآن قالبًا.
</p>

<h2 id="3-1">
	الخطوة 3- كتابة قالب
</h2>

<p>
	القالب هو أكثر من مجرد نص عادي بترميز UTF-8، إذ يحتوي القالب على نص ثابت إضافةً إلى الإجراءات التي توجّه محرك القالب حول كيفية معالجة البيانات وإنشاء المخرجات. تُغلّف الإجراءات بأقواس معقوصة <code>{{ &lt;action&gt; }}</code>، وتعمل على البيانات باستخدام تدوين النقطة (<code>.</code>).
</p>

<p>
	من الشائع استخدام بُنى البيانات القابلة للتكرار عند تمرير البيانات إلى قالب، مثل <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-arrays-%D9%88%D8%A7%D9%84%D8%B4%D8%B1%D8%A7%D8%A6%D8%AD-slices-%D9%81%D9%8A-%D8%AC%D9%88-go-r1866/" rel="">الشرائح أو المصفوفات</a> أو <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B1%D9%88%D8%A7%D8%A8%D8%B7-maps-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1865/" rel="">الروابط maps</a>. سنستكشف في هذه الخطوة كيفية التكرار على شريحة في القالب باستخدام <code>range</code>.
</p>

<h3 id="-1">
	التكرار على شريحة
</h3>

<p>
	يمكننا استخدام الكلمة المفتاحية <code>range</code> في لغة جو داخل حلقة <code>for</code> للتكرار على شريحة، وكذلك هو الحال في القوالب؛ إذ يمكننا استخدام الإجراء <code>range</code> لتحقيق نفس النتيجة، ولكن بصيغة مختلفة قليلًا؛ فبدلًا من استخدام كلمة مفتاحية <code>for</code>، يمكن ببساطة استخدام <code>range</code> متبوعًا بالبيانات القابلة للتكرار، وتغلق الحلقة بالتعليمة <code>{{ end }}</code>.
</p>

<p>
	لنعدّل ملف "pets.tmpl" عن طريق استبدال محتوياته بما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5770_9" style=""><span class="pun">{{</span><span class="pln"> range </span><span class="pun">.</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">---</span><span class="pln">
</span><span class="pun">(</span><span class="typ">Pet</span><span class="pln"> will appear here</span><span class="pun">...)</span><span class="pln">
</span><span class="pun">{{</span><span class="pln"> end </span><span class="pun">}}</span></pre>

<p>
	يتخذ الإجراء <code>range</code> النقطة (<code>.</code>) وسيطًا له، والذي يمثل كامل شريحة <code>dogs</code>، ثم نُغلق الحلقة باستخدام <code>{{ end }}</code>. نضع ضمن الحلقة نصًا ثابتًا سيُعرض لكل حيوان أليف. في هذه المرحلة عمومًا، لن تُعرض أية معلومات عن الكلاب في الخرج.
</p>

<p>
	احفظ الملف "pets.tmpl" وشغّل ملف البرنامج "pets.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run pets.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">---
(Pet will appear here...)

---
(Pet will appear here...)
</pre>

<p>
	يُطبع النص الثابت مرتين نظرًا لوجود كلبين في الشريحة. دعونا الآن نستبدل هذا ببعض النصوص الثابتة المفيدة، إلى جانب بيانات الكلاب.
</p>

<h3 id="-2">
	عرض حقل
</h3>

<p>
	عند استخدام <code>range</code> مع النقطة <code>.</code> في القالب السابق، إذ تشير النقطة إلى العنصر الحالي في الشريحة أثناء كل تكرار للحلقة وعندما يكون هناك عنصر واحد في الشريحة فهو يشير إلى كامل الشريحة. يتيح ذلك الوصول إلى الحقول المُصدّرة لكل حيوان أليف مباشرةً باستخدام تدوين النقطة دون الحاجة إلى الإشارة إلى فهارس الشريحة. بالتالي لكي نعرض حقل، يمكن ببساطة تغليفه بأقواس معقوصة وإسباقه بنقطة. لنحّدث ملف "pets.tmpl" بالشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5770_11" style=""><span class="pun">{{</span><span class="pln"> range </span><span class="pun">.</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">---</span><span class="pln">
</span><span class="typ">Name</span><span class="pun">:</span><span class="pln">  </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Name</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">   </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Sex</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Age</span><span class="pun">:</span><span class="pln">   </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Age</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Breed</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Breed</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">{{</span><span class="pln"> end </span><span class="pun">}}</span></pre>

<p>
	بذلك سيتضمن الخرج أربعة حقول لكل كلب: الاسم والجنس والعمر والسلالة. تمثل النقطة <code>.</code> الحيوان الأليف الحالي الذي يجري تكراره في الحلقة. إذًا يمكننا الوصول إلى الحقل المقابل لكل حيوان أليف وعرض قيمته جنبًا إلى جنب مع التسميات المناسبة باستخدام تدوين النقطة.
</p>

<p>
	لنُشغّل ملف البرنامج "pets.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run pets.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">---
Name:  Jujube

Sex:   Female

Age:   10 months

Breed: German Shepherd/Pitbull

---
Name:  Zephyr

Sex:   Male

Age:   13 years, 3 months

Breed: German Shepherd/Border Collie
</pre>

<p>
	يبدو الأمر جيدًا. الآن دعونا نرى كيفية استخدام المنطق الشرطي لعرض الحقل الخامس.
</p>

<h2 id="-3">
	استخدام الشروط
</h2>

<p>
	تجاهلنا الحقل <code>Intact</code> في القالب السابق؛ لإبقاء التقرير أكثر سهولة للقراءة، فبدلًا من عرض القيمة المنطقية مباشرةً <code>true</code> أو <code>false</code>، يمكننا استخدام إجراء <code>if-else</code> لتخصيص الخرج بناءً على قيمة الحقل، وتقديم معلومات أوضح وأكثر سهولة للفهم من مجرد وضع <code>true</code> أو <code>false</code>.
</p>

<p>
	نفتح ملف "pets.tmpl" مجددًا ونعدّل القالب على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5770_13" style=""><span class="pun">{{</span><span class="pln"> range </span><span class="pun">.</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">---</span><span class="pln">
</span><span class="typ">Name</span><span class="pun">:</span><span class="pln">  </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Name</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">   </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Sex</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">({{</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Intact</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">intact</span><span class="pun">{{</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">fixed</span><span class="pun">{{</span><span class="pln"> end </span><span class="pun">}})</span><span class="pln">

</span><span class="typ">Age</span><span class="pun">:</span><span class="pln">   </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Age</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Breed</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Breed</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">{{</span><span class="pln"> end </span><span class="pun">}}</span></pre>

<p>
	يشتمل القالب الآن على عبارة <code>if-else</code> للتحقق من قيمة الحقل <code>Intact</code>. إذا كان الحقل <code>true</code>، فإنه يطبع <code>(intact)</code>، وإلا فإنه يطبع <code>(fixed)</code>. يمكننا أيضًا تحسين الخرج أكثر؛ من خلال عرض المصطلحات الخاصة بالجنس لكلب حالته <code>fixed</code>، مثل <code>spayed</code> أو <code>neutered</code>، بدلًا من استخدام المصطلح العام <code>fixed</code>. لتحقيق ذلك يمكننا إضافة عبارة <code>if</code> متداخلة داخل كتلة<code>else</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5770_15" style=""><span class="pun">{{</span><span class="pln"> range </span><span class="pun">.</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">---</span><span class="pln">
</span><span class="typ">Name</span><span class="pun">:</span><span class="pln">  </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Name</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">   </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Sex</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">({{</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Intact</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">intact</span><span class="pun">{{</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">}}{{</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">eq </span><span class="pun">.</span><span class="typ">Sex</span><span class="pln"> </span><span class="str">"Female"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">spayed</span><span class="pun">{{</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">neutered</span><span class="pun">{{</span><span class="pln"> end </span><span class="pun">}}{{</span><span class="pln"> end </span><span class="pun">}})</span><span class="pln">

</span><span class="typ">Age</span><span class="pun">:</span><span class="pln">   </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Age</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Breed</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Breed</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">{{</span><span class="pln"> end </span><span class="pun">}}</span></pre>

<p>
	مع هذا التعديل؛ يتحقق القالب أولًا مما إذا كان الحيوان الأليف سليمًا. إذا لم يكن الأمر كذلك، فإنه يتحقق أيضًا مما إذا كان الحيوان الأليف أنثى <code>Female</code>. يسمح هذا بمعلومات أكثر دقة في التقرير.
</p>

<p>
	احفظ ملف القالب وشغّل ملف البرنامج "pets.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run pets.go
</pre>

<p>
	سيكون الخرج:
</p>

<pre class="ipsCode">---
Name:  Jujube

Sex:   Female (spayed)

Age:   10 months

Breed: German Shepherd/Pitbull

---
Name:  Zephyr

Sex:   Male (intact)

Age:   13 years, 3 months

Breed: German Shepherd/Border Collie
</pre>

<p>
	لدينا كلبان وثلاث حالات محتملة لعرض <code>Intact</code>. دعونا نضيف كلبًا آخر إلى الشريحة في <code>pets.go</code> لتغطية الحالات الثلاث:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4991_17" style=""><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    dogs </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="typ">Pet</span><span class="pun">{</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">   </span><span class="str">"Jujube"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">    </span><span class="str">"Female"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Intact</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Age</span><span class="pun">:</span><span class="pln">    </span><span class="str">"10 months"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Breed</span><span class="pun">:</span><span class="pln">  </span><span class="str">"German Shepherd/Pitbull"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">   </span><span class="str">"Zephyr"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">    </span><span class="str">"Male"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Intact</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Age</span><span class="pun">:</span><span class="pln">    </span><span class="str">"13 years, 3 months"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Breed</span><span class="pun">:</span><span class="pln">  </span><span class="str">"German Shepherd/Border Collie"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">   </span><span class="str">"Bruce Wayne"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">    </span><span class="str">"Male"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Intact</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Age</span><span class="pun">:</span><span class="pln">    </span><span class="str">"3 years, 8 months"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Breed</span><span class="pun">:</span><span class="pln">  </span><span class="str">"Chihuahua"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	لنُشغّل ملف البرنامج "pets.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run pets.go
</pre>

<p>
	ليكون الخرج:
</p>

<pre class="ipsCode">---
Name:  Jujube

Sex:   Female (spayed)

Age:   10 months

Breed: German Shepherd/Pitbull

---
Name:  Zephyr

Sex:   Male (intact)

Age:   13 years, 3 months

Breed: German Shepherd/Border Collie

---
Name:  Bruce Wayne

Sex:   Male (neutered)

Age:   3 years, 8 months

Breed: Chihuahua
</pre>

<p>
	رائع، يبدو كما هو متوقع.
</p>

<p>
	الآن دعونا نناقش دوال القالب، مثل الدالة <code>eq</code> التي استخدمناها للتو.
</p>

<h3 id="-4">
	استخدام دوال القالب
</h3>

<p>
	توفر الحزمة <code>text/template</code> -إضافةً إلى الدالة <code>eq</code> التي استخدمناها سابقًا- العديد من الدوال الأخرى لمقارنة قيم الحقول وإرجاع النتائج المنطقية، مثل <code>gt</code> (أكبر من) و <code>ne</code> (عدم تساوي) و <code>le</code> (أقل من أو يساوي) والمزيد. يمكن استدعاء هذه الدوال بطريقتين مختلفتين:
</p>

<ol>
	<li>
		كتابة اسم الدالة متبوعة بمعامل واحد أو أكثر ومفصولة بمسافات. هذه هي الطريقة التي استخدمنا بها الدالة <code>eq</code> في هذا المقال:<code>"eq .Sex "Female</code>.
	</li>
	<li>
		كتابة معامل واحد متبوع برمز الأنبوب <code>|</code>، ثم اسم الدالة والمعلمات الإضافية إذا لزم الأمر. يسمح هذا بربط استدعاءات عدة دوال معًا، مع جعل خرج كل دالة مدخلًا للتالية. هذا مشابه لكيفية <a href="https://academy.hsoub.com/devops/linux/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A5%D8%B9%D8%A7%D8%AF%D8%A9-%D8%AA%D9%88%D8%AC%D9%8A%D9%87-%D8%A7%D9%84%D8%A5%D8%AF%D8%AE%D8%A7%D9%84%D8%A7%D9%84%D8%A5%D8%AE%D8%B1%D8%A7%D8%AC-io-%D9%81%D9%8A-%D9%84%D9%8A%D9%86%D9%83%D8%B3-r39/" rel="">عمل أنابيب الأوامر في سطر أوامر <code>Unix</code></a>.
	</li>
</ol>

<p>
	مثلًا يمكن كتابة عملية المقارنة السابقة باستخدام الدالة <code>eq</code> في القالب بالشكل: <code>"Sex | eq "Female.</code>، وهذا يُكافئ التعبير <code>"eq .Sex "Female</code>.
</p>

<p>
	دعونا الآن نستخدم الدالة <code>len</code> لعرض عدد الكلاب في الجزء العلوي من التقرير. نفتح ملف "pets.tmpl" ونضيف الشيفرة التالية في البداية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5770_17" style=""><span class="typ">Number</span><span class="pln"> of dogs</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> len </span><span class="pun">-}}</span><span class="pln">

</span><span class="pun">{{</span><span class="pln"> range </span><span class="pun">.</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	يمكنك أيضًا كتابتها بالشكل <code>{{ - . len }}</code>. تحسب هذه الدالة طول البيانات المُمررة في <code>.</code>، وهي في هذه الحالة شريحة الكلاب. بالتالي سنتمكن من عرض عدد الكلاب في أعلى التقرير من خلال تضمين هذه الدالة في القالب.
</p>

<p>
	لاحظ الشَرطة <code>-</code> بجانب الأقواس المزدوجة المعقوصة، وتمنع هذه الشرطة طباعة الأسطر الجديدة <code>n\</code> بعد الإجراء. يمكن أيضًا استخدامها لمنع طباعة السطر الجديد قبل الإجراء من خلال وضعها قبل الإجراء، أي في البداية <code>{{ - . len - }}</code>.
</p>

<p>
	لنُشغّل ملف البرنامج "pets.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run pets.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Number of dogs: 3
---
Name:  Jujube

Sex:   Female (spayed)

Age:   10 months

Breed: German Shepherd &amp; Pitbull

---
Name:  Zephyr

Sex:   Male (intact)

Age:   13 years, 3 months

Breed: German Shepherd &amp; Border Collie

---
Name:  Bruce Wayne

Sex:   Male (neutered)

Age:   3 years, 8 months

Breed: Chihuahua
</pre>

<p>
	باستخدام الشرطة في <code>{{- len | .}}</code>، لا توجد أسطر فارغة بين جملة <code>Number of dogs</code> وتفاصيل الكلب الأول.
</p>

<p>
	تجدر الإشارة إلى أن الدوال المضمنة في حزمة <code>text/template</code> محدودة، ومع ذلك يمكن استخدام أي دالة في لغة جو مع القوالب طالما أنها تُرجع قيمة واحدة أو قيمتين، إذ تكون الثانية قيمةً من نوع خطأ <code>error</code>. يسمح هذا بتوسيع دوال القوالب التي يمكن استخدامها من خلال دمج دوال خاصة.
</p>

<h2 id="-5">
	استخدام دوال لغة جو مع القوالب
</h2>

<p>
	لنفترض أننا نريد كتابة قالب يأخذ شريحة من الكلاب ويعرض فقط الكلب الأخير. يمكننا في قوالب لغة جو استخراج مجموعة فرعية من شريحة باستخدام الدالة المبنية مسبقًا <code>slice</code>، والتي تعمل بطريقة تشبه <code>[mySlice [x:y</code> في لغة جو. إذا كنا نريد مثلًا استرداد العنصر الأخير من شريحة مكونة من ثلاثة عناصر، فيمكن استخدام <code>{{ slice . 2 }}</code>. من المهم ملاحظة أن <code>slice</code> تُرجع شريحةً أخرى، وليس عنصرًا فرديًا. لذا، <code>{{slice. 2}}</code> تكافئ <code>[:slice [2</code>، وليس <code>[slice [2</code>. يمكن أيضًا أن تقبل الدالة <code>slice</code> عدة فهارس، مثل<code>{{ slice. 0 2 }}</code>لاسترداد الشريحة <code>[slice [0: 2</code>، لكننا لن نستخدم ذلك في هذا السيناريو.
</p>

<p>
	يظهر التحدي عندما نريد الإشارة إلى الفهرس الأخير للشريحة داخل القالب الخاص بنا. على الرغم من أن الدالة <code>len</code> متاحة، إلا أن العنصر الأخير في الشريحة موجود في الفهرس <code>len - 1</code>، وللأسف، لا تدعم القوالب العمليات الحسابية. هنا يمكننا إنشاء دالة مخصصة للتغلب على هذا القيد، وذلك من خلال كتابة دالة تُنقص قيمة عدد صحيح مُمرر لها.
</p>

<p>
	بدايةً ننشئ ملف قالب جديد. نفتح ملفًا جديدًا يسمى "lastPet.tmpl" ونضع المحتوى التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5770_19" style=""><span class="pun">{{-</span><span class="pln"> range </span><span class="pun">(</span><span class="pln">len </span><span class="pun">.</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> dec </span><span class="pun">|</span><span class="pln"> slice </span><span class="pun">.</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">---</span><span class="pln">
</span><span class="typ">Name</span><span class="pun">:</span><span class="pln">  </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Name</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">   </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Sex</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">({{</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Intact</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">intact</span><span class="pun">{{</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">}}{{</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="str">"Female"</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> eq </span><span class="pun">.</span><span class="typ">Sex</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">spayed</span><span class="pun">{{</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">neutered</span><span class="pun">{{</span><span class="pln"> end </span><span class="pun">}}{{</span><span class="pln"> end </span><span class="pun">}})</span><span class="pln">

</span><span class="typ">Age</span><span class="pun">:</span><span class="pln">   </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Age</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Breed</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Breed</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">{{</span><span class="pln"> end </span><span class="pun">-}}</span></pre>

<p>
	يستخدم هذا القالب إجراء <code>range</code> مع الشريحة المُعدّلة للتكرار على آخر كلب في الشريحة المحددة. نُطبّق الدالة المخصصة <code>dec</code> الموجودة في السطر الأول لتقليل طول الشريحة، مما يسمح لنا بالوصول إلى الفهرس الأخير. يعرض القالب بعد ذلك المعلومات ذات الصلة بالكلب الأخير، بما في ذلك الاسم والجنس والعمر والسلالة.
</p>

<p>
	لتعريف الدالة <code>dec</code> المخصصة وتمريرها إلى القالب، نُجري التغييرات التالية داخل الدالة <code>main</code> في الملف "pets.go" -بعد شريحة الكلاب وقبل استدعاء <code>()tmpl.Execute</code>- كما هو موضح أدناه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4991_19" style=""><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
    funcMap </span><span class="pun">:=</span><span class="pln"> </span><span class="kwd">template</span><span class="pun">.</span><span class="typ">FuncMap</span><span class="pun">{</span><span class="pln">
        </span><span class="str">"dec"</span><span class="pun">:</span><span class="pln"> func</span><span class="pun">(</span><span class="pln">i </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> i </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    var tmplFile </span><span class="pun">=</span><span class="pln"> </span><span class="pun">“</span><span class="pln">lastPet</span><span class="pun">.</span><span class="pln">tmpl</span><span class="pun">”</span><span class="pln">
    tmpl</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> </span><span class="kwd">template</span><span class="pun">.</span><span class="typ">New</span><span class="pun">(</span><span class="pln">tmplFile</span><span class="pun">).</span><span class="typ">Funcs</span><span class="pun">(</span><span class="pln">funcMap</span><span class="pun">).</span><span class="typ">ParseFiles</span><span class="pun">(</span><span class="pln">tmplFile</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        panic</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	صرّحنا عن <code>FuncMap</code> على أنها رابط map للدوال، إذ تمثِّل أزواج (المفتاح، القيمة) أسماء الدوال والتطبيق المقابلة لها. نُعرّف في هذه الحالة الدالة <code>dec</code> على أنها دالة مجهولة تطرح <code>1</code> من عدد صحيح وتعيد النتيجة.
</p>

<p>
	نُغيّر بعد ذلك اسم ملف القالب إلى "lastPet.tmpl". أخيرًا نستدعي التابع <code>Funcs</code> من القالب، قبل استدعاء <code>ParseFiles</code>، ونمرر له <code>funcMap</code> لإتاحة الدالة <code>dec</code> داخل القالب. من المهم ملاحظة أنه يجب استدعاء <code>Funcs</code> قبل <code>ParseFiles</code> لتسجيل الدالة المخصصة بطريقة صحيحة مع القالب.
</p>

<p>
	دعونا نفهم بدايةً ما يحدث في الإجراء <code>range</code>:
</p>

<pre class="ipsCode">{{- range (len . | dec | slice . ) }}
</pre>

<p>
	يجري في هذا السطر الحصول على طول شريحة الكلاب باستخدام <code>. len</code>، ثم تمرير النتيجة إلى الدالة <code>dec</code> المخصصة لطرح قيمة <code>1</code> من المتغير المُمرر لها <code>len . | dec</code>، ثم تمرير النتيجة مثل معاملٍ ثانٍ إلى الدالة <code>slice</code>. لذلك، بعبارات أبسط، بالنسبة لشريحة مكونة من ثلاثة كلاب، فإن <code>range</code> تعادل:
</p>

<pre class="ipsCode">{{- range (slice . 2) }}
</pre>

<p>
	لنُشغّل ملف البرنامج "pets.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run pets.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">---
Name:  Bruce Wayne

Sex:   Male (neutered)

Age:   3 years, 8 months

Breed: Chihuahua
</pre>

<p>
	يبدو هذا جيدًا. ماذا لو أردنا إظهار آخر كلبين بدلًا من آخر كلب فقط؟ نُحرّر الملف "lastPet.tmpl" ونضيف استدعاءً آخرًا للدالة <code>dec</code>:
</p>

<pre class="ipsCode">{{- range (len . | dec | dec | slice . ) }}
. . .
</pre>

<p>
	لنُشغّل ملف البرنامج "pets.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run pets.go
</pre>

<p>
	ليكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">---
Name:  Zephyr

Sex:   Male (intact)

Age:   13 years, 3 months

Breed: German Shepherd/Border Collie

---
Name:  Bruce Wayne

Sex:   Male (neutered)

Age:   3 years, 8 months

Breed: Chihuahua
</pre>

<p>
	يمكن تحسين الدالة <code>dec</code> من خلال جعلها تأخذ معاملًا واحدًا ونغيّر اسمها بحيث نكتب <code>minus 2</code> بدلًا من <code>dec | dec</code>.
</p>

<p>
	لنفرض أننا أردنا عرض الكلاب الهجينة مثل "Zephyr" بطريقة مختلفة، وذلك باستبدال الشرطة المائلة بعلامة العطف &amp;. لحسن الحظ لن نضطر لكتابة دالة خاصة لذلك، إذ يمكننا الاستفادة من دالة موجودة في الحزمة <code>strings</code>، لكن نحتاج إلى إجراء بعض التغييرات على ملف "pets.go" قبل ذلك. نستورد أولًا الحزمة <code>strings</code> مع الحزم الأخرى في أعلى الملف. نُحدِّث بعد ذلك المتغير <code>funcMap</code> داخل الدالة <code>main</code> لتضمين دالة <code>ReplaceAll</code> من حزمة <code>strings</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4991_21" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"strings"</span><span class="pln">
    </span><span class="str">"text/template"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
    funcMap </span><span class="pun">:=</span><span class="pln"> </span><span class="kwd">template</span><span class="pun">.</span><span class="typ">FuncMap</span><span class="pun">{</span><span class="pln">
        </span><span class="str">"dec"</span><span class="pun">:</span><span class="pln">     func</span><span class="pun">(</span><span class="pln">i </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> i </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
        </span><span class="str">"replace"</span><span class="pun">:</span><span class="pln"> strings</span><span class="pun">.</span><span class="typ">ReplaceAll</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="com">// main نهاية الدالة</span></pre>

<p>
	من خلال إضافة <code>strings.ReplaceAll</code> إلى <code>funcMap</code>، نكون قد جعلناها متاحةً في القالب تحت الاسم <code>replace</code>. نفتح ملف "lastPet.tmpl" ونعدّله لاستخدام <code>replace</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5770_21" style=""><span class="pun">{{-</span><span class="pln"> range </span><span class="pun">(</span><span class="pln">len </span><span class="pun">.</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> dec </span><span class="pun">|</span><span class="pln"> dec </span><span class="pun">|</span><span class="pln"> slice </span><span class="pun">.</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">---</span><span class="pln">
</span><span class="typ">Name</span><span class="pun">:</span><span class="pln">  </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Name</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">   </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Sex</span><span class="pln"> </span><span class="pun">}}</span><span class="pln"> </span><span class="pun">({{</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Intact</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">intact</span><span class="pun">{{</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">}}{{</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="str">"Female"</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> eq </span><span class="pun">.</span><span class="typ">Sex</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">spayed</span><span class="pun">{{</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">neutered</span><span class="pun">{{</span><span class="pln"> end </span><span class="pun">}}{{</span><span class="pln"> end </span><span class="pun">}})</span><span class="pln">

</span><span class="typ">Age</span><span class="pun">:</span><span class="pln">   </span><span class="pun">{{</span><span class="pln"> </span><span class="pun">.</span><span class="typ">Age</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">

</span><span class="typ">Breed</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{{</span><span class="pln"> replace </span><span class="pun">.</span><span class="typ">Breed</span><span class="pln"> </span><span class="pun">“/”</span><span class="pln"> </span><span class="pun">“</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln"> </span><span class="pun">”</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
</span><span class="pun">{{</span><span class="pln"> end </span><span class="pun">-}}</span></pre>

<p>
	لنُشغّل ملف البرنامج "pets.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run pets.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">---
Name:  Zephyr

Sex:   Male (intact)

Age:   13 years, 3 months

Breed: German Shepherd &amp; Border Collie

---
Name:  Bruce Wayne

Sex:   Male (neutered)

Age:   3 years, 8 months

Breed: Chihuahua
</pre>

<p>
	تحتوي سلالة Zephyr الآن على علامة عطف بدلًا من شرطة مائلة. أجرينا هذا التعديل على حقل <code>Breed</code> داخل القالب بدلًا من تعديل البيانات في <code>pets.go</code>. يتبع هذا المبدأ القائل بأن عرض البيانات هو مسؤولية القوالب وليس الشيفرة.
</p>

<p>
	تجدر الإشارة إلى أن بعض بيانات الكلاب، مثل حقل <code>Breed</code>، تحتوي على بعض المظاهر التي قد لا تكون طريقة عرض المعلومات فيها مثالية في <a href="https://academy.hsoub.com/programming/general/%D9%87%D9%86%D8%AF%D8%B3%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA/" rel="">هندسة البرمجيات</a> عمومًا، إذ يمكن أن يؤدي التنسيق الحالي لتخزين سلالات متعددة في سلسلة واحدة مفصولة بشرطة مائلة <code>/</code> إلى اختلافات في عملية إدخال البيانات، مما يؤدي إلى تنسيقات غير متسقة في قاعدة البيانات (على سبيل المثال، <code>Labrador/Poodle</code> و <code>Labrador &amp; Poodle</code>و <code>Labrador, Poodle</code>و <code>Labrador-Poodle mix</code>، إلخ).
</p>

<p>
	لمعالجة هذه المشكلة وتحسين المرونة في البحث حسب السلالة وتقديم البيانات، قد يكون من الأفضل تخزين حقل <code>Breed</code> مثل شريحة من السلاسل (<code>string[]</code>) بدلًا من سلسلة واحدة. سيؤدي هذا التغيير إلى إزالة الغموض في التنسيق ويسمح بمعالجة أسهل في القوالب. يمكن بعد ذلك استخدام دالة <code>strings.Join</code> ضمن القالب لربط جميع السلالات، جنبًا إلى جنب مع ملاحظة إضافية من خلال الحقل <code>Breed.</code> بحيث تشير إلى ما إذا كان الكلب سلالة أصيلة <code>(purebred)</code> أو سلالة هجينة <code>(mixed breed)</code>.
</p>

<p>
	دعونا في الختام نعرض نفس البيانات في مستند HTML ونرى لماذا يجب علينا دائمًا استخدام حزمة <code>html/template</code> عندما يكون ناتج القالب الخاصة بنا بتنسيق HTML.
</p>

<h2 id="5html">
	الخطوة 5- كتابة قالب HTML
</h2>

<p>
	في حين أن الحزمة <code>text/template</code> مناسبة لطباعة الخرج بدقة (سواءً من سطر الأوامر أو مكان آخر) وإنشاء ملفات منظّمة من البرامج الدفعية Batch program (برامج تعالج سلسلة من المهام أو الأوامر دفعة واحدة أو بطريقة غير تفاعلية)، إلا أنه من الشائع استخدام قوالب لغة جو لتصيير صفحات HTML في <a href="https://academy.hsoub.com/apps/web/" rel="">تطبيقات الويب</a>. على سبيل المثال، يعتمد مُنشئ الموقع الثابت (أداة تساعد في إنشاء ملفات HTML ثابتة بناءً على القوالب والمحتوى. يبسط عملية إنشاء مواقع الويب وإدارتها عن طريق تحويل القوالب والمحتوى والموارد الأخرى إلى موقع ويب ثابت جاهز للنشر) هوغو Hugo على كل من <code>text/template</code> و <code>html/template</code> مثل أساس لنظام القوالب الخاص به. تتيح هذه الحزم للمستخدمين تحديد القوالب ذات معاملات النوع وإدراج البيانات ديناميكيًا فيها، مما يتيح إنشاء صفحات HTML لمواقع الويب.
</p>

<p>
	تقدم لغة HTML ميزات فريدة لا نراها مع النص العادي، إذ تستخدم أقواس الزاوية لتغليف العناصر (<code>&lt;td&gt;</code>) وعلامات العطف لتمييز الكيانات (<code>;nbsp&amp;</code>) وعلامات الاقتباس لتغليف قيم أو سمات الوسوم (<code>&lt;"/a href="https://www.digitalocean.com&gt;</code>). عند إدخال البيانات التي تحتوي على هذه الأحرف باستخدام حزمة <code>text/template</code>، يمكن أن ينتج عن ذلك HTML تالف أو حتى حقن شيفرة Code injection (ثغرة أمنية يتمكن منها المهاجم من إدخال التعليمات البرمجية الضارة وتنفيذها داخل تطبيق أو نظام).
</p>

<p>
	تعالج حزمة <code>html/template</code> هذه التحديات، بحيث تهرب تلقائيًا من المحارف التي قد تخلق إشكالية، وتستبدلها بكيانات HTML الآمنة. تُصبح علامة العطف في البيانات (<code>;amp&amp;</code>) وقوس الزاوية اليسرى (<code>;It&amp;</code>) وهكذا.
</p>

<p>
	دعونا نواصل استخدام نفس بيانات الكلاب، لإثبات خطورة استخدام <code>text/template</code> مع HTML. نفتح "pets.go" ونعدّل حقل <code>Name</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4991_23" style=""><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
    dogs </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="typ">Pet</span><span class="pun">{</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">   </span><span class="str">"&lt;script&gt;alert(\"Gotcha!\");&lt;/script&gt;Jujube"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">    </span><span class="str">"Female"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Intact</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Age</span><span class="pun">:</span><span class="pln">    </span><span class="str">"10 months"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Breed</span><span class="pun">:</span><span class="pln">  </span><span class="str">"German Shepherd/Pit Bull"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">   </span><span class="str">"Zephyr"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">    </span><span class="str">"Male"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Intact</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Age</span><span class="pun">:</span><span class="pln">    </span><span class="str">"13 years, 3 months"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Breed</span><span class="pun">:</span><span class="pln">  </span><span class="str">"German Shepherd/Border Collie"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">   </span><span class="str">"Bruce Wayne"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Sex</span><span class="pun">:</span><span class="pln">    </span><span class="str">"Male"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Intact</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Age</span><span class="pun">:</span><span class="pln">    </span><span class="str">"3 years, 8 months"</span><span class="pun">,</span><span class="pln">
            </span><span class="typ">Breed</span><span class="pun">:</span><span class="pln">  </span><span class="str">"Chihuahua"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	نُنشئ الآن قالب HTML في ملف جديد يسمى "petsHtml.tmpl":
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5770_27" style=""><span class="tag">&lt;p&gt;&lt;strong&gt;</span><span class="pln">Pets:</span><span class="tag">&lt;/strong&gt;</span><span class="pln"> {{ . | len }}</span><span class="tag">&lt;/p&gt;</span><span class="pln">
{{ range . }}
</span><span class="tag">&lt;hr</span><span class="pln"> </span><span class="tag">/&gt;</span><span class="pln">
</span><span class="tag">&lt;dl&gt;</span><span class="pln">
    </span><span class="tag">&lt;dt&gt;</span><span class="pln">Name</span><span class="tag">&lt;/dt&gt;</span><span class="pln">
    </span><span class="tag">&lt;dd&gt;</span><span class="pln">{{ .Name }}</span><span class="tag">&lt;/dd&gt;</span><span class="pln">
    </span><span class="tag">&lt;dt&gt;</span><span class="pln">Sex</span><span class="tag">&lt;/dt&gt;</span><span class="pln">
    </span><span class="tag">&lt;dd&gt;</span><span class="pln">{{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }})</span><span class="tag">&lt;/dd&gt;</span><span class="pln">
    </span><span class="tag">&lt;dt&gt;</span><span class="pln">Age</span><span class="tag">&lt;/dt&gt;</span><span class="pln">
    </span><span class="tag">&lt;dd&gt;</span><span class="pln">{{ .Age }}</span><span class="tag">&lt;/dd&gt;</span><span class="pln">
    </span><span class="tag">&lt;dt&gt;</span><span class="pln">Breed</span><span class="tag">&lt;/dt&gt;</span><span class="pln">
    </span><span class="tag">&lt;dd&gt;</span><span class="pln">{{ replace .Breed “/” “ &amp; ” }}</span><span class="tag">&lt;/dd&gt;</span><span class="pln">
</span><span class="tag">&lt;/dl&gt;</span><span class="pln">
{{ end }}</span></pre>

<p>
	نحفظ قالب HTML. نحتاج إلى تعديل المتغير <code>tmpFile</code> قبل تشغيل "pets.go"، ولكن دعونا أيضًا نُعدّل البرنامج لإخراج القالب إلى ملف بدلًا من الطرفية. نفتح الملف "pets.go" ونضيف الشيفرة التالية داخل الدالة <code>main</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5770_29" style=""><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
    funcMap </span><span class="pun">:=</span><span class="pln"> </span><span class="kwd">template</span><span class="pun">.</span><span class="typ">FuncMap</span><span class="pun">{</span><span class="pln">
        </span><span class="str">"dec"</span><span class="pun">:</span><span class="pln">     func</span><span class="pun">(</span><span class="pln">i </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> i </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">},</span><span class="pln">
        </span><span class="str">"replace"</span><span class="pun">:</span><span class="pln"> strings</span><span class="pun">.</span><span class="typ">ReplaceAll</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    var tmplFile </span><span class="pun">=</span><span class="pln"> </span><span class="str">"petsHtml.tmpl"</span><span class="pln">
    tmpl</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> </span><span class="kwd">template</span><span class="pun">.</span><span class="typ">New</span><span class="pun">(</span><span class="pln">tmplFile</span><span class="pun">).</span><span class="typ">Funcs</span><span class="pun">(</span><span class="pln">funcMap</span><span class="pun">).</span><span class="typ">ParseFiles</span><span class="pun">(</span><span class="pln">tmplFile</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        panic</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    var f </span><span class="pun">*</span><span class="pln">os</span><span class="pun">.</span><span class="typ">File</span><span class="pln">
    f</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Create</span><span class="pun">(</span><span class="str">"pets.html"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        panic</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> tmpl</span><span class="pun">.</span><span class="typ">Execute</span><span class="pun">(</span><span class="pln">f</span><span class="pun">,</span><span class="pln"> dogs</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        panic</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> f</span><span class="pun">.</span><span class="typ">Close</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        panic</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="com">// end main</span></pre>

<p>
	نفتح ملف <code>File</code> جديد يسمى "pets.html" ونمرّره (بدلًا من <code>os.Stdout</code>) إلى <code>tmpl.Execute</code>، ثم نغلق الملف عند الانتهاء.
</p>

<p>
	لنُشغّل ملف البرنامج "pets.go" من خلال الأمر <code>go run</code> لإنشاء ملف HTML. نفتح بعد ذلك صفحة الويب المحلية هذه في المتصفح:
</p>

<pre class="ipsCode">$ go run pets.go
</pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140294" href="https://academy.hsoub.com/uploads/monthly_2023_12/img1.png.901f5caef8f1da9a3c767a091f36773c.png" rel=""><img alt="img1" class="ipsImage ipsImage_thumbnailed" data-fileid="140294" data-ratio="69.83" data-unique="kcnngdwh8" style="width: 600px; height: auto;" width="500" src="https://academy.hsoub.com/uploads/monthly_2023_12/img1.thumb.png.a2bedc1028e581f833d668ef2b07f946.png"> </a>
</p>

<p>
	شغّل المتصفح البرنامج النصي المحقون، وهذا هو السبب في أنه لا يجب أبدًا استخدام حزمة <code>text/template</code> لإنشاء HTML، خاصةً عندما لا يمكن الوثوق تمامًا بمصدر بيانات القالب.
</p>

<p>
	بصرف النظر عن محارف الهروب في HTML، تعمل حزمة <code>html /template</code> تمامًا مثل <code>text/template</code> ولها نفس الاسم الأساسي ("قالب" template)، مما يعني أن كل ما علينا فعله لجعل القالب آمنًا هو استبدال استيراد <code>text/template</code> مع <code>html /template</code>. لنعدّل ملف "pets.go" وفقًا لذلك الآن:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4991_27" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"strings"</span><span class="pln">
    </span><span class="str">"html/template"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	نحفظ الملف لتعديل بيانات "pets.html" ونشغّله مرةً أخيرة. ثم نعيد تحميل ملف HTML في المتصفح:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="140295" href="https://academy.hsoub.com/uploads/monthly_2023_12/img2.png.9e38d0a956610148fa54d780c24f7844.png" rel=""><img alt="img2" class="ipsImage ipsImage_thumbnailed" data-fileid="140295" data-ratio="69.83" data-unique="lghd6jtkf" style="width: 600px; height: auto;" width="600" src="https://academy.hsoub.com/uploads/monthly_2023_12/img2.thumb.png.eebc2bc33a6e8b57914625fe49255bd7.png"> </a>
</p>

<p>
	صيّرت حزمة <code>html/template</code> النص المُدخل على أنه نص فقط في صفحة الويب. نفتح الملف "pets.html" في محرر النصوص (أو نعرض مصدر الصفحة في المتصفح) وننظر إلى أول كلب Jujube:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_5770_31" style=""><span class="pln">. . .
</span><span class="tag">&lt;dl&gt;</span><span class="pln">
        </span><span class="tag">&lt;dt&gt;</span><span class="pln">Name</span><span class="tag">&lt;/dt&gt;</span><span class="pln">
        </span><span class="tag">&lt;dd&gt;</span><span class="pln">&amp;lt;script&amp;gt;alert(&amp;#34;Gotcha!&amp;#34;);&amp;lt;/script&amp;gt;Jujube</span><span class="tag">&lt;/dd&gt;</span><span class="pln">
        </span><span class="tag">&lt;dt&gt;</span><span class="pln">Sex</span><span class="tag">&lt;/dt&gt;</span><span class="pln">
        </span><span class="tag">&lt;dd&gt;</span><span class="pln">Female (spayed)</span><span class="tag">&lt;/dd&gt;</span><span class="pln">
        </span><span class="tag">&lt;dt&gt;</span><span class="pln">Age</span><span class="tag">&lt;/dt&gt;</span><span class="pln">
        </span><span class="tag">&lt;dd&gt;</span><span class="pln">10 months</span><span class="tag">&lt;/dd&gt;</span><span class="pln">
        </span><span class="tag">&lt;dt&gt;</span><span class="pln">Breed</span><span class="tag">&lt;/dt&gt;</span><span class="pln">
        </span><span class="tag">&lt;dd&gt;</span><span class="pln">German Shepherd &amp;amp; Pit Bull</span><span class="tag">&lt;/dd&gt;</span><span class="pln">
</span><span class="tag">&lt;/dl&gt;</span><span class="pln">
. . .</span></pre>

<p>
	استبدلت حزمة html أقواس الزاوية ومحارف الاقتباس في اسم Jujube، وكذلك علامة العطف في السلالة.
</p>

<h2 id="-6">
	الخاتمة
</h2>

<p>
	توفر قوالب لغة جو حلًا متعدد القدرات لدمج البيانات في تنسيقات نصية متنوعة. يمكن استخدام القوالب في أدوات سطر الأوامر لتنسيق الخرج، وكذلك في تطبيقات الويب لإنشاء صفحات HTML. تعلمنا خلال هذا المقال كيفية الاستفادة من حزم القوالب المضمنة في لغة جو لإنتاج نص جيد التنظيم وعرض HTML باستخدام نفس البيانات.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-templates-in-go" rel="external nofollow">How To Use Templates in Go</a> لصاحبه Kristin Davidson.
</p>

<h2 id="-7">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D9%85%D8%B9%D9%85%D9%85%D8%A9-generics-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r2206/" rel="">كيفية استخدام الأنواع المعممة Generics في لغة جو Go</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-r1737/" rel="">كيفية استخدام القوالب في تطبيقات فلاسك Flask</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/html/%D9%85%D9%83%D9%88%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%B9%D9%86%D8%A7%D8%B5%D8%B1-html-%D8%A7%D9%84%D9%85%D8%AE%D8%B5%D8%B5%D8%A9-%D9%88%D9%82%D9%88%D8%A7%D9%84%D8%A8%D9%87%D8%A7-r1352/" rel="">مكونات الويب: عناصر HTML المخصصة وقوالبها</a>.
	</li>
</ul>
]]></description><guid isPermaLink="false">2207</guid><pubDate>Sat, 30 Dec 2023 16:08:03 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x623;&#x646;&#x648;&#x627;&#x639; &#x627;&#x644;&#x645;&#x639;&#x645;&#x645;&#x629; Generics &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648; Go</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D9%85%D8%B9%D9%85%D9%85%D8%A9-generics-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r2206/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_12/----Generics----Go.png.cff4dd6f974e241fdd4fca8924760d73.png" /></p>
<p>
	قدمت لغة جو في الإصدار 1.18 ميزةً جديدة تُعرف باسم <a href="https://academy.hsoub.com/programming/rust/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D9%85%D8%B9%D9%85%D9%85%D8%A9-generic-types-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-rust-r1935/" rel="">الأنواع المُعمّمة generic types</a> -أو اختصارًا generics- والتي كانت في قائمة أمنيات مُطوّري هذه اللغة لبعض الوقت. والنوع المُعمّم في لغات البرمجة، هو نوع يمكن استخدامه مع أنواع متعددة أخرى.
</p>

<p>
	سابقًا، عندما كنا نرغب في استخدام نوعين مختلفين لنفس المتغير في <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a>، كنا نحتاج إما لاستخدام واجهة مُعرّفة بأسلوب معين مثل <a href="https://pkg.go.dev/io#Reader" rel="external nofollow"><code>io.Reader</code></a>، أو استخدام <a href="https://www.digitalocean.com/community/tutorials/how-to-use-interfaces-in-go" rel="external nofollow"><code>{}interface</code></a> الذي يسمح باستخدام أي قيمة. المشكلة في أن استخدام <code>{}interface</code> يجعل التعامل مع تلك الأنواع صعبًا، لأنه يجب التحويل بين العديد من الأنواع الأخرى المحتملة للتفاعل معها (عليك تحويل القيم بين <a href="https://academy.hsoub.com/programming/general/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1726/" rel="">أنواع مختلفة من البيانات</a> للتعامل معها). على سبيل المثال، إذا كان لدينا قيمة من <code>{}interface</code> تمثل عددًا، يجب علينا تحويلها إلى <code>int</code> قبل أن تتمكن من إجراء العمليات الحسابية عليها. هذه العملية قد تكون معقدة وتستلزم كتابة الكثير من التعليمات الإضافية للتعامل مع تحويل الأنواع المختلفة، أما الآن وبفضل الأنواع المُعمّمة، يمكننا التفاعل مباشرةً مع الأنواع المختلفة دون الحاجة للتحويل بينها، مما يؤدي إلى شيفرة نظيفة سهلة القراءة.
</p>

<p>
	نُنشئ في هذا المقال برنامجًا يتفاعل مع مجموعة من البطاقات، إذ سنبدأ بإنشائها بحيث تستخدم <code>{}interface</code> للتفاعل مع البطاقات الأخرى، ثم نُحدّثها من أجل استخدام الأنواع المُعمّمة، ثم سنضيف نوعًا ثانيًا من البطاقات باستخدام الأنواع المُعمّمة، ثم سنُحدّثها لتقييد نوعها المُعمّم، بحيث تدعم أنواع البطاقات فقط. نُنشئ أخيرًا دالةً تستخدم البطاقات الخاصة بنا وتدعم الأنواع المُعمّمة.
</p>

<h2 id="">
	المتطلبات الأولية
</h2>

<p>
	لمتابعة هذا المقال التعليمي، سنحتاج إلى:
</p>

<ul>
	<li>
		إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a> لإعداده.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
	<li>
		فهم قوي لأساسيات لغة جو، مثل المتغيرات والدوال وأنواع البيانات والشرائح والحلقات ..إلخ. كل الأساسيات ومقالات أخرى متقدمة في هذه اللغة تجدها <a href="https://academy.hsoub.com/programming/go/" rel="">هنا</a>.
	</li>
</ul>

<h2 id="collections">
	التجميعات Collections في لغة جو بدون استخدام الأنواع المعممة
</h2>

<p>
	يشير مصطلح <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87%D8%A7-%D8%A7%D9%84%D8%AA%D8%AC%D9%85%D9%8A%D8%B9%D8%A7%D8%AA-collections-r1288/" rel="">التجميعة Collection</a> في هذا السياق إلى حاوية بيانات يمكن أن تحتوي على قيم أو عناصر متعددة، وهو مفهوم ذو مستوى أعلى يشمل أنواعًا مختلفة من <a href="https://academy.hsoub.com/programming/general/%D9%87%D9%8A%D8%A7%D9%83%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-data-structures/" rel="">هياكل البيانات</a>، مثل المصفوفات والقوائم والمجموعات والروابط maps، مما يسمح بتخزين وتنظيم عدة قيم مرتبطة معًا. توفر التجميعات عمليات وتوابع للوصول إلى العناصر التي تحتويها ومعالجتها وتكرارها. نستخدم مصطلح "التجميعة" هنا لوصف الهيكل أو الحاوية المستخدمة لتخزين وإدارة أوراق اللعب في البرنامج.
</p>

<p>
	تتمتع لغة جو بميزة قوية تتمثّل في قدرتها على تمثيل العديد من الأنواع بطريقة مرنة باستخدام <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-interfaces-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1999/" rel="">الواجهات interfaces</a>. يمكن للكثير من الشيفرات المكتوبة بلغة جو أن تعمل جيدًا باستخدام دوال الواجهات المتاحة، وهذا هو أحد الأسباب التي جعلت اللغة موجودةً لفترة طويلة دون دعم الأنواع المُعمّمة.
</p>

<p>
	نُنشئ في هذا المقال برنامجًا يحاكي عملية الحصول على بطاقة لعب عشوائية <code>PlayingCard</code> من مجموعة بطاقات <code>Deck</code>. سنستخدم في هذا القسم <code>{}interface</code> للسماح للدستة <code>Deck</code> بالتفاعل مع أي نوع من البطاقات. نُعدّل لاحقًا البرنامج لاستخدام الأنواع المُعمّمة لنتمكن من فهم الفروق بينهما والتعرف على الحالات التي تكون فيها الأنواع المُعمّمة خيارًا أفضل من غيرها.
</p>

<p>
	يمكن تصنيف أنظمة النوع type systems في <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">لغات البرمجة</a> عمومًا إلى فئتين مختلفتين: النوع typing والتحقق من النوع type checking. تُشير الفئة الأولى إلى كيفية التعامل مع الأنواع في اللغة وكيفية تعريف وتمثيل أنواع البيانات المختلفة. هناك نوعان رئيسيان من أنظمة "النوع": النوع القوي strong والنوع الضعيف weak وكذلك <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87%D8%A7-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A9-r1287/" rel="">الثابت static والديناميكي dynamic</a>. تُطبّق قواعد صارمة في النوع القوي تضمن توافق الأنواع وعدم وجود تضارب بينها. على سبيل المثال، لا يمكن تخزين قيمة من نوع بيانات ما في متغير من نوع بيانات آخر (لا يمكن تخزين قيمة <code>int</code> في متغير <code>string</code>) إلا إذا كانت هناك توافقية بينهما. أما في النوع الضعيف، فقد تسمح اللغة بتحويل تلقائي للأنواع من خلال عمليات تحويل ضمنية.
</p>

<p>
	أما بالنسبة "للتحقق من النوع"، فهذا يشير إلى كيفية التحقق من صحة استخدام الأنواع في البرنامج. هناك نوعان رئيسيان من أنظمة التحقق من النوع: التحقق الثابت statically-checked والتحقق الديناميكي dynamically-checked. تُفحص الأنواع في التحقق الثابت خلال عملية التصريف ويُفحص توافقها وصحتها والتأكد من عدم وجود أخطاء في النوع قبل تنفيذ البرنامج. أما في التحقق الدينامكي، فإن الفحص يجري أثناء تنفيذ البرنامج نفسه، ويمكن للأنواع أن تتغير وتحول ديناميكيًا خلال تنفيذ البرنامج.
</p>

<p>
	تنتمي لغة جو عمومًا إلى فئة اللغات ذات النوع القوي والتحقق الثابت، إذ تستخدم قواعد صارمة للتحقق من توافق الأنواع وتتحقق من صحتها خلال عملية التصريف، مما يساعد في تجنب الأخطاء الناتجة عن تعامل غير صحيح مع الأنواع في البرنامج، لكن يترتب على ذلك بعض القيود على البرامج، لأنه يجب أن تُعرّف الأنواع التي تنوي استخدامها قبل تصريف البرنامج. يمكن التعامل مع هذا من خلال استخدام النوع <code>{}interface</code>، إذ يعمل نوع <code>{}interface</code> مع أي قيمة.
</p>

<p>
	لبدء إنشاء البرنامج باستخدام <code>{}interface</code> لتمثيل البطاقات، نحتاج إلى مجلد للاحتفاظ بمجلد البرنامج فيه، وليكن باسم "projects". أنشئ المجلد وانتقل إليه:
</p>

<pre class="ipsCode">$ mkdir projects
$ cd projects
</pre>

<p>
	نُنشئ الآن مجلدًا للمشروع، وليكن باسم "generics" ثم ننتقل إليه:
</p>

<pre class="ipsCode">$ mkdir generics
$ cd generics
</pre>

<p>
	من داخل هذا المجلد، نفتح الملف "main.go" باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">محرر نانو nano أو أي محرر آخر تريده</a>:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	نضع مقتطف الشيفرة التالي داخل هذا الملف، إذ نُصرّح عن الحزمة <code>package</code> لإخبار المُصرّف أن يحوّل البرنامج إلى ملف ثنائي لتتمكّن من تشغيله مباشرةً. نستورد أيضًا الحزم اللازمة عن طريق تعليمة <code>import</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_7" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"math/rand"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span></pre>

<p>
	نُعرّف النوع <code>PlayingCard</code> والدوال والتوابع المرتبطة به:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_9" style=""><span class="pun">...</span><span class="pln">

type </span><span class="typ">PlayingCard</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Suit</span><span class="pln"> string
    </span><span class="typ">Rank</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">NewPlayingCard</span><span class="pun">(</span><span class="pln">suit string</span><span class="pun">,</span><span class="pln"> card string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="typ">PlayingCard</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">PlayingCard</span><span class="pun">{</span><span class="typ">Suit</span><span class="pun">:</span><span class="pln"> suit</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Rank</span><span class="pun">:</span><span class="pln"> card</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">pc </span><span class="pun">*</span><span class="typ">PlayingCard</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"%s of %s"</span><span class="pun">,</span><span class="pln"> pc</span><span class="pun">.</span><span class="typ">Rank</span><span class="pun">,</span><span class="pln"> pc</span><span class="pun">.</span><span class="typ">Suit</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عرّفنا بنية بيانات تُسمى <code>PlayingCard</code> مع الخصائص نوع البطاقة <code>Suit</code> وترتيبها <code>Rank</code> لتمثيل مجموعة ورق اللعب <code>Deck</code> المكونة من 52 بطاقة. يكون <code>Suit</code> أحد القيم التالية: <code>Diamonds</code> أو <code>Hearts</code> أو <code>Clubs</code> أو <code>Spades</code>. أما <code>Rank</code> يكون أما <code>A</code> أو <code>2</code> أو <code>3</code>، وهكذا حتى <code>K</code>.
</p>

<p>
	عرّفنا أيضًا دالة <code>NewPlayingCard</code> لتكون <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%A8%D9%88%D8%A7%D9%86%D9%8A-%D9%88%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-object-initialization-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-r1109/" rel="">باني constructor</a> لبنية البيانات <code>PlayingCard</code>، وتابع <code>String</code> ليُرجع ترتيب ونوع البطاقة باستخدام <code>fmt.Sprintf</code>.
</p>

<p>
	ننشئ الآن النوع <code>Deck</code> بالدوال <code>AddCard</code> و <code>RandomCard</code>، إضافةً إلى الدالة <code>NewPlayingCardDeck</code> لإنشاء <code>Deck*</code> مملوء بجميع بطاقات اللعبة التي عددها 52.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_11" style=""><span class="pun">...</span><span class="pln">

type </span><span class="typ">Deck</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    cards </span><span class="pun">[]</span><span class="pln">interface</span><span class="pun">{}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">NewPlayingCardDeck</span><span class="pun">()</span><span class="pln"> </span><span class="pun">*</span><span class="typ">Deck</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    suits </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">string</span><span class="pun">{</span><span class="str">"Diamonds"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Hearts"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Clubs"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Spades"</span><span class="pun">}</span><span class="pln">
    ranks </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">string</span><span class="pun">{</span><span class="str">"A"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"2"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"3"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"4"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"5"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"6"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"7"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"8"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"9"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"10"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"J"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Q"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"K"</span><span class="pun">}</span><span class="pln">

    deck </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">Deck</span><span class="pun">{}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> _</span><span class="pun">,</span><span class="pln"> suit </span><span class="pun">:=</span><span class="pln"> range suits </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> _</span><span class="pun">,</span><span class="pln"> rank </span><span class="pun">:=</span><span class="pln"> range ranks </span><span class="pun">{</span><span class="pln">
            deck</span><span class="pun">.</span><span class="typ">AddCard</span><span class="pun">(</span><span class="typ">NewPlayingCard</span><span class="pun">(</span><span class="pln">suit</span><span class="pun">,</span><span class="pln"> rank</span><span class="pun">))</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> deck
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">d </span><span class="pun">*</span><span class="typ">Deck</span><span class="pun">)</span><span class="pln"> </span><span class="typ">AddCard</span><span class="pun">(</span><span class="pln">card interface</span><span class="pun">{})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    d</span><span class="pun">.</span><span class="pln">cards </span><span class="pun">=</span><span class="pln"> append</span><span class="pun">(</span><span class="pln">d</span><span class="pun">.</span><span class="pln">cards</span><span class="pun">,</span><span class="pln"> card</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">d </span><span class="pun">*</span><span class="typ">Deck</span><span class="pun">)</span><span class="pln"> </span><span class="typ">RandomCard</span><span class="pun">()</span><span class="pln"> interface</span><span class="pun">{}</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    r </span><span class="pun">:=</span><span class="pln"> rand</span><span class="pun">.</span><span class="typ">New</span><span class="pun">(</span><span class="pln">rand</span><span class="pun">.</span><span class="typ">NewSource</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">().</span><span class="typ">UnixNano</span><span class="pun">()))</span><span class="pln">

    cardIdx </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Intn</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">d</span><span class="pun">.</span><span class="pln">cards</span><span class="pun">))</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> d</span><span class="pun">.</span><span class="pln">cards</span><span class="pun">[</span><span class="pln">cardIdx</span><span class="pun">]</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	في البنية <code>Deck</code> المُعرّفة أعلاه، أنشأنا حقلًا يُسمّى <code>cards</code> لاحتواء مجموعة ورق اللعب. نظرًا لأننا نرغب في أن تكون مجموعة البطاقات قادرة على استيعاب أنواع متعددة مختلفة من البطاقات، فلا يمكن تعريفها فقط على أنها <code>PlayingCard*</code>. لذا عرّفناها على أنها <code>{}interface[]</code> حتى نتمكن من استيعاب أي نوع من البطاقات التي قد نُنشئها مستقبلًا. بالإضافة إلى الحقل <code>{}interface[]</code> في <code>Deck</code>، أنشأنا التابع <code>AddCard</code> الذي يقبل قيمة من نفس نوع <code>interface{}</code> لإضافة بطاقة إلى حقل البطاقات في <code>Deck</code>.
</p>

<p>
	أنشأنا أيضًا التابع <code>RandomCard</code> الذي يُرجع بطاقةً عشوائية من <code>cards</code> في <code>Deck</code>. يستخدم هذا التابع حزمة <code>math/rand</code> لإنشاء رقم عشوائي بين <code>0</code> وعدد البطاقات في المجموعة. يُنشئ سطر <code>rand.New</code> مولد رقم عشوائي جديد باستخدام الوقت الحالي مثل مصدر للعشوائية، وإلا فإن الرقم العشوائي يمكن أن يكون نفسه في كل مرة. يستخدم السطر <code>((r.Intn(len(d.cards</code> مولد الأرقام العشوائية لإنشاء قيمة صحيحة بين <code>0</code> والعدد المُقدم. نظرًا لأن التابع <code>Intn</code> لا يشمل قيمة المعامل ضمن نطاق الأرقام الممكنة، فلا حاجة لطرح <code>1</code> من الطول لمعالجة فكرة البدء من <code>0</code> (إذا كان لدينا مجموعة ورق لعب تحتوي على <code>10</code> عناصر، فالأرقام الممكنة ستكون من <code>0</code> إلى <code>9</code>). لدينا أيضًا <code>RandomCard</code> تُرجع قيمة البطاقة في الفهرس المحدد بالرقم العشوائي.
</p>

<p>
	<strong>تحذير:</strong> كن حذرًا في اختيار مولد الأرقام العشوائية الذي تستخدمه في برامجك. ليست حزمة <a href="https://pkg.go.dev/math/rand" rel="external nofollow"><code>math/rand</code></a> آمنة من الناحية التشفيرية ولا ينبغي استخدامها في البرامج التي تتعلق بالأمان. توفر حزمة <a href="https://pkg.go.dev/crypto/rand" rel="external nofollow"><code>crypto/rand</code></a> مولد أرقام عشوائية يمكن استخدامه لهذه الأغراض.
</p>

<p>
	بالنسبة للدالة <code>NewPlayingCardDeck</code> فهي تُرجع القيمة <code>Deck*</code> مملوءة بجميع البطاقات الموجودة في دستة بطاقات اللعب. نستخدم شريحتين، واحدة تحتوي على جميع الأشكال المتاحة وأخرى تحتوي على جميع الرتب المتاحة، ثم نكرر فوق كل قيمة لإنشاء <code>PlayingCard*</code> جديدة لكل تركيبة قبل إضافتها إلى المجموعة باستخدام <code>AddCard</code>. تُرجع القيمة بمجرد إنشاء بطاقات مجموعة ورق اللعب.
</p>

<p>
	بعد إعداد <code>Deck</code> و <code>PlayingCard</code>، يمكننا إنشاء الدالة <code>main</code> لاستخدامها لسحب البطاقات:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_13" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    deck </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">NewPlayingCardDeck</span><span class="pun">()</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"--- drawing playing card ---\n"</span><span class="pun">)</span><span class="pln">
    card </span><span class="pun">:=</span><span class="pln"> deck</span><span class="pun">.</span><span class="typ">RandomCard</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"drew card: %s\n"</span><span class="pun">,</span><span class="pln"> card</span><span class="pun">)</span><span class="pln">

    playingCard</span><span class="pun">,</span><span class="pln"> ok </span><span class="pun">:=</span><span class="pln"> card</span><span class="pun">.(*</span><span class="typ">PlayingCard</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">!</span><span class="pln">ok </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"card received wasn't a playing card!"</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"card suit: %s\n"</span><span class="pun">,</span><span class="pln"> playingCard</span><span class="pun">.</span><span class="typ">Suit</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"card rank: %s\n"</span><span class="pun">,</span><span class="pln"> playingCard</span><span class="pun">.</span><span class="typ">Rank</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أنشأنا بدايةً مجموعةً جديدة من البطاقات باستخدام الدالة <code>NewPlayingCardDeck</code> ووضعناها في المتغير <code>deck</code>، ثم استخدمنا <code>fmt.Printf</code> لطباعة جملة تدل على أننا نسحب بطاقة. نستخدم التابع <code>RandomCard</code> من <code>deck</code> للحصول على بطاقة عشوائية من المجموعة. نستخدم بعد ذلك <code>fmt.Printf</code> مرةً أخرى لطباعة البطاقة التي سحبناها من المجموعة.
</p>

<p>
	نظرًا لأن نوع المتغير <code>card</code> هو <code>{}interface</code>، سنحتاج إلى استخدام <a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D8%AA%D9%88%D9%83%D9%8A%D8%AF-assertion-%D9%88%D8%A7%D9%84%D8%AA%D9%88%D8%B5%D9%8A%D9%81-annotation-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-r1312/" rel="">توكيد النوع type assertion</a> للحصول على مرجع إلى البطاقة بنوعها الأصلي <code>PlayingCard*</code>. إذا كان النوع في المتغير <code>card</code> ليس نوع <code>PlayingCard*</code>، الذي يجب أن يكون عليه وفقًا لطريقة كتابة البرنامج الحالية، ستكون قيمة <code>ok</code> تساوي <code>false</code> وسيطبع البرنامج رسالة خطأ باستخدام <code>fmt.Printf</code> ويخرج مع شيفرة خطأ <code>1</code> باستخدام <code>os.Exit</code>. إذا كان النوع <code>PlayingCard*</code> سيطبع البرنامج قيمتي <code>Suit</code> و <code>Rank</code> من <code>playingCard</code> باستخدام <code>fmt.Printf</code>.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	يُفترض أن نرى في الخرج بطاقة مُختارة عشوائيًا من المجموعة، إضافةً إلى نوع البطاقة وترتيبها:
</p>

<pre class="ipsCode">--- drawing playing card ---
drew card: Q of Diamonds
card suit: Diamonds
card rank: Q
</pre>

<p>
	قد تكون النتيجة المطبوعة مختلفةً عن النتيجة المعروضة أعلاه، لأن البطاقة مسحوبة عشوائيًا من المجموعة، لكن يجب أن تكون مشابهة. يُطبع السطر الأول قبل سحب البطاقة العشوائية من المجموعة، ثم يُطبع السطر الثاني بمجرد سحب البطاقة. يمكن رؤية خرج البطاقة باستخدام القيمة المُرجعة من التابع <code>String</code> من <code>PlayingCard</code>. يمكننا أخيرًا رؤية سطري النوع والرتبة المطبوعة بعد تحويل قيمة <code>{}interface</code> المستلمة إلى قيمة <code>PlayingCard*</code> مع الوصول إلى حقول <code>Suit</code> و <code>Rank</code>.
</p>

<p>
	أنشأنا في هذا القسم مجموعة <code>Deck</code> تستخدم قيم <code>{}interface</code> لتخزين والتفاعل مع أي قيمة، وأيضًا نوع <code>PlayingCard</code> ليكون بمثابة البطاقات داخل هذه الدستة <code>Deck</code>، ثم استخدمنا <code>Deck</code> و <code>PlayingCard</code> لاختيار بطاقة عشوائية من المجموعة وطباعة معلومات حول تلك البطاقة.
</p>

<p>
	للوصول إلى معلومات محددة تتعلق بقيمة <code>PlayingCard*</code> التي سُحبت، كُنّا بحاجة لعمل إضافي يتجلى بتحويل النوع <code>{}interface</code> إلى النوع <code>PlayingCard*</code> مع الوصول إلى حقول <code>Suit</code> و <code>Rank</code>. ستعمل الأمور في المجموعة <code>Deck</code> جيدًا بهذه الطريقة، ولكن قد ينتج أخطاء إذا أُضيفت قيمة أخرى غير <code>PlayingCard*</code> إلى المجموعة <code>Deck</code>.
</p>

<p>
	سنُعدّل في القسم التالي المجموعة <code>Deck</code>، بحيث يمكننا الاستفادة من خصائص الأنواع القوية والتحقق الثابت للأنواع في لغة جو، مع الاحتفاظ بالمرونة في قبول قيم <code>{}interface</code>، وذلك من خلال استخدام <a href="https://academy.hsoub.com/programming/rust/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D9%85%D8%B9%D9%85%D9%85%D8%A9-generic-types-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-rust-r1935/" rel="">الأنواع المعممة</a>.
</p>

<h2 id="-1">
	التجميعات في لغة جو مع استخدام الأنواع المعممة
</h2>

<p>
	أنشأنا في القسم السابق تجميعةً باستخدام شريحة من أنواع <code>{}interface</code>، ولكن كان علينا إتمام عمل إضافي لاستخدام تلك القيم يتجلى بتحويل القيم من نوع <code>{}interface</code> إلى النوع الفعلي لتلك القيم. يمكننا باستخدام الأنواع المعممة إنشاء معامل واحد أو أكثر للأنواع، والتي تعمل تقريبًا مثل معاملات الدالة، ولكن يمكن أن تحتوي على أنواع بدلًا من بيانات. توفر الأنواع المعممة بهذا الأسلوب طريقةً لاستبدال نوع مختلف من معاملات الأنواع في كل مرة يجري فيها استخدام النوع المعمم. هنا يأتي اسم الأنواع المعممة؛ فبما أن النوع المعمم يمكن استخدامه مع أنواع متعددة، وليس فقط نوع محدد مثل <code>io.Reader</code> أو <code>{}interface</code>، يكون عامًا بما يكفي ليناسب عدة حالات استخدام.
</p>

<p>
	نُعدّل في هذا القسم مجموعة ورق اللعب <code>Deck</code> لتكون من نوع معمم يمكنه استخدام أي نوع محدد للبطاقة عند إنشاء نسخة من <code>Deck</code> بدلًا من استخدام <code>{}interface</code>.
</p>

<p>
	نفتح ملف "main.go" ونحذف استيراد حزمة <code>os</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_15" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"math/rand"</span><span class="pln">
    </span><span class="com">// "os" حذف استيراد </span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span></pre>

<p>
	لن نحتاج بعد الآن إلى استخدام دالة <code>os.Exit</code>، لذا فمن الآمن حذف هذا الاستيراد. نُعدّل البنية <code>Deck</code> الآن لتكون نوعًا معممًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_17" style=""><span class="pun">...</span><span class="pln">

type </span><span class="typ">Deck</span><span class="pun">[</span><span class="pln">C any</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    cards </span><span class="pun">[]</span><span class="pln">C
</span><span class="pun">}</span></pre>

<p>
	يقدم هذا التحديث الصيغة الجديدة لتعريف البنية <code>Deck</code>، بحيث نجعلها تحقق مفهوم النوع المُعمم، وذلك من خلال استخدام مفهوم "الموضع المؤقت Placeholder" أو "معاملات النوع Type parameters". يمكننا التفكير في هذه المعاملات بطريقة مشابهة للمعاملات التي نُضمّنُها في دالة؛ فعند استدعاء دالة، تُقدم قيمًا لكل معامل في الدالة. وهنا أيضًا، عند إنشاء قيمة من النوع المعمم، نُقدّم أنواعًا لمعاملات الأنواع.
</p>

<p>
	نلاحظ بعد اسم البنية <code>Deck</code> أننا أضفنا عبارة داخل قوسين مربعين (<code>[]</code>)، إذ تسمح هذه الأقواس المعقوفة لنا بتحديد معامل أو أكثر من هذه المعاملات للبنية.
</p>

<p>
	نحتاج في حالتنا للنوع <code>Deck</code> إلى معامل نوع واحد فقط، يُطلق عليه اسم <code>C</code>، لتمثيل نوع البطاقات في المجموعة <code>deck</code>. من خلال كتابتنا <code>C any</code> نكون قد صرّحنا عن معامل نوع باسم <code>C</code> يمكننا استخدامه في البنية <code>struct</code> ليُمثّل أي نوع (<code>any</code>). ينوب <code>any</code> عن الأنواع المختلفة، أي يُعرف نوعه في الزمن الحقيقي، أي وقت تمرير قيمته، فإذا كانت القيمة المُمررة سلسلة يكون سلسلة، وإذا كانت عددًا صحيحًا يكون صحيحًا.
</p>

<p>
	يُعد النوع <code>any</code> في حقيقة الأمر اسمًا بديلًا للنوع <code>{}interface</code>، وهذا ما يجعل الأنواع المعممة أكثر سهولة للقراءة، فنحن بغنى عن كتابة <code>{}C interface</code> ونكتب <code>C any</code> فقط. يحتاج <code>deck</code> الخاص بنا إلى نوع معمم واحد فقط لتمثيل البطاقات، ولكن إذا كنا نحتاج إلى أنواع معممة إضافية، فيمكن إضافتها من خلال فصلها بفواصل، مثل <code>F any</code>, <code>C any</code>. يمكن أن يكون اسم المعامل المُستخدم لمعاملات النوع أي شيء نرغب فيه إذا لم يكن محجوزًا، ولكنها عادةً قصيرة وتبدأ بحرف كبير.
</p>

<p>
	عدّلنا أيضًا نوع الشريحة <code>cards</code> في البنية <code>Deck</code> ليكون من النوع <code>C</code>. عند استخدام مفهوم الأنواع المعممة، يمكننا استخدام معاملات النوع لتنوب عن أي نوع، أي يمكننا وضعها في نفس الأماكن التي نضع فيها أنواع البيانات عادةً. في حالتنا، نريد أن يمثل المعامل <code>C</code> كل بطاقة في الشريحة، لذا نضع اسم الشريحة ثم <code>[]</code> ثم المعامل <code>C</code> الذي سينوب عن النوع.
</p>

<p>
	نُعدّل الآن التابع <code>AddCard</code> لاستخدام النوع المعمّم الذي عرّفناه، لكن سنتخطى تعديل دالة <code>NewPlayingCardDeck</code> حاليًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_19" style=""><span class="pun">...</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">d </span><span class="pun">*</span><span class="typ">Deck</span><span class="pun">[</span><span class="pln">C</span><span class="pun">])</span><span class="pln"> </span><span class="typ">AddCard</span><span class="pun">(</span><span class="pln">card C</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    d</span><span class="pun">.</span><span class="pln">cards </span><span class="pun">=</span><span class="pln"> append</span><span class="pun">(</span><span class="pln">d</span><span class="pun">.</span><span class="pln">cards</span><span class="pun">,</span><span class="pln"> card</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تضمّن التحديث الخاص بالتابع <code>AddCard</code> في <code>Deck</code>، إضافة المعامل المُعمّم <code>[C]</code> إلى المستقبل الخاص بالتابع. يُخبر هذا لغة جو باسم المعامل المُعمّم الذي سنستخدمه في أماكن أخرى في تصريح التابع، ويتيح لنا ذلك معرفة النوع المُمرّر مثل قيمة إلى معامل النوع <code>C</code> عند استخدام التابع. يتبع هذا النوع من الكتابة بأقواس معقوفة نفس طريقة التصريح عن بنية <code>struct</code>، إذ يُعرّف معامل النوع داخل الأقواس المعقوفة بعد اسم الدالة والمستقبل. لا نحتاج في حالتنا إلى تحديد أي قيود لأنها مُحددة فعلًا في تصريح <code>Deck</code>. عدّلنا أيضًا معامل الدالة <code>card</code> لاستخدام معامل النوع <code>C</code> بدلًا من النوع الأصلي <code>{}interface</code>، وهذا يتيح للدالة استخدام النوع <code>C</code> الذي سيُعرف ما هو لاحقًا.
</p>

<p>
	بعد تعديل التابع <code>AddCard</code>، نُعدّل التابع <code>RandomCard</code> لاستخدام النوع المعمم <code>C</code> أيضًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_21" style=""><span class="pun">...</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">d </span><span class="pun">*</span><span class="typ">Deck</span><span class="pun">[</span><span class="pln">C</span><span class="pun">])</span><span class="pln"> </span><span class="typ">RandomCard</span><span class="pun">()</span><span class="pln"> C </span><span class="pun">{</span><span class="pln">
    r </span><span class="pun">:=</span><span class="pln"> rand</span><span class="pun">.</span><span class="typ">New</span><span class="pun">(</span><span class="pln">rand</span><span class="pun">.</span><span class="typ">NewSource</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">().</span><span class="typ">UnixNano</span><span class="pun">()))</span><span class="pln">

    cardIdx </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Intn</span><span class="pun">(</span><span class="pln">len</span><span class="pun">(</span><span class="pln">d</span><span class="pun">.</span><span class="pln">cards</span><span class="pun">))</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> d</span><span class="pun">.</span><span class="pln">cards</span><span class="pun">[</span><span class="pln">cardIdx</span><span class="pun">]</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بدلًا من استخدام نوع معمّم <code>C</code> مثل معامل دالة، عدّلنا التابع <code>()RandomCard</code> ليعيد قيمةً من النوع المُعمّم <code>C</code> بدلًا من معامل الدالة <code>{}interface</code>. هذا يعني أنه عند استدعاء هذا التابع، سنحصل على قيمة من النوع <code>C</code> المحدد، بدلًا من الحاجة إلى تحويل القيمة إلى نوع محدد بواسطة "تأكيدات النوع type assertion". عدّلنا أيضًا المستقبل الخاص بالدالة ليتضمن <code>[C]</code>، وهو ما يخبر لغة جو بأن الدالة هي جزء من <code>Deck</code> التي تستخدم النوع المعمم <code>C</code> مثل معامل. لا نحتاج إلى أي تحديثات أخرى في الدالة. حقل <code>cards</code> في <code>Deck</code> مُعدّل فعلًا في تصريح البنية <code>struct</code> ليكون من النوع <code>C[]</code>، مما يعني أنه عندما يعيد التابع قيمة من <code>cards</code>، فإنه يعيد قيمةً من النوع <code>C</code> المحدد، وبالتالي لا حاجة لتحويل النوع بعد ذلك.
</p>

<p>
	بعد تعديلنا للنوع <code>Deck</code> لاستخدام الأنواع المعممة، نعود للدالة <code>NewPlayingCardDeck</code> لجعلها تستخدم النوع المعمم <code>Deck</code> مع الأنواع <code>PlayingCard*</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_23" style=""><span class="pun">...</span><span class="pln">

func </span><span class="typ">NewPlayingCardDeck</span><span class="pun">()</span><span class="pln"> </span><span class="pun">*</span><span class="typ">Deck</span><span class="pun">[*</span><span class="typ">PlayingCard</span><span class="pun">]</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    suits </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">string</span><span class="pun">{</span><span class="str">"Diamonds"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Hearts"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Clubs"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Spades"</span><span class="pun">}</span><span class="pln">
    ranks </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">string</span><span class="pun">{</span><span class="str">"A"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"2"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"3"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"4"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"5"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"6"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"7"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"8"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"9"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"10"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"J"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Q"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"K"</span><span class="pun">}</span><span class="pln">

    deck </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">Deck</span><span class="pun">[*</span><span class="typ">PlayingCard</span><span class="pun">]{}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> _</span><span class="pun">,</span><span class="pln"> suit </span><span class="pun">:=</span><span class="pln"> range suits </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> _</span><span class="pun">,</span><span class="pln"> rank </span><span class="pun">:=</span><span class="pln"> range ranks </span><span class="pun">{</span><span class="pln">
            deck</span><span class="pun">.</span><span class="typ">AddCard</span><span class="pun">(</span><span class="typ">NewPlayingCard</span><span class="pun">(</span><span class="pln">suit</span><span class="pun">,</span><span class="pln"> rank</span><span class="pun">))</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> deck
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	تبقى معظم الشيفرة في <code>NewPlayingCardDeck</code> كما هي، باستثناء بعض التغييرات البسيطة، ولكن الآن نستخدم إصدارًا مُعمّمًا من <code>Deck</code>، ويجب علينا تحديد النوع الذي نرغب في استخدامه مع <code>C</code> عند استخدام <code>Deck</code>. نفعل ذلك عن طريق الإشارة إلى نوع <code>Deck</code> بالطريقة المعتادة، سواء كان <code>Deck</code> نفسه أو مرجعًا مثل <code>Deck*</code>، ثم تقديم النوع الذي يجب استبدال <code>C</code> به باستخدام نفس الأقواس المعقوفة التي استخدمناها عند التصريح عن معاملات النوع في البداية. بمعنى آخر، عندما نُعرّف متغير من نوع <code>Deck</code> المعمّم، يجب علينا تحديد النوع الفعلي للمعامل <code>C</code>؛ فإذا كانت <code>Deck</code> من النوع <code>Deck*</code>، يجب أن نُوفّر النوع الفعلي الذي يجب استبدال <code>C</code> به في الأقواس المعقوفة مثل <code>[PlayingCard*]</code>. هكذا نحصل على نسخة محددة من <code>Deck</code> لنوع البطاقات التي ترغب في استخدامها، والتي في هذه الحالة هي <code>PlayingCard*</code>.
</p>

<p>
	يمكن تشبيه هذه العملية لإرسال قيمة لمعامل دالة عند استدعاء الدالة؛ فعند استخدامنا لنوع <code>Deck</code> المعمّم، فإننا نمرر النوع الذي نرغب في استخدامه مثل معامل لهذا النوع.
</p>

<p>
	بالنسبة لنوع القيمة المُعادة في <code>NewPlayingCardDeck</code>، نستمر في استخدام <code>Deck*</code> كما فعلت من قبل، لكن هذه المرة، يجب أن تتضمن الأقواس المعقوفة و <code>PlayingCard*</code> أيضًا؛ فمن خلال تقديم <code>[PlayingCard*]</code> لمعامل النوع، فأنت تقول أنك ترغب في استخدام النوع <code>PlayingCard*</code> في تصريح <code>Deck</code> والتوابع الخاصة به ليحل محل قيمة <code>C</code>، وهذا يعني أن نوع حقل <code>cards</code> في <code>Deck</code> يتغير فعليًا من <code>C[]</code> إلى <code>PlayingCard*[]</code>.
</p>

<p>
	بمعنى آخر، عندما ننشئ نسخةً جديدةً من <code>Deck</code> باستخدام <code>NewPlayingCardDeck</code>، فإننا نُنشئ <code>Deck</code> يمكنه تخزين أي نوع محدد من <code>PlayingCard*</code>. هذا يتيح لنا استخدام مجموعة متنوعة من أنواع البطاقات في <code>Deck</code> بدلًا من الاعتماد على <code>{}interface</code>. بالتالي يمكننا الآن التعامل مباشرةً مع البطاقات بنوعها الفعلي بدلًا من الحاجة إلى استبدال الأنواع والتحويلات التي كنا نستخدمها مع <code>{}interface</code> في الإصدار السابق من <code>Deck</code>.
</p>

<p>
	بالمثل، عند إنشاء نسخة جديدة من <code>Deck</code>، يجب علينا أيضًا توفير النوع الذي يحل محل <code>C</code>. نستخدم عادةً <code>{}Deck&amp;</code> لإنشاء مرجع جديد إلى <code>Deck</code>، ولكن بدلًا من ذلك يجب علينا تضمين النوع داخل الأقواس المعقوفة للحصول على <code>{}[PlayingCard*]&amp;</code>.
</p>

<p>
	الآن بعد أن عدّلنا الأنواع الخاصة بنا لاستخدام الأنواع المُعمّمة، نُعدّل الدالة <code>main</code> للاستفادة منها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_25" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    deck </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">NewPlayingCardDeck</span><span class="pun">()</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"--- drawing playing card ---\n"</span><span class="pun">)</span><span class="pln">
    playingCard </span><span class="pun">:=</span><span class="pln"> deck</span><span class="pun">.</span><span class="typ">RandomCard</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"drew card: %s\n"</span><span class="pun">,</span><span class="pln"> playingCard</span><span class="pun">)</span><span class="pln">
    </span><span class="com">// Code removed</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"card suit: %s\n"</span><span class="pun">,</span><span class="pln"> playingCard</span><span class="pun">.</span><span class="typ">Suit</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"card rank: %s\n"</span><span class="pun">,</span><span class="pln"> playingCard</span><span class="pun">.</span><span class="typ">Rank</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أزلنا هذه المرة بعض التعليمات، لأنه لم تعد هناك حاجة لتأكيد قيمة <code>{}interface</code> على أنها <code>PlayingCard*</code>. عندما عدّلنا التابع <code>RandomCard</code> في <code>Deck</code> لإعادة النوع <code>C</code> و <code>NewPlayingCardDeck</code> لإعادة <code>[Deck[*PlayingCard*</code>، نكون قد عدّلنا التابع <code>RandomCard</code> بجعله يعيد <code>PlayingCard*</code> بدلًا من <code>{}interface</code>. هذا يعني أن نوع <code>playingCard</code> هو <code>PlayingCard*</code> أيضًا بدلًا من <code>interface{}</code> ويمكننا الوصول إلى حقول <code>Suit</code> و <code>Rank</code> مباشرةً.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سنشاهد خرجًا مشابهًا لما سبق، ولكن البطاقة المسحوبة قد تكون مختلفة:
</p>

<pre class="ipsCode">--- drawing playing card ---
drew card: 8 of Hearts
card suit: Hearts
card rank: 8
</pre>

<p>
	على الرغم من أن الخرج هو نفسه مثل الإصدار السابق من البرنامج عندما استخدمنا <code>{}interface</code>، إلا أن الشيفرة أنظف وتتجنب الأخطاء المحتملة. لم يعد هناك حاجةٌ لتأكيد النوع <code>PlayingCard*</code>، مما يُجنّبنا التعامل مع الأخطاء الإضافية، كما أنّه من خلال تحديد أن نسخة <code>Deck</code> يمكن أن تحتوي فقط على <code>PlayingCard*</code>، لن يكون هناك أي احتمال لوجود قيمة أخرى غير <code>PlayingCard*</code> تُضاف إلى مجموعة البطاقات.
</p>

<p>
	عدّلنا في هذا القسم البنية <code>Deck</code> لتكون نوعًا مُعمّمًا، مما يوفر مزيدًا من السيطرة على أنواع البطاقات التي يمكن أن تحتويها كل نسخة من مجموعة أوراق اللعب. عدّلنا أيضًا التابعين <code>AddCard</code> و <code>RandomCard</code> إما لقبول وسيط مُعمم أو لإرجاع قيمة مُعممة. عدّلنا أيضًا <code>NewPlayingCardDeck</code> لإرجاع <code>Deck*</code> يحتوي على بطاقات <code>PlayingCard*</code>. أخيرًا أزلنا التعامل مع الأخطاء في الدالة <code>main</code> لأنه لم يعد هناك حاجة لذلك.
</p>

<p>
	الآن بعد تعديل <code>Deck</code> ليكون مُعممًا، يمكننا استخدامه لاحتواء أي نوع من البطاقات التي نرغب. سنستفيد في القسم التالي من هذه المرونة من خلال إضافة نوع جديد من البطاقات إلى البرنامج.
</p>

<h2 id="-2">
	استخدام أنواع مختلفة مع الأنواع المعممة
</h2>

<p>
	يمكننا -بإنشاء نوع مُعمّم مثل النوع <code>Deck</code>- استخدامه مع أي نوع آخر؛ فعندما نُنشئ نسخةً من <code>Deck</code>، ونرغب باستخدامها مع أنواع <code>PlayingCard*</code>، فإن الشيء الوحيد الذي نحتاجه هو تحديد هذا النوع عند إنشاء القيمة، وإذا أردنا استخدام نوع آخر، نُبدّل نوع <code>PlayingCard*</code> بالنوع الجديد الذي نرغب باستخدامه.
</p>

<p>
	نُنشئ في هذا القسم بنية بيانات جديدة تُسمى <code>TradingCard</code> لتمثيل نوع مختلف من البطاقات، ثم نُعدّل البرنامج لإنشاء <code>Deck</code> يحتوي على عدة <code>TradingCard*</code>.
</p>

<p>
	لإنشاء النوع <code>TradingCard</code>، نفتح ملف "main.go" مرةً أخرى ونضيف التعريف التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_27" style=""><span class="pun">...</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">TradingCard</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">CollectableName</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">NewTradingCard</span><span class="pun">(</span><span class="pln">collectableName string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="typ">TradingCard</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">TradingCard</span><span class="pun">{</span><span class="typ">CollectableName</span><span class="pun">:</span><span class="pln"> collectableName</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">tc </span><span class="pun">*</span><span class="typ">TradingCard</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> tc</span><span class="pun">.</span><span class="typ">CollectableName</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	النوع <code>TradingCard</code> مشابه لنوع <code>PlayingCard</code>، ولكن بدلًا من وجود حقول <code>Suit</code> و <code>Rank</code>، يحتوي على حقل <code>CollectableName</code> لتتبع اسم بطاقة التداول، كما يتضمن دالة الباني <code>NewTradingCard</code> والتابع <code>String</code>، أي بطريقة مشابهة للنوع <code>PlayingCard</code>.
</p>

<p>
	نُنشئ الآن الدالة البانية <code>NewTradingCardDeck</code> لإنشاء <code>Deck</code> مُعبأة بقيم <code>TradingCards*</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_29" style=""><span class="pun">...</span><span class="pln">

func </span><span class="typ">NewPlayingCardDeck</span><span class="pun">()</span><span class="pln"> </span><span class="pun">*</span><span class="typ">Deck</span><span class="pun">[*</span><span class="typ">PlayingCard</span><span class="pun">]</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">NewTradingCardDeck</span><span class="pun">()</span><span class="pln"> </span><span class="pun">*</span><span class="typ">Deck</span><span class="pun">[*</span><span class="typ">TradingCard</span><span class="pun">]</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    collectables </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">string</span><span class="pun">{</span><span class="str">"Sammy"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Droplets"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Spaces"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"App Platform"</span><span class="pun">}</span><span class="pln">

    deck </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">Deck</span><span class="pun">[*</span><span class="typ">TradingCard</span><span class="pun">]{}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> _</span><span class="pun">,</span><span class="pln"> collectable </span><span class="pun">:=</span><span class="pln"> range collectables </span><span class="pun">{</span><span class="pln">
        deck</span><span class="pun">.</span><span class="typ">AddCard</span><span class="pun">(</span><span class="typ">NewTradingCard</span><span class="pun">(</span><span class="pln">collectable</span><span class="pun">))</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> deck
</span><span class="pun">}</span></pre>

<p>
	عند إنشاء أو إرجاع <code>Deck*</code> في هذه الحالة، فإننا نُبدّل قيم <code>PlayingCard*</code> بقيم <code>TradingCard*</code>، وهذا هو الأمر الوحيد الذي نحتاج إلى تغييره في <code>Deck</code>. هناك مجموعة من <a href="https://www.digitalocean.com/" rel="external nofollow">البطاقات الخاصة</a>، والتي يمكننا المرور عليها لإضافة كل <code>TradingCard*</code> إلى <code>Deck</code>. يعمل التابع <code>AddCard</code> في <code>Deck</code> بنفس الطريقة كما في السابق، لكن هذه المرة يقبل قيمة <code>TradingCard*</code> من <code>NewTradingCard</code>. إذا حاولنا تمرير قيمة من <code>NewPlayingCard</code>، سيُعطي المُصرّف خطأ لأنه يتوقع <code>TradingCard*</code> وليس <code>PlayingCard*</code>.
</p>

<p>
	نُعدّل الدالة <code>main</code> لإنشاء <code>Deck</code> جديدة من أجل عدة <code>TradingCard*</code>، وسحب بطاقة عشوائية باستخدام <code>RandomCard</code>، وطباعة معلومات البطاقة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_31" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    playingDeck </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">NewPlayingCardDeck</span><span class="pun">()</span><span class="pln">
    tradingDeck </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">NewTradingCardDeck</span><span class="pun">()</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"--- drawing playing card ---\n"</span><span class="pun">)</span><span class="pln">
    playingCard </span><span class="pun">:=</span><span class="pln"> playingDeck</span><span class="pun">.</span><span class="typ">RandomCard</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"card rank: %s\n"</span><span class="pun">,</span><span class="pln"> playingCard</span><span class="pun">.</span><span class="typ">Rank</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"--- drawing trading card ---\n"</span><span class="pun">)</span><span class="pln">
    tradingCard </span><span class="pun">:=</span><span class="pln"> tradingDeck</span><span class="pun">.</span><span class="typ">RandomCard</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"drew card: %s\n"</span><span class="pun">,</span><span class="pln"> tradingCard</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"card collectable name: %s\n"</span><span class="pun">,</span><span class="pln"> tradingCard</span><span class="pun">.</span><span class="typ">CollectableName</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أنشأنا مجموعة جديدة من بطاقات التداول باستخدام <code>NewTradingCardDeck</code> وخزّناها في <code>tradingDeck</code>. نظرًا لأننا لا نزال نستخدم نفس النوع <code>Deck</code> كما كان من قبل، يمكننا استخدام <code>RandomCard</code> للحصول على بطاقة تداول عشوائية من <code>Deck</code> وطباعة البطاقة. يمكننا أيضًا الإشارة إلى وطباعة حقل <code>CollectableName</code> مباشرةً من <code>tradingCard</code> لأن <code>Deck</code> المُعمّم الذي نستخدمه قد عرّف <code>C</code> على أنه <code>TradingCard*</code>.
</p>

<p>
	يُظهر هذا التحديث أيضًا قيمة استخدام الأنواع المُعمّمة. لدعم نوع بطاقة جديد تمامًا، لم نحتاج إلى تغيير <code>Deck</code> إطلاقًا، فمن خلال معاملات النوع في <code>Deck</code> تمكّننا من تحديد نوع البطاقة الذي نريده، وبدءًا من هذه النقطة فصاعدًا، يُستخدم النوع <code>TradingCard*</code> بدلًا من النوع <code>PlayingCard*</code> في أية تفاعلات مع قيم <code>Deck</code>.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ليكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">--- drawing playing card ---
drew card: Q of Diamonds
card suit: Diamonds
card rank: Q
--- drawing trading card ---
drew card: App Platform
card collectable name: App Platform
</pre>

<p>
	يجب أن نرى نتيجةً مشابهة للنتيجة الموضحة أعلاه (مع بطاقات مختلفة بسبب العشوائية) بعد انتهاء تشغيل البرنامج. تظهر بطاقتين مسحوبتين: البطاقة الأصلية للّعب وبطاقة التداول الجديدة المُضافة.
</p>

<p>
	أضفنا في هذا القسم النوع <code>TradingCard</code> الجديد لتمثيل نوع بطاقة مختلف عن نوع البطاقة الأصلية <code>PlayingCard</code>. بمجرد إضافة نوع البطاقة <code>TradingCard</code>،أنشأنا الدالة <code>NewTradingCardDeck</code> لإنشاء وملء مجموعة ورق اللعب ببطاقات التداول. أخيرًا عدّلنا الدالة <code>main</code> لاستخدام مجموعة ورق التداول الجديدة وطباعة معلومات مُتعلقة بالبطاقة العشوائية المستخرجة.
</p>

<p>
	بصرف النظر عن إنشاء دالة جديدة <code>NewTradingCardDeck</code> لملء الدستة ببطاقات مختلفة، لم نحتاج إلى إجراء أي تحديثات أخرى على مجموعة ورق اللعب لدعم نوع بطاقة جديد تمامًا. هذه هي قوة الأنواع المُعمّمة؛ إذ يمكننا كتابة الشيفرة مرةً واحدةً فقط واستخدامها متى أردنا لأنواع مختلفة من البيانات المماثلة أيضًا. هناك مشكلة واحدة في <code>Deck</code> الحالي، وهي أنه يمكن استخدامه لأي نوع، بسبب التصريح <code>C any</code> الذي لدينا. قد يكون هذا هو ما نريده حتى نتمكن من إنشاء مجموعة ورق لعب للقيم <code>int</code> بكتابة <code>{}[Deck[int&amp;</code>، ولكن إذا كنا نرغب في أن يحتوي <code>Deck</code> فقط على بطاقات، سنحتاج إلى طريقة لتقييد أنواع البيانات المسموح بها مع <code>C</code>.
</p>

<h2 id="-3">
	القيود على الأنواع المعممة
</h2>

<p>
	غالبًا لا نرغب أو نحتاج إلى أي قيود على الأنواع المستخدمة في الأنواع المُعمّمة لأننا قد لا نهتم بنوع البيانات المُستخدمة، لكن قد نحتاج في أحيان أخرى إلى القدرة على تقييد الأنواع المستخدمة بواسطة الأنواع المُعمّمة. على سبيل المثال، إذا كُنّا نُنشئ نوعًا مُعمّمًا <code>Sorter</code>، قد نرغب في تقييد أنواعه المُعمّمة لتلك التي تحتوي على تابع <code>Compare</code> (أي تقييد الأنواع المستخدمة معه، بحيث تكون فقط الأنواع القابلة للمقارنة)، حتى يتمكن <code>Sorter</code> من مقارنة العناصر التي يحتوي عليها. بدون هذا القيد، فقد لا تحتوي القيم على التابع <code>Compare</code>، ولن يعرف <code>Sorter</code> كيفية مقارنتها.
</p>

<p>
	نُنشئ في هذا القسم واجهةً جديدة تسمى <code>Card</code>، ثم نُعدّل <code>Deck</code> للسماح فقط بإضافة أنواع <code>Card</code>.
</p>

<p>
	لتطبيق التحديثات، افتح ملف "main.go" وضِف الواجهة <code>Card</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_33" style=""><span class="pun">...</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
</span><span class="pun">...</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">Card</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Stringer</span><span class="pln">

    </span><span class="typ">Name</span><span class="pun">()</span><span class="pln"> string
</span><span class="pun">}</span></pre>

<p>
	واجهة <code>Card</code> مُعرّفة بنفس طريقة تعريف أي <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-interfaces-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1999/" rel="">واجهة أخرى في لغة جو</a>؛ وليس هناك متطلبات خاصة لاستخدامها مع الأنواع المُعمّمة. نحن نقول في واجهة <code>Card</code> هذه، أنه لكي نعد شيئًا ما بطاقة <code>Card</code>، يجب أن يُحقق النوع <a href="https://pkg.go.dev/fmt#Stringer" rel="external nofollow"><code>fmt.Stringer</code></a> (يجب أن يحتوي على تابع <code>String</code> الذي تحتويه البطاقات فعلًا)، ويجب أيضًا أن يحتوي على تابع <code>Name</code> الذي يعيد قيمة من نوع <code>string</code>.
</p>

<p>
	نُعدّل الآن أنواع <code>TradingCard</code> و <code>PlayingCard</code> لإضافة التابع <code>Name</code> الجديد، إضافةً إلى التابع <code>String</code> الموجود فعلًا، لكي نستوفي شروط تحقيق الواجهة <code>Card</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_35" style=""><span class="pun">...</span><span class="pln">

</span><span class="pun">...</span><span class="pln">

type </span><span class="typ">TradingCard</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">tc </span><span class="pun">*</span><span class="typ">TradingCard</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Name</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> tc</span><span class="pun">.</span><span class="typ">String</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span><span class="pln">

type </span><span class="typ">PlayingCard</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">pc </span><span class="pun">*</span><span class="typ">PlayingCard</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Name</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> pc</span><span class="pun">.</span><span class="typ">String</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يحتوي كُلًا من <code>TradingCard</code> و <code>PlayingCard</code> فعليًا على تابع <code>String</code> الذي يُحقق الواجهة <code>fmt.Stringer</code>. لذا، لتحقيق الواجهة <code>Card</code>، نحتاج فقط إلى إضافة التابع الجديد <code>Name</code>. بما أن <code>fmt.Stringer</code> يمكنها إرجاع أسماء البطاقات، يمكننا ببساطة إرجاع نتيجة التابع <code>String</code> في التابع <code>Name</code>.
</p>

<p>
	نُعدّل الآن <code>Deck</code>، بحيث يُسمح فقط باستخدام أنواع <code>Card</code> مع <code>C</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_37" style=""><span class="pun">...</span><span class="pln">

type </span><span class="typ">Deck</span><span class="pun">[</span><span class="pln">C </span><span class="typ">Card</span><span class="pun">]</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    cards </span><span class="pun">[]</span><span class="pln">C
</span><span class="pun">}</span></pre>

<p>
	كان لدينا <code>C any</code> قبل هذا التحديث مثل قيد نوع type constraint والمعروف أيضًا بتقييد النوع type restriction، وهو ليس قيدًا صارمًا. بما أن <code>any</code> هو اسم <code>{}interface</code> البديل (له نفس المعنى)، فقد سمح باستخدام أي نوع في جو بمثابة قيمة للمعامل <code>C</code>. الآن بعدما عوّضنا <code>any</code> بالواجهة <code>Card</code> الجديدة، سيتحقق المُصرّف في جو أن أي نوع مستخدم مع <code>C</code> يُحقق واجهة <code>Card</code> عند تصريف البرنامج.
</p>

<p>
	يمكننا الآن بعد وضع هذا القيد، استخدام أي توابع تقدمها الواجهة <code>Card</code> داخل توابع النوع <code>Deck</code>. إذا أردنا من <code>RandomCard</code> طباعة اسم البطاقة المسحوبة، ستتمكن من الوصول إلى التابع <code>Name</code> لأنها جزء من الواجهة <code>Card</code>. سنرى ذلك في الجزء التالي.
</p>

<p>
	هذه التحديثات القليلة هي التحديثات الوحيدة التي نحتاج إليها لتقييد النوع <code>Deck</code>، بحيث يستخدم فقط لقيم <code>Card</code>.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج:
</p>

<pre class="ipsCode">--- drawing playing card ---
drew card: 5 of Clubs
card suit: Clubs
card rank: 5
--- drawing trading card ---
drew card: Droplets
card collectable name: Droplets
</pre>

<p>
	نلاحظ أن الناتج لم يتغير من حيث الشكل العام، فنحن لم نُعدّل بالوظيفة الرئيسية للبرنامج؛ وضعنا فقط قيودًا على الأنواع، إذ أضفنا في هذا الجزء الواجهة <code>Card</code> الجديدة وعدّلنا كلًا من <code>TradingCard</code> و <code>PlayingCard</code> لتحقيق هذه الواجهة. عدّلنا أيضًا <code>Deck</code> بهدف تقييد الأنواع التي يتعامل معها، بحيث يكون النوع المستخدم يُحقق الواجهة <code>Card</code>.
</p>

<p>
	كل ما فعلناه إلى الآن هو إنشاء نوع بيانات جديد من خلال مفهوم البنى <code>struct</code> وجعله مُعمّمًا، لكن لغة جو تسمح أيضًا بإنشاء دوال مُعممة، إضافةً إلى إنشاء أنواع مُعممة.
</p>

<h2 id="-4">
	إنشاء دوال معممة
</h2>

<p>
	يتبع إنشاء دوال مُعممة في لغة جو طريقة تصريح مشابه جدًا للأنواع المُعممة في لغة جو. يتطلب إنشاء دوال مُعممة إضافة مجموعة ثانية من المعاملات إلى تلك الدوال كما هو الحال في الأنواع المُعممة التي تحتوي على معاملات نوع.
</p>

<p>
	نُنشئ في هذا الجزء دالة مُعممة جديدة باسم <code>printCard</code>، ونستخدم تلك الدالة لطباعة اسم البطاقة المقدمة.
</p>

<p>
	نفتح ملف "main.go" ونجري التحديثات التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_40" style=""><span class="pun">...</span><span class="pln">

func printCard</span><span class="pun">[</span><span class="pln">C any</span><span class="pun">](</span><span class="pln">card C</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"card name:"</span><span class="pun">,</span><span class="pln"> card</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"card collectable name: %s\n"</span><span class="pun">,</span><span class="pln"> tradingCard</span><span class="pun">.</span><span class="typ">CollectableName</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"--- printing cards ---\n"</span><span class="pun">)</span><span class="pln">
    printCard</span><span class="pun">[*</span><span class="typ">PlayingCard</span><span class="pun">](</span><span class="pln">playingCard</span><span class="pun">)</span><span class="pln">
    printCard</span><span class="pun">(</span><span class="pln">tradingCard</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نلاحظ أن التصريح عن الدالة <code>printCard</code> مألوف من ناحية تعريف معاملات النوع (أقواس معقوفة تحتوي على معاملات النوع المعممة. تحدد هذه المعاملات النوع الذي سيجري استخدامه في الدالة)، تليها المعاملات العادية للدالة داخل قوسين، ثم في دالة <code>main</code>، تستخدم الدالة <code>printCard</code> لطباعة كل من <code>PlayingCard*</code> و <code>TradingCard*</code>.
</p>

<p>
	قد نلاحظ أن أحد استدعاءات <code>printCard</code> يتضمن معامل النوع <code>[PlayingCard*]</code>، بينما الاستدعاء الثاني لا يحتوي على نفس معامل النوع <code>[TradingCard*]</code>. يمكن لمُصرّف لغة جو أن يستدل على معامل النوع المقصود من القيمة التي نمررها إلى المعاملات، لذلك في حالات مثل هذه، تكون معاملات النوع اختيارية. يمكننا أيضًا إزالة معامل النوع <code>[PlayingCard*]</code>.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيظهر هذه المرة خطأ في التصريف:
</p>

<pre class="ipsCode"># وسطاء سطر الأوامر
./main.go:87:33: card.Name undefined (type C has no field or method Name)
</pre>

<p>
	عند استخدامنا للشيفرة المذكورة أعلاه وتصريفها تظهر رسالة الخطأ "card.Name undefined"، وتعني أن <code>Name</code> غير معرفة في النوع <code>C</code>. هذا يحدث لأننا استخدمنا <code>any</code> قيدًا للنوع في معامل النوع <code>C</code> في دالة <code>printCard</code>. تعني <code>any</code> أن أي نوع يمكن استخدامه مع <code>C</code>، ولكنه لا يعرف عنه أي شيء محدد مثل وجود التابع <code>Name</code> في النوع.
</p>

<p>
	لحل هذه المشكلة والسماح بالوصول إلى <code>Name</code>، يمكننا استخدام الواجهة <code>Card</code> قيدًا للنوع في <code>C</code>. تحتوي الواجهة <code>Card</code> على التابع <code>Name</code> ونُطبقها في النوعين <code>TradingCard</code> و <code>PlayingCard</code>. وبذلك تضمن لنا لغة جو أن يكون لدى <code>C</code> التابع <code>Name</code> وبالتالي يتيح لنا استخدامها في الدالة <code>printCard</code> دون وجود أخطاء في وقت التصريف.
</p>

<p>
	نُعدّل ملف "main.go" لآخر مرة لاستبدال القيد <code>any</code> بالقيد <code>Card</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_1100_42" style=""><span class="pun">...</span><span class="pln">

func printCard</span><span class="pun">[</span><span class="pln">C </span><span class="typ">Card</span><span class="pun">](</span><span class="pln">card C</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"card name:"</span><span class="pun">,</span><span class="pln"> card</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج:
</p>

<pre class="ipsCode">--- drawing playing card ---
drew card: 6 of Hearts
card suit: Hearts
card rank: 6
--- drawing trading card ---
drew card: App Platform
card collectable name: App Platform
--- printing cards ---
card name: 6 of Hearts
card name: App Platform
</pre>

<p>
	نلاحظ سحب البطاقتين كما اعتدنا سابقًا، ولكن تطبع الآن الدالة <code>printCard</code> البطاقات وتستخدم التابع <code>Name</code> للحصول على الاسم لتجري طباعته.
</p>

<p>
	أنشأنا في هذا القسم دالةً جديدة مُعممة <code>printCard</code> قادرة على أخذ أي قيمة <code>Card</code> وطباعة الاسم. رأينا أيضًا أن استخدام قيد النوع <code>any</code> بدلًا من <code>Card</code> أو قيمةً أخرى محددة يؤثر على التوابع المتاحة.
</p>

<h2 id="-5">
	الخاتمة
</h2>

<p>
	أنشأنا في هذا المقال برنامجًا جديدًا يحتوي على بنية اسمها <code>Deck</code> تُمثّل نوعًا يمكن أن يُعيد بطاقة عشوائية من مجموعة ورق اللعب بصيغة <code>{}interface</code>، وأنشأنا النوع <code>PlayingCard</code> لتمثيل بطاقة اللعب في المجموعة، ثم عدّلنا النوع <code>Deck</code> لدعم مفهوم النوع المُعمّم وتمكنّا من إزالة بعض عمليات التحقق من الأخطاء لأن النوع المُعمّم يضمن عدم ظهور ذلك النوع من الأخطاء. أنشأنا بعد ذلك نوعًا جديدًا يسمى <code>TradingCard</code> لتمثيل نوع مختلف من البطاقات التي يمكن أن تدعمها <code>Deck</code>، وكذلك أنشأنا مجموعة ورق لعب لكل نوع من أنواع البطاقات وأرجعنا بطاقةً عشوائيةً من كل مجموعة. أضفنا أيضًا قيدًا للنوع إلى البنية <code>Deck</code> لضمان أنه يمكن إضافة أنواع تُحقق الواجهة <code>Card</code> فقط إلى مجموعة ورق اللعب. أنشأنا أخيرًا دالة مُعمّمة تسمى <code>printCard</code> يمكنها طباعة اسم أي قيمة من نوع <code>Card</code> باستخدام التابع <code>Name</code>.
</p>

<p>
	يمكن أن يُقلل استخدام الأنواع المُعمّمة في الشيفرة الخاصة بنا إلى حد كبير عدد الأسطر البرمجية اللازمة لدعم أنواع متعددة لنفس الشيفرة. يجب أن نُحقق التوازن بين الأداء وسهولة قراءة الشيفرة عند استخدام الأنواع المُعمّمة؛ إذ يؤثر استخدام الأنواع المُعمّمة على الأداء، لكنه يُسهّل القراءة. من جهة أخرى، استخدام الواجهات بدلًا من الأنواع المُعمّمة أفضل من ناحية الأداء، لكنه أسوأ في القراءة. بالتالي، إذا كان بإمكاننا استخدام الواجهة بدلًا من الأنواع المُعمّمة فمن الأفضل استخدام الواجهة.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-make-http-requests-in-go" rel="external nofollow">How To Make HTTP Requests in Go</a> لصاحبه Kristin Davidson.
</p>

<h2 id="-6">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-http-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r2191/" rel="">كيفية إنشاء طلبات HTTP في لغة جو Go</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/rust/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D9%85%D8%B9%D9%85%D9%85%D8%A9-generic-types-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-rust-r1935/" rel="">مقدمة إلى مفهوم الأنواع المعممة Generic Types في لغة Rust</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D8%B9%D9%85%D9%85%D8%A9-generic-programming-r1406/" rel="">مفهوم البرمجة المعممة Generic Programming</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2206</guid><pubDate>Tue, 26 Dec 2023 16:06:03 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x625;&#x646;&#x634;&#x627;&#x621; &#x637;&#x644;&#x628;&#x627;&#x62A; HTTP &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648; Go</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-http-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r2191/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_12/---HTTP----Go.png.00622ba68e9eaf1dd81e8eb488f2bbb3.png" /></p>
<p>
	يُعد <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">بروتوكول HTTP</a> الخيار الأفضل غالبًا عندما يحتاج المطورون إلى إنشاء اتصال بين البرامج. تقدم <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a> (المعروفة بمكتبتها القياسية الشاملة) دعمًا قويًا لبروتوكول HTTP من خلال حزمة <code>net/http</code> الموجودة في المكتبة القياسية. لا تتيح هذه الحزمة إنشاء خوادم HTTP فحسب، بل تتيح أيضًا إجراء طلبات HTTP مثل عميل.
</p>

<p>
	سننشئ في هذه المقالة برنامجًا يتفاعل مع خادم HTTP من خلال إجراء أنواع مختلفة من الطلبات. سنبدأ بطلب <code>GET</code> باستخدام عميل HTTP الافتراضي في لغة جو، ثم سنعمل على تحسين البرنامج ليشمل طلب <code>POST</code> مع متن الطلب. أخيرًا نُخصص طلب <code>POST</code> من خلال دمج ترويسة HTTP وتحقيق آلية المهلة، للتعامل مع الحالات التي تتجاوز فيها مدة الطلب حدًا معينًا.
</p>

<h2 id="">
	المتطلبات الأولية
</h2>

<p>
	لمتابعة هذا المقال التعليمي، سنحتاج إلى:
</p>

<ul>
	<li>
		إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a> لإعداده.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
	<li>
		معرفة <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AE%D8%A7%D8%AF%D9%85-http-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2190/" rel="">بكيفية إنشاء خادم HTTP في لغة جو</a>.
	</li>
	<li>
		فهم لتنظيمات جو goroutines والقنوات channels. يمكنك الاطلاع على مقالة <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">كيفية تشغيل عدة دوال على التساير في لغة جو Go</a>.
	</li>
	<li>
		الإلمام بكيفية <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-http-%D9%81%D9%8A-nodejs-r1868/" rel="">إنشاء طلبات HTTP وإرسالها</a> (موصى به).
	</li>
</ul>

<h2 id="get">
	تقديم طلب GET
</h2>

<p>
	نناقش في هذه الفقرة كيفية تقديم طلب <code>GET</code> باستخدام حزمة <a href="https://pkg.go.dev/net/http" rel="external nofollow"><code>net/http</code></a> مثل عميل، إذ توفّر لنا هذه الحزمةطرقًا وخيارات متنوعة للتفاعل مع موارد HTTP. أحد الخيارات الشائعة هو استخدام عميل "HTTP عام" مع دوال مثل <a href="https://pkg.go.dev/net/http#Get" rel="external nofollow"><code>http.Get</code></a>، التي تسمح بإنشاء طلب <code>GET</code> سريعًا باستخدام <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> ومتن فقط. يمكننا أيضًا إنشاء <code>http.Request</code> للحصول على مزيد من التحكم وتخصيص جوانب معينة من الطلب. سنبدأ في هذا القسم بإنشاء برنامج أولي يستخدم <code>http.Get</code> لتقديم طلب HTTP، ونعدّله لاحقًا لاستخدام <code>http.Request</code> مع عميل HTTP الافتراضي.
</p>

<h3 id="httpget">
	استخدام دالة http.Get لتقديم طلب
</h3>

<p>
	نستخدم الدالة <code>http.Get</code> في الإصدار الأولي من البرنامج لإرسال طلب إلى خادم HTTP داخل البرنامج، إذ تُعد هذه الدالة مناسبة لأنها تتطلب الحد الأدنى من التهيئة، أي أنها لا تحتاج إلى الكثير من التفاصيل والإعدادات، وهذا ما يجعلها مثالية لتقديم طلب واحد مباشر. بالتالي، يكون استخدام الدالة <code>http.Get</code> هو الأسلوب الأنسب عندما نحتاج إلى تنفيذ طلب سريع لمرة واحدة فقط.
</p>

<p>
	كما هو معتاد، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه، ويمكن وضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_37" style=""><span class="pln">$ mkdir projects
$ cd projects</span></pre>

<p>
	ننشئ مجلدًا للمشروع وننتقل إليه. لنسميه مثلًا "httpclient":
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_39" style=""><span class="pln">$ mkdir httpclient
$ cd httpclient</span></pre>

<p>
	نستخدم الآن <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">محرر نانو nano أو أي محرر آخر تريده</a> لفتح ملف "main.go":
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	نضع بداخله الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_41" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"errors"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"net/http"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> serverPort </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3333</span><span class="pln"> </span><span class="com">// ضبط منفذ الخادم على 3333</span></pre>

<p>
	أضفنا في الشيفرة السابقة الحزمة <code>main</code> لضمان إمكانية تصريف البرنامج وتنفيذه. نستورد أيضًا عدة حزم لاستخدامها في البرنامج. نُعّرف ثابت <code>const</code> اسمه <code>serverPort</code> بقيمة <code>3333</code>، سيجري استخدامه مثل منفذ لخادم HTTP والعميل.
</p>

<p>
	ننشئ دالة <code>main</code> ضمن الملف "main.go" ونبدأ بإعداد خادم HTTP مثل تنظيم goroutine:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_44" style=""><span class="pln">func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// مثل تنظيم جو HTTP نبدأ تشغيل خادم</span><span class="pln">
    go func</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="com">// نُنشئ جهاز توجيه جديد</span><span class="pln">
        mux </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">NewServeMux</span><span class="pun">()</span><span class="pln">

        </span><span class="com">// معالجة مسار الجذر "/" وطباعة معلومات الطلب</span><span class="pln">
        mux</span><span class="pun">.</span><span class="typ">HandleFunc</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> func</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Server: %s /\n"</span><span class="pun">,</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Method</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">})</span><span class="pln">

        </span><span class="com">// HTTP  تهيئة خادم</span><span class="pln">
        server </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">Server</span><span class="pun">{</span><span class="pln">
            </span><span class="typ">Addr</span><span class="pun">:</span><span class="pln">    fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">":%d"</span><span class="pun">,</span><span class="pln"> serverPort</span><span class="pun">),</span><span class="pln">
            </span><span class="typ">Handler</span><span class="pun">:</span><span class="pln"> mux</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">

        </span><span class="com">//  بدء تشغيل الخادم، ومعالجة الأخطاء المحتملة</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> server</span><span class="pun">.</span><span class="typ">ListenAndServe</span><span class="pun">();</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">!</span><span class="pln">errors</span><span class="pun">.</span><span class="typ">Is</span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">ErrServerClosed</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Error running HTTP server: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}()</span><span class="pln">

    </span><span class="com">// الانتظار لمدة قصيرة للسماح للخادم بالبدء</span><span class="pln">
    time</span><span class="pun">.</span><span class="typ">Sleep</span><span class="pun">(</span><span class="lit">100</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Millisecond</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تعمل الدالة <code>main</code> بمثابة نقطة دخول للبرنامج، ونستخدم الكلمة المفتاحية <code>go</code> للإشارة إلى أن خادم HTTP سيجري تشغيله ضمن تنظيم جو goroutine. نعالج مسار الجذر <code>/</code>، ونطبع معلومات الطلب باستخدام <code>fmt.Printf</code>، ونُهيّئ خادم HTTP بالعنوان والمعالج المحددين.
</p>

<p>
	يبدأ الخادم بالاستماع إلى الطلبات باستخدام الدالة <code>ListenAndServe</code>، ويجري التعامل مع أية أخطاء محتملة؛ فإذا حدث خطأ ولم يكن هذا الخطأ هو <code>http.ErrServerClosed</code> (إغلاق طبيعي تحدثنا عنه في <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AE%D8%A7%D8%AF%D9%85-http-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2190/" rel="">المقال السابق</a>)، ستُطبع رسالة خطأ. نستخدم الدالة <code>time.Sleep</code> للسماح للخادم بوقت كافٍ لبدء التشغيل قبل تقديم الطلبات إليه.
</p>

<p>
	نجري الآن بعض التعديلات الإضافية على الدالة <code>mian</code> من خلال إعداد عنوان URL للطلب، وذلك بدمج اسم المضيف <code><a href="http://localhost" ipsnoembed="false" rel="external nofollow">http://localhost</a></code> مع قيمة <code>serverPort</code> باستخدام <code>fmt.Sprintf</code>. نستخدم بعد ذلك الدالة <code>http.Get</code> لتقديم طلب إلى عنوان URL هذا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_46" style=""><span class="pun">...</span><span class="pln">
    requestURL </span><span class="pun">:=</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"http://localhost:%d"</span><span class="pun">,</span><span class="pln"> serverPort</span><span class="pun">)</span><span class="pln">
    res</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">Get</span><span class="pun">(</span><span class="pln">requestURL</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"error making http request: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"client: got response!\n"</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"client: status code: %d\n"</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">.</span><span class="typ">StatusCode</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يرسل البرنامج طلب HTTP باستخدام عميل HTTP الافتراضي إلى عنوان URL المحدد عند استدعاء <code>http.Get</code>، ويعيد <a href="https://pkg.go.dev/net/http#Response" rel="external nofollow"><code>http.Response</code></a> في حالة نجاح الطلب أو ظهور قيمة خطأ في حالة فشل الطلب. في حال حدوث خطأ، يطبع البرنامج رسالة الخطأ ويخرج من البرنامج باستخدام <a href="https://pkg.go.dev/os#Exit" rel="external nofollow"><code>os.Exit</code></a> مع شيفرة خطأ <code>1</code>. إذا نجح الطلب، يطبع البرنامج رسالةً تشير إلى تلقي استجابة، جنبًا إلى جنب مع شيفرة حالة HTTP للاستجابة.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_48" style=""><span class="pln">server</span><span class="pun">:</span><span class="pln"> GET </span><span class="pun">/</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> got response</span><span class="pun">!</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> status code</span><span class="pun">:</span><span class="pln"> </span><span class="lit">200</span></pre>

<p>
	يشير السطر الأول من الخرج إلى أن الخادم تلقى طلب <code>GET</code> من العميل على المسار <code>/</code>. يشير السطران التاليان إلى أن العميل قد تلقى استجابة من الخادم بنجاح وأن شيفرة حالة الاستجابة كان <code>200</code>.
</p>

<p>
	على الرغم من أن الدالة <code>http.Get</code> ملائمة لإجراء طلبات HTTP سريعة مثل تلك الموضحة في هذا القسم، لكن استخدام<code>http.Request</code> يوفر نطاقًا أوسع من الخيارات لتخصيص الطلب.
</p>

<h3 id="httprequest">
	استخدام دالة http.Request لتقديم طلب
</h3>

<p>
	توفّر لنا <code>http.Request</code> مزيدًا من التحكم في الطلب أكثر من مجرد استخدام تابع HTTP (على سبيل المثال، <code>GET</code> و <code>POST</code>) وعنوان URL فقط. على الرغم من أننا لن نستخدم ميزات إضافية في الوقت الحالي، يسمح لنا استخدام <code>http.Request</code> بإضافة تخصيصات في أقسام لاحقة من هذا المقال.
</p>

<p>
	يتضمن التحديث الأولي تعديل معالج خادم HTTP لإرجاع استجابة تتضمّن بيانات <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">جسون JSON</a> وهمية باستخدام <code>fmt.Fprintf</code>. تُنشأ هذه البيانات ضمن خادم HTTP مكتمل باستخدام حزمة <code>encoding/json</code>. لمعرفة المزيد حول العمل مع بيانات جسون في لغة جو، يمكن الرجوع إلى المقال "<a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B5%D9%8A%D8%BA%D8%A9-json-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2165/" rel="">كيفية استخدام جسون JSON في لغة جو</a>". نحتاج أيضًا إلى استيراد <code>io/ioutil</code> لاستخدامه في تحديث لاحق.
</p>

<p>
	نفتح ملف "main.go" لتضمين<code>http.Request</code> كما هو موضح أدناه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_11" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    </span><span class="str">"io/ioutil"</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

</span><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    mux</span><span class="pun">.</span><span class="typ">HandleFunc</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> func</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server: %s /\n"</span><span class="pun">,</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Method</span><span class="pun">)</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Fprintf</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="pun">`{</span><span class="str">"message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello!"</span><span class="pun">}`)</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
    </span><span class="pun">...</span></pre>

<p>
	هدفنا هو استبدال استخدام <code>http.Get</code>، من خلال استخدام كل من الدالة <code>http.NewRequest</code> من الحزمة <code>net/http</code> -لإنشاء <code>http.Request</code> جديد، والذي يمثل طلب HTTP يمكن إرساله إلى الخادم - و <code>http.DefaultClient</code> ليكون عميل HTTP افتراضي يوفر لنا استخدام التابع <code>Do</code> لإرسال <code>http.Request</code> إلى الخادم واسترداد <code>http.Response</code> المقابل. يسمح هذا التغيير بمزيد من التحكم في الطلب وإجراء تعديلات عليه قبل الإرسال إلى الخادم:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_13" style=""><span class="pun">...</span><span class="pln">
    requestURL </span><span class="pun">:=</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"http://localhost:%d"</span><span class="pun">,</span><span class="pln"> serverPort</span><span class="pun">)</span><span class="pln">
    req</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">NewRequest</span><span class="pun">(</span><span class="pln">http</span><span class="pun">.</span><span class="typ">MethodGet</span><span class="pun">,</span><span class="pln"> requestURL</span><span class="pun">,</span><span class="pln"> nil</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"client: could not create request: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    res</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">DefaultClient</span><span class="pun">.</span><span class="typ">Do</span><span class="pun">(</span><span class="pln">req</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"client: error making http request: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"client: got response!\n"</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"client: status code: %d\n"</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">.</span><span class="typ">StatusCode</span><span class="pun">)</span><span class="pln">

    resBody</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> ioutil</span><span class="pun">.</span><span class="typ">ReadAll</span><span class="pun">(</span><span class="pln">res</span><span class="pun">.</span><span class="typ">Body</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"client: could not read response body: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"client: response body: %s\n"</span><span class="pun">,</span><span class="pln"> resBody</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نبدأ باستخدام الدالة <code>http.NewRequest</code> لإنشاء قيمة <code>http.Request</code>. نحدد تابع HTTP للطلب على أنه <code>GET</code> باستخدام <code>http.MethodGet</code>. نُنشئ أيضًا عنوان URL للطلب من خلال دمج اسم المضيف <code><a href="http://localhost" ipsnoembed="false" rel="external nofollow">http://localhost</a></code> مع قيمة <code>serverPort</code> باستخدام <code>fmt.Sprintf</code>. نفحص أيضًا متن الطلب إذا كان فارغًا (أي قيمة <code>nil</code>) لنتعامل أيضًا مع أي خطأ محتمل قد يحدث أثناء إنشاء الطلب.
</p>

<p>
	بخلاف <code>http.Get</code> الذي يرسل الطلب على الفور، فإن<code>http.NewRequest</code> يُجهّز الطلب فقط ولا يرسله مباشرةً، ويتيح لنا ذلك تخصيص الطلب كما نريد قبل إرساله فعليًا.
</p>

<p>
	بمجرد إعداد <code>http.Request</code>، نستخدم<code>(http.DefaultClient.Do (req</code> لإرسال الطلب إلى الخادم باستخدام عميل HTTP الافتراضي <code>http.DefaultClient</code>. يبدأ التابع <code>Do</code> الطلب ويعيد الاستجابة المستلمة من الخادم مع أي خطأ محتمل.
</p>

<p>
	بعد الحصول على الرد نطبع معلومات عنه، إذ نعرض أن العميل قد تلقى استجابة بنجاح، إلى جانب شيفرة حالة HTTP للاستجابة.
</p>

<p>
	نقرأ بعد ذلك متن استجابة HTTP باستخدام الدالة <a href="https://pkg.go.dev/io/ioutil#ReadAll" rel="external nofollow"><code>ioutil.ReadAll</code></a>. يُمثّل متن الاستجابة على أنه <code>io.ReadCloser</code>، والذي يجمع بين <code>io.Reader</code> و <code>io.Closer</code>. نستخدم <code>ioutil.ReadAll</code> لقراءة جميع البيانات من متن الاستجابة حتى النهاية أو حدوث خطأ. تُعيد الدالة البيانات بقيمة <code>byte[]</code>، والتي نطبعها مع أي خطأ مصادف.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	الخرج مشابه لما سبق مع إضافة بسيطة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_51" style=""><span class="pln">server</span><span class="pun">:</span><span class="pln"> GET </span><span class="pun">/</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> got response</span><span class="pun">!</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> status code</span><span class="pun">:</span><span class="pln"> </span><span class="lit">200</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> response body</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello!"</span><span class="pun">}</span></pre>

<p>
	يُظهر السطر الأول أن الخادم لا يزال يتلقى طلب <code>GET</code> إلى المسار <code>/</code>. يتلقى العميل استجابة من الخادم برمز الحالة <code>200</code>. يقرأ العميل متن استجابة الخادم ويطبعها أيضًا، والتي تكون في هذه الحالة <code>{" message ":" hello! "}</code>. يمكننا معالجة استجابة جسون هذه أيضًا باستخدام حزمة <code>encoding/json</code>، لكن لن ندخل في هذه التفاصيل الآن.
</p>

<p>
	طورّنا في هذا القسم برنامج باستخدام خادم HTTP وقدمنا طلبات HTTP إليه باستخدام طرق مختلفة. استخدمنا في البداية الدالة <code>http.Get</code> لتنفيذ طلب <code>GET</code> للخادم باستخدام عنوان URL الخاص بالخادم فقط. حدّثنا بعد ذلك البرنامج لاستخدام <code>http.NewRequest</code> لإنشاء قيمة <code>http.Request</code>، ثم استخدمنا التابع <code>Do</code> لعميل HTTP الافتراضي<code>http.DefaultClient</code>، لإرسال الطلب وطباعة متن الاستجابة.
</p>

<p>
	تُعد طلبات <code>GET</code> مفيدةً لاسترداد المعلومات من الخادم، لكن بروتوكول HTTP يوفر طرقًا أخرى متنوعة للاتصال بين البرامج. إحدى هذه الطرق هي طريقة <code>POST</code>، والتي تتيح لك إرسال معلومات من برنامجك إلى الخادم.
</p>

<h2 id="post">
	إرسال طلب POST
</h2>

<p>
	يُستخدم طلب <code>GET</code> في <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">واجهة برمجة تطبيقات</a> REST، فقط لاسترداد المعلومات من الخادم، لذلك لكي يُشارك البرنامج بالكامل في REST، يحتاج أيضًا إلى دعم إرسال طلبات <code>POST</code>، وهو عكس طلب <code>GET</code> تقريبًا، إذ يرسل العميل البيانات إلى الخادم داخل متن الطلب.
</p>

<p>
	سنُعدّل في هذا القسم البرنامج لإرسال طلب <code>POST</code> بدلُا من طلب <code>GET</code>، إذ سيتضمن طلب <code>POST</code> متنًا للطلب، وسنُعدّل الخادم لتوفير معلومات أكثر تفصيلًا حول الطلبات الواردة من العميل.
</p>

<p>
	نفتح ملف "main.go" ونُضمّن الحزم الإضافية التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_15" style=""><span class="pun">...</span><span class="pln">

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"bytes"</span><span class="pln">
    </span><span class="str">"errors"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"io/ioutil"</span><span class="pln">
    </span><span class="str">"net/http"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"strings"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	ثم نُعدّل دالة المعالجة لطباعة معلومات متنوعة حول الطلب الوارد، مثل قيم سلسلة الاستعلام وقيم الترويسة ومتن الطلب:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_17" style=""><span class="pun">...</span><span class="pln">
  mux</span><span class="pun">.</span><span class="typ">HandleFunc</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> func</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server: %s /\n"</span><span class="pun">,</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Method</span><span class="pun">)</span><span class="pln">
      fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server: query id: %s\n"</span><span class="pun">,</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">URL</span><span class="pun">.</span><span class="typ">Query</span><span class="pun">().</span><span class="typ">Get</span><span class="pun">(</span><span class="str">"id"</span><span class="pun">))</span><span class="pln">
      fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server: content-type: %s\n"</span><span class="pun">,</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Header</span><span class="pun">.</span><span class="typ">Get</span><span class="pun">(</span><span class="str">"content-type"</span><span class="pun">))</span><span class="pln">
      fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server: headers:\n"</span><span class="pun">)</span><span class="pln">
      </span><span class="kwd">for</span><span class="pln"> headerName</span><span class="pun">,</span><span class="pln"> headerValue </span><span class="pun">:=</span><span class="pln"> range r</span><span class="pun">.</span><span class="typ">Header</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"\t%s = %s\n"</span><span class="pun">,</span><span class="pln"> headerName</span><span class="pun">,</span><span class="pln"> strings</span><span class="pun">.</span><span class="typ">Join</span><span class="pun">(</span><span class="pln">headerValue</span><span class="pun">,</span><span class="pln"> </span><span class="str">", "</span><span class="pun">))</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      reqBody</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> ioutil</span><span class="pun">.</span><span class="typ">ReadAll</span><span class="pun">(</span><span class="pln">r</span><span class="pun">.</span><span class="typ">Body</span><span class="pun">)</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
             fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server: could not read request body: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
      fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server: request body: %s\n"</span><span class="pun">,</span><span class="pln"> reqBody</span><span class="pun">)</span><span class="pln">

      fmt</span><span class="pun">.</span><span class="typ">Fprintf</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="pun">`{</span><span class="str">"message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello!"</span><span class="pun">}`)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	أضفنا في هذا التحديث لمعالج طلب HTTP الخاص بالخادم تعليمات <code>fmt.Printf</code> لتوفير مزيد من المعلومات حول الطلب الوارد. يُستخدم التابع <code>r.URL.Query ().Get</code> لاسترداد قيمة سلسلة الاستعلام المسماة <code>id</code>. يُستخدم التابع <code>r.Header.Get</code> للحصول على قيمة الترويسة <code>content-type</code>. تتكرر حلقة <code>for</code> مع<code>r.Header</code> فوق كل ترويسة HTTP يستقبلها الخادم وتطبع اسمها وقيمتها. يمكن أن تكون هذه المعلومات ذات قيمة لأغراض استكشاف الأخطاء وإصلاحها في حالة ظهور أي مشكلات مع سلوك العميل أو الخادم. تُستخدم أيضًا الدالة <code>ioutil.ReadAll</code> لقراءة متن الطلب من <code>r.Body</code>.
</p>

<p>
	بعد تحديث دالة المعالجة للخادم، نُعدّل شيفرة الطلب في الدالة <code>main</code>، بحيث ترسل طلب <code>POST</code> مع متن الطلب:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_19" style=""><span class="pun">...</span><span class="pln">
 time</span><span class="pun">.</span><span class="typ">Sleep</span><span class="pun">(</span><span class="lit">100</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Millisecond</span><span class="pun">)</span><span class="pln">

 jsonBody </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">byte</span><span class="pun">(`{</span><span class="str">"client_message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello, server!"</span><span class="pun">}`)</span><span class="pln">
 bodyReader </span><span class="pun">:=</span><span class="pln"> bytes</span><span class="pun">.</span><span class="typ">NewReader</span><span class="pun">(</span><span class="pln">jsonBody</span><span class="pun">)</span><span class="pln">

 requestURL </span><span class="pun">:=</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"http://localhost:%d?id=1234"</span><span class="pun">,</span><span class="pln"> serverPort</span><span class="pun">)</span><span class="pln">
 req</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">NewRequest</span><span class="pun">(</span><span class="pln">http</span><span class="pun">.</span><span class="typ">MethodPost</span><span class="pun">,</span><span class="pln"> requestURL</span><span class="pun">,</span><span class="pln"> bodyReader</span><span class="pun">)</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	في هذا التعديل قُدّم متغيران جديدان: يمثل الأول <code>jsonBody</code> بيانات جسون مثل قيم من النوع <code>byte[]</code> بدلًا من سلسلة <code>string</code>. يُستخدم هذا التمثيل لأنه عند ترميز بيانات جسون باستخدام حزمة <code>encoding/json</code>، فإنها تُرجع <code>byte[]</code> بدلًا من سلسلة.
</p>

<p>
	المتغير الثاني <code>bodyReader</code>، هو <a href="https://pkg.go.dev/bytes#Reader" rel="external nofollow"><code>bytes.Reader</code></a> مُغلّف ببيانات <code>jsonBody</code>. يتطلب <code>http.Request</code> أن يكون متن الطلب من النوع <code>io.Reader</code>. نظرًا لأن قيمة <code>jsonBody</code> هي <code>byte[]</code> ولا تُحقق <code>io.Reader</code> مباشرةً، يُستخدم <code>bytes.Reader</code> لتوفير واجهة <code>io.Reader</code> الضرورية، مما يسمح باستخدام قيمة <code>jsonBody</code> على أنها متن الطلب.
</p>

<p>
	نُعدّل أيضًا المتغير <code>requestURL</code> لتضمين قيمة سلسلة الاستعلام <code>id = 1234</code>، وذلك لتوضيح كيف يمكن تضمين سلسلة استعلام في عنوان URL للطلب إلى جانب مكونات عنوان URL القياسية الأخرى. أخيرًا نُعدّل استدعاء الدالة <code>http.NewRequest</code> لاستخدام التابع <code>POST</code> مع <code>http.MethodPost</code>، وضبط متن الطلب على <code>bodyReader</code>، وهو قارئ بيانات جسون.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_21" style=""><span class="pln">$ go run main</span><span class="pun">.</span><span class="pln">go</span></pre>

<p>
	سيكون الخرج بالتأكيد أطول من السابق، بسبب طباعة معلومات إضافية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_23" style=""><span class="pln">server</span><span class="pun">:</span><span class="pln"> POST </span><span class="pun">/</span><span class="pln">
server</span><span class="pun">:</span><span class="pln"> query id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1234</span><span class="pln">
server</span><span class="pun">:</span><span class="pln"> content</span><span class="pun">-</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> 
server</span><span class="pun">:</span><span class="pln"> headers</span><span class="pun">:</span><span class="pln">
        </span><span class="typ">Accept</span><span class="pun">-</span><span class="typ">Encoding</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> gzip
        </span><span class="typ">User</span><span class="pun">-</span><span class="typ">Agent</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Go</span><span class="pun">-</span><span class="pln">http</span><span class="pun">-</span><span class="pln">client</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln">
        </span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Length</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">36</span><span class="pln">
server</span><span class="pun">:</span><span class="pln"> request body</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"client_message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello, server!"</span><span class="pun">}</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> got response</span><span class="pun">!</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> status code</span><span class="pun">:</span><span class="pln"> </span><span class="lit">200</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> response body</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello!"</span><span class="pun">}</span></pre>

<p>
	يُظهر الخرج السلوك المُحدّث للخادم والعميل بعد إجراء التغييرات لإرسال طلب <code>POST</code>؛ إذ يشير السطر الأول إلى أن الخادم قد تلقى طلب <code>POST</code> للمسار <code>/</code>؛ ويعرض السطر الثاني قيمة سلسلة الاستعلام <code>id</code>، والتي تحمل القيمة <code>1234</code> في هذا الطلب؛ بينما يعرض السطر الثالث قيمة ترويسة <code>Content-Type</code>، وهي فارغة في هذه الحالة.
</p>

<p>
	قد يختلف ترتيب الترويسات المطبوعة من <code>r.Headers</code> أثناء التكرار عليها باستخدام <code>range</code> في كل مرة نُشغّل فيها البرنامج، فبدءًا من إصدار جو 1.12، لا يمكن ضمان أن يكون الترتيب الذي يجري فيه الوصول إلى العناصر هو نفس الترتيب الذي تُدرج في العناصر في الرابط <code>map</code>، وهذا لأن لغة جو تُحقق الروابط باستخدام بنية لا تحافظ على ترتيب الإدراج، لذلك قد يختلف ترتيب طباعة الترويسات عن الترتيب الذي جرى استلامها به فعليًا في طلب HTTP. الترويسات الموضحة في المثال هي <code>Accept-Encoding</code> و <code>User-Agent</code> و <code>Content-Length</code>. قد نرى أيضًا قيمة مختلفة للترويسة <code>User-Agent</code> عما رأيناه أعلاها اعتمادًا على إصدار جو المُستخدم.
</p>

<p>
	يعرض السطر التالي متن الطلب الذي استلمه الخادم، وهو بيانات جسون التي يرسلها العميل. يمكن للخادم بعد ذلك استخدام حزمة <code>encoding/json</code> لتحليل بيانات جسون هذه التي أرسلها العميل وصياغة استجابة. تمثل الأسطر التالية خرج العميل، مما يشير إلى أنه تلقى استجابةً مع رمز الحالة <code>200</code>. يحتوي متن الاستجابة المعروض في السطر الأخير على بيانات جسون أيضًا.
</p>

<p>
	أجرينا في القسم السابق العديد من التحديثات لتحسين البرنامج. أولًا، استبدلنا طلب <code>GET</code> بطلب <code>POST</code>، مما يتيح للعميل إرسال البيانات إلى الخادم في متن الطلب، إذ أنجزنا هذا عن طريق تحديث شيفرة الطلب وتضمين متن الطلب باستخدام <code>byte[]</code>. عدّلنا أيضًا دالة معالجة الطلب الخاصة بالخادم لتوفير معلومات أكثر تفصيلًا حول الطلبات الواردة، مثل قيمة سلسلة الاستعلام <code>id</code> وترويسة <code>Content-Type</code> وجميع الترويسات المستلمة من العميل.
</p>

<p>
	تجدر الإشارة إلى أنه في الخرج المعروض، لم تكن ترويسة <code>Content-Type</code> موجودة في طلب HTTP، مما يشير عادةً إلى نوع المحتوى المُرسل في المتن. سنتعلم في القسم التالي المزيد عن كيفية تخصيص طلب HTTP، بما في ذلك ضبط ترويسة <code>Content-Type</code> لتحديد نوع البيانات المُرسلة.
</p>

<h2 id="http">
	تخصيص طلب HTTP
</h2>

<p>
	يسمح تخصيص طلب HTTP بنقل مجموعة واسعة من أنواع البيانات بين العملاء والخوادم. كان بإمكان عملاء HTTP سابقًا افتراض أن البيانات المستلمة من خادم HTTP هي <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> غالبًا، أما اليوم يمكن أن تشمل البيانات تنسيقات مختلفة، مثل جسون والموسيقى والفيديو وغيرهم. لنقل معلومات إضافية متعلقة بالبيانات المرسلة، يشتمل بروتوكول HTTP على العديد من الترويسات، أهمها الترويسة <code>Content-Type</code>، التي تُخبر الخادم (أو العميل، اعتمادًا على اتجاه البيانات) بكيفية تفسير البيانات التي يتلقاها.
</p>

<p>
	سنعمل في القسم التالي على تحسين البرنامج عن طريق ضبط الترويسة <code>Content-Type</code> في طلب HTTP للإشارة إلى أن الخادم يجب أن يتوقع بيانات جسون. ستتاح لنا الفرصة أيضًا لاستخدام عميل HTTP مخصص بدلًا من <code>http.DefaultClient</code> الافتراضي، مما يتيح لنا تخصيص إرسال الطلب وفقًا للمتطلبات المحددة.
</p>

<p>
	نفتح ملف "main.go" لإجراء التعديلات الجديدة على الدالة <code>main</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_25" style=""><span class="pun">...</span><span class="pln">

  req</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">NewRequest</span><span class="pun">(</span><span class="pln">http</span><span class="pun">.</span><span class="typ">MethodPost</span><span class="pun">,</span><span class="pln"> requestURL</span><span class="pun">,</span><span class="pln"> bodyReader</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
         fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"client: could not create request: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
         os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  req</span><span class="pun">.</span><span class="typ">Header</span><span class="pun">.</span><span class="typ">Set</span><span class="pun">(</span><span class="str">"Content-Type"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"application/json"</span><span class="pun">)</span><span class="pln">

  client </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">Client</span><span class="pun">{</span><span class="pln">
     </span><span class="typ">Timeout</span><span class="pun">:</span><span class="pln"> </span><span class="lit">30</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Second</span><span class="pun">,</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  res</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> client</span><span class="pun">.</span><span class="typ">Do</span><span class="pun">(</span><span class="pln">req</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
      fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"client: error making http request: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
      os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	بدايةً، جرى النفاذ إلى ترويسات <code>http.Request</code> باستخدام <code>req.Header</code> وضبطنا ترويسة <code>Content-Type</code> على <code>application/json</code>، إذ يُمثّل ضبط هذه الترويسة إشارةً للخادم إلى أن البيانات الموجودة في متن الطلب مُنسّقة بتنسيق جسون، وهذا يساعد الخادم في تفسير متن الطلب ومعالجته بطريقة صحيحة.
</p>

<p>
	أنشأنا أيضًا نسخة خاصة من البنية <code>http.Client</code> في المتغير <code>client</code>. يتيح لنا هذا مزيدًا من التحكم في سلوك العميل. يمكننا في هذه الحالة ضبط قيمة المهلة <code>Timeout</code> للعميل على 30 ثانية؛ وهذا يعني أنه إذا لم يجري تلقي الرد في غضون 30 ثانية، سيستسلم العميل ويتوقف عن الانتظار. هذا مهم لمنع البرنامج من إهدار الموارد بإبقاء الاتصالات مفتوحة إلى أجل غير مسمى.
</p>

<p>
	من خلال إنشاء <code>http.Client</code> خاص بنا مع مهلة محددة، فإننا تتأكد من أن البرنامج لديه حد محدد للمدة التي سينتظرها الرد، ويختلف هذا عن استخدام <code>http.DefaultClient</code> الافتراضي، والذي لا يحدد مهلة وسينتظر إلى أجل غير مسمى. لذلك يعد تحديد المهلة أمرًا بالغ الأهمية لإدارة الموارد بفعالية وتجنب مشكلات الأداء المحتملة.
</p>

<p>
	أخيرًا عدّلنا الطلب لاستخدام التابع <code>Do</code> من المتغير <code>client</code>. هذا التغيير واضح ومباشر لأننا كنا نستخدم التابع <code>Do</code> في جميع أنحاء البرنامج، سواء مع العميل الافتراضي أو العميل المخصص. الاختلاف الوحيد الآن هو أننا أنشأنا صراحةً متغيرًا من <code>http.Client</code>.
</p>

<p>
	توفر هذه التحديثات مزيدًا من التحكم والمرونة لبرنامجنا، مما يسمح بضبط ترويسات محددة وإدارة المهلات الزمنية بالطريقة المناسبة. من خلال فهم وتخصيص هذه الجوانب لطلب HTTP، يمكننا ضمان الأداء الأمثل والتواصل الموثوق مع الخادم.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	يجب أن تكون مخرجاتك مشابهة جدًا للإخراج السابق ولكن مع مزيد من المعلومات حول نوع المحتوى:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_27" style=""><span class="pln">server</span><span class="pun">:</span><span class="pln"> POST </span><span class="pun">/</span><span class="pln">
server</span><span class="pun">:</span><span class="pln"> query id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1234</span><span class="pln">
server</span><span class="pun">:</span><span class="pln"> content</span><span class="pun">-</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> application</span><span class="pun">/</span><span class="pln">json
server</span><span class="pun">:</span><span class="pln"> headers</span><span class="pun">:</span><span class="pln">
        </span><span class="typ">Accept</span><span class="pun">-</span><span class="typ">Encoding</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> gzip
        </span><span class="typ">User</span><span class="pun">-</span><span class="typ">Agent</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Go</span><span class="pun">-</span><span class="pln">http</span><span class="pun">-</span><span class="pln">client</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln">
        </span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Length</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">36</span><span class="pln">
        </span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Type</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> application</span><span class="pun">/</span><span class="pln">json
server</span><span class="pun">:</span><span class="pln"> request body</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"client_message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello, server!"</span><span class="pun">}</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> got response</span><span class="pun">!</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> status code</span><span class="pun">:</span><span class="pln"> </span><span class="lit">200</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> response body</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello!"</span><span class="pun">}</span></pre>

<p>
	يمكننا ملاحظة أن الخادم قد استجاب بقيمة الترويسة <code>Content-Type</code> المُرسلة من قِبل العميل وهي من النوع <code>application/json</code>، ونلاحظ القيمة <code>content-type</code> وهي أيضًا <code>application/json</code>.
</p>

<p>
	يسمح وجود <code>Content-Type</code> بالمرونة في التعامل مع أنواع مختلفة من واجهات برمجة التطبيقات في وقت واحد، إذ يمكّن الخادم من التمييز بين الطلبات التي تحتوي على بيانات جسون والطلبات التي تحتوي على بيانات XML. يصبح هذا التمييز مهمًا خاصةً عند التعامل مع واجهات برمجة التطبيقات التي تدعم تنسيقات بيانات مختلفة.
</p>

<p>
	لفهم كيفية عمل مهلة العميل Timeout، يمكننا تعديل الشيفرة لمحاكاة سيناريو يستغرق فيه الطلب وقتًا أطول من المهلة المحددة، وذلك لكي نتمكن من ملاحظة تأثير المهلة.
</p>

<p>
	يمكننا إضافة استدعاء دالة <code>time.Sleep</code> ضمن دالة المعالجة لخادم HTTP، إذ تؤدي هذه الدالة إلى تأخير مُصطنع في استجابة الخادم. للتأكد من أن التأخير يتجاوز قيمة المهلة التي حددناها، نجعل <code>time.Sleep</code> يستمر لمدة 35 ثانية، وبالتالي سيظهر الخادم وكأنه غير مستجيب، مما يتسبب في انتظار العميل للاستجابة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_29" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    go func</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        mux </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">NewServeMux</span><span class="pun">()</span><span class="pln">
        mux</span><span class="pun">.</span><span class="typ">HandleFunc</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> func</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="pun">...</span><span class="pln"> 
            fmt</span><span class="pun">.</span><span class="typ">Fprintf</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="pun">`{</span><span class="str">"message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello!"</span><span class="pun">}`)</span><span class="pln">
            time</span><span class="pun">.</span><span class="typ">Sleep</span><span class="pun">(</span><span class="lit">35</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Second</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">})</span><span class="pln">
        </span><span class="pun">...</span><span class="pln">
    </span><span class="pun">}()</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	عند تنفيذ البرنامج مع إضافة <code>(time.Sleep (35 * time.Second</code> في دالة المعالجة، سيتغير سلوك العميل والخادم، وذلك بسبب التأخير المصطنع الذي حدث في استجابة الخادم. نلاحظ أيضًا أن البرنامج يستغرق وقتًا أطول للخروج مقارنةً بالسابق، وذلك لأن البرنامج ينتظر انتهاء طلب HTTP قبل الإنهاء. الآن مع الانتظار الجديد الذي أضفناه، لن يكتمل طلب HTTP حتى تصل الاستجابة أو نتجاوز المهلة المحددة البالغة 30 ثانية، ونظرًا لأن الخادم متوقف مؤقتًا لمدة 35 ثانية، متجاوزًا مدة المهلة، فسيلغي العميل الطلب بعد 30 ثانية، وستجري عملية معالجة خطأ المهلة الزمنية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8762_31" style=""><span class="pln">server</span><span class="pun">:</span><span class="pln"> POST </span><span class="pun">/</span><span class="pln">
server</span><span class="pun">:</span><span class="pln"> query id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1234</span><span class="pln">
server</span><span class="pun">:</span><span class="pln"> content</span><span class="pun">-</span><span class="pln">type</span><span class="pun">:</span><span class="pln"> application</span><span class="pun">/</span><span class="pln">json
server</span><span class="pun">:</span><span class="pln"> headers</span><span class="pun">:</span><span class="pln">
        </span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Type</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> application</span><span class="pun">/</span><span class="pln">json
        </span><span class="typ">Accept</span><span class="pun">-</span><span class="typ">Encoding</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> gzip
        </span><span class="typ">User</span><span class="pun">-</span><span class="typ">Agent</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Go</span><span class="pun">-</span><span class="pln">http</span><span class="pun">-</span><span class="pln">client</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln">
        </span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Length</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">36</span><span class="pln">
server</span><span class="pun">:</span><span class="pln"> request body</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"client_message"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello, server!"</span><span class="pun">}</span><span class="pln">
client</span><span class="pun">:</span><span class="pln"> error making http request</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Post</span><span class="pln"> </span><span class="str">"http://localhost:3333?id=1234"</span><span class="pun">:</span><span class="pln"> context deadline exceeded </span><span class="pun">(</span><span class="typ">Client</span><span class="pun">.</span><span class="typ">Timeout</span><span class="pln"> exceeded </span><span class="kwd">while</span><span class="pln"> awaiting headers</span><span class="pun">)</span><span class="pln">
exit status </span><span class="lit">1</span></pre>

<p>
	نلاحظ تلقي الخادم طلب <code>POST</code> بطريقة صحيحة إلى المسار <code>/</code>، والتقط أيضًا معامل الاستعلام <code>id</code> بقيمة<code>1234</code>. حدد الخادم نوع محتوى الطلب <code>content-type</code> على أنه <code>application/json</code> بناءً على ترويسة نوع المحتوى، كما عرض الخادم الترويسات التي تلقاها. طبع الخادم أيضًا متن الطلب.
</p>

<p>
	نلاحظ حدوث خطأ <code>context deadline exceeded</code> من جانب العميل أثناء طلب HTTP كما هو متوقع. تشير رسالة الخطأ إلى أن الطلب تجاوز المهلة الزمنية البالغة 30 ثانية (الخادم تلقى الطلب وعالجه، لكنه بسبب وجود تعليمة انتظار من خلال استدعاء دالة <code>time.Sleep</code>، سيبدو وكأنه لم ينتهي من معالجته). بالتالي سيفشل استدعاء التابع <code>client.Do</code>، ويخرج البرنامج مع رمز الحالة <code>1</code> باستخدام <code>(os.Exit (1</code>.
</p>

<p>
	يسلط هذا الضوء على أهمية ضبط قيم المهلة الزمنية بطريقة مناسبة لضمان عدم تعليق الطلبات إلى أجل غير مسمى.
</p>

<p>
	أجرينا خلال هذا القسم تعديلات على البرنامج لتخصيص طلب HTTP من خلال إضافة ترويسة <code>Content-Type</code> إليه. عدّلنا البرنامج أيضًا من خلال إنشاء <code>http.Client</code> جديد مع مهلة زمنية 30 ثانية، والتي استخدمناها لاحقًا لتنفيذ طلب HTTP. فحصنا أيضًا تأثير المهلة من خلال استخدام التعليمة <code>time.Sleep</code> داخل معالج طلب HTTP. سمح لنا هذا بملاحظة أن استخدام عميل <code>http.Client</code> مع المهلات الزمنية المضبوطة بدقة أمرٌ بالغ الأهمية لمنع الطلبات من الانتظار إلى أجل غير مسمى. بالتالي اكتسبنا رؤى حول أهمية ضبط المهلات المناسبة لضمان معالجة الطلب بكفاءة ومنع المشكلات المحتملة مع الطلبات التي قد تظل خاملة.
</p>

<h2 id="-1">
	الخاتمة
</h2>

<p>
	اكتسبنا خلال هذا المقال فهمًا شاملًا للعمل مع طلبات HTTP في لغة جو باستخدام حزمة <code>net/http</code>؛ إذ بدأنا بتقديم طلب <code>GET</code> إلى خادم HTTP باستخدام كل من دالة <code>http.Get</code> و <code>http.NewRequest</code> مع عميل HTTP الافتراضي؛ ثم وسّعنا بعد ذلك معرفتنا عن طريق إجراء طلب <code>POST</code> مع متن طلب باستخدام <code>bytes.NewReader</code>.
</p>

<p>
	تعلمنا كيفية تخصيص الطلب عن طريق ضبط ترويسة <code>Content-Type</code> باستخدام التابع <code>Set</code> في حقل ترويسة <code>http.Request</code>.اكتشفنا أهمية ضبط المهلات للتحكم في مدة الطلبات من خلال إنشاء عملي <code>http.Client</code>.
</p>

<p>
	تجدر الإشارة إلى أن حزمة <code>net/http</code> توفر دوال أكثر مما غطيناه في هذا المقال، فعلى سبيل المثال يمكن استخدام دالة <code>http.Post</code> لتقديم طلبات <code>POST</code> والاستفادة من ميزات مثل إدارة ملفات تعريف الارتباط.
</p>

<p>
	من خلال إتقان هذه المفاهيم، نكون قد زودنا أنفسنا بالمهارات الأساسية للتفاعل مع خوادم HTTP وإنشاء أنواع مختلفة من الطلبات باستخدام لغة جو.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-make-http-requests-in-go" rel="external nofollow">How To Make HTTP Requests in Go</a> لصاحبه Kristin Davidson.
</p>

<h2 id="-2">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AE%D8%A7%D8%AF%D9%85-http-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2190/" rel="">كيفية إنشاء خادم HTTP في لغة Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-http-%D9%81%D9%8A-nodejs-r1868/" rel="">إنشاء طلبات HTTP وإرسالها</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B5%D9%8A%D8%BA%D8%A9-json-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2165/" rel="">كيفية استخدام صيغة JSON في لغة Go</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2191</guid><pubDate>Thu, 14 Dec 2023 16:09:02 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x625;&#x646;&#x634;&#x627;&#x621; &#x62E;&#x627;&#x62F;&#x645; HTTP &#x641;&#x64A; &#x644;&#x63A;&#x629; Go</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AE%D8%A7%D8%AF%D9%85-http-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2190/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_12/---HTTP--Go.png.1c478c65744bd6e9f3c1ed5151630a3f.png" /></p>
<p>
	يكرّس العديد من المطورين جزءًا من وقتهم لبناء خوادم تُسهّل توزيع المحتوى عبر الإنترنت. يُعد <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-r73/" rel="">بروتوكول النقل التشعبي Hypertext Transfer Protocol</a>-أو اختصارًا HTTP- من أهم الوسائل المستخدمة لتوزيع المحتوى مهما كان نوع البيانات عبر الإنترنت. تتضمّن مكتبة لغة جو القياسية وظائفًا مدمجة لإنشاء خادم HTTP لتخديّم محتوى الويب، أو إنشاء طلبات HTTP للتواصل مع هذه الخوادم.
</p>

<p>
	سنتعلم في هذا المقال كيفية إنشاء خادم HTTP باستخدام مكتبة <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a> القياسية، وكيفية توسيع وظائف الخادم لاستخراج البيانات من أجزاء مختلفة من طلب HTTP، مثل سلسلة الاستعلام والمتن Body وبيانات النموذج في الطلب. سنتعلّم أيضًا كيفية تعديل استجابة الخادم عن طريق إضافة ترويسات HTTP ورموز حالة مخصصة status codes، وبالتالي السماح للمطورين بتخصيص سلوك الخادم الخاص بهم.
</p>

<p>
	<strong>توضيح بعض المصطلحات:</strong>
</p>

<p>
	في سياق إنشاء خادم HTTP باستخدام مكتبة جو القياسية، تشير المصطلحات "سلسلة استعلام الطلب" و"المتن" و"بيانات النموذج" إلى أجزاء مختلفة من طلب HTTP.
</p>

<ol>
	<li>
		سلسلة الاستعلام: هي جزء من عنوان URL الذي يأتي بعد رمز علامة الاستفهام (<code>?</code>). يحتوي عادةً على أزواج ذات قيمة مفتاح مفصولة بعلامات <code>&amp;</code>.
	</li>
	<li>
		المتن أو النص الأساسي: يحتوي متن طلب HTTP على البيانات التي يرسلها العميل إلى الخادم، مثل JSON أو XML أو النص العادي. يُستخدم بكثرة في طلبات من نوع <code>POST</code>و <code>PUT</code> و <code>PATCH</code> لإرسال البيانات إلى الخادم.
	</li>
	<li>
		بيانات النموذج: تُرسل عادةً بيانات النموذج مثل جزء من طلب <code>POST</code> مع ضبط ترويسة "نوع المحتوى" على "application/x-www-form-urlencoded" أو "multipart/form-data". وهو يتألف من أزواج مفتاح - قيمة على غرار سلسلة الاستعلام، ولكن يُرسل في متن الطلب.
	</li>
</ol>

<h2 id="http">
	بروتوكول HTTP
</h2>

<p>
	هو بروتوكول يعمل على مستوى التطبيقات، ويستخدم لنقل مستندات الوسائط التشعبية، مثل صفحات HTML عبر الإنترنت. يعمل HTTP بمثابة أساس لاتصالات البيانات على شبكة الويب العالمية.
</p>

<p>
	يتبع HTTP نموذج خادم-العميل، إذ يرسل العميل (عادةً متصفح ويب) طلبًا إلى الخادم، ويستجيب الخادم بالمعلومات المطلوبة. تُستخدم بعض الدوال، مثل <code>GET</code> و <code>POST</code>و <code>PUT</code> و <code>DELETE</code> لتسهيل عملية الاتصال بين العميل والخادم.
</p>

<p>
	يمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-r73/" rel="">مدخل إلى HTTP</a> على أكاديمية حسوب لمزيدٍ من المعلومات حول بروتوكول HTTP.
</p>

<h2 id="">
	المتطلبات الأولية
</h2>

<p>
	لمتابعة هذا المقال التعليمي، سنحتاج إلى:
</p>

<ul>
	<li>
		إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a> لإعداده.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
	<li>
		القدرة على استخدام <a href="https://curl.se/" rel="external nofollow">أداة curl</a> لإجراء طلبات ويب.
	</li>
	<li>
		إلمام بكيفية استخدام <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">جسون JSON في لغة جو</a>.
	</li>
	<li>
		معرفة بكيفية <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">استخدام السياقات Contexts في لغة جو Go</a>.
	</li>
	<li>
		فهم لتنظيمات جو goroutines والقنوات channels. يمكنك الاطلاع على مقالة <a href="%D8%B1%D8%A7%D8%A8%D8%B7" rel="">كيفية تشغيل عدة دوال على التساير في لغة جو Go</a>.
	</li>
	<li>
		الإلمام بكيفية <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-http-%D9%81%D9%8A-nodejs-r1868/" rel="">إنشاء طلبات HTTP وإرسالها</a> (موصى به).
	</li>
</ul>

<h2 id="-1">
	إعداد المشروع
</h2>

<p>
	تتوفّر معظم وظائف HTTP التي تسمح لنا بإجراء الطلبات من خلال حزمة <a href="https://pkg.go.dev/net/http" rel="external nofollow"><code>net/http</code></a> الموجودة في المكتبة القياسية في لغة جو، بينما تتولى حزمة <a href="https://pkg.go.dev/net" rel="external nofollow"><code>net</code></a> بقية عمليات الاتصال بالشبكة. توفّر حزمة <code>net/http</code> أيضًا خادم HTTP يمكن استخدامه لمعالجة تلك الطلبات.
</p>

<p>
	سننشئ في هذا القسم برنامجًا يستخدم الدالة <a href="https://pkg.go.dev/net/http#ListenAndServe" rel="external nofollow"><code>http.ListenAndServe</code></a> لتشغيل خادم HTTP يستجيب للطلبات ذات المسارات <code>/</code> و <code>hello/</code>، ثم سنوسّع البرنامج لتشغيل خوادم HTTP متعددة في نفس البرنامج.
</p>

<p>
	كما هو معتاد، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه، ويمكن وضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_8" style=""><span class="pln">$ mkdir projects
$ cd projects</span></pre>

<p>
	الآن من داخل هذا المجلد، سنشغّل الأمر <code>mkdir</code> لإنشاء مجلد "httpserver" ثم سنستخدم <code>cd</code> للانتقال إليه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_10" style=""><span class="pln">$ mkdir httpserver
$ cd httpserver</span></pre>

<p>
	الآن، بعد أن أنشأنا مجلدًا للبرنامج وانتقلنا إليه، يمكننا البدء في تحقيق خادم HTTP.
</p>

<h2 id="-2">
	الاستماع إلى الطلبات وتقديم الردود
</h2>

<p>
	يتضمّن مخدّم HTTP في لغة جو مكونين رئيسيين: المخدّم الذي يستمع إلى الطلبات القادمة من العميل الذي يرسل طلبات HTTP (عميل HTTP أو عملاء HTTP) ومُعالج طلبات (أو أكثر) يستجيب لتلك الطلبات. سنبدأ حاليًا في استخدام الدالة <code>http.HandleFunc</code> التي تخبر المُخدّم بالدالة التي يجب استدعاؤها لمعالجة الطلب، ثم سنستخدم الدالة <code>http.ListenAndServe</code> لتشغيل الخادم وإخباره بالتحضير للاستماع إلى طلب HTTP جديد وتخديمه من خلال معالجته بدوال المعالجة Handler functions التي نُنشئها مُسبقًا.
</p>

<p>
	بما أننا الآن داخل مجلد "httpserver"، يمكننا فتح ملف "main.go" باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">محرر نانو nano أو أي محرر آخر تريده</a>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_12" style=""><span class="pln">$ nano main</span><span class="pun">.</span><span class="pln">go</span></pre>

<p>
	سننشئ داخل هذا الملف دالتين <code>getRoot</code> و <code>getHello</code> سيمثلان دوال المعالجة الخاصة بنا، ثم سننشئ دالةً رئيسية <code>main</code> لاستخدامها في إعداد معالجات الطلبات من خلال الدالة <code>http.HandleFunc</code>، وذلك بتمرير المسار <code>/</code> الخاص بالدالة <code>getRoot</code> والمسار <code>hello/</code> الخاص بالدالة <code>getHello</code>. بمجرد أن ننتهي من إعداد دوال المعالجة الخاصة بنا، يمكننا استدعاء <code>http.ListenAndServe</code> لبدء تشغيل المخدّم والاستماع للطلبات. دعنا نضيف التعليمات البرمجية التالية إلى الملف لبدء تشغيل البرنامج وإعداد المعالجات:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_14" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"errors"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"io"</span><span class="pln">
    </span><span class="str">"net/http"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func getRoot</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"got / request\n"</span><span class="pun">)</span><span class="pln">
    io</span><span class="pun">.</span><span class="typ">WriteString</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="str">"This is my website!\n"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
func getHello</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"got /hello request\n"</span><span class="pun">)</span><span class="pln">
    io</span><span class="pun">.</span><span class="typ">WriteString</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Hello, HTTP!\n"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أعددنا في البداية الحزمة الخاصة بالبرنامج <code>package</code> مع استيراد الحزم المطلوبة من خلال تعليمة <code>import</code>، كما أنشأنا الدالتين <code>getRoot</code> و <code>getHello</code>، ونلاحظ أن لهما نفس البصمة Signature، إذ تقبلان نفس الوسيطين، هما: قيمة <code>http.ResponseWriter</code> وقيمة <code>http.Request*</code>. هاتان الدالتان لهما بصمة تطابق النوع <a href="https://pkg.go.dev/net/http#HandlerFunc" rel="external nofollow"><code>http.HandlerFunc</code></a>، والذي يشيع استخدامه لتعريف دوال معالجة HTTP. عند تقديم طلب إلى الخادم، فإنه يُزوِّد هاتين القيمتين بمعلومات حول الطلب الحالي، ثم يستدعي دالة المعالج التي تتوافق مع هذه القيم.
</p>

<p>
	تُستخدم القيمة <a href="https://pkg.go.dev/net/http#ResponseWriter" rel="external nofollow"><code>http.ResponseWriter</code></a> (االمُسماة <code>w</code>) ضمن <code>http.HandlerFunc</code> للتحكّم بمعلومات الاستجابة التي يُعاد كتابتها إلى العميل الذي قدّم الطلب، مثل متن الاستجابة response body (جزء من استجابة HTTP ويحمل الحمولة الفعلية أو المعلومات التي طلبها العميل أو التي يوفرها الخادم) أو <a href="https://academy.hsoub.com/devops/servers/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D9%88%D8%A5%D8%B5%D9%84%D8%A7%D8%AD-%D8%B1%D9%85%D9%88%D8%B2-%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-http-%D8%A7%D9%84%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-r116/" rel="">رموز الحالة status codes</a>، وهي معلومات حول نتيجة الطلب والحالة الحالية للخادم، وهي جزء من بروتوكول HTTP يجري تضمينها في ترويسة الاستجابة، وتشير إلى ما إذا كان الطلب ناجحًا أو واجه خطأً أو يتطلب إجراءً إضافيًا. بعد ذلك، تُستخدم القيمة <a href="https://pkg.go.dev/net/http#Request" rel="external nofollow"><code>http.Request*</code></a> (المسماة <code>r</code>) للحصول على معلومات حول الطلب الذي جاء إلى الخادم، مثل المتن المُرسل في حالة طلب <code>POST</code> أو معلومات حول العميل الذي أجرى الطلب.
</p>

<p>
	في كل من معالجات HTTP التي أنشأناها، يمكننا استخدام الدالة <code>fmt.Printf</code> للطباعة، وذلك عندما يأتي طلب لدالة المعالجة، ثم نستخدم <code>http.ResponseWriter</code> لإرسال نص ما إلى متن الاستجابة. <code>http.ResponseWriter</code> هي واجهة في حزمة <code>http</code> تمثل الاستجابة التي سترسل مرةً أخرى إلى العميل عند تقديم طلب إلى الخادم، وهي <a href="https://pkg.go.dev/io#Writer" rel="external nofollow"><code>io.Writer</code></a> مما يعني أنها توفر إمكانية كتابة البيانات. نستخدم <code>http.ResponseWriter</code>في الشيفرة السابقة مثل وسيط<code>w</code> في دوال المعالجة (<code>getRoot</code> و <code>getHello</code>)، للسماح بالتحكم في الرد المرسل إلى العميل، وبالتالي إمكانية كتابة متن الاستجابة، أو ضبط الترويسات headers، أو تحديد رمز الحالة باستخدامها. نستخدم الدالة <a href="https://pkg.go.dev/io#WriteString" rel="external nofollow"><code>io.WriteString</code></a> لكتابة الاستجابة ضمن متن الرسالة.
</p>

<p>
	لنضيف الآن الدالة <code>main</code> إلى الشيفرة السابقة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_16" style=""><span class="pun">...</span><span class="pln">
func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    http</span><span class="pun">.</span><span class="typ">HandleFunc</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> getRoot</span><span class="pun">)</span><span class="pln">
    http</span><span class="pun">.</span><span class="typ">HandleFunc</span><span class="pun">(</span><span class="str">"/hello"</span><span class="pun">,</span><span class="pln"> getHello</span><span class="pun">)</span><span class="pln">

    err </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">ListenAndServe</span><span class="pun">(</span><span class="str">":3333"</span><span class="pun">,</span><span class="pln"> nil</span><span class="pun">)</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	تكون الدالة الرئيسية <code>main</code> في جزء الشيفرة السابق، مسؤولةً عن إعداد خادم HTTP وتحديد معالجات الطلب. هناك استدعاءان إلى الدالة <code>http.HandleFunc</code>، بحيث يربط كل استدعاء لها دالة معالجة من أجل مسار طلب محدد ضمن مجمّع الخادم الافتراضي default server multiplexer. يتطلب الأمر وسيطين: الأول هو مسار الطلب (في هذه الحالة <code>/</code> ثم <code>hello/</code>) ودالة المعالجة (<code>getRoot</code> ثم <code>getHello</code> على التوالي). الدالتان <code>getRoot</code> و <code>getHello</code> هما دوال المعالجة التي سيجري استدعاؤها عند تقديم طلب إلى المسارات المقابلة (<code>/</code> و <code>hello/</code>). للدالتين توقيع مماثل للدالة <code>http.HandlerFunc</code> التي تقبل<code>http.ResponseWriter</code> و <code>http.Request*</code> مثل وسطاء.
</p>

<p>
	تُستخدم الدالة <code>http.ListenAndServe</code> لبدء تشغيل خادم HTTP للاستماع إلى الطلبات الواردة. يتطلب الأمر وسيطين، هما: عنوان الشبكة للاستماع عليه (في هذه الحالة <code>3333:</code>) ومعالج اختياري <code>http.Handler</code>. يحدد <code>3333:</code> في برنامجنا أن الخادم يجب أن يستمع إلى المنفذ <code>3333</code>، ونظرًا لعدم تحديد عنوان IP، سيستمع إلى جميع عناوين IP المرتبطة بالحاسب. يمثّل منفذ الشبكة network port -مثل "3333"- طريقةً تمكّن جهاز الحاسوب من أن يكون لديه عدة برامج تتواصل مع بعضها بنفس الوقت، بحيث يستخدم كل برنامج منفذه المخصص، وبالتالي عند اتصال العميل مع منفذ معين يعلم الحاسوب إلى أي منفذ سيُرسل. إذا كنت تريد قصر الاتصالات على المضيف المحلي <code>localhost</code> فقط، فيمكنك استخدام <code>‎127.0.0.1:3333</code>.
</p>

<p>
	تمرّر الدالة <code>http.ListenAndServe</code> قيمة <code>nil</code> من أجل المعامل <code>http.Handler</code>، وهذا يخبر دالة <code>ListenAndServe</code> بأنك تريد استخدام مجمّع الخادم الافتراضي وليس أي مجمّع ضبطه سابقًا.
</p>

<p>
	الدالة <code>ListenAndServe</code> هي استدعاء "حظر"، مما يعني أنها ستمنع تنفيذ التعليمات البرمجية الأخرى حتى يُغلق الخادم. تُعيد هذه الدالة خطًأ إذا فشلت عملية بدء تشغيل الخادم أو إذا حدث خطأ أثناء التشغيل. من المهم تضمين عملية معالجة الأخطاء بعد استدعاء <code>http.ListenAndServe</code>، وذلك لأن الدالة يمكن أن تفشل، بالتالي من الضروري التعامل مع الأخطاء المحتملة. يُستخدم المتغير <code>err</code> لالتقاط أي خطأ يُعاد بواسطة <code>ListenAndServe</code>. يمكننا إضافة شيفرة معالجة الأخطاء بعد هذا السطر لمعالجة أي أخطاء محتملة قد تحدث أثناء بدء تشغيل الخادم أو تشغيله كما سنرى.
</p>

<p>
	لنضيف الآن شيفرة معالجة الأخطاء إلى دالة <code>ListenAndServe</code> ضمن دالة <code>main</code> الرئيسية كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_18" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    err </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">ListenAndServe</span><span class="pun">(</span><span class="str">":3333"</span><span class="pun">,</span><span class="pln"> nil</span><span class="pun">)</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">Is</span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">ErrServerClosed</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server closed\n"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"error starting server: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">&lt;^&gt;}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بعد استدعاء <code>http.ListenAndServe</code>، يجري تخزين الخطأ المُعاد في المتغير <code>err</code>. تجري عملية فحص الخطأ الأولى باستخدام <code>(errors.Is(err, http.ErrServerClosed</code>، إذ يجري التحقق ما إذا كان الخطأ هو <a href="https://pkg.go.dev/net/http#pkg-variables" rel="external nofollow"><code>http.ErrServerClosed</code></a>، والذي يُعاد عندما يُغلق الخادم أو يجري إيقاف تشغيله. يعني ظهور هذا الخطأ أن الخادم قد أُغلق بطريقة متوقعة، بالتالي طباعة الرسالة "server closed".
</p>

<p>
	يُنجز فحص الخطأ الثاني باستخدام <code>Err != nil</code>. يتحقق هذا الشرط مما إذا كان الخطأ ليس <code>nil</code>، مما يشير إلى حدوث خطأ أثناء بدء تشغيل الخادم أو تشغيله. إذا تحقق الشرط، فهذا يعني حدوث خطأ غير متوقع، وبالتالي طباعة رسالة خطأ مع تفاصيل الخطأ باستخدام <code>fmt.Printf</code>، كما يجري إنهاء البرنامج مع شيفرة الخطأ <code>1</code> باستخدام <code>(os.Exit (1</code> للإشارة إلى حدوث خطأ.
</p>

<p>
	تجدر الإشارة إلى أن أحد الأخطاء الشائعة التي قد تواجهها هو أن العنوان قيد الاستخدام فعلًا <code>address already in use</code>. يحدث هذا عندما تكون الدالة <code>ListenAndServe</code> غير قادرة على الاستماع إلى العنوان أو المنفذ المحدد للخادم لأنه قيد الاستخدام فعلًا من قبل برنامج آخر، أي إذا كان المنفذ شائع الاستخدام أو إذا كان برنامج آخر يستخدم نفس العنوان أو المنفذ.
</p>

<p>
	<strong>ملاحظة:</strong> عند ظهور هذا الخطأ، يجب التأكد من إيقاف أي مثيلات سابقة للبرنامج ثم محاولة تشغيله مرة أخرى. إذا استمر الخطأ يجب علينا محاولة استخدام رقم منفذ مختلف لتجنب التعارضات، فمن المحتمل أن برنامجًا آخر يستخدم المنفذ المحدد. يمكن اختيار رقم منفذ مختلف (أعلى من <code>1024</code> وأقل من <code>65535</code>) وتعديل الشيفرة وفقًا لذلك.
</p>

<p>
	على عكس برامج لغة جو الأخرى؛ لن يُنهى البرنامج فورًا من تلقاء نفسه. لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_20" style=""><span class="pln">$ go run main</span><span class="pun">.</span><span class="pln">go</span></pre>

<p>
	بما أن البرنامج يبقى قيد التشغيل في الطرفية الحالية، فسنحتاج إلى فتح نافذة أخرى للطرفية لكي نتفاعل مع الخادم (ستظهر الأوامر بلون مختلف عن الطرفية الأولى).
</p>

<p>
	ضمن الطرفية الثانية التي فتحناها، نستخدم الأمر <code>curl</code> لتقديم طلب HTTP إلى خادم HTTP الخاص بنا. <code>curl</code> هي أداة مُثبّتة افتراضيًا على العديد من الأنظمة التي يمكنها تقديم طلبات للخوادم من أنواع مختلفة، وسنستخدمها في هذا المقال لإجراء طلبات HTTP. يستمع الخادم إلى الاتصالات على المنفذ <code>3333</code> لجهاز الحاسوب، لذا يجب تقديم الطلب للمضيف المحلي على نفس المنفذ:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_22" style=""><span class="pln">$ curl http</span><span class="pun">:</span><span class="com">//localhost:3333</span></pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">This is my website!
</pre>

<p>
	العبارة <code>This is my website</code> ناتجة عن الدالة <code>getRoot</code>، وذلك لأنك استخدمت المسار <code>/</code> على خادم HTTP. دعونا الآن نستخدم المسار <code>hello/</code> على نفس المضيف والمنفذ، وذلك بإضافة المسار إلى نهاية أمر <code>curl</code>:
</p>

<pre class="ipsCode">$ curl http://localhost:3333/hello
</pre>

<p>
	ليكون الخرج هذه المرة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_24" style=""><span class="typ">Hello</span><span class="pun">,</span><span class="pln"> HTTP</span><span class="pun">!</span></pre>

<p>
	نلاحظ أن الخرج السابق كان نتيجةً لاستدعاء الدالة <code>getHello</code>. إذا عدنا إلى المحطة الطرفية الأولى التي يعمل عليها خادم HTTP، نلاحظ وجود سطرين أنتجهما الخادم الخاص بنا: واحد للطلب <code>/</code> والآخر للطلب <code>hello/</code>.
</p>

<pre class="ipsCode">got / request
got /hello request
</pre>

<p>
	سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "Ctrl+C".
</p>

<p>
	لقد أنشأنا برنامجًا يمثل خادم HTTP، لكنه يستخدم مجمّع خادم افتراضي وخادم HTTP افتراضي أيضًا. يمكن أن يؤدي الاعتماد على القيم الافتراضية أو العامة Global إلى حدوث أخطاء يصعب تكرارها أو يصعب إنتاجها باستمرار Reproduce consistently، إذ يمكن أن تُعدِّل أجزاءً مختلفة من البرنامج هذه القيم العامة في أوقات مختلفة، مما يؤدي إلى حالة غير صحيحة أو غير متسقة. يصبح تحديد مثل هذه الأخطاء أمرًا صعبًا لأنها قد تحدث فقط في ظل ظروف معينة أو إذا جرى استدعاء وظائف معينة بترتيب معين.
</p>

<h2 id="-3">
	معالجات طلبات التجميع
</h2>

<p>
	عند بدء تشغيل خادم HTTP سابقًا؛ استخدمنا مجمّع خادم افتراضي عن طريق تمرير قيمة صفرية (أي <code>nil</code>) للمعامل <code>http.Handler</code> في دالة <code>ListenAndServe</code>. رأينا أيضًا أن هناك بعض المشاكل التي قد تطرأ في حالة استخدام المعاملات الافتراضية. بما أن <code>http.Handler</code> هو <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-interfaces-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1999/" rel="">واجهة interface</a>، فهذا يعني أنه لدينا الخيار لإنشاء بنية مخصصة تحقق هذه الواجهة. هناك طبعًا حالات نحتاج فيها فقط إلى <code>http.Handler</code> الافتراضي الذي يستدعي دالة واحدة لمسار طلب معين، أي كما في حالة مجمّع الخادم الافتراضي، لكن هناك حالات قد تتطلّب أكثر من ذلك؛ هذا ما نناقشه تاليًا.
</p>

<p>
	لنعدّل البرنامج الآن لاستخدام <code>http.ServeMux</code>، إنها أداة تعمل مثل مجمّع للخادم، وهي مسؤولة عن التوجيه والتعامل مع طلبات HTTP الواردة بناءً على مساراتها. تحقّق<code>http.ServeMux</code> الواجهة <code>http.Handler</code> المؤمنة من قِبل حزمة <code>net/http</code>، مما يعني قدرتها على التعامل مع طلبات HTTP وإنشاء الاستجابات المناسبة. بالتالي مزيد من التحكم في التوجيه والتعامل مع مسارات الطلبات المختلفة، وإتاحة الفرصة لتحديد دوال أو معالجات محددة لكل مسار. بالتالي نكون قد اتبعنا نهجًا أكثر تنظيمًا وقابلية للتخصيص للتعامل مع طلبات HTTP في البرنامج.
</p>

<p>
	يمكن تهيئة بنية <code>http.ServeMux</code> بطريقة مشابهة للمجمّع الافتراضي، لذا لن نحتاج إلى إجراء العديد من التغييرات على البرنامج لبدء استخدام مجمّع الخادم المخصّص بدلًا من الافتراضي. لتحديث البرنامج وفقًا لذلك، نفتح ملف "main.go" مرةً أخرى ونجري التعديلات اللازمة لاستخدام <code>http.ServeMux</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_26" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    mux </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">NewServeMux</span><span class="pun">()</span><span class="pln">
    mux</span><span class="pun">.</span><span class="typ">HandleFunc</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">,</span><span class="pln"> getRoot</span><span class="pun">)</span><span class="pln">
    mux</span><span class="pun">.</span><span class="typ">HandleFunc</span><span class="pun">(</span><span class="str">"/hello"</span><span class="pun">,</span><span class="pln"> getHello</span><span class="pun">)</span><span class="pln">

    err </span><span class="pun">:=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">ListenAndServe</span><span class="pun">(</span><span class="str">":3333"</span><span class="pun">,</span><span class="pln"> mux</span><span class="pun">)</span><span class="pln">

    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أنشأنا <code>http.ServeMux</code> جديد باستخدام باني <code>http.NewServeMux</code> وأسندناه إلى المتغير <code>mux</code>، ثم عدّلنا استدعاءات <code>http.HandleFunc</code> لاستخدام المتغير <code>mux</code> بدلًا من استدعاء حزمة <code>http</code> مباشرة. أخيرًا، عدّلنا استدعاء <code>http.ListenAndServe</code> لتزويده بالمعالج <code>http.Handler</code> الذي أنشأنه <code>mux</code> بدلًا من <code>nil</code>.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_28" style=""><span class="pln">$ go run main</span><span class="pun">.</span><span class="pln">go</span></pre>

<p>
	سيستمر البرنامج في العمل كما في المرة السابقة، لذا نحتاج إلى تشغيل أوامر في طرفية أخرى للتفاعل مع الخادم. أولًا، نستخدم <code>curl</code> لطلب المسار <code>/</code>:
</p>

<pre class="ipsCode">$ curl http://localhost:3333
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">This is my website!
</pre>

<p>
	الخرج كما هو في المرة السابقة. دعونا الآن نستخدم المسار <code>hello/</code> على نفس المضيف والمنفذ، وذلك بإضافة المسار إلى نهاية أمر <code>curl</code>:
</p>

<pre class="ipsCode">$ curl http://localhost:3333/hello
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">Hello, HTTP!
</pre>

<p>
	نلاحظ أن الخرج السابق كان كما المرة السابقة أيضًا.
</p>

<p>
	إذا عدنا الآن إلى الطرفية الأولى، فسنرى مخرجات كل من <code>/</code> و <code>hello /</code> كما كان من قبل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_30" style=""><span class="pln">got </span><span class="pun">/</span><span class="pln"> request
got </span><span class="pun">/</span><span class="pln">hello request</span></pre>

<p>
	نلاحظ أن التعديلات التي أجريناها لا تغيّر في وظيفة البرنامج، وإنما فقط بدلنا مجمّع الخادم الافتراضي بآخر مخصص.
</p>

<p>
	سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "Ctrl+C".
</p>

<h2 id="-4">
	تشغيل عدة خوادم في وقت واحد
</h2>

<p>
	سنجري خلال هذا القسم تعديلات على البرنامج لاستخدام عدة خوادم HTTP في نفس الوقت باستخدام <a href="https://pkg.go.dev/net/http#Server" rel="external nofollow"><code>http.Server</code></a> التي توفرها حزمة <code>net/http</code>. يمكننا بالحالة الافتراضية تشغيل خادم HTTP واحد فقط في البرنامج، لكن قد تكون هناك سيناريوهات نحتاج فيها إلى تخصيص سلوك الخادم أو تشغيل عدة خوادم في نفس الوقت، مثل استضافة موقع ويب عام Public website وموقع ويب إداري خاص Private admin website داخل نفس البرنامج. لأجل ذلك سنعدّل ملف "main.go" لإنشاء نسخ متعددة من <code>http.Server</code> لأغراض مختلفة، إذ سيكون لكل خادم التهيئة والإعدادات الخاصة به. يتيح لك هذا مزيدًا من التحكم في سلوك الخادم ويمكّنك من التعامل مع وظائف خادم متعددة داخل نفس البرنامج.
</p>

<p>
	سنعدّل أيضًا دوال المعالجة لتحقيق إمكانية الوصول إلى <a href="https://pkg.go.dev/context#Context" rel="external nofollow"><code>Context.Context</code></a> المرتبط مع <code>http.Request*</code>؛ أي إمكانية الوصول إلى سياق الطلبات الواردة، إذ يمكننا من خلال هذا السياق تمييز الخادم الذي يأتي الطلب منه. إذًا من خلال تخزين هذه المعلومات في متغير السياق، يصبح بمقدورنا استخدامها داخل دالة المعالجة لتنفيذ إجراءات محددة أو تخصيص الاستجابة بناءً على الخادم الذي أنشأ الطلب.
</p>

<p>
	لنفتح ملف "main.go" ونعدّله بالتالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_32" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> 
</span><span class="com">// os لاحظ أننا حذفنا استيراد </span><span class="pln">
    </span><span class="str">"context"</span><span class="pln">
    </span><span class="str">"errors"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"io"</span><span class="pln">
    </span><span class="str">"net"</span><span class="pln">
    </span><span class="str">"net/http"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> keyServerAddr </span><span class="pun">=</span><span class="pln"> </span><span class="str">"serverAddr"</span><span class="pln">

func getRoot</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ctx </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">()</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%s: got / request\n"</span><span class="pun">,</span><span class="pln"> ctx</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">(</span><span class="pln">keyServerAddr</span><span class="pun">))</span><span class="pln">
    io</span><span class="pun">.</span><span class="typ">WriteString</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="str">"This is my website!\n"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
func getHello</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ctx </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">()</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%s: got /hello request\n"</span><span class="pun">,</span><span class="pln"> ctx</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">(</span><span class="pln">keyServerAddr</span><span class="pun">))</span><span class="pln">
    io</span><span class="pun">.</span><span class="typ">WriteString</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Hello, HTTP!\n"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عدّلنا بيان الاستيراد <code>import</code> لتضمين الحزم المطلوبة، ثم أنشأنا سلسلة نصية ثابتة <code>const string</code> تسمى <code>keyServerAddr</code> لتعمل مثل مفتاح لقيمة عنوان خادم HTTP في سياق <code>http.Request</code>، ثم عدّلنا دوال المعالجة <code>getRoot</code> و <code>getHello</code> للوصول إلى قيمة <code>Context.Context</code> التابعة إلى <code>http.Request</code>. بعد الحصول على القيمة يمكننا تضمين عنوان خادم HTTP في خرج <code>fmt.Printf</code> حتى نتمكن من معرفة أي من الخادمين تعامل مع طلب HTTP.
</p>

<p>
	لنعدّل الآن الدالة <code>main</code> بإضافة قيمتي <code>http.Server</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_34" style=""><span class="pun">...</span><span class="pln">
func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    mux</span><span class="pun">.</span><span class="typ">HandleFunc</span><span class="pun">(</span><span class="str">"/hello"</span><span class="pun">,</span><span class="pln"> getHello</span><span class="pun">)</span><span class="pln">

    ctx</span><span class="pun">,</span><span class="pln"> cancelCtx </span><span class="pun">:=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">WithCancel</span><span class="pun">(</span><span class="pln">context</span><span class="pun">.</span><span class="typ">Background</span><span class="pun">())</span><span class="pln">
    serverOne </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Server</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Addr</span><span class="pun">:</span><span class="pln">    </span><span class="str">":3333"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Handler</span><span class="pun">:</span><span class="pln"> mux</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">BaseContext</span><span class="pun">:</span><span class="pln"> func</span><span class="pun">(</span><span class="pln">l net</span><span class="pun">.</span><span class="typ">Listener</span><span class="pun">)</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">Context</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            ctx </span><span class="pun">=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">WithValue</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">,</span><span class="pln"> keyServerAddr</span><span class="pun">,</span><span class="pln"> l</span><span class="pun">.</span><span class="typ">Addr</span><span class="pun">().</span><span class="typ">String</span><span class="pun">())</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> ctx
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span></pre>

<p>
	التغيير الأول الذي أجريناه هو إنشاء قيمة <code>Context.Context</code> جديدة مع دالة متاحة هي الدالة `cancelCtx'. هذا يسمح لنا بإلغاء السياق عند الحاجة.
</p>

<p>
	عرّفنا أيضًا نسخةً تسمى <code>serverOne</code> من <code>http.Server</code>، وهو مشابه لخادم HTTP الذي كنا نستخدمه، ولكن بدلًا من تمرير العنوان والمعالج مباشرةً إلى <code>http.ListenAndServe</code>، يمكن إسنادهما مثل قيم <code>Addr</code> و <code>Handler</code> في بنية <code>http.Server</code>.
</p>

<p>
	تعديل آخر كان بإضافة دالة <code>BaseContext</code>، وهي دالة تسمح بتعديل أجزاء من <code>Context.Context</code> الذي جرى تمريره إلى دوال المعالجة عند استدعاء التابع <code>Context</code> من <code>http.Request*</code>. أضفنا في الشيفرة السابقة عنوان الاستماع الخاص بالخادم إلى السياق باستخدام المفتاح <code>serverAddr</code>، وهذا يعني أن العنوان الذي يستمع فيه الخادم للطلبات الواردة مرتبط بمفتاح <code>serverAddr</code> في السياق. عندما نستدعي الدالة <code>BaseContext</code>، فإنها تتلقى <code>net.Listener</code>، والذي يمثل مستمع الشبكة الأساسي الذي يستخدمه الخادم.
</p>

<p>
	بالنسبة لبرنامجنا: من خلال استدعاء <code>()l.Addr(). String</code>، نكون قد حصلنا على عنوان شبكة المستمع. يتضمن هذا عادةً عنوان IP ورقم المنفذ الذي يستمع الخادم عليه. بعد ذلك نضيف العنوان الذي حصلنا عليه إلى السياق باستخدام الدالة <code>Context.WithValue</code>، والتي تتيح لنا تخزين أزواج المفتاح والقيمة في السياق. في هذه الحالة يكون المفتاح هو <code>serverAddr</code>، والقيمة المرتبطة به هي عنوان الاستماع الخاص بالخادم.
</p>

<p>
	لنعرّف الآن الخادم الثاني <code>serverTwo</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_36" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    serverOne </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Server</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="pun">...</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    serverTwo </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Server</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Addr</span><span class="pun">:</span><span class="pln">    </span><span class="str">":4444"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Handler</span><span class="pun">:</span><span class="pln"> mux</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">BaseContext</span><span class="pun">:</span><span class="pln"> func</span><span class="pun">(</span><span class="pln">l net</span><span class="pun">.</span><span class="typ">Listener</span><span class="pun">)</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">Context</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            ctx </span><span class="pun">=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">WithValue</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">,</span><span class="pln"> keyServerAddr</span><span class="pun">,</span><span class="pln"> l</span><span class="pun">.</span><span class="typ">Addr</span><span class="pun">().</span><span class="typ">String</span><span class="pun">())</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> ctx
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span></pre>

<p>
	نعرّف الخادم الثاني بنفس طريقة تعريف الأول، لكن نضع حقل العنوان على <code>4444:</code> بدلًا من <code>3333:</code>، وذلك لكي يستمع الخادم الأول على للاتصالات على المنفذ 3333 ويستمع الخادم الثاني على المنفذ 4444.
</p>

<p>
	لنعدّل الآن البرنامج من أجل استخدام الخادم الأول <code>serverOne</code> وجعله يعمل مثل تنظيم جو goroutine:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_38" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    serverTwo </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Server</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="pun">...</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    go func</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        err </span><span class="pun">:=</span><span class="pln"> serverOne</span><span class="pun">.</span><span class="typ">ListenAndServe</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">Is</span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">ErrServerClosed</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server one closed\n"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"error listening for server one: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        cancelCtx</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">}()</span></pre>

<p>
	نستخدم تنظيم goroutine لبدء تشغيل الخادم الأول <code>serverOne</code> باستخدام الدالة <code>ListenAndServe</code> كما فعلنا سابقًا، لكن هذه المرة دون أي معاملات لأن قيم <code>http.Server</code> جرى تهيئتها مسبقًا باستخدام العنوان والمعالج المطلوبين.
</p>

<p>
	تجري عملية معالجة الأخطاء كما في السابق؛ فإذا كان الخادم مغلقًا، فإنه يطبع رسالة تشير إلى أن "الخادم الأول" أصبح مغلقًا؛ وإذا كان هناك خطأ آخر بخلاف <code>http.ErrServerClosed</code>، فستظهر رسالة خطأ.
</p>

<p>
	تُستدعى أخيرًا الدالة <code>CancelCtx</code> لإلغاء السياق الذي قدمناه لمُعالجات HTTP ودوال <code>BaseContext</code> لكلا الخادمين. بالتالي إنهاء أي عمليات جارية تعتمد عليه بأمان. هذا يضمن أنه إذا انتهى الخادم لأي سبب، فسيُنهى أيضًا السياق المرتبط به.
</p>

<p>
	لنعدّل الآن البرنامج لاستخدام الخادم الثاني، بحيث يعمل مثل تنظيم جو :
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_40" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    go func</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="pun">...</span><span class="pln">
    </span><span class="pun">}()</span><span class="pln">
    go func</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        err </span><span class="pun">:=</span><span class="pln"> serverTwo</span><span class="pun">.</span><span class="typ">ListenAndServe</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">Is</span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">ErrServerClosed</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server two closed\n"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"error listening for server two: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        cancelCtx</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">}()</span><span class="pln">

    </span><span class="pun">&lt;-</span><span class="pln">ctx</span><span class="pun">.</span><span class="typ">Done</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تنظيم جو هنا هو نفسه الأول من الناحية الوظيفية، فهو يُشغّل <code>serverTwo</code> فقط بدلًا من <code>serverOne</code>. بعد بدء تشغيل <code>serverTwo</code>، يصل التنظيم الخاص بالدالة <code>main</code> إلى السطر <code>ctx.Done</code>. ينتظر هذا السطر إشارةً من قناة <code>ctx.Done</code>، والتي تُعاد عند إلغاء السياق أو الانتهاء منه. من خلال هذا الانتظار نكون قد منعنا تنظيم الدالة الرئيسية من الخروج من الدالة <code>main</code> حتى تنتهي تنظيمات جو لكلا الخادمين من العمل. إذًا، الغرض من هذا الأسلوب هو التأكد من استمرار تشغيل البرنامج حتى انتهاء كلا الخادمين أو مواجهة خطأ.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_42" style=""><span class="pln">$ go run main</span><span class="pun">.</span><span class="pln">go</span></pre>

<p>
	نُشغّل الآن أوامر <code>curl</code> (في الطرفية الثانية) لطلب المسار <code>/</code> والمسار<code>hello/</code> من الخادم الذي يستمع على 3333، مثل الطلبات السابقة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_44" style=""><span class="pln">$ curl http</span><span class="pun">:</span><span class="com">//localhost:3333</span><span class="pln">
$ curl http</span><span class="pun">:</span><span class="com">//localhost:3333/hello</span></pre>

<p>
	سيكون الخرج كما في المرات السابقة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_46" style=""><span class="typ">This</span><span class="pln"> is my website</span><span class="pun">!</span><span class="pln">
</span><span class="typ">Hello</span><span class="pun">,</span><span class="pln"> HTTP</span><span class="pun">!</span></pre>

<p>
	لنشغّل الأوامر نفسها مرة أخرى، ولكن هذه المرة مع المنفذ 4444 الذي يتوافق مع <code>serverTwo</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_48" style=""><span class="pln">$ curl http</span><span class="pun">:</span><span class="com">//localhost:4444</span><span class="pln">
$ curl http</span><span class="pun">:</span><span class="com">//localhost:4444/hello</span></pre>

<p>
	سيبقى الخرج نفسه كما في المرة السابقة أيضًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_50" style=""><span class="typ">This</span><span class="pln"> is my website</span><span class="pun">!</span><span class="pln">
</span><span class="typ">Hello</span><span class="pun">,</span><span class="pln"> HTTP</span><span class="pun">!</span></pre>

<p>
	لنلقي نظرةً الآن على الطرفية الأولى حيث يعمل الخادم:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_52" style=""><span class="pun">[::]:</span><span class="lit">3333</span><span class="pun">:</span><span class="pln"> got </span><span class="pun">/</span><span class="pln"> request
</span><span class="pun">[::]:</span><span class="lit">3333</span><span class="pun">:</span><span class="pln"> got </span><span class="pun">/</span><span class="pln">hello request
</span><span class="pun">[::]:</span><span class="lit">4444</span><span class="pun">:</span><span class="pln"> got </span><span class="pun">/</span><span class="pln"> request
</span><span class="pun">[::]:</span><span class="lit">4444</span><span class="pun">:</span><span class="pln"> got </span><span class="pun">/</span><span class="pln">hello request</span></pre>

<p>
	الخرج مشابه لما رأيناه من قبل، لكنه يعرض هذه المرة الخادم الذي استجاب للطلب. يظهِر الطلبان الأولان أنهما جاءا من الخادم الذي يستمع على المنفذ 3333 أي <code>serverOne</code>، والطلبان الثانيان جاءا من الخادم الذي يستمع على المنفذ 4444 أي <code>serverTwo</code>. إنها القيم التي جرى استردادها من قيمة <code>serverAddr</code> في <code>BaseContext</code>. قد يكون الخرج مختلفًا قليلًا عن الخرج أعلاه اعتمادًا على ما إذا كان جهاز الحاسب المُستخدم يستخدم <a href="https://academy.hsoub.com/devops/networking/%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D9%85%D9%86-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-ip-r504/" rel="">IPv6</a> أم لا. إذا كان الحاسب يستخدم IPv6 فسيكون الخرج كما أعلاه، وإلا سنرى <code>0.0.0.0</code> بدلًا من <code>[::]</code>. السبب في ذلك هو أن جهاز الحاسب سيتواصل مع نفسه عبر IPv6، و <code>[::]</code> هو تدوين IPv6 والذي يُقابل <code>0.0.0.0</code> في IPv4. بعد الانتهاء نضغط "CONTROL+C" لإنهاء الخادم.
</p>

<p>
	تعرفنا في هذا القسم على عملية إنشاء برنامج خادم HTTP وتحسينه تدريجيًا للتعامل مع السيناريوهات المختلفة. بدأنا بتهيئة الخادم الافتراضي باستخدام <code>http.HandleFunc</code> و <code>http.ListenAndServe</code>. بعد ذلك عدّلناه لاستخدام <code>http.ServeMux</code> مثل مجمّع خادم، مما يسمح لنا بالتعامل مع معالجات طلبات متعددة لمسارات مختلفة. وسّعنا البرنامج أيضًا لاستخدام <code>http.Server</code>، مما يمنحنا مزيدًا من التحكم في تهيئة الخادم ويسمح لنا بتشغيل خوادم HTTP متعددة في نفس الوقت داخل نفس البرنامج.
</p>

<p>
	على الرغم من أن البرنامج يعمل، إلا أنه يفتقر إلى التفاعل الذي يتجاوز تحديد المسارات المختلفة. لمعالجة هذا القيد، يركز القسم التالي على دمج قيم سلسلة الاستعلام query string values في وظائف الخادم، مما يتيح للمستخدمين التفاعل مع الخادم باستخدام معاملات الاستعلام.
</p>

<h2 id="-5">
	فحص سلسلة الاستعلام الخاصة بالطلب
</h2>

<p>
	ينصب التركيز في هذا القسم على دمج قيم سلسلة الاستعلام في وظائف خادم HTTP. سلسلة الاستعلام هي مجموعة من القيم الملحقة بنهاية عنوان URL، تبدأ بالمحرف <code>?</code> وتستخدم المُحدّد <code>&amp;</code> للقيم الإضافية. توفر قيم سلسلة الاستعلام وسيلة للمستخدمين للتأثير على الاستجابة التي يتلقونها من خادم HTTP عن طريق تخصيص النتائج أو تصفيتها، فمثلًا قد يستخدم أحد الخوادم قيمة <code>results</code> للسماح للمستخدم بتحديد شيء مثل <code>results = 10</code> ليقول إنه يرغب في رؤية 10 عناصر في قائمة النتائج.
</p>

<p>
	لتحقيق هذه الميزة نحتاج إلى تحديث دالة المعالجة <code>getRoot</code> في ملف "main.go" للوصول إلى قيم سلسلة الاستعلام <code>http.Request*</code> باستخدام التابع <code>r.URL.Query</code>، ثم طباعتها بعد ذلك على الخرج. نزيل أيضًا <code>serverTwo</code> وكل الشيفرات المرتبطة به من الدالة <code>main</code>، لأنها لم تعد مطلوبة للتغييرات القادمة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_54" style=""><span class="pun">...</span><span class="pln">

func getRoot</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ctx </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">()</span><span class="pln">

    hasFirst </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">URL</span><span class="pun">.</span><span class="typ">Query</span><span class="pun">().</span><span class="typ">Has</span><span class="pun">(</span><span class="str">"first"</span><span class="pun">)</span><span class="pln">
    first </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">URL</span><span class="pun">.</span><span class="typ">Query</span><span class="pun">().</span><span class="typ">Get</span><span class="pun">(</span><span class="str">"first"</span><span class="pun">)</span><span class="pln">
    hasSecond </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">URL</span><span class="pun">.</span><span class="typ">Query</span><span class="pun">().</span><span class="typ">Has</span><span class="pun">(</span><span class="str">"second"</span><span class="pun">)</span><span class="pln">
    second </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">URL</span><span class="pun">.</span><span class="typ">Query</span><span class="pun">().</span><span class="typ">Get</span><span class="pun">(</span><span class="str">"second"</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%s: got / request. first(%t)=%s, second(%t)=%s\n"</span><span class="pun">,</span><span class="pln">
        ctx</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">(</span><span class="pln">keyServerAddr</span><span class="pun">),</span><span class="pln">
        hasFirst</span><span class="pun">,</span><span class="pln"> first</span><span class="pun">,</span><span class="pln">
        hasSecond</span><span class="pun">,</span><span class="pln"> second</span><span class="pun">)</span><span class="pln">
    io</span><span class="pun">.</span><span class="typ">WriteString</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="str">"This is my website!\n"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	يمكننا في دالة <code>getRoot</code> المحدَّثة استخدام الحقل <code>r.URL</code> الذي يتبع إلى <code>http.Request*</code> للوصول إلى الخصائص المتعلقة بعنوان URL المطلوب. باستخدام التابع <code>Query</code> في الحقل <code>r.URL</code>، يمكننا الوصول إلى قيم سلسلة الاستعلام المرتبطة بالطلب. هناك طريقتان يمكن استخدامهما للتفاعل مع بيانات سلسلة الاستعلام:
</p>

<ol>
	<li>
		يتحقق التابع <code>Has</code> ما إذا كانت سلسلة الاستعلام تحتوي على قيمة بمفتاح معين، مثل"first" أو "second". يعيد التابع قيمة بوليانية <code>bool</code> تشير إلى وجود المفتاح.
	</li>
	<li>
		يسترد التابع <code>Get</code> القيمة المرتبطة بمفتاح معين من سلسلة الاستعلام وتكون من نوع <code>string</code>، وإذا لم يعثر على المفتاح، يعيد عادةً سلسلة فارغة.
	</li>
</ol>

<p>
	نظريًّا يمكننا دائمًا استخدام <code>Get</code> لاسترداد قيم سلسلة الاستعلام لأنها ستعيد دائمًا إما القيمة الفعلية للمفتاح المحدد أو سلسلة فارغة إذا كان المفتاح غير موجود. هذا جيد في كثير من الحالات، ولكن في بعض الحالات الأخرى، قد نرغب في معرفة الفرق بين المستخدم الذي يقدم قيمة فارغة أو عدم تقديم قيمة إطلاقًا. إذًا، بناءً على حالة الاستخدام الخاصة بنا، قد يكون من المهم التمييز بين المستخدم الذي يقدم قيمة فارغة للمرشّح أو عدم تقديم مرشح <code>filter</code> إطلاقًا:
</p>

<ul>
	<li>
		يُقدّم المستخدم قيمة فارغة للمرشح <code>filter</code>: هنا يحدد المستخدم صراحة قيمة المرشح، ولكنه يضبطها على قيمة فارغة. قد يشير هذا إلى اختيار متعمد لاستبعاد نتائج معينة أو تطبيق شرط مرشح<code>filter</code> معين. مثلًا، إذا كانت لدينا دالة بحث وقدم المستخدم قيمة فارغة للمرشح، فيمكننا تفسيرها على أنها "إظهار كافة النتائج".
	</li>
	<li>
		لا يوفر المستخدم مرشّح إطلاقًا: هنا لم يقدّم المستخدم مرشّحًا في الطلب. هذا يعني عادةً أن المستخدم يريد استرداد جميع النتائج دون تطبيق أي تصفية محددة. يمكن عدّه سلوكًا افتراضيًا، إذ لا تُطبّق شروط ترشيح محددة.
	</li>
</ul>

<p>
	يسمح لك استخدام <code>Has</code> و <code>Get</code> بالتمييز بين الحالات التي يقدم فيها المستخدم صراحة قيمةً فارغة والحالات التي لا تُقدّم فيها قيمة إطلاقًا. بالتالي إمكانية التعامل مع السيناريوهات المختلفة اعتمادًا على حالة الاستخدام المحددة الخاصة بنا.
</p>

<p>
	يمكنك تحديث دالة <code>getRoot</code> لعرض قيم <code>Has</code> و <code>Get</code> من أجل قيمتي سلسلة الاستعلام <code>first</code> و <code>second</code>.
</p>

<p>
	لنعدّل الدالة <code>main</code> بحيث نستخدم خادم واحد مرة أخرى:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_56" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    mux</span><span class="pun">.</span><span class="typ">HandleFunc</span><span class="pun">(</span><span class="str">"/hello"</span><span class="pun">,</span><span class="pln"> getHello</span><span class="pun">)</span><span class="pln">

    ctx </span><span class="pun">:=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">Background</span><span class="pun">()</span><span class="pln">
    server </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Server</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Addr</span><span class="pun">:</span><span class="pln">    </span><span class="str">":3333"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Handler</span><span class="pun">:</span><span class="pln"> mux</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">BaseContext</span><span class="pun">:</span><span class="pln"> func</span><span class="pun">(</span><span class="pln">l net</span><span class="pun">.</span><span class="typ">Listener</span><span class="pun">)</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">Context</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            ctx </span><span class="pun">=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">WithValue</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">,</span><span class="pln"> keyServerAddr</span><span class="pun">,</span><span class="pln"> l</span><span class="pun">.</span><span class="typ">Addr</span><span class="pun">().</span><span class="typ">String</span><span class="pun">())</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> ctx
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    err </span><span class="pun">:=</span><span class="pln"> server</span><span class="pun">.</span><span class="typ">ListenAndServe</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">Is</span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">ErrServerClosed</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"server closed\n"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"error listening for server: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نلاحظ ضمن الدالة <code>main</code> إزالة المراجع والشيفرات المرتبطة بالخادم الثاني <code>serverTwo</code>، لأننا لم نعد بحاجة إلى خوادم متعددة. نقلنا أيضًا تنفيذ الخادم (<code>serverOne</code> سابقًا) خارج التنظيم goroutine وإلى الدالة <code>main</code>. هذا يعني أنه سيجري بدء تشغيل الخادم بطريقة متزامنة، و ينتظر التنفيذ حتى يُغلق الخادم قبل المتابعة.
</p>

<p>
	تغيير آخر كان باستخدام الدالة <code>server.ListenAndServe</code>؛ فبدلًا من استخدام <code>http.ListenAndServe</code>، يمكن الآن استخدام<code>server.ListenAndServe</code> لبدء الخادم. يتيح ذلك الاستفادة من تهيئة <code>http.Server</code> وأي تخصيصات أجريناها. كذلك أضفنا شيفرة لمعالجة الأخطاء، وذلك للتحقق ما إذا كان الخادم مغلقًا أو واجه أي خطأ آخر أثناء الاستماع. إذا كان الخطأ هو <code>http.ErrServerClosed</code>، فهذا يعني أن عملية الإغلاق عن قصد (إغلاق طبيعي)، وخلاف ذلك ستجري طباعة الخطأ. بإجراء هذه التغييرات سيُشغّل برنامجنا الآن خادم HTTP واحد باستخدام وفقًا لتهيئة<code>http.Server</code> ليبدأ في الاستماع للطلبات الواردة، ولن تحتاج إلى تحديثات أخرى من أجل تخصيصات للخادم مستقبلًا.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_60" style=""><span class="pln">$ go run main</span><span class="pun">.</span><span class="pln">go</span></pre>

<p>
	نُشغّل الآن أوامر <code>curl</code> (في الطرفية الثانية). هنا نحتاج إلى إحاطة عنوان URL بعلامات اقتباس مفردة (<code>'</code>)، وإلا فقد تُفسر صدفة الطرفية (أي Shell) الرمز <code>&amp;</code> في سلسلة الاستعلام على أنه ميزة "تشغيل الأمر في الخلفية". ضمن عنوان URL نضيف <code>first=1</code> إلى <code>first</code> و <code>=second</code> إلى <code>second</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_58" style=""><span class="pln">$ curl </span><span class="str">'http://localhost:3333?first=1&amp;second='</span></pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_62" style=""><span class="typ">This</span><span class="pln"> is my website</span><span class="pun">!</span></pre>

<p>
	لاحظ أن الخرج لم يتغير عن المرة السابقة، لكن إذا عدنا إلى خرج برنامج الخادم، فسنرى أن الخرج الجديد يتضمن قيم سلسلة الاستعلام:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_64" style=""><span class="pun">[::]:</span><span class="lit">3333</span><span class="pun">:</span><span class="pln"> got </span><span class="pun">/</span><span class="pln"> request</span><span class="pun">.</span><span class="pln"> first</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> second</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)=</span></pre>

<p>
	يظهِر خرج قيمة سلسلة الاستعلام <code>first</code> أن التابع <code>Has</code> أعاد <code>true</code> لأن <code>first</code> لها قيمة، وأيضًا التابع <code>Get</code> أعاد القيمة <code>1</code>. يُظهر ناتج <code>second</code> أنه قد أعاد <code>true</code> لأننا ضمّنّا <code>second</code>، لكن التابع <code>Get</code> لم يُعيد أي شيء إلا سلسلة فارغة. يمكننا أيضًا محاولة إجراء طلبات مختلفة عن طريق إضافة وإزالة <code>first</code> و <code>second</code> أو إسناد قيم مختلفة لنرى كيف تتغير النتائج.
</p>

<p>
	سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "Ctrl+C".
</p>

<p>
	حدّثنا في هذا القسم البرنامج لاستخدام <code>http.Server</code> واحد فقط مرةً أخرى، لكن أضفنا أيضًا دعمًا لقراءة قيم <code>first</code> و <code>second</code> من سلسلة الاستعلام لدالة المعالجة <code>getRoot</code>.
</p>

<p>
	ليس استخدام سلسلة الاستعلام الطريقة الوحيدة للمستخدمين لتقديم مدخلات إلى خادم HTTP، فهناك طريقة أخرى شائعة لإرسال البيانات إلى الخادم وهي تضمين البيانات في متن الطلب. سنعدّل في القسم التالي البرنامج لقراءة نص الطلب من بيانات <code>http.Request*</code>.
</p>

<h2 id="-6">
	قراءة متن الطلب
</h2>

<p>
	عند إنشاء واجهة برمجة تطبيقات مبنية على HTTP، مثل <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">واجهة برمجة تطبيقات</a> REST، قد تكون هناك حالات تتجاوز فيها البيانات المُرسلة قيود تضمينها في عنوان URL نفسه، مثل الطول الأعظمي للعنوان. قد نحتاج أيضًا إلى تلقي بيانات لا تتعلق بكيفية تفسير البيانات، وهذا يشير إلى الحالات التي لا ترتبط فيها البيانات المرسلة في متن الطلب ارتباطًا مباشرًا بالمحتوى نفسه. بمعنى آخر: لا توفر هذه البيانات الإضافية إرشادات أو بيانات وصفية حول المحتوى، ولكنها تتضمن معلومات تكميلية تحتاج إلى المعالجة بطريقة منفصل. تخيل صفحة بحث، يمكن فيها للمستخدمين إدخال كلمات للبحث عن عناصر محددة. تمثل كلمات البحث نفسها تفسير المحتوى المقصود، ومع ذلك قد تكون هناك بيانات إضافية في متن الطلب، مثل تفضيلات المستخدم أو الإعدادات، والتي لا ترتبط مباشرةً باستعلام البحث نفسه ولكنها لا تزال بحاجة إلى النظر فيها أو معالجتها بواسطة الخادم. للتعامل مع مثل هذه السيناريوهات، يمكننا تضمين البيانات في متن طلب HTTP باستخدام توابع مثل <code>POST</code> أو <code>PUT</code>.
</p>

<p>
	تُستخدم قيمة <code>http.Request*</code> في <code>http.HandlerFunc</code> للوصول إلى معلومات متعلقة بالطلب الوارد، بما في ذلك متن الطلب، والذي يمكن الوصول إليه من خلال حقل <code>Body</code>.
</p>

<p>
	سنعدّل في هذا القسم دالة المعالجة <code>getRoot</code> لقراءة نص الطلب. لنفتح ملف <code>main.go</code> ونعدّل <code>getRoot</code> لاستخدام <code>ioutil.ReadAll</code> لقراءة حقل<code>r.Body</code> للطلب:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_66" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    </span><span class="str">"io/ioutil"</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

</span><span class="pun">...</span><span class="pln">

func getRoot</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
    second </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">URL</span><span class="pun">.</span><span class="typ">Query</span><span class="pun">().</span><span class="typ">Get</span><span class="pun">(</span><span class="str">"second"</span><span class="pun">)</span><span class="pln">

    body</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> ioutil</span><span class="pun">.</span><span class="typ">ReadAll</span><span class="pun">(</span><span class="pln">r</span><span class="pun">.</span><span class="typ">Body</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"could not read body: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%s: got / request. first(%t)=%s, second(%t)=%s, body:\n%s\n"</span><span class="pun">,</span><span class="pln">
        ctx</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">(</span><span class="pln">keyServerAddr</span><span class="pun">),</span><span class="pln">
        hasFirst</span><span class="pun">,</span><span class="pln"> first</span><span class="pun">,</span><span class="pln">
        hasSecond</span><span class="pun">,</span><span class="pln"> second</span><span class="pun">,</span><span class="pln">
        body</span><span class="pun">)</span><span class="pln">
    io</span><span class="pun">.</span><span class="typ">WriteString</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="str">"This is my website!\n"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	تُستخدم الدالة <code>ioutil.ReadAll</code> لقراءة <code>r.Body</code> من <code>http.Request*</code> لاسترداد بيانات متن الطلب. <code>ioutil.ReadAll</code> هي أداة مساعدة تقرأ البيانات من <code>io.Reader</code> حتى تنتهي من القراءة أو ظهور خطأ. نظرًا لأن <code>r.Body</code> هو <a href="https://pkg.go.dev/io#Reader" rel="external nofollow"><code>io.Reader</code></a>، فيمكن استخدامه لقراءة متن الطلب. نعدّل العبارة <code>fmt.Printf</code> بعد قراءة النص لتضمين محتوى المتن في الخرج.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	نُشغّل الآن أوامر <code>curl</code> (في الطرفية الثانية)، لتقديم طلب <code>POST</code> مع خيار <code>X POST-</code> وتحديد أن طريقة الطلب يجب أن تكون <code>POST</code>، ومتن طلب باستخدام الخيار <code>b-</code>. يُضبط متن الطلب على السلسلة النصيّة المقدمة، والتي في هذه الحالة هي "This is the body":
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_68" style=""><span class="pln">$ curl </span><span class="pun">-</span><span class="pln">X POST </span><span class="pun">-</span><span class="pln">d </span><span class="str">'This is the body'</span><span class="pln"> </span><span class="str">'http://localhost:3333?first=1&amp;second='</span></pre>

<p>
	ليكون الخرج:
</p>

<pre class="ipsCode">This is my website!
</pre>

<p>
	خرج دالة المعالجة هو نفسه، لكن سنرى تسجيلات الخادم الخاصة بك قد حُدّثت. سيُرسل طلب <code>POST</code> عندما نشغّل هذا الأمر إلى الخادم المحلي الذي يعمل على المنفذ <code>3333</code> مع محتوى المتن المحدد ومعلمات سلسلة الاستعلام. بالتالي يعالج الخادم الطلب ونرى الخرج في سجلات الطرفية للخادم، بما في ذلك قيم سلسلة الاستعلام ومتن الطلب.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_70" style=""><span class="pun">[::]:</span><span class="lit">3333</span><span class="pun">:</span><span class="pln"> got </span><span class="pun">/</span><span class="pln"> request</span><span class="pun">.</span><span class="pln"> first</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)=</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> second</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">)=,</span><span class="pln"> body</span><span class="pun">:</span><span class="pln">
</span><span class="typ">This</span><span class="pln"> is the body</span></pre>

<p>
	سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "Ctrl+C".
</p>

<p>
	عدّلنا في هذا القسم البرنامج لقراءة وطباعة متن الطلب. تفتح هذه الإمكانية إمكانيات التعامل مع أنواع مختلفة من البيانات، مثل جسون <a href="https://academy.hsoub.com/programming/javascript/%D8%B5%D9%8A%D8%BA%D8%A9-json-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r826/" rel="">encoding/json</a>، وتتيح إنشاء واجهات برمجة تطبيقات يمكنها التفاعل مع بيانات المستخدم بطريقة مألوفة.
</p>

<p>
	تجدر الملاحظة إلى أنه لا تأتي جميع بيانات المستخدم في شكل واجهات برمجة التطبيقات. تتضمن العديد من مواقع الويب نماذج يملؤها المستخدمون، والتي ترسل البيانات إلى الخادم مثل بيانات نموذج Form. سنعمل على تحسين البرنامج في القسم التالي ليشمل القدرة على قراءة بيانات النموذج ومعالجتها، بالإضافة إلى جسم الطلب وبيانات سلسلة الاستعلام التي كنا نعمل معها سابقًا.
</p>

<h2 id="-7">
	استرجاع بيانات النموذج
</h2>

<p>
	لطالما كان إرسال البيانات باستخدام <a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-forms-%D9%88%D8%A7%D9%84%D8%B9%D8%B1%D9%88%D8%B6-%D8%A7%D9%84%D8%B9%D8%A7%D9%85%D8%A9-%D9%88%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%B3%D8%A7%D9%83%D9%86%D8%A9-%D9%81%D9%8A-django-r476/" rel="">النماذج</a> أو الاستمارات هو الطريقة القياسية للمستخدمين لإرسال البيانات إلى خادم HTTP والتفاعل مع مواقع الويب، وعلى الرغم من انخفاض شعبية النماذج بمرور الوقت، إلا أنها لا تزال تخدم أغراضًا مختلفة لتقديم البيانات. توفر قيمة <code>http.Request*</code> في <code>http.HandlerFunc</code> طريقةً للوصول إلى هذه البيانات، بطريقة مشابهة للطريقة التي توفر بها الوصول إلى سلسلة الاستعلام ومتن الطلب. سنعدّل في هذا القسم الدالة <code>getHello</code> لتلقي اسم مستخدم من نموذج والرد بتحية شخصية.
</p>

<p>
	لنفتح ملف "main.go" ونعدّل <code>getHello</code> لاستخدام التابع <code>PostFormValue</code> من <code>http.Request*</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_72" style=""><span class="pun">...</span><span class="pln">

func getHello</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ctx </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">()</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%s: got /hello request\n"</span><span class="pun">,</span><span class="pln"> ctx</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">(</span><span class="pln">keyServerAddr</span><span class="pun">))</span><span class="pln">

    myName </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">PostFormValue</span><span class="pun">(</span><span class="str">"myName"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> myName </span><span class="pun">==</span><span class="pln"> </span><span class="str">""</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        myName </span><span class="pun">=</span><span class="pln"> </span><span class="str">"HTTP"</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    io</span><span class="pun">.</span><span class="typ">WriteString</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"Hello, %s!\n"</span><span class="pun">,</span><span class="pln"> myName</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	الآن في دالة <code>getHello</code> المحدَّثة، تجري قراءة قيم النموذج المرسلة إلى دالة المعالجة والبحث عن قيمة تسمى <code>myName</code>. إذا لم يُعثر على القيمة أو كانت سلسلة فارغة، تُضبط القيمة الافتراضية <code>HTTP</code> إلى المتغير <code>myName</code> لمنع عرض اسم فارغ على الصفحة. نعدّل أيضًا الخرج المرسل إلى المستخدم لكي يعرض الاسم الذي قدمه أو <code>HTTP</code> إذا لم يُقدّم أي اسم.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_76" style=""><span class="pln">$ go run main</span><span class="pun">.</span><span class="pln">go</span></pre>

<p>
	نُشغّل الآن أوامر <code>curl</code> (في الطرفية الثانية)، لتقديم طلب <code>POST</code> مع خيار <code>X POST-</code>، وبدلًا من استخدام الخيار <code>b-</code>، نستخدم الخيار <code>'F 'myName=Sammy</code> لتضمين بيانات النموذج مع حقل <code>myName</code> والقيمة <code>Sammy</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_74" style=""><span class="pln">curl </span><span class="pun">-</span><span class="pln">X POST </span><span class="pun">-</span><span class="pln">F </span><span class="str">'myName=Sammy'</span><span class="pln"> </span><span class="str">'http://localhost:3333/hello'</span></pre>

<p>
	سيكون الخرج:
</p>

<pre class="ipsCode">Hello, Sammy!
</pre>

<p>
	يُستخدم التابع <code>r.PostFormValue</code> في <code>getHello</code> لاسترداد قيمة حقل النموذج <code>myName</code>. يبحث هذا التابع تحديدًا عن القيم المنشورة (البيانات المرسلة من العميل إلى الخادم) في متن الطلب. يمكن أيضًا استخدام التابع <code>r.FormValue</code>، والذي يتضمن كلًا من متن النموذج وأي قيم أخرى في سلسلة الاستعلام. إذا استخدمنا <code>("r.FormValue("myName</code> وأزلنا الخيار <code>F-</code>، فيمكن تضمين <code>myName = Sammy</code> في سلسلة الاستعلام لرؤية القيمة <code>Sammy</code>.
</p>

<p>
	يوصى عمومًا بأن نكون أكثر صرامة وأن نستخدم الدالة <code>r.PostFormValue</code> إذا كنا نريد تحديدًا استرداد القيم من متن النموذج، إذ تتجنب هذه الدالة التعارضات أو الأخطاء المحتملة التي قد تنشأ عن خلط قيم النموذج من مصادر مختلفة. إذًا، تعد الدالة <code>r.PostFormValue</code> خيارًا أكثر أمانًا عند التعامل مع بيانات النموذج، لكن إذا كنا نحتاج إلى المرونة في وضع البيانات في كل من متن النموذج وسلسلة الاستعلام، فربما نضطر إلى الدالة الأخرى.
</p>

<p>
	عند النظر إلى سجلات الخادم، سنرى أن طلب <code>hello/</code> قد جرى تسجيله بطريقة مشابهة للطلبات السابقة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_78" style=""><span class="pun">[::]:</span><span class="lit">3333</span><span class="pun">:</span><span class="pln"> got </span><span class="pun">/</span><span class="pln">hello request</span></pre>

<p>
	سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "CONTROL+C".
</p>

<p>
	عدّلنا في هذا القسم البرنامج لقراءة اسم من بيانات النموذج المنشورة على الصفحة ثم إعادة هذا الاسم إلى المستخدم.
</p>

<p>
	يمكن أن تسوء بعض الأشياء في هذه المرحلة من البرنامج، لا سيما عند التعامل مع أحد الطلبات، ولن يجري إعلام المستخدمين. لنعدّل في القسم التالي دوال المعالجة لإرجاع رموز حالة HTTP والترويسات headers.
</p>

<h2 id="-8">
	الرد باستجابة تتضمن الترويسات ورمز الحالة
</h2>

<p>
	هناك بعض الميزات المستخدمة خلف الكواليس ضمن بروتوكول HTTP لتسهيل الاتصال الفعّال بين المتصفحات والخوادم. إحدى هذه الميزات هي "رموز الحالة"، والتي تعمل مثل وسيلة للخادم لتوضّح للعميل ما إذا كان الطلب ناجحًا أو واجه أي مشكلات في أي من الطرفين.
</p>

<p>
	آلية اتصال أخرى تستخدمها خوادم وعملاء HTTP هي استخدام "حقول الترويسة header fields"، التي تتكون من أزواج ذات قيمة مفتاح يجري تبادلها بين العميل والخادم لنقل المعلومات عن أنفسهم. يمتلك بروتوكول HTTP عدة ترويسات معرّفة مسبقًا، مثل ترويسة <code>Accept</code>، التي يستخدمها العميل لإعلام الخادم بنوع البيانات التي يمكنه التعامل معها. يمكن أيضًا تعريف ترويسات خاصة باستخدام البادئة <code>x-</code> متبوعة بالاسم المطلوب.
</p>

<p>
	سنعمل في هذا القسم على تحسين البرنامج بجعل حقل النموذج <code>myName</code> في دالة المعالجة <code>getHello</code> حقلًا إلزاميًا. بالتالي، إذا لم تُعطى قيمة للحقل <code>myName</code>، فسيستجيب الخادم للعميل برمز الحالة "Bad Request طلب غير صالح" وتضمين الترويسة <code>x-missing-field</code> في الاستجابة، والتي تُعلم العميل بالحقل المفقود من الطلب.
</p>

<p>
	لنفتح ملف "main.go" ونعدّل الدالة <code>getHello</code> وفقًا لما ذُكر:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_80" style=""><span class="pun">...</span><span class="pln">

func getHello</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ctx </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">()</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%s: got /hello request\n"</span><span class="pun">,</span><span class="pln"> ctx</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">(</span><span class="pln">keyServerAddr</span><span class="pun">))</span><span class="pln">

    myName </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">PostFormValue</span><span class="pun">(</span><span class="str">"myName"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> myName </span><span class="pun">==</span><span class="pln"> </span><span class="str">""</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        w</span><span class="pun">.</span><span class="typ">Header</span><span class="pun">().</span><span class="typ">Set</span><span class="pun">(</span><span class="str">"x-missing-field"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"myName"</span><span class="pun">)</span><span class="pln">
        w</span><span class="pun">.</span><span class="typ">WriteHeader</span><span class="pun">(</span><span class="pln">http</span><span class="pun">.</span><span class="typ">StatusBadRequest</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    io</span><span class="pun">.</span><span class="typ">WriteString</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"Hello, %s!\n"</span><span class="pun">,</span><span class="pln"> myName</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">…</span></pre>

<p>
	سابقًا: إذا كان حقل <code>myName</code> فارغًا، كان يُسند إليه قيمة افتراضية، أما الآن ضمن الدالة <code>getHello</code> المُحدَّثة، نُرسل رسالة خطأ إلى العميل. يُستخدم بدايةً التابع <code>w.Header().Set</code> لضبط الترويسة <code>x-missing-field</code> بقيمة <code>myName</code> في ترويسة الاستجابة، ثم التابع <code>w.WriteHeader</code> لكتابة ترويسات الاستجابة ورمز الحالة "طلب غير صالح" إلى العميل. أخيرًا تُستخدم تعليمة <code>return</code> لضمان إنهاء الدالة وعدم إرسال استجابة إضافية.
</p>

<p>
	من الضروري التأكد من ضبط الترويسات وإرسال رمز الحالة بالترتيب الصحيح، إذ يجب إرسال جميع الترويسات في بروتوكول HTTP قبل الجسم، مما يعني أنه يجب إجراء أي تعديلات على <code>()w.Header</code> قبل استدعاء <code>w.WriteHeader</code>. عند استدعاء <code>w.WriteHeader</code> يُرسل رمز الحالة والترويسات، ويمكن كتابة المتن بعد ذلك حصرًا. يجب اتباع هذا الأمر لضمان حسن سير استجابة HTTP.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" بعد حفظه من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	نُشغّل الآن الأمر <code>curl -X POST</code> (في الطرفية الثانية) مع المسار <code>hello/</code> وبدون تضمين <code>F-</code> لإرسال بيانات النموذج. نحتاج أيضًا إلى تضمين الخيار <code>v-</code> لإخبار <code>curl</code> بإظهار الخرج المطوّل حتى نتمكن من رؤية جميع الترويسات والخرج للطلب:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_82" style=""><span class="pln">curl </span><span class="pun">-</span><span class="pln">v </span><span class="pun">-</span><span class="pln">X POST </span><span class="str">'http://localhost:3333/hello'</span></pre>

<p>
	سنرى هذه المرة الكثير من المعلومات بسبب استخدامنا الخيار <code>v-</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_84" style=""><span class="pun">*</span><span class="pln">   </span><span class="typ">Trying</span><span class="pln"> </span><span class="pun">::</span><span class="lit">1</span><span class="pun">:</span><span class="lit">3333.</span><span class="pun">..</span><span class="pln">
</span><span class="pun">*</span><span class="pln"> </span><span class="typ">Connected</span><span class="pln"> to localhost </span><span class="pun">(::</span><span class="lit">1</span><span class="pun">)</span><span class="pln"> port </span><span class="lit">3333</span><span class="pln"> </span><span class="pun">(#</span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="pun">&gt;</span><span class="pln"> POST </span><span class="pun">/</span><span class="pln">hello HTTP</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln">
</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">Host</span><span class="pun">:</span><span class="pln"> localhost</span><span class="pun">:</span><span class="lit">3333</span><span class="pln">
</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">User</span><span class="pun">-</span><span class="typ">Agent</span><span class="pun">:</span><span class="pln"> curl</span><span class="pun">/</span><span class="lit">7.77</span><span class="pun">.</span><span class="lit">0</span><span class="pln">
</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">Accept</span><span class="pun">:</span><span class="pln"> </span><span class="pun">*</span><span class="com">/*
&gt; 
* Mark bundle as not supporting multiuse
&lt; HTTP/1.1 400 Bad Request
&lt; X-Missing-Field: myName
&lt; Date: Wed, 02 Mar 2022 03:51:54 GMT
&lt; Content-Length: 0
&lt; 
* Connection #0 to host localhost left intact</span></pre>

<p>
	تشير الأسطر الأولى إلى أن <code>curl</code> تحاول إنشاء اتصال بالخادم عند منفذ المضيف المحلي <code>3333</code>. الأسطر المسبوقة بالرمز <code>&lt;</code> تمثل الطلب المُرسل بواسطة <code>curl</code>، إذ يُرسل طلب <code>POST</code> إلى العنوان أو المسار <code>hello/</code> باستخدام بروتوكول HTTP 1.1. يتضمن الطلب ترويسات مثل <code>User-Agent</code> و <code>Accept</code> و <code>Host</code>. والجدير بالذكر أن الطلب لا يتضمن متنًا، كما هو موضح في السطر الفارغ.
</p>

<p>
	تظهر استجابة الخادم بالسابقة <code>&gt;</code> بعد إرسال الطلب. يشير السطر الأول إلى أن الخادم قد استجاب برمز حالة "طلب غير صالح" (المعروفة برمز الحالة 400)، كما تُضمّن الترويسة <code>X-Missing-Field</code> التي جرى ضبطها في ترويسة استجابة الخادم، مع تحديد أن الحقل المفقود هو <code>myName</code>. ينتهي الرد بدون أي محتوى في المتن، وهذا ما يتضح من طول المحتوى <code>0</code>.
</p>

<p>
	إذا نظرنا مرةً أخرى إلى خرج الخادم، فسنرى طلب <code>hello/</code> للخادم الذي جرت معالجته في الخرج:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3918_86" style=""><span class="pun">[::]:</span><span class="lit">3333</span><span class="pun">:</span><span class="pln"> got </span><span class="pun">/</span><span class="pln">hello request</span></pre>

<p>
	سيستمر البرنامج بالعمل، لذا يجب علينا إيقافه يدويًا من خلال الضغط على المفتاحين "CONTROL+C".
</p>

<p>
	عدّلنا في هذا القسم خادم HTTP ليشمل التحقق من صحة إرسال الطلب <code>hello/</code>. إذا لم يُقدّم اسم في في الطلب، تُضبط ترويسة باستخدام التابع <code>w.Header(). Set</code> للإشارة إلى الحقل المفقود. يُستخدم التابع <code>w.WriteHeader</code> بعد ذلك، لكتابة الترويسات إلى العميل، جنبًا إلى جنب مع رمز الحالة التي تشير إلى "طلب غير صالح". إذًا يُخبر الخادم العميل بوجود مشكلة في الطلب من خلال ضبط الترويسة ورمز الحالة. يسمح هذا الأسلوب بمعالجة الأخطاء بطريقة صحيحة ويوفر ملاحظات للعميل فيما يتعلق بحقل النموذج المفقود.
</p>

<h2 id="-9">
	الخاتمة
</h2>

<p>
	تعلمنا كيفية إنشاء خادم HTTP في لغة جو باستخدام حزمة <code>net/http</code>، وطوّرنا الخادم باستخدام مجمّع مخصص للخادم واستخدام نسخ<code>http.Server</code> متعددة. اسكتشفنا أيضًا طرقًا مختلفة للتعامل مع مُدخلات المستخدم، مثل قيم سلسلة الاستعلام ومتن الطلب وبيانات النموذج. تحققنا أيضًا من صحة الطلب من خلال إعادة ترويسات HTTP مخصصة ورموز حالة إلى العميل.
</p>

<p>
	تتمثل إحدى نقاط القوة في نظام بروتوكول HTTP في توافقه مع العديد من الأطر التي تتكامل بسلاسة مع حزمة <code>net/http</code>. توضح مشاريع مثل <a href="github.com/go-chi/chi" rel="">github.com/go-chi/chi</a> ذلك من خلال توسيع وظائف خادم http القياسي من خلال تحقيق الواجهة <code>http.Handler</code>. يتيح ذلك للمطورين الاستفادة من البرامج الوسيطة والأدوات الأخرى دون الحاجة لإعادة كتابة المنطق الخاص الخادم، وهذا يسمح بالتركيز على إنشاء <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs-%D9%88%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r1441/" rel="">برمجيات وسيطة middleware</a> وأدوات أخرى لتحسين الأداء بدلًا من التعامل مع الوظائف الأساسية فقط.
</p>

<p>
	توفر حزمة <code>net/http</code> المزيد من الإمكانات التي لم نتناولها في هذا المقال، مثل العمل مع ملفات تعريف الارتباط وخدمة حركة مرور HTTPS.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-make-an-http-server-in-go" rel="external nofollow">How To Make an HTTP Server in Go</a> لصاحبه Kristin Davidson.
</p>

<h2 id="-10">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B5%D9%8A%D8%BA%D8%A9-json-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2165/" rel="">كيفية استخدام صيغة جسون JSON في لغة جو Go</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">مدخل إلى خادم الويبن</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-%D8%B4%D8%B1%D8%AD-%D8%A7%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r74/" rel="">مدخل إلى HTTP: شرح التخاطب بين العميل والخادم</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D9%88%D8%A5%D8%B5%D9%84%D8%A7%D8%AD-%D8%B1%D9%85%D9%88%D8%B2-%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-http-%D8%A7%D9%84%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-r116/" rel="">رموز الحالة status codes</a>.
	</li>
</ul>
]]></description><guid isPermaLink="false">2190</guid><pubDate>Thu, 07 Dec 2023 16:01:00 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x635;&#x64A;&#x63A;&#x629; JSON &#x641;&#x64A; &#x644;&#x63A;&#x629; Go</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B5%D9%8A%D8%BA%D8%A9-json-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2165/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_11/---JSON----Go.png.16536f956acd661d39cb66941b3ac79b.png" /></p>
<p>
	إحدى الخصائص المهمة في البرامج الحديثة هو إمكانية التواصل مع البرامج الأخرى، سواءٌ كان <a href="https://academy.hsoub.com/programming/go/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC%D9%83-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D9%81%D9%8A-%D8%AC%D9%88-go-r1788/" rel="">برنامج جو</a> يتحقق ما إذا كان لدى المستخدم حق الوصول إلى برنامج آخر، أو برنامج <a href="https://academy.hsoub.com/programming/javascript/" rel="">جافا سكريبت JavaScript</a> يحصل على قائمة بالطلبات السابقة لعرضها على موقع ويب، أو برنامج <a href="https://academy.hsoub.com/programming/rust/" rel="">رست Rust</a> يقرأ نتائج اختبار من ملف، فهناك حاجة إلى طريقة نزوّد البرامج من خلالها بالبيانات. لدى أغلب <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">لغات البرمجة</a> طريقة خاصة في تخزين البيانات داخليًّا، والتي لا تفهمها اللغات البرمجية الأخرى. للسماح لهذه اللغات بالتفاعل مع بعضها، يجب تحويل <a href="https://academy.hsoub.com/programming/general/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1726/" rel="">البيانات</a> إلى تنسيق أو صيغة مشتركة يمكنهم فهمها جميعًا. إحدى هذه الصيغ هي صيغة <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">جسون JSON</a>، إنها وسيلة شائعة لنقل البيانات عبر الإنترنت وكذلك بين البرامج في نفس النظام. تمتلك <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a> والعديد من لغات البرمجة الأخرى طريقة لتحويل البيانات من وإلى صيغة جسون في مكتباتها القياسية.
</p>

<p>
	سنُنشئ في هذا المقال برنامجًا يستخدم حزمة <code>encoding/json</code> لتحويل صيغة البيانات من النوع <code>map</code> إلى بيانات جسون، ثم من النوع <code>struct</code> إلى جسون، كما سنتعلم كيفية إجراء تحويل عكسي.
</p>

<h2 id="">
	المتطلبات
</h2>

<ul>
	<li>
		إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a> لإعداده.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
	<li>
		(اختياري) معرفة بكيفية التعامل مع التاريخ والوقت في لغة جو. يمكنك الاطلاع على مقالة <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%A7%D8%B1%D9%8A%D8%AE-%D9%88%D8%A7%D9%84%D9%88%D9%82%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2163/" rel="">استخدام التاريخ والوقت في لغة جو Go</a>.
	</li>
	<li>
		معرفة مسبقة بصيغة<a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">جسون</a>.
	</li>
	<li>
		معرفة مسبقة بكيفية التعامل مع وسوم البنية Struct tags لتخصيص حقول البنية. يمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%B3%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%86%D9%8A%D8%A9-struct-tags-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1998/" rel="">استخدام وسوم البنية Struct Tags في لغة جو</a>.
	</li>
</ul>

<h2 id="mapsjson">
	استخدام الروابط Maps لتوليد بيانات بصيغة JSON
</h2>

<p>
	توفر حزمة <code>encoding/json</code> بعض الدوال لتحويل البيانات من وإلى جسون JSON. الدالة الأولى هي <code>json.Marshal</code>. التنظيم Marshaling أو المعروف أيضًا باسم السلسلة Serialization، هو عملية تحويل بيانات البرنامج من الذاكرة إلى تنسيق يمكن نقله أو حفظه في مكان آخر، وهذا ما تفعله الدالة <code>json.Marshal</code> في لغة جو، إذ تحول بيانات البرنامج قيد التشغيل (الموجود في الذاكرة) إلى بيانات جسون. تقبل هذه الدالة أي قيمة من النوع واجهة <code>{}interface</code> لتنظيمها بصيغة جسون، لذلك يُسمح بتمرير أي قيمة مثل معامل، لتُعيد الدالة البيانات ممثلة بصيغة جسون في النتيجة.
</p>

<p>
	سننشئ في هذا القسم برنامجًا يستخدم دالة <code>json.Marshal</code> لإنشاء ملف جسون يحتوي أنواعًا مختلفة من البيانات من قيم <code>map</code>، ثم سنطبع هذه القيم على شاشة الخرج. تُمثّل بيانات جسون غالبًا على شكل كائن مفاتيحه من سلاسل نصيّة وقيمه من أنواع مختلفة، وهذا يُشبه آلية تمثيل البيانات في روابط جو، لذا فإن الطريقة الأفضل لإنشاء بيانات جسون في لغة جو هي وضع البيانات ضمن رابطة <code>map</code> مع مفاتيح من النوع <code>string</code> وقيم من النوع <code>{}interface</code>. تٌفسّر مفاتيح <code>map</code> مباشرةً على أنها مفاتيح جسون، ويمكن أن تكون قيم النوع <code>interface</code> أي <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1791/" rel="">نوع بيانات في لغة جو</a>، مثل <code>int</code>، أو <code>string</code>، أو حتى <code>{}map[string]interface</code>
</p>

<p>
	لبدء استخدام الحزمة <code>encoding/json</code>، وكما هو معتاد، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه، ويمكن وضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه:
</p>

<pre class="ipsCode">$ mkdir projects
$ cd projects
</pre>

<p>
	الآن، من داخل هذا المجلد، سنشغّل الأمر <code>mkdir</code> لإنشاء مجلد "jsondata" ثم سنستخدم <code>cd</code> للانتقال إليه:
</p>

<pre class="ipsCode">$ mkdir jsondata
$ cd jsondata
</pre>

<p>
	يمكننا الآن فتح ملف "main.go" باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">محرر نانو nano أو أي محرر آخر تريده</a>:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	نضيف داخل ملف "main.go" دالة <code>main</code> لتشغيل البرنامج، ثم نضيف قيمة <code>{}map[string]interface</code> مع مفاتيح وقيم من أنواع مختلفة، ثم نستخدم الدالة <code>json.Marshal</code> لتحويل بيانات <code>map</code> إلى بيانات جسون:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_8" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"encoding/json"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    data </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">map</span><span class="pun">[</span><span class="pln">string</span><span class="pun">]</span><span class="pln">interface</span><span class="pun">{}{</span><span class="pln">
        </span><span class="str">"intValue"</span><span class="pun">:</span><span class="pln">    </span><span class="lit">1234</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"boolValue"</span><span class="pun">:</span><span class="pln">   </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"stringValue"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello!"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"objectValue"</span><span class="pun">:</span><span class="pln"> </span><span class="typ">map</span><span class="pun">[</span><span class="pln">string</span><span class="pun">]</span><span class="pln">interface</span><span class="pun">{}{</span><span class="pln">
            </span><span class="str">"arrayValue"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[]</span><span class="typ">int</span><span class="pun">{</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">},</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    jsonData</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> json</span><span class="pun">.</span><span class="typ">Marshal</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"could not marshal json: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"json data: %s\n"</span><span class="pun">,</span><span class="pln"> jsonData</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نلاحظ في المتغير <code>data</code> أن كل قيمة لديها مفتاح <code>string</code>، لكن قيم هذه المفاتيح تختلف، فأحدها <code>int</code> وأحدها <code>bool</code> والأخرى هي رابط <code>{}map[string]interface</code> مع قيم <code>int</code> بداخلها. عند تمرير المتغير <code>data</code> إلى <code>json.Marshal</code>، ستنظر الدالة إلى جميع القيم التي يتضمنها الرابط وتحدد نوعها وكيفية تمثيلها في جسون، وإذا حدثت مشكلة في تفسير بيانات الرابط، ستُعيد خطأ يصف المشكلة. إذا نجحت العملية، سيتضمّن المتغير <code>jsonData</code> بيانات من النوع <code>byte[]</code> تُمثّل البيانات التي جرى تنظيمها إلى صيغة جسون. بما أن <code>byte[]</code> يمكن تحويلها إلى قيمة <code>string</code> باستخدام <code>(myString := string(jsonData</code> أو العنصر النائب <code>s%</code> ضمن تنسيق سلسلة، يمكننا طباعة بيانات جسون على شاشة الخرج باستخدام دالة الطباعة <code>fmt.Printf</code>.
</p>

<p>
	بعد حفظ وإغلاق الملف، لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">json data: {"boolValue":true,"intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}
</pre>

<p>
	نلاحظ من الخرج أن قيمة جسون هي كائن مُمثّل بأقواس معقوصة curly braces <code>{}</code> تحيط به، وأن جميع قيم المتغير <code>data</code> موجودة ضمن هذين القوسين. نلاحظ أيضًا أن رابط المفتاح <code>objectValue</code> الذي هو <code>{}map[string]interface</code> قد جرى تفسيره إلى كائن جسون آخر مُمثّل بأقواس معقوصة <code>{}</code> تحيط به أيضًا، ويتضمن أيضًا المفتاح <code>arrayValue</code> بداخله مع مصفوفة القيم المقابلة <code>[1،2،3،4]</code>.
</p>

<h3 id="-1">
	ترميز البيانات الزمنية في جسون
</h3>

<p>
	لا تقتصر قدرات الحزمة <code>encoding/json</code> على إمكانية تمثيل البيانات من النوع <code>string</code> و <code>int</code>، إذ يمكنها التعامل مع أنواع أعقد مثل البيانات الزمنية من النوع <code>time.Time</code> من الحزمة <code>time</code>.
</p>

<p>
	لنفتح ملف البرنامج "main.go" مرةً أخرى ونضيف قيمة من النوع <code>time.Time</code> باستخدام الدالة <code>time.Date</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_11" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"encoding/json"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    data </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">map</span><span class="pun">[</span><span class="pln">string</span><span class="pun">]</span><span class="pln">interface</span><span class="pun">{}{</span><span class="pln">
        </span><span class="str">"intValue"</span><span class="pun">:</span><span class="pln">    </span><span class="lit">1234</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"boolValue"</span><span class="pun">:</span><span class="pln">   </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"stringValue"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello!"</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"dateValue"</span><span class="pun">:</span><span class="pln">   time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2022</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">),</span><span class="pln">
        </span><span class="str">"objectValue"</span><span class="pun">:</span><span class="pln"> </span><span class="typ">map</span><span class="pun">[</span><span class="pln">string</span><span class="pun">]</span><span class="pln">interface</span><span class="pun">{}{</span><span class="pln">
            </span><span class="str">"arrayValue"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[]</span><span class="typ">int</span><span class="pun">{</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">},</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يؤدي هذا التعديل إلى ضبط التاريخ على <code>March 2, 2022</code> والوقت على <code>‎9:10:00 AM</code> في المنطقة الزمنية <code>UTC</code> وربطهم بالمفتاح <code>dateValue</code>. لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيعطي الخرج التالي:
</p>

<pre class="ipsCode">json data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}
</pre>

<p>
	نلاحظ هذه المرة في الخرج الحقل <code>dateValue</code> ضمن بيانات جسون، وأن الوقت مُنسّقٌ وفقًا لتنسيق <code>RFC 3339</code>، وهو تنسيق شائع يُستخدم لنقل التواريخ والأوقات على أنها قيم <code>string</code>.
</p>

<h3 id="null">
	ترميز قيم Null في جسون
</h3>

<p>
	قد نحتاج إلى التعامل مع قيم <code>null</code>، وتحويلها إلى صيغة جسون أيضًا. يمكن لحزمة <code>encoding/json</code> تولي هذه المهمة أيضًا، إذ يمكننا التعامل مع قيم <code>nil</code> (تُقابل قيم <code>null</code>) مثل أي قيمة من نوع آخر ضمن الرابط.
</p>

<p>
	لنفتح ملف "main.go" ولنضع قيمتي <code>null</code> ضمن الرابط:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_13" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    data </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">map</span><span class="pun">[</span><span class="pln">string</span><span class="pun">]</span><span class="pln">interface</span><span class="pun">{}{</span><span class="pln">
        </span><span class="str">"intValue"</span><span class="pun">:</span><span class="pln">    </span><span class="lit">1234</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"boolValue"</span><span class="pun">:</span><span class="pln">   </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"stringValue"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello!"</span><span class="pun">,</span><span class="pln">
                </span><span class="str">"dateValue"</span><span class="pun">:</span><span class="pln">   time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2022</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">),</span><span class="pln">
        </span><span class="str">"objectValue"</span><span class="pun">:</span><span class="pln"> </span><span class="typ">map</span><span class="pun">[</span><span class="pln">string</span><span class="pun">]</span><span class="pln">interface</span><span class="pun">{}{</span><span class="pln">
            </span><span class="str">"arrayValue"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[]</span><span class="typ">int</span><span class="pun">{</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">},</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
        </span><span class="str">"nullStringValue"</span><span class="pun">:</span><span class="pln"> nil</span><span class="pun">,</span><span class="pln">
        </span><span class="str">"nullIntValue"</span><span class="pun">:</span><span class="pln">    nil</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وضعنا في الشيفرة أعلاه قيمتي <code>null</code> مع مفتاحين مختلفين، هما <code>nullStringValue</code> و <code>nullIntValue</code> على التوالي، وعلى الرغم من أن أسماء المفاتيح تُشير إلى قيم <code>string</code> و <code>int</code>، لكن هي ليست كذلك (مجرد أسماء). طبعًا كل القيم ضمن الرابط مُشتقة من النوع <code>{}interface</code> والقيمة <code>nil</code> هي قيمة مُحتملة لهذا النوع وبالتالي تُفسّر على أنها <code>null</code> فقط، وهذا كل شيء. لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">json data: {"boolValue":true,"dateValue":"2022-03-02T09:10:00Z","intValue":1234,"nullIntValue":null,"nullStringValue":null,"objectValue":{"arrayValue":[1,2,3,4]},"stringValue":"hello!"}
</pre>

<p>
	نلاحظ في الخرج أن الحقلين <code>nullIntValue</code> و <code>nullStringValue</code> مُضمنان مع قيمة <code>null</code> لكل منهما، بالتالي تمكنا من استخدام <code>{}map[string]interface</code> مع قيم <code>null</code> دون أية مشاكل.
</p>

<p>
	أنشأنا في هذا القسم برنامجًا يمكنه تحويل قيم بيانات من النوع <code>{}map[string]interface</code> إلى بيانات جسون. أضفنا بعد ذلك حقلًا يستخدم بيانات زمنية من النوع <code>time.Time</code> ضمن الرابط، وأخيرًا أضفنا حقلين يستخدمان القيمة <code>null</code>.
</p>

<p>
	بالرغم من مرونة استخدام <code>{}map[string]interface</code> لتحويل البيانات إلى بيانات جسون، إلا أنه قد يكون عُرضةً لحدوث أخطاء غير مقصودة، ولا سيما إذا كنا بحاجة إلى إرسال نفس البيانات إلى عدة أماكن. إذا أرسلنا نُسخًا من هذه البيانات إلى أكثر من مكان ضمن الشيفرة، فربما نُغيّر عن طريق الخطأ اسم حقل أو نضع قيمة غير صحيحة ضمن حقل. في هكذا حالات ربما يكون من المفيد استخدام النوع <code>struct</code> لتمثيل البيانات التي نُريد تحويلها إلى جسون.
</p>

<h2 id="structs">
	استخدام البنى Structs لتوليد بيانات بصيغة جسون
</h2>

<p>
	تُعَدّ جو لغةً ثابتة الأنواع statically-typed language مثل <a href="https://academy.hsoub.com/programming/c/" rel="">لغة C</a> و<a href="https://academy.hsoub.com/programming/java/" rel="">جافا Java</a> و <a href="https://academy.hsoub.com/programming/cpp/" rel="">++C</a>، وهذا يعني أن كل تعليمة في البرنامج تُفحَص في وقت التصريف. تتمثل فائدة ذلك في السماح للمُصرّف باستنتاج نوع المتغيرات والتحقق منها وفرض التناسق بين قيم المتغيرات. تستفيد الحزمة <code>encoding/json</code> من ذلك من خلال تعريف <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%A8%D9%86%D9%89-structs-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1976/" rel="">بنية <code>struct</code></a> تُمثّل بيانات جسون. يمكننا التحكم في كيفية تفسير البيانات التي تتضمنها البنية باستخدام وسوم البنية Struct tags. سنعدّل البرنامج السابق خلال هذا القسم، لاستخدام بنية بدلًا من رابط، لتوليد بيانات جسون.
</p>

<p>
	عند استخدام <code>struct</code> لتعريف بيانات جسون، يجب علينا تصدير أسماء الحقول (وليس اسم النوع <code>struct</code> نفسه) التي نريد تحويلها إلى جسون، وذلك fأن نبدأ أسماء الحقول بحرف كبير (أي بدلًا من كتابة <code>intValue</code> نكتب <code>IntValue</code>) وإلا لن تكون الحزمة <code>encoding/json</code> قادرةً على الوصول إلى هذه الحقول لتحويلها إلى جسون.
</p>

<p>
	الآن بالنسبة لأسماء الحقول، إذا لم نستخدم وسوم البنية للتحكم في تسمية هذه الحقول، ستُفسّر كما هي مباشرةً ضمن البنية. قد يكون استخدام الأسماء الافتراضية هو ما نريده في بيانات جسون، وذلك وفقًا للطريقة التي نرغب بها بتنسيق بياناتنا، وبذلك لن نحتاج في هذه الحالة إلى أية وسوم. يستخدم العديد من المبرمجين تنسيقات أسماء، مثل <code>intValue</code>، أو <code>int_value</code> مع حقول البيانات، وسنحتاج في هذه الحالة إلى وسوم البنية للتحكم في كيفية تفسير هذه الأسماء.
</p>

<p>
	سيكون لدينا في المثال التالي بنية <code>struct</code> مع حقل وحيد اسمه <code>IntValue</code> وسنحول هذه البنية إلى صيغة جسون:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_15" style=""><span class="pln">type myInt </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">IntValue</span><span class="pln"> </span><span class="typ">int</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

data </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">myInt</span><span class="pun">{</span><span class="typ">IntValue</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1234</span><span class="pun">}</span></pre>

<p>
	إذا حوّلنا المتغير <code>data</code> إلى صيغة جسون باستخدام الدالة <code>json.Marshal</code>، سنرى الخرج التالي:
</p>

<pre class="ipsCode">{"IntValue":1234}
</pre>

<p>
	لكن لو كنا نريد أن يكون اسم الحقل في ملف جسون هو <code>intValue</code> بدلًا من <code>IntValue</code>، سنحتاج إلى إخبار <code>encoding/json</code> بذلك. بما أن <code>json.Marshal</code> لا تعرف ماذا نتوقع أن يكون اسم بيانات جسون، سنحتاج إلى إخبارها من خلال إضافة وسم البنية <code>json</code> بعد اسم الحقل مباشرةً مع إرفاقه بالاسم الذي نريد أن يظهر به في صيغة جسون. إذًا، من خلال إضافة هذا الوسم إلى الحقل <code>IntValue</code> مع الاسم الذي نريد يظهر به <code>intValue</code>، ستستخدم الدالة <code>json.Marshal</code> الاسم الذي نريده اسمًا للحقل ضمن صيغة جسون:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_17" style=""><span class="pln">type myInt </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">IntValue</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"intValue"</span><span class="pun">`</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

data </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">myInt</span><span class="pun">{</span><span class="typ">IntValue</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1234</span><span class="pun">}</span></pre>

<p>
	إذا حوّلنا المتغير <code>data</code> إلى صيغة جسون باستخدام الدالة <code>json.Marshal</code>، سنرى الخرج التالي، وكما نلاحظ فإنه يستخدم اسم الحقل الذي نريده:
</p>

<pre class="ipsCode">{"intValue":1234}
</pre>

<p>
	سنعدل البرنامج الآن لتعريف نوع بيانات <code>struct</code> يُمكن تحويله إلى بيانات جسون. سنضيف بنيةً باسم <code>myJSON</code> لتمثيل البيانات بطريقة يمكن تحويلها إلى جسون ونضيف البنية <code>myObject</code> التي ستكون قيمة للحقل <code>ObjectValue</code> ضمن البنية <code>myJSON</code>. سنضيف أيضًا وسمًا لكل اسم حقل ضمن البنية <code>myJSON</code> لتحديد الاسم الذي نريد أن يظهر به الحقل ضمن بيانات جسون. يجب أيضًا أن نحدّث الإسناد الخاص بالمتغير <code>data</code>بحيث نسند له بنية <code>myJSON</code> مع التصريح عنه بنفس الطريقة التي نتعامل مع بنى جو الأخرى.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_19" style=""><span class="pun">...</span><span class="pln">

type myJSON </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">IntValue</span><span class="pln">        </span><span class="typ">int</span><span class="pln">       </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"intValue"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">BoolValue</span><span class="pln">       </span><span class="kwd">bool</span><span class="pln">      </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"boolValue"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">StringValue</span><span class="pln">     string    </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"stringValue"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">DateValue</span><span class="pln">       time</span><span class="pun">.</span><span class="typ">Time</span><span class="pln"> </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"dateValue"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">ObjectValue</span><span class="pln">     </span><span class="pun">*</span><span class="pln">myObject </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"objectValue"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">NullStringValue</span><span class="pln"> </span><span class="pun">*</span><span class="pln">string   </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"nullStringValue"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">NullIntValue</span><span class="pln">    </span><span class="pun">*</span><span class="typ">int</span><span class="pln">      </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"nullIntValue"</span><span class="pun">`</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

type myObject </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">ArrayValue</span><span class="pln"> </span><span class="pun">[]</span><span class="typ">int</span><span class="pln"> </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"arrayValue"</span><span class="pun">`</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    otherInt </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">4321</span><span class="pln">
    data </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">myJSON</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">IntValue</span><span class="pun">:</span><span class="pln">    </span><span class="lit">1234</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">BoolValue</span><span class="pun">:</span><span class="pln">   </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">StringValue</span><span class="pun">:</span><span class="pln"> </span><span class="str">"hello!"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">DateValue</span><span class="pun">:</span><span class="pln">   time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2022</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">9</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">),</span><span class="pln">
        </span><span class="typ">ObjectValue</span><span class="pun">:</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">myObject</span><span class="pun">{</span><span class="pln">
            </span><span class="typ">ArrayValue</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[]</span><span class="typ">int</span><span class="pun">{</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4</span><span class="pun">},</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
        </span><span class="typ">NullStringValue</span><span class="pun">:</span><span class="pln"> nil</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">NullIntValue</span><span class="pun">:</span><span class="pln">    </span><span class="pun">&amp;</span><span class="pln">otherInt</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُشبه معظم التغييرات في الشيفرة أعلاه ما فعلناه في المثال السابق مع الحقل <code>IntValue</code>، إلا أن هناك بعض الأشياء تستحق الإشارة إليها. أحد هذه الأشياء هو الحقل <code>ObjectValue</code> الذي يستخدم قيمةً مرجعية <code>myObject*</code> لإخبار دالة <code>json.Marshal</code> -التي تؤدي عملية التنظيم- إلى وجود قيمة مرجعية من النوع <code>myObject</code> أو قيمة <code>nil</code>. بهذه الطريقة نكون قد عرّفنا كائن جسون بأكثر من طبقة، وفي حال كانت هذه الطريقة مطلوبة، سيكون لدينا بنيةً أخرى من نوع <code>struct</code> داخل النوع <code>myObject</code>، وهكذا، وبالتالي نلاحظ أنه بإمكاننا تعريف كائنات جسون أعقد وأعقد باستخدام أنواع <code>struct</code> وفقًا لحاجتنا.
</p>

<p>
	واحد من الأشياء الأخرى التي تستحق الذكر هي الحقلين <code>NullStringValue</code> و <code>NullIntValue</code>، وعلى عكس <code>StringValue</code> و <code>IntValue</code>؛ أنواع هذه القيم هي أنواع مرجعية <code>int*</code> و <code>string*</code>، وقيمها الافتراضية هي قيم صفريّة أي <code>nil</code> وهذا يُقابل القيمة 0 لنوع البيانات <code>int</code> والسلسلة الفارغة <code>''</code> لنوع البيانات <code>string</code>. يمكننا من الكلام السابق أن نستنتج أنه في حال أردنا التعبير عن قيمة من نوع ما تحتمل أن تكون <code>nil</code>، فيجب أن نجعلها قيمةً مرجعية. مثلًا لو كنا نريد أن نعبر عن قيمة حقل تُمثّل إجابة مُستخدم عن سؤال ما، فهنا قد يُجيب المُستخدم عن السؤال أو قد لا يُجيب (نضع <code>niil</code>).
</p>

<p>
	نُعدّل قيمة الحقل <code>NullIntValue</code> بضبطه على القيمة <code>4321</code> لنُظهر كيف يمكن إسناد قيمة لنوع مرجعي مثل <code>int*</code>. تجدر الإشارة إلى أنه في لغة جو، يمكننا إنشاء مراجع لأنواع <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1791/" rel="">البيانات الأولية primitive types</a> فقط، مثل <code>int</code> و <code>string</code> باستخدام المتغيرات. إذًا، لإسناد قيمة إلى الحقل <code>NullIntValue</code>، نُسند أولًا قيمةً إلى متغير آخر <code>otherInt</code>، ثم نحصل على مرجع منه <code>otherInt&amp;</code> (بدلًا من كتابة <code>4321&amp;</code> مباشرةً).
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">json data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullStringValue":null,"nullIntValue":4321}
</pre>

<p>
	نلاحظ أن هذا الناتج هو نفسه عندما استخدمنا <code>{}map[string]interface</code>، باستثناء أن قيمة <code>nullIntValue</code> هذه المرة هي <code>4321</code> لأن هذه هي قيمة <code>otherInt</code>.
</p>

<p>
	يستغرق الأمر في البداية بعض الوقت لتعريف البنية <code>struct</code> وإعداد حقولها، ولكن يمكننا بعد ذلك استخدامها مرارًا وتكرارًا في الشيفرة، وستكون النتيجة هي نفسها بغض النظر عن مكان استخدامها، كما يمكننا تعديلها من مكان واحد بدلًا من تعديلها في كل مكان تتواجد نسخة منها فيه كما في حالة الروابط <code>map</code>.
</p>

<p>
	تتيح لنا الدالة <code>json.Marshal</code> إمكانية تحديد الحقول التي نُريد تضمينها في جسون، في حال كانت قيمة تلك الحقول صفريّة (أي Null وهذا يُكافئ 0 في حالة <code>int</code> و <code>false</code> في حالة <code>bool</code> والسلسلة الفارغة في حالة <code>string</code> ..إلخ). قد يكون لدينا أحيانًا كائن جسون كبير أو حقول اختيارية لا نريد تضمينها دائمًا في بيانات جسون، لذا يكون تجاهل هذه الحقول مفيدًا. يكون التحكم في تجاهل هذه الحقول -عندما تكون قيمها صفريّة أو غير صفريّة- باستخدام الخيار <code>omitempty</code> ضمن وسم بنية <code>json</code>.
</p>

<p>
	لنُحدّث البرنامج السابق لإضافة الخيار <code>omitempty</code> إلى حقل <code>NullStringValue</code> وإضافة حقل جديد يسمى <code>EmptyString</code> مع نفس الخيار:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_21" style=""><span class="pun">...</span><span class="pln">

type myJSON </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">

    </span><span class="typ">NullStringValue</span><span class="pln"> </span><span class="pun">*</span><span class="pln">string   </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"nullStringValue,omitempty"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">NullIntValue</span><span class="pln">    </span><span class="pun">*</span><span class="typ">int</span><span class="pln">      </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"nullIntValue"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">EmptyString</span><span class="pln">     string    </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"emptyString,omitempty"</span><span class="pun">`</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	بعد تحويل البنية <code>myJSON</code> إلى بيانات جسون، سنلاحظ أن الحقل <code>EmptyString</code> والحقل <code>NullStringValue</code> غير موجودان في بيانات جسون، لأن قيمهما صفريّة.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">json data: {"intValue":1234,"boolValue":true,"stringValue":"hello!","dateValue":"2022-03-02T09:10:00Z","objectValue":{"arrayValue":[1,2,3,4]},"nullIntValue":4321}
</pre>

<p>
	نلاحظ أن الحقل <code>nullStringValue</code> لم يعد موجودًا في الخرج، لأنه يُعد حقلًا بقيمة <code>nil</code>، بالتالي فإن الخيار <code>omitempty</code> استبعده من الخرج. نفس الأمر بالنسبة للحقل <code>emptyString</code>، لأن قيمته صفريّة (القيمة الصفريّة تُكافئ <code>nil</code> كما سبق وذكرنا).
</p>

<p>
	استخدمنا في هذا القسم أسلوب البُنى <code>struct</code> بدلًا من الروابط، لتمثيل كائن جسون في برنامجنا، ثم حولناها إلى بيانات جسون باستخدام الدالة <code>json.Marshal</code>. تعلمنا أيضًا كيفية تجاهل القيم الصفريّة من بيانات جسون.
</p>

<p>
	هذه البيانات (بعد تحويلها لصيغة جسون) قد تُرسل إلى برامج أخرى، وبالتالي إذا كان البرنامج الآخر مكتوب بلغة جو، يجب أن نعرف كيف يمكننا قراءة هذا النوع من البيانات. يمكنك أن تتخيل الأمر على أنه برنامجي جو A و B أحدهما مُخدم والآخر عميل. يُرسل A طلبًا إلى B، فيعالجه ويرسله بصيغة جسون إلى A، ثم يقرأ A هذه البيانات. لأجل ذلك توفر الحزمة <code>encoding/json</code> طريقةً لفك ترميز بيانات جسون وتحويلها إلى أنواع جو المقابلة (مجرد عملية عكسية). سنتعلم في القسم التالي كيفية قراءة بيانات جسون وتحويلها إلى روابط.
</p>

<h2 id="-2">
	تحليل بيانات جسون باستخدام الروابط
</h2>

<p>
	بطريقة مشابهة لما فعلناه عندما استخدمنا <code>{}map[string]interface</code> مثل طريقة مرنة لتوليد بيانات جسون، يمكننا استخدامها أيضًا مثل طريقة مرنة لقراءة بيانات جسون. تعمل الدالة <code>json.Unmarshal</code> بطريقة معاكسة للدالة <code>json.Marshal</code>، إذ تأخذ بيانات جسون وتحولها إلى بيانات جو، وتأخذ أيضًا متغيرًا لوضع البيانات التي جرى فك تنظيمها فيه، وتعيد إما خطأ <code>error</code> في حال فشل عملية التحليل أو <code>nil</code> في حال نجحت.
</p>

<p>
	سنعدّل برنامجنا في هذا القسم، بحيث نستخدم الدالة <code>json.Unmarshal</code> لقراءة بيانات جسون من سلسلة وتخزينها في متغير من النوع <code>map</code>، وطباعة الخرج على الشاشة. لنعدّل البرنامج إذًا، بحيث نفك تنظيم البيانات باستخدام الدالة السابقة ونحولها إلى رابط <code>{}map[string]interface</code>. لنبدأ باستبدال المتغير <code>data</code> الأصلي بمتغير <code>jsonData</code> يحتوي على سلسلة جسون، ثم نُصرّح عن متغير <code>data</code> جديد على أنه <code>{}map[string]interfac</code> لتلقي بيانات جسون، ثم نستخدم الدالة <code>json.Unmarshal</code> مع هذه المتغيرات للوصول إلى بيانات جسون:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_23" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    jsonData </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="str">"intValue"</span><span class="pun">:</span><span class="lit">1234</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"boolValue"</span><span class="pun">:</span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"stringValue"</span><span class="pun">:</span><span class="str">"hello!"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"dateValue"</span><span class="pun">:</span><span class="str">"2022-03-02T09:10:00Z"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"objectValue"</span><span class="pun">:{</span><span class="pln">
                </span><span class="str">"arrayValue"</span><span class="pun">:[</span><span class="lit">1</span><span class="pun">,</span><span class="lit">2</span><span class="pun">,</span><span class="lit">3</span><span class="pun">,</span><span class="lit">4</span><span class="pun">]</span><span class="pln">
            </span><span class="pun">},</span><span class="pln">
            </span><span class="str">"nullStringValue"</span><span class="pun">:</span><span class="pln">null</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"nullIntValue"</span><span class="pun">:</span><span class="pln">null
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">`</span><span class="pln">

    var data </span><span class="typ">map</span><span class="pun">[</span><span class="pln">string</span><span class="pun">]</span><span class="pln">interface</span><span class="pun">{}</span><span class="pln">
    err </span><span class="pun">:=</span><span class="pln"> json</span><span class="pun">.</span><span class="typ">Unmarshal</span><span class="pun">([]</span><span class="pln">byte</span><span class="pun">(</span><span class="pln">jsonData</span><span class="pun">),</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"could not unmarshal json: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"json map: %v\n"</span><span class="pun">,</span><span class="pln"> data</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">  </span></pre>

<p>
	جرى إسناد قيمة المتغير <code>jsonData</code> في الشيفرة أعلاه من خلال <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1792/" rel="">سلسلة نصية أولية</a>، وذلك للسماح بكتابة البيانات ضمن أسطر متعددة لتسهيل القراءة. بعد التصريح عن المتغير <code>data</code> على أنه متغير من النوع <code>{}map[string]interface</code>، نمرر <code>jsonData</code> والمتغير <code>data</code> إلى الدالة <code>json.Unmarshal</code> لفك تنظيم بيانات جسون وتخزين النتيجة في <code>data</code>. يُمرَّر المتغير <code>jsonData</code> إلى دالة فك التنظيم على شكل مصفوفة بايت <code>byte[]</code>، لأن الدالة تتطلب النوع <code>byte[]</code>، والمتغير <code>jsonData</code> عُرّف على أنه قيمة من نوع سلسلة نصية <code>string</code>. طبعًا هذا الأمر ينجح، لأنه في لغة جو، يمكن تحويل <code>string</code> إلى <code>byte[]</code> والعكس. بالنسبة للمتغير <code>data</code>، ينبغي تمريره مثل مرجع، لأن الدالة تتطلب معرفة موقع المتغير في الذاكرة. أخيرًا، يجري فك تنظيم البيانات وتخزين النتيجة في المتغير <code>data</code>، لنطبع النتيجة بعدها باستخدام دالة <code>fmt.Printf</code>.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">json map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:&lt;nil&gt; nullStringValue:&lt;nil&gt; objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!]
</pre>

<p>
	يُظهر الخرج نتيجة تحويل بيانات جسون إلى رابط. نلاحظ أن جميع الحقول من بيانات جسون موجودة، بما في ذلك القيم الصفريّة <code>null</code>. بما أن بيانات جو الآن مُخزنة في قيمة من النوع <code>{}map[string]interface</code>، سيكون لدينا القليل من العمل مع بياناتها؛ إذ نحتاج إلى الحصول على القيمة من الرابط باستخدام قيمة مفتاح <code>string</code> معينة، وذلك للتأكد من أن القيمة التي تلقيناها هي القيمة التي نتوقعها، لأن القيمة المعادة هي قيمة من النوع <code>{}interface</code>.
</p>

<p>
	لنفتح ملف "main.go" ونُحدّث البرنامج لقراءة حقل <code>dateValue</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_25" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"json map: %v\n"</span><span class="pun">,</span><span class="pln"> data</span><span class="pun">)</span><span class="pln">

    rawDateValue</span><span class="pun">,</span><span class="pln"> ok </span><span class="pun">:=</span><span class="pln"> data</span><span class="pun">[</span><span class="str">"dateValue"</span><span class="pun">]</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">!</span><span class="pln">ok </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"dateValue does not exist\n"</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    dateValue</span><span class="pun">,</span><span class="pln"> ok </span><span class="pun">:=</span><span class="pln"> rawDateValue</span><span class="pun">.(</span><span class="pln">string</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">!</span><span class="pln">ok </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"dateValue is not a string\n"</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"date value: %s\n"</span><span class="pun">,</span><span class="pln"> dateValue</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	استخدمنا في الشيفرة أعلاه المفتاح <code>dateValue</code> لاستخراج قيمة من الرابط بكتابة <code>["data["dateValue</code>، وخزّنا النتيجة في <code>rawDateValue</code> ليكون قيمةً من النوع <code>{}interface</code>، واستخدمنا المتغير <code>ok</code> للتأكد من أن الحقل ذو المفتاح <code>dateValue</code> موجود ضمن الرابط. استخدمنا بعدها توكيد النوع type assertion، للتأكد من أن <code>rawDateValue</code> هو قيمة <code>string</code>، وأسندناه إلى المتغير <code>dateValue</code>. استخدمنا بعدها المتغير <code>ok</code> للتأكد من نجاح عملية التوكيد. أخيرًا، طبعنا <code>dateValue</code> باستخدام دالة الطباعة <code>fmt.Printf</code>.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">json map: map[boolValue:true dateValue:2022-03-02T09:10:00Z intValue:1234 nullIntValue:&lt;nil&gt; nullStringValue:&lt;nil&gt; objectValue:map[arrayValue:[1 2 3 4]] stringValue:hello!]
date value: 2022-03-02T09:10:00Z
</pre>

<p>
	يمكننا أن نلاحظ في السطر الأخير من الخرج استخراج قيمة الحقل <code>dateValue</code> من الرابط <code>map</code> وتغير نوعها إلى <code>string</code>.
</p>

<p>
	استخدمنا في هذا القسم الدالة <code>json.Unmarshal</code> لفك تنظيم unmarshal بيانات جسون وتحويلها إلى بيانات في برنامج جو بالاستعانة بمتغير من النوع <code>{}map[string]interface</code>. بعد ذلك استخرجنا قيمة الحقل <code>dateValue</code> من الرابط الذي وضعنا فيه بيانات جسون وطبعناها على الشاشة.
</p>

<p>
	الجانب السيء في استخدام النوع <code>{}map[string]interface</code> في عملية فك التنظيم، هو أن مُفسّر اللغة لا يعرف أنواع الحقول التي جرى فك تنظيمها؛ فكل ما يعرفه أنها من النوع <code>{}interface</code>، وبالتالي لا يمكنه أن يفعل شيئًا أكثر من تخمين الأنواع. بالتالي لن يجري فك تنظيم أنواع البيانات المعقدة، مثل <code>time.Time</code> في الحقل <code>dateValue</code> إلى بيانات من النوع <code>time.Time</code>، وإنما تُفسّر على أنها <code>string</code>. تحدث مشكلة مماثلة إذا حاولنا الوصول إلى أي قيمة رقمية number في الرابط بهذه الطريقة، لأن الدالة <code>json.Unmarshal</code> لا تعرف ما إذا كان الرقم من النوع <code>int</code> أو <code>float</code> أو <code>int64</code> ..إلخ. لذا يكون التخمين الأفضل هو عدّ الرقم من النوع <code>float64</code> لأنه النوع الأكثر مرونة.
</p>

<p>
	نستنتج مما سبق جانب إيجابي للروابط وهو مرونة استخدامها، وجانب سيئ يتجلى بالمشكلات السابقة. هنا تأتي ميزة استخدام البنى لحل المشكلات السابقة. بطريقة مشابهة لآلية تنظيم البيانات من بنية <code>struct</code> باستخدام الدالة <code>json.Marshal</code> لإنتاج بيانات جسون، يمكن إجراء عملية معاكسة باستخدام <code>json.Unmarshal</code> أيضًا كما سبق وفعلنا مع الروابط. يمكننا باستخدام البنى الاستغناء عن تعقيدات توكيد النوع التي عانينا منها مع الروابط، من خلال تعريف أنواع البيانات في حقول البنية لتحديد أنواع بيانات جسون التي يجري فك تنظيمها. هذا ما سنتحدث عنه في القسم التالي.
</p>

<h2 id="-3">
	تحليل بيانات جسون باستخدام البنى
</h2>

<p>
	عند قراءة بيانات جسون، هناك فرصة جيدة لمعرفة أنواع البيانات التي نتلقاها من خلال استخدام البُنى؛ فمن خلال استخدام البُنى، يمكننا منح مُفسّر اللغة تلميحات تُساعده في تحديد شكل ونوع البيانات التي يتوقعها. عرّفنا في المثال السابق البنيتين <code>myJSON</code> و <code>myObject</code> وأضفنا وسوم <code>json</code> لتحديد أسماء الحقول بعد تحويلها إلى جسون. يمكننا الآن استخدام قيم البنية <code>struct</code> نفسها لفك ترميز سلسلة جسون المُستخدمة، وهذا ما قد يكون مفيدًا لتقليل التعليمات البرمجية المكررة في البرنامج عند تنظم أو فك تنظيم بيانات جسون نفسها. فائدة أخرى لاستخدام بنية في فك تنظيم بيانات جسون هي إمكانية إخبار المُفسّر بنوع بيانات كل حقل، وهناك فائدة أخرى تأتي من استخدام مُفسّر اللغة للتحقق من استخدام الأسماء الصحيحة للحقول، وبالتالي تجنب أخطاء قد تحدث في أسماء الحقول (من النوع <code>string</code>) عند استخدام الروابط.
</p>

<p>
	لنفتح ملف "main.go"، ونعدّل تصريح المتغير <code>data</code> لاستخدام مرجع للبنية <code>myJSON</code> ونضيف بعض تعليمات الطباعة <code>fmt.Printf</code> لإظهار بيانات الحقول المختلفة في <code>myJSON</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_27" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">

    var data </span><span class="pun">*</span><span class="pln">myJSON
    err </span><span class="pun">:=</span><span class="pln"> json</span><span class="pun">.</span><span class="typ">Unmarshal</span><span class="pun">([]</span><span class="pln">byte</span><span class="pun">(</span><span class="pln">jsonData</span><span class="pun">),</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">data</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"could not unmarshal json: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"json struct: %#v\n"</span><span class="pun">,</span><span class="pln"> data</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"dateValue: %#v\n"</span><span class="pun">,</span><span class="pln"> data</span><span class="pun">.</span><span class="typ">DateValue</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"objectValue: %#v\n"</span><span class="pun">,</span><span class="pln"> data</span><span class="pun">.</span><span class="typ">ObjectValue</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نظرًا لأننا عرّفنا سابقًا أنواع البُنى، فلن نحتاج إلا إلى تحديث نوع الحقل <code>data</code> لدعم عملية فك التنظيم في بنية. تُظهر بقية التحديثات بعض البيانات الموجودة في البنية نفسها. لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ويظهر لدينا الخرج التالي:
</p>

<pre class="ipsCode">json struct: &amp;main.myJSON{IntValue:1234, BoolValue:true, StringValue:"hello!", DateValue:time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC), ObjectValue:(*main.myObject)(0x1400011c180), NullStringValue:(*string)(nil), NullIntValue:(*int)(nil), EmptyString:""}
dateValue: time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC)
objectValue: &amp;main.myObject{ArrayValue:[]int{1, 2, 3, 4}}
</pre>

<p>
	هناك شيئان يجب أن نشير لهما في الخرج السابق، إذ نلاحظ أولًا في سطر <code>json struct</code> وسطر <code>dateValue</code>، أن قيمة التاريخ والوقت من بيانات جسون جرى تحويلها إلى قيمة من النوع <code>time.Time</code> (يظهر النوع <code>time.Date</code> عند استخدام العنصر النائب <code>v#%</code>). بما أن مُفسّر جو كان قادرًا على التعرّف على النوع time.Time في حقل DateValue، فهو قادر أيضًا على تحليل قيم النوع string.
</p>

<p>
	الشيء الثاني الذي نلاحظه هو أن <code>EmptyString</code> يظهر على سطر <code>json struct</code> على الرغم من أنه لم يُضمّن في بيانات جسون الأصلية. إذا جرى تضمين حقل في بنية مُستخدمة في عملية فك تنظيم بيانات جسون، وكان هذا الحقل غير موجود في بيانات جسون الأصلية، فإنه هذا الحقل يُضبط بالقيمة الافتراضية لنوعه ويجري تجاهله. يمكننا بهذه الطريقة تعريف جميع الحقول المحتملة التي قد تحتوي عليها بيانات جسون بأمان، دون القلق بشأن حدوث خطأ إذا لم يكن الحقل موجودًا في أي من جانبي العملية.
</p>

<p>
	ضُبط كل من الحقلين <code>NullStringValue</code> و <code>NullIntValue</code> على قيمتهما الافتراضية <code>nil</code>، لأن بيانات جسون تقول أن قيمهما <code>null</code>، لكن حتى لو لم يكونا ضمن بيانات جسون، سيأخذان نفس القيمة. على غرار الطريقة التي تجاهلت بها الدالة <code>json.Unmarshal</code> حقل <code>EmptyString</code> في البنية <code>struct</code> عندما كان حقل <code>emptyString</code> مفقودًا من بيانات جسون، فإن العكس هو الصحيح أيضًا؛ فإذا كان هناك حقل في بيانات جسون ليس له ما يقابله في البنية <code>struct</code>، سيتجاهل مفسّر اللغة هذا الحقل، وينتقل إلى الحقل التالي لتحليله. بالتالي إذا كانت بيانات جسون التي نقرأها كبيرة جدًا وكان البرنامج يحتاج عددًا صغيرًا من تلك الحقول، يمكننا إنشاء بنية تتضمن الحقول التي نحتاجها فقط، إذ يتجاهل مُفسّر اللغة أية حقول من بيانات جسون غير موجودة في البنية.
</p>

<p>
	لنفتح ملف "main.go" ونعدّل <code>jsonData</code> لتضمين حقل غير موجود في <code>myJSON</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8046_29" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    jsonData </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">
        </span><span class="pun">{</span><span class="pln">
            </span><span class="str">"intValue"</span><span class="pun">:</span><span class="lit">1234</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"boolValue"</span><span class="pun">:</span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"stringValue"</span><span class="pun">:</span><span class="str">"hello!"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"dateValue"</span><span class="pun">:</span><span class="str">"2022-03-02T09:10:00Z"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"objectValue"</span><span class="pun">:{</span><span class="pln">
                </span><span class="str">"arrayValue"</span><span class="pun">:[</span><span class="lit">1</span><span class="pun">,</span><span class="lit">2</span><span class="pun">,</span><span class="lit">3</span><span class="pun">,</span><span class="lit">4</span><span class="pun">]</span><span class="pln">
            </span><span class="pun">},</span><span class="pln">
            </span><span class="str">"nullStringValue"</span><span class="pun">:</span><span class="pln">null</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"nullIntValue"</span><span class="pun">:</span><span class="pln">null</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"extraValue"</span><span class="pun">:</span><span class="lit">4321</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">`</span><span class="pln">

    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">json struct: &amp;main.myJSON{IntValue:1234, BoolValue:true, StringValue:"hello!", DateValue:time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC), ObjectValue:(*main.myObject)(0x14000126180), NullStringValue:(*string)(nil), NullIntValue:(*int)(nil), EmptyString:""}
dateValue: time.Date(2022, time.March, 2, 9, 10, 0, 0, time.UTC)
objectValue: &amp;main.myObject{ArrayValue:[]int{1, 2, 3, 4}}
</pre>

<p>
	نلاحظ عدم ظهور الحقل الجديد <code>extraValue</code> الذي أضفناه إلى بيانات جسون ضمن الخرج، إذ تجاهله مُفسّر اللغة، لأنّه غير موجود ضمن البنية <code>myJSON</code>.
</p>

<p>
	استخدمنا في هذا المقال أنواع البُنى <code>struct</code> المُعرّفة مسبقًا في عملية فك تنظيم بيانات جسون. رأينا كيف أن ذلك يُمكّن مُفسّر اللغة من تحليل قيم الأنواع المعقدة مثل <code>time.Time</code>، ويسمح بتجاهل الحقل <code>EmptyString</code> الموجود ضمن البنية وغير موجود ضمن بيانات جسون. رأينا أيضًا كيف يمكن التحكم في الحقول التي نستخرجها من بيانات جسون وتحديد ما نريده من حقول بدقة.
</p>

<h2 id="-4">
	الخاتمة
</h2>

<p>
	أنشأنا في هذا المقال برنامجًا يستخدم الحزمة <code>encoding/json</code> من مكتبة لغة جو القياسية. استخدمنا بدايةً الدالة <code>json.Marshal</code> مع النوع <code>{}map[string]interface</code> لإنشاء بيانات جسون بطريقة مرنة. عدّلنا بعد ذلك البرنامج لاستخدام النوع <code>struct</code> مع وسوم <code>json</code> لإنشاء بيانات جسون بطريقة متسقة وموثوقة باستخدام الدالة <code>json.Marshal</code>. استخدمنا بعد ذلك الدالة <code>json.Unmarshal</code> مع النوع <code>{}map[string]interface</code> لفك ترميز سلسلة جسون وتحويلها إلى بيانات يمكن التعامل معها في برنامج جو. أخيرًا، استخدمنا نوع بنية <code>struct</code> مُعرّف مسبقًا في عملية فك تنظيم بيانات جسون باستخدام دالة <code>json.Unmarshal</code> للسماح لمُفسّر اللغة بإجراء التحليل واستنتاج أنواع البيانات وفقًا لحقول البنية التي عرّفناها.
</p>

<p>
	يمكننا من خلال الحزمة <code>encoding/json</code> التفاعل مع العديد من <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">واجهات برمجة التطبيقات APIs</a> المتاحة على الإنترنت لإنشاء عمليات متكاملة مع مواقع الويب الأخرى. يمكننا أيضًا تحويل بيانات جو في برامجنا إلى تنسيق يمكن حفظه ثم تحميله لاحقًا للمتابعة من حيث توقف البرنامج (لأن عملية السلسلة Serialization تحفظ البيانات قيد التشغيل في الذاكرة). تتضمن الحزمة <code>encoding/json</code> أيضًا دوالًا أخرى مُفيدة للتعامل مع بيانات جسون، مثل الدالة <code>json.MarshalIndent</code> التي تساعدنا في عرض بيانات جسون بطريقة مُرتبة وأكثر وضوحًا للاطلاع عليها ومساعدتنا في استكشاف الأخطاء وإصلاحها.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-json-in-go" rel="external nofollow">How To Use JSON in Go</a> لصاحبه Kristin Davidson.
</p>

<h2 id="-5">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%B3%D9%8A%D8%A7%D9%82%D8%A7%D8%AA-contexts-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2164/" rel="">استخدام السياقات Contexts في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%B3%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%86%D9%8A%D8%A9-struct-tags-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1998/" rel="">استخدام وسوم البنية Struct Tags في لغة جو</a>.
	</li>
</ul>
]]></description><guid isPermaLink="false">2165</guid><pubDate>Sun, 19 Nov 2023 13:02:01 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x633;&#x64A;&#x627;&#x642;&#x627;&#x62A; Contexts &#x641;&#x64A; &#x644;&#x63A;&#x629; Go</title><link>https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%B3%D9%8A%D8%A7%D9%82%D8%A7%D8%AA-contexts-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2164/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_11/--Contexts----Go.png.716c6aaf1cddb256753f8608ee87f681.png" /></p>
<p>
	عند تطوير التطبيقات الكبيرة، وخصوصًا برمجيات الخادم - يكون من المفيد أحيانًا لدالةٍ ما معرفة بعض المعلومات عن البيئة التي تُنفّذ بها إلى جانب المعلومات اللازمة لعمل الدالة نفسها. لنأخذ مثلًا دالة خادم ويب تتعامل مع <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">طلب HTTP</a> لعميل معين، هنا قد تحتاج الدالة إلى معرفة عنوان URL الذي يطلبه العميل فقط لتحقيق الاستجابة، وفي هذه الحالة ربما تحتاج فقط إلى تمرير العنوان مثل معامل إلى الدالة. المشكلة أن هناك بعض الأشياء المفاجئة التي يمكن أن تحدث مثل انقطاع الاتصال مع العميل قبل تحقيق الاستجابة وتلقيه الرد. بالتالي، إذا كانت الدالة التي تؤدي الاستجابة لا تعرف أن العميل غير متصل، لن يصل الرد والعمليات التي يجريها الخادم ستكون مجرد هدر للموارد الحاسوبية على استجابة لن تُستخدم. لتفادي هكذا حالات يجب أن يكون بمقدور الخادم معرفة سياق الطلب (مثل حالة اتصال العميل)، وبالتالي إمكانية إيقاف معالجة الطلب بمجرد انقطاع الاتصال مع العميل. هذا من شأنه الحفاظ على الموارد الحاسوبية ويحد من الهدر ويتيح للخادم التحرر أكثر من الضغط وتقديم أداء أفضل. تظهر فائدة هذا النوع من المعلومات أكثر في الحالات التي تتطلّب فيها الدوال وقتًا طويلًا نسبيًا في التنفيذ، مثل إجراء استدعاءات قاعدة البيانات. لمعالجة هذه القضايا ومنح إمكانية الوصول الكامل لمثل هذه المعلومات تُقدم <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a> حزمة السياق <code>context</code> في المكتبة القياسية.
</p>

<p>
	سنُنشئ خلال هذا المقال برنامجًا يستخدم سياقًا داخل دالة. سنعدّل بعدها البرنامج لتخزين بيانات إضافية في السياق واستردادها من دالة أخرى. بعد ذلك سنستفيد من فكرة السياق لإرسال إشارة للدالة التي تُجري عملية المعالجة، لتوقف تنفيذ أي عمليات معالجة مُتبقية.
</p>

<h2 id="">
	المتطلبات
</h2>

<ul>
	<li>
		إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a> لإعداده.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
	<li>
		فهم لخيوط معالجة جو goroutines والقنوات channels. يمكنك الاطلاع على مقالة <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%B9%D8%AF%D8%A9-%D8%AF%D9%88%D8%A7%D9%84-%D8%B9%D8%A8%D8%B1-%D9%85%D9%8A%D8%B2%D8%A9-%D8%A7%D9%84%D8%AA%D8%B3%D8%A7%D9%8A%D8%B1-concurrency-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2152/" rel="">كيفية تشغيل عدة دوال على التساير في لغة جو Go</a>.
	</li>
	<li>
		معرفة بكيفية التعامل مع التاريخ والوقت في لغة جو. يمكنك الاطلاع على مقالة <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%A7%D8%B1%D9%8A%D8%AE-%D9%88%D8%A7%D9%84%D9%88%D9%82%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2163/" rel="">استخدام التاريخ والوقت في لغة جو Go</a>.
	</li>
	<li>
		معرفة كيفية التعامل مع تعليمة <code>switch</code> في لغة جو. يمكنك الاطلاع على مقالة <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%A8%D8%AF%D9%8A%D9%84-switch-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1949/" rel="">التعامل مع التعليمة Switch في لغة جو Go</a>.
	</li>
</ul>

<h2 id="context">
	إنشاء سياق context
</h2>

<p>
	تستخدم العديد من الدوال في لغة جو حزمة <code>context</code> لجمع معلومات إضافية حول البيئة التي تُنفّذ فيها، وعادةً ما يُقدّم هذا السياق للدوال التي تستدعيها أيضًا. ستتمكن البرامج من خلال واجهة <code>context.Context</code> التي توفرها حزمة السياق، وتمريرها من من دالة إلى أخرى، من نقل معلومات السياق من أعلى نقطة تنفيذ في البرنامج (دالة <code>main</code>) إلى أعمق نقطة تنفيذ في البرنامج (دالة ضمن دالة أخرى أو ربما أعمق). مثلًا، تُقدّم الدالة <a href="https://pkg.go.dev/net/http#Request.Context" rel="external nofollow"><code>Context</code></a> من النوع <code>http.Request</code> سياقًا <code>context.Context</code> يتضمن معلومات عن العميل الذي أرسل الطلب، ويُحذف أو ينتهي هذا السياق في حالة قُطع الاتصال مع العميل، حتى لو لم يكن الطلب قد انتهت معالجته. بالتالي، إذا كانت هناك دالة ما تستدعي الدالة <a href="https://pkg.go.dev/database/sql#DB.QueryContext" rel="external nofollow"><code>QueryContext</code></a> التابعة إلى <code>sql.DB</code>، وكان قد مُرر لهذه الدالة قيمة <code>context.Context</code>، وقُطع الاتصال مع العميل، سيتوقف تنفيذ الاستعلام مباشرةً في حالة لم يكن قد انتهى من التنفيذ.
</p>

<p>
	سننشئ في هذا القسم برنامجًا يتضمن دالة تتلقى سياقًا مثل معامل، ونستدعي هذه الدالة باستخدام سياق فارغ نُنشئه باستخدام الدالتين <code>context.TODO</code> و <code>Context.Background</code>. كما هو معتاد، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه، ويمكن وضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه:
</p>

<pre class="ipsCode">$ mkdir projects
$ cd projects
</pre>

<p>
	الآن من داخل هذا المجلد، سنشغّل الأمر <code>mkdir</code> لإنشاء مجلد "contexts" ثم سنستخدم <code>cd</code> للانتقال إليه:
</p>

<pre class="ipsCode">$ mkdir contexts
$ cd contexts
</pre>

<p>
	يمكننا الآن فتح ملف "main.go" باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">محرر نانو nano أو أي محرر آخر تريده</a>:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	سنُنشئ الآن دالة <code>doSomething</code> داخل ملف "main.go". تقبل هذه الدالة <code>context.Context</code> مثل معامل، ثم نضيف دالة <code>main</code> التي تُنشئ سياقًا وتستدعي <code>doSomething</code> باستخدام ذلك السياق. نضيف ما يلي داخل ملف <code>main.go</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6097_8" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"context"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func doSomething</span><span class="pun">(</span><span class="pln">ctx context</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Doing something!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ctx </span><span class="pun">:=</span><span class="pln"> context</span><span class="pun">.</span><span class="pln">TODO</span><span class="pun">()</span><span class="pln">
    doSomething</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	استخدمنا الدالة <code>context.TODO</code> داخل الدالة <code>main</code>، لإنشاء سياق فارغ ابتدائي. يمكننا استخدام السياق الفارغ مثل موضع مؤقت placeholder عندما لا نكون متأكدين من السياق الذي يجب استخدامه. لدينا أيضًا الدالة <code>doSomething</code> التي تقبل معاملًا وحيدًا هو <code>context.Context</code> -له الاسم <code>ctx</code>- وهو الاسم الشائع له، ويُفضل أن يكون أول معامل في الدالة في حال كان هناك معاملات أخرى، لكن الدالة لا تستخدمه الآن فعليًّا.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Doing something!
</pre>

<p>
	نلاحظ أن الخرج الذي أظهرته الدالة <code>fmt.Println</code> هو <code>!Doing something</code> نتيجةً لاستدعاء الدالة <code>doSomething</code>. لنعدّل البرنامج الآن ونستخدم الدالة <code>context.Background</code> التي تُنشئ سياقًا فارغًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6097_10" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ctx </span><span class="pun">:=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">Background</span><span class="pun">()</span><span class="pln">
    doSomething</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تنشئ الدالة <code>context.Background</code> سياقًا فارغًا مثل <code>context.TODO</code>، ومن حيث المبدأ تؤدي كلتا الدالتين السابقتين نفس الغرض. الفرق الوحيد هو أن <code>context.TODO</code> تُستخدم عندما تُريد أن تُخبر المطورين الآخرين أن هذا السياق هو مجرد سياق مبدأي وغالبًا يجب تعديله، أما <code>context.Background</code> تتُستخدم عندما لا نحتاج إلى هكذا إشارة إلى المطورين الآخرين، أي نحتاج ببساطة إلى سياق فارغ لا أكثر ولا أقل، وفي حال لم تكن متأكدًا أيهما تستخدم، ستكون الدالة <code>context.Background</code> خيارًا افتراضيًا.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج:
</p>

<pre class="ipsCode">Doing something!
</pre>

<p>
	سيكون الخرج طبعًا نفسه كما في المرة السابقة، وبذلك نكون قد تعلمنا كيفية إنشاء سياق فارغ بطريقتين مختلفتين.
</p>

<p>
	لا يفيدنا السياق الفارغ تمامًا إذا بقي على هذا النحو، فعلى الرغم من قدرتنا على تمريره بين الدوال، إلا أنه لا يوفر أية معلومات. لجعل السياق مفيدًا نُضيف له بيانات يمكن للدوال الأخرى استردادها والاطلاع عليها.
</p>

<h2 id="-1">
	إضافة معلومات إلى السياق
</h2>

<p>
	إحدى فوائد استخدام <code>context.Context</code> في برنامجٍ ما هي القدرة على الوصول إلى البيانات المخزنة داخل سياق ما، إذ يمكن لكل طبقة من البرنامج إضافة معلومات إضافية حول ما يحدث من خلال إضافة البيانات إلى سياق وتمرير السياق من دالة إلى أخرى. مثلًا، قد تضيف الدالة الأولى اسم مستخدم إلى السياق، والدالة التالية مسار الملف إلى المحتوى الذي يحاول المستخدم الوصول إليه، والدالة الثالثة تقرأ الملف من قرص النظام وتسجل ما إذا كان قد نجح تحميله أم لا، إضافةً إلى المستخدم الذي حاول تحميله.
</p>

<p>
	يمكن استخدم الدالة <code>Context.WithValue</code> من حزمة السياق لإضافة قيمة جديدة إلى السياق. تقبل الدالة ثلاث معاملات: السياق الأب (الأصلي) <code>context.Context</code> والمفتاح والقيمة. السياق الأب هو السياق الذي يجب إضافة القيمة إليه مع الاحتفاظ بجميع المعلومات الأخرى المتعلقة بالسياق الأصلي. يُستخدم المفتاح لاسترداد القيمة من السياق. يمكن أن يكون المفتاح والقيمة من أي <a href="https://academy.hsoub.com/programming/general/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1726/" rel="">نوع بيانات</a>، وفي هذا المقال سيكونان من نوع سلسلة نصية <code>string</code>.
</p>

<p>
	تعيد الدالة <code>Context.WithValue</code> قيمةً من النوع <code>context.Context</code> تتضمن السياق الأب مع المعلومات المُضافة. يمكن الحصول على القيمة التي يُخزنها السياق <code>context.Context</code> من خلال استخدام التابع <code>Value</code> مع المفتاح.
</p>

<p>
	لنفتح الآن ملف "main.go" ولنضِف قيمة إلى السياق باستخدام الدالة السابقة، ثم نحدِّث دالة <code>doSomething</code>، بحيث تطبع تلك القيمة باستخدام دالة <code>fmt.Printf</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6097_12" style=""><span class="pun">...</span><span class="pln">

func doSomething</span><span class="pun">(</span><span class="pln">ctx context</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"doSomething: myKey's value is %s\n"</span><span class="pun">,</span><span class="pln"> ctx</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">(</span><span class="str">"myKey"</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ctx </span><span class="pun">:=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">Background</span><span class="pun">()</span><span class="pln">

    ctx </span><span class="pun">=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">WithValue</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">,</span><span class="pln"> </span><span class="str">"myKey"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"myValue"</span><span class="pun">)</span><span class="pln">

    doSomething</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أسندنا في الشيفرة السابقة سياقًا جديدًا إلى المتغير <code>ctx</code>، الذي يُستخدم للاحتفاظ بالسياق الأب، ويُعد هذا نمط شائع الاستخدام في حال لم يكن هناك سبب للإشارة إلى سياق أب محدد. إذا كنا بحاجة إلى الوصول إلى السياق الأب في وقتٍ ما، يمكننا إسناد القيمة إلى متغير جديد، كما سنرى قريبًا.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">doSomething: myKey's value is myValue
</pre>

<p>
	يمكننا رؤية القيمة <code>myValue</code> التي خزّناها في السياق ضمن الخرج السابق، وذلك نتيجةً لاستدعاء الدالة <code>doSomething</code> ضمن الدالة <code>main</code>. طبعًا القيمة <code>myValue</code> المُستخدمة هنا هي فقط من أجل التوضيح، فمثلًا لو كنا نعمل على خادم كان من الممكن أن تكون هذه القيمة شيئًا آخر مثل وقت بدء تشغيل البرنامج.
</p>

<p>
	عند استخدام السياقات من المهم معرفة أن القيم المخزنة في سياقٍ <code>context.Context</code> ما تكون ثابتة immutable. بالتالي عندما استدعينا الدالة <code>context.WithValue</code> ومررنا لها السياق الأب، حصلنا على سياق جديد وليس السياق الأب أو نسخة منه، وإنما حصلنا على سياق جديد يضم المعلومات الجديدة إضافةً إلى سياق الأب مُغلّفًا wrapped ضمنه.
</p>

<p>
	لنفتح الآن ملف "main.go" لإضافة دالة جديدة <code>doAnother</code> تقبل سياقًا <code>context.Context</code> وتطبع قيمة السياق من خلال المفتاح، ونعدّل أيضًا الدالة <code>doSomething</code>، بحيث نُنشئ داخلها سياقًا جديدًا يُغلّف السياق الأب ويضيف معلومات جديدة ولتكن <code>anotherValue</code>، ثم نستدعي الدالة <code>doAnother</code> على السياق <code>anotherCtx</code> الناتج، ونطبع في السطر الأخير من الدالة قيمة السياق الأب.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6097_14" style=""><span class="pun">...</span><span class="pln">

func doSomething</span><span class="pun">(</span><span class="pln">ctx context</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"doSomething: myKey's value is %s\n"</span><span class="pun">,</span><span class="pln"> ctx</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">(</span><span class="str">"myKey"</span><span class="pun">))</span><span class="pln">

    anotherCtx </span><span class="pun">:=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">WithValue</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">,</span><span class="pln"> </span><span class="str">"myKey"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"anotherValue"</span><span class="pun">)</span><span class="pln">
    doAnother</span><span class="pun">(</span><span class="pln">anotherCtx</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"doSomething: myKey's value is %s\n"</span><span class="pun">,</span><span class="pln"> ctx</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">(</span><span class="str">"myKey"</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func doAnother</span><span class="pun">(</span><span class="pln">ctx context</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"doAnother: myKey's value is %s\n"</span><span class="pun">,</span><span class="pln"> ctx</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">(</span><span class="str">"myKey"</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج:
</p>

<pre class="ipsCode">doSomething: myKey's value is myValue
doAnother: myKey's value is anotherValue
doSomething: myKey's value is myValue
</pre>

<p>
	نلاحظ في الخرج سطرين من الدالة <code>doSomething</code> وسطر من الدالة <code>doAnother</code>. لم نغير شيئًا داخل الدالة <code>main</code>؛ إذ أنشأنا سياقًا فارغًا وغلّفناه مع القيمة <code>myValue</code> والمفتاح <code>myKey</code> ومرّرنا السياق الناتج إلى <code>doSomething</code>، ويمكننا أن نلاحظ أن هذه القيمة مطبوعة على أول سطر من الخرج.
</p>

<p>
	يظهر السطر الثاني من الخرج أنه عند استخدام <code>context.WithValue</code> داخل <code>doSomething</code> لتغليف السياق الأب <code>ctx</code> وتوليد السياق <code>anotherCtx</code> بالقيمة <code>anotherValue</code> والمفتاح <code>myKey</code> (المفتاح نفسه للأب لم يتغير) وتمرير هذا السياق الناتج إلى <code>doAnother</code>، فإن القيمة الجديدة تتخطى القيمة الابتدائية.
</p>

<p>
	بالنسبة للسطر الأخير، نلاحظ أنه يطبع القيمة المرتبطة بالمفتاح <code>myKey</code> والمرتبطة بالسياق الأب وهي <code>myValue</code>. نظرًا لأن الدالة <code>context.WithValue</code> تغلّف السياق الأب فقط، سيبقى السياق الأب محتفظًا بنفس القيم الأصلية نفسها. عندما نستدعي التابع <code>Value</code> على سياق ما، فإنه يُعيد القيمة المرتبطة بالمفتاح من المستوى الحالي للسياق. عند استدعاء <code>anotherCtx.Value</code> من أجل مفتاح <code>myKey</code>، سيعيد القيمة <code>anotherValue</code> لأنها القيمة المغلّفة للسياق، وبالتالي يتجاوز أي قيم أخرى مُغلّفة للمفتاح، وعند استدعاء <code>anotherCtx</code> داخل <code>doSomething</code> للمرة الثانية، لن تغلِّف <code>anotherCtx</code> السياق <code>ctx</code>، وستُعاد القيمة الأصلية <code>myValue</code>.
</p>

<p>
	<strong>ملاحظة:</strong> السياق أداة قوية يمكن من خلالها تخزين جميع القيم التي نريد الاحتفاظ بها. قد يبدو من المغري وضع جميع البيانات في السياق واستخدام تلك البيانات في الدوال بدلًا من المعاملات، ولكن يمكن أن يؤدي ذلك إلى شيفرة يصعب قراءتها والحفاظ عليه. يجب تحقيق التوازن بين البيانات المُخزنة في السياق والبيانات المُمررة إلى دالةٍ ما مثل معاملات. القاعدة الأساسية المناسبة هي أن أي بيانات مطلوبة لتشغيل دالة ما يجب أن تُمرر مثل معاملات. مثلًا قد يكون من المفيد الاحتفاظ بأسماء المستخدمين ضمن السياق لاستخدامها عند تسجيل المعلومات لاحقًا، إلا أنه من الممكن أن تكون هناك حاجة لاستخدام اسم المستخدم في تحديد ما إذا كانت الدالة يجب أن تعرض بعض المعلومات المحددة المرتبطة به، وبالتالي من الأفضل تضمينها مثل معامل للدالة حتى لو كانت متاحةً في السياق، لكي يسهل معرفة البيانات التي تُستخدم ضمن الدالة لنا وللآخرين.
</p>

<p>
	حدّثنا في هذا القسم برنامجنا لتخزين قيمة في سياق، ثم تغليف السياق لتجاوز هذه القيمة. هذه ليست الأداة الوحيدة التي يمكن للسياقات توفيرها، إذ يمكن للسياقات أن تُستخدم للإشارة إلى أجزاء أخرى من البرنامج عندما ينبغي التوقف عن المعالجة لتجنب هدر الموارد الحاسوبية.
</p>

<h2 id="-2">
	إنهاء سياق
</h2>

<p>
	إحدى الأدوات الأخرى التي يُقدمها السياق <code>context.Context</code> هي إمكانية الإشارة إلى أي دالة تستخدمه بأن السياق قد انتهى ويجب عدّه مكتملًا، وبالتالي يمكن للدوال إيقاف تنفيذ أي عمل يؤدونه. هذا يسمح للبرنامج أن يكون أكثر كفاءة، فهو أصبح يعرف متى يجب أن يتوقف عن معالجة طلب ما عندما تنتهي الحاجة إليه. مثلًا، إذا أرسل المستخدم طلبًا للحصول على صفحة ويب من الخادم وليكن عن طريق الخطأ، ثم ضغط على زر "إيقاف" أو إغلاق المتصفح قبل انتهاء تحميل الصفحة، في هذه الحالة إن لم يُدرك الخادم أن المستخدم قرر إلغاء العملية، سيعالج هذا الطلب دون جدوى، فالمستخدم لن يرى النتيجة لأنه لا يريدها بعد الآن، وبالتالي تُهدر الموارد على طلب دون فائدة، ولا سيما إذا كانت الصفحة المطلوبة تتطلب تنفيذ بعض الاستعلامات من قاعدة البيانات؛ أما إذا كانت الدالة التي تُخدّم الطلب تستخدم سياقًا، فإنها ستعرف وستُخبر بقية الدوال ذات الصلة بأن السياق انتهى لأن الخادم ألغاه، وبالتالي يمكنهم تخطي تنفيذ أية استعلامات مُتبقية من قاعدة البيانات. يؤدي ذلك إلى الحد من هدر الموارد من خلال إتاحة وقت المعالجة هذا إلى طلب آخر ربما يكون منتظرًا.
</p>

<p>
	سنعدّل في هذا القسم البرنامج ليكون قادرًا على معرفة وقت انتهاء السياق، وسنتعرّف على 3 توابع لإنهاء السياق.
</p>

<h3 id="-3">
	تحديد انتهاء السياق
</h3>

<p>
	يحدث تحديد ما إذا كان السياق قد انتهى بنفس الطريقة، وذلك بصرف النظر عن السبب؛ إذ يوفر النوع <code>context.Context</code> تابعًا يُسمى <code>Done</code> للتحقق من انتهاء سياق ما. يُعيد هذا التابع <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%B9%D8%AF%D8%A9-%D8%AF%D9%88%D8%A7%D9%84-%D8%B9%D8%A8%D8%B1-%D9%85%D9%8A%D8%B2%D8%A9-%D8%A7%D9%84%D8%AA%D8%B3%D8%A7%D9%8A%D8%B1-concurrency-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2152/" rel="">قناةً channel</a> تُغلق حالما ينتهي السياق، وأي دالة تُتابع هذه القناة توقف تنفيذ أي عملية ذات صلة في حال أُغلقت القناة. لا يُكتب على هذه القناة أية قيم، وبالتالي عند إغلاق القناة تُعيد القيمة <code>nil</code> إذا حاولنا قراءتها. إذًا، يمكننا من خلال هذه القناة، إنشاء دوال يمكنها معالجة الطلبات ومعرفة متى يجب أن تكمل المعالجة ومتى يجب أن تتوقف من خلال التحقق الدوري من حالة القناة، كما أن الجمع بين معالجة الطلبات والفحص الدوري لحالة القناة وتعليمة <code>select</code> يمكن أن يقدم فائدةً أكبر من خلال السماح بإرسال أو استقبال البيانات من قنوات أخرى في نفس الوقت، إذ تُستخدم التعليمة <code>select</code> للسماح للبرنامج بالكتابة أو القراءة من عدة قنوات بصورة متزامنة. يمكن تنفيذ عملية واحدة خاصة بقناة في وقت واحد ضمن تعليمة <code>select</code>، لذا نستخدم <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%AD%D9%84%D9%82%D8%A9-%D8%A7%D9%84%D8%AA%D9%83%D8%B1%D8%A7%D8%B1-for-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1950/" rel="">حلقة <code>for</code></a> على تعليمة <code>select</code> كما سنرى في المثال التالي، لإجراء عدة عمليات على القناة.
</p>

<p>
	يُمكن إنشاء تعليمة <code>select</code> من خلال الكلمة المفتاحية <code>select</code> متبوعةً بقوسين <code>{}</code> مع تعليمة <code>case</code> واحدة أو أكثر ضمن القوسين. يمكن أن تكون كل تعليمة <code>case</code> عملية قراءة أو كتابة على قناة، وتنتظر تعليمة <code>select</code> حتى تُنفّذ إحدى حالتها (تبقى منتظرة حتى تُنفذ إحدى تعليمات <code>case</code>)، وفي حال أردنا ألا تنتظر، يمكننا أن نستخدم التعليمة <code>default</code>، وهي الحالة الافتراضية التي تُنفّذ في حال عدم تحقق شرط تنفيذ إحدى الحالات الأخرى (تشبه تعليمة <code>switch</code>).
</p>

<p>
	توضّح الشيفرة التالية كيف يمكن استخدام تعليمة <code>select</code> ضمن دالة، بحيث تتلقى نتائج من قناة وتراقب انتهاء السياق من خلال التابع <code>Done</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6097_16" style=""><span class="pln">ctx </span><span class="pun">:=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">Background</span><span class="pun">()</span><span class="pln">
resultsCh </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="pun">*</span><span class="typ">WorkResult</span><span class="pun">)</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    select </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">case</span><span class="pln"> </span><span class="pun">&lt;-</span><span class="pln"> ctx</span><span class="pun">.</span><span class="typ">Done</span><span class="pun">():</span><span class="pln">
        </span><span class="com">// The context is over, stop processing results</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">
    </span><span class="kwd">case</span><span class="pln"> result </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&lt;-</span><span class="pln"> resultsCh</span><span class="pun">:</span><span class="pln">
                    </span><span class="com">// عالج النتائج</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُمرر عادةً قيم كل من <code>ctx</code> و <code>resultsCh</code> إلى دالة مثل معاملات، إذ يكون <code>ctx</code> سياقًا من النوع <code>context.Context</code>، بينما <code>resultsCh</code> هي قيمة من قناة يمكننا القراءة منها فقط داخل الدالة، وغالبًا ما تكون هذه القيمة نتيجة من عامل worker (أو خيوط معالجة جو) في مكانٍ ما. في كل مرة تُنفذ فيها تعلمية <code>select</code> سيوقف جو تنفيذ الدالة ويراقب جميع تعليمات <code>case</code>، وحالما تكون هناك إمكانية لتنفيذ أحدها (قراءة من قناة كما في في حالة <code>resultsCh</code>، أو كتابة أو معرفة حالة القناة عبر <code>Done</code>) يُنفذ فرع هذه الحالة، ولا يمكن التنبؤ بترتيب تنفيذ تعلميات <code>case</code> هذه، إذ من الممكن أن تُنفذ أكثر من حالة بالتزامن.
</p>

<p>
	نلاحظ في الشيفرة أعلاه أنه يمكننا استمرار تنفيذ الحلقة إلى الأبد طالما أن السياق لم ينتهي، أي طالما <code>ctx.Done</code> لم تُشر إلى إغلاق القناة، إذ لاتوجد أي تعليمات <code>break</code> أو <code>return</code> إلا داخل عبارة <code>case</code>. بالرغم من عدم إسناد الحالة <code>case &lt;- ctx.Done</code> أي قيمة لأي متغير، إلا أنه سيظل بالإمكان تنفيذها عند إغلاق <code>ctx.Done</code>، لأن القناة تحتوي على قيمة يمكن قراءتها حتى لو لم نُسنِد تلك القيمة وتجاهلناها. إذا لم تُغلق القناة <code>ctx.Done</code> (أي لم يتحقق فرع الحالة <code>case &lt;- ctx.Done</code>)، فسوف تنتظر تعليمة <code>select</code> حتى تُغلق أو حتى يُصبح بالإمكان قراءة قيمة من <code>resultsCh</code>.
</p>

<p>
	إذا كانت <code>resultsCh</code> تحمل قيمة يمكن قراءتها يُنفّذ فرع الحالة الذي يتضمنها ويجري انتظارها ريثما تنتهي من التنفيذ، وبعدها يجري الدخول في تكرار آخر للحلقة (تُنفّذ حالة واحدة في الاستدعاء الواحد لتعليمة <code>select</code>)، وكما ذكرنا سابقًا إذا كان بالإمكان تنفيذ الحالتان، يجري الاختيار بينهما عشوائيًا.
</p>

<p>
	في حال وجود تعليمة <code>default</code>، فإن الأمر الوحيد الذي يتغير هو أنه يُنفذ حالًا في حال لم تكن إحدى الحالات الأخرى قابلة للتنفيذ، وبعد تنفيذ <code>default</code> يجري الخروج من <code>select</code> ثم يجري الدخول إليها مرةً أخرى بسبب وجود الحلفة. يؤدي هذا إلى تنفيذ حلقة <code>for</code> بسرعة كبيرة لأنها لن تتوقف أبدًا وتنتظر القراءة من قناة. تُسمى الحلقة في هذه الحالة "حلقة مشغولة busy loop" لأنه بدلًا من انتظار حدوث شيء ما، تكون الحلقة مشغولة بالتكرار مرارًا وتكرارًا. يستهلك ذلك الكثير من دورات <a href="https://academy.hsoub.com/programming/os-embedded-systems/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%88%D8%AD%D8%AF%D8%A9-%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D8%B1%D9%83%D8%B2%D9%8A%D8%A9-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8-r1716/" rel="">وحدة المعالجة المركزية CPU</a>، لأن البرنامج لا يحصل أبدًا على فرصة للتوقف عن التشغيل للسماح بتنفيذ التعليمات البرمجية الأخرى. عمومًا تكون هذه العملية مفيدة أحيانًا، فمثلًا إذا كنا نريد التحقق مما إذا كانت القناة جاهزة لفعل شيء ما قبل الذهاب لإجراء عملية أخرى غير متعلقة بالقناة.
</p>

<p>
	كما ذكرنا، تتجلى الطريقة الوحيدة للخروج من الحلقة في هذا المثال بإغلاق القناة المُعادة من التابع <code>Done</code>، والطريقة الوحيدة لإغلاق هذه القناة هي إنهاء السياق، بالتالي نحن بحاجة إلى طريقة لإنهائه. توفر لغة جو عدة طرق لإجراء ذلك وفقًا للهدف الذي نبتغيه، والخيار المباشر هو استدعاء دالة "إلغاء cancel" السياق.
</p>

<h3 id="-4">
	إلغاء السياق
</h3>

<p>
	يعد إلغاء السياق Cancelling context أكثر طريقة مباشرة ويمكن التحكم بها لإنهاء السياق. يمكننا -بأسلوب مشابه لتضمين قيمة في سياق باستخدام دالة <code>context.WithValue</code>- ربط دالة إلغاء سياق مع سياق باستخدام دالة <code>context.WithCancel</code>، إذ تقبل هذه الدالة السياق الأب مثل معامل وتعيد سياقًا جديدًا إضافةً إلى دالة يمكن استخدامها لإلغاء السياق المُعاد؛ وكذلك يؤدي استدعاء دالة الحذف المُعادة فقط إلى إلغاء السياق الذي أُعيد مع جميع السياقات الأخرى التي تستخدمه مثل سياق أب، وذلك بطريقة مشابهة أيضًا لأسلوب عمل دالة <code>context.WithValue</code>. هذا لا يعني طبعًا أنه لا يمكن إلغاء السياق الأب الذي مررناه إلى دالة <code>context.WithCancel</code>، بل يعني أنه لا يُلغى إذا استدعيت دالة الإلغاء بهذا الشكل.
</p>

<p>
	لنفتح ملف "main.go" لنرى كيف نستخدم <code>context.WithCancel</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6097_18" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"context"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func doSomething</span><span class="pun">(</span><span class="pln">ctx context</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ctx</span><span class="pun">,</span><span class="pln"> cancelCtx </span><span class="pun">:=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">WithCancel</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">)</span><span class="pln">

    printCh </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">)</span><span class="pln">
    go doAnother</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">,</span><span class="pln"> printCh</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> num </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> num</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        printCh </span><span class="pun">&lt;-</span><span class="pln"> num
    </span><span class="pun">}</span><span class="pln">

    cancelCtx</span><span class="pun">()</span><span class="pln">

    time</span><span class="pun">.</span><span class="typ">Sleep</span><span class="pun">(</span><span class="lit">100</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Millisecond</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"doSomething: finished\n"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func doAnother</span><span class="pun">(</span><span class="pln">ctx context</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">,</span><span class="pln"> printCh </span><span class="pun">&lt;-</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        select </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">case</span><span class="pln"> </span><span class="pun">&lt;-</span><span class="pln">ctx</span><span class="pun">.</span><span class="typ">Done</span><span class="pun">():</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> ctx</span><span class="pun">.</span><span class="typ">Err</span><span class="pun">();</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
                fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"doAnother err: %s\n"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"doAnother: finished\n"</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln">
        </span><span class="kwd">case</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&lt;-</span><span class="pln">printCh</span><span class="pun">:</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"doAnother: %d\n"</span><span class="pun">,</span><span class="pln"> num</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	أضفنا استيرادًا للحزمة <code>time</code> وجعلنا الدالة <code>doAnother</code> تقبل قناةً جديدة لطباعة أرقام على شاشة الخرج. استخدمنا تعليمة <code>select</code> ضمن حلقة <code>for</code> للقراءة من تلك القناة والتابع <code>Done</code> الخاص بالسياق. أنشأنا ضمن الدالة <code>doSomething</code> سياقًا يمكن إلغاؤه إضافةً إلى قناة لإرسال الأرقام إليها. أضفنا استدعاءً للدالة <code>doAnother</code> سبقناه بالكلمة المفتاحية <code>go</code> ليُنفّذ مثل <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%B9%D8%AF%D8%A9-%D8%AF%D9%88%D8%A7%D9%84-%D8%B9%D8%A8%D8%B1-%D9%85%D9%8A%D8%B2%D8%A9-%D8%A7%D9%84%D8%AA%D8%B3%D8%A7%D9%8A%D8%B1-concurrency-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2152/" rel="">خيوط معالجة جو goroutine</a>، ومررنا له السياق <code>ctx</code> والقناة <code>printCh</code>. أخيرًا أرسلنا بعض الأرقام إلى القناة ثم ألغينا السياق.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">doAnother: 1
doAnother: 2
doAnother: 3
doAnother err: context canceled
doAnother: finished
doSomething: finished
</pre>

<p>
	تعمل الدالة <code>doSomething</code> في الشيفرة السابقة على إرسال العمل إلى خيط معالجة واحد أو أكثر يقرؤون الأرقام من القناة ويطبعوها، وتكون في هذه الحالة الدالة <code>doAnother</code> هي العامل worker وعملها هو طباعة الأرقام، وحالما يبدأ خيط معالجة جو <code>doAnother</code>، تبدأ دالة <code>doSomething</code> بإرسال الارقام للطباعة.
</p>

<p>
	تنتظر تعليمة <code>select</code> -داخل الدالة <code>doAnother</code>- إغلاق قناة <code>ctx.Done</code> أو استقبال رقم على القناة <code>printCh</code>. تبدأ الدالة <code>doSomething</code> عملية إرسال الأرقام إلى القناة بعد بدء تنفيذ <code>doAnother</code> كما نرى في الشيفرة أعلاه، إذ ترسل 3 أرقام إلى القناة، وبالتالي تُفعّل 3 عمليات طباعة <code>fmt.Printf</code> لكل رقم (فرع الحالة الثانية داخل تعليمة <code>select</code>)، ثم تستدعي دالة <code>cancelCtx</code> لإلغاء السياق. بعد أن تقرأ الدالة <code>doAnother</code> الأرقام الثلاثة من القناة، ستنتظر العملية التالية من القناة، أي تبقى منتظرة ولن يُنفّذ الفرع المقابل في <code>select</code>، وبما أن <code>doSomething</code> في هذه الأثناء استدعت <code>cancelCtx</code>، بالتالي يُستدعى فرع <code>ctx.Done</code>، الذي يستخدم الدالة <code>Err</code> التي يوفرها النوع <code>context.Context</code> لتحديد كيفية إنهاء السياق. بما أننا ألغينا السياق باستخدام <code>cancelCtx</code>، بالتالي سيكون الخطأ الذي نراه هو <code>context canceled</code>.
</p>

<p>
	<strong>ملاحظة:</strong> إذا سبق وشغّلت برامج لغة جو من قبل ونظرت إلى الخرج، فربما سبق وشاهدت الخطأ <code>context canceled</code> من قبل، فهو خطأ شائع عند استخدام <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-r73/" rel="">حزمة <code>http</code></a> من لغة جو، ويهدف لمعرفة وقت قطع اتصال العميل بالخادم، قبل أن يعالج الخادم الاستجابة.
</p>

<p>
	أخيرًا، تستخدم الدالة <code>doSomething</code> بعد ذلك الدالة <code>time.Sleep</code> للانتظار لفترة قصيرة من الوقت، لتعطي بذلك الدالة <code>doAnother</code> وقتًا لمعالجة حالة السياق المُلغى وإنهاء التنفيذ، ثم تطبع الدالة <code>doSomething</code> رسالة تُشير إلى انتهاء التنفيذ. تجدر الملاحظة إلى أنه لا داعٍ إلى استخدام دالة <code>time.Sleep</code> غالبًا، لكنها ضرورية عندما تنتهي الشيفرة من التنفيذ بسرعة، وسينتهي بدونها البرنامج دون رؤية كامل الخرج على الشاشة.
</p>

<p>
	تكون الدالة <code>context.WithCancel</code> ودالة الإلغاء التي تُعيدها مفيدةً أكثر عندما يتطلب الأمر قدرًا كبيرًا من التحكم عند إنهاء السياق، لكن في كثير من الأحيان قد لا نحتاج إلى هذا القدر من التحكم. الدالة التالية المتاحة لإنهاء السياقات في حزمة السياق <code>context</code> هي <code>Context.WithDeadline</code>، إذ تنهي هذه الدالة السياق تلقائيًا نيابةً عنا.
</p>

<h3 id="-5">
	إعطاء السياق مهلة زمنية للانتهاء
</h3>

<p>
	يمكننا تحديد مهلة زمنية Deadline يجب خلالها أن ينتهي السياق باستخدام الدالة <code>Context.WithDeadline</code>، وبعد انتهاء هذه المهلة سوف ينتهي السياق تلقائيًا. الأمر أشبه بأن نكون في امتحان، ويكون هناك مهلة محددة لحل الأسئلة، وتُسحب الورقة منا عند انتهائها تلقائيًا، حتى لو لم ننتهي من حلها. لتحديد موعد نهائي لسياق ما، نستخدم الدالة <code>Context.WithDeadline</code> مع تمرير السياق الأب وقيمة زمنية من النوع <code>time.Time</code> تُشير إلى الموعد النهائي. تُعيد هذه الدالة سياقًا جديدًا ودالة لإلغاء السياق، وكما رأينا في <code>Context.WithCancel</code> تُطبق عملية الإلغاء على السياق الجديد وعلى أبنائه (السياقات التي تستخدمه). يمكننا أيضًا إلغاء السياق يدويًا عن طريق استدعاء دالة الإلغاء كما في دالة <code>context.WithCancel</code>.
</p>

<p>
	لنفتح ملف البرنامج لنحدثه ونستخدم دالة <code>Context.WithDeadline</code> بدلًا من <code>context.WithCancel</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6097_20" style=""><span class="pun">...</span><span class="pln">

func doSomething</span><span class="pun">(</span><span class="pln">ctx context</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    deadline </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">().</span><span class="typ">Add</span><span class="pun">(</span><span class="lit">1500</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Millisecond</span><span class="pun">)</span><span class="pln">
    ctx</span><span class="pun">,</span><span class="pln"> cancelCtx </span><span class="pun">:=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">WithDeadline</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">,</span><span class="pln"> deadline</span><span class="pun">)</span><span class="pln">
    defer cancelCtx</span><span class="pun">()</span><span class="pln">

    printCh </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">)</span><span class="pln">
    go doAnother</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">,</span><span class="pln"> printCh</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> num </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> num</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        select </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">case</span><span class="pln"> printCh </span><span class="pun">&lt;-</span><span class="pln"> num</span><span class="pun">:</span><span class="pln">
            time</span><span class="pun">.</span><span class="typ">Sleep</span><span class="pun">(</span><span class="lit">1</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Second</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">case</span><span class="pln"> </span><span class="pun">&lt;-</span><span class="pln">ctx</span><span class="pun">.</span><span class="typ">Done</span><span class="pun">():</span><span class="pln">
            </span><span class="kwd">break</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    cancelCtx</span><span class="pun">()</span><span class="pln">

    time</span><span class="pun">.</span><span class="typ">Sleep</span><span class="pun">(</span><span class="lit">100</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Millisecond</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"doSomething: finished\n"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	تستخدم الشيفرة الآن الدالة <code>Context.WithDeadline</code> ضمن الدالة <code>context.WithDeadline</code> لإلغاء السياق تلقائيًا بعد 1500 ميلي ثانية (1.5 ثانية) من بدء تنفيذ الدالة، إذ حددنا الوقت من خلال دالة <code>time.Now</code>. إضافةً إلى استخدام الدالة <code>Context.WithDeadline</code>، أجرينا بعض التعديلات الأخرى؛ فنظرًا لأنه من المحتمل أن ينتهي البرنامج الآن عن طريق استدعاء <code>cancelCtx</code> مباشرةً أو الإلغاء التلقائي وفقًا للموعد النهائي، حدّثنا دالة <code>doSomething</code>، بحيث نستخدم تعليمة <code>select</code> لإرسال الأرقام على القناة. بالتالي، إذا كانت <code>doAnother</code> لا تقرأ من <code>printCh</code> وكانت قناة <code>ctx.Done</code> مُغلقة، ستلاحظ ذلك <code>doSomething</code> وتتوقف عن محاولة إرسال الأرقام.
</p>

<p>
	نلاحظ أيضًا استدعاء <code>cancelCtx</code> مرتين، مرة عبر <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D9%84%D9%8A%D9%85%D8%A9-defer-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1963/" rel="">تعليمة <code>defer</code></a> ومرة كما في السابق. وجود الاستدعاء الأول غير ضروري طالما أن الاستدعاء الثاني موجود وسيُنفذ دومًا، ولكن من المهم وجوده لو كانت هناك تعليمة <code>return</code> أو حدث ما يُمكن أن يتسبب في عدم تنفيذ الاستدعاء الثاني. على الرغم من إلغاء السياق بعد انقضاء المهلة الزمنية، إلا أننا نستدعي دالة الإلغاء، وذلك من أجل تحرير أية موارد مُستخدمة بمثابة إجراء أكثر أمانًا.
</p>

<p>
	لنُشغّل ملف البرنامج "main.go" من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">doAnother: 1
doAnother: 2
doAnother err: context deadline exceeded
doAnother: finished
doSomething: finished
</pre>

<p>
	نلاحظ هذه المرة إلغاء السياق بسبب خطأ تجاوز الموعد النهائي<code>deadline exceeded</code> قبل طباعة جميع الأرقام الثلاثة، وهذا منطقي فالمهلة الزمنية هي 1.5 ثانية بدءًا من لحظة تنفيذ <code>doSomething</code>، وبما أن <code>doSomething</code> تنتظر ثانية واحدة بعد إرسال رقم، فستنتهي المهلة قبل طباعة الرقم الثالث. بمجرد انقضاء المهلة الزمنية، ينتهي تنفيذ كل من <code>doSomething</code> و <code>doAnother</code>، وذلك لأنهما يراقبان لحظة إغلاق قناة <code>ctx.Done</code>. لو عدّلنا مدة المهلة وجعلناها أكثر من 3 ثوان، فربما سنشاهد الخطأ <code>context canceled</code> يظهر من جديد، وذلك لأن المهلة طويلة.
</p>

<p>
	قد يكون الخطأ <code>context deadline exceeded</code> مألوفًا أيضًا، ولا سيما للمبرمجين الذين يستخدمون <a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-go-%D8%B9%D9%84%D9%89-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AE%D8%AA%D9%84%D9%81%D8%A9-r2022/" rel="">تطبيقات لغة جو</a> ويقرأون رسائل الخطأ التي تظهر. هذا الخطأ شائع في خوادم الويب التي تستغرق وقتًا في إرسال الاستجابات إلى العميل. مثلًا، إذا استغرق استعلام من قاعدة البيانات أو عملية ما وقتًا طويلًا، فقد يتسبب ذلك بإلغاء سياق الطلب وظهور هذا الخطأ لأن الخادم لا يسمح بتجاوز مهلة معينة في معالجة طلب ما. يسمح لنا إلغاء السياق باستخدام <code>context.WithCancel</code> بدلًا من <code>cont4ext.WithCancel</code> بإلغاء السياق تلقائيًا بعد انتهاء مهلة نتوقع أنها كافية، دون الحاجة إلى تتبع ذلك الوقت. بالتالي: إذا كنا نعرف متى يجب أن ينتهي السياق (أي نعرف المهلة الكافية)، ستكون هذه الدالة خيارًا مناسبًا.
</p>

<p>
	أحيانًا ربما لا نهتم بالوقت المحدد الذي ينتهي فيه السياق، وتريد أن ينتهي بعد دقيقة واحدة من بدئه. هنا يمكننا أيضًا استخدام الدالة <code>context.WithDeadline</code> مع بعض توابع الحزمة <code>time</code> لتحقيق الأمر، لكن لغة جو توفر لنا الدالة <code>context.WithTimeout</code> لتبسيط الأمر.
</p>

<h3 id="-6">
	إعطاء السياق وقت محدد
</h3>

<p>
	تؤدي دالة <code>context.WithTimeout</code> نفس المهمة التي تؤديها الدالة السابقة، والفرق الوحيد هو أننا في <code>context.WithDeadline</code> نمرر قيمة زمنية محددة من النوع <code>time.Time</code> لإنهاء السياق، أما في <code>context.WithTimeout</code> نحتاج فقط إلى تمرير المدة الزمنية، أي قيمة من النوع <code>time.Duration</code>. إذًا، يمكننا استخدام <code>context.WithDeadline</code> إذا أردنا تحديد وقت معين <code>time.Time</code>. ستحتاج -بدون <code>context.WithTimeout</code>- إلى استخدام الدالة <code>()time.Now</code> والتابع <code>Add</code> لتحديد المهلة الزمنية، أما مع <code>context.WithTimeout</code>، فيمكنك تحديد المهلة مباشرةً.
</p>

<p>
	لنفتح ملف البرنامج مجددًا ونعدله، بحيث نستخدم <code>context.WithTimeout</code> بدلًا من <code>context.WithDeadline</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6097_22" style=""><span class="pun">...</span><span class="pln">

func doSomething</span><span class="pun">(</span><span class="pln">ctx context</span><span class="pun">.</span><span class="typ">Context</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ctx</span><span class="pun">,</span><span class="pln"> cancelCtx </span><span class="pun">:=</span><span class="pln"> context</span><span class="pun">.</span><span class="typ">WithTimeout</span><span class="pun">(</span><span class="pln">ctx</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1500</span><span class="pun">*</span><span class="pln">time</span><span class="pun">.</span><span class="typ">Millisecond</span><span class="pun">)</span><span class="pln">
    defer cancelCtx</span><span class="pun">()</span><span class="pln">

    </span><span class="pun">...</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	لنُشغّل ملف البرنامج <code>main.go</code> من خلال الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ليكون الخرج:
</p>

<pre class="ipsCode">doAnother: 1
doAnother: 2
doAnother err: context deadline exceeded
doAnother: finished
doSomething: finished
</pre>

<p>
	نلاحظ أن الخرج ورسالة الخطأ هي نفسها التي حصلنا عليها في الخرج السابق عندما استخدمنا الدالة <code>context.WithDeadline</code>، ورسالة الخطأ أيضًا نفسها التي تظهر أن <code>context.WithTimeout</code> هي فعليًا مغلّّف يجري عمليات رياضية نيابةً عنك.
</p>

<p>
	استخدمنا في هذا القسم 3 طرق مختلفة لإنهاء السياق <code>context.Context.</code>؛ إذ بدأنا بالدالة <code>context.WithCancel</code> التي تسمح لنا باستدعاء دالة لإلغاء السياق؛ ثم الدالة <code>context.WithDeadline</code> مع قيمة <code>time.Time</code> لإنهاء السياق في وقت محدد؛ ثم الدالة <code>context.WithTimeout</code> مع قيمة <code>time.Duration</code> لإنهاء السياق بعد مدة معينة.
</p>

<p>
	نضمن من خلال استخدام هذه الدوال أن البرنامج لن يستهلك موارد الحاسب أكثر مما تحتاجه، كما يُسهّل فهم الأخطاء التي تُسببها السياقات عملية استكشاف الأخطاء وإصلاحها في برامج جو.
</p>

<h2 id="-7">
	الخاتمة
</h2>

<p>
	أنشأنا خلال هذا المقال برنامجًا يستخدم حزمة السياق <code>context</code> التي تقدمها لغة جو بطرق مختلفة. أنشأنا دالةً تقبل سياقًا <code>context.Context</code> مثل معامل، واستخدمنا الدالتين <code>context.TODO</code> و <code>context.Background</code> لإنشاء سياق فارغ. بعد ذلك، استخدمنا الدالة <code>context.WithValue</code> لإنشاء سياق جديد يُغلّف سياقًا آخر ويحمل قيمة جديدة، وتعرّفنا على كيفية قراءة هذه القيمة لاحقًا من خلال التابع <code>Value</code> من داخل الدوال الأخرى التي تستخدم هذا السياق. بعد ذلك، تعرفنا على التابع <code>Done</code> الذي يُساعدنا في معرفة الوقت الذي تنتفي فيه الحاجة إلى إبقاء السياق. تعلمنا أيضًا كيف نلغي السياق بطرق مختلفة من خلال الدوال <code>context.WithCancel</code> و <code>context.WithDeadline</code> و <code>context.WithTimeout</code> وكيف نضع حدًا للمدة التي يجب أن تُنفّذ بها التعليمات البرمجية التي تستخدم تلك السياقات.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-contexts-in-go" rel="external nofollow">How To Use Contexts in Go</a> لصاحبه Kristin Davidson.
</p>

<h2 id="-8">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%A7%D8%B1%D9%8A%D8%AE-%D9%88%D8%A7%D9%84%D9%88%D9%82%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2163/" rel="">استخدام التاريخ والوقت في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-go-%D8%B9%D9%84%D9%89-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AE%D8%AA%D9%84%D9%81%D8%A9-r2022/" rel="">بناء تطبيقات لغة Go على أنظمة التشغيل والمعماريات المختلفة</a>.
	</li>
</ul>
]]></description><guid isPermaLink="false">2164</guid><pubDate>Sun, 12 Nov 2023 13:00:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x62A;&#x627;&#x631;&#x64A;&#x62E; &#x648;&#x627;&#x644;&#x648;&#x642;&#x62A; &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648;</title><link>https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%A7%D8%B1%D9%8A%D8%AE-%D9%88%D8%A7%D9%84%D9%88%D9%82%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2163/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_11/------Go.png.d0dd09067ec497421bc29caf283e6100.png" /></p>
<p>
	صُممت البرمجيات لتسهيل إنجاز الأعمال، وفي بعض الأحيان يتطلب الأمر التعامل مع التاريخ والوقت، إذ يمكن رؤية قيم التاريخ والوقت في معظم البرامج الحديثة، فمثلًا لو أردنا تطوير تطبيق يعطينا تنبيهات عن أوقات الصلاة، سيتوجب على البرنامج تشغيل تنبيه عند وقت وتاريخ محدد. مثال آخر هو تتبع الوقت في السيارات الحديثة، إذ نحتاج إلى إرسال تنبيهات إلى مالك السيارة لإبلاغه عن وجود مشكلة أو حاجة معينة للسيارة، أو تتبع التغييرات في <a href="https://academy.hsoub.com/devops/servers/databases/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-database/" rel="">قاعدة بيانات</a> لإنشاء سجل تدقيق، أو الوقت الذي يتطلبه إنجاز عملية ما مثل عبور شارع معين للوصول إلى الوجهة …إلخ. يشير هذا إلى ضرورة التعامل مع التاريخ والوقت في البرامج والتفاعل معها وعرضها على المستخدمين بتنسيق واضح وسهل الفهم، فهذه خاصية أساسية لهكذا تطبيقات.
</p>

<p>
	سنُنشئ خلال هذا المقال برنامج جو يمكنه معرفة التوقيت المحلي الحالي من خلال جهاز الحاسب الذي يعمل عليه، ثم عرضه على الشاشة بتنسيق سهل القراءة. بعد ذلك، سنفسّر سلسلة نصية لاستخراج معلومات التاريخ والوقت، كما سنغيّر أيضًا قيم التاريخ والوقت من منطقة زمنية إلى أخرى، ونضيف أو نطرح قيم الوقت لتحديد الفاصل الزمني بين وقتين.
</p>

<h2 id="">
	المتطلبات
</h2>

<p>
	لتتابع هذا المقال، ستحتاج إلى:
</p>

<ul>
	<li>
		إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a> لإعداده.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
</ul>

<h2 id="-1">
	الحصول على التاريخ والوقت الحالي
</h2>

<p>
	سنستخدم خلال هذا القسم حزمة لغة جو <code>time</code> للوصول إلى قيم التاريخ والوقت الحالي. تُقدّم لنا هذه الحزمة القياسية من مكتبة <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a> القياسية -العديد من الدوال المتعلقة بالتاريخ والوقت، والتي يمكن من خلالها تمثيل نقاط معينة من الوقت باستخدام النوع <code>time.Time</code>. يمكننا أيضًا من خلال هذه الدوال التقاط بعض المعلومات عن المنطقة الزمنية التي تمثِّل التاريخ والوقت المقابل.
</p>

<p>
	كما هي العادة، سنحتاج لبدء إنشاء برامجنا إلى إنشاء مجلد للعمل ووضع الملفات فيه. يمكن أن نضع المجلد في أي مكان على الحاسب، إذ يكون للعديد من المبرمجين عادةً مجلدٌ يضعون داخله كافة مشاريعهم. سنستخدم في هذا المقال مجلدًا باسم "projects"، لذا فلننشئ هذا المجلد وننتقل إليه:
</p>

<pre class="ipsCode">$ mkdir projects
$ cd projects
</pre>

<p>
	الآن، من داخل هذا المجلد، سنشغّل الأمر <code>mkdir</code> لإنشاء مجلد "datetime" ثم سنستخدم <code>cd</code> للانتقال إليه:
</p>

<pre class="ipsCode">$ mkdir datetime
$ cd datetime
</pre>

<p>
	يمكننا الآن فتح ملف <code>main.go</code> باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">محرر نانو nano أو أي محرر آخر تريده</a>:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	نضيف الدالة <code>main</code> التي سنكتب فيها تعليمات الحصول على التاريخ والوقت وعرضهما:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_8" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    currentTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> currentTime</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	استخدمنا الدالة <code>time.Now</code> من الحزمة <code>time</code> للحصول على الوقت الحالي مثل قيمة من النوع <code>time.Time</code> وتخزينها في المتغير <code>currentTime</code>، ثم طبعنا قيمة هذا المتغير باستخدام الدالة <code>fmt.Println</code>، إذ سيُطبع وفقًا لتنسيق سلسلة نصية افتراضي خاص بالنوع <code>time.Time</code>.
</p>

<p>
	شغّل الآن ملف البرنامج "main.go" باستخدام الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيبدو الخرج الذي يعرض التاريخ والوقت الحاليين مشابهًا لما يلي:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626
</pre>

<p>
	طبعًا في كل مرة نُشغّل فيها هذا البرنامج سيكون التاريخ والوقت مختلفين، كما أن المنطقة الزمنية <code>‎0500 CDT-</code> مُتغيرة تبعًا للمنطقة الزمنية التي ضُبط عليها الحاسب كما سبق وأشرنا. نلاحظ وجود القيمة <code>=m</code>، التي تُشير إلى <a href="https://pkg.go.dev/time#hdr-Monotonic_Clocks" rel="external nofollow">ساعة رتيبة monotonic clock</a>، وتُستخدم ضمنيًّا في جو عند قياس الاختلافات في الوقت، وقد صُممت لتعويض التغييرات المحتملة في تاريخ ووقت ساعة نظام الحاسب أثناء تشغيل البرنامج. من خلال هذه الساعة، ستبقى القيمة المُعادة من الدالة <code>time.Now</code> صحيحة حتى لو جرى تغيير ساعة نظام الحاسب لاحقًا. مثلًا لو استدعينا الدالة <code>time.Now</code> الآن وكان الوقت 10:50، ثم بعد دقيقتين جرى تأخير لساعة الحاسب بمقدار 60 دقيقة، ثم بعد 5 دقائق (من الاستدعاء الأول للدالة <code>time.Now</code>) استدعينا الدالة <code>time.Now</code> مرةً أخرى (في نفس البرنامج)، سيكون الخرج 10:55 وليس 9:55. لست بحاجة إلى فهم أكثر من ذلك حول آلية عمل هذه الساعة خلال المقال، لكن إذا كنت ترغب في معرفة المزيد حول الساعات الرتيبة وكيفية استخدامها، لكن يمكنك الذهاب إلى <a href="https://pkg.go.dev/time#hdr-Monotonic_Clocks" rel="external nofollow">التوثيق الرسمي</a> ورؤية المزيد من التفاصيل لو أحببت.
</p>

<p>
	قد يكون التنسيق الذي يظهر به التاريخ والوقت على شاشة الخرج غير مناسب وقد ترغب بتغييره أو أنه يتضمن أجزاء من التاريخ أو الوقت أكثر مما تريد عرضه. لحسن الحظ، يوفر النوع <code>time.Time</code> العديد من الدوال لتنسيق عرض التاريخ والوقت وعرض أجزاء محددة منهما؛ فمثلًا لو أردنا معرفة السنة فقط من المتغير <code>currentTime</code> يمكننا استخدام التابع <code>Year</code> أو يمكننا عرض الساعة فقط من خلال التابع <code>Hour</code>.
</p>

<p>
	لنفتح ملف "main.go" مرةً أخرى ونضيف التعديلات التالية لرؤية ذلك:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_10" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    currentTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> currentTime</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The year is"</span><span class="pun">,</span><span class="pln"> currentTime</span><span class="pun">.</span><span class="typ">Year</span><span class="pun">())</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The month is"</span><span class="pun">,</span><span class="pln"> currentTime</span><span class="pun">.</span><span class="typ">Month</span><span class="pun">())</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The day is"</span><span class="pun">,</span><span class="pln"> currentTime</span><span class="pun">.</span><span class="typ">Day</span><span class="pun">())</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The hour is"</span><span class="pun">,</span><span class="pln"> currentTime</span><span class="pun">.</span><span class="typ">Hour</span><span class="pun">())</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The minute is"</span><span class="pun">,</span><span class="pln"> currentTime</span><span class="pun">.</span><span class="typ">Hour</span><span class="pun">())</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The second is"</span><span class="pun">,</span><span class="pln"> currentTime</span><span class="pun">.</span><span class="typ">Second</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626
The year is 2021
The month is August
The day is 15
The hour is 14
The minute is 14
The second is 45
</pre>

<p>
	كما ذكرنا منذ قليل: في كل مرة نُشغّل فيها هذا البرنامج سيكون التاريخ أو الوقت مختلفين، لكن التنسيق يجب أن يكون متشابهًا. طبعنا في هذا المثال التاريخ كاملًا (السطر الأول)، ثم استخدمنا توابع النوع <code>time.Time</code> لعرض تفاصيل محددة من التاريخ كلٌ منها بسطر منفرد؛ بحيث عرضنا السنة ثم الشهر ثم اليوم ثم الساعة وأخيرًا الثواني. ربما نلاحظ أن الشهر طُبع اسمه <code>August</code> وليس رقمه كما في التاريخ الكامل، وذلك لأن التابع <code>Month</code> يعيد الشهر على أنه قيمة من النوع <code>time.Month</code> بدلًا من مجرد رقم، ويكون التنسيق عند طباعته <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1792/?tab=comments" rel="">سلسلة نصية <code>string</code></a>.
</p>

<p>
	لنحدّث ملف "main.go" مرةً أخرى ونضع استدعاءات التوابع السابقة كلها ضمن دالة <code>fmt.Printf</code> لنتمكن من طباعة التاريخ والوقت الحاليين بتنسيق أقرب إلى ما قد نرغب في عرضه على المستخدم:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_12" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    currentTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> currentTime</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%d-%d-%d %d:%d:%d\n"</span><span class="pun">,</span><span class="pln">
        currentTime</span><span class="pun">.</span><span class="typ">Year</span><span class="pun">(),</span><span class="pln">
        currentTime</span><span class="pun">.</span><span class="typ">Month</span><span class="pun">(),</span><span class="pln">
        currentTime</span><span class="pun">.</span><span class="typ">Day</span><span class="pun">(),</span><span class="pln">
        currentTime</span><span class="pun">.</span><span class="typ">Hour</span><span class="pun">(),</span><span class="pln">
        currentTime</span><span class="pun">.</span><span class="typ">Hour</span><span class="pun">(),</span><span class="pln">
        currentTime</span><span class="pun">.</span><span class="typ">Second</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 -0500 CDT m=+0.000066626
2021-8-15 14:14:45
</pre>

<p>
	الخرج الآن أقرب بكثير إلى ما نريد، ولكن لا تزال هناك بعض الأشياء التي يمكن تعديلها في الخرج، فمثلًا عُرض الشهر رقمًا هذه المرة، لأننا استخدمنا العنصر النائب <code>d%</code> الذي سيُجبر النوع <code>time.Month</code> على استخدام رقم وليس سلسلة نصية. هنا قد نفضل عرض رقمين 08 بدلًا من رقم واحد 8، وبالتالي يجب أن نغيّر تنسيق <code>fmt.Printf</code> وفقًا لذلك، ولكن ماذا لو أردنا أيضًا إظهار الوقت بتنسيق 12 ساعة بدلًا من 24 ساعة كما هو موضح في الخرج أعلاه؟ سيتطلب هذا بعض العمليات الحسابية الخاصة ضمن الدالة <code>fmt.Printf</code>.
</p>

<p>
	بالرغم من إمكانية طباعة التواريخ والأوقات باستخدام <code>fmt.Printf</code>، ولكن يمكن أن يُصبح اﻷمر مرهقًا إذا أردنا إجراء تنسيقات أعقد، فقد ينتهي بنا الأمر بكتابة عدد كبير من الأسطر البرمجية لكل جزء نريد عرضه، أو قد نحتاج إلى إجراء عدد من العمليات الحسابية الخاصة لتحديد ما نريد عرضه.
</p>

<p>
	أنشأنا في هذا القسم برنامجًا يستخدم الدالة <code>time.Now</code> للحصول على الوقت الحالي واستخدمنا دوال مثل <code>Year</code> و <code>Hour</code> على النوع <code>time.Time</code> لعرض معلومات عن التاريخ والوقت الحاليين، كما تعرّفنا على كيفية إجراء تنسيقات بسيطة على آلية عرضهما، مثل عرض أجزاء محددة من التاريخ والوقت. رأينا أيضًا أن عملية الحصول على تنسيقات أعقد تصبح أصعب باستخدام الدالة <code>fmt.Printf</code>. لتسهيل الأمر توفر لغة جو تابع خاص لتنسيق التواريخ والأوقات ويعمل بطريقة مشابهة لعمل الدالة <code>fmt.Printf</code> التي استخدمناها في البداية.
</p>

<h2 id="-2">
	طباعة وتنسيق تواريخ محددة
</h2>

<p>
	إضافة إلى التوابع التي رأيناها في القسم السابق، يوفر النوع <code>time.Time</code> تابعًا يُدعى <code>Format</code>، يسمح لك بتقديم نسق layout على هيئة سلسلة نصية <code>string</code> (بطريقة مشابهة لتنسيق السلاسل في دالتي <code>fmt.Printf</code> و <code>fmt.Sprintf</code>). يُخبر هذا النسق التابع <code>Format</code> بالشكل الذي نريده لطباعة التاريخ والوقت.
</p>

<p>
	نستخدم خلال هذا القسم نفس الشيفرة السابقة، ولكن بطريقة أكثر إيجازًا وسهولةً من خلال التابع <code>Format</code>. بدايةً ربما يكون من الأفضل معرفة كيف يؤثر تابع <code>Format</code> على خرج التاريخ والوقت في حال لم يتغير في كل مرة نُشغّل البرنامج، أي عندما نُثبّت التاريخ والوقت. نحصل حاليًا على الوقت الحالي باستخدام <code>time.Now</code>، لذلك في كل مرة نُشغّل البرنامج سيظهر رقم مختلف. هناك دالةٌ مفيدة أخرى توفرها الحزمة <code>time</code>، وهي الدالة <code>time.Date</code> التي تسمح بتحديد تاريخ ووقت محددين لتمثيلهم مثل قيم من النوع <code>time.Time</code>.
</p>

<p>
	لنبدأ باستخدام الدالة <code>time.Date</code> بدلًا من <code>time.Now</code> في البرنامج. افتح ملف "main.go" مرةً أخرى واستبدل <code>time.Now</code> بدالة <code>time.Date</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_14" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    theTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">,</span><span class="pln"> </span><span class="lit">14</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Local</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> theTime</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تتضمن معاملات الدالة <code>time.Date</code> السنة والشهر واليوم والساعة والدقيقة والثواني من التاريخ والوقت اللذين نريد تمثيلهما للنوع <code>time.Time</code>. يمثل المعامل قبل الأخير النانو ثانية، والمعامل الأخير هو المنطقة الزمنية المطلوب إنشاء الوقت لها (نُغطي موضوع المناطق الزمنية لاحقًا). شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 -0500 CDT
</pre>

<p>
	سيكون الخرج الذي الذي سنراه في كل مرة نُشغّل فيها البرنامج هو نفسه باستثناء المنطقة الزمنية لأنها تعتمد على المنطقة الزمنية لجهاز الحاسب الذي تُشغّل عليه، وذلك لأننا نستخدم تاريخ ووقت محددين. طبعًا لا يزال تنسيق الخرج مشابهًا لما رأيناه من قبل، لأن البرنامج لا يزال يستخدم التنسيق الافتراضي للنوع <code>time.Time</code>. بعد أن أصبح لدينا تاريخ ووقت قياسي للعمل به -يمكننا استخدامه لتغيير كيفية تنسيق الوقت عند عرضه باستخدام التابع <code>Format</code>.
</p>

<h3 id="format">
	تنسيق عرض التاريخ والوقت باستخدام تابع Format
</h3>

<p>
	تتضمن العديد من <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">لغات البرمجة</a> طريقة مشابهة لتنسيق التواريخ والأوقات التي تُعرض، ولكن الطريقة التي تُنشئ بها لغة جو تصميمًا لتلك التنسيقات قد تكون مختلفةً قليلًا عن باقي لغات البرمجة. يستخدم تنسيق التاريخ في اللغات الأخرى نمطًا مشابهًا لكيفية عمل <code>Printf</code> في لغة جو، إذ يُستخدم محرف <code>%</code> متبوعًا بحرف يمثل جزءًا من التاريخ أو الوقت المطلوب إدراجه. مثلًا قد تُمثّل السنة المكونة من 4 أرقام بواسطة العنصر النائب <code>Y%</code> موضوعًا ضمن السلسلة؛ أما في لغة جو تُمثّل هذه الأجزاء من التاريخ أو الوقت بمحارف تمثل تاريخًا محددًا. مثلًا، لتمثيل سنة معينة من 4 أرقام نكتب 2006 ضمن السلسلة. تتمثل فائدة هذا النوع من التصميم في أن ما نراه في الشيفرة يمثل ما سنراه في الخرج، وبالتالي عندما نكون قادرين على رؤية شكل الخرح، فإنه يسهل علينا التحقق من أن التنسيق الذي كتبناه يُطابق ما نبحث عنه، كما أنه يسهل على المطوّرين الآخرين فهم خرج البرنامج دون تشغيل البرنامج أصلًا.
</p>

<p>
	يعتمد جو التصميم الافتراضي التالي، لعرض التاريخ والوقت:
</p>

<pre class="ipsCode">01/02 03:04:05PM '06 -0700
</pre>

<p>
	إذا نظرت إلى كل جزء من التاريخ والوقت في هذا التصميم الافتراضي، فسترى أنها تزيد بمقدار واحد لكل جزء. يأتي الشهر أولًا 01 ثم يليه يوم الشهر 02 ثم الساعة 03 ثم الدقيقة 04 ثم الثواني 05 ثم السنة 06 (أي 2006)، وأخيرًا المنطقة الزمنية 07. يسهل هذا الأمر إنشاء تنسيقات التاريخ والوقت مستقبلًا. يمكن أيضًا العثور على أمثلة للخيارات المتاحة للتنسيق في <a href="https://pkg.go.dev/time#pkg-constants" rel="external nofollow">توثيق</a> لغة جو الخاصة بحزمة <code>time</code>.
</p>

<p>
	سنستخدم الآن التابع الجديد <code>Format</code> لاستبدال وتنظيف تنسيق التاريخ الذي طبعناه في القسم الأخير. يتطلب الأمر -بدون <code>Format</code>- عدة أسطر واستدعاءات لدوال من أجل عرض ما نريده، لكن باستخدام هذه الدالة يُصبح الأمر أسهل وأنظف.
</p>

<p>
	لنفتح ملف "main.go" ونضيف استدعاء جديد للدالة <code>fmt.Println</code> ونمرر لها القيمة المُعادة من استدعاء التابع <code>Format</code> على المتغير <code>theTime</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_16" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    theTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">,</span><span class="pln"> </span><span class="lit">14</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Local</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> theTime</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">theTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="str">"2006-1-2 15:4:5"</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذا نظرنا إلى التصميم المستخدم للتنسيق، فسنرى أنه يستخدم نفس التصميم الافتراضي في تحديد كيفية تنسيق التاريخ (January 2, 2006). شيء واحد يجب ملاحظته هو أن الساعة تستخدم 15 بدلًا من 03. يوضح هذا أننا نرغب في عرض الساعة بتنسيق 24 ساعة بدلًا من تنسيق 12 ساعة. شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-8-15 14:30:45
</pre>

<p>
	نلاحظ أننا حصلنا على خرج مماثل للقسم الأخير لكن بطريقة أبسط. كل ما نحتاج إليه هو سطر واحد من التعليمات البرمجية وسلسلة تمثّل التصميم، ونترك الباقي للدالة <code>Format</code>. اعتمادًا على التاريخ أو الوقت الذي نعرضه، من المحتمل أن يكون استخدام تنسيق متغير الطول مثل التنسيق السابق عند طباعة الأرقام مباشرةً صعب القراءة لنا أو للمستخدمين أو لأي شيفرة أخرى تحاول قراءة القيمة. يؤدي استخدام 1 لتنسيق الشهر إلى عرض شهر آذار (مارس) بالرقم 3، بينما يحتاج شهر تشرين الأول (أكتوبر) محرفين ويظهر بالرقم 10.
</p>

<p>
	افتح الملف "main.go" وأضِف سطرًا إضافيًا إلى البرنامج لتجربة نسق آخر أكثر تنظيمًا. هذه المرة سنضع بادئة 0 قبل أجزاء التاريخ والوقت الفردية، ونجعل الساعة تستخدم تنسيق 12 ساعة.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_18" style=""><span class="pln">func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    theTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">,</span><span class="pln"> </span><span class="lit">14</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Local</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> theTime</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">theTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="str">"2006-1-2 15:4:5"</span><span class="pun">))</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">theTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="str">"2006-01-02 03:04:05 pm"</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-8-15 14:30:45
2021-08-15 02:30:45 pm
</pre>

<p>
	بإضافة بادئة 0 إلى أجزاء سلسلة النسق، يصبح الرقم 8 للشهر في الخرج الجديد 08، ونفس الأمر بالنسبة للساعة التي أصبحت الآن بتنسيق 12 ساعة. لاحظ أن النسق الذي نكتبه يوافق ما نراه في الخرج، وهذا يجعل الأمور أبسط.
</p>

<p>
	في بعض الحالات يكون هناك برامج أخرى تتفاعل مع البرنامج الذي لدينا، وقد يتضمن ذلك التعامل مع تنسيقات وتصميمات التواريخ في برنامجنا، كما أنه قد يكون من المتعب ومن العبء إعادة إنشاء هذه التنسيقات في كل مرة. في هذه الحالة ربما يكون من الأبسط استخدام تصميم تنسيق مُعرّف مُسبقًا ومعروف.
</p>

<h3 id="-3">
	استخدام تنسيقات معرفة مسبقا
</h3>

<p>
	هناك العديد من التنسيقات جاهزة الاستخدام والشائعة للتاريخ، مثل العلامات الزمنية Timestamps لرسائل التسجيل log messages، وفي حال أردت إنشاء هكذا تنسيقات في كل مرة تحتاجها، سيكون أمرًا مملًا ومتعبًا. تتضمن حزمة <code>time</code> تنسيقات جاهزة يمكنك استخدامها لجعل الأمور أسهل واختصار الجهد المكرر. أحد هذه التنسيقات هو RFC 3339، وهو مستند يُستخدم لتحديد كيفية عمل المعايير على الإنترنت، ويمكن بعد ذلك بناء تنسيقات RFC على بعضها. تحدد بعض تنسيقات RFC مثل RFC 2616 كيفية عمل <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-r73/" rel="">بروتوكول HTTP</a>، وهناك تنسيقات تُبنى على هذا التنسيق لتوسيع تعريف بروتوكول HTTP. لذا في حالة RFC 3339، يحدد RFC تنسيقًا قياسيًا لاستخدامه في الطوابع الزمنية على الإنترنت. التنسيق معروف ومدعوم جيدًا عبر الإنترنت، لذا فإن فرص رؤيته في مكان آخر عالية.
</p>

<p>
	تُمثّل جميع تنسيقات الوقت المُعرّفة مسبقًا في الحزمة الزمنية <code>time</code> بسلسلة ثابتة <code>const string</code> تُسمى بالتنسيق الذي تمثله. هناك اثنين من التنسيقات المتاحة للتنسيق RFC 3339، هما: <code>time.RFC3339</code> و <code>time.RFC3339Nano</code>، والفرق بينهما أن التنسيق الثاني يُمثّل الوقت بالنانو ثانية.
</p>

<p>
	لنفتح الآن ملف "main.go" ونعدّل البرنامج لاستخدام تنسيق <code>time.RFC3339Nano</code> للخرج:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_20" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    theTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">,</span><span class="pln"> </span><span class="lit">14</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Local</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> theTime</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">theTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">RFC3339Nano</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نظرًا لأن التنسيقات المعرّفة مسبقًا هي قيم من النوع <code>string</code> للتنسيق المطلوب، نحتاج فقط إلى استبدال التنسيق الذي كُنا تستخدمه عادةً بهذا التنسيق (أو أي تنسيق جاهز آخر نريده). شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-08-15T14:30:45.0000001-05:00
</pre>

<p>
	يُعد تنسيق <code>RFC 3339</code> جيدًا للاستخدام إذا كنا بحاجة إلى حفظ قيمة زمنية مثل سلسلة نصية <code>string</code> في مكان ما، إذ يمكن قراءتها بسهولة من قبل العديد من لغات البرمجة والتطبيقات الأخرى، كما أنها مختصرة قدر الإمكان.
</p>

<p>
	عدّلنا خلال هذا القسم البرنامج بحيث استخدمنا التابع <code>Format</code> لطباعة التاريخ والوقت، إذ يمنحنا هذا النسق المرن إمكانية الحصول على الخرج الأخير. أخيرًا، تعرّفنا واستخدمنا واحدًا من السلاسل النصية المعرفة مسبقًا لطباعة الوقت والتاريخ اعتمادًا على تنسيقات جاهزة ومدعومة يمكن استخدامها مباشرةً دون تكلّف عناء كتابة تصميمات تنسيق يدويًا.
</p>

<p>
	سنعدّل في القسم التالي برنامجنا لتحويل قيمة السلسلة <code>string</code> إلى النوع <code>time.Time</code> لنتمكن من معالجتها من خلال تحليلها Parsing. يمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/programming/c-sharp/dotnet/%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%AA%D8%A7%D8%B1%D9%8A%D8%AE-%D9%88%D8%A7%D9%84%D9%88%D9%82%D8%AA-%D9%81%D9%8A-dot-net-r944/" rel="">تحليل التاريخ والوقت في dot NET</a> على أكاديمية حسوب لمزيدٍ من المعلومات حول مفهوم التحليل.
</p>

<h2 id="-4">
	تحويل السلاسل النصية إلى قيم زمنية عبر تحليلها
</h2>

<p>
	قد نصادف في العديد من التطبيقات تواريخ ممثلة بسلاسل نصية من النوع <code>string</code>، وقد نرغب بإجراء بعض التعديلات أو بعض العمليات عليها؛ فمثلًا قد تحتاج إلى استخراج جزء التاريخ من القيمة، أو جزء الوقت، أو كامل القيمة للتعامل معها ..إلخ، إضافةً إلى إمكانية استخدام التابع <code>Format</code> لإنشاء قيم <code>string</code> من قيم النوع <code>time.Time</code>. تتيح لغة جو أيضًا إمكانية التحويل بالعكس، أي من سلسلة إلى <code>time.Time</code> من خلال الدالة <code>time.Parse</code>، إذ تعمل هذه الدالة بآلية مشابهة للتابع <code>Format</code>، بحيث تأخذ نسق التاريخ والوقت المتوقع إضافةً إلى قيمة السلسلة مثل معاملات.
</p>

<p>
	لنفتح ملف "main.go" ونحدّثه لاستخدام دالة <code>time.Parse</code> لتحويل <code>timeString</code> إلى متغير <code>time.Time</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_22" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    timeString </span><span class="pun">:=</span><span class="pln"> </span><span class="str">"2021-08-15 02:30:45"</span><span class="pln">
    theTime</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Parse</span><span class="pun">(</span><span class="str">"2006-01-02 03:04:05"</span><span class="pun">,</span><span class="pln"> timeString</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Could not parse time:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> theTime</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">theTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">RFC3339Nano</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	على عكس التابع <code>Format</code>، تُعيد الدالة <code>time.Parse</code> قيمة خطأ محتملة في حالة عدم تطابق قيمة السلسلة المُمرّرة مع التنسيق المُمرّر. إذا نظرنا إلى النسق المستخدم، سنرى أن النسق المُعطى لدالة <code>time.Parse</code> يستخدم 1 للشهر 2 لليوم من الشهر ..إلخ، وهذا هو نفس النسق المُستخدم في تابع <code>Format</code>.
</p>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">The time is 2021-08-15 02:30:45 +0000 UTC
2021-08-15T02:30:45Z
</pre>

<p>
	هناك بعض الأشياء التي يجب ملاحظتها في هذا الخرج، أولها هو أن المنطقة الزمنية الناتجة عن تحليل المتغير <code>timeString</code> هي المنطقة الزمنية الافتراضية، وهي إزاحة 0+، والتي تُعرف <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time" rel="external nofollow">بالتوقيت العالمي المُنسّق Coordinated Universal Time</a> -أو اختصارًا UTC أو توقيت غرينتش، فنظرًا لعدم احتواء القيمة الزمنية أو التصميم على المنطقة الزمنية، لا تعرف الدالة <code>time.Parse</code> المنطقة الزمنية التي تريد ربطها بها، وبالتالي تعدّه غرينتش. إذا كنت بحاجة لتحديد المنطقة الزمنية، يمكنك استخدام الدالة <code>time.ParseInLocation</code> لذلك. يمكن ملاحظة استخدام نسق <code>time.RFC3339Nano</code>، لكن الخرج لا يتضمن قيم بالنانو ثانية، وسبب ذلك هو أن القيم التي تحللها دالة <code>time.Parse</code> ليست نانو ثانية، وبالتالي تُضبط القيمة لتكون 0 افتراضيًا، وعندما تكون النانو ثانية 0، لن يتضمّن استخدام التنسيق <code>time.RFC3339Nano</code> قيمًا بالنانو ثانية في الخرج. يمكن للتابع <code>time.Parse</code> أيضًا استخدام أيًّا من تنسيقات الوقت المعرفة مسبقًا والمتوفرة في حزمة الوقت <code>time</code> عند تحليل قيمة سلسلة.
</p>

<p>
	لنفتح ملف "main.go" ونعدّل قيمة <code>timeString</code>، يحبث نحل مشكلة عدم ظهور قيم النانو ثانية عند استخدام تنسيق <code>time.RFC3339Nano</code> ولنحدّث معاملات <code>time.Parse</code> بطريقة توافق التعديلات الجديدة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_24" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    timeString </span><span class="pun">:=</span><span class="pln"> </span><span class="str">"2021-08-15T14:30:45.0000001-05:00"</span><span class="pln">
    theTime</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Parse</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">RFC3339Nano</span><span class="pun">,</span><span class="pln"> timeString</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Could not parse time:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> theTime</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">theTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">RFC3339Nano</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-08-15T14:30:45.0000001-05:00
</pre>

<p>
	يُظهر خرج <code>Format</code> هذه المرة بأن <code>time.Parse</code> كانت قادرة على تحليل كلٍ من المنطقة الزمنية وقيم النانو ثانية من متغير <code>timeString</code>.
</p>

<p>
	تعلمنا في هذا القسم كيفية استخدام دالة <code>time.Parse</code> لتحليل سلسلة <code>string</code> تُمثّل تاريخًا معينًا وفقًا لتصميم معين، وتعلمنا كيفية تحديد المنطقة الزمنية للسلسلة المُحللة. سنتعرّف في القسم التالي على كيفية التعامل مع المناطق الزمنية بطريقة أوسع وكيفية التبديل بين المناطق الزمنية المختلفة من خلال الميزات التي يوفرها النوع <code>time.Time</code>.
</p>

<h2 id="-5">
	التعامل مع المناطق الزمنية
</h2>

<p>
	يُشاع في التطبيقات التي يستخدمها مُستخدمين من أنحاء مختلفة من العالم -تخزّين التواريخ والأوقات باستخدام توقيت غرينيتش UTC، ثم التحويل إلى التوقيت المحلي للمستخدم عند الحاجة. يسمح ذلك بتخزين البيانات بتنسيق موحّد ويجعل الحسابات بينها أسهل، نظرًا لأن التحويل مطلوب فقط عند عرض التاريخ والوقت للمستخدم.
</p>

<p>
	أنشأنا خلال المقال برنامجًا يعمل وفقًا للمنطقة الزمنية المحلية المتواجدين بها. لحفظ قيم البيانات من النوع <code>time.Time</code> على أنها قيم UTC، نحتاج أولًا إلى تحويلها إلى قيم UTC باستخدام التابع <code>UTC</code> الذي يُعيد القيمة الزمنية الموافقة لنظام UTC من متغير التاريخ الذي يستدعيها.
</p>

<p>
	<strong>ملاحظة:</strong> يجري التحويل هنا من المنطقة المحلية التي يوجد بها الحاسب الذي نستخدمه إلى UTC، وبالتالي إذا كان حاسبنا موجود ضمن منطقة UTC لن نرى فرقًا.
</p>

<p>
	لنفتح ملف "main.go" ونُطبق هذا الكلام:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_26" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    theTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">,</span><span class="pln"> </span><span class="lit">14</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Local</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> theTime</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">theTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">RFC3339Nano</span><span class="pun">))</span><span class="pln">

    utcTime </span><span class="pun">:=</span><span class="pln"> theTime</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The UTC time is"</span><span class="pun">,</span><span class="pln"> utcTime</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">utcTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">RFC3339Nano</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	هذه المرة يُنشئ البرنامج متغير <code>theTime</code> على أنه فيمة من النوع <code>time.Time</code> وفقًا لمنطقتنا الزمنية، ثم يطبعه بتنسيقين مختلفين، ثم يستخدم تابع <code>UTC</code> للتحويل من المنطقة الزمنية المحلية الحالية إلى المنطقة الزمنية UTC.
</p>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-08-15T14:30:45.0000001-05:00
The UTC time is 2021-08-15 19:30:45.0000001 +0000 UTC
2021-08-15T19:30:45.0000001Z
</pre>

<p>
	سيختلف الخرج اعتمادًا على المنطقة الزمنية المحلية، ولكن سنرى في الخرج السابق أن المنطقة الزمنية في المرة الأولى كانت بتوقيت CDT (التوقيت الصيفي المركزي لأمريكا الشمالية)، وهي "5-" ساعات من UTC. بعد استدعاء تابع <code>UTC</code> وطباعة الوقت وفق نظام UTC، سنرى أن الوقت تغير من 14 إلى 19، لأن تحويل الوقت إلى UTC أضاف خمس ساعات. من الممكن أيضًا تحويل UTC إلى التوقيت المحلي باستخدام التابع <code>Local</code> على متغير النوع <code>time.Time</code> بنفس الأسلوب.
</p>

<p>
	افتح ملف "main.go" مرةً أخرى، وضِف استدعاءً للتابع <code>Local</code> على المتغير <code>utcTime</code> لتحويله مرةً أخرى إلى المنطقة الزمنية المحلية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_28" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    theTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">,</span><span class="pln"> </span><span class="lit">14</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Local</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> theTime</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">theTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">RFC3339Nano</span><span class="pun">))</span><span class="pln">

    utcTime </span><span class="pun">:=</span><span class="pln"> theTime</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The UTC time is"</span><span class="pun">,</span><span class="pln"> utcTime</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">utcTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">RFC3339Nano</span><span class="pun">))</span><span class="pln">

    localTime </span><span class="pun">:=</span><span class="pln"> utcTime</span><span class="pun">.</span><span class="typ">Local</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The Local time is"</span><span class="pun">,</span><span class="pln"> localTime</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">localTime</span><span class="pun">.</span><span class="typ">Format</span><span class="pun">(</span><span class="pln">time</span><span class="pun">.</span><span class="pln">RFC3339Nano</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ليكون الخرج:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-08-15T14:30:45.0000001-05:00
The UTC time is 2021-08-15 19:30:45.0000001 +0000 UTC
2021-08-15T19:30:45.0000001Z
The Local time is 2021-08-15 14:30:45.0000001 -0500 CDT
2021-08-15T14:30:45.0000001-05:00
</pre>

<p>
	نلاحظ أنه جرى التحويل من UTC إلى المنطقة الزمنية المحلية، وهذا يعني طرح خمس ساعات من UTC، وتغيير الساعة من 19 إلى 14.
</p>

<p>
	عدّلنا خلال هذا القسم البرنامج لتحويل البيانات الزمنية من منطقة زمنية محلية إلى توقيت غرينتش UTC باستخدام التابع <code>UTC</code>، كما أجرينا تحويلًا معاكسًا باستخدام التابع <code>Local</code>.
</p>

<p>
	تتمثل إحدى الميزات الإضافية التي تقدمها حزمة <code>time</code> والتي يمكن أن تكون مفيدة في تطبيقاتك -في تحديد ما إذا كان وقت معين قبل أو بعد وقت آخر.
</p>

<h2 id="-6">
	مقارنة الأوقات الزمنية
</h2>

<p>
	قد تكون مقارنة تاريخين مع بعضهما صعبة أحيانًا، بسبب المتغيرات التي يجب أخذها بالحسبان عند المقارنة، مثل الحاجة إلى الانتباه إلى المناطق الزمنية أو حقيقة أن الأشهر لها عدد مختلف من الأيام عن بعضها. توفر الحزمة الزمنية <code>time</code> تابعين لتسهيل ذلك، هما: <code>Before</code> و <code>After</code> اللذان يمكن تطبيقهما على متغيرات النوع <code>time.Time</code>. تقبل هذه التوابع قيمةً زمنيةً واحدة وتعيدان إما <code>true</code> أو <code>false</code> اعتمادًا على ما إذا كان الوقت المُمثّل بالمتغير الذي يستدعيهما قبل أو بعد الوقت المقدم.
</p>

<p>
	لنفتح ملف "main.go" ونرى كيف تجري الأمور:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_30" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    firstTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">,</span><span class="pln"> </span><span class="lit">14</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The first time is"</span><span class="pun">,</span><span class="pln"> firstTime</span><span class="pun">)</span><span class="pln">

    secondTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">12</span><span class="pun">,</span><span class="pln"> </span><span class="lit">25</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40</span><span class="pun">,</span><span class="pln"> </span><span class="lit">55</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The second time is"</span><span class="pun">,</span><span class="pln"> secondTime</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"First time before second?"</span><span class="pun">,</span><span class="pln"> firstTime</span><span class="pun">.</span><span class="typ">Before</span><span class="pun">(</span><span class="pln">secondTime</span><span class="pun">))</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"First time after second?"</span><span class="pun">,</span><span class="pln"> firstTime</span><span class="pun">.</span><span class="typ">After</span><span class="pun">(</span><span class="pln">secondTime</span><span class="pun">))</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Second time before first?"</span><span class="pun">,</span><span class="pln"> secondTime</span><span class="pun">.</span><span class="typ">Before</span><span class="pun">(</span><span class="pln">firstTime</span><span class="pun">))</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Second time after first?"</span><span class="pun">,</span><span class="pln"> secondTime</span><span class="pun">.</span><span class="typ">After</span><span class="pun">(</span><span class="pln">firstTime</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">The first time is 2021-08-15 14:30:45.0000001 +0000 UTC
The second time is 2021-12-25 16:40:55.0000002 +0000 UTC
First time before second? true
First time after second? false
Second time before first? false
Second time after first? true
</pre>

<p>
	بما أننا نستخدم في الشيفرة أعلاه نظام الوقت UTC، فيجب أن يكون الخرج متشابهًا بغض النظر عن المنطقة الزمنية. نلاحظ أنه عند استخدام تابع <code>Before</code> مع المتغير <code>firstTime</code> وتمرير الوقت <code>secondTime</code> الذي نريد المقارنة به، ستكون النتيجة <code>true</code> أي أن ‎2021-08-15 قبل ‎2021-12-25. عند استخدام <code>After</code> مع المتغير <code>firstTime</code> وتمرير <code>secondTime</code>، تكون النتيجة <code>false</code> لأن ‎2021-08-15 ليس بعد 2021-12-25. يؤدي تغيير ترتيب استدعاء التوابع على <code>secondTime</code> إلى إظهار نتائج معاكسة.
</p>

<p>
	هناك طريقة أخرى لمقارنة القيم الزمنية في حزمة الوقت وهي تابع <code>Sub</code>، الذي يطرح تاريخًا من تاريخ آخر ويُعيد قيمةً من نوع جديد هو <code>time.Duration</code>. على عكس قيم النوع <code>time.Time</code> التي تمثل نقاط زمنية مطلقة، تمثل قيمة <code>time.Duration</code> فرقًا في الوقت. مثلًا، قد تعني عبارة "في ساعة واحدة" مدة duration لأنها تعني شيئًا مختلفًا بناءً على الوقت الحالي من اليوم، لكنها تمثّل "عند الظهر" وقتًا محددًا ومطلقًا. تستخدم لغة جو النوع <code>time.Duration</code> في بعض الحالات، مثل الوقت الذي نريد فيه تحديد المدة التي يجب أن تنتظرها الدالة قبل إعادة خطأ أو كما هو الحال هنا، إذ نحتاج إلى معرفة مقدار الزمن بين وقت وآخر.
</p>

<p>
	لنفتح الآن ملف "main.go" ونستخدم التابع <code>Sub</code> على المتغير <code>firstTime</code> و <code>secondTime</code> ونطبع النتائج:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_32" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    firstTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">,</span><span class="pln"> </span><span class="lit">14</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The first time is"</span><span class="pun">,</span><span class="pln"> firstTime</span><span class="pun">)</span><span class="pln">

    secondTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">12</span><span class="pun">,</span><span class="pln"> </span><span class="lit">25</span><span class="pun">,</span><span class="pln"> </span><span class="lit">16</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40</span><span class="pun">,</span><span class="pln"> </span><span class="lit">55</span><span class="pun">,</span><span class="pln"> </span><span class="lit">200</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The second time is"</span><span class="pun">,</span><span class="pln"> secondTime</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Duration between first and second time is"</span><span class="pun">,</span><span class="pln"> firstTime</span><span class="pun">.</span><span class="typ">Sub</span><span class="pun">(</span><span class="pln">secondTime</span><span class="pun">))</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Duration between second and first time is"</span><span class="pun">,</span><span class="pln"> secondTime</span><span class="pun">.</span><span class="typ">Sub</span><span class="pun">(</span><span class="pln">firstTime</span><span class="pun">))</span></pre>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">The first time is 2021-08-15 14:30:45.0000001 +0000 UTC
The second time is 2021-12-25 16:40:55.0000002 +0000 UTC
Duration between first and second time is -3170h10m10.0000001s
Duration between second and first time is 3170h10m10.0000001s
</pre>

<p>
	يوضح الناتج أعلاه أن هناك 3170 ساعة و 10 دقائق و 10 ثوان و 100 نانو ثانية بين التاريخين، وهناك بعض الأشياء التي يجب ملاحظتها في الخرج، أولها أن المدة بين المرة الأولى <code>first time</code> والثانية <code>second time</code> قيمة سالبة، وهذا يشير إلى أن الوقت الثاني بعد الأول، وسيكون الأمر مماثلًا إذا طرحنا 5 من 0 وحصلنا على 5-. نلاحظ أيضًا أن أكبر وحدة قياس للمدة هي ساعة، لذلك فهي لا تُقسم إلى أيام أو شهور. بما أن عدد الأيام في الشهر غير متسق وقد يكون لكلمة "اليوم" معنى مختلف عند التبديل للتوقيت الصيفي، فإن قياس الساعة هو أدق مقياس، إذ أنه لا يتقلب.
</p>

<p>
	عدّلنا البرنامج خلال هذا القسم مرتين للمقارنة بين الأوقات الزمنية باستخدام ثلاث توابع مختلفة؛ إذ استخدمنا أولًا التابعين <code>Before</code> و <code>After</code> لتحديد ما إذا كان الوقت قبل أو بعد وقت آخر؛ ثم استخدمنا <code>Sub</code> لمعرفة المدة بين وقتين.
</p>

<p>
	ليس الحصول على المدة بين وقتين الطريقة الوحيدة التي تستخدم بها الحزمة الزمنية الدالة <code>time.Duration</code>، إذ يمكننا أيضًا استخدامها لإضافة وقت أو إزالته من قيمة من النوع <code>time.Time</code>.
</p>

<h2 id="-7">
	إضافة وطرح الأوقات الزمنية
</h2>

<p>
	إحدى العمليات الشائعة عند كتابة التطبيقات التي تستخدم التواريخ والأوقات هي تحديد وقت الماضي أو المستقبل بناءً على وقت آخر مرجعي، فمثلًا يمكن استخدام هذا الوقت المرجعي لتحديد موعد تجديد الاشتراك بخدمة، أو ما إذا كان قد مر قدرٌ معينٌ من الوقت منذ آخر مرة جرى فيها التحقق من أمر معين. توفر حزمة الوقت <code>time</code> طريقةً لمعالجة الأمر، وذلك من خلال تحديد المدد الزمنية الخاصة بنا باستخدام متغيرات من النوع <code>time.Duration</code>.
</p>

<p>
	إنشاء قيمة من النوع <code>time.Duration</code> بسيط جدًا والعمليات الرياضية عليه متاحة كما لو أنه متغير عددي عادي؛ فمثلًا لإنشاء مدة زمنية <code>time.Duration</code> تُمثّل ساعة أو ساعتين أو ثلاثة ..إلخ، يمكننا استخدام <code>time.Hour</code> مضروبةً بعدد الساعات التي نريدها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_34" style=""><span class="pln">oneHour </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Hour</span><span class="pln">
twoHours </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Hour</span><span class="pln">
tenHours </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Hour</span></pre>

<p>
	نستخدم <code>time.Minute</code> و <code>time.Second</code> في حال الدقائق والثواني:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_36" style=""><span class="pln">tenMinutes </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Minute</span><span class="pln">
fiveSeconds </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">5</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Second</span></pre>

<p>
	يمكن أيضًا إضافة مدة زمنية إلى مدة أخرى للحصول على مجموعهما. لنفتح ملف <code>main.go</code> ونطبق ذلك:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_38" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    toAdd </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Hour</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"1:"</span><span class="pun">,</span><span class="pln"> toAdd</span><span class="pun">)</span><span class="pln">

    toAdd </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Minute</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"2:"</span><span class="pun">,</span><span class="pln"> toAdd</span><span class="pun">)</span><span class="pln">

    toAdd </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Second</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"3:"</span><span class="pun">,</span><span class="pln"> toAdd</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">1: 1h0m0s
2: 1h1m0s
3: 1h1m1s
</pre>

<p>
	نلاحظ من الخرج أن المدة الأولى المطبوعة هي ساعة واحدة، وهذا يتوافق مع <code>‎1 * time.Hour</code> في الشيفرة. أضفنا بعد ذلك <code>‎1 * time.Minute</code> إلى القيمة <code>toAdd</code> أي ساعة ودقيقة واحدة. أخيرًا أضفنا <code>‎1 * time.Second</code> إلى قيمة <code>toAdd</code>، لينتج لدينا ساعة واحدة ودقيقة واحدة وثانية في المدة الزمنية الممثلة بالنوع <code>time.Duration</code>.
</p>

<p>
	يمكن أيضًا دمج عمليات إضافة المُدد معًا في عبارة واحدة، أو طرح مدة من أخرى:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_40" style=""><span class="pln">oneHourOneMinute </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Hour</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Minute</span><span class="pln">
tenMinutes </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Hour</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Minute</span></pre>

<p>
	لنفتح ملف "main.go" ونعدله بحيث نستخدم توليفة من هذه العمليات لطرح دقيقة واحدة وثانية واحدة من <code>toAdd</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_43" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

    </span><span class="pun">...</span><span class="pln">

    toAdd </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Second</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"3:"</span><span class="pun">,</span><span class="pln"> toAdd</span><span class="pun">)</span><span class="pln">

    toAdd </span><span class="pun">-=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">*</span><span class="pln">time</span><span class="pun">.</span><span class="typ">Minute</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">*</span><span class="pln">time</span><span class="pun">.</span><span class="typ">Second</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"4:"</span><span class="pun">,</span><span class="pln"> toAdd</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيعطي الخرج التالي:
</p>

<pre class="ipsCode">1: 1h0m0s
2: 1h1m0s
3: 1h1m1s
4: 1h0m0s
</pre>

<p>
	يظهر السطر الرابع من الخرج، والذي يُمثّل ناتج طرح المجموع <code>‎1*time.Minute + 1*time.Second</code> من <code>toAdd</code> أن العملية ناجحة، إذ حصلنا على قيمة ساعة واحدة (طُرحت الثانية والدقيقة).
</p>

<p>
	يتيح لنا استخدام هذه المُدد المقترنة بالتابع <code>Add</code> للنوع <code>time.Time</code> حساب المدد الزمنية بين نقطتين زمنيتين إحداهما نقطة مرجعية، مثل حساب المدة منذ أول يوم اشتراك في خدمة معينة حتى اللحظة. لرؤية مثال آخر نفتح ملف "main.go" ونجعل قيمة <code>toAdd</code> تساوي 24 ساعة أي <code>‎24 * time.Hour</code>، ثم نستخدم التابع <code>Add</code> على قيمة متغير من النوع <code>time.Time</code> لمعرفة الوقت الذي سيكون بعد 24 ساعة من تلك النقطة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_45" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    theTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">,</span><span class="pln"> </span><span class="lit">14</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> theTime</span><span class="pun">)</span><span class="pln">

    toAdd </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">24</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Hour</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Adding"</span><span class="pun">,</span><span class="pln"> toAdd</span><span class="pun">)</span><span class="pln">

    newTime </span><span class="pun">:=</span><span class="pln"> theTime</span><span class="pun">.</span><span class="typ">Add</span><span class="pun">(</span><span class="pln">toAdd</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The new time is"</span><span class="pun">,</span><span class="pln"> newTime</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 +0000 UTC
Adding 24h0m0s
The new time is 2021-08-16 14:30:45.0000001 +0000 UTC
</pre>

<p>
	نلاحظ أن إضافة 24 ساعة إلى التاريخ ‎2021-08-15 سينتج عنه التاريخ الجديد ‎2021-08-16. يمكننا أيضًا استخدام التابع <code>Add</code> لطرح الوقت، إذ سنستخدم قيمة سالبة ببساطة، فيصبح الأمر كما لو أننا نستخدم التابع <code>Sub</code>. لنفتح ملف "main.go" ونطبق هذا الكلام، fحيث سنطرح هذه المرة 24 ساعة، أي يجب أن نستخدم قيمة "24-".
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_191_47" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    theTime </span><span class="pun">:=</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Date</span><span class="pun">(</span><span class="lit">2021</span><span class="pun">,</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">,</span><span class="pln"> </span><span class="lit">14</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="lit">45</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">.</span><span class="pln">UTC</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The time is"</span><span class="pun">,</span><span class="pln"> theTime</span><span class="pun">)</span><span class="pln">

    toAdd </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">24</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Hour</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Adding"</span><span class="pun">,</span><span class="pln"> toAdd</span><span class="pun">)</span><span class="pln">

    newTime </span><span class="pun">:=</span><span class="pln"> theTime</span><span class="pun">.</span><span class="typ">Add</span><span class="pun">(</span><span class="pln">toAdd</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The new time is"</span><span class="pun">,</span><span class="pln"> newTime</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	`
</p>

<p>
	شغّل البرنامج باستخدام <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">The time is 2021-08-15 14:30:45.0000001 +0000 UTC
Adding -24h0m0s
The new time is 2021-08-14 14:30:45.0000001 +0000 UTC
</pre>

<p>
	نلاحظ من الخرج أنه قد طُرح 24 ساعة من الوقت الأصلي.
</p>

<p>
	استخدمنا خلال هذا القسم التوابع <code>time.Hour</code> و <code>time.Minute</code> و <code>time.Second</code> لإنشاء قيم من النوع <code>time.Duration</code>. استخدمنا أيضًا قيم النوع <code>time.Duration</code> مع التابع <code>Add</code> للحصول على قيمة جديدة لمتغير من النوع <code>time.Time</code>. سيكون لدينا -من خلال التوابع <code>time.Now</code> و <code>Add</code> و <code>Before</code> و <code>After</code>- القدرة الكافية على التعامل مع التاريخ والوقت في التطبيقات.
</p>

<h2 id="-8">
	الخاتمة
</h2>

<p>
	استخدمنا خلال هذا المقال الدالة <code>time.Now</code> للحصول على التاريخ والوقت المحلي الحالي على شكل قيم من النوع <code>time.Time</code>، ثم استخدمنا التوابع <code>Year</code> و <code>Month</code> و <code>Hour</code> ..إلخ، للحصول على معلومات محددة من التاريخ والوقت. استخدمنا بعد ذلك التابع <code>Format</code> لطباعة الوقت بالطريقة التي نريدها وفقًا لتنسيق مخصص نقدمه أو وفقًا لتنسيق جاهز مُعرّف مسبقًا. استخدمنا الدالة <code>time.Parse</code> لتحويل قيمة سلسلة نصية <code>string</code> تمثّل بيانات زمنية إلى قيمة من النوع <code>time.Time</code> لنتمكن من التعامل معها. تعلمنا أيضًا كيفية التبديل من المنطقة الزمنية المحلية إلى منطقة غرينتش UTC باستخدام التابع <code>Local</code> والعكس. تعلمنا استخدام توابع <code>Sub</code> و <code>Add</code> لإجراء عمليات جمع وطرح على البيانات الزمنية، لإيجاد الفرق بين مدتين زمنيتين أو لإضافة مدة زمنية إلى بيانات زمنية محددة.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-dates-and-times-in-go" rel="external nofollow">How To Use Dates and Times in Go</a> لصاحبه Kristin Davidson.
</p>

<h2 id="-9">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D8%B1%D9%81%D8%A7%D9%82-%D9%85%D8%B9%D9%84%D9%88%D9%85%D8%A7%D8%AA-%D8%A5%D8%B6%D8%A7%D9%81%D9%8A%D8%A9-%D8%B9%D9%86-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2153/" rel="">كيفية إرفاق معلومات إضافية عن الأخطاء في لغة Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC%D9%83-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D9%81%D9%8A-%D8%AC%D9%88-go-r1788/" rel="">كتابة برنامجك الأول في جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-go-%D8%B9%D9%84%D9%89-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AE%D8%AA%D9%84%D9%81%D8%A9-r2022/" rel="">بناء تطبيقات لغة Go على أنظمة التشغيل والمعماريات المختلفة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2163</guid><pubDate>Sun, 05 Nov 2023 13:00:00 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x625;&#x631;&#x641;&#x627;&#x642; &#x645;&#x639;&#x644;&#x648;&#x645;&#x627;&#x62A; &#x625;&#x636;&#x627;&#x641;&#x64A;&#x629; &#x639;&#x646; &#x627;&#x644;&#x623;&#x62E;&#x637;&#x627;&#x621; &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648;</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D8%B1%D9%81%D8%A7%D9%82-%D9%85%D8%B9%D9%84%D9%88%D9%85%D8%A7%D8%AA-%D8%A5%D8%B6%D8%A7%D9%81%D9%8A%D8%A9-%D8%B9%D9%86-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2153/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_10/--------Go.png.c648ade0d9d26349bb3ee2809513fb2d.png" /></p>
<p>
	عندما تفشل دالة في <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a>، فإنها تُعيد قيمةً باستخدام الواجهة <code>error</code> للسماح للمُستدعي بمعالجة الخطأ. في كثير من الأحيان يستخدم المطورون الدالة <code>fmt.Errorf</code> من الحزمة <code>fmt</code> لإعادة هذه القيم. قبل الإصدار 1.13 من لغة جو، كان الجانب السلبي لاستخدام هذه الدالة هو أننا سنفقد المعلومات المتعلقة بالأخطاء. لحل هذه المشكلة استخدم المطورون حزمًا توفر أساليب لتغليف wrap هذه الأخطاء داخل أخطاء أخرى، أو إنشاء أخطاء مخصصة من خلال تنفيذ التابع <code>Error() string</code> على أحد أنواع خطأ <code>struct</code> الخاصة بهم. أحيانًا، يكون إنشاء هذه الأنواع من <code>struct</code> مملًا إذا كان لديك عدد من الأخطاء التي لا تحتاج إلى المعالجة الصريحة من قبل من المُستدعي.
</p>

<p>
	جاء إصدار جو 1.13 مع ميزات لتسهيل التعامل مع هذه الحالات، وتتمثل إحدى الميزات في القدرة على تغليف الأخطاء باستخدام دالة <code>fmt.Errorf</code> بقيمة خطأ <code>error</code> يمكن فكها لاحقًا للوصول إلى الأخطاء المغلفة. بالتالي، أنهى تضمين دالة تغليف للأخطاء داخل مكتبة جو القياسية الحاجة إلى استخدام طرق ومكتبات خارجية. إضافةً إلى ذلك، تجعل الدالتان <code>errors.Is</code> و <code>errors.As</code> من السهل تحديد ما إذا كان خطأ محدد مُغلّف في أي مكان داخل خطأ ما، ويمنحنا الوصول إلى هذا الخطأ مباشرةً دون الحاجة إلى فك جميع الأخطاء يدويًا.
</p>

<p>
	سننشئ في هذا المقال برنامجًا يستخدم هذه الدوال لإرفاق معلومات إضافية مع الأخطاء التي تُعاد من الدوال، ثم سننشئ بنية <code>struct</code> للأخطاء المخصصة والتي تدعم دوال التغليف وفك التغليف.
</p>

<h2>
	المتطلبات
</h2>

<p>
	لتتابع هذا المقال، يجب أن تستوفي الشروط التالية:
</p>

<ul>
	<li>
		إصدار مُثبّت من جو 1.13 أو أعلى، ويمكنك الاستعانة بمقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a> لإعداده.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
	<li>
		(اختياري) قراءة مقال <a href="https://academy.hsoub.com/programming/go/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1891/" rel="">معالجة الأخطاء في لغة جو Go </a>، قد يكون مفيدًا للحصول على شرح أكثر تعمقًا لمعالجة الأخطاء. عمومًا نحن نغطي بعض الموضوعات منها في هذا المقال، لكن بشرح عالي المستوى.
	</li>
	<li>
		(اختياري) هذ المقال نوعًا ما هو امتداد لمقال <a href="https://academy.hsoub.com/programming/go/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%AD%D8%A7%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D8%A7%D9%86%D9%87%D9%8A%D8%A7%D8%B1-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1901/" rel="">معالجة حالات الانهيار في لغة جو Go </a> مع الإشارة إلى بعض الميزات. قراءة المقالة السابقة مهم أو مفيد، لكن ليس ضروري.
	</li>
</ul>

<h2>
	إعادة ومعالجة الأخطاء في لغة جو
</h2>

<p>
	تُعد معالجة الأخطاء من الممارسات الجيدة التي تحدث أثناء تنفيذ البرامج حتى لا يراها المستخدمون أبدًا، لكن لا بُد من التعرّف عليها أولًا لمعالجتها. يمكننا في لغة جو معالجة الأخطاء في البرامج من خلال إعادة معلومات متعلقة بهذه الأخطاء من الدوال التي حدث فيها الخطأ باستخدام واجهة <code>interface</code> من نوع خاص هو <code>error</code>، إذ يسمح استخدام هكذا واجهة لأي نوع بيانات في لغة جو أن يُعاد مثل قيمة من نوع <code>error</code> طالما أن ذلك النوع لديه تابع <code>Error() string</code> معرّف. توفر مكتبة جو القياسية دوالًا، مثل <code>fmt.Errorf</code> للتعامل مع هذا الأمر وإعادة خطأ <code>error</code>.
</p>

<p>
	سننشئ في هذا المقال برنامجًا مع دالة تستخدم <code>fmt.Errorf</code> لإعادة خطأ <code>error</code>، وسنضيف أيضًا معالج خطأ للتحقق من الأخطاء التي يمكن أن ترجعها الدالة. يمكنك العودة للمقال <a href="https://academy.hsoub.com/programming/go/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1891/" rel="">معالجة الأخطاء في لغة جو Go </a>.
</p>

<p>
	لدى معظم المطورين مجلد يضعون داخله مشاريعهم، وسنستخدم في هذا المقال مجلدًا باسم "projects". لننشئ هذا المجلد وننتقل إليه:
</p>

<pre class="ipsCode">$ mkdir projects
$ cd projects
</pre>

<p>
	سننشئ داخل مجلد "projects" مجلدًا باسم "errtutorial" لوضع البرنامج داخله:
</p>

<pre class="ipsCode">$ mkdir errtutorial
</pre>

<p>
	سننتقل إليه:
</p>

<pre class="ipsCode">$ cd errtutorial
</pre>

<p>
	سنستخدم الآن الأمر <code>go mod init</code> لإنشاء وحدة <code>errtutorial</code>:
</p>

<pre class="ipsCode">$ go mod init errtutorial
</pre>

<p>
	نفتح الآن ملف "main.go" من مجلد <code>errtutorial</code> باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">محرر نانو nano أو أي محرر آخر تريده</a>:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	الآن، سنكتب البرنامج، الذي سيمر ضمن حلقة على الأرقام من 1 إلى 3 ويحاول أن يحدد ما إذا كان أحد هذه الأرقام صالحًا أم لا باستخدام دالة تُدعى <code>validateValue</code>؛ فإذا لم يكن الرقم صالحًا، سيستخدم البرنامج الدالة <code>fmt.Errorf</code> لتوليد قيمة من النوع <code>error</code> تُعاد منها، إذ تسمح هذه الدالة بإنشاء قيمة <code>error</code>، بحيث تكون رسالة الخطأ هي الرسالة التي تقدمها للدالة، وهي تعمل بطريقة مشابهة للدالة <code>fmt.Printf</code>، لكن تُعاد مثل خطأ بدلًا من طباعة الرسالة على الشاشة.
</p>

<p>
	الآن، ستكون هناك عملية تحقق من قيمة الخطأ في الدالة الرئيسية <code>main</code>، لمعرفة ما إذا كانت القيمة هي <code>nil</code> أو لا؛ فإذا كانت <code>nil</code> ستكون الدالة نجحت في التنفيذ دون أخطاء وسنرى الرسالة <code>!valid</code>، وإلا سيُطبع الخطأ الحاصل.
</p>

<p>
	لنضع الآن هذه الشيفرة داخل ملف "main.go":
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_8" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func validateValue</span><span class="pun">(</span><span class="pln">number </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> number </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Errorf</span><span class="pun">(</span><span class="str">"that's odd"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> number </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Errorf</span><span class="pun">(</span><span class="str">"uh oh"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> nil
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> num </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> num</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"validating %d... "</span><span class="pun">,</span><span class="pln"> num</span><span class="pun">)</span><span class="pln">
        err </span><span class="pun">:=</span><span class="pln"> validateValue</span><span class="pun">(</span><span class="pln">num</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"there was an error:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"valid!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تأخذ الدالة <code>validateValue</code> في البرنامج السابق رقمًا وتعيد خطأً اعتمادًا على ما إذا كانت القيمة المُمررة صالحة أم لا. في مثالنا العدد 1 غير صالح، وبالتالي تُعيد الدالة خطأ مع رسالة <code>that's odd</code>. الرقم 2 غير صالح ويؤدي إلى إرجاع خطأ مع رسالة <code>uh oh</code>. تستخدم الدالة <code>validateValue</code> الدالة <code>fmt.Errorf</code> لتوليد قيمة الخطأ المُعادة، إذ تُعد الدالة <code>fmt.Errorf</code> ملائمة لإعادة الأخطاء، لأنها تسمح بتنسيق رسالة خطأ باستخدام تنسيق مشابه للدالة <code>fmt.Printf</code> أو <code>fmt.Sprintf</code> دون الحاجة إلى تمرير هذه السلسلة <code>string</code> إلى <code>errors.New</code>.
</p>

<p>
	ستبدأ <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%AD%D9%84%D9%82%D8%A9-%D8%A7%D9%84%D8%AA%D9%83%D8%B1%D8%A7%D8%B1-for-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1950/" rel="">حلقة <code>for</code></a> داخل الدالة <code>main</code> بالمرور على الأرقام من 1 إلى 3 وتخزّن القيمة ضمن المتغير <code>num</code>. سيؤدي استدعاء <code>fmt.Printf</code> داخل متن الحلقة إلى طباعة الرقم الذي يتحقق منه البرنامج حاليًا. بعد ذلك، سيجري استدعاء الدالة <code>validateValue</code> مع تمرير المتغير <code>num</code> (الذي نريد التحقق من صلاحيته)، وتخزين نتيجة الاستدعاء هذا في المتغير <code>err</code>. أخيرًا، إذا كانت قيمة <code>err</code> ليست <code>nil</code>، فهذا يعني أن خطًأ ما قد حدث وستُطبع رسالة الخطأ باستخدام <code>fmt.Println</code>، أما إذا كانت قيمته <code>nil</code>، فهذا يعني أن الرقم صالح وسنرى على الخرج <code>"!valid"</code>.
</p>

<p>
	بعد حفظ التغييرات الأخيرة يمكننا تشغيل ملف البرنامج "main.go" باستخدام الأمر <code>go run</code> من المجلد "errtutorial":
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيُظهر الخرج أن البرنامج قد تحقق من صحة كل رقم، وأن الرقم 1 والرقم 2 أديا إلى إظهار الأخطاء المناسبة لهما:
</p>

<pre class="ipsCode">validating 1... there was an error: that's odd
validating 2... there was an error: uh oh
validating 3... valid!
</pre>

<p>
	نلاحظ أن البرنامج يحاول التحقق من الأرقام الثلاثة من خلال الدالة <code>validateValue</code>؛ ففي المرة الأولى قد حصل على قيمة غير صالحة هي 1 فطبع رسالة الخطأ <code>that's odd</code>؛ وحصل في المرة الثانية على قيمة غير صالحة أيضًا هي 2 فطبع رسالة خطأ مختلفة هي <code>uh oh</code>؛ وحصل في المرة الثالثة على الرقم 3 وهي قيمة صالحة فأعاد قيمة <code>!valid</code> وفي هذه الحالة تكون قيمة الخطأ المُعادة هي <code>nil</code> إشارةً إلى عدم حدوث أي مشاكل وأن الرقم صالح. وفقًا للطريقة التي كُتبت فيها الدالة <code>validateValue</code>، يمكننا أن نقول أنها ستُعيد <code>nil</code> من أجل أي قيمة باستثناء الرقمين 1 و2.
</p>

<p>
	استخدمنا في هذا القسم الدالة <code>fmt.Errorf</code> لإنشاء قيم خطأ مُعادة من دالة، وأضفنا مُعالج خطأ يطبع رسالة الخطأ عند حصوله من الدالة. قد يكون من المفيد أحيانًا معرفة معنى الخطأ، وليس مجرد حدوث خطأ. سنتعلم في القسم التالي كيفية تخصيص معالجة الأخطاء لحالات محددة.
</p>

<h2>
	معالجة أخطاء محددة باستخدام أخطاء الحارس Sentinel errors
</h2>

<p>
	عندما نستقبل قيمة خطأ من دالة، فإن أبسط طريقة لمعالجة هذا الخطأ هي التحقق من قيمته إذا كانت <code>nil</code> أو لا، إذ يخبرنا هذا ما إذا كانت الدالة تتضمن خطأً، ولكن قد نرغب أحيانًا في تخصيص معالجة الأخطاء لحالة خطأ محددة. لنتخيل أن لدينا شيفرة مرتبطة بخادم بعيد، وأن معلومات الخطأ الوحيدة التي نحصل عليها هي "لديك خطأ". قد نرغب في معرفة ما إذا كان الخطأ بسبب عدم توفر الخادم، أو إذا كانت بيانات اعتماديات الاتصال connection credentials الخاصة بنا غير صالحة. إذا كنا نعلم أن الخطأ يعني أن بيانات اعتماد المستخدم خاطئة، فقد نرغب في إعلام المستخدم فورًا بذلك، ولكن إذا كان الخطأ يعني أن الخادم غير متاح، فقد نرغب في محاولة إعادة الاتصال عدة مرات قبل إعلام المستخدم. سنتمكن من خلال تحديد الاختلاف بين هذه الأخطاء من كتابة برامج أكثر قوة وسهولة في الاستخدام.
</p>

<p>
	تتمثل إحدى الطرق التي يمكن من خلالها التحقق من نوع محدد من الخطأ في استخدام التابع <code>Error</code> على النوع <code>error</code> للحصول على الرسالة من الخطأ ومقارنة هذه القيمة بنوع الخطأ الذي نبحث عنه. لنتخيل أننا نريد إظهار رسالة غير الرسالة <code>there was an error: uh oh</code> التي رأيناها سابقًا عند الحصول على الخطأ <code>uh oh</code>، وإحدى الطرق لمعالجة هذه الحالة هي التحقق من القيمة المعادة من التابع <code>Error</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_11" style=""><span class="kwd">if</span><span class="pln"> err</span><span class="pun">.</span><span class="typ">Error</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">"uh oh"</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// Handle 'uh oh' error.</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"oh no!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيجري في الشيفرة أعلاه التحقق من قيمة السلسلة المُعادة من <code>()err.Error</code> لمعرفة ما إذا كانت القيمة هي <code>uh oh</code> وستجري الأمور على نحو سليم، لكن لن تعمل الشيفرة السابقة إذا كانت السلسلة النصية التي تُعبّر عن الخطأ <code>uh oh</code> مختلفة قليلًا في مكان آخر في البرنامج.
</p>

<p>
	يمكن أن يؤدي التحقق من الأخطاء بهذه الطريقة أيضًا إلى تحديثات مهمة على الشيفرة إذا كانت رسالة الخطأ نفسها بحاجة إلى التحديث، إذ يجب تحديث كل مكان يجري فيه التحقق من الخطأ. خذ على سبيل المثال الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_13" style=""><span class="pln">func giveMeError</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Errorf</span><span class="pun">(</span><span class="str">"uh h"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

err </span><span class="pun">:=</span><span class="pln"> giveMeError</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> err</span><span class="pun">.</span><span class="typ">Error</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">"uh h"</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// "uh h" error code</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تتضمن رسالة الخطأ هنا خطأً إملائيًا، إذ يغيب الحرف <code>o</code>عن السلسلة <code>uh oh</code>. إذا حدث ولاحظنا هذا الخطأ وأردنا إصلاحه، يتعين علينا إصلاحه في جميع أجزاء الشيفرة الأخرى التي تتضمن جملة التحقق <code>"err.Error() == "uh oh</code>، وإذا نسينا أحدهم وهذا محتمل لأنه تغيير في حرف واحد فقط، فلن يعمل معالج الخطأ المخصص لأنه يتوقع <code>uh h</code> وليس <code>uh oh</code>. قد نرغب في مثل هذه الحالات بمعالجة خطأ محدد بطريقة مختلفة، إذ من الشائع إنشاء متغير يكون الغرض منه الاحتفاظ بقيمة خطأ. يمكن للشيفرة بهذه الطريقة أن تتحقق من حصول هذا المتغير بدلًا من السلسلة. تبدأ أسماء هذه المتغيرات عادةً بالعبارة <code>Err</code> أو <code>err</code> للإشارة إلى أنها أخطاء. استخدم <code>err</code>، إذا كان الهدف استخدام الخطأ فقط داخل الحزمة المعرّف فيها، أما إذا أردت استخدامه خارج الحزمة، استخدم البادئة <code>Err</code> ليصبح قيمة مُصدّرة exported value على غرار ما نفعله مع الدوال أو <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%A8%D9%86%D9%89-structs-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1976/" rel="">البنى <code>struct</code></a>.
</p>

<p>
	لنفترض الآن أنك كنت تستخدم إحدى قيم الخطأ هذه في المثال السابق الذي تضمن أخطاء إملائية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_15" style=""><span class="pln">var errUhOh </span><span class="pun">=</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Errorf</span><span class="pun">(</span><span class="str">"uh h"</span><span class="pun">)</span><span class="pln">

func giveMeError</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> errUhOh
</span><span class="pun">}</span><span class="pln">

err </span><span class="pun">:=</span><span class="pln"> giveMeError</span><span class="pun">()</span><span class="pln">
</span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">==</span><span class="pln"> errUhOh </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// "uh oh" error code</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	جرى هنا تعريف المتغير <code>errUhOh</code> على أنه قيمة الخطأ للخطأ "uh oh" (على الرغم من أنه يحتوي على أخطاء إملائية). تُعيد الدالة <code>giveMeError</code> قيمة <code>errUhOh</code> لأنها تريد إعلام المُستدعي بحدوث خطأ <code>uh oh</code>. تقارن شيفرة معالجة الخطأ بعد ذلك قيمة <code>err</code> التي أُعيدت من <code>giveMeError</code> مع <code>errUhOh</code> لمعرفة ما إذا كان الخطأ "uh oh" هو الخطأ الذي حدث.
</p>

<p>
	حتى إذا جرى العثور على الخطأ الإملائي وجرى إصلاحه، فستظل جميع التعليمات البرمجية تعمل، لأن التحقق من الخطأ يجري من خلال المقارنة مع القيمة <code>errUhOh</code>، وقيمة <code>errUhOh</code> هي قيمة ثابتة تُعاد من <code>giveMeError</code>.
</p>

<p>
	تُعرّف قيمة الخطأ المُراد فحصها ومقارنتها بهذه الطريقة بخطأ الحارس sentinel error، وهو خطأ مُصمّم ليكون قيمة فريدة يمكن مقارنتها دائمًا بمعنًى معين. ستحمل قيمة <code>errUhOh</code> السابقة دائمًا نفس المعنى (حدوث خطأ <code>uh oh</code>)، لذلك يمكن للبرنامج الاعتماد على مقارنة خطأ مع <code>errUhOh</code> لتحديد ما إذا كان هذا الخطأ قد حدث أم لا. تتضمن مكتبة جو القياسية عددًا من أخطاء الحارس لتطوير برامج جو. أحد الأمثلة على ذلك هو خطأ <a href="https://pkg.go.dev/database/sql#pkg-variables" rel="external nofollow"><code>sql.ErrNoRows</code></a>، الذي يُعاد عندما لا يُعيد استعلام قاعدة البيانات أية نتائج، لذلك يمكن معالجة هذا الخطأ بطريقة مختلفة عن خطأ الاتصال. بما أنه خطأ حارس، بالتالي يمكن استخدامه في شيفرة التحقق من الأخطاء لمعرفة متى لا يُعيد الاستعلام أية صفوف rows، ويمكن للبرنامج التعامل مع ذلك بطريقة مختلفة عن الأخطاء الأخرى.
</p>

<p>
	عند إنشاء قيمة خطأ حارس، تُستخدم الدالة <a href="https://pkg.go.dev/errors#New" rel="external nofollow"><code>errors.New</code></a> من حزمة <code>errors</code> بدلًا من دالة <code>fmt.Errorf</code> التي كنا نستخدمها. لا يؤدي استخدام <code>errors.New</code> بدلًا من <code>fmt.Errorf</code> إلى إجراء أي تغييرات أساسية في كيفية عمل الخطأ، ويمكن استخدام كلتا الدالتين بالتبادل في معظم الأوقات. أكبر فرق بين الاثنين هو أن <code>errors.New</code> تنشئ خطأ مع رسالة ثابتة، بينما تسمح دالة <code>fmt.Errorf</code> بتنسيق الرسالة مع القيم بطريقة مشابهة لآلية تنسيق السلاسل في <code>fmt.Printf</code> أو <code>fmt.Sprintf</code>.
</p>

<p>
	نظرًا لأن أخطاء الحارس هي أخطاء أساسية بقيم لا تتغير، يُعد استخدام <code>errors.New</code> لإنشائها شائعًا. لنحدّث الآن البرنامج السابق من أجل استخدام خطأ الحارس مع الخطأ <code>uh oh</code> بدلًا من <code>fmt.Errorf</code>. نفتح ملف "main.go" لإضافة خطأ الحارس <code>errUhOh</code> الجديد وتحديث البرنامج لاستخدامه. تُحدَّث دالة <code>validateValue</code> بحيث تعيد خطأ الحارس بدلًا من استخدام <code>fmt.Errorf</code>. تُحدّث دالة <code>main</code> بحيث تتحقق من وجود خطأ حارس <code>errUhOh</code> وطباعة <code>oh no</code> عندما يواجهها خطأ بدلًا من رسالة <code>:there was an error</code> التي تظهر لأخطاء أخرى.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_17" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"errors"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

var </span><span class="pun">(</span><span class="pln">
    errUhOh </span><span class="pun">=</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">New</span><span class="pun">(</span><span class="str">"uh oh"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func validateValue</span><span class="pun">(</span><span class="pln">number </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> number </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Errorf</span><span class="pun">(</span><span class="str">"that's odd"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> number </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> errUhOh
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> nil
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> num </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> num</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"validating %d... "</span><span class="pun">,</span><span class="pln"> num</span><span class="pun">)</span><span class="pln">
        err </span><span class="pun">:=</span><span class="pln"> validateValue</span><span class="pun">(</span><span class="pln">num</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">==</span><span class="pln"> errUhOh </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"oh no!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"there was an error:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"valid!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نحفظ الشيفرة ونشغّل البرنامج كالعادة باستخدام الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيُظهر الخرج هذه المرة ناتج الخطأ العام للقيمة 1، لكنه يستخدم الرسالة المخصصة <code>!oh no</code> عندما تصادف الخطأ <code>errUhOh</code> الناتج من تمرير 2 إلى <code>validateValue</code>:
</p>

<pre class="ipsCode">validating 1... there was an error: that's odd
validating 2... oh no!
validating 3... valid!
</pre>

<p>
	يؤدي استخدام أخطاء الحارس داخل شيفرة فحص الأخطاء إلى تسهيل التعامل مع حالات الخطأ الخاصة، إذ يمكن لأخطاء الحارس مثلًا المساعدة في تحديد ما إذا كان الملف الذي نقرأه قد فشل لأننا وصلنا إلى نهاية الملف، والذي يُشار إليه بخطأ الحارس <a href="https://pkg.go.dev/io#pkg-variables" rel="external nofollow"><code>io.EOF</code></a>، أو إذا فشل لسبب آخر.
</p>

<p>
	أنشأنا في هذا القسم برنامج جو يستخدم خطأ حارس باستخدام <code>errors.New</code> للإشارة إلى حدوث نوع معين من الأخطاء. بمرور الوقت ومع نمو البرنامج، قد نصل إلى النقطة التي نريد فيها تضمين مزيدٍ من المعلومات في الخطأ الخاص بنا بدلًا من مجرد قيمة الخطأ <code>uh oh</code>. لا تقدم قيمة الخطأ هذه أي سياق حول مكان حدوث الخطأ أو سبب حدوثه، وقد يكون من الصعب تعقب تفاصيل الخطأ في البرامج الأكبر حجمًا. للمساعدة في استكشاف الأخطاء وإصلاحها ولتقليل وقت تصحيح الأخطاء، يمكننا الاستفادة من تغليف الأخطاء لتضمين التفاصيل التي نحتاجها.
</p>

<h2>
	تغليف وفك تغليف الأخطاء
</h2>

<p>
	يُقصد بتغليف الأخطاء أخذ قيمة خطأ معينة ووضع قيمة خطأ أخرى داخلها، وكأنها هدية مغلفة، وبما أنها مغلفة، فهذا يعني أنك بحاجة لفك غلافها لمعرفة ما تحتويه. يمكننا من خلال تغليف الخطأ تضمين معلومات إضافية حول مصدر الخطأ أو كيفية حدوثه دون فقدان قيمة الخطأ الأصلية، نظرًا لوجودها داخل الغلاف.
</p>

<p>
	قبل الإصدار 1.13 كان من الممكن تغليف الأخطاء، إذ كان بالإمكان إنشاء قيم خطأ مخصصة تتضمن الخطأ الأصلي، ولكن سيتعين علينا إما إنشاء أغلفة خاصة، أو استخدام مكتبة تؤدي الغرض نيابةً عنا. بدءًا من الإصدار 1.13 أضافت لغة جو دعمًا لعملية تغليف الأخطاء وإلغاء التغليف بمثابة جزء من المكتبة القياسية عن طريق إضافة الدالة <a href="https://pkg.go.dev/errors#Unwrap" rel="external nofollow"><code>errors.Unwrap</code></a> والعنصر النائب <code>w%</code> لدالة <code>fmt.Errorf</code>.
</p>

<p>
	سنحدّث خلال هذا القسم برنامجنا لاستخدام العنصر النائب <code>w%</code> لتغليف الأخطاء بمزيد من المعلومات، وبعد ذلك ستستخدم الدالة <code>errors.Unwrap</code> لاسترداد المعلومات المغلفة.
</p>

<h3>
	تغليف الأخطاء مع الدالة fmt.Errorf
</h3>

<p>
	كانت الدالة <code>fmt.Errorf</code> تُستخدم سابقًا لإنشاء رسائل خطأ منسقة بمعلومات إضافية باستخدام عناصر نائبة مثل <code>v%</code> للقيم المعمّمة و <code>s%</code> للسلاسل النصية، أما حديثًا (بدءًا من الإصدار 1.13) أُضيف عنصر نائب جديد هو <code>w%</code>. عندما يجري تضمين هذا العنصر ضمن تنسيق السلسلة وتتوفر قيمة للخطأ، ستتضمّن قيمة الخطأ المُعادة من <code>fmt.Errorf</code> قيمة الخطأ <code>error</code> المُغلّف في الخطأ المُنشأ.
</p>

<p>
	افتح ملف "main.go" وحدّثه ليشمل دالةً جديدةً تسمى <code>runValidation</code>. ستأخذ هذه الدالة الرقم الذي يجري التحقق منه حاليًا وستُشغّل أي عملية تحقق مطلوبة على هذا الرقم، وفي حالتنا ستحتاج إلى تنفيذ الدالة <code>runValidation</code> فقط. إذا واجه البرنامج خطأً في التحقق من القيمة، سيغلِّف الخطأ باستخدام <code>fmt.Errorf</code> والعنصر النائب <code>w%</code> لإظهار حدوث خطأ في التشغيل، ثم يعيد هذا الخطأ الجديد. ينبغي أيضًا تحديث الدالة <code>main</code>، فبدلًا من استدعاء <code>validateValue</code> مباشرةً، نستدعي <code>runValidation</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_19" style=""><span class="pun">...</span><span class="pln">

var </span><span class="pun">(</span><span class="pln">
    errUhOh </span><span class="pun">=</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">New</span><span class="pun">(</span><span class="str">"uh oh"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func runValidation</span><span class="pun">(</span><span class="pln">number </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    err </span><span class="pun">:=</span><span class="pln"> validateValue</span><span class="pun">(</span><span class="pln">number</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Errorf</span><span class="pun">(</span><span class="str">"run error: %w"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> nil
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> num </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> num</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"validating %d... "</span><span class="pun">,</span><span class="pln"> num</span><span class="pun">)</span><span class="pln">
        err </span><span class="pun">:=</span><span class="pln"> runValidation</span><span class="pun">(</span><span class="pln">num</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">==</span><span class="pln"> errUhOh </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"oh no!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"there was an error:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"valid!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يمكننا الآن -بعد حفظ التحديثات- تشغيل البرنامج:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيظهر خرج مشابه لما يلي:
</p>

<pre class="ipsCode">validating 1... there was an error: run error: that's odd
validating 2... there was an error: run error: uh oh
validating 3... valid!
</pre>

<p>
	هناك عدة أشياء يمكن ملاحظتها من هذا الخرج. إذ نرى أولًا رسالة الخطأ للقيمة 1 مطبوعةً الآن ومتضمنة <code>run error: that's odd</code> في رسالة الخطأ. يوضح هذا أن الخطأ جرى تغليفه بواسطة الدالة <code>fmt.Errorf</code> الخاصة بالدالة <code>runValidation</code> وأن قيمة الخطأ التي جرى تغليفها <code>that's odd</code> مُضمّنة في رسالة الخطأ. هناك مشكلة، إذ أن معالج الخطأ الخاص الذي أضفناه إلى خطأ <code>errUhOh</code> لم يُنفّذ، وسنرى في السطر الثاني من الخرج والذي يتحقق من صلاحية الرقم 2 -رسالة الخطأ الافتراضية للقيمة 2 وهي:
</p>

<pre class="ipsCode">there was an error: run error: uh oh
</pre>

<p>
	بدلًا من الرسالة المتوقعة <code>!oh no</code>. نحن نعلم أن الدالة <code>ValidateValue</code> لا تزال تُعيد الخطأ <code>uh oh</code>، إذ يمكننا رؤية ذلك في نهاية الخطأ المُغلف، لكن معالج الخطأ في <code>errUhOh</code> لم يعد يعمل. يحدث هذا لأن الخطأ الذي أُعيد من الدالة <code>runValidation</code> لم يعد الخطأ <code>errUhOh</code>، وإنما الخطأ المغلف الذي أُنشئ بواسطة الدالة <code>fmt.Errorf</code>. عندما تحاول الجملة الشرطية <code>if</code> مقارنة متغير <code>err</code> مع <code>errUhOh</code>، فإنها تُعيد خطأ لأن <code>errUhOh</code> لم يعد مساويًا للخطأ الذي يُغلّف<code>errUhOh</code>، ولحل هذه المشكلة يجب الحصول الخطأ من داخل الغلاف عن طريق فك التغليف باستخدام دالة <code>errors.Unwrap</code>.
</p>

<h3>
	فك تغليف الأخطاء باستخدام errors.Unwrap
</h3>

<p>
	إضافةً إلى العنصر النائب <code>w%</code> في الإصدار 1.13، أُضيفت بعض الدوال الجديدة إلى حزمة الأخطاء <code>errors</code>. واحدة من هذه الدوال هي الدالة <code>errors.Unwrap</code>، التي تأخذ خطأ <code>error</code> مثل معامل، وإذا كان الخطأ المُمرّر مُغلّف خطأ، ستعيد الخطأ المُغلّف، وإذا لم يكن الخطأ المُمرّر غلافًا تُعيد <code>nil</code>.
</p>

<p>
	نفتح الآن ملف "main.go"، وباستخدام الدالة <code>errors.Unwrap</code> سنحدّث آلية التحقق من خطأ <code>errUhOh</code> لمعالجة الحالة التي يجري فيها تغليف <code>errUhOh</code> داخل مغلّف خطأ:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_21" style=""><span class="pln">func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> num </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> num</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"validating %d... "</span><span class="pun">,</span><span class="pln"> num</span><span class="pun">)</span><span class="pln">
        err </span><span class="pun">:=</span><span class="pln"> runValidation</span><span class="pun">(</span><span class="pln">num</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">==</span><span class="pln"> errUhOh </span><span class="pun">||</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">Unwrap</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> errUhOh </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"oh no!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"there was an error:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"valid!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغّل البرنامج:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	لتكون النتيجة على النحو التالي:
</p>

<pre class="ipsCode">validating 1... there was an error: run error: that's odd
validating 2... oh no!
validating 3... valid!
</pre>

<p>
	سنرى الآن في السطر الثاني من الخرج أن خطأ <code>!oh no</code> للقيمة 2 قد ظهر. يسمح الاستدعاء الإضافي للدالة <code>errors.Unwrap</code> الذي أضفناه إلى تعليمة <code>if</code> باكتشاف <code>errUhOh</code> عندما تكون <code>err</code> هي قيمة خطأ <code>errUhOh</code> وكذلك إذا كان <code>err</code> هو خطأ يُغلّف خطأ <code>errUhOh</code> مباشرةً.
</p>

<p>
	استخدمنا في هذا القسم العنصر <code>w%</code> المضاف إلى <code>fmt.Errorf</code> لتغليف الخطأ <code>errUhOh</code> داخل خطأ آخر وإعطائه معلومات إضافية. استخدمنا بعد ذلك <code>errors.Unwrap</code> للوصول إلى الخطأ <code>errorUhOh</code> المُغلّف داخل خطأ آخر. يُعد تضمين أخطاء داخل أخطاء أخرى مثل قيم ضمن سلسلة <code>string</code> أمرًا مقبولًا بالنسبة للأشخاص الذين يقرؤون رسائل الخطأ، ولكن قد ترغب أحيانًا في تضمين معلومات إضافية مع غلاف الأخطاء لمساعدة البرنامج في معالجة الخطأ، مثل رمز الحالة status code في خطأ طلب HTTP، وفي هكذا حالة يمكنك إنشاء خطأ مخصص جديد لإعادته. يمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/devops/servers/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D9%88%D8%A5%D8%B5%D9%84%D8%A7%D8%AD-%D8%B1%D9%85%D9%88%D8%B2-%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-http-%D8%A7%D9%84%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-r116/" rel="">كيفية استكشاف وإصلاح رموز أخطاء HTTP الشائعة</a> على أكاديمية حسوب لمزيدٍ من المعلومات حول رموز حالة أخطاء HTTP الشائعة.
</p>

<h2>
	أخطاء مغلفة مخصصة
</h2>

<p>
	بما أن القاعدة الوحيدة للواجهة <code>error</code> في لغة جو هي أن تتضمن تابع <code>Error</code>، فمن الممكن تحويل العديد من أنواع لغة جو إلى خطأ مخصص، وتتمثل إحدى الطرق في تعريف نوع بنية <code>struct</code> مع معلومات إضافية حول الخطأ وتضمين تابع <code>Error</code>.
</p>

<p>
	بالنسبة لخطأ التحقق Validation error، سيكون من المفيد معرفة القيمة التي تسببت بالخطأ. لننشئ الآن بنيةً جديدة اسمها <code>ValueError</code> تحتوي على حقل من أجل القيمة <code>Value</code> التي تسبب الخطأ وحقل <code>Err</code> يحتوي على الخطأ الفعلي. تستخدم عادةً أنواع الأخطاء المخصصة اللاحقة <code>Error</code> في نهاية اسم النوع، للإشارة إلى أنه نوع يتوافق مع الواجهة <code>error</code>. افتح الآن ملف "main.go" وضِف البنية الجديدة <code>ValueError</code>، إضافةً إلى دالة <code>newValueError</code> لتُنشئ نسخًا من هذه البنية.
</p>

<p>
	نحتاج أيضًا إلى إنشاء تابع يُسمى <code>Error</code> من أجل البنية <code>ValueError</code> لكي تُعد من النوع <code>error</code>. يجب أن يُعيد التابع <code>Error</code> القيمة التي تريد عرضها عندما يجري تحويل الخطأ إلى سلسلة نصية. نستخدم في حالتنا الدالة <code>fmt.Sprintf</code> لإعادة سلسلة نصية تعرض <code>:value error</code> ثم الخطأ المُغلّف. حدِّث الدالة <code>ValidateValue</code>، فبدلًا من إعادة الخطأ الأساسي فقط، ستستخدم الدالة <code>newValueError</code> لإعادة خطأ مخصص:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_23" style=""><span class="pun">...</span><span class="pln">

var </span><span class="pun">(</span><span class="pln">
    errUhOh </span><span class="pun">=</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Errorf</span><span class="pun">(</span><span class="str">"uh oh"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">ValueError</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Value</span><span class="pln"> </span><span class="typ">int</span><span class="pln">
    </span><span class="typ">Err</span><span class="pln">   error
</span><span class="pun">}</span><span class="pln">

func newValueError</span><span class="pun">(</span><span class="pln">value </span><span class="typ">int</span><span class="pun">,</span><span class="pln"> err error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="typ">ValueError</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">ValueError</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Value</span><span class="pun">:</span><span class="pln"> value</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Err</span><span class="pun">:</span><span class="pln">   err</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">ve </span><span class="pun">*</span><span class="typ">ValueError</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Error</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"value error: %s"</span><span class="pun">,</span><span class="pln"> ve</span><span class="pun">.</span><span class="typ">Err</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span><span class="pln">

func validateValue</span><span class="pun">(</span><span class="pln">number </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> number </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> newValueError</span><span class="pun">(</span><span class="pln">number</span><span class="pun">,</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Errorf</span><span class="pun">(</span><span class="str">"that's odd"</span><span class="pun">))</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> number </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> newValueError</span><span class="pun">(</span><span class="pln">number</span><span class="pun">,</span><span class="pln"> errUhOh</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> nil
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	لنُشغّل البرنامج الآن:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ستكون النتيجة على النحو التالي:
</p>

<pre class="ipsCode">validating 1... there was an error: run error: value error: that's odd
validating 2... there was an error: run error: value error: uh oh
validating 3... valid!
</pre>

<p>
	يظهر الناتج الآن أن الأخطاء مُغلّفة داخل <code>ValueError</code> من خلال عرض <code>:value error</code> قبلها، لكن نلاحظ أن خطأ <code>uh oh</code> لم يُكتشف مرةً أخرى لأن <code>errUhOh</code> داخل طبقتين من الأغلفة الآن، هما: <code>ValueError</code> وغلاف <code>fmt.Errorf</code> من <code>runValidation</code>. تُستخدم الدالة <code>errors.Unwrap</code> مرةً واحدةً فقط على الخطأ، لذلك ينتج عن استخدام <code>(errors.Unwrap(err</code> القيمة <code>ValueError*</code> وليس <code>errUhOh</code>. تتمثل إحدى الحلول في تعديل عملية التحقق من <code>errUhOh</code> بإضافة استدعاء <code>()errors.Unwrap</code> مرتين لفك كلتا الطبقتين.
</p>

<p>
	نفتح الآن ملف "main.go" ونُعدّل الدالة <code>main</code> لإضافة التعديل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_25" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> num </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> num</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"validating %d... "</span><span class="pun">,</span><span class="pln"> num</span><span class="pun">)</span><span class="pln">
        err </span><span class="pun">:=</span><span class="pln"> runValidation</span><span class="pun">(</span><span class="pln">num</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">==</span><span class="pln"> errUhOh </span><span class="pun">||</span><span class="pln">
            errors</span><span class="pun">.</span><span class="typ">Unwrap</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> errUhOh </span><span class="pun">||</span><span class="pln">
            errors</span><span class="pun">.</span><span class="typ">Unwrap</span><span class="pun">(</span><span class="pln">errors</span><span class="pun">.</span><span class="typ">Unwrap</span><span class="pun">(</span><span class="pln">err</span><span class="pun">))</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> errUhOh </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"oh no!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"there was an error:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"valid!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لنُشغّل البرنامج الآن:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ستكون النتيجة على النحو التالي:
</p>

<pre class="ipsCode">validating 1... there was an error: run error: value error: that's odd
validating 2... there was an error: run error: value error: uh oh
validating 3... valid!
</pre>

<p>
	نلاحظ أن معالجة الأخطاء الخاصة <code>errUhOh</code> لا تعمل، إذ كنا نتوقع ظهور خرج معالج الخطأ الخاص <code>!oh no</code> من أجل سطر التحقق الثاني لكن ما زالت الرسالة الافتراضية <code>. . .:there was an error: run error</code> تظهر بدلًا منها. هذا يحدث لأن <code>errors.Unwrap</code> لا تعرف كيفية فك تغليف الخطأ المخصص <code>ValueError</code>. يجب أن يكون لدى الخطأ المخصص تابع فك تغليف <code>Unwrap</code> خاص، يعيد الخطأ الداخلي مثل قيمة <code>error</code>.عندما كنا نُنشئ أخطاءً باستخدام <code>fmt.Errorf</code> مع العنصر النائب <code>w%</code>، كان مُصرّف جو يُنشئ خطأ مع تابع تغليف <code>Unwrap</code> تلقائيًا، لذلك لم نكن بحاجة إلى إضافة التابع يدويًا، لكننا هنا نستخدم دالةً خاصة، وبالتالي يجب أن نُضيف هذا التابع يدويًا.
</p>

<p>
	إذًا، لإصلاح مشكلة <code>errUhOh</code> نفتح ملف "main.go" ونضيف التابع <code>Unwrap</code> إلى <code>ValueError</code> التي تُعيد الحقل <code>Err</code> الذي يحتوي على الخطأ الداخلي المغلف:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_27" style=""><span class="pun">...</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">ve </span><span class="pun">*</span><span class="typ">ValueError</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Error</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"value error: %s"</span><span class="pun">,</span><span class="pln"> ve</span><span class="pun">.</span><span class="typ">Err</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">ve </span><span class="pun">*</span><span class="typ">ValueError</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Unwrap</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> ve</span><span class="pun">.</span><span class="typ">Err</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	لنُشغّل البرنامج الآن:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ستكون النتيجة كما يلي:
</p>

<pre class="ipsCode">validating 1... there was an error: run error: value error: that's odd
validating 2... oh no!
validating 3... valid!
</pre>

<p>
	نلاحظ أن المشكلة قد حُلت وظهرت رسالة الخطأ <code>!oh no</code> التي تُشير إلى الخطأ <code>errUhOh</code> هذه المرة، لأن <code>errors.Unwrap</code> أصبح قادرًا على فك تغليف <code>ValueError</code>.
</p>

<p>
	أنشأنا في هذا القسم خطأ جديد مخصص <code>ValueError</code>، لتزويدنا والمستخدمين بمعلومات إضافية عن عملية التحقق في جزء من رسالة الخطأ. أضفنا أيضًا دعمًا (الدالة <code>Unwrap</code>) لعملية فك تغليف الأخطاء إلى <code>ValueError</code>، بحيث يمكن استخدام <code>errors.Unwrap</code> للوصول إلى الخطأ المغلف.
</p>

<p>
	يمكننا أن نلاحظ عمومًا أن معالجة الأخطاء أصبحت صعبة نوعًا ما ويصعب الحفاظ عليها، إذ يجب علينا إضافة دالة فك تغليف <code>errors.Unwrap</code> من أجل كل طبقة تغليف جديدة. لحسن حظنا هناك دوال جاهزة <code>errors.Is</code> و <code>errors.As</code> تجعل العمل مع الأخطاء المغلفة أسهل.
</p>

<h2>
	التعامل مع الأخطاء المغلفة Wrapped Errors
</h2>

<p>
	عند الحاجة إلى إضافة استدعاء جديد للدالة <code>errors.Unwrap</code> من أجل كل طبقة تغليف في البرنامج، سيستغرق الأمر وقتًا طويلًا ويصعب الحفاظ عليه. لهذا الأمر أُضيفت الدالتان <code>errors.Is</code> و <code>errors.As</code> إلى حزمة الأخطاء <code>errors</code> بدءًا من الإصدار 1.13 من لغة جو، إذ تعمل هاتان الدالتان على تسهيل التعامل مع الأخطاء من خلال السماح لك بالتفاعل مع الأخطاء بغض النظر عن مدى عمق تغليفها داخل الأخطاء الأخرى. تسمح الدالة <code>errors.Is</code> بالتحقق ما إذا كانت قيمة خطأ حارس معين موجودةً في أي مكان داخل خطأ مغلف؛ بينما تتيح الدالة <code>errors.As</code> الحصول على مرجع لنوع معين من الأخطاء من أي مكان داخل خطأ مغلّف.
</p>

<h3>
	فحص قيمة خطأ باستخدام الدالة errors.Is
</h3>

<p>
	يؤدي استخدام الدالة <code>errors.Is</code> للتحقق من وجود خطأ معين إلى جعل معالجة الخطأ الخاص <code>errUhOh</code> أقصر بكثير، لأنه يعالج جميع الأخطاء المتداخلة تلقائيًّا بدل إجرائها يدويًا من قبلنا. تأخذ الدالة معاملين كل منهما خطأ <code>error</code>، المعامل الأول هو الخطأ الذي تلقيناه والثاني هو الخطأ الذي نريد التحقق منه. لإزالة التعقيدات من عملية معالجة الخطأ <code>errUhOh</code>، سنفتح ملف "main.go"، ونحدّث عملية التحقق من <code>errUhOh</code> في دالة <code>main</code> لنستخدم الدالة <code>errors.Is</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_29" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> num </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> num</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"validating %d... "</span><span class="pun">,</span><span class="pln"> num</span><span class="pun">)</span><span class="pln">
        err </span><span class="pun">:=</span><span class="pln"> runValidation</span><span class="pun">(</span><span class="pln">num</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">Is</span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> errUhOh</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"oh no!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"there was an error:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"valid!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لنُشغّل البرنامج الآن:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سنحصل على النتيجة التالية:
</p>

<pre class="ipsCode">validating 1... there was an error: run error: value error: that's odd
validating 2... oh no!
validating 3... valid!
</pre>

<p>
	يظهر الخرج رسالة الخطأ <code>!oh no</code>، وهذا يعني أنه على الرغم من وجود عملية فحص خطأ واحدة من أجل <code>errUhOh</code>، سيظل بالإمكان فك سلسلة الأخطاء والوصول إلى الخطأ المُغلّف. تستفيد الدالة <code>errors.Is</code> من التابع <code>Unwrap</code> لمواصلة البحث العميق في سلسلة من الأخطاء، حتى تعثر على قيمة الخطأ المطلوبة أو خطأ حارس أو تُصادف تابع <code>Unwrap</code> يعيد قيمة <code>nil</code>. بعد إضافة دالة <code>errors.Is</code> في إصدار 1.13، أصبح استخدامها موصى به للتحقق من وجود أخطاء. تجدر الإشارة إلى أنه يمكن استخدام هذه الدالة مع قيم الخطأ الأخرى، مثل خطأ <code>sql.ErrNoRows</code> سالف الذكر.
</p>

<h3>
	استرداد نوع الخطأ باستخدام errors.As
</h3>

<p>
	الدالة الثانية التي سنتحدث عنها هي <code>errors.As</code>، والتي تُستخدم عندما نريد الحصول على مرجع لنوع معين من الأخطاء للتفاعل معه بتفصيل أكبر. مثلًا يتيح الخطأ المخصص <code>ValueError</code> الذي أضفناه سابقًا؛ الوصول إلى القيمة الفعلية التي يجري التحقق من صحتها في حقل <code>Value</code> الخاص بالخطأ، ولكن لا يمكن الوصول إليه إلا إذا كان لدينا مرجع لهذا الخطأ أولًا. هنا يأتي دور <code>errors.As</code> التي تأخذ معاملين، الأول خطأ والثانية متغير لنوع الخطأ، إذ يجري المرور على سلسلة الأخطاء لمعرفة ما إذا كان أي من الأخطاء المغلفة يتطابق مع النوع المُقدم، فإذا حصل تطابق مع أحدها فسيُضبط المتغير المُمرر لنوع الخطأ بالخطأ الذي عثر عليه <code>errors.As</code>، وستعيد الدالة <code>true</code> وإلا ستعيد <code>false</code>.
</p>

<p>
	إذًا، أصبح بإمكاننا باستخدام هذه الدلة الاستفادة من نوع <code>ValueError</code> لإظهار معلومات إضافية عن الخطأ في معالج الأخطاء. لنفتح ملف "main.go" للمرة الأخيرة ونحدّث الدالة <code>main</code> لإضافة حالة جديدة لمعالجة الأخطاء من نوع <code>ValueError</code>، بحيث تطبع قيمة الخطأ والرقم غير الصالح وخطأ التحقق:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3954_31" style=""><span class="pun">...</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> num </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> num</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"validating %d... "</span><span class="pun">,</span><span class="pln"> num</span><span class="pun">)</span><span class="pln">
        err </span><span class="pun">:=</span><span class="pln"> runValidation</span><span class="pun">(</span><span class="pln">num</span><span class="pun">)</span><span class="pln">

        var valueErr </span><span class="pun">*</span><span class="typ">ValueError</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">Is</span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> errUhOh</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"oh no!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">As</span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">valueErr</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"value error (%d): %v\n"</span><span class="pun">,</span><span class="pln"> valueErr</span><span class="pun">.</span><span class="typ">Value</span><span class="pun">,</span><span class="pln"> valueErr</span><span class="pun">.</span><span class="typ">Err</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"there was an error:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"valid!"</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	صرّحنا في الشيفرة السابقة عن متغير جديد اسمه <code>valueErr</code> واستخدمنا <code>errors.As</code>، للحصول على مرجع إلى <code>ValueError</code> في حال كان مغلفًا داخل قيمة <code>err</code>. بمجرد الوصول إلى الخطأ الخاص <code>ValueError</code>، سنتمكن من الوصول إلى أية حقول إضافية يوفرها هذا النوع، مثل القيمة الفعلية التي فشل التحقق منها. قد يكون هذا مفيدًا إذا كانت عملية التحقق عميقة في البرنامج ولم يكن لدينا إمكانية الوصول إلى القيم لمنح المستخدمين تلميحات حول مكان حدوث خطأ ما. مثال آخر على المكان الذي يمكن أن يكون مفيدًا فيه هو في حال كنا في صدد برمجة شبكة وواجهنا خطأ <a href="https://pkg.go.dev/net#DNSError" rel="external nofollow"><code>net.DNSError</code></a>. يمكنك -من خلال الحصول على مرجع للخطأ- معرفة ما إذا كان الخطأ ناتجًا عن عدم القدرة على الاتصال، أو ما إذا كان الخطأ ناتجًا عن القدرة على الاتصال، ولكن لم يُعثر على المورد المطلوب، وهذا يمكّننا من التعامل مع الخطأ بطرق مختلفة. لرؤية كيف تجري الأمور مع الدالة <code>errors.As</code> دعونا نُشغّل البرنامج:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ستكون النتيجة على النحو التالي:
</p>

<pre class="ipsCode">validating 1... value error (1): that's odd
validating 2... oh no!
validating 3... valid!
</pre>

<p>
	لن ترى رسالة الخطأ الافتراضي <code>… :there was an error</code> هذه المرة في الخرج، لأن جميع الأخطاء تُعالج بواسطة معالجات الأخطاء الأخرى. يظهر السطر اﻷول من الخرج الخاص بالتحقق من صحة القيمة 1 أن الدالة <code>errors.As</code> تُعيد <code>true</code> لأن رسالة الخطأ <code>... value error</code> عُرضت. بما أن الدالة <code>errors.As</code> تُعيد <code>true</code>، يُضبط المتغير <code>valueErr</code> ليكون خطأ <code>ValueError</code> ويمكن استخدامه لطباعة القيمة التي فشلت في التحقق من الصحة من خلال الوصول إلى <code>valueErr.Value</code> (لاحظ كيف طُبعت القيمة 1 والتي فشلت في اختبار التحقق من الصحة). يظهر أيضًا السطر الثاني من الخرج، والذي يشير إلى اختبار التحقق من صحة الرقم 2، أنه على الرغم من تغليف <code>errUhOh</code> داخل غلاف <code>ValueError</code>، ما زالت رسالة الخطأ <code>!oh no</code> تظهر (بالرغم من وجود طبقتي تغليف)، وهذا لأن معالج الأخطاء الخاص الذي يستخدم الدالة <code>errors.Is</code> مع <code>errUhOh</code> تأتي أولًا في مجموعة تعليمات اختبار <code>if</code> لمعالجة الأخطاء. بما أن هذا المعالج يُعيد <code>true</code> قبل تنفيذ <code>errors.As</code>، يُنفّذ معالج <code>!oh no</code>. لو كانت الدالة <code>errors.As</code> تظهر قبل الدالة <code>errors.Is</code> في البرنامج لرأينا خرجًا مشابهًا لحالة القيمة 1، أي أن رسالة <code>!oh no</code> ستكون <code>value error (2): uh oh</code>.
</p>

<p>
	حدّثنا خلال هذا القسم البرنامج لنتمكن من استخدام <code>errors.Is</code> لإزالة الاستدعاءات الكثيرة والإضافية للدالة <code>errors.Unwrap</code> وجعل شيفرة معالجة الأخطاء أكثر متانة وسهولة للتعديل. استخدمنا أيضًا الدالة <code>errors.As</code> للتحقق ما إذا كان أي من الأخطاء المغلّفة هو <code>ValueError</code>، ثم استخدمنا حقولها في حال توافرها.
</p>

<h2>
	الخاتمة
</h2>

<p>
	تعرّفنا خلال هذا المقال على كيفية تغليف خطأ باستخدام العنصر النائب <code>w%</code> وكيفية فكه باستخدام الدالة <code>errors.Unwrap</code>. أنشأنا أيضًا نوع خطأ مخصص يدعم الدالة <code>errors.Unwrap</code>، واستخدمناه لاستكشاف دوال مُساعدة جديدة <code>errors.Is</code> و <code>errors.As</code> تُسهّل علينا عملية إرفاق معلومات أعمق وأكثر تفصيلًا عن الأخطاء التي نُنشئها، أو نتعامل معها وتضمن استمرارية عملية فحص الأخطاء حتى في حالة الأخطاء العميقة.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-add-extra-information-to-errors-in-go" rel="external nofollow">How to Add Extra Information to Errors in Go</a> لصاحبه Kristin Davidson.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%B9%D8%AF%D8%A9-%D8%AF%D9%88%D8%A7%D9%84-%D8%B9%D8%A8%D8%B1-%D9%85%D9%8A%D8%B2%D8%A9-%D8%A7%D9%84%D8%AA%D8%B3%D8%A7%D9%8A%D8%B1-concurrency-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2152/" rel="">كيفية تنفيذ عدة دوال عبر ميزة التساير Concurrency في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1891/" rel="">معالجة الأخطاء في لغة جو Go</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2153</guid><pubDate>Wed, 25 Oct 2023 13:06:00 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x62A;&#x646;&#x641;&#x64A;&#x630; &#x639;&#x62F;&#x629; &#x62F;&#x648;&#x627;&#x644; &#x639;&#x628;&#x631; &#x645;&#x64A;&#x632;&#x629; &#x627;&#x644;&#x62A;&#x633;&#x627;&#x64A;&#x631; Concurrency &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648;</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%B9%D8%AF%D8%A9-%D8%AF%D9%88%D8%A7%D9%84-%D8%B9%D8%A8%D8%B1-%D9%85%D9%8A%D8%B2%D8%A9-%D8%A7%D9%84%D8%AA%D8%B3%D8%A7%D9%8A%D8%B1-concurrency-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-r2152/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_10/-------Concurrency----Go.png.6fed597f03e8507edc553c14798c9ba2.png" /></p>
<p>
	واحدة من أهم الميزات التي تدعمها <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a> هي القدرة على إجراء أكثر من عملية في وقت واحد، وهذا ما يُسمى "بالتساير Concurrency"، وقد أصبحت فكرة تشغيل التعليمات البرمجية بطريقة متسايرة جزءًا مهمًا في تطبيقات الحاسب نظرًا لما تتيحه من استثمار أكبر للموارد الحاسوبية المتاحة والسرعة في إنهاء تنفيذ البرامج، فبدلًا من تنفيذ كتلة واحدة من التعليمات البرمجية في وقت واحد، يمكن تنفيذ عدة كتل من التعليمات البرمجية.
</p>

<p>
	مفهوم <strong>التساير</strong> هو فكرة يمكن دعم البرامج بها من خلال تصميم البرنامج بطريقة تسمح بتنفيذ أجزاء منه باستقلالية عن الأجزاء الأخرى.
</p>

<p>
	تمتلك لغة جو ميزتين، هما: <a href="https://golangdocs.com/goroutines-in-golang" rel="external nofollow"><strong>خيوط معالجة جو Goroutines</strong></a> و<a href="https://golangdocs.com/channels-in-golang" rel="external nofollow"><strong>القنوات Channels</strong></a> تُسهّلان إجراء عمليات التساير؛ إذ تُسهّل الأولى عملية إعداد الشيفرة المتسايرة للبرنامج، وتجعل الثانية عملية التواصل بين أجزاء البرنامج المتساير آمنة.
</p>

<p>
	سنتعرّف في هذا المقال على كل من خيوط معالجة جو والقنوات، إذ سننشئ برنامجًا يستخدم خيوط معالجة جو لتشغيل عدة دوال في وقت واحد، ثم سنضيف قنوات لتحقيق اتصال آمن بين أجزاء البرنامج المتساير. أخيرًا، سنضيف عدة خيوط معالجة (كل منها يُسمّى عامل Worker، إشارةً إلى أدائه مهمة ما)، لمحاكاة حالات أكثر تعقيدًا.
</p>

<h2>
	الفرق بين التزامن Synchronous وعدم التزامن Asynchronous والتساير Concurrency والتوازي Parallelism
</h2>

<p>
	<strong>التزامن</strong> و<strong>عدم التزامن</strong> هما نموذجان مختلفان للبرمجة، يشيران إلى أنماط البرمجة. تُكتب التعليمات البرمجية في النموذج الأول مثل خطوات، إذ تُنفّذ التعليمات البرمجية من الأعلى إلى الأسفل، خطوةً بخطوة، وتنتقل إلى الخطوة الثانية فقط عندما تنتهي من الخطوة الأولى، ويمكن هنا التنبؤ بالخرج:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_8" style=""><span class="pln">func step1</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="str">"1"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
func step2</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="str">"2"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    step1</span><span class="pun">()</span><span class="pln">
    step2</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// result -&gt; 12</span></pre>

<p>
	تُكتب التعليمات البرمجية في النموذج الثاني مثل مهمات، ويجري تنفيذها بعد ذلك على التساير. <strong>التنفيذ المتساير</strong> يعني أنه من المحتمل أن تُنفّذ جميع المهام في نفس الوقت، وهنا الخرج لايمكن التنبؤ به:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_10" style=""><span class="pln">func task1</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="str">"1"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
func task2</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> print</span><span class="pun">(</span><span class="str">"2"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    task1</span><span class="pun">()</span><span class="pln">
    task2</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// result -&gt; 12 or 21</span></pre>

<p>
	في نموذج البرمجة غير المتزامن، يكون التعامل مع المهام على أنها خطوة واحدة تُشغّل مهام متعددة، ولا يؤخذ بالحسبان كيفية ترتيب هذه المهام. يمكن تشغيلها في وقت واحد أو في بعض الحالات، ستكون هناك بعض المهام التي تتُنفّذ أولًا ثم تتوقف مؤقتًا وتأتي مهام أخرى لتحل محلها بالتناوب وهكذا. يُطلق على هذا السلوك اسم <strong>متساير</strong>.
</p>

<p>
	لفهم الأمر أكثر تخيل أنّه طُلب منك تناول كعكة ضخمة وغناء أغنية، وستفوز إذا كنت أسرع من يغني الأغنية وينهي الكعكة. القاعدة هي أن تغني وتأكل في نفس الوقت، ويمكنك أن تأكل كامل الكعكة ثم تغني كامل الأغنية، أو أن تأكل نصف كعكة ثم تغني نصف أغنية ثم تفعل ذلك مرةً أخرى، إلخ. ينطبق الأمر نفسه على علوم الحاسب؛ فهناك مهمتان تُنفذان بصورةٍ متسايرة، ولكن تُنفذان على وحدة معالجة مركزية أحادية النواة، لذلك ستقرر وحدة المعالجة المركزية تشغيل مهمة أولًا ثم المهمة الأخرى، أو تشغيل نصف مهمة ونصف مهمة أخرى، إلخ. يجعلنا هذا التقسيم نشعر بأن جميع المهام تُُنفّذ بنفس الوقت.
</p>

<p>
	حسنًا تبقى لنا معرفة <strong>التنفيذ المتوازي</strong>. بالعودة إلى مثال الكعكة والأغنية تخيل أن يُسمح لك بالاستعانة بصديق، وبالتالي يمكن أن يغني بينما أنت تأكل. هذا يقابل أن يكون لدينا وحدة معالجة مركزية بنواتين، إذ يمكن تنفيذ المهمة -التي يمكن تقسيمها لمهمتين فرعيتين- على نواتين مختلفتين، وهذا ما يسمى <strong>بالتوازي</strong>، وهو نوع معين من التساير، إذ تُنفّذ المهام فعلًا في وقت واحد. لا يمكن تحقيق التوازي إلا في بيئات متعددة النواة.
</p>

<h2>
	المتطلبات
</h2>

<p>
	لتتابع هذه المقالة، يجب أن تستوفي الشروط التالية:
</p>

<ul>
	<li>
		إصدار مُثبّت من جو 1.13 أو أعلى، ويمكنك الاستعانة بمقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a> لإعداده.
	</li>
	<li>
		على درايةٍ بكيفية عمل واستخدام الدوال في لغة جو. يمكنك الاطلاع على مقالة <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D9%88%D8%A7%D8%B3%D8%AA%D8%AF%D8%B9%D8%A7%D8%A1-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1962/" rel="">كيفية تعريف واستدعاء الدوال في لغة جو Go</a>.
	</li>
</ul>

<h2>
	تشغيل عدة دوال في وقت واحد باستخدام خيوط معالجة جو Goroutines
</h2>

<p>
	تُصمّم المعالجات الحديثة أو <a href="https://academy.hsoub.com/programming/os-embedded-systems/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%88%D8%AD%D8%AF%D8%A9-%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D8%B1%D9%83%D8%B2%D9%8A%D8%A9-%D9%88%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8-r1716/" rel="">وحدة المعالجة المركزية CPU</a> لأجهزة الحواسيب بحيث يمكنها تنفيذ أكبر عدد من المجاري التدفقية Streams من الشيفرة (أي كتل من التعليمات البرمجية) في نفس الوقت. لتحقيق هذه الإمكانية تتضمن المعالجات نواة أو <a href="https://academy.hsoub.com/apps/operating-systems/%D9%88%D8%AD%D8%AF%D8%A9-%D8%A7%D9%84%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D9%85%D8%B1%D9%83%D8%B2%D9%8A%D8%A9-r879/" rel="">عدة أنوية</a> (يمكنك التفكير بكل نواة على أنها معالج أصغر) كل منها قادر على تنفيذ جزء من تعليمات البرنامج في نفس الوقت. بالتالي، يمكننا أن نستنتج أنّه يمكن الحصول على أداء أسرع بازدياد عدد الأنوية التي تُشغّل البرنامج. لكن مهلًا، لا يكفي وجود عدة أنوية لتحقيق الأمر؛ إذ يجب أن يدعم البرنامج إمكانية التنفيذ على نوى متعددة وإلا لن يُنفّذ البرنامج إلا على نواة واحدة في وقت واحد ولن نستفيد من خاصية النوى المتعددة والتنفيذ المتساير.
</p>

<p>
	للاستفادة من وجود النوى المتعددة، يقع على عاتق المبرمج تصميم شيفرة البرنامج، بحيث يمكن تقسيم هذه الشيفرة إلى كتل تُنفّذ باستقلالية عن بعضها. ليس تقسيم شيفرة البرنامج إلى عدة أجزاء أو كتل برمجية أمرًا سهلًا، فهو يمثّل تحدٍ للمبرمج. لحسن الحظ أن لغة جو تجعل الأمر أسهل من خلال الأداة goroutine أو "تنظيم جو".
</p>

<p>
	تنظيم جو هو نوع خاص من الدوال يمكنه العمل في نفس الوقت الذي تعمل به خيوط المعالجة الأخرى. نقول عن برنامج أنّه مُصمّم ليُنفّذ على التساير عندما يتضمن كتل من التعليمات البرمجية التي يمكن تنفيذها باستقلالية في وقت واحد. بالحالة العادية عندما تُستدعى دالة ما، ينتظر البرنامج انتهاء تنفيذ كامل الدالة قبل أن يكمل تنفيذ الشيفرة. يُعرف هذا النوع من التنفيذ باسم التشغيل في "المقدمة Foreground" لأنه يمنع البرنامج من تنفيذ أي شيء آخر قبل أن ينتهي. من خلال "تنظيم جو"، ستُستدعى الدالة وتنفّذ في "الخلفية Background" بينما يُكمل البرنامج تنفيذ باقي الشيفرة. نقول عن شيفرةً -أو جزء من شيفرة- أنها تُنفّذ في الخلفية عندما لا يمنع تنفيذها باقي أجزاء الشيفرة من التنفيذ، أي عندما لا تكون باقي أجزاء الشيفرة مُضطرة لانتظارها حتى تنتهي.
</p>

<p>
	تأتي قوة خيوط معالجة جو -وهي عبارة عن خيط معالجة lightweight thread يديره مشغل جو الآني Go runtime)- من فكرة أن كل تنظيم يمكنه أن يشغل نواة معالج واحدة في نفس الوقت. إذا كان لديك معالج بأربع نوى ويتضمن برنامجك 4 خيوط معالجة، فإن كل تنظيم يمكنه أن يُنفّذ على نواة في نفس الوقت. عندما تُنفّذ عدة أجزاء من الشيفرة البرمجيّة في نفس الوقت على نوى مختلفة (كما في الحالة السابقة) -نقول عن عملية التنفيذ أنها تفرعيّة (أو متوازية). يمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/programming/c-sharp/dotnet/%D8%AA%D9%86%D9%81%D9%8A%D8%B0-%D8%A7%D9%84%D9%85%D9%87%D8%A7%D9%85-%D8%A8%D8%A7%D9%84%D8%AA%D9%88%D8%A7%D8%B2%D9%8A-%D9%81%D9%8A-dot-net-r980/" rel="">تنفيذ المهام بالتوازي في dot NET</a> على أكاديمية حسوب لمزيدٍ من المعلومات حول تنفيذ المهام على التوازي.
</p>

<p>
	لتوضيح الفرق بين التساير concurrency والتوازي parallelism، ألقِ نظرةً على المخطط التالي. عندما يُنفّذ المعالج دالةً ما، فهو عادةً لا يُنفّذها من البداية للنهاية دفعةً واحدة، إذ يعمل نظام التشغيل أحيانًا على التبديل بين الدوال أو خيوط المعالجة أو البرامج الأخرى التي تُنفّذ على نواة المعالج عندما تنتظر الدالة حدوث شيءٍ ما (مثلًا قد يتطلب تنفيذ الدالة استقبال مُدخلات من المستخدم أو قراءة ملف). يعرض لنا المخطط كيف يمكن للبرنامج المصمّم للعمل على التساير التنفيّذ على نواة واحدة أو عدة نوى، كما يوضح أيضًا أنه من الملائم أكثر تنفيّذ خيوط معالجة جو على التوازي من التشغيل على نواة واحدة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" href="https://academy.hsoub.com/uploads/monthly_2023_10/diagram1(1).png.3eab680701452bedc5629c6ee31e957c.png" data-fileid="137024" data-fileext="png" rel=""><img class="ipsImage ipsImage_thumbnailed" data-fileid="137024" data-ratio="59.22" data-unique="2kte9lhrh" width="900" alt="diagram1(1).png" src="https://academy.hsoub.com/uploads/monthly_2023_10/diagram1(1).thumb.png.515e02040bfdad67c9bfb1b7c9a14ad5.png"></a>
</p>

<p>
	يوّضح المخطط على اليسار والمُسمّى "تساير Concurrency" كيف يمكن تنفيذ برنامج متساير التصميم على نواة معالج وحيدة من خلال تشغيل أجزاء من <code>goroutine1</code>ثم دالة أو تنظيم أو برنامج ثم <code>goroutine2</code> ثم <code>goroutine1</code> مرةً أخرى وهكذا. هنا يشعر المستخدم بأن البرنامج ينفِّذ كل الدوال أو خيوط المعالجة في نفس الوقت، على الرغم من أنها تُنفّذ على التسلسل.
</p>

<p>
	يوضّح العمود الأيمن من المخطط والمسمّى "توازي Parallelism" كيف أن نفس البرنامج يمكن أن يُنفّذ على التوازي على معالج يملك نواتين، إذ يُظهر المخطط أن النواة الأولى تنفِّذ <code>goroutine1</code> وهناك دوال أو خيوط معالجة أو برامج أخرى تُنفّذ معها أيضًا، نفس الأمر بالنسبة للنواة الثانية التي تنفِّذ <code>goroutine1</code>. نلاحظ أحيانًا أن <code>goroutine1</code> و <code>goroutine2</code> تُنفّذان في نفس الوقت لكن على نوى مختلفة.
</p>

<p>
	يظهر هذا المخطط أيضًا سمات أخرى من سمات جو القوية، وهي قابلية التوسع Scalability، إذ يكون البرنامج <strong>قابلًا للتوسع</strong> عندما يمكن تشغيله على أي شيء بدءًا من جهاز حاسوب صغير به عدد قليل من الأنوية وحتى خادم كبير به عشرات الأنوية والاستفادة من هذه الموارد الإضافية، أي كلما أعطيته موارد أكبر، يمكنه الاستفادة منها. يوضّح المخطط أنه من خلال استخدام خيوط معالجة جو، يمكن لبرنامج متساير أن يُنفّذ على نواة وحيدة، لكن مع زيادة عدد الأنوية سيكون البرنامج قابلًا للتنفيذ المتوازي، وبالتالي يمكن تنفيّذ أكثر من تنظيم في نفس الوقت وتسريع الأداء.
</p>

<p>
	للبدء بإنشاء برنامج متساير، علينا أولًا إنشاء مجلد "multifunc" في المكان الذي تختاره. قد يكون لديك مجلد مشاريع خاص بك، لكن هنا سنعتمد مجلدًا اسمه "projects". يمكنك إنشاء المجلد إما من خلال <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D9%83%D8%A7%D9%85%D9%84%D8%A9-ide-r1513/" rel="">بيئة تطوير متكاملة IDE</a> أو سطر الأوامر. إذا كنت تستخدم سطر الأوامر، فابدأ بإنشاء مجلد "projects" وانتقل إليه:
</p>

<pre class="ipsCode">$ mkdir projects
$ cd projects
</pre>

<p>
	من المجلد "projects" استخدم الأمر <code>mkdir</code> لإنشاء مجلد المشروع "multifunc" وانتقل إليه:
</p>

<pre class="ipsCode">$ mkdir multifunc
$ cd multifunc
</pre>

<p>
	افتح الآن ملف "main.go" باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">محرر نانو nano أو أي محرر آخر تريده</a>:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	ضع بداخله الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_12" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func generateNumbers</span><span class="pun">(</span><span class="pln">total </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> idx </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> idx </span><span class="pun">&lt;=</span><span class="pln"> total</span><span class="pun">;</span><span class="pln"> idx</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Generating number %d\n"</span><span class="pun">,</span><span class="pln"> idx</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func printNumbers</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> idx </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> idx </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> idx</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Printing number %d\n"</span><span class="pun">,</span><span class="pln"> idx</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    printNumbers</span><span class="pun">()</span><span class="pln">
    generateNumbers</span><span class="pun">(</span><span class="lit">3</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يعرّف هذا البرنامج الأولي دالتين <code>generateNumbers</code> و <code>printNumbers</code>، إضافةً إلى الدالة الرئيسية <code>main</code> التي تُستدعى هذه الدوال ضمنها. تأخذ الدالة الأولى معامًلا يُدعى <code>total</code> يُمثّل عدد الأعداد المطلوب توليدها، وفي حالتنا مررنا القيمة 3 لهذه الدالة وبالتالي سنرى الأعداد من 1 إلى 3 على شاشة الخرج. لا تأخذ الدالة الثانية أيّة معاملات، فهي تطبع دومًا الأرقام من 0 إلى 3.
</p>

<p>
	بعد حفظ ملف main.go، يمكننا تشغيله باستخدام الأمر التالي:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">Printing number 1
Printing number 2
Printing number 3
Generating number 1
Generating number 2
Generating number 3
</pre>

<p>
	نلاحظ أن الدالة <code>printNumbers</code> نُفّذت أولًا، ثم تلتها الدالة <code>generateNumbers</code>، وهذا منطقي لأنها استُدعيت أولًا. تخيل الآن أن كل من هاتين الدالتين تحتاج 3 ثوان حتى تُنفّذ. بالتالي عند تنفيذ البرنامج السابق بطريقة متزامنة synchronously (خطوةً خطوة)، سيتطلب الأمر 6 ثوانٍ للتنفيذ (3 ثوان لكل دالة). الآن، لو تأملنا قليلًا سنجد أن هاتين الدالتين مستقلتان عن بعضهما بعضًا، أي لا تعتمد أحدهما على نتيجة تنفيذ الأخرى، وبالتالي يمكننا الاستفادة من هذا الأمر والحصول على أداء أسرع للبرنامج من خلال تنفيذ الدوال بطريقة متسايرة باستخدام خيوط معالجة جو. نظريًّا: سنتمكن من تنفيذ البرنامج في نصف المدة (3 ثوان)، وذلك لأن كل من الدالتين تحتاج 3 ثوان لتُنفّذ، وبما أنهما سيُنفذان في نفس الوقت، بالتالي يُفترض أن ينتهي تنفيذ البرنامج خلال 3 ثوان.
</p>

<p>
	يختلف حقيقةً الجانب النظري هنا عن التجربة، فليس ضروريًا أن يكتمل التنفيذ خلال 3 ثوان، لأن هناك العديد من العوامل الأخرى الخارجية، مثل البرامج الأخرى التي تؤثر على زمن التنفيذ.
</p>

<p>
	إن تنفيذ دالة على التساير من خلال تنظيم جو مُشابه لتنفيّذ دالة بطريقة متزامنة. لتنفيذ دالة من خلال تنظيم جو (أي بطريقة متسايرة) يجب وضع الكلمة المفتاحية <code>go</code> قبل استدعاء الدالة.
</p>

<p>
	لجعل البرنامج يُنفّذ جميع خيوط المعالجة على التساير، يتعين علينا إضافة تعديل بسيط للبرنامج، بحيث نجعله ينتظر انتهاء تنفيذ كل خيوط معالجة جو. هذا ضروري لأنه إذا انتهى تنفيذ دالة <code>main</code> ولم تنتظر انتهاء خيوط معالجة جو، فقد لا يكتمل تنفيذ خيوط معالجة جو (تحدث مقاطعة لتنفيذها إن لم تكن قد انتهت).
</p>

<p>
	لتحقيق عملية الانتظار هذه نستخدم <code>WaitGroup</code> من حزمة <code>sync</code> التابعة لجو، والتي تتضمّن الأدوات الأولية للتزامن synchronization primitives، مثل <code>WaitGroup</code> المُصممة لتحقيق التزامن بين عدة أجزاء من البرنامج. ستكون مهمة التزامن في مثالنا هي تعقب اكتمال تنفيذ الدوال السابقة وانتظارها حتى تنتهي لكي يُسمح بإنهاء البرنامج.
</p>

<p>
	تعمل الأداة الأولية <code>WaitGroup</code> عن طريق حساب عدد الأشياء التي تحتاج إلى انتظارها باستخدام دوال <code>Add</code> و <code>Done</code> و <code>Wait</code>. تزيد الدالة <code>Add</code> العداد من خلال الرقم المُمرر إلى الدالة، بينما تنقص الدالة <code>Done</code> العداد بمقدار واحد. تنتظر الدالة <code>Wait</code> حتى تصبح قيمة العداد 0، وهذا يعني أن الدالة <code>Done</code> استُدعيت بما يكفي من المرات لتعويض استدعاءات <code>Add</code>. بمجرد وصول العداد إلى الصفر، ستعود دالة الانتظار وسيستمر البرنامج في العمل.
</p>

<p>
	لنعدّل ملف "main.go" لتنفيذ الدوال من خلال خيوط معالجة جو باستخدام الكلمة المفتاحية <code>go</code>، ولنضِف <code>sync.WaitGroup</code> إلى البرنامج:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_14" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"sync"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func generateNumbers</span><span class="pun">(</span><span class="pln">total </span><span class="typ">int</span><span class="pun">,</span><span class="pln"> wg </span><span class="pun">*</span><span class="pln">sync</span><span class="pun">.</span><span class="typ">WaitGroup</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    defer wg</span><span class="pun">.</span><span class="typ">Done</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> idx </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> idx </span><span class="pun">&lt;=</span><span class="pln"> total</span><span class="pun">;</span><span class="pln"> idx</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Generating number %d\n"</span><span class="pun">,</span><span class="pln"> idx</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func printNumbers</span><span class="pun">(</span><span class="pln">wg </span><span class="pun">*</span><span class="pln">sync</span><span class="pun">.</span><span class="typ">WaitGroup</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    defer wg</span><span class="pun">.</span><span class="typ">Done</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> idx </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> idx </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> idx</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Printing number %d\n"</span><span class="pun">,</span><span class="pln"> idx</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    var wg sync</span><span class="pun">.</span><span class="typ">WaitGroup</span><span class="pln">

    wg</span><span class="pun">.</span><span class="typ">Add</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
    go printNumbers</span><span class="pun">(&amp;</span><span class="pln">wg</span><span class="pun">)</span><span class="pln">
    go generateNumbers</span><span class="pun">(</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">wg</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Waiting for goroutines to finish..."</span><span class="pun">)</span><span class="pln">
    wg</span><span class="pun">.</span><span class="typ">Wait</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Done!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سنحتاج -بعد التصريح عن <code>WaitGroup</code>- إلى معرفة عدد الأشياء التي يجب انتظارها. سيدرّك <code>wg</code> عند وضع التعليمة <code>(wg.Add(2</code> داخل الدالة <code>main</code> قبل بدء تنفيذ خيوط معالجة جو، أن عليه انتظار استدعائين <code>Done</code> حتى يُنهي عملية الانتظار. إذا لم نفعل ذلك قبل بدء تنفيذ خيوط معالجة جو، فمن المحتمل أن تحدث حالات تعطّل في البرنامج أو قد تحدث حالة هلع Panic في الشيفرة، لأن <code>wg</code> لا يعرف أنه يجب أن ينتظر أية استدعاءات <code>Done</code>.
</p>

<p>
	ستستخدم كل دالة <code>defer</code> بعد ذلك، لاستدعاء <code>Done</code> بهدف تخفيض العداد بواحد بعد انتهاء تنفيذ الدالة. تُحدَّث الدالة <code>main</code> أيضًا لتضمين استدعاء <code>Wait</code> من <code>WaitGroup</code>، لذا ستنتظر الدالة <code>main</code> حتى تُستدعى الدالة <code>Done</code> مرتين قبل إنهاء البرنامج.
</p>

<p>
	بعد حفظ ملف main.go، يمكننا تشغيله باستخدام الأمر التالي:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Printing number 1
Waiting for goroutines to finish...
Generating number 1
Generating number 2
Generating number 3
Printing number 2
Printing number 3
Done!
</pre>

<p>
	قد يختلف الخرج عما تراه أعلاه (تذكر أن التنفيذ المتساير لا يمكن التنبؤ بسلوكه)، وقد يختلف في كل مرة تُنفّذ فيها الشيفرة. سيعتمد الخرج الناتج عن تنفيذ الدالتين السابقتين على مقدار الوقت الذي يمنحه نظام التشغيل ومُصرّف لغة جو لكل دالة وهذا أمر يصعب معرفته؛ فمثلًا قد تُمنح كل دالة وقتًا كافيًا لتُنفّذ كل تعليماتها دفعةً واحدة قبل أن يقاطع نظام التشغيل تنفيذها، وبالتالي سيكون الخرج كما لو أن التنفيذ كان تسلسيًا، وفي بعض الأحيان لن تحصل الدالة على ما يكفي من الوقت دفعةً واحدة، أي تطبع سطر ثم يتوقف تنفيذها ثم تطبع الدالة الثانية سطر ثم يتوقف تنفيذها ثم نعود للدالة الأولى فتطبع باقي الأسطر وهكذا، وسنرى عندها خرجًا مشابهًا للخرج أعلاه.
</p>

<p>
	على سبيل التجربة، يمكننا حذف تعليمة استدعاء <code>()wg.Wait</code> في الدالة الرئيسية <code>main</code> ثم تنفيذ الشيفرة عدة مرات باستخدام <code>go run</code>. اعتمادًا على جهاز الحاسب المُستخدم، قد ترى بعض النتائج من دالتي <code>generateNumbers</code> و <code>printNumbers</code>، ولكن من المحتمل أيضًا ألا ترى أي خرج منهما إطلاقًا، وذلك لأنه عند حذفنا لاستدعاء الدالة <code>Wait</code>، لن ينتظر البرنامج اكتمال تنفيذ الدالتين حالما ينتهي من تنفيذ باقي تعليماته؛ أي بدلًا من استخدام مبدأ "ضعهم في الخلفية وأكمل عملك وانتظرهم"، سيعتمد مبدأ "ضعهم في الخلفية وأكمل عملك وانساهم"، وبما أن الدالة الرئيسية تنتهي بعد وقت قصير من دالة <code>Wait</code>، فهناك فرصة جيدة لأن يصل برنامجك إلى نهاية الدالة <code>main</code> ويخرج قبل انتهاء تنفيذ خيوط معالجة جو. عند حصول ذلك قد ترى بعضًا من الأرقام تُطبع من خلال الدالتين، لكن غالبًا لن ترى الخرج المتوقع كاملًا.
</p>

<p>
	أنشأنا في هذا القسم برنامجًا يستخدم الكلمة المفتاحية <code>go</code> لتنفيذ دالتين على التساير وفقًا لمبدأ خيوط معالجة جو وطباعة سلسلة من الأرقام. استخدمنا أيضًا <code>sync.WaitGroup</code> لجعل البرنامج ينتظر هذه خيوط المعالجة حتى تنتهي قبل الخروج من البرنامج.
</p>

<p>
	ربما نلاحظ أن الدالتين <code>generateNumbers</code> و <code>printNumbers</code> لا تعيدان أية قيم، إذ يتعذر على خيوط معالجة جو إعادة قيم مثل الدوال العادية، على الرغم من أنه بمقدورنا استخدام الكلمة المفتاحية <code>go</code> مع الدوال التي تُعيد قيم، ولكن سيتجاهل المُصرّف هذه القيم ولن تتمكن من الوصول إليها. هذا يطرح تساؤلًا؛ ماذا نفعل إذا كنا بحاجة إلى تلك القيم المُعادة (مثلًا نريد نقل بيانات من تنظيم إلى تنظيم آخر)؟ يكمن الحل باستخدام قنوات جو التي أشرنا لها في بداية المقال، والتي تسمح لخيوط معالجة جو بالتواصل بطريقة آمنة.
</p>

<h2>
	التواصل بين خيوط معالجة جو بأمان من خلال القنوات
</h2>

<p>
	أحد أصعب أجزاء البرمجة المتسايرة هو الاتصال بأمان بين أجزاء البرنامج المختلفة التي تعمل في وقت واحد، فإذا لم تكن حذرًا قد تتعرض لمشاكل من نوع خاص أثناء التنفيذ. على سبيل المثال، يمكن أن يحدث ما يسمى <a href="https://en.wikipedia.org/wiki/Race_condition" rel="external nofollow">سباق البيانات Data race</a> عندما يجري تنفيذ جزأين من البرنامج على التساير، ويحاول أحدهما تحديث متغير بينما يحاول الجزء الآخر قراءته في نفس الوقت. عندما يحدث هذا، يمكن أن تحدث القراءة أو الكتابة بترتيب غير صحيح، مما يؤدي إلى استخدام أحد أجزاء البرنامج أو كليهما لقيمة خاطئة. مثلًا، كان من المفترض الكتابة ثم القراءة، لكن حدث العكس، وبالتالي نكون قد قرأنا قيمة خاطئة. يأتي اسم "سباق البيانات" من فكرة أن عاملين Worker يتسابقان للوصول إلى نفس المتغير أو المورد. على الرغم من أنه لا يزال من الممكن مواجهة مشكلات التساير مثل سباق البيانات في لغة جو، إلا أن تصميم اللغة يُسهّل تجنبها. إضافةً إلى خيوط معالجة جو، تُعد قنوات جو ميزةً أخرى تهدف إلى جعل التساير سهلًا وأكثر أمنًا للاستخدام. يمكن التفكير بالقناة على أنها أنبوب pipe بين تنظيمين أو أكثر، ويمكن للبيانات العبور خلالها. يضع أحد خيوط معالجة البيانات في أحد طرفي الأنبوب ليتلقاه تنظيم آخر في الطرف الآخر، وتجري معالجة الجزء الصعب المتمثل في التأكد من انتقال البيانات من واحد إلى آخر بأمان نيابةً عنّا. إنشاء قناة في جو مُشابه لإنشاء <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-arrays-%D9%88%D8%A7%D9%84%D8%B4%D8%B1%D8%A7%D8%A6%D8%AD-slices-%D9%81%D9%8A-%D8%AC%D9%88-go-r1866/" rel="">شريحة slice</a>، باستخدام الدالة المُضمّنة <code>()make</code>. يكون التصريح من خلال استخدام الكلمة المفتاحية <code>chan</code> متبوعةً <a href="https://academy.hsoub.com/programming/general/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1726/" rel=""> بنوع البيانات</a> التي تريد إرسالها عبر القناة؛ فمثلًا لإنشاء قناة تُرسل قيمًا من الأعداد الصحيحة، يجب أن تستخدم النوع <code>chan int</code>؛ وإذا أردت قناة لإرسال قيم بايت <code>byte[]</code>، سنكتب <code>chan []byte</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_16" style=""><span class="pln">bytesChan </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="pun">[]</span><span class="pln">byte</span><span class="pun">)</span></pre>

<p>
	يمكنك -بمجرد إنشاء القناة- إرسال أو استقبال البيانات عبر القناة باستخدام العامل <code>-&gt;</code>، إذ يحدد موضع العامل <code>-&gt;</code> بالنسبة إلى متغير القناة ما إذا كنت تقرأ من القناة أو تكتب إليها؛ فللكتابة إلى قناة نبدأ بمتغير القناة متبوعًا بالعامل <code>-&gt;</code>، ثم القيمة التي نريد كتابتها إلى القناة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_18" style=""><span class="pln">intChan </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">)</span><span class="pln">
intChan </span><span class="pun">&lt;-</span><span class="pln"> </span><span class="lit">10</span></pre>

<p>
	لقراءة قيمة من قناة: نبدأ بالمتغير الذي نريد وضع القيمة فيه، ثم نضع إما <code>=</code> أو <code>=:</code> لإسناد قيمة إلى المتغير متبوعًا بالعامل <code>-&gt;</code>، ثم القناة التي تريد القراءة منها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_20" style=""><span class="pln">intChan </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">)</span><span class="pln">
intVar </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&lt;-</span><span class="pln"> intChan</span></pre>

<p>
	للحفاظ على هاتين العمليتين في وضع سليم، تذكر أن السهم <code>-&gt;</code> يشير دائمًا إلى اليسار (عكس <code>&lt;-</code>)، ويشير السهم إلى حيث تتجه القيمة. في حالة الكتابة إلى قناة، يكون السهم منطلقًا من القيمة إلى القناة. أما عند القراءة من قناة، فيكون السهم من القناة إلى المتغير. على غرار الشريحة: يمكن قراءة القناة باستخدام الكلمة المفتاحية <code>range</code> في <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%AD%D9%84%D9%82%D8%A9-%D8%A7%D9%84%D8%AA%D9%83%D8%B1%D8%A7%D8%B1-for-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1950/" rel="">حلقة <code>for</code></a>. عند قراءة قناة باستخدام <code>range</code>، ستُقرأ القيمة التالية من القناة في كل تكرار للحلقة وتُوضع في متغير الحلقة، وستستمر القراءة من القناة حتى إغلاق القناة، أو الخروج من حلقة <code>for</code> بطريقة ما، مثل استخدام تعليمة <code>break</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_22" style=""><span class="pln">intChan </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> range intChan </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// Use the value of num received from the channel</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> num </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">break</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	قد نرغب أحيانًا في السماح لدالة فقط بالقراءة أو الكتابة من القناة وليس كلاهما. لأجل ذلك يمكننا أن نضيف العامل <code>-&gt;</code> إلى تصريح القناة <code>chan</code>. يمكننا استخدام المعامل <code>-&gt;</code> بطريقة مشابهة لعملية القراءة والكتابة من قناة للسماح فقط بالقراءة أو الكتابة أو القراءة والكتابة معًا. على سبيل المثال، لتعريف قناة للقراءة فقط لقيم <code>int</code>، سيكون التصريح <code>chan int-&gt;</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_24" style=""><span class="pln">func readChannel</span><span class="pun">(</span><span class="pln">ch </span><span class="pun">&lt;-</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ch is read-only</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لجعل القناة للكتابة فقط، سيكون التصريح <code>chan&lt;- int</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_26" style=""><span class="pln">func writeChannel</span><span class="pun">(</span><span class="pln">ch chan</span><span class="pun">&lt;-</span><span class="pln"> </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ch is write-only</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لاحظ أن السهم يُشير إلى خارج القناة للقراءة ويشير إلى القناة من أجل الكتابة. إذا لم يكن للتصريح سهم، كما في حالة <code>chan int</code>، فهذا يعني أنه يمكن استخدام القناة للقراءة والكتابة.
</p>

<p>
	أخيرًا، عند انتهاء الحاجة من استخدام القناة، يمكن إغلاقها باستخدام الدالة <code>()close</code>، وتًعد هذه الخطوة ضرورية، فقد يؤدي إنشاء القنوات وتركها دون استخدام عدة مرات في أحد البرامجما يُعرف باسم <a href="https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%B0%D8%A7%D9%83%D8%B1%D8%A9-memory-management-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-c-r994/" rel="">تسرب الذاكرة Memory leak</a>، إذ يحدث تسرب للذاكرة عندما يُنشئ برنامج ما شيئًا ما يستخدم ذاكرة الحاسب، لكنه لا يحرّر تلك الذاكرة بعد الانتهاء من استخدامها، وهذا من شأنه أن يُبطئ تنفيذ البرنامج والحاسب عمومًا؛ فعند إنشاء قناة باستخدام <code>()make</code>، يخصص نظام التشغيل جزءًا من ذاكرة الحاسب للقناة، وتحرّر هذه الذاكرة عند استدعاء الدالة <code>()close</code> ليُتاح استخدامها من قبل شيء آخر.
</p>

<p>
	لنحدّث الآن ملف "main.go" لاستخدام قناة <code>chan int</code> للتواصل بين خيوط معالجة جو. ستنشئ الدالة <code>generateNumbers</code> أرقامًا وتكتبها على القناة بينما ستقرأ الدالة <code>printNumbers</code> هذه الأرقام من القناة وتطبعها على الشاشة. سننشئ في الدالة <code>main</code> قناةً جديدة لتمريرها مثل معامل لكل دالة من الدوال الأخرى، ثم تستخدم <code>()close</code> على القناة لإغلاقها لعدم الحاجة لاستخدامها بعد ذلك. يجب ألا تكون دالة <code>generateNumbers</code> تنظيم جو بعد ذلك، لأنه بمجرد الانتهاء من تنفيذ هذه الدالة، سينتهي البرنامج من إنشاء جميع الأرقام التي يحتاجها. تُستدعى دالة <code>()close</code> بهذه الطريقة على القناة فقط قبل انتهاء تشغيل كلتا الدالتين.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_28" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"sync"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func generateNumbers</span><span class="pun">(</span><span class="pln">total </span><span class="typ">int</span><span class="pun">,</span><span class="pln"> ch chan</span><span class="pun">&lt;-</span><span class="pln"> </span><span class="typ">int</span><span class="pun">,</span><span class="pln"> wg </span><span class="pun">*</span><span class="pln">sync</span><span class="pun">.</span><span class="typ">WaitGroup</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    defer wg</span><span class="pun">.</span><span class="typ">Done</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> idx </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> idx </span><span class="pun">&lt;=</span><span class="pln"> total</span><span class="pun">;</span><span class="pln"> idx</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"sending %d to channel\n"</span><span class="pun">,</span><span class="pln"> idx</span><span class="pun">)</span><span class="pln">
        ch </span><span class="pun">&lt;-</span><span class="pln"> idx
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func printNumbers</span><span class="pun">(</span><span class="pln">ch </span><span class="pun">&lt;-</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">,</span><span class="pln"> wg </span><span class="pun">*</span><span class="pln">sync</span><span class="pun">.</span><span class="typ">WaitGroup</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    defer wg</span><span class="pun">.</span><span class="typ">Done</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> range ch </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"read %d from channel\n"</span><span class="pun">,</span><span class="pln"> num</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    var wg sync</span><span class="pun">.</span><span class="typ">WaitGroup</span><span class="pln">
    numberChan </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">)</span><span class="pln">

    wg</span><span class="pun">.</span><span class="typ">Add</span><span class="pun">(</span><span class="lit">2</span><span class="pun">)</span><span class="pln">
    go printNumbers</span><span class="pun">(</span><span class="pln">numberChan</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">wg</span><span class="pun">)</span><span class="pln">

    generateNumbers</span><span class="pun">(</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> numberChan</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">wg</span><span class="pun">)</span><span class="pln">

    close</span><span class="pun">(</span><span class="pln">numberChan</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Waiting for goroutines to finish..."</span><span class="pun">)</span><span class="pln">
    wg</span><span class="pun">.</span><span class="typ">Wait</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Done!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تستخدم أنواع <code>chan</code> في معاملات الدالتين <code>generateNumbers</code> و <code>printNumbers</code> أنواع القراءة فقط والكتابة فقط، إذ تحتاج الدالة <code>generateNumbers</code> إلى القدرة على الكتابة في القناة وبالتالي نجعل <code>chan</code> للكتابة فقط من خلال جعل السهم <code>-&gt;</code> يشير إلى القناة، بينما <code>printNumbers</code> تحتاج للقراءة فقط من خلال جعل السهم <code>-&gt;</code> يشير إلى خارج القناة.
</p>

<p>
	على الرغم من أنه كان بإمكاننا جعل الدوال تستطيع القراءة والكتابة وليس فقط واحدًا منهما من خلال استخدام <code>chan int</code>، إلا أنه من الأفضل تقييدهم وفقًا لما تحتاجه كل دالة، وذلك لتجنب التسبب بطريق الخطأ في توقف برنامجك عن العمل والوقوع في حالة الجمود أو التوقف التام deadlock. تحدث حالة الجمود عندما ينتظر ينتظر جزء A من البرنامج جزءًا آخر B لفعل شيء ما، لكن الجزء B هذا ينتظر أيضًأ الجزء A لفعل شيء ما. هنا أصبح كل منهما ينتظر الآخر وبالتالي فإن كلاهما سيبقى منتظرًا ولن ينتهي من التنفيذ، ولن يكتمل تنفيذ البرنامج أيضًا.
</p>

<p>
	قد يحدث الجمود بسبب الطريقة التي تعمل بها اتصالات القنوات في لغة جو، فعندما يكتب جزء من البرنامج إلى قناة، فإنه سينتظر حتى يقرأ جزءًا آخر من البرنامج من تلك القناة قبل المتابعة، وبالمثل، إذا كان البرنامج يقرأ من قناة، فإنه سينتظر حتى يكتب جزءًا آخر من البرنامج على تلك القناة قبل أن يستمر. عندما يكتب جزء A على القناة، سينتظر الجزء B أن يقرأ ما كتبه على القناة قبل أن يستمر في عمله. بطريقة مشابهة إذا كان الجزء A يقرأ من قناة، سينتظر حتى يكتب الجزء B قبل أن يستمر في عمله. يُقال أن جزءًا من البرنامج "محظور Blocking" عندما ينتظر حدوث شيء آخر، وذلك لأنه ممنوع من المتابعة حتى يحدث ذلك الشيء. تُحظر القنوات عند الكتابة إليها أو القراءة منها، لذلك إذا كانت لدينا دالة نتوقع منها أن تكتب إلى القناة ولكنها عن طريق الخطأ تقرأ من القناة، فقد يدخل البرنامج في حالة الجمود لأن القناة لن يُكتب فيها إطلاقًا. لضمان عدم حدوث ذلك نستخدم أسلوب القراءة فقط <code>chan int-&gt;</code> أو الكتابة فقط <code>chan&lt;- int</code> بدلًًا من القراءة والكتابة معًا <code>chan int</code>.
</p>

<p>
	أحد الجوانب المهمة الأخرى للشيفرة المحدّثة هو استخدام <code>()close</code> لإغلاق القناة بمجرد الانتهاء من الكتابة عليها عن طريق <code>generateNumbers</code>، إذ يؤدي استدعاء الدالة <code>()close</code> في البرنامج السابق إلى إنهاء حلقة <code>for ... range</code> في دالة <code>printNumbers</code>. نظرًا لأن استخدام <code>range</code> للقراءة من القناة يستمر حتى تُغلق القناة التي يُقرأ منها، بالتالي إذا لم تُستدعى <code>close</code> على <code>numberChan</code> فلن تنتهي <code>printNumbers</code> أبدًا، وفي هذه الحالة لن يُستدعى التابع <code>Done</code> الخاص بـ <code>WaitGroup</code> إطلاقًا من خلال <code>defer</code> عند الخروج من <code>printNumbers</code>، وإذا لم يُستدعى، لن ينتهي تنفيذ البرنامج، لأن التابع <code>Wait</code> الخاص بـ <code>WaitGroup</code> في الدالة <code>main</code> لن يستمر. هذا مثال آخر على حالة الجمود، لأن الدالة <code>main</code> تنتظر شيئًا لن يحدث أبدًا.
</p>

<p>
	نفّذ الآن ملف "main.go" باستخدام الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	قد يختلف الخرج قليلًا عما هو معروض أدناه، ولكن يجب أن يكون متشابهًا:
</p>

<pre class="ipsCode">sending 1 to channel
sending 2 to channel
read 1 from channel
read 2 from channel
sending 3 to channel
Waiting for functions to finish...
read 3 from channel
Done!
</pre>

<p>
	يُظهر خرج البرنامج السابق أن الدالة <code>generateNumbers</code> تولّد الأرقام من واحد إلى ثلاثة أثناء كتابتها على القناة المشتركة مع <code>printNumbers</code>. حالما تستقبل <code>printNumbers</code> الرقم تطبعه على شاشة الخرج، وبعد أن تولّد <code>generateNumbers</code> الأرقام الثلاثة كلها، سيكون قد انتهى تنفيذها وستخرج، سامحةً بذلك للدالة <code>main</code> بإغلاق القناة والانتظار ريثما تنتهي<code>printNumbers</code>. بمجرد أن تنتهي <code>printNumbers</code> من طباعة الأرقام، تستدعي <code>Done</code> الخاصة بـ <code>WaitGroup</code> وينتهي تنفيذ البرنامج.
</p>

<p>
	على غرار نتائج الخرج التي رأيناها سابقًا، سيعتمد الخرج الذي تراه على عوامل خارجية مختلفة، مثل عندما يختار نظام التشغيل أو مُصرّف لغة جو تشغيل تنظيم أو عامل معين قبل الآخر أو يبدّل بينهما، ولكن يجب أن يكون الخرج متشابهًا عمومًا.
</p>

<p>
	تتمثل فائدة تصميم البرامج باستخدام خيوط معالجة جو والقنوات في أنه بمجرد تصميم البرامج بطريقة تقبل التقسيم، يمكنك توسيع نطاقه ليشمل المزيد من خيوط معالجة جو. بما أن <code>generateNumbers</code> يكتب فقط على القناة، فلا يهم عدد الأشياء الأخرى التي تقرأ من تلك القناة، إذ سيرسل فقط أرقامًا إلى أي شيء يقرأ القناة. يمكنك الاستفادة من ذلك عن طريق تشغيل أكثر من دالة <code>printNumbers</code> مثل تنظيم جو، بحيث يقرأ كل منها من نفس القناة ويتعامل مع البيانات في نفس الوقت.
</p>

<p>
	الآن، بعد أن استخدم البرنامج قنوات للتواصل، نفتح ملف "main.go" مرةً أخرى لنُحدّث البرنامج بطريقة تمكننا من استخدام عدة دوال <code>printNumbers</code> على أنها خيوط معالجة جو. سنحتاج إلى تعديل استدعاء <code>wg.Add</code>، بحيث نضيف واحدًا لكل تنظيم نبدأه، ولا داعٍ لإضافة واحد إلى<code>WaitGroup</code> من أجل استدعاء <code>generateNumbers</code> بعد الآن، لأن البرنامج لن يستمر دون إنهاء تنفيذ كامل الدالة، على عكس ما كان يحدث عندما كنا ننفذه مثل تنظيم.
</p>

<p>
	للتأكد من أن هذه الطريقة لا تقلل من عدد <code>WaitGroup</code> عند انتهائها، يجب علينا إزالة سطر <code>()defer wg.Done</code> من الدالة. تسهّل إضافة رقم التنظيم إلى <code>printNumbers</code> رؤية كيفية قراءة القناة بواسطة كل منهم. تُعد زيادة كمية الأرقام التي تُنشأ فكرةً جيدة أيضًا بحيث يسهل تتبعها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2095_30" style=""><span class="pln">func generateNumbers</span><span class="pun">(</span><span class="pln">total </span><span class="typ">int</span><span class="pun">,</span><span class="pln"> ch chan</span><span class="pun">&lt;-</span><span class="pln"> </span><span class="typ">int</span><span class="pun">,</span><span class="pln"> wg </span><span class="pun">*</span><span class="pln">sync</span><span class="pun">.</span><span class="typ">WaitGroup</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> idx </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> idx </span><span class="pun">&lt;=</span><span class="pln"> total</span><span class="pun">;</span><span class="pln"> idx</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"sending %d to channel\n"</span><span class="pun">,</span><span class="pln"> idx</span><span class="pun">)</span><span class="pln">
        ch </span><span class="pun">&lt;-</span><span class="pln"> idx
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func printNumbers</span><span class="pun">(</span><span class="pln">idx </span><span class="typ">int</span><span class="pun">,</span><span class="pln"> ch </span><span class="pun">&lt;-</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">,</span><span class="pln"> wg </span><span class="pun">*</span><span class="pln">sync</span><span class="pun">.</span><span class="typ">WaitGroup</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    defer wg</span><span class="pun">.</span><span class="typ">Done</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> num </span><span class="pun">:=</span><span class="pln"> range ch </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%d: read %d from channel\n"</span><span class="pun">,</span><span class="pln"> idx</span><span class="pun">,</span><span class="pln"> num</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    var wg sync</span><span class="pun">.</span><span class="typ">WaitGroup</span><span class="pln">
    numberChan </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> idx </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> idx </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> idx</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        wg</span><span class="pun">.</span><span class="typ">Add</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
        go printNumbers</span><span class="pun">(</span><span class="pln">idx</span><span class="pun">,</span><span class="pln"> numberChan</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">wg</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    generateNumbers</span><span class="pun">(</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> numberChan</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">wg</span><span class="pun">)</span><span class="pln">

    close</span><span class="pun">(</span><span class="pln">numberChan</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Waiting for goroutines to finish..."</span><span class="pun">)</span><span class="pln">
    wg</span><span class="pun">.</span><span class="typ">Wait</span><span class="pun">()</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Done!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بعد تحديث ملف "main.go"، يمكنك تشغيل البرنامج مرةً أخرى باستخدام <code>go run</code>. ينبغي أن يبدأ البرنامج بإنشاء ثلاثة خيوط معالجة للدالة <code>printNumbers</code> قبل المتابعة وتوليد الأرقام، كما ينبغي أن يُنشئ البرنامج أيضًا خمسة أرقام بدلًا من ثلاثة لتسهيل رؤية الأرقام موزعة بين كل من خيوط المعالجة الثلاثة للدالة <code>printNumbers</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	قد يبدو الخرج مشابهًا لهذا (قد يختلف الخرج قليلًا):
</p>

<pre class="ipsCode">sending 1 to channel
sending 2 to channel
sending 3 to channel
3: read 2 from channel
1: read 1 from channel
sending 4 to channel
sending 5 to channel
3: read 4 from channel
1: read 5 from channel
Waiting for goroutines to finish...
2: read 3 from channel
Done!
</pre>

<p>
	بالنظر إلى الخرج في هذه المرة، هناك احتمال كبير ليختلف الخرج عن الناتج الذي تراه أعلاه، لأنه هناك 3 خيوط معالجة من الدالة <code>printNumbers</code> تُنفّذ، وأيٌّ منها قد يقرأ أحد الأرقام المولّدة، وبالتالي هناك احتمالات خرج عديدة. عندما يتلقى أحد خيوط معالجة الدالة <code>printNumbers</code> رقمًا، فإنه يقضي وقتًا قصيرًا في طباعة هذا الرقم على الشاشة، وفي نفس الوقت يكون هناك تنظيم آخر يقرأ الرقم التالي من القناة ويفعل الشيء نفسه. عندما ينتهي تنظيم من قراءة الرقم الذي استقبله وطباعته على الشاشة، سيذهب للقناة مرةً أخرى ويحاول قراءة رقم آخر وطباعته، وإذا لم يجد رقمًا جديدًا، فسيبدأ الحظر حتى يمكن قراءة الرقم التالي. بمجرد أن تنتهي الدالة <code>generateNumbers</code> من التنفيذ وتستدعى الدالة <code>()close</code> على القناة، ستغلِق كل خيوط معالجة الدالة <code>printNumbers</code> حلقاتها وتخرج، وعندما تخرج جميع خيوط المعالجة الثلاثة وتستدعي <code>Done</code> من <code>WaitGroup</code>، يصل عدّاد <code>WaitGroup</code> إلى الصفر وينتهي البرنامج. يمكنك أيضًا تجربة زيادة أو إنقاص عدد خيوط المعالجة أو الأرقام التي تُنشأ لمعرفة كيف يؤثر ذلك على الخرج.
</p>

<p>
	عند استخدام خيوط معالجة جو، تجنب أن تُكثر منها؛ فمن الناحية النظرية يمكن أن يحتوي البرنامج على مئات أو حتى الآلاف من خيوط المعالجة، وهذا ما قد يكون له تأثير عكسي على الأداء، فقد يُبطئ برنامجك والحاسب والوقوع في حالة مجاعة الموارد Resource Starvation. في كل مرة تُنفّذ فيها لغة جو تنظيمًا، يتطلب ذلك وقتًا إضافيًا لبدء التنفيذ من جديد، إضافةً إلى الوقت اللازم لتشغيل الشيفرة في الدالة التالية، وبالتالي من الممكن أن يستغرق الحاسب وقتًا أطول في عملية التبديل بين خيوط المعالجة مقارنةً تشغيل التنظيم نفسه، وهذا ما نسميه مجاعة الموارد، لأن البرنامج وخيوط المعالجة لا تأخذ الموارد الكافية للتنفيذ أو ربما لا تحصل على أية موارد، وفي هذه الحالة يكون من الأفضل تخفيض عدد خيوط المعالجة (أجزاء الشيفرة التي تعمل بالتساير) التي ينفذها البرنامج، لتخفيض عبء الوقت الإضافي المُستغرق في التبديل بينها، ومنح المزيد من الوقت لتشغيل البرنامج نفسه. يُفضّل غالبًا أن يكون عدد خيوط المعالجة مساوٍ لعدد النوى الموجودة في المعالج أو ضعفها.
</p>

<p>
	يتيح استخدام مزيج من خيوط معالجة جو والقنوات إمكانية إنشاء برامج قوية جدًا وقابلة للتوسع من أجل أجهزة حواسيب أكبر وأكبر. رأينا في هذا القسم أنه يمكن استخدام القنوات للتواصل بين عدد قليل من خيوط معالجة جو أو حتى آلاف دون الحاجة للكثير من التغييرات. إذا أخذنا هذا في الحسبان عند كتابة البرامج، سنتمكن من الاستفادة من التساير المتاح في لغة جو لتزويد المستخدمين بتجربة شاملة أفضل.
</p>

<h2>
	الخاتمة
</h2>

<p>
	أنشأنا في هذا المقال برنامجًا يطبع أرقامًا على الشاشة باستخدام الكلمة المفتاحية <code>go</code> وخيوط معالجة جو التي تتيح لنا التنفيذ المتساير. بمجرد تشغيل البرنامج أنشأنا قنواتًا جديدةً تمرِّر قيمًا صحيحة <code>int</code> عبرها باستخدام <code>(make(chan int</code>، ثم استخدمنا القناة من خلال إرسال أرقام من تنظيم جو إلى تنظيم جو آخر عبرها، ليطبعها الأخير على الشاشة بدوره. أخيرًا وسّعنا البرنامج من خلال إنشاء عدة خيوط معالجة تؤدي نفس المهمة (تستقبل أرقام من القناة وتطبعها)، وكان مثالًا على كيفية استخدام القنوات وخيوط المعالجة لتسريع البرامج على أجهزة الحواسيب متعددة النوى.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-run-multiple-functions-concurrently-in-go" rel="external nofollow">How To Run Multiple Functions Concurrently in Go</a> لصاحبه Kristin Davidson.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%AD%D8%AF%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-private-module-%D8%B6%D9%85%D9%86-%D9%85%D8%B4%D8%B1%D9%88%D8%B9%D9%83-%D8%A8%D9%84%D8%BA%D8%A9-go-r2088/" rel="">كيفية استخدام وحدة خاصة Private Module ضمن مشروعك بلغة Go</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">تعرف على لغة البرمجة Go</a>.
	</li>
</ul>
]]></description><guid isPermaLink="false">2152</guid><pubDate>Tue, 10 Oct 2023 13:00:00 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x648;&#x62D;&#x62F;&#x629; &#x62E;&#x627;&#x635;&#x629; Private Module &#x636;&#x645;&#x646; &#x645;&#x634;&#x631;&#x648;&#x639;&#x643; &#x628;&#x644;&#x63A;&#x629; Go</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%AD%D8%AF%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-private-module-%D8%B6%D9%85%D9%86-%D9%85%D8%B4%D8%B1%D9%88%D8%B9%D9%83-%D8%A8%D9%84%D8%BA%D8%A9-go-r2088/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_08/-----Private----Go.png.0f36c194fd9e55bc554e381e994dc78a.png" /></p>
<p>
	واحدة من ميزات لغة جو هو أنها تتضمن عددًا كبيرًا من الوحدات مفتوحة المصدر، وبالتالي يمكن الوصول إليها وفحصها واستخدامها والتعلم منها بحرية. أحيانًا يكون من الضروري إنشاء وحدة جو خاصة لأسباب مختلفة، مثل الاحتفاظ بمنطق عمل داخلي خاص بالشركة.
</p>

<p>
	ستتعلم في هذه المقالة كيفية نشر وحدة module خاصة في لغة جو، وكيفية إعداد الاستيثاق authentication للوصول إليها، وستتعلم أيضًا كيفية استخدام هذا النوع من الوحدات ضمن مشروع.
</p>

<h2>
	المتطلبات الأساسية
</h2>

<ul>
	<li>
		أن يكون لديك مساحة عمل خاصة في لغة جو، وإذا لم يكن لديك اتبع سلسلة المقالات التالية:
		<ul>
			<li>
				<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a>.
			</li>
			<li>
				<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>.
			</li>
			<li>
				<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
			</li>
		</ul>
	</li>
	<li>
		أن تكون على دراية بكيفية إنشاء الوحدات في لغو جو. يمكنك مراجعة مقالة <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2086/" rel="">كيفية استخدام الوحدات Modules في لغة جو Go</a>.
	</li>
	<li>
		لديك معرفة مسبقة بنظام التحكم بالإصدار غيت Git. يمكنك مراجعة مقالة <a href="https://academy.hsoub.com/programming/workflow/git/%D9%85%D8%A7-%D9%87%D9%88-git%D8%9F-r1592/" rel="">ما هو جيت Git؟</a>.
	</li>
	<li>
		إنشاء مستودع فارغ باسم mysecret للوحدة التي ستنشرها.
	</li>
	<li>
		رمز وصول شخصي Personal access token على جيت هاب. ستحتاجه للسماح للغة جو بالوصول إلى مستودعك الخاص.
	</li>
</ul>

<h1>
	توزيع وحدة خاصة
</h1>

<p>
	على عكس العديد من لغات البرمجة، توزّع لغة جو الوحدات البرمجية من المستودعات بدلًا من خادم الحزمة المركزي. تتمثل إحدى فوائد هذا النهج في أن نشر وحدة خاصة يشبه إلى حد بعيد نشر وحدة عامة، إذ تُوزّع الوحدة الخاصة من خلال مستودع شيفرة مصدر خاص، بدلًا من طلب خادم حزمة خاص منفصل تمامًا. نظرًا لأن معظم خيارات استضافة التعليمات البرمجية المصدر تدعم هذا الأمر بما يكفي، فلا داع لإعداد خادم خاص إضافي.
</p>

<p>
	من أجل استخدام وحدة خاصة، ينبغي أن يكون لديك حق الوصول إلى وحدة خاصة. سنُنشئ في هذا القسم وحدةً خاصة وننشرها، وستتمكن من استخدامها لاحقًا خلال هذه المقالة من الوصول إلى وحدة خاصة من شيفرة أُخرى.
</p>

<p>
	سنستخدم بدايةً الأمر <code>git clone</code> لأخذ نسخة محلية من المستودع الفارغ الذي لا بُد أن نكون قد أنشأناه لأنه جزء من المتطلبات الأساسية. يجب أن يكون هذا المستودع خاصًا، وسنفترض أن اسمه "mysecret". يمكن نسخ هذا المستودع إلى أي مكان نريده على جهاز الحاسوب، لكن يميل العديد من المطورين إلى إنشاء مجلد خاص يتضمن جميع مشاريعهم. سنستخدم هنا مجلدًا باسم "projects". ابدأ بإنشاء المجلد وانتقل إليه:
</p>

<pre class="ipsCode">$ mkdir projects
$ cd projects
</pre>

<p>
	من مجلد "projects"، شغل الأمر <code>git clone</code> لنسخ المستودع "mysecret" إلى جهاز الحاسب:
</p>

<pre class="ipsCode">$ git clone git@github.com:your_github_username/mysecret.git
</pre>

<p>
	ستعطي جو تأكيدًا على عملية نسخ الوحدة، وقد تحذّرك بأنك قد استنسخت مستودعًا فارغًا، إذا حصل ذلك فلا داعٍ للقلق بشأن ذلك الأمر:
</p>

<pre class="ipsCode">Cloning into 'mysecret'...
warning: You appear to have cloned an empty repository.
</pre>

<p>
	انتقل الآن باستخدام الأمر <code>cd</code> إلى المجلد الجديد "mysecret" الذي استنسخته واستخدم الأمر <code>go mod init</code> لإنشاء الوحدة الجديدة وتمرير موقع المستودع اسمًا لها:
</p>

<pre class="ipsCode">$ cd mysecret
$ go mod init github.com/your_github_username/mysecret
</pre>

<p>
	يُعد التأكد من تطابق اسم الوحدة مع موقع المستودع أمرًا مهمًا؛ لأن هذه هي الطريقة التي تعثر بها أداة <code>go mod init</code> على مكان تنزيل الوحدة عند استخدامها في مشاريع أخرى.
</p>

<p>
	سنستخدم الآن محرر نصوص مثل نانو nano، لإنشاء وفتح ملف يحمل نفس اسم المستودع "mysecret.go".
</p>

<pre class="ipsCode">$ nano mysecret.go
</pre>

<p>
	يمكن تسمية هذا الملف بأي اسم، ولكن يُفضّل استخدام نفس اسم الحزمة لتسهيل معرفة مكان البدء عند العمل مع حزمة غير مألوفة.
</p>

<p>
	نضيف إلى الملف "mysecret.go" الدالة <code>SecretProcess</code> لطباعة الرسالة <code>!Running the secret process</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2310_8" style=""><span class="pln">package mysecret

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

func </span><span class="typ">SecretProcess</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Running the secret process!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يمكنك الآن نشر الوحدة الخاصة ليستخدمها الآخرون. بما أن المستودع الذي أنشأته هو مستودع خاص، بالتالي لا يمكن لشخص غيرك الوصول إليه، ويمنحك ذلك إمكانية التحكم في حق وصول الآخرين إلى وحدتك، وبالتالي يمكنك السماح لأشخاص محددين فقط بالوصول إليها أو عدم السماح لأي شخص بالوصول.
</p>

<p>
	بما أن الوحدات البرمجية الخاصة والعامة في لغة جو هي مستودعات مصدرية، فإن عملية نشر وحدة خاصة تتبع نفس نهج عملية نشر وحدة عامة.
</p>

<p>
	يجب علينا الآن إدراج stage الملفات باستخدام <code>git add</code> وإيداعها بالمستودع باستخدام <code>git commit</code>، لكي ننشر الوحدة:
</p>

<pre class="ipsCode">$ git add .
$ git commit -m "Initial private module implementation"
</pre>

<p>
	سترى تأكيدًا من جيت بأن الإيداع الأولي قد نجح، إضافةً إلى ملخص للملفات المضمنة في الإيداع. سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">[main (root-commit) bda059d] Initial private module implementation
 2 files changed, 10 insertions(+)
 create mode 100644 go.mod
 create mode 100644 mysecret.go
</pre>

<p>
	نستخدم الآن الأمر <code>git push</code> لدفع الوحدة إلى مستودع غيت هب:
</p>

<pre class="ipsCode">$ git push
</pre>

<p>
	سيدفع جو تغييراتك ويجعلها متاحة لأي شخص يملك حق الوصول إلى مستودعك الخاص، وسيكون الخرج كما يلي:
</p>

<pre class="ipsCode">git push origin main
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 404 bytes | 404.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:your_github_username/mysecret.git
 * [new branch]      main -&gt; main
</pre>

<p>
	يمكنك أيضًا إضافة إصدارات إلى الوحدة الخاصة التي أنشأتها <a href="https://academy.hsoub.com/programming/go/%D8%AA%D9%88%D8%B2%D9%8A%D8%B9-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D9%88%D8%A8%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-go-r2087/" rel="">بنفس الطريقة التي نفعلها مع الوحدات العامة</a>.
</p>

<p>
	أنشأت في هذا القسم وحدةً برمجيةً جديدةً تمتلك دالة تُدعى <code>SecretProcess</code> ونشرتها في مستودع mysecret الخاص بك على غيت هب، وبما أن هذا المستودع خاص، فهذا يعني أن الوحدة خاصة أيضًا، ومن أجل الوصول إلى هذه الوحدة من شيفرة أخرى أو برنامج آخر في جو، ستحتاج إلى إعداد مُحدد، حتى يعرف مُصرّف اللغة كيفية الوصول إليها.
</p>

<h2>
	ضبط جو لمنح إمكانية الوصول إلى الوحدات البرمجية الخاصة
</h2>

<p>
	كما ذكرنا سابقًا، تُوّزع الوحدات البرمجية المكتوبة بلغة جو من مستودعات الشيفرة المصدرية. يُضيف فريق تطوير لغة جو بعض الخدمات المركزية إلى هذه الوحدات للمساعدة في ضمان استمرار وجود هذه الوحدات في حالة حدوث شيء ما للمستودع الأصلي. تتضمن الإعدادات الافتراضية في لغة جو استخدام هذه الخدمات تلقائيًا، ولكن يمكن أن تحدث مشاكل عند محاولة تنزيل وحدة خاصة لأنها تتضمن إمكانية وصول مُقيّدة.
</p>

<p>
	لإخبار مُصرّف اللغة بأن بعض مسارات الاستيراد خاصة وأنه لا يجب محاولة استخدام خدمات جو المركزية معها، يمكنك استخدام متغير البيئة <code>GOPRIVATE</code>، وهو يمثّل قائمةً من بادئات مسار الاستيراد مفصولة بفواصل، إذ ستحاول أدوات جو الوصول إليها مباشرةً عند مواجهتها بدلًا من المرور عبر الخدمات المركزية. أحد الأمثلة على ذلك هو الوحدة الخاصة التي أنشأتها للتو.
</p>

<p>
	إذًا، من أجل استخدام الوحدة الخاصة، ينبغي أن نخبر مُصرّف اللغة عن المسار الذي نعدّه خاصًا، وذلك عن طريق ضبط قيمة المتغير <code>GOPRIVATE</code>. هناك عدد قليل من الخيارات الممكن إجراؤها عند ضبط قيم متغير <code>GOPRIVATE</code>، وأحد الخيارات هو ضبطه على <code>github.com</code>، لكن قد لا يفي ذلك بالغرض لأن هذا سيخبر جو بعدم استخدام الخدمات المركزية لأي وحدة مستضافة على <code>github.com</code>، بما في ذلك الوحدات التي لا تمتلكها.
</p>

<p>
	الخيار الثاني هو ضبط <code>GOPRIVATE</code> على مسار المستخدم الخاص بك فقط، مثلًا <code>github.com/your_github_username</code>، فهذا يحل المشكلة السابقة التي تتجلى بعدّ كل الوحدات المستضافة خاصة، ولكن في وقت ما قد يكون لديك وحدات عامة أنشأتها وتريد تنزيلها من نفس الحساب. قد يكون هذا حلًا مناسبًا لحد ما، ولكن كما ذكرنا، قد يمنعك من استخدام وحدات عامة من نفس الحساب.
</p>

<p>
	الخيار الأكثر دقة هو إعداد <code>GOPRIVATE</code>، بحيث يُطابق مسار الوحدة الخاصة بك تمامًا، مثلًا <code>github.com/your_github_username/mysecret</code>، إذ يحل ذلك المشاكل السابقة التي رأيناها في الخيار الأول والثاني، ولكن هنا يجب أن نضيف كل وحدة خاصة إلى هذا المتغير (قد تكون هذه مشكلةً للبعض) كما يلي:
</p>

<pre class="ipsCode">GOPRIVATE=github.com/your_github_username/mysecret,github.com/your_github_username/othersecret
</pre>

<p>
	يعود اختيار الخيار الأفضل لك، حسبما تراه مناسبًا أكثر.
</p>

<p>
	نظرًا لأن لديك الآن وحدةً خاصة واحدة فقط، سنستخدم اسم المستودع الكامل قيمةً للمتغير. لإجراء ذلك، استخدم الأمر <code>export</code> كما يلي:
</p>

<pre class="ipsCode">$ export GOPRIVATE=github.com/your_github_username/mysecret
</pre>

<p>
	إذا كنت ترغب في التحقق من نجاح عملية ضبط المتغير، فيمكنك استعراض قيمته باستخدام الأمر <code>env</code> مع <code>grep</code>:
</p>

<pre class="ipsCode">$ env | grep GOPRIVATE
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">GOPRIVATE=github.com/your_github_username/mysecret
</pre>

<p>
	الآن، أصبح <a href="https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%AA%D8%B5%D8%B1%D9%8A%D9%81-compilation-%D9%81%D9%8A-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r976/" rel="">مُصرّف اللغة</a> يعرف أن وحدتك خاصة، لكن هذا لا يزال غير كافٍ لاستخدام الوحدة في شيفرة أو برنامج آخر. جرّب تشغيل هذه الوحدة في وحدة أخرى:
</p>

<pre class="ipsCode">$ go get github.com/your_github_username/mysecret
</pre>

<p>
	وسترى الخطأ التالي:
</p>

<pre class="ipsCode">go get: module github.com/your_github_username/mysecret: git ls-remote -q origin in /Users/your_github_username/go/pkg/mod/cache/vcs/2f8c...b9ea: exit status 128:
    fatal: could not read Username for 'https://github.com': terminal prompts disabled
Confirm the import path was entered correctly.
If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.
</pre>

<p>
	تشير رسالة الخطأ هذه إلى أن مُصرّف اللغة حاول تنزيل الوحدة الخاصة بك، لكنه واجه شيئًا لا يمكنه الوصول إليه. بما أنك تستخدم غيت لتنزيل الوحدة، فغالبًا سيُطلب منك إدخال بيانات الاعتماد credentials الخاصة بك، لكن هنا يتصل مُصرّف اللغة بغيت نيابةً عنك، وبالتالي لا يمكنه مطالبتك بإدخال بيانات الاعتماد، وبالتالي يظهر الخطأ. إذًا، أنت بحاجة إلى توفير طريقة لغيت لاسترداد بيانات الاعتماد الخاصة بك.
</p>

<h2>
	توفير بيانات الاعتماد اللازمة للاتصال بالوحدة الخاصة عند استخدام بروتوكول HTTPS
</h2>

<p>
	هناك طريقة واحدة لإخبار جو بكيفية تسجيل الدخول نيابةً عنك، وهي ملف "netrc." الموجود في المجلد الرئيسي "Home" الخاص بالمستخدم؛ إذ يحتوي هذا الملف على أسماء مُضيفات مختلفة Hosts مع بيانات اعتماد تسجيل الدخول لهم، ويستخدم على نطاق واسع في عدة أدوات برمجية بما في ذلك لغة جو.
</p>

<p>
	سيحاول الأمر <code>go get</code> استخدام HTTPS أولًا عندما يحاول تنزيل الوحدة، لكن كما ذكرنا؛ لا يمكنه مطالبتك باسم المستخدم وكلمة المرور. لمنح غيت بيانات الاعتماد الخاصة بك، يجب أن يكون لديك ملف "netrc." يتضمن <code>github.com</code> في مجلدك الرئيسي.
</p>

<p>
	لإنشاء ملف "netrc." على نظام لينوكس Linux أو ماك أو اسم MacOS أو على نظام ويندوز الفرعي لنظام لينوكس Windows Subsystem for Linux -أو اختصارًا WSL، افتح ملف "netrc." في المجلد الرئيسي (/~) حتى تتمكن من تحريره:
</p>

<pre class="ipsCode">$ nano ~/.netrc
</pre>

<p>
	أنشئ الآن مدخلًا جديدًا إلى هذا الملف. ينبغي أن تكون قيمة الحقل <code>machine</code> هي اسم المضيف الذي تضبط بيانات الاعتماد له، وهو <code>github.com</code> في هذه الحالة، أما قيمة <code>login</code> فهي اسم المستخدم الخاص بك على غيت هب. أخيرًا، قيمة <code>password</code> يجب أن تكون رمز الوصول الشخصي الخاص بك، والذي من المفترض أن تكون قد أنشأته على غيت هب.
</p>

<pre class="ipsCode">machine github.com
login your_github_username
password your_github_access_token
</pre>

<p>
	يمكنك أيضًا وضع جميع المدخلات ضمن سطر واحد في الملف:
</p>

<pre class="ipsCode">machine github.com login your_github_username password your_github_access_token
</pre>

<p>
	<strong>ملاحظة:</strong> إذا كنت تستخدم <a href="https://academy.hsoub.com/apps/productivity/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%88%D8%AF%D8%B9-%D9%81%D9%8A-bitbucket-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-git-r458/" rel="">بيت باكيت Bitbucket</a> لاستضافة شيفرة المصدر الخاصة بك، فقد تحتاج أيضًا إلى إضافة مُدخل ثاني من أجل <code>api.bitbucket.org</code> إضافةً إلى <code>bitbucket.org</code>. كانت بيت باكيت توفّر سابقًا استضافةً لأنواع متعددة من أنظمة التحكم في الإصدار، لذلك سيستخدم مُصرّف جو <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel="">واجهة برمجة التطبيقات</a> للتحقق من نوع المستودع قبل محاولة تنزيله. هذا الأمر كان في الماضي ولم يعد الأمر كذلك، إلا أن واجهة برمجة التطبيقات التي تُمكّنك من فحص نوع المستودع ما زالت موجودة. بكافة الأحوال، إذا واجهت هذه المشكلة، فقد تبدو رسالة الخطأ على النحو التالي:
</p>

<pre class="ipsCode">go get bitbucket.org/your_github_username/mysecret: reading https://api.bitbucket.org/2.0/repositories/your_bitbucket_username/protocol?fields=scm: 403 Forbidden
    server response: Access denied. You must have write or admin access.
</pre>

<p>
	إذا رأيت رسالة الخطأ <a href="https://academy.hsoub.com/devops/servers/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D9%88%D8%A5%D8%B5%D9%84%D8%A7%D8%AD-%D8%B1%D9%85%D9%88%D8%B2-%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-http-%D8%A7%D9%84%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-r116/" rel="">"‎403 Forbidden"</a> أثناء محاولة تنزيل وحدة خاصة، تحقق من اسم مستخدم جو الذي تحاول الاتصال به؛ فمن الممكن أن تشير إلى اسم مستخدم مختلف مثل <code>api.bitbucket.org</code>، والذي ينبغي إضافته إلى الملف "netrc.".
</p>

<p>
	بذلك تكون قد أعددت بيئتك لاستخدام مصادقة HTTPS لتنزيل الوحدة الخاصة بك. ذكرنا سابقًا أن الأمر <code>go get</code> يستخدم افتراضيًا HTTPS عندما يحاول تنزيل الوحدة، لكن من الممكن أيضًا جعله يستخدم <a href="https://academy.hsoub.com/devops/security/ssh/%D9%85%D8%A7-%D9%87%D9%8A-%D8%AA%D9%82%D9%86%D9%8A%D8%A9-ssh%D8%9F-r793/" rel="">بروتوكول النقل الآمن Secure Shell</a> -أو اختصارًا <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>- بدلًا من ذلك. يمكن أن يكون استخدام <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> بدلًا من HTTPS مفيدًا حتى تتمكن من استخدام نفس مفتاح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> الذي استخدمته لدفع الوحدة الخاصة بك، كما يسمح لك باستخدام مفاتيح النشر deploy keys عند إعداد بيئة <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd-r644/" rel="">CI/CD</a> إذا كنت تفضل عدم إنشاء رمز وصول شخصي.
</p>

<h2>
	توفير بيانات الاعتماد اللازمة للاتصال بالوحدة الخاصة عند استخدام بروتوكول <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>
</h2>

<p>
	يوفر غيت خيار إعداد يُسمى <code>insteadOf</code> لاستخدام مفتاح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> الخاص بك بمثابة طريقة للمصادقة مع الوحدة الخاصة بك بدلًا من HTTPS. يتيح لك هذا الخيار أن تقول "بدلًا من" استخدام "/https://github.com" مثل عنوان URL لإرسال طلبات إلى غيت، تُريد استخدام "/<abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr></abbr>://git@github.com".
</p>

<p>
	يتواجد هذا الإعداد في أنظمة لينوكس وماك ونظام ويندوز الفرعي WSL في ملف "gitconfig.". قد تكون على دراية بهذا الملف فعلًا، إذ يتضمن أيضًا إعدادات عنوان البريد الإلكتروني والاسم الخاصين بك. افتح هذا الملف "gitconfig./~" الآن من مجلدك الرئيسي "Home" باستخدام محرر النصوص الذي تفضله وليكن نانو هنا:
</p>

<pre class="ipsCode">$ nano ~/.gitconfig
</pre>

<p>
	عدّل هذا الملف ليحتوي على قسم <code>url</code> من أجل "/<abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr></abbr>://git@github.com" كما يلي:
</p>

<pre class="ipsCode">[user]
    email = your_github_username@example.com
    name = Sammy the Shark

[url "ssh://git@github.com/"]
    insteadOf = https://github.com/
</pre>

<p>
	ترتيب قسم <code>user</code> بالنسبة للقسم <code>url</code> غير مهم، ولا داع للقلق إن لم يكن هناك أي شيء آخر غير قسم <code>url</code> الذي أضفته منذ قليل. أيضًا تريتب حقلي <code>email</code> و <code>name</code> داخل قسم <code>user</code> غير مهم.
</p>

<p>
	سيخبر القسم الجديد الذي أضفته غيت أن أي عنوان URL تكون بادئته "/https://github.com" يجب أن تُستبدل بادئته إلى "/<abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr></abbr>: //git@github.com" (الاختلاف بالبادئة). هذا سيؤثر طبعًا على أوامر <code>go get</code>، لأن جو تستخدم HTTPS افتراضيًا. لو أخذنا مسار الوحدة الخاصة بك مثالًا، فإن جو سيُحوّل مسار الاستيراد "github.com/your_github_username/mysecret" إلى عنوان URL يكون كما يلي: "https://github.com/your_github_username/mysecret". عندما يصادف جو عنوان URL هذا، سيرى عنوان URL يطابق البادئة "/https://github.com" المشار إليها بواسطة <code>insteadOf</code> وسيعيد عنوان URL إلى "<abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr></abbr>://git@github.com/your_github_username/mysecret" يمكن أيضًا استخدام هذا النمط لنطاقات أخرى وليس فقط لغيت هب، طالما أن @<abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr></abbr>://git يعمل مع ذلك المضيف أيضًا.
</p>

<p>
	ضبطنا خلال هذا القسم غيت لاستخدام <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>، وذلك من أجل تنزيل وحدات جو، وذلك من خلال تحديث ملف "gitconfig."، إذ أضفنا القسم <code>url</code>. الآن بعد أن أكملنا الإعدادات اللازمة للمصادقة، أصبح بالإمكان الوصول إلى الوحدة واستخدامها في برامج جو.
</p>

<h2>
	استخدام وحدة خاصة
</h2>

<p>
	تعلمت خلال الأقسام السابقة كيفية إعداد جو ليتمكن من الوصول إلى الوحدة الخاصة بك عبر HTTPS أو <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> أو ربما كليهما. إذًا، أصبح بإمكانك الآن استخدامها مثل أي وحدة عامة أخرى. ستنشئ الآن وحدةً جديدةً تستخدم وحدتك الخاصة.
</p>

<p>
	أنشئ الآن مجلدًا باسم "myproject" باستخدام الأمر <code>mkdir</code> وضعه في مجلد المشاريع الخاص بك "projects":
</p>

<pre class="ipsCode">$ mkdir myproject
</pre>

<p>
	انتقل الآن إلى المجلد الذي أنشأته باستخدام الأمر <code>cd</code> وهيّئ الوحدة الجديدة باستخدام الأمر <code>go mod init</code> اعتمادًا على عنوان URL للمستودع الذي ستضع فيه المشروع، مثل "github.com/your_github_username/myproject". إذا كنت لا تخطط لدفع المشروع إلى مستودع آخر، فيمكن أن يكون اسم الوحدة <code>myproject</code> أو أي اسم آخر، ولكن من الممارسات الجيدة استخدام عناوين URL الكاملة، لأن معظم الوحدات التي تجري مشاركتها ستحتاج إليها.
</p>

<pre class="ipsCode">$ cd myproject
$ go mod init github.com/your_github_username/myproject
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">go: creating new go.mod: module github.com/your_github_username/myproject
</pre>

<p>
	انشئ الآن ملف "main.go" لتضع فيه أول شيفرة باستخدام محرر النصوص الذي تفضله وليكن نانو:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	ضع الشيفرة التالية داخل هذا الملف، والتي تتضمن الدالة <code>main</code> التي سنستدعي الوحدة بداخلها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2310_10" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"My new project!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغل ملف "main.go" باستخدام الأمر <code>go run</code> لرؤية المشروع النهائي الذي يستخدم الوحدة الخاصة بك:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">My new project!
</pre>

<p>
	أضف الآن وحدتك الخاصة مثل اعتمادية لمشروعك الجديد باستخدام الأمر <code>go get</code> تمامًا كما تفعل مع الوحدة العامة:
</p>

<pre class="ipsCode">$ go get github.com/your_github_username/mysecret
</pre>

<p>
	تنزّل عندها أداة <code>go</code> شيفرة الوحدة الخاصة وتضيفها مثل اعتمادية باستخدام سلسلة نصية للإصدار تطابق إيداع التعميلة hash الأخير ووقت ذلك الإيداع:
</p>

<pre class="ipsCode">go: downloading github.com/your_github_username/mysecret v0.0.0-20210920195630-bda059d63fa2
go get: added github.com/your_github_username/mysecret v0.0.0-20210920195630-bda059d63fa2
</pre>

<p>
	افتح ملف "main.go" مرةً أخرى وحدّثه لإضافة استدعاء لدالة الوحدة الخاصة <code>SecretProcess</code> ضمن دالة <code>main</code> الرئيسية. ستحتاج أيضًا إلى تحديث عبارة <code>import</code> لإضافة وحدتك الخاصة <code>github.com/your_github_username/mysecret</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2310_12" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">

    </span><span class="str">"github.com/your_github_username/mysecret"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"My new project!"</span><span class="pun">)</span><span class="pln">
    mysecret</span><span class="pun">.</span><span class="typ">SecretProcess</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شغل ملف "main.go" باستخدام الأمر <code>go run</code> لرؤية المشروع النهائي الذي يستخدم الوحدة الخاصة بك:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ستلاحظ أن الخرج يتضمن الجملة <code>!My new project</code> من الشيفرة الأصلية، كما يتضمن الجملة <code>!Running the secret process</code> من الوحدة mysecret التي استوردناها.
</p>

<pre class="ipsCode">My new project!
Running the secret process!
</pre>

<p>
	استخدمنا في هذا القسم الأمر <code>go init</code> لإنشاء وحدة جديدة للوصول إلى الوحدة الخاصة التي نشرناها سابقًا. بعد إنشاء الوحدة، ستتمكن من استخدام الأمر <code>go get</code> لتنزيل الوحدة الخاصة بك؛ تمامًا كما لو كنت تتعامل مع وحدة عامة. استخدمنا أيضًا الأمر <code>go run</code> لتصريف وتشغيل البرنامج الذي يستخدم الوحدة الخاصة.
</p>

<h2>
	الخاتمة
</h2>

<p>
	نشرنا خلال هذه المقالة وحدة خاصة باستخدام لغة جو، وتعلمنا كيفية إعداد متطلبات المصادقة باستخدام بروتوكول HTTPS ومفتاح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> من أجل الوصول إلى الوحدة الخاصة من شيفرة أخرى. استخدمنا أيضًا الوحدة الخاصة ضمن مشروع.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-a-private-go-module-in-your-own-project" rel="external nofollow">How to Use a Private Go Module in Your Own Project</a> لصاحبه Kristin Davidson.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D8%AA%D9%88%D8%B2%D9%8A%D8%B9-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D9%88%D8%A8%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-go-r2087/" rel="">توزيع الوحدات Modules المكتوبة بلغة Go</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A3%D9%88%D9%84-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC-%D9%88%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%84%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r241/" rel="">كتابة أول برنامج (ومكتبة) لك باستخدام لغة البرمجة Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r534/" rel="">الدليل السريع إلى لغة البرمجة Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D9%88%D8%A8%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-%D9%88%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87%D8%A7-r1996/" rel="">بناء البرامج المكتوبة بلغة جو Go وتثبيتها</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/apps/productivity/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%88%D8%AF%D8%B9-%D9%81%D9%8A-bitbucket-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-git-r458/" rel="">التعامل مع ملفات المستودع في BitBucket واستخدام Git</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2088</guid><pubDate>Fri, 25 Aug 2023 16:00:00 +0000</pubDate></item><item><title>&#x62A;&#x648;&#x632;&#x64A;&#x639; &#x627;&#x644;&#x648;&#x62D;&#x62F;&#x627;&#x62A; Modules &#x627;&#x644;&#x645;&#x643;&#x62A;&#x648;&#x628;&#x629; &#x628;&#x644;&#x63A;&#x629; Go</title><link>https://academy.hsoub.com/programming/go/%D8%AA%D9%88%D8%B2%D9%8A%D8%B9-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D9%88%D8%A8%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-go-r2087/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_08/--Modules---Go.png.64b709bc5f5b8c4c91f635a4c06e65b8.png" /></p>
<p>
	تسمح العديد من لغات البرمجة الحديثة -بما في ذلك لغة جو- للمطورين بتوزيع مكتبات جاهزة للآخرين لاستخدامها في برامجهم. تستخدم بعض اللغات مستودعًا مركزيًا لتثبيت هذه المكتبات، بينما توزعها لغة جو من نفس مستودع التحكم في الإصدار version control repository المستخدم لإنشاء المكتبات. تستخدم لغة جو أيضًا نظام إصدار يسمى <span ipsnoautolink="true">الإصدار الدلالي Semantic Versioning</span>، ليوضح للمستخدمين متى وما هو نوع التغييرات التي أُجريت. يساعد ذلك المستخدمين على معرفة ما إذا كان الإصدار الأحدث من الوحدة آمنًا للترقية، وما إذا كان يساعد في ضمان استمرار عمل برامجهم مع الوحدة.
</p>

<p>
	سننشئ في هذه المقالة وحدةً برمجيةً جديدة باستخدام لغة جو وسننشرها، وسنتعلم استخدام الإصدار الدلالي، وسننشر إصدارًا دلاليًا من الوحدة التي أنشأناها.
</p>

<h2>
	المتطلبات
</h2>

<ul>
	<li>
		أن يكون لديك مساحة عمل خاصة في لغة جو، وإذا لم يكن لديك اتبع سلسلة المقالات التالية:
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
	<li>
		أن تكون على دراية بكيفية إنشاء الوحدات في لغو جو. يمكنك مراجعة مقالة <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2086/" rel="">كيفية استخدام الوحدات Modules في لغة جو Go</a>.
	</li>
	<li>
		لديك معرفة مسبقة بنظام التحكم بالإصدار غيت Git. يمكنك مراجعة مقالة <a href="https://academy.hsoub.com/programming/workflow/git/%D9%85%D8%A7-%D9%87%D9%88-git%D8%9F-r1592/" rel="">ما هو جيت Git؟</a>.
	</li>
	<li>
		إنشاء مستودع فارغ باسم pubmodule للوحدة التي ستنشرها.
	</li>
</ul>

<h2>
	إنشاء وحدة للتحضير لنشرها
</h2>

<p>
	على عكس العديد من لغات البرمجة الأخرى، توزّع الوحدة المُنشأة باستخدام لغة جو مباشرةً من مستودع الشيفرة المصدر الذي يتضمنها بدلًا من مستودع حزم مستقل. يسهل هذا على المستخدمين العثور على الوحدات المشار إليها في التعليمات البرمجية الخاصة بهم وعلى المشرفين على الوحدة لنشر إصدارات جديدة من الوحدة الخاصة بهم. سننشئ في هذا القسم وحدةً جديدة، وسننشرها بعد ذلك لجعلها متاحةً للمستخدمين الآخرين. لنبدأ بإنشاء الوحدة الآن.
</p>

<p>
	سنستخدم بدايةً الأمر <code>git clone</code> لأخذ نسخة محلية من المستودع الفارغ الذي لا بُد أن نكون أنشأناه لأنه جزء من المتطلبات الأساسية. يمكن نسخ هذا المستودع إلى أي مكان نريده على جهاز الحاسب، لكن يميل العديد من المطورين إلى إنشاء مجلد خاص يتضمن جميع مشاريعهم. سنستخدم هنا مجلدًا باسم "projects".
</p>

<p>
	أنشئ مجلد "projects" وانتقل إليه:
</p>

<pre class="ipsCode">$ mkdir projects
$ cd projects
</pre>

<p>
	من مجلد "projects"، شغل الأمر <code>git clone</code> لنسخ المستودع إلى جهاز الحاسب:
</p>

<pre class="ipsCode">$ git clone git@github.com:your_github_username/pubmodule.git
</pre>

<p>
	ستكون نتيجة تنفيذ هذا الأمر هي نسخ الوحدة إلى مجلد "pubmodule" داخل مجلد "projects". قد تتلقى تحذيرًا بأنك نسخت مستودعًا فارغًا، ولكن لا داعٍ للقلق بشأن ذلك:
</p>

<pre class="ipsCode">Cloning into 'pubmodule'...
warning: You appear to have cloned an empty repository.
</pre>

<p>
	انتقل الآن إلى المجلد "pubmodule":
</p>

<pre class="ipsCode">$ cd pubmodule
</pre>

<p>
	سنستخدم الآن الأمر <code>go mod init</code> لإنشاء الوحدة الجديدة وتمرير موقع المستودع اسمًا للوحدة. يُعد التأكد من تطابق اسم الوحدة مع موقع المستودع أمرًا مهمًا، لأن هذه هي الطريقة التي تعثر بها أداة <code>go mod init</code> على مكان تنزيل الوحدة عند استخدامها في مشاريع أخرى:
</p>

<pre class="ipsCode">$ go mod init github.com/your_github_username/pubmodule
</pre>

<p>
	ستظهر رسالة تؤكد عملية إنشاء الوحدة من خلال إعلامنا بأن الملف "go.mod" قد أُنشئ:
</p>

<pre class="ipsCode">go: creating new go.mod: module github.com/your_github_username/pubmodule
</pre>

<p>
	سنستخدم الآن محرر نصوص مثل نانو nano، لإنشاء وفتح ملف يحمل نفس اسم المستودع "pubmodule.go".
</p>

<pre class="ipsCode">$ nano pubmodule.go
</pre>

<p>
	يمكن تسمية هذا الملف بأي اسم، ولكن يُفضّل استخدام نفس اسم الحزمة لتسهيل معرفة مكان البدء عند العمل مع حزمة غير مألوفة، كما ينبغي أن يكون اسم الحزمة هو نفس اسم المستودع. بالتالي، عندما يُشير شخص ما إلى تابع أو نوع من تلك الحزمة، فإنها تتطابق مع اسم المستودع، مثل <code>pubmodule.MyFunction</code>. سيسهل ذلك عليهم معرفة مصدر الحزمة في حال احتاجوا إلى الرجوع إليها لاحقًا.
</p>

<p>
	نُضيف الآن الدالة <code>Hello</code> إلى الحزمة، والتي ستعيد السلسلة <code>!Hello, You</code>. ستكون هذه الدالة متاحةً لأي شخص يستورد الحزمة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9532_7" style=""><span class="pln">package pubmodule

func </span><span class="typ">Hello</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Hello, You!"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لقد أنشأنا الآن وحدةً جديدةً باستخدام <code>go mod init</code> مع اسم وحدة يتطابق مع اسم المستودع البعيد (github.com/your<em>github</em>username/pubmodule)، وأضفنا أيضًا ملفًا باسم "pubmodule.go" إلى الوحدة، مع دالة تُسمى <code>Hello</code> يمكن استدعاؤها من قِبل مستخدمي هذه الوحدة. سننشر في الخطوة التالية هذه الوحدة بهدف إتاحتها للآخرين.
</p>

<h2>
	نشر الوحدة
</h2>

<p>
	لقد حان الوقت لنشر الوحدة التي أنشأناها في الخطوة السابقة. نظرًا لأن وحدات لغة جو تُوزّع من نفس مستودعات الشيفرة التي تُخزّن فيها، فسوف نُودع commit الشيفرة في مستودع غيت المحلي وندفعه push إلى المستودع الخاص بنا على <code>github.com/your_github_username/pubmodule</code>، ولكن قبل ذلك من الجيد التأكد من عدم إيداع ملفات لا نريد إيداعها عن طريق الخطأ أو عدم الانتباه، لأنها ستُنشر علنًا عند دفع الشيفرة إلى غيت هب. يمكن إظهار جميع الملفات داخل مجلد "pubmodule" والتغييرات التي ستُنفّذ باستخدام الأمر التالي:
</p>

<pre class="ipsCode">$ git status
</pre>

<p>
	سيبدو الخرج كما يلي:
</p>

<pre class="ipsCode">On branch main

No commits yet

Untracked files:
  (use "git add &lt;file&gt;..." to include in what will be committed)
    go.mod
    pubmodule.go
</pre>

<p>
	يجب أن نرى ملف "go.mod" الذي أُنشئ بواسطة الأمر <code>go mod init</code>، وملف "pubmodule.go" الذي أنشأنا فيه دالة <code>Hello</code>. قد يكون اسم الفرع مختلفًا عن الخرج السابق اعتمادًا على كيفية إنشاء المستودع، وستكون الأسماء غالبًا إما <code>main</code> أو <code>master</code>.
</p>

<p>
	عندما نتأكد من أن الملفات التي نريدها فقط موجودة، يمكننا إدراج stage الملفات باستخدام <code>git add</code> وإيداعها بالمستودع باستخدام الأمر <code>git commit</code>:
</p>

<pre class="ipsCode">$ git add .
$ git commit -m "Initial Commit"
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">[main (root-commit) 931071d] Initial Commit
 2 files changed, 8 insertions(+)
 create mode 100644 go.mod
 create mode 100644 pubmodule.go
</pre>

<p>
	نستخدم الأمر <code>git push</code> لدفع الوحدة إلى مستودع غيت هب:
</p>

<pre class="ipsCode">$ git push
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 367 bytes | 367.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:your_github_username/pubmodule.git
 * [new branch]      main -&gt; main
</pre>

<p>
	ستُدفع الوحدة إلى المستودع بعد تشغيل الأمر <code>git push</code>، وستكون متاحةً الآن لأي شخص آخر لاستخدامها. ستستخدم جو الشيفرة الموجودة في الفرع الافتراضي للمستودع على أنها شيفرة للوحدة إذا لم يكن لديك أيّة إصدارات منشورة. لا يهم ما إذا كان اسم الفرع الافتراضي هو <code>main</code> أو <code>master</code> أو أي شيء آخر، المهم هو الفرع الذي ضُبط على أنه فرع افتراضي.
</p>

<p>
	تعلّمنا في هذا القسم كيفية نشر وحدة في لغة جو على مستودع غيت هب من المستودع المحلي لإتاحتها للآخرين. واحدة من الأمور الأساسية التي يجب أخذها بالحسبان، هي التأكد من أن مستخدمي الوحدة يمكنهم استخدام إصدار مستقر منها.
</p>

<p>
	من المحتمل أن نرغب في إجراء تغييرات وإضافة ميزات إلى الوحدة من الآن فصاعدًا، ولكن إذا أجرينا هذه التغييرات دون استخدام مفهوم الإصدارات، فقد تتعطل شيفرة شخص ما يستخدم هذه الوحدة عن طريق الخطأ. لحل هذه المشكلة: يمكننا إضافة إصدارات إلى الوحدة عندما نصل إلى مرحلة جديدة في عملية التطوير، ومع ذلك، عند إضافة إصدارات جديدة يجب التأكد من اختيار رقم إصدار مفيد حتى يعرف المستخدمون ما إذا كان من الآمن لهم الترقية فورًا أم لا.
</p>

<h2>
	الإصدار الدلالي
</h2>

<p>
	يعطي رقم الإصدار التعبيري meaningful version number للمستخدمين فكرةً عن مدى تغير الواجهة العامة أو واجهة برمجة التطبيقات التي يتفاعلون معها. تنقل جو هذه التغييرات من خلال نظام أو مخطط إصدار versioning scheme يُعرف باسم <a href="https://semver.org/lang/ar/" rel="external nofollow">الإصدار الدلالي semantic versioning</a> -أو اختصارًا SemVer. يستخدم الإصدار الدلالي سلسلة الإصدار لنقل المعنى حول تغييرات الشيفرة، ومن هنا يأتي اسم الإصدار الدلالي. يُستخدم الإصدار الدلالي لتحديد الإصدارات الأحدث من الإصدار الحالي المُستخدم، وما إذا كان بالإمكان الترقية للإصدار الأحدث آليًا بأمان.
</p>

<p>
	يعطي الإصدار الدلالي كل رقم في سلسلة الإصدار معنى meaning. يحتوي الإصدار النموذجي في SemVer على ثلاثة أرقام أساسية: الإصدار الرئيسي major والإصدار الثانوي minor وإصدار التصحيح patch version. تُدمج كل من هذه الأرقام مع بعضها بواسطة النقطة (.) لتشكيل سلسلة الإصدار، مثلًا <code>1.2.3</code>، تُرتّب فيه الأرقام وفقًا للآلية التالية: أول رقم للإصدار الرئيسي ثم الإصدار الثانوي ثم إصدار التصحيح أخيرًا. يمكنك بهذه الطريقة معرفة أيهما أحدث لأن الرقم الموجود في مكان معين أعلى من الإصدارات السابقة. على سبيل المثال، الإصدار <code>2.2.3</code> أحدث من <code>1.2.3</code> لأن الإصدار الرئيسي أعلى، وبالمثل، فإن الإصدار <code>1.4.3</code> أحدث من <code>1.2.10</code> لأن الإصدار الثانوي أعلى، وعلى الرغم من أن <code>10</code> أعلى من <code>3</code> في إصدار التصحيح، فإن الإصدار الثانوي <code>4</code> أعلى من <code>2</code>، لذا فإن هذا الإصدار له الأسبقية.
</p>

<p>
	عندما يزداد رقم في سلسلة الإصدار، يُعاد ضبط جميع الأجزاء الأخرى من الإصدار التي تليها إلى 0. على سبيل المثال، تؤدي زيادة الإصدار الثانوي من <code>1.3.10</code> إلى <code>1.4.0</code> وزيادة الإصدار الرئيسي من <code>2.4.1</code> إلى <code>3.0.0</code>.
</p>

<p>
	يسمح استخدام هذه القواعد للغة جو بتحديد إصدار الوحدة التي يجب استخدامها عند تشغيل <code>go get</code>. مثلًا، لنفرض أن لدينا مشروعًا يستخدم الإصدار <code>1.4.3</code> من الوحدة <code>github.com/your_github_username/pubmodule</code>. إذا كنا نعتمد على كون الوحدة <code>pubmodule</code> مستقرة، فقد نرغب فقط في ترقية إصدار التصحيح تلقائيًا <code>3.</code>.
</p>

<p>
	إذا شغّلت الأمر:
</p>

<pre class="ipsCode">go get -u=patch github.com/your_github_username/pubmodule
</pre>

<p>
	ستستنتج لغو جو أننا تريد ترقية إصدار التصحيح للوحدة، وستبحث فقط عن الإصدارات الجديدة مع <code>1.4</code> للجزء الرئيسي والثانوي من الإصدار.
</p>

<p>
	من المهم أن نضع في الحسبان كيف تغيرت واجهة برمجة التطبيقات العامة للوحدة عند إنشاء إصدار جديد منها، إذ ينقل لنا كل جزء من سلسلة الإصدار الدلالية نطاق التغيير في واجهة برمجة التطبيقات وكذلك للمستخدمين. تنقسم هذه الأنواع من التغييرات عادةً إلى ثلاث فئات مختلفة، تتماشى مع كل مكون من مكونات الإصدار؛ إذ تؤدي التغيرات الأصغر إلى زيادة إصدار التصحيح، وتزيد التغييرات متوسطة الحجم من الإصدار الثانوي، وتزيد التغييرات الأكبر في الإصدار الرئيسي. سيساعدنا استخدام هذه الفئات في تحديد رقم الإصدار المراد زيادته وتجنب تعطل break الشيفرة الخاصة بنا أو شيفرة أي شخص آخر يعتمد على هذه الوحدة.
</p>

<h2>
	أرقام الإصدارات الرئيسية
</h2>

<p>
	الرقم الأول في SemVer هو رقم الإصدار الرئيسي (<code>1.4.3</code>)، وهو أهم رقم يجب مراعاته عند إطلاق إصدار جديد من الوحدة. في هذا النوع من الإصدارات يكون هناك تغيّر كبير في الإصدار يُشير إلى تغيرات غير متوافقة backward-incompatible changes مع الإصدارات السابقة لواجهة برمجة التطبيقات العامة المستخدمة <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D9%84%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-api%D8%9F-r1512/" rel=""><abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a>؛ وقد يكون التغير غير المتوافق مع الإصدارات السابقة هو أي تغير يطرأ على الوحدة من شأنه أن يتسبب في تعطل breaking برنامج شخص ما إذا أجرى الترقية دون إجراء أي تغييرات أخرى. يمكن أن يمثّل التعطّل أي حالة فشل في البناء بسبب تغيير اسم دالة أو تغيير في كيفية عمل المكتبة، بحيث أن تابعًا أصبح يُعيد "v1" بدلًا من "1". هذا فقط من أجل واجهة برمجة التطبيقات العامة الخاصة بنا، ومع ذلك يمكن لشخص آخر استخدام أي أنواع أو توابع قد صُدّرت.
</p>

<p>
	إذا كان الإصدار يتضمن فقط تحسينات لن يلاحظها مستخدم المكتبة، فلا يحتاج إلى تغيير كبير في الإصدار. قد تكون إحدى الطرق لتذكر التغييرات التي تناسب هذه الفئة هي أخذ كل "تحديث" أو "حذف" بمثابة زيادة كبيرة في الإصدار.
</p>

<p>
	<strong>ملاحظة:</strong> على عكس الأنواع الأخرى من الأرقام في SemVer، فإن الإصدار الرئيسي 0 له أهمية خاصة إضافية، إذ يُعد إصدارًا "قيد التطوير in development". لا يُعد أي SemVer بإصدار رئيسي 0 مستقرًا، وأي شيء يمكن أن يتغير في واجهة برمجة التطبيقات في أي وقت. يُفضّل أن نبدأ دومًا بالإصدار الرئيسي 0 عند إنشاء وحدة جديدة، وتحديث الإصدارات الثانوية والإصدارات التصحيحية فقط حتى ننتهي من التطوير الأولي للوحدة. بعد الانتهاء من تغيير واجهة برمجة التطبيقات العامة للوحدة وعدّها مستقرةً للمستخدمين، حان الوقت لبدء الإصدار <code>1.0.0</code>.
</p>

<p>
	لنأخذ الشيفرة التالية مثالًا على الشكل الذي قد يبدو عليه تغيير الإصدار الرئيسي. لدينا دالة تسمى <code>UserAddress</code> تقبل حاليًا <code>string</code> معاملًا وتعيد <code>string</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9532_9" style=""><span class="pln">func </span><span class="typ">UserAddress</span><span class="pun">(</span><span class="pln">username string</span><span class="pun">)</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// تُعيد عنوان المستخدم مثل سلسلة</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُعيد الدالة حاليًا سلسلة، وقد يكون من الأفضل لنا وللمستخدمين إذا أعادت الدالة السابقة بنيةً <code><a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%A8%D9%86%D9%89-structs-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1976/" rel="">struct</a></code> مثل <code>Address*</code>، إذ يمكن بهذه الطريقة تضمين بيانات إضافية (مثل الرمز البريدي) وبطريقة منظمة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9532_11" style=""><span class="pln">type </span><span class="typ">Address</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Address</span><span class="pln">    string
    </span><span class="typ">PostalCode</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">UserAddress</span><span class="pun">(</span><span class="pln">username string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="typ">Address</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// تُعيد عنوان المستخدم والرمز البريدي مثل بنية</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	قد يكون هذا مثالًا على تغيير رئيسي في الإصدار لأنه سيتطلب من المستخدمين إجراء تغييرات على التعليمات البرمجية الخاصة بهم من أجل استخدامها. سيكون الأمر نفسه صحيحًا إذا قررنا حذف الدالة <code>UserAddress</code>، لأن المستخدمين سيحتاجون إلى تحديث التعليمات البرمجية الخاصة بهم لاستخدام البديل.
</p>

<p>
	مثال آخر على تغيير الإصدار الرئيسي هو إضافة معامل جديد إلى دالة <code>UserAddress</code> على الرغم من أنها ما زالت تُعيد <code>string</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9532_14" style=""><span class="pln">func </span><span class="typ">UserAddress</span><span class="pun">(</span><span class="pln">username string</span><span class="pun">,</span><span class="pln"> uppercase </span><span class="kwd">bool</span><span class="pun">)</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// true تُعيد عنوان المستخدم مثل سلسلة بأحرف كبيرة إذا كان المعامل المنطقي قيمته</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نظرًا لأن هذا التغيير يتطلب أيضًا من المستخدمين تحديث التعليمات البرمجية الخاصة بهم إذا كانوا يستخدمون دالة <code>UserAddress</code>، فسيتطلب ذلك أيضًا زيادة كبيرة في الإصدار.
</p>

<p>
	لن تكون كل التغييرات التي نجريها على الشيفرة جذرية، إذ سنُجري أحيانًا تغييرات على واجهة برمجة التطبيقات العامة <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>، بحيث نُضيف دوالًا أو قيمًا جديدة، ولكن هذا لا يغير أيًا من الدوال أو القيم الحالية.
</p>

<h2>
	أرقام الإصدارات الثانوية
</h2>

<p>
	الرقم الثاني في إصدار SemVer هو رقم الإصدار الثانوي (<code>1.4.3</code>)، وهنا يطرأ تغيير بسيط في الإصدار للإشارة إلى التغييرات المتوافقة مع الإصدارات السابقة لواجهة برمجة التطبيقات العامة الخاصة بنا. سيكون التغيير المتوافق مع الإصدارات السابقة أي تغيير لا يؤثر على التعليمات البرمجية أو المشاريع التي تستخدم الوحدة الحالية. على غرار رقم الإصدار الرئيسي؛ يؤثر هذا فقط على واجهة برمجة التطبيقات العامة. قد تكون إحدى الطرق لتذكر التغييرات التي تناسب هذه الفئة، هي أي شيء يعد "إضافة"، ولكن ليس "تحديثًا".
</p>

<p>
	باستخدام نفس المثال السابق الذي شرحنا فيه رقم الإصدار الرئيسي، تخيل أن لديك تابع باسم <code>UserAddress</code> يُعيد سلسلة <code>string</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9532_16" style=""><span class="pln">func </span><span class="typ">UserAddress</span><span class="pun">(</span><span class="pln">username string</span><span class="pun">)</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="com">//تُعيد عنوان المستخدم مثل سلسلة</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	هنا بدلًا من تحديث <code>UserAddress</code> بجعله يُعيد <code>Address*</code>، سنضيف تابع جديد تمامًا يسمى <code>UserAddressDetail</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9532_18" style=""><span class="pln">type </span><span class="typ">Address</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Address</span><span class="pln">    string
    </span><span class="typ">PostalCode</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">UserAddress</span><span class="pun">(</span><span class="pln">username string</span><span class="pun">)</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
   </span><span class="com">// تُعيد عنوان المستخدم مثل سلسلة</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">UserAddressDetail</span><span class="pun">(</span><span class="pln">username string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="typ">Address</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
   </span><span class="com">// تُعيد عنوان المستخدم والرمز البريدي مثل بنية</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لا تتطلب إضافة الدالة الجديدة <code>UserAddressDetail</code> إجراء تغييرات من قِبل المستخدمين إذا حدّثوا إلى هذا الإصدار من الوحدة، لذلك ستُعد زيادة بسيطة في رقم الإصدار. يمكن للمستخدمين الاستمرار في استخدام <code>UserAddress</code> وسيحتاجون فقط إلى تحديث التعليمات البرمجية الخاصة بهم إذا كانوا يفضلون تضمين المعلومات الإضافية من <code>UserAddressDetail</code>.
</p>

<p>
	من المحتمل ألا تكون تغييرات واجهة برمجة التطبيقات العامة هي المرة الوحيدة التي نُطلق فيها إصدارًا جديدًا من الوحدة. تُعد الأخطاء bugs جزءًا لا مفر منه من تطوير البرامج، ورقم إصدار التصحيح موجود للتستر على تلك الثغرات.
</p>

<h2>
	أرقام إصدارات التصحيح
</h2>

<p>
	الرقم الأخير في صيغة SemVer هي إصدار التصحيح، (<code>1.4.3</code>). تغيير إصدار التصحيح هو أي تغيير <strong>لا يؤثر</strong> على واجهة برمجة التطبيقات العامة <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> للوحدة. تكون غالبًا التغييرات التي لا تؤثر على واجهة برمجة التطبيقات العامة للوحدة، أشياءً مثل إصلاحات الأخطاء أو إصلاحات الأمان.
</p>

<p>
	بالعودة إلى الدالة <code>UserAddress</code> من الأمثلة السابقة، لنفترض أن إصدارًا من الوحدة يفتقد إلى جزء من العنوان في السلسلة التي تُعيدها الدالة. إذا أطلقنا إصدارًا جديدًا من الوحدة لإصلاح هذا الخطأ، سيؤدي ذلك إلى زيادة إصدار التصحيح فقط، ولن يتضمن الإصدار أيّة تغييرات في كيفية استخدام المستخدم لواجهة برمجة التطبيقات العامة <code>UserAddress</code>، وإنما مُجرد تعديلات لضمان صحة البيانات المُعادة.
</p>

<p>
	يُعد اختيار رقم إصدار جديد بعناية طريقة مهمة لكسب ثقة المستخدمين، إذ يُظهر استخدام الإصدار الدلالي للمستخدمين مقدار العمل المطلوب للتحديث إلى إصدار جديد، وبالتأكيد لن تفاجئهم عن طريق الخطأ بتحديث يكسر برنامجهم. بعد التفكير في التغييرات التي أجريناها على الوحدة، وتحديد رقم الإصدار التالي المطلوب استخدامه، يمكننا الآن نشر الإصدار الجديد وإتاحته للمستخدمين.
</p>

<h2>
	نشر إصدار جديد من الوحدة
</h2>

<p>
	سنحتاج إلى تحديث الوحدة بالتغييرات التي نخطط لإجرائها قبل نشر أي إصدار جديد من الوحدة. لن نكون قادرين على تحديد أي جزء من الإصدار الدلالي يجب أن يزداد دون أن نُجري تغييرات. بالنسبة للوحدة التي أنشأناها، سنضيف التابع <code>Goodbye</code> لاستكمال التابع <code>Hello</code>، وبعد ذلك ستنشر هذا الإصدار الجديد للاستخدام.
</p>

<p>
	افتح ملف "pubmodule.go" وضِف التابع الجديد <code>Goodbye</code> إلى واجهة برمجة التطبيقات العامة <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9532_20" style=""><span class="pln">package pubmodule

func </span><span class="typ">Hello</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Hello, You!"</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">Goodbye</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Goodbye for now!"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سنحتاج الآن إلى التحقق من التغييرات التي يُتوقع أن تُنفّذ عن طريق تنفيذ الأمر التالي:
</p>

<pre class="ipsCode">$ git status
</pre>

<p>
	يوضّح الخرج أن التغيير الوحيد في الوحدة الخاصة بنا، هو التابع الذي أضفناه إلى ملف "pubmodule.go":
</p>

<pre class="ipsCode">On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add &lt;file&gt;..." to update what will be committed)
  (use "git restore &lt;file&gt;..." to discard changes in working directory)
    modified:   pubmodule.go

no changes added to commit (use "git add" and/or "git commit -a")
</pre>

<p>
	نضيف بعد ذلك التغيير إلى الملفات المُدرجة ونجري التغيير في المستودع المحلي باستخدام <code>git add</code> و <code>git commit</code>:
</p>

<pre class="ipsCode">$ git add .
$ git commit -m "Add Goodbye method"
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">[main 3235010] Add Goodbye method
 1 file changed, 4 insertions(+)
</pre>

<p>
	سنحتاج إلى دفع التغييرات بعد تنفيذها إلى مستودع غيت هب الخاص بنا، وتكون عادةً هذه الخطوة مختلفة قليلًا عند العمل على مشروع برمجي أكبر أو عند العمل مع مطورين آخرين في مشروع. عند إجراء تطوير على ميزة جديدة، يُنشئ المطور فرع غيت جديد لإجراء تلك التغييرات وتحضيرها إلى أن تصبح هذه الميزة مستقرة وجاهزة للإصدار، وبعدها سيأتي مطور آخر ويُراجع هذه التغييرات ضمن ذلك الفرع للتأكد أو لاكتشاف المشكلات التي ربما غفل عنها المطور الأول. يُدمج الفرع الخاص بتلك الميزة في الفرع الافتراضي، مثل <code>master</code> أو <code>main</code> بمجرد الانتهاء من المراجعة، وتُجمع هذه التغييرات في الفرع الافتراضي حتى يحين وقت نشر إصدار جديد.
</p>

<p>
	هنا لا تمر الوحدة بهذه العملية (إضافة ميزة جديدة في فرع آخر)، لذا سيكون دفع التغييرات التي أجريناها على المستودع، هو فقط التغييرات التي أجريناها:
</p>

<pre class="ipsCode">$ git push
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">numerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 369 bytes | 369.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:your_github_username/pubmodule.git
   931071d..3235010  main -&gt; main
</pre>

<p>
	يوضح الخرج أن الشيفرة الجديدة جاهزة للاستخدام في الفرع الافتراضي من قبل المستخدمين.
</p>

<p>
	كل ما فعلناه حتى الآن يُطابق ما فعلناه عند نشر الوحدة في البداية. الآن، تأتي نقطة مهمة في عملية إطلاق الإصدارات، وهي اختيار رقم الإصدار.
</p>

<p>
	إذا نظرنا إلى التغييرات التي أجريناها على الوحدة، فإن التغيّر الوحيد على واجهة برمجة التطبيقات العامة (أو أي تغيير) هو إضافة التابع <code>Goodbye</code> إلى الوحدة. بما أنه يمكن للمستخدم التحديث من الإصدار السابق الذي كان يحتوي فقط على الدالة <code>Hello</code>، دون إجراء تغييرات من جانبهم، سيكون هذا التغيير متوافقًا مع الإصدارات السابقة. قد يعني ذلك (ضمن مفهوم الإصدار الدلالي) التغيير المتوافق مع الإصدارات السابقة لواجهة برمجة التطبيقات العامة زيادةً في رقم الإصدار الثانوي. هذا الإصدار هو الإصدار الأول من الوحدة التي نشرناها، لذلك ليس هناك إصدار سابق لنزيد عليه. عمومًا، إذا كان الإصدار الحالي هو <code>0.0.0</code> أي "لا يوجد إصدار"، ستقودنا زيادة الإصدار الثانوي إلى الإصدار <code>0.1.0</code>، وهو الإصدار التالي من الوحدة التي أنشأناها.
</p>

<p>
	الآن، بعد أن أصبح لدينا رقم إصدار نُعطيه للوحدة، أصبح بإمكاننا استخدامه مع وسوم غيت لنشر إصدار جديد. عندما يستخدم المطورون غيت لتتبع شيفرة المصدر الخاصة بهم (حتى في لغات أخرى غير جو)، فإن العرف الشائع هو استخدام وسوم غيت لتتبع الشيفرة التي أُطلقت لإصدار معين؛ فبهذه الطريقة يمكنهم استخدام الوسم إذا احتاجوا في أي وقت إلى إجراء تغييرات على إصدار قديم. بما أن لغة جو تُنزّل الوحدات من المستودعات المصدرية، بالتالي تستطيع الاستفادة من هذه الخاصية في استخدام وسوم الإصدار نفسها.
</p>

<p>
	لنشر نسخة جديدة من الوحدة التي أنشأناها باستخدام هذه الوسوم، سنضع وسمًا على الشيفرة التي نطلقها باستخدام الأمر <code>git tag</code>، وستحتاج أيضًا إلى تقديم وسم الإصدار مثل وسيط لهذا الأمر.
</p>

<p>
	لإنشاء وسم الإصدار، نبدأ بالبادئة <code>v</code> ونضيف SemVer بعدها مباشرةً. في حالتنا، سيكون وسم الإصدار النهائي هو <code>v0.1.0</code>.
</p>

<p>
	شغل الأمر <code>git tag</code> لتمييز الوحدة التي أنشأناها بوسم الإصدار هذا:
</p>

<pre class="ipsCode">$ git tag v0.1.0
</pre>

<p>
	سنحتاج -بعد إضافة وسم الإصدار محليًا- إلى دفع هذا الوسم إلى مستودع غيت هب باستخدام <code>git push</code> مع <code>origin</code>:
</p>

<pre class="ipsCode">$ git push origin v0.1.0
</pre>

<p>
	بعد نجاح تنفيذ الأمر <code>git push</code>، سترى أن الوسم <code>v0.1.0</code> قد أُنشئ:
</p>

<pre class="ipsCode">Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:your_github_username/pubmodule.git
 * [new tag]         v0.1.0 -&gt; v0.1.0
</pre>

<p>
	يوضح الخرج أعلاه أن الوسم السابق قد أَُضيف، وأن مستودع غيت هب الخاص بنا يحتوي على الوسم الجديد <code>v0.1.0</code>، وسيكون متاحًا لمستخدمي الوحدة الوصول إليه.
</p>

<p>
	بعد نشر إصدار جديد من الوحدة واستخدام الأمر <code>git tag</code>، لن يكون المستخدم بحاجة إلى تنزيل إصدار جديد بناءً على أحدث إيداع تعمية hash من الفرع الافتراضي، وذلك عندما يرغب بالحصول على أحدث إصدار من الوحدة باستخدام الأمر <code>go get</code>.
</p>

<p>
	بعد إطلاق إصدار الوحدة، ستبدأ أداة <code>go</code> في استخدام هذه الإصدارات لتحديد أفضل طريقة لتحديث الوحدة، ويتيح لنا ذلك إلى جانب الإصدار الدلالي، إمكانية تكرار الوحدات وتحسينها مع تزويد المستخدمين بتجربة متسقة ومستقرة.
</p>

<h2>
	الخاتمة
</h2>

<p>
	أنشأنا خلال هذه المقالة وحدةً برمجيةً عامة باستخدام لغة جو ونشرناها في مستودع غيت هب حتى يتمكن الآخرون من استخدامها، واستخدمنا أيضًا مفهوم الإصدار الدلالي لتحديد رقم إصدار مناسب للوحدة. وسّعنا أيضًا دوال هذه الوحدة، ونشرنا الإصدار الجديد اعتمادًا على مفهوم الإصدار الدلالي مع ضمان عدم تعطّل البرامج التي تعتمد عليها.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-distribute-go-modules" rel="external nofollow">How to Distribute Go Modules</a> لصاحبه Kristin Davidson.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2086/" rel="">كيفية استخدام الوحدات Modules في لغة جو Go</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A3%D9%88%D9%84-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC-%D9%88%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%84%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r241/" rel="">كتابة أول برنامج (ومكتبة) لك باستخدام لغة البرمجة Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r534/" rel="">الدليل السريع إلى لغة البرمجة Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D9%88%D8%A8%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-%D9%88%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87%D8%A7-r1996/" rel="">بناء البرامج المكتوبة بلغة جو Go وتثبيتها</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2087</guid><pubDate>Sun, 20 Aug 2023 16:00:00 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x648;&#x62D;&#x62F;&#x627;&#x62A; Modules &#x641;&#x64A; &#x644;&#x63A;&#x629; Go</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-go-r2086/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_08/---Modules----Go.png.701c681ced48170d88ad015158402892.png" /></p>
<p>
	أضاف مؤلفو لغة جو في النسخة 1.13 طريقةً جديدةً لإدارة المكتبات التي يعتمد عليها مشروع مكتوب باستخدام هذه اللغة، تسمى وحدات Go، وقد أتت استجابةً إلى حاجة المطورين إلى تسهيل عملية الحفاظ على الإصدارات المختلفة من الاعتماديات dependencies وإضافة المزيد من المرونة إلى الطريقة التي ينظم بها المطورون مشاريعهم على أجهزتهم.
</p>

<p>
	تتكون الوحدات عادةً في لغة جو من مشروع أو مكتبة واحدة إلى جانب مجموعة من حزم لغة جو التي تُطلق released معًا بعد ذلك. تعمل الوحدات في هذه اللغة على حل العديد من المشكلات بالاستعانة بمتغير البيئة <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-gopath-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1789/" rel=""><code>GOPATH</code></a> الخاص بنظام التشغيل، من خلال السماح للمستخدمين بوضع شيفرة مشروعهم في المجلد الذي يختارونه وتحديد إصدارات الاعتماديات لكل وحدة.
</p>

<p>
	سننشئ في هذه المقالة وحدة جو عامة public خاصة بنا وسنضيف إليها حزمة، وسنتعلم أيضًا كيفية إضافة وحدة عامة أنشأها آخرون إلى مشروعنا، وإضافة إصدار محدد من هذه الوحدة إلى المشروع.
</p>

<h2>
	المتطلبات
</h2>

<ul>
	<li>
		أن يكون لديك مساحة عمل خاصة في لغة جو، وإذا لم يكن لديك اتبع سلسلة المقالات التالية:
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك أو اس macOS</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
	<li>
		معرفة بكيفية <a href="https://academy.hsoub.com/programming/go/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1903/" rel="">إنشاء الحزم في لغة جو Go</a>.
	</li>
</ul>

<h2>
	إنشاء وحدة جديدة
</h2>

<p>
	قد تبدو الوحدة module مشابهة للحزمة package للوهلة الأولى في لغة جو، لكن تحتوي الوحدة على عدد من الشيفرات التي تُحقق وظيفة الحزمة، إضافةً إلى ملفين مهمين في المجلد الجذر root هما "go.mod" و "go.sum". تحتوي هذه الملفات على معلومات تستخدمها أداة <code>go</code> لتتبع إعدادات ضبط Configuration الوحدة الخاصة بك، وتجري عادةً صيانتها بواسطة الأداة نفسها، لذا لن تكون مضطرًا لفعل ذلك بنفسك.
</p>

<p>
	أول شيء عليك التفكير فيه هو المكان الذي ستضع فيه الوحدة، فمفهوم الوحدات يجعل بالإمكان وضع المشاريع في أي مكان من نظام الملفات، وليس فقط ضمن مجلد حُدد مسبقًا في جو.
</p>

<p>
	ربما يكون لديك مجلد خاص لإنشاء المشاريع وترغب في استخدامه، لكن هنا سننشئ مجلدًا باسم projects، ونسمي الوحدة الجديدة <code>mymodule</code>. يمكنك إنشاء المجلد projects إما من خلال <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D9%83%D8%A7%D9%85%D9%84%D8%A9-ide-r1513/" rel="">بيئة تطوير متكاملة IDE</a> أو من خلال <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر الأوامر</a>. في حال استخدامك لسطر الأوامر، ابدأ بإنشاء المجلد وانتقل إليه:
</p>

<pre class="ipsCode">$ mkdir projects
$ cd projects
</pre>

<p>
	سننشئ بعد ذلك مجلد الوحدة، إذ يكون عادةً اسم المجلد المتواجد في المستوى الأعلى من الوحدة هو نفس اسم الوحدة لتسهيل تتبع الأشياء.
</p>

<p>
	أنشئ ضمن مجلد projects مجلدًا باسم "mymodule":
</p>

<pre class="ipsCode">$ mkdir mymodule
</pre>

<p>
	بعد إنشاء مجلد الوحدة، ستصبح لدينا البنية التالية:
</p>

<pre class="ipsCode">└── projects
    └── mymodule
</pre>

<p>
	الخطوة التالية هي إنشاء ملف "go.mod" داخل مجلد "mymodule" كي نُعّرف الوحدة، وسنستخدم لأجل ذلك الأمر <code>go mod init</code> ونحدد له اسم الوحدة <code>mymodule</code> كما يلي:
</p>

<pre class="ipsCode">$ go mod init mymodule
</pre>

<p>
	يعطي هذا الأمر الخرج التالي عند إنشاء الوحدة:
</p>

<pre class="ipsCode">go: creating new go.mod: module mymodule
</pre>

<p>
	سيصبح لديك الآن بنية المجلد التالية:
</p>

<pre class="ipsCode">└── projects
    └── mymodule
        └── go.mod
</pre>

<p>
	دعنا نلقي نظرةً على ملف "go.mod" لمعرفة ما فعله الأمر <code>go mod init</code>.
</p>

<h2>
	الملف go.mod
</h2>

<p>
	يلعب الملف "go.mod" جزءًا مهمًا جدًا عند تشغيل الأوامر باستخدام الأداة <code>go</code>، إذ يحتوي على اسم الوحدة وإصدارات الوحدات الأخرى التي تعتمد عليها الوحدة الخاصة بك، ويمكن أن يحتوي أيضًا على موجّهات directives أخرى مثل <code>replace</code>، التي يمكن أن تكون مفيدة لإجراء عمليات تطوير على وحدات متعددة في وقت واحد.
</p>

<p>
	افتح الملف go.mod الموجود ضمن المجلد mymodule باستخدام محرر نانو nano أو محرر النصوص المفضل لديك:
</p>

<pre class="ipsCode">$ nano go.mod
</pre>

<p>
	ستكون محتوياته هكذا (قليلة أليس كذلك؟):
</p>

<pre class="ipsCode">module mymodule

go 1.16
</pre>

<p>
	في السطر الأول لدينا موجّه يُدعى <code>module</code>، مهمته إخبار مُصرّف اللغة عن اسم الوحدة الخاصة بك، بحيث عندما يبحث في مسارات الاستيراد <code>import</code> ضمن حزمة فإنه يعرف أين عليه أن يبحث عن <code>mymodule</code>. تأتي القيمة <code>mymodule</code> من المعامل الذي مررته إلى <code>go mod init</code>:
</p>

<pre class="ipsCode">module mymodule
</pre>

<p>
	لدينا في السطر الثاني والأخير من الملف موجّهًا آخرًا هو <code>go</code>، مهمته إخبار المصرّف عن إصدار اللغة التي تستهدفها الوحدة. بما أننا ننشئ الوحدة باستخدام النسخة 1.16 من لغة جو، فإننا نكتب:
</p>

<pre class="ipsCode">go 1.16
</pre>

<p>
	سيتوسع هذا الملف مع إضافة المزيد من المعلومات إلى الوحدة، ولكن من الجيد إلقاء نظرة عليه الآن لمعرفة كيف يتغير عند إضافة اعتماديات في أوقات لاحقة.
</p>

<p>
	لقد أنشأت الآن وحدة جو باستخدام <code>go mod init</code> واطلعت على ما يحتويه ملف "go.mod"، لكن ليس لهذه الوحدة أي وظيفة تفعلها حتى الآن.
</p>

<p>
	سنبدأ في الخطوة التالية بتطوير هذه الوحدة وإضافة بعض الوظائف إليها.
</p>

<h1>
	كيفية إضافة شيفرات برمجية إلى الوحدة
</h1>

<p>
	ستنشئ ملف "main.go" داخل المجلد "mymodule" لنضع فيه شيفرة برمجية ونشغلها ونختبر صحة عمل الوحدة. يُعد استخدام ملف "main.go" في برامج هذه اللغة للإشارة إلى نقطة بداية البرنامج أمرًا شائعًا.
</p>

<p>
	الأهم من هذا الملف هو الدالة <code>main</code> التي سنكتبها بداخله، أما اسم الملف بحد ذاته فهو اختياري، لكن من الأفضل تسميته بهذا الاسم لجعل عملية العثور عليه أسهل. سنجعل الدالة <code>main</code> تطبع رسالةً ترحيبيةً عند تشغيل البرنامج.
</p>

<p>
	نُنشئ الآن ملفًا باسم "main.go" باستخدام محرّر نانو أو أي محرر نصوص مفضل لديك:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	ضع التعليمات البرمجية التالية في ملف "main.go"، إذ تصرّح هذه التعليمات أولًا عن الحزمة، ثم تستورد الحزمة <code>fmt</code>، وتطبع رسالةً ترحيبية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5345_6" style=""><span class="pln">package main
</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">
func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Hello, Modules!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُمثّل الحزمة في لغة جو بالمجلد الذي تُنشأ فيه، ولكل ملف داخل مجلد الحزمة سطر يُصرّح فيه عن الحزمة التي ينتمي إليها هذا الملف. أعطينا الحزمة الاسم <code>main</code> في ملف "main.go" الذي أنشأته للتو، ويمكنك تسمية الحزمة بالطريقة التي تريدها، ولكن حزمة <code>main</code> خاصة في لغة جو، فعندما يرى مُصرّف اللغة أن الحزمة تحمل اسم <code>main</code>، فإنه يعلم أن الحزمة ثنائية binary، ويجب تصريفها إلى ملف تنفيذي، وليست مجرد مكتبة مصممة لاستخدامها في برنامج آخر.
</p>

<p>
	بعد تحديد الحزمة التي يتبع لها الملف، يجب أن نستورد الحزمة <code>fmt</code> لكي نستطيع استخدام الدالة <code>Println</code> منها، وذلك لطباعة الرسالة الترحيبية <code>!Hello, Modules</code> على شاشة الخرج.
</p>

<p>
	أخيرًا، نُعرّف الدالة <code>main</code> التي بدورها لها معنى خاص في لغة جو فهي مرتبطة بحزمة <code>main</code>، وعندما يرى المُصرّف دالة اسمها <code>main</code> داخل حزمة باسم <code>main</code>سيعرف أن الدالة <code>main</code> هي الدالة الأولى التي يجب تشغيلها، ويُعرف هذا بنقطة دخول البرنامج.
</p>

<p>
	بمجرد إنشاء ملف "main.go"، ستصبح بنية مجلد الوحدة مشابهة لما يلي:
</p>

<pre class="ipsCode">└── projects
    └── mymodule
        └── go.mod
        └── main.go
</pre>

<p>
	إذا كنت معتادًا على استخدام لغة جو ومتغير البيئة <code>GOPATH</code>، فإن تشغيل شيفرة ضمن وحدة مشابهٌ لكيفية تشغيل شيفرة من مجلد في <code>GOPATH</code>. لا تقلق إذا لم تكن معتادًا على <code>GOPATH</code>، لأن استخدام الوحدات يحل محل استخدامها.
</p>

<p>
	هناك طريقتان شائعتان لتشغيل برنامج تنفيذي في لغة جو، هما: إنشاء ملف ثنائي باستخدام <code>go build</code> وتشغيله، أو تشغيل الملف مباشرةً باستخدام <code>go run</code>. ستستخدم هنا <code>go run</code> لتشغيل الوحدة مباشرةً، إذ أن الأخير يجب أن يُشغّل لوحده.
</p>

<p>
	شغّل ملف "main.go" الذي أنشأته باستخدام الأمر <code>go run</code>:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	ستحصل على الخرج التالي:
</p>

<pre class="ipsCode">Hello, Modules!
</pre>

<p>
	أضفنا في هذا القسم الملف "main.go" إلى الوحدة التي أنشأناها وعرّفنا فيها نقطة دخول متمثلة بالدالة <code>main</code> وجعلناها تطبع عبارة ترحيبية.
</p>

<p>
	لم نستفد حتى الآن من خصائص أو فوائد الوحدات في لغة جو؛ إذ لا تختلف الوحدة التي أنشأناها للتو عن أي ملف يُشغّل باستخدام <code>go run</code>، وأول فائدة حقيقية لوحدات جو هي القدرة على إضافة الاعتماديات إلى مشروعك في أي مجلد وليس فقط ضمن مجلد "GOPATH"، كما يمكنك أيضًا إضافة حزم إلى وحدتك.
</p>

<p>
	سنوسّع في القسم التالي هذه الوحدة عن طريق إنشاء حزمة إضافية داخلها.
</p>

<h2>
	إضافة حزمة إلى الوحدة
</h2>

<p>
	قد تحتوي الوحدة على أي عدد من الحزم والحزم الفرعية sub-packages على غرار حزمة جو القياسية، وقد لا تحتوي على أية حزم. سننشئ الآن حزمة باسم <code>mypackage</code> داخل المجلد "mymodule". سنستخدم كما جرت العادة الأمر <code>mkdir</code> ونمرر له الوسيط <code>mypackage</code> لإنشاء مجلد جديد داخل المجلد "mymodule":
</p>

<pre class="ipsCode">$ mkdir mypackage
</pre>

<p>
	سيؤدي هذا إلى إنشاء مجلد جديد باسم "mypackage" مثل حزمة فرعية جديدة من المجلد "mymodule":
</p>

<pre class="ipsCode">└── projects
    └── mymodule
        └── mypackage
        └── main.go
        └── go.mod
</pre>

<p>
	استخدم الأمر <code>cd</code> للانتقال إلى المجلد "mypackage" الجديد، ثم استخدم محرر نانو أو محرر النصوص المفضل لديك لإنشاء ملف "mypackage.go". يمكن أن يكون لهذا الملف أي اسم، ولكن استخدم نفس اسم الحزمة لتسهيل عملية العثور على الملف الأساسي للحزمة:
</p>

<pre class="ipsCode">$ cd mypackage
$ nano mypackage.go
</pre>

<p>
	ضِف داخل الملف "mypackage.go" الشيفرة التالية التي تتضمن دالةً تدعى <code>PrintHello</code> تطبع عبارة <code>!Hello, Modules! This is mypackage speaking</code> عند استدعائها.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5345_8" style=""><span class="pln">package mypackage

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

func </span><span class="typ">PrintHello</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Hello, Modules! This is mypackage speaking!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نحن نريد أن تكون الدالة <code>PrintHello</code> متاحةً للحزم والبرامج الأخرى، لذا جعلناها <a href="https://academy.hsoub.com/programming/go/%D9%81%D9%87%D9%85-%D9%85%D8%AC%D8%A7%D9%84-%D8%B1%D8%A4%D9%8A%D8%A9-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-visibility-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1916/" rel="">دالةً مُصدّرةً exported function</a> بكتابتنا لأول حرف منها بالحالة الكبيرة P.
</p>

<p>
	بعد أن أنشأنا الحزمة <code>mypackage</code> ووضعنا فيها دالةً مُصدَّرةً، سنحتاج الآن إلى استيرادها من حزمة <code>mymodule</code> لاستخدامها. هذا مشابه لكيفية استيراد حزم أخرى مثل حزمة <code>fmt</code>، لكن هنا يجب أن نضع اسم الوحدة قبل اسم الحزمة <code>mymodule/mypackage</code>.
</p>

<p>
	افتح ملف "main.go" من مجلد "mymodule" واستدعِ الدالة <code>PrintHello</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5345_10" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">

    </span><span class="str">"mymodule/mypackage"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Hello, Modules!"</span><span class="pun">)</span><span class="pln">

    mypackage</span><span class="pun">.</span><span class="typ">PrintHello</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	كما ذكرنا قبل قليل، عندما نريد استيراد الحزمة <code>mypackage</code> نضع اسم الوحدة قبلها مع وضع الفاصل <code>/</code> بينهما، وهو نفسه اسم الوحدة الذي وضعته في ملف "go.mod" (أي <code>mymodule</code>).
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5345_12" style=""><span class="str">"mymodule/mypackage"</span></pre>

<p>
	إذا أضفت لاحقًا حزمًا أخرى داخل <code>mypackage</code>، يمكنك استيرادها بطريقة مماثلة. على سبيل المثال، إذا كان لديك حزمة أخرى تسمى <code>extrapackage</code> داخل <code>mypackage</code>، فسيكون مسار استيراد هذه الحزمة هو <code>mymodule/mypackage/extrapackage</code>.
</p>

<p>
	شغّل الوحدة مرةً أخرى بعد إجراء التعديلات عليها باستخدام الأمر <code>go run</code> ومرر اسم الملف "main.go" الموجود ضمن المجلد "mymodule" كما في السابق:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	عند التشغيل سترى نفس الرسالة التي حصلنا عليها سابقًا والمتمثلة بالرسالة <code>!Hello, Modules</code> إضافةً إلى الرسالة المطبوعة من دالة <code>PrintHello</code> والموجودة في الحزمة الجديدة التي أضفناها:
</p>

<pre class="ipsCode">Hello, Modules!
Hello, Modules! This is mypackage speaking!
</pre>

<p>
	لقد أضفت الآن حزمةً جديدة إلى وحدتك عن طريق إنشاء مجلد يسمى "mypackage" مع دالة <code>PrintHello</code>. يمكنك لاحقًا توسيع هذه الوحدة بإضافة حزم ودوال جديدة إليها، كما سيكون من الجيد تضمين وحدات أنشأها أشخاص آخرون في وحدتك.
</p>

<p>
	سنضيف في القسم التالي وحدةً بعيدة (من غيت Git) مثل اعتمادية لوحدتك.
</p>

<h2>
	تضمين وحدة بعيدة أنشأها آخرون في وحدتك
</h2>

<p>
	تُوزَّع وحدات لغة جو من مستودعات التحكم بالإصدار Version Control Repositories -أو اختصارًا VCSs- وأكثرها شيوعًا <a href="https://academy.hsoub.com/programming/workflow/git/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9%D9%8A-%D9%84%D9%84%D8%B9%D9%85%D9%84-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D8%BA%D9%8A%D8%AA-git-r1587/" rel="">غيت git</a>. عندما ترغب بإضافة وحدة جديدة مثل اعتمادية لوحدتك، تستخدم مسار المستودع للإشارة إلى الوحدة التي ترغب باستخدامها. عندما يرى مُصرّف اللغة مسار الاستيراد لهذه الوحدة، سيكون قادرًا على استنتاج مكان وجود هذه الوحدة اعتمادًا على مسار المستودع.
</p>

<p>
	سنضيف في المثال التالي <a href="https://github.com/spf13/cobra" rel="external nofollow">المكتبة cobra</a> مثل اعتمادية للوحدة التي أنشأناها، وهي مكتبة شائعة لإنشاء تطبيقات الطرفية Console.
</p>

<p>
	بطريقة مشابهة لما فعلناه عند إنشاء الوحدة <code>mymodule</code> سنستخدم الأداة <code>go</code> مرةً أخرى، لكن سنضيف لها <code>get</code> أي سنستخدم الأمر <code>go get</code>، وسنضع بعده مسار المستودع.
</p>

<p>
	شغّل الأمر <code>go get</code> من داخل مجلد "mymodule":
</p>

<pre class="ipsCode">$ go get github.com/spf13/cobra
</pre>

<p>
	عند تشغيل هذا الأمر ستبحث أداة <code>go</code> عن مستودع Cobra من المسار الذي حددته، وستبحث عن أحدث إصدار منها من خلال البحث في الفروع ووسوم المستودع. بعد ذلك، سيُحمّل هذا الإصدار ويبدأ في تعقبها، وذلك من خلال إضافة اسم الوحدة والإصدار إلى ملف "go.mod" للرجوع إليه مستقبلًا.
</p>

<p>
	افتح ملف "go.mod" الموجود في المجلد "mymodule" لترى كيف حدّثت أداة <code>go</code> ملف "go.mod" عند إضافة الاعتمادية الجديدة. قد يتغير المثال أدناه اعتمادًا على الإصدار الحالي من Cobra أو إصدار أداة <code>go</code> التي تستخدمها، ولكن يجب أن تكون البنية العامة للتغييرات متشابهة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5345_14" style=""><span class="pln">module mymodule

go </span><span class="lit">1.16</span><span class="pln">

require </span><span class="pun">(</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">inconshreveable</span><span class="pun">/</span><span class="pln">mousetrap v1</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">spf13</span><span class="pun">/</span><span class="pln">cobra v1</span><span class="pun">.</span><span class="lit">2.1</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">spf13</span><span class="pun">/</span><span class="pln">pflag v1</span><span class="pun">.</span><span class="lit">0.5</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
</span><span class="pun">)</span></pre>

<p>
	لاحظ إضافة قسم جديد مُتمثّل بالموجّه <code>require</code>، ومهمته إخبار مُصرّف اللغة عن الوحدة التي تريدها (في حالتنا هي <code>github.com/spf13/cobra</code>) وعن إصدارها أيضًا. نلاحظ أيضًا أن هناك تعليق مكتوب فيه <code>indirect</code>، ليوضح أنه في الوقت الذي أُضيف فيه الموجّه <code>require</code>، لم تكن هناك إشارة مباشرة إلى الوحدة في أي من ملفات المصدر الخاصة بالوحدة. نلاحظ أيضًا وجود بعض الأسطر في <code>require</code> تشير إلى وحدات أخرى تتطلبها المكتبة Cobra والتي يجب على أداة جو أن تشير إليها أيضًا.
</p>

<p>
	نلاحظ أيضًا أنه عند تشغيل الأمر <code>go run</code> أُنشئ ملف جديد باسم "go.sum" في مجلد "mymodule"، وهو ملفٌ آخر مهم لوحدات جو يحتوي على معلومات تستخدمها جو لتسجيل قيم معمّاة hashes وإصدارات معينة من الاعتماديات. يضمن هذا تناسق الاعتماديات، حتى لو ثُبتت على جهاز مختلف.
</p>

<p>
	ستحتاج أيضًا إلى تحديث الملف "main.go" بعد تنزيل الاعتمادية، وذلك من خلال إضافة بعض التعليمات البرمجية الخاصة بها، لكي تتمكن من استخدامها.
</p>

<p>
	افتح الملف "main.go" الموجود في المجلد "mymodule" وضع فيه المحتويات التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5345_16" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">

    </span><span class="str">"github.com/spf13/cobra"</span><span class="pln">

    </span><span class="str">"mymodule/mypackage"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    cmd </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">cobra</span><span class="pun">.</span><span class="typ">Command</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Run</span><span class="pun">:</span><span class="pln"> func</span><span class="pun">(</span><span class="pln">cmd </span><span class="pun">*</span><span class="pln">cobra</span><span class="pun">.</span><span class="typ">Command</span><span class="pun">,</span><span class="pln"> args </span><span class="pun">[]</span><span class="pln">string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Hello, Modules!"</span><span class="pun">)</span><span class="pln">

            mypackage</span><span class="pun">.</span><span class="typ">PrintHello</span><span class="pun">()</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Calling cmd.Execute()!"</span><span class="pun">)</span><span class="pln">
    cmd</span><span class="pun">.</span><span class="typ">Execute</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُنشئ هذه الشيفرة بنية <code>cobra.Command</code> مع دالة <code>Run</code> تطبع عبارة ترحيب، والتي ستُنفّذ بعد ذلك باستدعاء <code>()cmd.Execute</code>.
</p>

<p>
	شغّل الشيفرة الآن بعد أن عدلناها:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سترى الخرج التالي الذي يبدو مشابهًا لما رأيته من قبل، لكننا استخدمنا هنا الاعتمادية الجديدة التي أضفناها كما هو موضح في السطر الأول:
</p>

<pre class="ipsCode">Calling cmd.Execute()!
Hello, Modules!
Hello, Modules! This is mypackage speaking!
</pre>

<p>
	يؤدي استخدام الأمر <code>go get</code> لإضافة اعتماديات إلى وحدتك إلى تنزيل أحدث إصدار من هذه الاعتمادية، وهو أمرٌ جيد لأن أحدث إصدار يتضمن كل التعديلات الجديدة والإصلاحات. قد نرغب أحيانًا في استخدام إصدار أقدم أو فرع مُحدد من مستودع هذه الاعتمادية. سنستخدم في القسم التالي الأداة <code>go get</code> لمعالجة هذه الحالة.
</p>

<h2>
	استخدام إصدار محدد من وحدة
</h2>

<p>
	بما أن وحدات لغة جو توزّع من مستودع التحكم في الإصدار، بالتالي يمكنها استخدام ميزاته، مثل الوسوم والفروع والإيداعات. يمكنك الإشارة إلى هذه الأمور في اعتمادياتك باستخدام الرمز <code>@</code> في نهاية مسار الوحدة جنبًا إلى جنب مع الإصدار الذي ترغب في استخدامه. لاحظ أنه كان بإمكانك استخدام هذه الميزة مباشرةً في المثال السابق، فهي لا تتطلب شيئًا إلا وضع الرمز والإصدار. إذًا يمكننا استنتاج أنه في حال عدم استخدام هذا الرمز، يفترض جو أننا نريد أحدث إصدار، وهذا يُقابل وضع <code>latest</code> بعد هذا الرمز. <code>latest</code> هي ميزة خاصة لأداة جو، وليست جزءًا من الوحدة أو مستودع الوحدة التي تريد استخدامها مثل <code>my-tag</code> أو <code>my-branch</code>. إذًا، تُكافئ الصيغة التالية تنزيل أحدث إصدار من الوحدة سالفة الذكر:
</p>

<pre class="ipsCode">$ go get github.com/spf13/cobra@latest
</pre>

<p>
	تخيل الآن أن هناك وحدة تستخدمها، وهي قيد التطوير حاليًا، ولنفترض اسمها <code>your_domain/sammy/awesome</code>. افترض أن هناك ميزة أضيفت إلى هذه الوحدة في فرع اسمه <code>new-feature</code>. لإضافة هذا الفرع مثل اعتمادية للوحدة الخاصة بك، يمكنك ببساطة استخدام مسار الوحدة متبوعًا بالرمز <code>@</code> متبوعًا باسم الفرع:
</p>

<pre class="ipsCode">$ go get your_domain/sammy/awesome@new-feature
</pre>

<p>
	عند تشغيل هذا الأمر ستتصل أداة <code>go</code> بالمستودع <code>your_domain/sammy/awesome</code>، وستنزّل الفرع <code>new-features</code> من آخر إيداع، وتضيف هذه المعلومات إلى الملف "go.mod".
</p>

<p>
	ليست الفروع الطريقة الوحيدة التي يمكنك من خلالها استخدام الرمز <code>@</code>، ولكن يمكنك استخدامه مع الوسوم أو الإيداعات أيضًا، فقد يكون أحيانًا أحدث إصدار من المكتبة التي تستخدمها إيداعًأ معطلًا، وفي هذه الحالة يمكنك الرجوع إلى إيداع سابق واستخدامه.
</p>

<p>
	بالعودة إلى نفس المثال السابق، افترض أنك بحاجة إلى الإشارة إلى الإيداع <code>07445ea</code> من <code>github.com/spf13/cobra</code> لأنه يحتوي على بعض التغييرات التي تحتاجها ولا يمكنك استخدام إصدار آخر لسبب ما. في هذه الحالة، يمكنك وضع قيمة معمّاة hash بعد الرمز <code>@</code>.
</p>

<p>
	شغّل الآن الأمر <code>go get</code> من داخل مجلد "mymodule" لتنزيل الإصدار الجديد:
</p>

<pre class="ipsCode">$ go get github.com/spf13/cobra@07445ea
</pre>

<p>
	إذا فتحت الآن ملف "go.mod" الخاص بالوحدة، فسترى أن <code>go get</code> قد حدّث سطر <code>require</code> للإشارة إلى الإيداع الذي تستخدمه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5345_18" style=""><span class="pln">module mymodule

go </span><span class="lit">1.16</span><span class="pln">

require </span><span class="pun">(</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">inconshreveable</span><span class="pun">/</span><span class="pln">mousetrap v1</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">spf13</span><span class="pun">/</span><span class="pln">cobra v1</span><span class="pun">.</span><span class="lit">1.2</span><span class="pun">-</span><span class="lit">0.20210209210842</span><span class="pun">-</span><span class="lit">07445ea179fc</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">spf13</span><span class="pun">/</span><span class="pln">pflag v1</span><span class="pun">.</span><span class="lit">0.5</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
</span><span class="pun">)</span></pre>

<p>
	على عكس الوسوم أو الفروع، ونظرًا لأن الإيداع يمثّل نقطةً زمنيةً معينة، تتضمن جو معلومات إضافية في موجّه <code>require</code> للتأكد من استخدام الإصدار الصحيح مستقبلًا؛ فإذا نظرت إلى الإصدار، سترى أنه يتضمن إيداعًا معمّى hash قد أضفته، أي <code>v1.1.2-0.20210209210842-07445ea179fc</code>.
</p>

<p>
	تستخدم جو هذه الوظيفة لدعم إطلاق عدة إصدارات من الوحدة، فعندما تطلق وحدة جو إصدارًا جديدًا، سيُضاف وسمًا جديدًا إلى المستودع مع وسم لرقم الإصدار. إذا كنت تريد استخدام إصدار محدد، يمكنك النظر إلى قائمة الوسوم في المستودع حتى تجد الإصدار الذي تبحث عنه، أما إذا كنت تعرف الإصدار، لن تكون مضطرًا للبحث ضمن قائمة الوسوم لأنها مرتبة بانتظام.
</p>

<p>
	بالعودة إلى نفس المثال Cobra السابق، ولنفترض أنك بحاجة إلى استخدام الإصدار 1.1.1، الذي يملك وسمًا يدعى <code>v1.1.1</code> في مستودع Cobra.
</p>

<p>
	سنستخدم الأمر <code>go get</code> مع الرمز <code>@</code> حتى نتمكن من استخدام هذا الإصدار الموسوم تمامًا كما فعلنا مع الفروع أو الوسوم التي لا تشير لإصدار non-version tag. الآن، حدّث الوحدة الخاصة بك حتى تستخدم Cobra 1.1.1 من خلال تشغيل الأمر <code>go get</code> مع رقم الإصدار <code>v1.1.1</code>:
</p>

<pre class="ipsCode">$ go get github.com/spf13/cobra@v1.1.1
</pre>

<p>
	إذا فتحت الآن ملف "go.mod" الخاص بالوحدة، فسترى أن <code>go get</code> قد حدّث سطر<code>require</code> للإشارة إلى الإيداع الذي تستخدمه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5345_20" style=""><span class="pln">module mymodule

go </span><span class="lit">1.16</span><span class="pln">

require </span><span class="pun">(</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">inconshreveable</span><span class="pun">/</span><span class="pln">mousetrap v1</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">spf13</span><span class="pun">/</span><span class="pln">cobra v1</span><span class="pun">.</span><span class="lit">1.1</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">spf13</span><span class="pun">/</span><span class="pln">pflag v1</span><span class="pun">.</span><span class="lit">0.5</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
</span><span class="pun">)</span></pre>

<p>
	أخيرًا، إذا كنت تستخدم إصدارًا محددًا من المكتبة، مثل الإيداع <code>07445ea</code> أو الإصدار <code>v1.1.1</code> الذين تعرفنا عليهما منذ قليل، ثم قررت استخدام أحدث إصدار من المكتبة، فمن الممكن إنجاز ذلك باستخدام <code>latest</code> كما ذكرنا سابقًا.
</p>

<p>
	لتحديث الوحدة الخاصة بك إلى أحدث إصدار من <code>Cobra</code>، استخدم الأمر <code>go get</code> مرةً أخرى مع مسار الوحدة وتحديد الإصدار <code>latest</code> بعد الرمز <code>@</code>:
</p>

<pre class="ipsCode">$ go get github.com/spf13/cobra@latest
</pre>

<p>
	بعد تنفيذ هذا الأمر، يُحدّث ملف "go.mod" ليبدو كما كان عليه عند تنزيل المكتبة Cobra أول مرة. قد يبدو الخرج مختلفًا قليلًا بحسب إصدار جو الذي تستخدمه والإصدار الأخير من Cobra ولكن يُفترض أن تلاحظ أن السطر <code>github.com/spf13/cobra</code> في قسم <code>require</code> قد تحدّث إلى آخر إصدار:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5345_22" style=""><span class="pln">module mymodule

go </span><span class="lit">1.16</span><span class="pln">

require </span><span class="pun">(</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">inconshreveable</span><span class="pun">/</span><span class="pln">mousetrap v1</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">spf13</span><span class="pun">/</span><span class="pln">cobra v1</span><span class="pun">.</span><span class="lit">2.1</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
    github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">spf13</span><span class="pun">/</span><span class="pln">pflag v1</span><span class="pun">.</span><span class="lit">0.5</span><span class="pln"> </span><span class="com">// indirect</span><span class="pln">
</span><span class="pun">)</span></pre>

<p>
	يُعد الأمر <code>go get</code> أداةً قوية يمكنك استخدامها لإدارة الاعتماديات في ملف "go.mod" دون الحاجة إلى تحريره يدويًا. يتيح لك استخدام الرمز <code>@</code> مع اسم الوحدة، أن تستخدم إصدارات معينة لوحدة ما، أو حتى إيداعات، أو فروع، أو الرجوع إلى أحدث إصدار من اعتمادياتك كما رأينا للتو. سيسمح استخدام مجموعة الأدوات التي تعرفنا عليها في هذه المقالة بضمان استقرار البرنامج مستقبلًا.
</p>

<h2>
	الخاتمة
</h2>

<p>
	أنشأنا في هذه المقالة وحدةً برمجية باستخدام لغة جو مع حزمة فرعية، واستخدمنا تلك الحزمة داخل الوحدة التي أنشأناها. أضفنا أيضًا وحدةً خارجية إليها مثل اعتمادية، وتعلمنا كيفية الإشارة إلى إصدارات الوحدة بطرق مختلفة.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-go-modules?fbclid=IwAR3MRyi6uktLvFflAfZSXzhMs-4wAaBHbtSsLW-jF0E59MEJIA7Lw1iMhAE" rel="external nofollow">How to Use Go Modules</a> لصاحبه Gopher Guides.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AD%D8%B2%D9%85%D8%A9-flag-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r2024/" rel="">كيفية استخدام الحزمة Flag في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A3%D9%88%D9%84-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC-%D9%88%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%84%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r241/" rel="">كتابة أول برنامج (ومكتبة) لك باستخدام لغة البرمجة Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1902/" rel="">استيراد الحزم في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D9%88%D8%A8%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-%D9%88%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87%D8%A7-r1996/" rel="">بناء البرامج المكتوبة بلغة جو Go وتثبيتها</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2086</guid><pubDate>Sat, 12 Aug 2023 16:00:00 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x62D;&#x632;&#x645;&#x629; Flag &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648; Go</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AD%D8%B2%D9%85%D8%A9-flag-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r2024/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_07/---Flag----Go.png.b45a47aacb443f6b715344dc9d752848.png" /></p>
<p>
	نادرًا ما تكون الأدوات المساعدة <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">لسطر الأوامر</a> Command-line utilities مفيدةً دون ضبط configuration إضافي وذلك عندما يتطلب الأمر إجراء عمليات خارج الصندوق. تُعد الإعدادات الافتراضية للأوامر أمرًا جيدًا ومهمًا، لكن ينبغي أن تتميز بإمكانية قبول إعدادات ضبط محددة من المستخدمين أيضًا. في معظم أنظمة التشغيل يمكن تخصيص أوامر سطر الأوامر من خلال استخدام الرايات Flags؛ وهي سلاسل نصية تُضاف إلى أمر ما، بحيث تؤدي إلى سلوك خاص لهذا الأمر حسب قيمتها. تتيح لك لغة جو إنشاء أدوات مساعدة لسطر الأوامر تقبل رايات يمكن من خلالها تخصيص سلوك الأوامر، وذلك باستخدام حزمة <code>flag</code> من المكتبة القياسية.
</p>

<p>
	ستتعلم في هذه المقالة طرقًا مختلفة لاستخدام حزمة <code>flag</code> بهدف إنشاء أنواع مختلفة من الأدوات المساعدة لسطر الأوامر. سنستخدم رايةً للتحكم في خرج برنامج وتقديم وسطاء موضعية Positional arguments (وهي وسطاء يجب وضعها في الموضع أو الترتيب المناسب المُحدد مسبقًا)، إذ يمكننا مزج الرايات والبيانات الأخرى وتنفيذ أوامر فرعية.
</p>

<h2>
	استخدام الرايات لتغيير سلوك البرنامج
</h2>

<p>
	يتضمن استخدام حزمة الراية ثلاث خطوات: تبدأ بتعريف متغيرات تلتقط قيم الرايات، ثم تحديد الرايات التي سيستخدمها التطبيق، وتنتهي بتحليل الرايات المُقدمة للتطبيق عند التنفيذ. تُركّز معظم الدوال داخل حزمة الراية على تعريف رايات وربطها بالمتغيرات التي تُعرّفها، وتنجز الدالة <code>()Parse</code> مرحلة التحليل.
</p>

<p>
	لتوضيح الأمور سنُنشئ برنامجًا بسيطًا يتضمن رايةً بوليانية تُغيّر الرسالة المطبوعة؛ فإذا كانت الراية <code>color-</code> موجودة، سيطبع البرنامج الرسالة باللون الأزرق؛ وإذا لم تُقدّم أي راية، فلن يكون للرسالة أي لون. انشئ ملفًا باسم boolean.go:
</p>

<pre class="ipsCode">$ nano boolean.go
</pre>

<p>
	اضِف ما يلي إلى الملف:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8147_8" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"flag"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">Color</span><span class="pln"> string

</span><span class="kwd">const</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="typ">ColorBlack</span><span class="pln">  </span><span class="typ">Color</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\u001b[30m"</span><span class="pln">
    </span><span class="typ">ColorRed</span><span class="pln">          </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\u001b[31m"</span><span class="pln">
    </span><span class="typ">ColorGreen</span><span class="pln">        </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\u001b[32m"</span><span class="pln">
    </span><span class="typ">ColorYellow</span><span class="pln">       </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\u001b[33m"</span><span class="pln">
    </span><span class="typ">ColorBlue</span><span class="pln">         </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\u001b[34m"</span><span class="pln">
    </span><span class="typ">ColorReset</span><span class="pln">        </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\u001b[0m"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func colorize</span><span class="pun">(</span><span class="pln">color </span><span class="typ">Color</span><span class="pun">,</span><span class="pln"> message string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">string</span><span class="pun">(</span><span class="pln">color</span><span class="pun">),</span><span class="pln"> message</span><span class="pun">,</span><span class="pln"> string</span><span class="pun">(</span><span class="typ">ColorReset</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    useColor </span><span class="pun">:=</span><span class="pln"> flag</span><span class="pun">.</span><span class="typ">Bool</span><span class="pun">(</span><span class="str">"color"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln"> </span><span class="str">"display colorized output"</span><span class="pun">)</span><span class="pln">
    flag</span><span class="pun">.</span><span class="typ">Parse</span><span class="pun">()</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">*</span><span class="pln">useColor </span><span class="pun">{</span><span class="pln">
        colorize</span><span class="pun">(</span><span class="typ">ColorBlue</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Hello, DigitalOcean!"</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Hello, DigitalOcean!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يستخدم هذا المثال <a href="https://en.wikipedia.org/wiki/ANSI_escape_code" rel="external nofollow">سلاسل الهروب آنسي ANSI Escape Sequences</a> لجعل الطرفية تُعطي خرجًا ملونًأ؛ وهي سلاسل خاصة من المحارف، لذا من المنطقي أن نُعرّف نوع خاص بها. في مثالنا نسمي هذا النوع <code>Color</code> ونعرّفه على أنه <code>string</code>، ثم نُعرّف لوحة ألوان لاستخدامها بكتلة نسميها <code>const</code> تتضمن عدة خيارات لونية اعتمادًا على النوع السابق. تستقبل الدالة <code>colorize</code> المُعرّفة بعد الكتلة <code>const</code> قيمةً لونيةً من اللوحة السابقة (أي عمليًّا متغير من النوع <code>Color</code>) إضافةً إلى الرسالة المطلوب تلوينها. بعد ذلك توجّه <a href="https://academy.hsoub.com/devops/linux/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D8%B7%D8%B1%D9%81%D9%8A%D8%A9-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D8%A8%D8%A7%D8%AD%D8%AA%D8%B1%D8%A7%D9%81-r367/" rel="">الطرفية Terminal</a> لتغيير اللون عن طريق طباعة تسلسل الهروب للون المطلوب، ثم طباعة الرسالة. أخيرًا، يُطلب من الطرفية إعادة ضبط اللون الأساسي لها من خلال طباعة <code>ColorReset</code>، أي نطبع سلسلة الهروب للون المطلوب ثم رسالتنا، فتظهر باللون المطلوب، ثم نعيد ضبط اللون إلى حالته الأصلي.
</p>

<p>
	نستخدم داخل الدالة <code>main</code> الدالة <code>flag.Bool</code> لتعريف راية بوليانية اسمها <code>color</code>. المعامل الثاني لهذه الدالة <code>false</code> هو القيمة الافتراضية للراية، أي عندما نُشغل البرنامج بدونها. على عكس ما قد تتوقعه، فإن ضبط هذا المعامل على <code>true</code> هو أمر غير صحيح، لأننا لا نريد أن يُطبق سلوك التلوين إلا عندما نُمرر الراية كما سترى بعد قليل. إذًا، تكون قيمة هذا المعامل تكون غالبًا <code>false</code> مع الرايات البوليانية.
</p>

<p>
	المعامل الأخير هو نص توضيحي لهذه الراية، أي كأنه توثيق استخدام أو وصف. القيمة التي تعيدها الدالة هي مؤشر إلى <code>bool</code>، وتُضبط قيمة هذا المؤشر من خلال الدالة <code>flag.Parse</code> بناءً على الراية التي يُمررها المستخدم. يمكننا بعد ذلك التحقق من قيمة هذا المؤشر البولياني عن طريق تحصيل قيمته باستخدام المعامل <code>*</code>. إذًا باستخدام هذه القيمة المنطقية، يمكننا استدعاء <code>colorize</code> عند تمرير <code>color-</code> أو استدعاء دالة الطباعة العادية <code>fmt.Println</code> دون تلوين إذا لم تُمرر الراية. احفظ وأغلق الملف وشغله بدون تقديم أي راية:
</p>

<pre class="ipsCode">$ go run boolean.go
</pre>

<p>
	ستحصل على الخرج التالي:
</p>

<pre class="ipsCode">Hello, DigitalOcean!
</pre>

<p>
	أعد تشغيله مع تمرير الراية <code>color-</code>:
</p>

<pre class="ipsCode">$ go run boolean.go -color
</pre>

<p>
	سيكون الناتج هو نفس النص، ولكن هذه المرة باللون الأزرق.
</p>

<p>
	يمكنك أيضًا إرسال أسماء ملفات أو بيانات أخرى إلى البرنامج، وليس فقط رايات.
</p>

<h2>
	التعامل مع الوسطاء الموضعية
</h2>

<p>
	تمتلك الأوامر غالبًا بعض الوسطاء التي تحدد سلوكها. لنأخذ مثلًا الأمر <code>head</code> الذي يطبع الأسطر الأولى من ملف ما. غالبًا ما يُستدعى هذا الأمر بالشكل التالي <code>head example.txt</code>، إذ يُمثل الملف example.txt وسيطًا موضعيًّا هنا.
</p>

<p>
	تستمر الدالة <code>()Parse</code> في تحليل الرايات التي تصادفها ريثما تجد شيئًا آخر لا يُمثّل راية (سنرى بعد قليل أنها ستتوقف عند مصادفة وسيط موضعي). لذا سنتعرف الآن على دالة أخرى توفرها الحزمة <code>flag</code> تتمثل بالدالة <code>()Args</code> والدالة <code>()Arg</code>. لتوضيح ذلك سنعيد تنفيذ الأمر <code>head</code> الذي يعرض أول عدة أسطر من ملف معين.
</p>

<p>
	أنشئ ملفًا جديدًا يسمى head.go وضِف الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8147_10" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"bufio"</span><span class="pln">
    </span><span class="str">"flag"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"io"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    var count </span><span class="typ">int</span><span class="pln">
    flag</span><span class="pun">.</span><span class="typ">IntVar</span><span class="pun">(&amp;</span><span class="pln">count</span><span class="pun">,</span><span class="pln"> </span><span class="str">"n"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="str">"number of lines to read from the file"</span><span class="pun">)</span><span class="pln">
    flag</span><span class="pun">.</span><span class="typ">Parse</span><span class="pun">()</span><span class="pln">

    var in io</span><span class="pun">.</span><span class="typ">Reader</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> filename </span><span class="pun">:=</span><span class="pln"> flag</span><span class="pun">.</span><span class="typ">Arg</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span><span class="pln"> filename </span><span class="pun">!=</span><span class="pln"> </span><span class="str">""</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        f</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Open</span><span class="pun">(</span><span class="pln">filename</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"error opening file: err:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
            os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        defer f</span><span class="pun">.</span><span class="typ">Close</span><span class="pun">()</span><span class="pln">

        in </span><span class="pun">=</span><span class="pln"> f
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        in </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stdin</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    buf </span><span class="pun">:=</span><span class="pln"> bufio</span><span class="pun">.</span><span class="typ">NewScanner</span><span class="pun">(</span><span class="pln">in</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> i </span><span class="pun">:=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> count</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">!</span><span class="pln">buf</span><span class="pun">.</span><span class="typ">Scan</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">break</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">buf</span><span class="pun">.</span><span class="typ">Text</span><span class="pun">())</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> buf</span><span class="pun">.</span><span class="typ">Err</span><span class="pun">();</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Fprintln</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pun">,</span><span class="pln"> </span><span class="str">"error reading: err:"</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نُعرّف بدايةً المتغير <code>count</code> الذي سيُخزّن عدد الأسطر التي يجب أن يقرأها البرنامج من الملف، كما نُعرّف أيضًا الراية <code>n-</code> باستخدام <code>flag.IntVar</code> لكي نعكس سلوك البرنامج <code>head</code>. تسمح لنا هذه الدالة بتمرير المؤشر الخاص بنا إلى متغير على عكس دوال حزمة الراية <code>flag</code> الأخرى، التي لا تحتوي على اللاحقة <code>Var</code>. بغض النظر عن هذا الفرق، ستكون بقية المعاملات للدالة <code>flag.IntVar</code> مثل نظيراتها في دوال <code>flag.Int</code> الأخرى التي لا تتضمن هذه اللاحقة، أي ستكون المعاملات كما يلي: اسم الراية ثم القيمة الافتراضية ثم الوصف. بعد ذلك، نستدعي دالة <code>()flag.Parse</code> لتفسير دخل المستخدم.
</p>

<p>
	يقرأ القسم التالي الملف، إذ نعرّف متغيرًا باسم <code>io.Reader</code> الذي سيُضبط إما على الملف الذي يطلبه المستخدم، أو الدخل القياسي الذي يُمرر إلى البرنامج. نستخدم الدالة <code>flag.Arg</code> داخل التعليمة <code>if</code> للوصول إلى أول وسيط موضعي يأتي بعد الرايات. ستكون قيمة <code>filename</code>، إما اسم ملف يُقدمه المستخدم أو سلسلة فارغة <code>""</code>؛ ففي حال تقديم اسم ملف، تُستخدم الدالة <code>os.Open</code> لفتح الملف وضبط المتغير <code>io.Reader</code> سالف الذكر؛ وإذا لم يُقدم اسم ملف (أي سلسلة فارغة) فإننا نستخدم <code>os.Stdin</code> للقراءة من الدخل القياسي.
</p>

<p>
	يستخدم القسم الأخير <code>bufio.Scanner*</code> الذي أنشئ باستخدام <code>bufio.NewScanner</code> لقراءة الأسطر من متغير <code>io.Reader</code> الذي يُمثله <code>in</code>. بعد ذلك، نكرّر حلقة عدة مرات حسب قيمة <code>count</code>. تُستدعى <code>break</code> إذا كان ناتج قراءة سطر باستخدام <code>buf.Scan</code> هو القيمة <code>false</code>، إذ يشير ذلك إلى أن عدد الأسطر أقل من الرقم الذي يطلبه المستخدم.
</p>

<p>
	شغّل هذا البرنامج واعرض محتويات الملف البرمجي نفسه الذي كتبته للتو من خلال استخدام <code>head.go</code> مثل وسيط، أي سنشغّل البرنامج head.go ونقرأ محتوياته أيضًا:
</p>

<pre class="ipsCode">$ go run head.go -- head.go
</pre>

<p>
	الفاصل <code>--</code> هو راية، إذ يُفسَّر وجودها من قِبل حزمة الراية <code>flag</code> على أنه لن يكون هناك رايات بدءًا منها. سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">package main

import (
        "bufio"
        "flag"
</pre>

<p>
	دعنا نستخدم الراية <code>n-</code> التي عرّفناها لتحديد عدد الأسطر المقروءة:
</p>

<pre class="ipsCode">$ go run head.go -n 1 head.go
</pre>

<p>
	سيكون الخرج هو أول سطر فقط من الملف:
</p>

<pre class="ipsCode">package main
</pre>

<p>
	أخيرًا، عندما يكتشف البرنامج أنه لم تُقدّم أية وسطاء موضعية، سيقرأ من الدخل القياسي، تمامًا مثل الأمر <code>head</code>. جرّب تشغيل هذا الأمر:
</p>

<pre class="ipsCode">$ echo "fish\nlobsters\nsharks\nminnows" | go run head.go -n 3
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">fish
lobsters
sharks
</pre>

<p>
	يقتصر سلوك دوال حزمة الراية التي رأيناها حتى الآن على فحص الأمر المُستدعى كاملًا. لا نريد دائمًا هذا السلوك، خاصةً إذا كانت الأداة التي نريدها تتضمن أوامر فرعية Sub-commands.
</p>

<h2>
	استخدام FlagSet لدعم إمكانية تحقيق الأوامر الفرعية
</h2>

<p>
	تتضمن تطبيقات سطر الأوامر الحديثة غالبًا وجود أوامر فرعية، بهدف تجميع مجموعة من الأدوات تحت أمر واحد. الأداة الأكثر شهرة التي تستخدم هذا النمط هي <a href="https://academy.hsoub.com/programming/workflow/git/%D9%85%D8%A7-%D9%87%D9%88-git%D8%9F-r1592/" rel=""><code>git</code></a>، فمثلًا في <code>git init</code>، لدينا <code>git</code> هو الأمر الأساسي و <code>init</code> هو الأمر الفرعي التابع له. إحدى السمات البارزة للأوامر الفرعية هي أن كل أمر فرعي يمكن أن يكون له مجموعته الخاصة من الرايات.
</p>

<p>
	يمكن لتطبيقات لغة جو أن تدعم الأوامر الفرعية من خلال نوع خاص يُدعى <code>(flag.(*FlagSet</code>. سننشئ برنامجًا يُنفذ أمرًا باستخدام أمرين فرعيين وبرايات مختلفة، وذلك لكي نجعل الأمور واضحة.
</p>

<p>
	نُنشئ ملفًا جديدًا يسمى subcommand.go بالمحتويات التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_8147_12" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"errors"</span><span class="pln">
    </span><span class="str">"flag"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func </span><span class="typ">NewGreetCommand</span><span class="pun">()</span><span class="pln"> </span><span class="pun">*</span><span class="typ">GreetCommand</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    gc </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">GreetCommand</span><span class="pun">{</span><span class="pln">
        fs</span><span class="pun">:</span><span class="pln"> flag</span><span class="pun">.</span><span class="typ">NewFlagSet</span><span class="pun">(</span><span class="str">"greet"</span><span class="pun">,</span><span class="pln"> flag</span><span class="pun">.</span><span class="typ">ContinueOnError</span><span class="pun">),</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    gc</span><span class="pun">.</span><span class="pln">fs</span><span class="pun">.</span><span class="typ">StringVar</span><span class="pun">(&amp;</span><span class="pln">gc</span><span class="pun">.</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"name"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"World"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"name of the person to be greeted"</span><span class="pun">)</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> gc
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">GreetCommand</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fs </span><span class="pun">*</span><span class="pln">flag</span><span class="pun">.</span><span class="typ">FlagSet</span><span class="pln">

    name string
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">g </span><span class="pun">*</span><span class="typ">GreetCommand</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Name</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> g</span><span class="pun">.</span><span class="pln">fs</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">g </span><span class="pun">*</span><span class="typ">GreetCommand</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Init</span><span class="pun">(</span><span class="pln">args </span><span class="pun">[]</span><span class="pln">string</span><span class="pun">)</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> g</span><span class="pun">.</span><span class="pln">fs</span><span class="pun">.</span><span class="typ">Parse</span><span class="pun">(</span><span class="pln">args</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">g </span><span class="pun">*</span><span class="typ">GreetCommand</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Run</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Hello"</span><span class="pun">,</span><span class="pln"> g</span><span class="pun">.</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"!"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> nil
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">Runner</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Init</span><span class="pun">([]</span><span class="pln">string</span><span class="pun">)</span><span class="pln"> error
    </span><span class="typ">Run</span><span class="pun">()</span><span class="pln"> error
    </span><span class="typ">Name</span><span class="pun">()</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func root</span><span class="pun">(</span><span class="pln">args </span><span class="pun">[]</span><span class="pln">string</span><span class="pun">)</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> len</span><span class="pun">(</span><span class="pln">args</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> errors</span><span class="pun">.</span><span class="typ">New</span><span class="pun">(</span><span class="str">"You must pass a sub-command"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    cmds </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">[]</span><span class="typ">Runner</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">NewGreetCommand</span><span class="pun">(),</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    subcommand </span><span class="pun">:=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Args</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> _</span><span class="pun">,</span><span class="pln"> cmd </span><span class="pun">:=</span><span class="pln"> range cmds </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">()</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> subcommand </span><span class="pun">{</span><span class="pln">
            cmd</span><span class="pun">.</span><span class="typ">Init</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="typ">Args</span><span class="pun">[</span><span class="lit">2</span><span class="pun">:])</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Run</span><span class="pun">()</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Errorf</span><span class="pun">(</span><span class="str">"Unknown subcommand: %s"</span><span class="pun">,</span><span class="pln"> subcommand</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> root</span><span class="pun">(</span><span class="pln">os</span><span class="pun">.</span><span class="typ">Args</span><span class="pun">[</span><span class="lit">1</span><span class="pun">:]);</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ينقسم هذا البرنامج إلى عدة أجزاء: الدالة <code>main</code>، والدالة <code>root</code>، وبعض الدوال لتحقيق الأمر الفرعي.
</p>

<p>
	تعالج الدالة <code>main</code> الأخطاء التي تُعاد من الأوامر، بينما تلتقط تعليمة <code>if</code> الأخطاء التي قد تُعيدها الدوال وتطبع الخطأ ويُنهى البرنامج وتعاد القيمة 1 إشارةً إلى حدوث خطأ إلى نظام التشغيل. نمرر جميع المعطيات التي استُدعي البرنامج معها داخل الدالة<code>main</code> إلى الدالة <code>root</code> مع حذف الوسيط الأول الذي يُمثل اسم البرنامج (في الأمثلة السابقة <code>subcommand\.</code>) من خلال استقطاع <code>os.Args</code>.
</p>

<p>
	تعرّف الدالة الدالة <code>root</code> الأمر الفرعي <code>Runner[]</code> حيثما تُعرّف جميع الأوامر الفرعية. <code>Runner</code> هي واجهة الأوامر الفرعية التي تسمح لدالة <code>root</code> باسترداد اسم الأمر الفرعي باستخدام <code>()Name</code> ومقارنته بمحتويات المتغير <code>subcommand</code>. بمجرد تحديد الأمر الفرعي الصحيح بعد التكرار على متغير <code>cmds</code>، نُهيئ الأمر الفرعي مع بقية الوسطاء ونستدعي التابع <code>()Run</code> الخاص بهذا الأمر.
</p>

<p>
	على الرغم من أننا نُعرّف أمرًا فرعيًّا واحدًا، إلا أن هذا الإطار سيسمح لنا بسهولة بإنشاء أوامر أخرى. نُعرّف نسخةً من النوع <code>GreetCommand</code> باستخدام <code>NewGreetCommand</code>، إذ نُنشئ <code>flag.FlagSet*</code> جديد باستخدام <code>flag.NewFlagSet</code>. تأخذ <code>flag.NewFlagSet</code> وسيطين، هما اسم مجموعة الرايات واستراتيجية للإبلاغ عن أخطاء التحليل. يمكن الوصول إلى اسم <code>flag.FlagSet*</code> باستخدام التابع<code>flag.(*FlagSet).Name</code>. نستخدم هذا في الطريقة <code>()GreetCommand).Name*)</code> بحيث يتطابق اسم الأمر الفرعي مع الاسم الذي قدمناه إلى <code>flag.FlagSet*</code>.
</p>

<p>
	يُعرّف <code>NewGreetCommand</code> أيضًا الراية <code>name-</code> بطريقة مشابهة للأمثلة السابقة، ولكنه بدلًا من ذلك يستدعيها مثل تابع للحقل <code>flag.FlagSet*</code> في <code>GreetCommand*</code> (نكتب <code>gc.fs</code>).
</p>

<p>
	عندما تستدعي <code>root</code> التابع <code>()Init</code> الذي يخص <code>GreetCommand*</code>، فإننا نمرر المعطيات المقدمة إلى التابع <code>Parse</code> الخاص بالحقل <code>flag.FlagSet*</code>.
</p>

<p>
	سيكون من الأسهل أن ترى الأوامر الفرعية إذا بنيت هذا البرنامج ثم شغلته. اِبنِ البرنامج كما يلي:
</p>

<pre class="ipsCode">$ go build subcommand.go
</pre>

<p>
	الآن، شغّل البرنامج دون وسطاء:
</p>

<pre class="ipsCode">$ ./subcommand
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">You must pass a sub-command
</pre>

<p>
	شغّل الآن البرنامج مع استخدام الأمر الفرعي <code>greet</code>:
</p>

<pre class="ipsCode">$ ./subcommand greet
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Hello World !
</pre>

<p>
	استخدم الآن الراية <code>name</code> مع <code>greet</code> لتحديد اسم:
</p>

<pre class="ipsCode">$ ./subcommand greet -name Sammy
</pre>

<p>
	سيكون الخرج كما يلي:
</p>

<pre class="ipsCode">Hello Sammy !
</pre>

<p>
	يوضح هذا المثال بعض المبادئ الكامنة وراء كيفية هيكلة تطبيقات سطر الأوامر الأكبر حجمًا أو الأكثر تعقيدًا في لغة جو. صُمم <code>FlagSets</code> لمنح المطورين مزيدًا من التحكم وكيفية معالجة الرايات من خلال منطق تحليل الراية.
</p>

<h2>
	الخاتمة
</h2>

<p>
	تمنح الرايات مستخدمي برنامجك التحكم في كيفية تنفيذ البرامج، أو التحكم بسلوكه، وبالتالي تجعله أكثر مرونةً في التعامل مع سياقات التنفيذ المحتملة. من المهم أن تمنح المستخدمين إعدادات افتراضية مفيدة، ولكن يجب أن تمنحهم الفرصة لتجاوز الإعدادات التي لا تناسب حالتهم. لقد رأينا أن حزمة الراية <code>flag</code> توفر خيارات مرنة لتعزيز إمكانية إضافة خيارات ضبط خاصة للمستخدمين. يمكنك اختيار بعض الرايات البسيطة، أو إنشاء مجموعة من الأوامر الفرعية القابلة للتوسيع. سيساعدك استخدام حزمة الراية في كلتا الحالتين على بناء أدوات مرنة لسطر الأوامر.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-the-flag-package-in-go" rel="external nofollow">How To Use the Flag Package in Go</a> لصاحبه Gopher Guides.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%B1%D8%A7%D9%8A%D8%A9-ldflags-%D9%84%D8%B6%D8%A8%D8%B7-%D9%85%D8%B9%D9%84%D9%88%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r2023/" rel="">استخدام الراية ldflags لضبط معلومات الإصدار لتطبيقات لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D9%88%D8%A7%D8%B3%D8%AA%D8%AF%D8%B9%D8%A7%D8%A1-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1962/" rel="">كيفية تعريف واستدعاء الدوال في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-go-%D8%B9%D9%84%D9%89-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AE%D8%AA%D9%84%D9%81%D8%A9-r2022/" rel="">البرمجة بلغة go كيفية تعريف واستدعاء الدوال في لغة جو Go</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2024</guid><pubDate>Sat, 05 Aug 2023 13:00:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x631;&#x627;&#x64A;&#x629; ldflags &#x644;&#x636;&#x628;&#x637; &#x645;&#x639;&#x644;&#x648;&#x645;&#x627;&#x62A; &#x627;&#x644;&#x625;&#x635;&#x62F;&#x627;&#x631; &#x644;&#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x644;&#x63A;&#x629; &#x62C;&#x648; Go</title><link>https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%B1%D8%A7%D9%8A%D8%A9-ldflags-%D9%84%D8%B6%D8%A8%D8%B7-%D9%85%D8%B9%D9%84%D9%88%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1-%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r2023/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_07/--ldflags-------Go.png.afe6515ba49101a2ac1ed08fe25afff7.png" /></p>
<p>
	يؤدي بناء الثنائيات أو الملفات التنفيذية Binaries جنبًا إلى جنب مع إنشاء البيانات الوصفية Metadata والمعلومات الأخرى المتعلقة بالإصدار Version عند نشر التطبيقات إلى تحسين عمليات المراقبة Monitoring والتسجيل Logging وتصحيح الأخطاء، وذلك من خلال إضافة معلومات تعريف تساعد في تتبع عمليات البناء التي تُجريها بمرور الوقت. يمكن أن تتضمن معلومات الإصدار العديد من الأشياء التي تتسم بالديناميكية، مثل وقت البناء والجهاز أو المستخدم الذي أجرى عملية بناء الملف التنفيذي ورقم المعرّف ID للإيداع Commit على <a href="https://academy.hsoub.com/programming/workflow/git/%D8%A8%D8%AF%D8%A1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D9%86%D8%B8%D8%A7%D9%85-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1%D8%A7%D8%AA-%D8%AC%D9%8A%D8%AA-git-r1593/" rel="">نظام إدارة الإصدار <abbr title="Version Control Systems | أنظمة التحكم بالنُّسخ"><abbr title="Version Control Systems | أنظمة التحكم بالنُّسخ">VCS</abbr></abbr></a> الذي تستخدمه غيت Git مثلًا. بما أن هذه المعلومات تتغير باستمرار، ستكون كتابة هذه المعلومات ضمن الشيفرة المصدر مباشرةً، وتعديلها في كل مرة نرغب فيها بإجراء تعديل أو بناء جديد أمرًا مملًا، وقد يُعرّض التطبيق لأخطاء. يمكن للملفات المصدرية التنقل وقد تُبدّل المتغيرات أو الثوابت الملفات خلال عملية التطوير، مما يؤدي إلى كسر عملية البناء.
</p>

<p>
	إحدى الطرق المستخدمة لحل هذه المشكلة في لغة جو هي استخدام الراية <code>ldflags-</code> مع الأمر <code>go build</code> لإدراج معلومات ديناميكية في الملف الثنائي التنفيذي في وقت البناء دون الحاجة إلى تعديل التعليمات البرمجية المصدرية. تُشير <code>Id</code> ضمن الراية السابقة إلى <a href="https://en.wikipedia.org/wiki/Linker_(computing)" rel="external nofollow">الرابط linker</a>، الذي يُمثّل البرنامج الذي يربط الأجزاء المختلفة من الشيفرة المصدرية المُصرّفة مع الملف التنفيذي النهائي. إذًا، تعني <code>ldflags</code> رايات الرابط linker flags؛ لأنها تمرر إشارة إلى الأداة cmd/link الخاصة بلغة جو، والتي تسمح لك بتغيير قيم الحزم المستوردة في وقت البناء من سطر الأوامر.
</p>

<p>
	سنستخدم في هذه المقالة الراية <code>ldflags-</code> لتغيير قيمة المتغيرات في وقت البناء وإدخال المعلومات الديناميكية ضمن ملف ثنائي تنفيذي، من خلال تطبيق يطبع معلومات الإصدار على الشاشة.
</p>

<h2>
	المتطلبات
</h2>

<ul>
	<li>
		أن يكون لديك مساحة عمل خاصة في لغة جو. لقد تحدّثنا عن ذلك في بداية السلسلة في المقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a>، و<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>، و<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
</ul>

<h2>
	بناء تطبيق تجريبي
</h2>

<p>
	يجب أن يكون لدينا تطبيق حتى نستطيع تجريب عملية إدراج المعلومات إليه دينامكيًا من خلال الراية <code>ldflags-</code>. سنبني في هذه الخطوة تطبيقًا بسيطًا للتجريب عليه، بحيث يطبع المعلومات الخاصة بالإصدار. بدايةً أنشئ مجلدًا باسم app -يُمثّل اسم التطبيق- داخل المجلد src:
</p>

<pre class="ipsCode">$ mkdir app
</pre>

<p>
	انتقل إلى هذا المجلد:
</p>

<pre class="ipsCode">$ cd app
</pre>

<p>
	أنشئ باستخدام محرر النصوص الذي تُفضّله وليكن <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel="">نانو nano</a> الملف main.go:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	ضع الشيفرة التالية بداخل هذا الملف، والتي تؤدي إلى طباعة معلومات الإصدار الحالي من التطبيق:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4951_8" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

var </span><span class="typ">Version</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"development"</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Version:\t"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Version</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	صرّحنا داخل الدالة <code>main</code> عن متغير يُدعى <code>Version</code>، ثم طبعنا السلسلة النصية <code>:Version</code> متبوعةً بإزاحة جدول واحدة tab كما يلي: <code>t\</code>، ثم قيمة المتغير <code>Version</code>. هنا أعطينا متغير الإصدار القيمة <code>development</code>، والتي ستكون إشارةً إلى الإصدار الافتراضي من التطبيق. سنُعدّل لاحقًا قيمة هذا المتغير، بحيث تُشير إلى الإصدار الرسمي من التطبيق، ووفقًا للتنسيق المُتبع في تسمية الإصدارات.
</p>

<p>
	احفظ واغلق الملف، ثم ابنِ الملف وشغّله للتأكد من أنه يعمل:
</p>

<pre class="ipsCode">$ go build
$ ./app
</pre>

<p>
	ستحصل على الخرج التالي:
</p>

<pre class="ipsCode">Version:     development
</pre>

<p>
	لديك الآن تطبيق يطبع معلومات الإصدار الافتراضي، ولكن ليس لديك حتى الآن طريقةً لتمرير معلومات الإصدار الحالي في وقت البناء. ستستخدم في الخطوة التالية الراية <code>ldflags-</code> لحل هذه المشكلة.
</p>

<h2>
	استخدام ldflags مع go build
</h2>

<p>
	تحدّثنا سابقًا أن رايات الربط تُستخدم لتمرير الرايات إلى أداة الربط الأساسية underlying الخاصة بلغة جو. يحدث ذلك وفقًا للصيغة التالية:
</p>

<pre class="ipsCode">$ go build -ldflags="-flag"
</pre>

<p>
	هنا مرّرنا <code>flag</code> إلى الأداة الأساسية <code>go tool link</code> التي تعمل بمثابة جزء من الأمر <code>go build</code>. هنا وضعنا علامتي اقتباس حول القيمة التي نمررها إلى <code>ldflags</code>، وذلك لكي نمنع حدوث التباس لدى <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%A7-%D9%87%D9%88-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%9F-r353/" rel="">سطر الأوامر</a> (لكي لا يفسرها بطريقة خاطئة أو يعدها عدة محارف كل منها لغرض مختلف). يمكنك تمرير العديد من رايات الروابط، وفي هذه المقالة سنحتاج إلى استخدام الراية <code>X-</code> لضبط معلومات متغير الإصدار في وقت الربط link time، وسيتبعها مسار المتغير (هنا اسم الحزمة متبوعة باسم المتغير) مع قيمته الجديدة.
</p>

<pre class="ipsCode">$ go build -ldflags="-X 'package_path.variable_name=new_value'"
</pre>

<p>
	لاحظ أننا وضعنا الخيار <code>X-</code> ضمن علامتي اقتباس وبجانبها وضعنا اسم الحزمة متبوعةً بنقطة "." متبوعةً باسم المتغير والقيمة الجديدة وأحطناهم بعلامات اقتباس مفردة لكي يُفسرها سطر الأوامر على أنها كتلة واحدة. إذًا، سنستخدم الصيغة السابقة لاستبدال قيمة متغير الإصدار <code>Version</code> في تطبيقنا:
</p>

<pre class="ipsCode">$ go build -ldflags="-X 'main.Version=v1.0.0'"
</pre>

<p>
	تمثّل <code>main</code> هنا مسار الحزمة للمتغير <code>Version</code>، لأنه يتواجد داخل الملف main.go. هنا <code>Version</code> هو المتغير المطلوب تعديله، والقيمة <code>v1.0.0</code> هي القيمة الجديدة التي نريد ضبطه عليها.
</p>

<p>
	عندما نستخدم الراية <code>ldflags</code>، يجب أن تكون القيمة التي تريد تغييرها موجودة وأن يكون المتغير موجودًا ضمن مستوى الحزمة ومن نوع <code>string</code>. لا يُسمح بأن يكون المتغير ثابتًا <code>const</code>، أو أن تُضبط قيمته من خلال استدعاء دالة. يتوافق كل شيء هنا مع المتطلبات، لذا ستعمل الأمور كما هو متوقع؛ فالمتغير موجود ضمن الملف main.go والمتغير والقيمة <code>v1.0.0</code> التي نريد ضبط المتغير عليها كلاهما من النوع <code>string</code>.
</p>

<p>
	شغّل التطبيق بعد بنائه:
</p>

<pre class="ipsCode">$./app
</pre>

<p>
	ستحصل على الخرج التالي:
</p>

<pre class="ipsCode">Version:     v1.0.0
</pre>

<p>
	إذًا، استطعنا من خلال الراية <code>ldflags-</code> تعديل قيمة متغير الإصدار من <code>development</code> إلى <code>v1.0.0</code> في وقت البناء. يمكنك تضمين تفاصيل الإصدار ومعلومات الترخيص وغيرهم من المعلومات باستخدام <code>ldflags-</code> جنبًا إلى جنب مع الملف التنفيذي النهائي الذي ترغب بنشره وذلك فقط من خلال سطر الأوامر. في هذا المثال: كان المتغير موجود في مسار واضح، لكن في أمثلة أخرى قد يكون العثور على مسار المتغير أمرًا معقدًا. سنناقش في الخطوة التالية هذا الموضوع، وسنرى ماهي أفضل الطرق لتحديد مسارات المتغيرات الموجودة في حزم فرعية في الحزم ذات الهيكلية الأكثر تعقيدًا.
</p>

<h2>
	تحديد مسار الحزمة للمتغيرات
</h2>

<p>
	كنا قد وضعنا في المثال السابق متغير الإصدار <code>Version</code> ضمن المستوى الأعلى من الحزمة في الملف main.go، لذا كان أمر الوصول إليه بسيطًا. هذه حالة مثالية، لكن في الواقع هذا لايحدث دائمًا، فأحيانًا تكون المتغيرات ضمن حزمة فرعية أخرى. عمومًا، لا يُحبذ وضع هكذا متغيرات ضمن main، لأنه حزمة غير قابلة للاستيراد، ويُفضّل وضع هكذا متغيرات ضمن حزمة أخرى. لذا سنعدل بعض الأمور في تطبيقنا، إذ سنُنشئ الحزمة app/build ونضع فيها معلومات حول وقت بناء الملف التنفيذي واسم المستخدم الذي بناه.
</p>

<p>
	انشئ مجلدًا جديدًا باسم الحزمة الجديدة:
</p>

<pre class="ipsCode">$ mkdir -p build
</pre>

<p>
	انشئ ملفًا جديدًا باسم build.go من أجل وضع المتغير/المتغيرات ضمنه:
</p>

<pre class="ipsCode">$ nano build/build.go
</pre>

<p>
	ضع بداخله المتغيرات التالية بعد فتحه باستخدام محرر النصوص الذي تريده:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4951_10" style=""><span class="pln">package build
var </span><span class="typ">Time</span><span class="pln"> string </span><span class="com"># سيُخزّن وقت بناء التطبيق</span><span class="pln">
var </span><span class="typ">User</span><span class="pln"> string </span><span class="com"># سيُخزّن اسم المستخدم الذي بناه</span></pre>

<p>
	لا يمكن لهذين المتغيرين أن يتواجدا بدون قيم، لذا لا داعٍ لوضع قيم افتراضية لهما كما فعلنا مع متغير الإصدار. احفظ وأغلق الملف.
</p>

<p>
	افتح ملف main.go لوضع المتغيرات داخله:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	ضع فيه المحتويات التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_4951_12" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"app/build"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

var </span><span class="typ">Version</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"development"</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Version:\t"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Version</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"build.Time:\t"</span><span class="pun">,</span><span class="pln"> build</span><span class="pun">.</span><span class="typ">Time</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"build.User:\t"</span><span class="pun">,</span><span class="pln"> build</span><span class="pun">.</span><span class="typ">User</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	استوردنا الحزمة app/build، ثم طبعنا قيمة <code>build.Time</code> و <code>build.User</code> بنفس الطريقة التي طبعنا فيها <code>Version</code> سابقًا. احفظ وأغلِق الملف.
</p>

<p>
	إذا أردت الآن الوصول إلى هذه المتغيرات عند استخدام الراية <code>ldflags-</code>، يمكنك استخدام اسم الحزمة app/build يتبعها <code>Time.</code> أو <code>User.</code> كوننا نعرف مسار الحزمة. سنستخدم الأمر <code>nm</code> بدلًا من ذلك من أجل محاكاة موقف أكثر تعقيدًا، والذي يكون فيه مسار الحزمة غير واضح.
</p>

<p>
	يُنتج الأمر <code>go tool nm</code> الرموز المتضمنة في ملف تنفيذي أو ملف كائن أو أرشيف، إذ يشير الرمز إلى كائن موجود في الشيفرة (متغير أو دالة مُعرّفة أو مستوردة). يمكنك العثور بسرعة على معلومات المسار من خلال إنشاء جدول رموز باستخدام <code>nm</code> واستخدام <code>grep</code> للبحث عن متغير.
</p>

<p>
	<strong>ملاحظة:</strong> لن يساعدك الأمر <code>nm</code> في العثور على مسار المتغير إذا كان اسم الحزمة يحتوي على أي محارف ليست <a href="https://wiki.hsoub.com/Arduino/asciichart" rel="external">ASCII</a>، أو " أو ٪ (هذه قيود خاصة بالأداة).
</p>

<p>
	ابنِ التطبيق أولًا لاستخدام هذا الأمر:
</p>

<pre class="ipsCode">$ go build
</pre>

<p>
	وجّه الأداة <code>nm</code> إلى التطبيق بعد بنائه وابحث في الخرج:
</p>

<pre class="ipsCode">$ go tool nm ./app | grep app
</pre>

<p>
	عند تشغيل الأمر <code>nm</code> ستحصل على العديد من البيانات، لذا وضعنا <code>|</code> لتوجيه الخرج إلى الأمر <code>grep</code> الذي يبحث بعد ذلك عن المسارات التي تحتوي على الاسم <code>app</code> في المستوى الأعلى منها. ستحصل على الخرج التالي:
</p>

<pre class="ipsCode"> 55d2c0 D app/build.Time
  55d2d0 D app/build.User
  4069a0 T runtime.appendIntStr
  462580 T strconv.appendEscapedRune
. . .
</pre>

<p>
	يظهر في أول سطرين مسارات المتغيرات التي تبحث عنها: <code>app/build.Time</code> و <code>app/build.User</code>.
</p>

<p>
	ابنِ التطبيق الآن بعد أن تعرفت على المسارات، وعدّل متغير الإصدار إضافةً إلى المتغيرات الجديدة التي أضفناها والتي تُمثّل وقت بناء التطبيق واسم المستخدم (تذكر أنك تُعدّل هذه المتغيرات في وقت البناء). لأجل ذلك ستحتاج إلى تمرير عدة رايات <code>X-</code> إلى الراية <code>ldflags-</code>:
</p>

<pre class="ipsCode">$ go build -v -ldflags="-X 'main.Version=v1.0.0' -X 'app/build.User=$(id -u -n)' -X 'app/build.Time=$(date)'"
</pre>

<p>
	هنا مررنا الأمر id -u -n لعرض المستخدم الحالي، والأمر date لعرض التاريخ الحالي.
</p>

<p>
	شغّل التطبيق بعد بنائه:
</p>

<pre class="ipsCode">$ ./app
</pre>

<p>
	ستحصل على الخرج التالي في حال كنت تعمل على نظام يستند إلى يونيكس Unix:
</p>

<pre class="ipsCode">Version:     v1.0.0
build.Time:     Fri Oct  4 19:49:19 UTC 2019
build.User:     sammy
</pre>

<p>
	لديك الآن ملف تنفيذي يتضمن معلومات الإصدار والبناء، والتي يمكن أن توفر مساعدة حيوية في الإنتاج عند حل المشكلات.
</p>

<h2>
	الخاتمة
</h2>

<p>
	وضّحت هذه المقالة مدى قوة استخدام <code>ldflags</code> لإدخال معلومات في وقت البناء إذا طُبقت بطريقة سليمة. يمكنك بهذه الطريقة التحكم في رايات الميزة feature flags (هي تقنية برمجية تُمكّن الفريق البرمجي من إجراء تغييرات بدون استخدام المزيد من التعليمات البرمجية) ومعلومات البيئة ومعلومات الإصدار والأمور الأخرى دون إدخال تغييرات على الشيفرة المصدر. يمكنك الاستفادة من الخصائص التي تمنحك إياها لغة جو لعمليات النشر من خلال استخدام <code>ldflags</code> في عمليات البناء.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/using-ldflags-to-set-version-information-for-go-applications" rel="external nofollow">Using ldflags to Set Version Information for Go Applications</a> لصاحبه Gopher Guides.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-go-%D8%B9%D9%84%D9%89-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AE%D8%AA%D9%84%D9%81%D8%A9-r2022/" rel="">بناء تطبيقات لغة Go على أنظمة التشغيل والمعماريات المختلفة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%B3%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%AA%D8%AE%D8%B5%D9%8A%D8%B5-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D9%86%D9%81%D9%8A%D8%B0%D9%8A%D8%A9-binaries-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1970/" rel="">استخدام وسوم البناء لتخصيص الملفات التنفيذية Binaries في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D9%88%D8%A8%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-%D9%88%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87%D8%A7-r1996/" rel="">بناء البرامج المكتوبة بلغة جو Go وتثبيتها</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2023</guid><pubDate>Thu, 20 Jul 2023 13:00:00 +0000</pubDate></item><item><title>&#x628;&#x646;&#x627;&#x621; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x644;&#x63A;&#x629; Go &#x639;&#x644;&#x649; &#x623;&#x646;&#x638;&#x645;&#x629; &#x627;&#x644;&#x62A;&#x634;&#x63A;&#x64A;&#x644; &#x648;&#x627;&#x644;&#x645;&#x639;&#x645;&#x627;&#x631;&#x64A;&#x627;&#x62A; &#x627;&#x644;&#x645;&#x62E;&#x62A;&#x644;&#x641;&#x629;</title><link>https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-go-%D8%B9%D9%84%D9%89-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AE%D8%AA%D9%84%D9%81%D8%A9-r2022/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_07/----Go---.png.3a3fda7223288b702492c9998a16ff56.png" /></p>
<p>
	عند تطوير البرمجيات من المهم الأخذ بالحسبان نوع نظام التشغيل الذي تبني التطبيق عليه والمعمارية التي ستُصرّف تطبيقك من أجلها إلى ملف ثنائي تنفيذي Binary، وتكون عملية تشغيل التطبيق على نظام تشغيل مختلف أو معمارية مختلفة غالبًا عمليةً بطيئة أو مستحيلة أحيانًا، لذا من الممارسات العمليّة الشائعة بناء ملف تنفيذي يعمل على العديد من المنصات المختلفة، وذلك لزيادة شعبية وعدد مستخدمي تطبيقك، إلا أن ذلك غالبًا ما يكون صعبًا عندما تختلف المنصة التي تطوّر تطبيقك عليها عن المنصة التي تريد نشره عليها؛ إذ كان يتطلب مثلًا تطوير برنامج على ويندوز ونشره على لينوكس Linux أو ماك أو إس MacOS سابقًا إعدادات بناء مُحددة من أجل كل بيئة تُريد تصريف البرنامج من أجلها، ويتطلب الأمر أيضًا الحفاظ على مزامنة الأدوات، إضافةً إلى الاعتبارات الأخرى التي قد تضيف تكلفةً وتجعل الاختبار التعاوني Collaborative Testing والنشر أكثر صعوبة.
</p>

<p>
	تحل لغة جو هذه المشكلة عن طريق بناء دعم لمنصّات متعددة مباشرةً من خلال الأداة <code>go build</code> وبقية أدوات اللغة. يمكنك باستخدام <a href="https://academy.hsoub.com/devops/linux/%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D9%88%D8%B6%D8%A8%D8%B7-%D9%85%D8%AA%D8%BA%D9%8A%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%B5%D8%AF%D9%81%D8%A9-shell-%D9%88%D8%A7%D9%84%D8%A8%D9%8A%D8%A6%D8%A9-%D9%81%D9%8A-%D9%84%D9%8A%D9%86%D9%83%D8%B3-r697/" rel="">متغيرات البيئة</a> و<a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%B3%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%AA%D8%AE%D8%B5%D9%8A%D8%B5-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D9%86%D9%81%D9%8A%D8%B0%D9%8A%D8%A9-binaries-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1970/" rel="">وسوم البناء</a> التحكم في نظام التشغيل والمعمارية التي بُنى الثنائي النهائي من أجلها، إضافةً إلى وضع مُخطط لسير العمل يمكن من خلاله التبديل إلى التعليمات البرمجية المُضمّنة والمتوافقة مع المنصة المطلوب تشغيل التطبيق عليها دون تغيير الشيفرة البرمجية الأساسية.
</p>

<p>
	ستُنشئ في هذا المقال تطبيقًا يربط السلاسل النصية مع بعضها في مسار ملف، إضافةً إلى كتابة شيفرات برمجية يمكن تضمينها اختياريًّا، يعتمد كلٌ منها على منصة مُحددة. ستُنشئ ملفات تنفيذية لأنظمة تشغيل ومعماريات مختلفة على نظامك الخاص، وسيُبيّن لك ذلك كم أن لغة جو قوية في هذا الجانب.
</p>

<p>
	<strong>ملاحظة:</strong> في تكنولوجيا المعلومات، المنصة هي أي <a href="https://academy.hsoub.com/apps/general/%D8%A7%D8%AE%D8%AA%D9%8A%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D8%AA%D8%A7%D8%AF-%D9%88%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D9%81%D9%8A-%D8%A7%D9%84%D8%B9%D8%A7%D9%84%D9%85-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A-r372/" rel="">عتاد Hardware</a> أو <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA/" rel="">برمجية Software</a> تُستخدم لاستضافة تطبيق أو خدمة. على سبيل المثال قد تتكون من عتاديات ونظام تشغيل وبرامج أخرى تستخدم مجموعة التعليمات الخاصة بالمعالج.
</p>

<h2>
	المتطلبات
</h2>

<ul>
	<li>
		تفترض هذه المقالة أنك على دراية بوسوم البناء في لغة جو، وإذا لم يكن لديك معرفةً بها، راجع مقالة <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%B3%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%AA%D8%AE%D8%B5%D9%8A%D8%B5-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D9%86%D9%81%D9%8A%D8%B0%D9%8A%D8%A9-binaries-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1970/" rel="">استخدام وسوم البناء لتخصيص الملفات التنفيذية Binaries</a>.
	</li>
	<li>
		أن يكون لديك مساحة عمل خاصة في لغة جو، وكنا قد تحدّثنا عن ذلك في بداية السلسلة في المقال <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a>، و<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS</a>، و<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
</ul>

<h2>
	المنصات التي يمكن أن تبني لها تطبيقك باستخدام لغة جو
</h2>

<p>
	قبل أن نعرض كيف يمكننا التحكم بعملية البناء من أجل بناء ملفات تنفيذية تتوافق مع منصات مختلفة، سنستعرض أولًا أنواع المنصات التي يمكن لجو البناء من أجلها، وكيف تشير جو إلى هذه المنصات باستخدام متغيرات البيئة <code>GOOS</code> و <code>GOARCH</code>.
</p>

<p>
	يمكن عرض قائمة بأسماء المنصات التي يمكن لجو أن تبني تطبيقًا من أجلها، وتختلف هذه القائمة من إصدار لآخر، لذا قد تكون القائمة التي سنعرضها عليك مختلفةً عن القائمة التي تظهر عندك وذلك تبعًا لإصدار جو الذي تعمل عليه (الإصدار الحالي 1.13).
</p>

<p>
	نفّذ الأمر التالي لعرض القائمة:
</p>

<pre class="ipsCode">$ go tool dist list
</pre>

<p>
	ستحصل على الخرج التالي:
</p>

<pre class="ipsCode">aix/ppc64        freebsd/amd64   linux/mipsle   openbsd/386
android/386      freebsd/arm     linux/ppc64    openbsd/amd64
android/amd64    illumos/amd64   linux/ppc64le  openbsd/arm
android/arm      js/wasm         linux/s390x    openbsd/arm64
android/arm64    linux/386       nacl/386       plan9/386
darwin/386       linux/amd64     nacl/amd64p32  plan9/amd64
darwin/amd64     linux/arm       nacl/arm       plan9/arm
darwin/arm       linux/arm64     netbsd/386     solaris/amd64
darwin/arm64     linux/mips      netbsd/amd64   windows/386
dragonfly/amd64  linux/mips64    netbsd/arm     windows/amd64
freebsd/386      linux/mips64le  netbsd/arm64   windows/arm
</pre>

<p>
	نلاحظ أن الخرج هو مجموعة من أزواج المفتاح والقيمة key-value مفصولة بالرمز "/"؛ إذ يمثّل المفتاح key (على اليسار) <a href="https://academy.hsoub.com/apps/operating-systems/%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84/" rel="">نظام التشغيل</a>. تُعد هذه الأنظمة قيمًا محتملة لمتغير البيئة <code>GOOS</code> (يُنطق "goose")، اختصارًا إلى Go Operating System؛ بينما تشير القيمة value (على اليمين) إلى المعمارية، وتمثّل القيم المُحتملة لمتغير البيئة <code>GOARCH</code> (تُنطق "gore-ch") وهي اختصار Go Architecture.
</p>

<p>
	دعنا نأخذ مثال linux/386 لفهم ما تعنيه وكيف يجري الأمر: تُمثّل linux المفتاح وهي قيمة المتغير <code>GOOS</code>، بينما تمثّل 386 المعالج Intel 80386 والتي ستكون هي القيمة، وتُمثّل قيمة المتغير <code>GOARCH</code>.
</p>

<div class="banner-container ipsBox ipsPadding">
	<div class="inner-banner-container">
		<p class="banner-heading">
			دورة تطوير التطبيقات باستخدام لغة Python
		</p>

		<p class="banner-subtitle">
			احترف تطوير التطبيقات مع أكاديمية حسوب والتحق بسوق العمل فور انتهائك من الدورة
		</p>

		<div>
			<a class="ipsButton ipsButton_large ipsButton_primary ipsButton_important" href="https://academy.hsoub.com/learn/python-application-development" rel="">اشترك الآن</a>
		</div>
	</div>

	<div class="banner-img">
		<img alt="دورة تطوير التطبيقات باستخدام لغة Python" src="https://academy.hsoub.com/learn/assets/images/courses/python-application-development.png">
	</div>
</div>

<p>
	هناك العديد من المنصات التي يمكن أن تتعامل معها من خلال الأداة <code>go build</code>، لكن غالبًا سيكون تعاملك مع منصة لينكس أو ويندوز أو داروين darwin في قيم المتغير <code>GOOS</code>. إذًا، هذا يُغطي المنصات الثلاثة الأكبر: ويندوز ولينكس وماك، إذ يعتمد الأخير على نظام التشغيل داروين. يمكن لجو عمومًا تغطية المنصات الأقل شهرة مثل nacl.
</p>

<p>
	عند تشغيل أمر مثل <code>go build</code>، يستخدم جو مُتغيرات البيئة <code>GOOS</code> و <code>GOARCH</code> المرتبطين بالمنصة الحالية، لتحديد كيفية بناء الثنائي. لمعرفة تركيبة المفتاح-قيمة للمنصة التي تعمل عليها، يمكنك استخدام الأمر <code>go env</code> وتمرير <code>GOOS</code> و <code>GOARCH</code> مثل وسيطين:
</p>

<pre class="ipsCode">$ go env GOOS GOARCH
</pre>

<p>
	يعمل الجهاز الذي نستخدمه بنظام ماك ومعمارية AMD64 لذا سيكون الخرج:
</p>

<pre class="ipsCode">darwin
amd64
</pre>

<p>
	أي أن منصتنا لديها القيم التالية لمتغيرات البيئة <code>GOOS=darwin</code> و <code>GOARCH=amd64</code>.
</p>

<p>
	أنت الآن تعرف ما هي <code>GOOS</code> و <code>GOARCH</code>، إضافةً إلى قيمهما المحتملة. سنكتب الآن برنامجًا لاستخدامه مثالًا على كيفية استخدام متغيرات البيئة هذه ووسوم البناء، بهدف بناء ملفات تنفيذية لمنصات أخرى.
</p>

<h2>
	بناء تطبيق يعتمد على المنصة
</h2>

<p>
	سنبدأ أولًا ببناء برنامج بسيط، ومن الأمثلة الجيدة لهذا الغرض الدالة <code>Join</code> من الحزمة <code>path/filepath</code> من مكتبة جو القياسية، إذ تأخذ هذه الدالة عدة سلاسل وتُرجع سلسلةً مكونةً من تلك السلاسل بعد ربطها اعتمادًا على فاصل مسار الملف filepath.
</p>

<p>
	يُعد هذا المثال التوضيحي مناسبًا لأن تشغيل البرنامج يعتمد على نظام التشغيل الذي يعمل عليه، ففي <a href="https://academy.hsoub.com/apps/operating-systems/windows/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-11-%D9%88%D8%B7%D8%B1%D9%8A%D9%82%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87-r704/" rel="">نظام التشغيل ويندوز</a>، يكون فاصل المسار <code>\</code>، بينما تستخدم الأنظمة المستندة إلى يونيكس Unix الفاصل <code>/</code>.
</p>

<p>
	سنبدأ ببناء تطبيق يستخدم <code>()filepath.Join</code>، وسنكتب لاحقًا تنفيذًا خاصًا لهذه الدالة يُخصص الشيفرة للثنائيات التي تتبع لمنصة محددة.
</p>

<p>
	أنشئ مجلدًا داخل المجلد src باسم تطبيقك:
</p>

<pre class="ipsCode">$ mkdir app
</pre>

<p>
	انتقل إلى المجلد:
</p>

<pre class="ipsCode">$ cd app
</pre>

<p>
	أنشئ ملفًا باسم main.go من خلال محرر النصوص نانو nano أو أي محرر آخر:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	ضع في الملف الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2715_8" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
  </span><span class="str">"fmt"</span><span class="pln">
  </span><span class="str">"path/filepath"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  s </span><span class="pun">:=</span><span class="pln"> filepath</span><span class="pun">.</span><span class="typ">Join</span><span class="pun">(</span><span class="str">"a"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"b"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"c"</span><span class="pun">)</span><span class="pln">
  fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">s</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تستخدم الدالة الرئيسية <code>()main</code> هنا الدالة <code>()filepath.Join</code> لربط ثلاث سلاسل مع فاصل المسار الصحيح المعتمد على المنصة.
</p>

<p>
	احفظ الملف واخرج منه، ثم نفّذه من خلال الأمر التالي:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	عند تشغيل هذا البرنامج، ستتلقى مخرجات مختلفة بناءً على المنصة التي تستخدمها، ففي نظام التشغيل ويندوز، سترى السلاسل مفصولة بالفاصل <code>\</code>:
</p>

<pre class="ipsCode">a\b\c
</pre>

<p>
	أما في أنظمة يونِكس مثل ماك ولِنُكس:
</p>

<pre class="ipsCode">a/b/c
</pre>

<p>
	يوضح هذا أن اختلاف بروتوكولات نظام الملفات المستخدمة في أنظمة التشغيل هذه، يقتضي على البرنامج بناء شيفرات مختلفة للمنصات المختلفة. نحن نعلم أن الاختلاف هنا سيكون بفاصل الملفات كما تحدثنا، وبما أننا نستخدم الدالة <code>()filepath.Join</code> فلا خوف من ذلك، لأنها ستأخذ بالحسبان اختلاف نظام التشغيل الذي تُستخدم ضمنه. تفحص سلسلة أدوات جو تلقائيًا <code>GOOS</code> و <code>GOARCH</code> في جهازك وتستخدم هذه المعلومات لاستخدام الشيفرة المناسبة مع وسوم البناء الصحيحة وفاصل الملفات المناسب.
</p>

<p>
	سنرى الآن من أين تحصل الدالة <code>()filepath.Join</code> على الفاصل المناسب. شغّل الأمر التالي لفحص المقتطف ذي الصلة من مكتبة جو القياسية:
</p>

<pre class="ipsCode">$ less /usr/local/go/src/os/path_unix.go
</pre>

<p>
	سيُظهر هذا الأمر محتويات الملف "path_unix.go". ألقِ نظرةً على السطر الأول منه، والذي يُمثّل وسوم البناء.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2715_10" style=""><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="com">// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris</span><span class="pln">

package os

</span><span class="kwd">const</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
  </span><span class="typ">PathSeparator</span><span class="pln">     </span><span class="pun">=</span><span class="pln"> </span><span class="str">'/'</span><span class="pln"> </span><span class="com">// OS-specific path separator</span><span class="pln">
  </span><span class="typ">PathListSeparator</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">':'</span><span class="pln"> </span><span class="com">// OS-specific path list separator</span><span class="pln">
</span><span class="pun">)</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	تعرّف الشيفرة الفاصل <code>PathSeparator</code> المستخدم مع الأنواع المختلفة من الأنظمة التي تستند على يُنِكس والتي يدعمها جو. لاحظ في السطر الأول وسوم البناء التي تمثل كل واحدة منها قيمةً محتملةً للمتغير <code>GOOS</code> وهي جميعها تمثل أنظمة تستند إلى ينكس. يأخذ <code>GOOS</code> أحد هذه القيم ويُنتج الفاصل المناسب تبعًا لنوع النظام.
</p>

<p>
	اضغط <code>q</code> للعودة لسطر الأوامر. افتح الآن ملف path_windows الذي يُعبر عن سلوك الدالة <code>()filepath.Join</code> على نظام ويندوز:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2715_12" style=""><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
package os

</span><span class="kwd">const</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
        </span><span class="typ">PathSeparator</span><span class="pln">   </span><span class="pun">=</span><span class="pln"> </span><span class="str">'\\'</span><span class="pln"> </span><span class="com">// OS-specific path separator</span><span class="pln">
        </span><span class="typ">PathListSeparator</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">';'</span><span class="pln">  </span><span class="com">// OS-specific path list separator</span><span class="pln">
</span><span class="pun">)</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	على الرغم من أن قيمة <code>PathSeparator</code> هنا هي <code>\\</code>، إلا أن الشيفرة ستعرض الشرطة المائلة الخلفية المفردة <code>\</code> اللازمة لمسارات ملفات ويندوز، إذ تُستخدم الشرطة المائلة الأولى بمثابة مفتاح هروب Escape character.
</p>

<p>
	لاحظ أنه في الملف الخاص بينكس كان لدينا وسوم بناء، أما في ملف ويندوز فلا يوجد وسوم بناء، وذلك لأن <code>GOOS</code> و <code>GOARCH</code> يمكن تمريرهما أيضًا إلى <code>go build</code> عن طريق إضافة شرطة سفلية "_" وقيمة متغير البيئة مثل لاحقة suffix لاسم الملف (سنتحدث عن ذلك أكثر بعد قليل).
</p>

<p>
	يجعل الجزء <code>windows_</code> من path_windows.go الملف يعمل كما لو كان يحتوي على وسم البناء <code>build windows+ //</code> في أعلى الملف، لذلك عند تشغيل البرنامج على ويندوز، سيستخدم الثوابت <code>PathSeparator</code> و <code>PathListSeparator</code> من الشيفرة الموجودة في الملف "path_windows.go". اضغط <code>q</code> للعودة لسطر الأوامر.
</p>

<p>
	أنشأت في هذه الخطوة برنامجًا يوضّح كيف يمكن لجو أن يجري التبديل تلقائيًا بين الشيفرات من خلال متغيرات البيئة <code>GOOS</code> و <code>GOARCH</code> ووسوم البناء. يمكنك الآن تحديث برنامجك وكتابة تنفيذك الخاص للدالة <code>()filepath.Join</code>، والاعتماد على وسوم البناء لتحديد الفاصل <code>PathSeparator</code> المناسب لمنصات ويندوز ويونيكس يدويًا.
</p>

<h2>
	تنفيذ دالة خاصة بالمنصة
</h2>

<p>
	بعد أن تعرّفت على كيفية تحقيق مكتبة جو القياسية للتعليمات البرمجية الخاصة بالمنصة، يمكنك استخدام وسوم البناء لأجل ذلك في تطبيقك. ستكتب الآن تعريفًا خاصًا للدالة <code>()filepath.Join</code>. افتح ملف main.go الخاص بتطبيقك:
</p>

<pre class="ipsCode">$ nano main.go
</pre>

<p>
	استبدل محتويات main.go وضع فيه الشيفرة التالية التي تتضمن دالة خاصة اسميناها <code>Join</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2715_14" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
  </span><span class="str">"fmt"</span><span class="pln">
  </span><span class="str">"strings"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

func </span><span class="typ">Join</span><span class="pun">(</span><span class="pln">parts </span><span class="pun">...</span><span class="pln">string</span><span class="pun">)</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> strings</span><span class="pun">.</span><span class="typ">Join</span><span class="pun">(</span><span class="pln">parts</span><span class="pun">,</span><span class="pln"> </span><span class="typ">PathSeparator</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  s </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Join</span><span class="pun">(</span><span class="str">"a"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"b"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"c"</span><span class="pun">)</span><span class="pln">
  fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">s</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تأخذ الدالة <code>Join</code> عدة سلاسل نصية من خلال المعامل <code>parts</code> وتربطهما معًا باستخدام الفاصل <code>PathSeparator</code>، وذلك من خلال الدالة <code>()strings.Join</code> من حزمة <code>strings</code>. لم نُعرّف <code>PathSeparator</code> بعد، لذا سنُعرّفه الآن في ملف آخر. احفظ main.go واخرج منه، وافتح المحرر المفضل لديك، وأنشئ ملفًا جديدًا باسم path.go:
</p>

<pre class="ipsCode">nano path.go
</pre>

<p>
	صرّح عن الثابت <code>PathSeparator</code> وأسند له فاصل المسارات الخاص بملفات يونيكس "/":
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2715_16" style=""><span class="pln">package main

</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">PathSeparator</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"/"</span></pre>

<p>
	صرّف التطبيق وشغّله:
</p>

<pre class="ipsCode">$ go build
$ ./app
</pre>

<p>
	ستحصل على الخرج التالي:
</p>

<pre class="ipsCode">a/b/c
</pre>

<p>
	هذا جيد بالنسبة لأنظمة ينكس، لكنه ليس ما نريده تمامًا، فهو يُعطي دومًا <code>a/b/c</code> بغض النظر عن المنصة، وهذا لا يتناسب مع ويندوز مثلًا. إذًا، نحن بحاجة إلى نسخة خاصة من <code>PathSeparator</code> لنظام ويندوز، وإخبار <code>go build</code> أي من هذه النسخ يجب استخدامها وفقًا للمنصة المطلوبة. هنا يأتي دور وسوم البناء.
</p>

<h2>
	استخدام وسوم البناء مع متغيرات البيئة
</h2>

<p>
	لكي نعالج حالة كون النظام هو ويندوز، سنُنشئ الآن ملفًا بديلًا للملف path.go وسنضيف وسوم بناء Build Tags مهمتها المطابقة مع المتغيرات <code>GOOS</code> و <code>GOARCH</code>، وذلك لضمان أن الشيفرة المستخدمة هي الشيفرة التي تعمل على المنصة المحددة.
</p>

<p>
	أضف بدايةً وسم بناء إلى الملف "path.go" لإخباره أن يبني لكل شيء باستثناء ويندوز، افتح الملف:
</p>

<pre class="ipsCode">$ nano path.go
</pre>

<p>
	أضِف وسم البناء التالي إلى الملف:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2715_18" style=""><span class="com">// +build !windows</span><span class="pln">
package main
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">PathSeparator</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"/"</span></pre>

<p>
	تُقدّم وسوم البناء في لغة جو إمكانية "العكس inverting" مما يعني أنه يمكنك توجيه جو لبناء هذا الملف من أجل أي منصة باستثناء ويندوز. لعكس وسم بناء، ضع "!" قبل الوسم كما فعلنا أعلاه.
</p>

<p>
	احفظ واخرج من الملف.
</p>

<p>
	والآن إذا حاولت تشغيل هذا البرنامج على ويندوز، ستتلقى الخطأ التالي:
</p>

<pre class="ipsCode">./main.go:9:29: undefined: PathSeparator
</pre>

<p>
	في هذه الحالة لن تكون جو قادرةً على تضمين path.go لتعريف فاصل المسار <code>PathSeparator</code>.
</p>

<p>
	الآن بعد أن تأكدت من أن path.go لن يعمل عندما يكون <code>GOOS</code> هو ويندوز. أنشئ ملفًا جديدًا windows.go:
</p>

<pre class="ipsCode">$ nano windows.go
</pre>

<p>
	أضِف ضمن هذا الملف <code>PathSeparator</code> ووسم بناء أيضًا لإخبار الأمر <code>go build</code> أن هذا الملف هو التحقيق المقابل للويندوز:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2715_20" style=""><span class="com">// +build windows</span><span class="pln">
package main
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">PathSeparator</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\\"</span></pre>

<p>
	احفظ الملف واخرج من محرر النصوص. يمكن للتطبيق الآن تصريف نسخة لنظام ويندوز ونسخة أخرى لباقي الأنظمة. ستُبنى الآن ملفات تنفيذية بطريقة صحيحة وفقًا للمنصة المطلوبة، إلا أنه هناك المزيد من التغييرات التي يجب عليك إجراؤها من أجل التصريف على المنصة التي لا يمكنك الوصول إليها.
</p>

<p>
	سنُعدّل في الخطوة التالية متغيرات البيئة المحلية <code>GOOS</code> و <code>GOARCH</code>.
</p>

<h2>
	استخدام متغيرات البيئة المحلية GOOS و GOARCH
</h2>

<p>
	استخدمنا سابقًا الأمر <code>go env GOOS GOARCH</code> لمعرفة النظام والمعمارية التي تعمل عليها. عند استخدام الأمر <code>go env</code>، سيبحث عن المتغيرين <code>GOOS</code> و <code>GOARCH</code>، فإذا وجدهما سيستخدم قيمهما وإلا سيفترض أن قيمهما هي معلومات المنصة الحالية (نظام ومعمارية المنصة). نستنتج مما سبق أنه بإمكاننا تحديد نظام ومعمارية لمتغيرات البيئة غير نظام ومعمارية المنصة الحالية. يعمل الأمر <code>go build</code> بطريقة مشابهة للأمر السابق، إذ يمكنك تحديد قيم لمتغيرات البيئة <code>GOOS</code> و <code>GOARCH</code> مختلفة عن المنصة الحالية.
</p>

<p>
	إذا كنت لا تستخدم نظام ويندوز، ابنِ نسخةً ثنائيةً من تطبيقك لنظام ويندوز عن طريق تعيين قيمة متغير البيئة <code>GOOS</code> على <code>windows</code> عند تشغيل الأمر <code>go build</code>:
</p>

<pre class="ipsCode">$ GOOS=windows go build
</pre>

<p>
	اسرد الآن محتويات مجلدك الحالي:
</p>

<pre class="ipsCode">$ ls
</pre>

<p>
	ستجد في الخرج ملفًا باسم app.exe، إذ يكون امتداده exe والذي يشير إلى ملف ثنائي تنفيذي في نظام ويندوز.
</p>

<pre class="ipsCode">app  app.exe  main.go  path.go  windows.go
</pre>

<p>
	يمكنك باستخدام الأمر <code>file</code> الحصول على مزيد من المعلومات حول هذا الملف، للتأكد من بنائه:
</p>

<pre class="ipsCode">$ file app.exe
</pre>

<p>
	ستحصل على:
</p>

<pre class="ipsCode">app.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows
</pre>

<p>
	يمكنك أيضًا إعداد واحد أو اثنين من متغيرات البيئة أثناء وقت البناء. نفّذ الأمر التالي:
</p>

<pre class="ipsCode">$ GOOS=linux GOARCH=ppc64 go build
</pre>

<p>
	بذلك يكون قد استُبدل ملفك التنفيذي app بملف لمعمارية مختلفة. شغّل الأمر <code>file</code> على تطبيقك الثنائي:
</p>

<pre class="ipsCode">$ file app
</pre>

<p>
	ستحصل على الخرج التالي:
</p>

<pre class="ipsCode">app: ELF 64-bit MSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, not stripped
</pre>

<p>
	من خلال متغيرات البيئة <code>GOOS</code> و <code>GOARCH</code> يمكنك الآن إنشاء تطبيق قابل للتنفيذ على منصات مختلفة، دون الحاجة إلى إعدادات ضبط مُعقدة. سنستخدم في الخطوة التالية بعض "الاصطلاحات" لأسماء الملفات لكي نجعل تنسيقها دقيقًا، إضافةً إلى البناء تلقائيًّا من دون الحاجة إلى وسوم البناء.
</p>

<h2>
	استخدام لواحق اسم الملف مثل دليل إلى المنصة المطلوبة
</h2>

<p>
	كما لاحظت سابقًا، تعتمد جو على استخدام وسوم البناء كثيرًا لفصل الإصدارات أو النسخ المختلفة من التطبيق إلى ملفات مختلفة بغية تبسيط التعليمات البرمجية، فعندما فتحت ملف "os/path_unix.go" كان هناك وسم بناء يعرض جميع التركيبات المحتملة التي تُعبر عن منصات شبيهة أو تستند إلى ينكس، إلا أن ملف "os/path_windows.go" لا يحتوي على وسوم بناء، لأن لاحقة اسم الملف كانت كافية لإخبار جو بالمنصة المطلوبة.
</p>

<p>
	دعونا نلقي نظرةً على تركيب أو قاعدة هذه الميزة، فعند تسمية ملف امتداده "go."، يمكنك إضافة <code>GOOS</code> و <code>GOARCH</code> مثل لواحق إلى اسم الملف بالترتيب، مع فصل القيم عن طريق الشرطات السفلية "_". إذا كان لديك ملف جو يُسمى filename.go، يمكنك تحديد نظام التشغيل والمعمارية عن طريق تغيير اسم الملف إلى filename_GOOSGO_ARCH.go. على سبيل المثال، إذا كنت ترغب في تصريفه لنظام التشغيل ويندوز باستخدام معمارية ‎64-bit ARM، فيمكنك كتابة اسم الملف filename_windows_arm64.go، إذ يساعد اصطلاح التسمية هذا في الحفاظ على الشيفرة منظمةً بدقة.
</p>

<p>
	حدّث البرنامج الآن بحيث نستخدم لاحقات اسم الملف بدلًا من وسوم البناء، وأعد تسمية ملف "path.go" و "windows.go" لاستخدام الاصطلاح المستخدم في حزمة<code>os</code>:
</p>

<pre class="ipsCode">$ mv path.go path_unix.go
$ mv windows.go path_windows.go
</pre>

<p>
	بعد تعديل اسم الملف أصبح بإمكانك حذف وسم البناء من ملف "path_windows.go":
</p>

<pre class="ipsCode">$ nano path_windows.go
</pre>

<p>
	بعد حذف <code>build windows+ //</code> سيكون ملفك كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2715_22" style=""><span class="pln">package main
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">PathSeparator</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\\"</span></pre>

<p>
	احفظ الملف واخرج منه.
</p>

<p>
	بما أن <code>unix</code> هي قيمة غير صالحة للمتغير <code>GOOS</code>، فإن اللاحقة unix.go_ ليس لها أي معنى لمُصرّف جو، وبالرغم من ذلك، فإنه ينقل الغاية المرجوة من الملف. لا يزال مثلًا ملف os/path_unix.go بحاجة إلى استخدام وسوم البناء، لذا يجب الاحتفاظ بهذا الملف دون تغيير.
</p>

<p>
	نستنتج أنه من خلال الاصطلاحات استطعنا التخلص من وسوم البناء غير الضرورية التي أضفناها إلى شيفرات التطبيق، كما جعلنا نظام الملفات أكثر تنظيمًا ووضوحًا.
</p>

<h2>
	الخاتمة
</h2>

<p>
	لا تحتاج لغة جو إلى أدوات إضافية لدعم فكرة المنصات المتعددة، وهذه ميزة قوية في جو.
</p>

<p>
	تعلمنا في هذه المقالة استخدام هذه الإمكانية عن طريق إضافة وسوم البناء واللواحق لاسم الملف، وذلك لتحديد الشيفرات البرمجية التي يجب تنفيذها وفقًا للمنصة المطلوب العمل عليه. أنشأنا تطبيقًا متعدد المنصات وتعلمنا كيفية التعامل مع متغيرات البيئة <code>GOOS</code> و <code>GOARCH</code> لبناء ملفات تنفيذية لمنصات أخرى تختلف عن المنصة الحالية. تعدد المنصات أمر مهم جدًا، فهو يُضيف ميزةً مهمة لتطبيقك، إذ تُمكنه من التصريف وفقًا للمنصة المطلوبة من خلال متغيرات البيئة هذه.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/building-go-applications-for-different-operating-systems-and-architectures" rel="external nofollow">Building Go Applications for Different Operating Systems and Architectures</a> لصاحبه Gopher Guides.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-interfaces-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1999/" rel="">كيفية استخدام الواجهات Interfaces في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D9%88%D8%A8%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-%D9%88%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87%D8%A7-r1996/" rel="">بناء البرامج المكتوبة بلغة جو Go وتثبيتها</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%88-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%8A%D9%86%D9%83%D8%B3%D8%9F-r451/" rel="">ما هو نظام التشغيل لينكس؟</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">2022</guid><pubDate>Fri, 07 Jul 2023 13:00:00 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x648;&#x627;&#x62C;&#x647;&#x627;&#x62A; Interfaces &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648; Go</title><link>https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-interfaces-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1999/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_06/------Go.png.d35160608ad2e5eeee3db5eb8a632f7a.png" /></p>
<p>
	أهم الصفات التي يجب أن تتمتع بها البرامج أو التعليمات البرمجية التي نكتبها، هي المرونة وإمكانية إعادة الاستخدام، إضافةً إلى الصفة التركيبية modular، إذ تُعد هذه الصفات الثلاثة أمرًا ضروريًّا لتطوير برامج متعددة الاستخدامات، كما أنها تُسهّل عمليات التعديل والصيانة على البرامج، فمثلًا إذا احتجنا لتعديل بسيط على البرنامج، سيكون بالإمكان إجراء هذا التعديل في مكانٍ واحد بدلًا من إجراء نفس التعديل في أماكن متعددة من البرنامج.
</p>

<p>
	تختلف كيفية تحقيق تلك الصفات من لغة إلى أخرى، فعلى سبيل المثال، يٌستخدم مفهوم الوراثة في لغات مثل <a href="https://academy.hsoub.com/programming/java/" rel="">جافا Java</a> و <a href="https://academy.hsoub.com/programming/cpp/" rel="">++C</a> و <a href="https://academy.hsoub.com/programming/c-sharp/" rel="">#C</a> لتحقيق ذلك. يمكن للمطورين أيضًا تحقيق أهداف التصميم هذه من خلال التركيب Composition، وهي طريقة لدمج الكائنات أو أنواع البيانات في أنواع أكثر تعقيدًا، وهو النهج المُستخدم في لغة جو لتحقيق الصفات السابقة. توفِّر الواجهات في لغة جو توابعًا لتنظيم التراكيب المعقدة، وسيتيح لك تعلم كيفية استخدامها إنشاء شيفرة مشتركة وقابلة لإعادة الاستخدام.
</p>

<p>
	ستتعلم في هذا المقال كيفية تركيب أنواع مخصصة لها سلوكيات مشتركة، مما سيتيح لنا إعادة استخدام الشيفرة التي نكتبها، وسنتعلم أيضًا كيفية تحقيق الواجهات للأنواع المخصصة التي تتوافق مع الواجهات المُعرّفة في حزم أخرى.
</p>

<h2>
	تعريف سلوك Behavior
</h2>

<p>
	تُعد الواجهات من العناصر الأساسية للتركيب، فهي تعرّف سلوك نوعٍ ما، وتُعد الواجهة <code>fmt.Stringer</code> من أكثر الواجهات شيوعًا في مكتبة جو القياسية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_8" style=""><span class="pln">type </span><span class="typ">Stringer</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string
</span><span class="pun">}</span></pre>

<p>
	نُعرّف في السطر الأول من الشيفرة السابقة نوعًا جديدًا يُدعى <code>Stringer</code>، ونحدد أنه واجهة. بعد ذلك، نكتب محتويات هذه البنية بين قوسين <code>{}</code>، إذ ستُعرِّف هذه المحتويات سلوك الواجهة، أي ما الذي تفعله الواجهة؛ فبالنسبة للواجهة السابقة <code>Stringer</code>، من الواضح أن السلوك الوحيد فيها هو تابع <code>()String</code> لا يأخذ أي وسطاء ويعيد سلسلةً نصية.
</p>

<p>
	سنرى الآن بعض المقتطفات البرمجية التي تمتلك سلوك الواجهة <code>fmt.Stringer</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_10" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Article</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Title</span><span class="pln"> string
    </span><span class="typ">Author</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">a </span><span class="typ">Article</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"The %q article was written by %s."</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Title</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Author</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    a </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Article</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Title</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Understanding Interfaces in Go"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Author</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy Shark"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">a</span><span class="pun">.</span><span class="typ">String</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	هنا نُنشئ نوعًا جديدًا اسمه <code>Article</code>، ويمتلك حقلين هما <code>Title</code> و <code>Author</code>، وكلاهما من نوع سلاسل نصية.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_12" style=""><span class="pun">...</span><span class="pln">
type </span><span class="typ">Article</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Title</span><span class="pln"> string
    </span><span class="typ">Author</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">…</span></pre>

<p>
	نُعرّف بعد ذلك تابعًا يسمى <code>String</code> على النوع <code>Article</code>، بحيث يعيد هذا التابع سلسلةً تمثل هذا النوع، أي محتوياته عمليًّا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_16" style=""><span class="pun">...</span><span class="pln">
func </span><span class="pun">(</span><span class="pln">a </span><span class="typ">Article</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"The %q article was written by %s."</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Title</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Author</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	نُعرّف بعد ذلك في الدالة <code>main</code> متغيرًا من النوع <code>Article</code> ونسميه <code>a</code>، ونسند السلسلة "Understanding Interfaces in Go" إلى الحقل <code>Title</code> والسلسلة "Sammy Shark" للحقل <code>Author</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_14" style=""><span class="pun">...</span><span class="pln">
a </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Article</span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Title</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Understanding Interfaces in Go"</span><span class="pun">,</span><span class="pln">
    </span><span class="typ">Author</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy Shark"</span><span class="pun">,</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	نطبع بعد ذلك نتيجة التابع <code>String</code> من خلال استدعاء الدالة <code>fmt.Println</code> وتمرير نتيجة استدعاء التابع <code>()a.String</code> إليها:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_18" style=""><span class="pun">...</span><span class="pln">
fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">a</span><span class="pun">.</span><span class="typ">String</span><span class="pun">())</span></pre>

<p>
	ستحصل عند تشغيل البرنامج على:
</p>

<pre class="ipsCode">The "Understanding Interfaces in Go" article was written by Sammy Shark.
</pre>

<p>
	لم نستخدم واجهةً حتى الآن، لكننا أنشأنا نوعًا يمتلك سلوكًا يطابق سلوك الواجهة <code>fmt.Stringer</code>. دعنا نرى كيف يمكننا استخدام هذا السلوك لجعل الشيفرة الخاصة بنا أكثر قابلية لإعادة الاستخدام.
</p>

<h2>
	تعريف واجهة Interface
</h2>

<p>
	بعد أن عرّفنا نوعًا جديدًا مع سلوك مُحدد، سنرى كيف يمكننا استخدام هذا السلوك، لكن قبل ذلك سنلقي نظرةً على ما سنحتاج إلى فعله إذا أردنا استدعاء التابع <code>String</code> من نوع <code>Article</code> داخل دالة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_21" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Article</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Title</span><span class="pln"> string
    </span><span class="typ">Author</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">a </span><span class="typ">Article</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"The %q article was written by %s."</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Title</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Author</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    a </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Article</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Title</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Understanding Interfaces in Go"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Author</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy Shark"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">a</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">a </span><span class="typ">Article</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">a</span><span class="pun">.</span><span class="typ">String</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أضفنا في هذه الشيفرة دالةً جديدةً تسمى <code>Print</code>، تأخذ وسيطًا من النوع <code>Article</code>. لاحظ أن كل ما تفعله هذه الدالة هو أنها تستدعي التابع <code>String</code>، لذا يمكننا بدلًا من ذلك تعريف واجهة للتمرير إلى الدالة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_23" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Article</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Title</span><span class="pln"> string
    </span><span class="typ">Author</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">a </span><span class="typ">Article</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"The %q article was written by %s."</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Title</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Author</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">Stringer</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    a </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Article</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Title</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Understanding Interfaces in Go"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Author</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy Shark"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">a</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">s </span><span class="typ">Stringer</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">s</span><span class="pun">.</span><span class="typ">String</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ننشئ هنا واجهةً اسمها <code>Stringer</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_25" style=""><span class="pun">...</span><span class="pln">
type </span><span class="typ">Stringer</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	تتضمّن هذه الواجهة تابعًا وحيدًا يسمى <code>()String</code> يعيد سلسلةً، ويُعرّف هذا التابع على نوع بيانات مُحدد، وعلى عكس الدوال فلا يمكن له أن يُستدعى إلا من متغير من هذا النوع.
</p>

<p>
	نعدّل بعد ذلك بصمة الدالة <code>Print</code> بحيث تستقبل وسيطًا من النوع <code>Stringer</code> الذي يُمثّل واجهةً، وليس نوعًا مُحددًا مثل <code>Article</code>. بما أن المصرّف يعرف أن <code>Stringer</code> هي واجهة تمتلك التابع <code>String</code>، فلن يقبل إلا الأنواع التي تُحقق هذا التابع.
</p>

<p>
	يمكننا الآن استخدام الدالة <code>Print</code> مع أي نوع يتوافق مع الواجهة <code>Stringer</code>. دعنا ننشئ نوعًا آخر لتوضيح ذلك:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_27" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Article</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Title</span><span class="pln">  string
    </span><span class="typ">Author</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">a </span><span class="typ">Article</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"The %q article was written by %s."</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Title</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Author</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">Book</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Title</span><span class="pln">  string
    </span><span class="typ">Author</span><span class="pln"> string
    </span><span class="typ">Pages</span><span class="pln">  </span><span class="typ">int</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">b </span><span class="typ">Book</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"The %q book was written by %s."</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">.</span><span class="typ">Title</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">.</span><span class="typ">Author</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">Stringer</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    a </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Article</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Title</span><span class="pun">:</span><span class="pln">  </span><span class="str">"Understanding Interfaces in Go"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Author</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy Shark"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">a</span><span class="pun">)</span><span class="pln">

    b </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Book</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Title</span><span class="pun">:</span><span class="pln">  </span><span class="str">"All About Go"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Author</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Jenny Dolphin"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Pages</span><span class="pun">:</span><span class="pln">  </span><span class="lit">25</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">b</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">Print</span><span class="pun">(</span><span class="pln">s </span><span class="typ">Stringer</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">s</span><span class="pun">.</span><span class="typ">String</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عرّفنا هنا <a href="https://academy.hsoub.com/programming/general/%D8%AF%D9%84%D9%8A%D9%84%D9%83-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1726/" rel="">نوع بيانات</a> جديد يُسمى <code>Book</code> يمتلك التابع <code>String</code>، وبالتالي يُحقق الواجهة <code>Stringer</code>، وبالتالي يمكننا استخدامه مثل وسيط للدالة <code>Print</code>.
</p>

<pre class="ipsCode">The "Understanding Interfaces in Go" article was written by Sammy Shark.
The "All About Go" book was written by Jenny Dolphin. It has 25 pages.
</pre>

<p>
	أوضحنا إلى الآن كيفية استخدام واجهة واحدة فقط، ويمكن عمومًا أن يكون للواجهة أكثر من سلوك معرّف. سنرى فيما يلي كيف يمكننا جعل واجهاتنا أكثر تنوعًا من خلال التصريح عن المزيد من التوابع.
</p>

<h2>
	تعدد السلوكيات في الواجهة
</h2>

<p>
	تُعد كتابة أنواع صغيرة وموجزة وتركيبها في أنواع أكبر وأكثر تعقيدًا من الأمور الجيدة عند كتابة الشيفرات في لغة جو، وينطبق الشيء نفسه عند إنشاء واجهات. لمعرفة كيفية إنشاء الواجهة، سنبدأ أولًا بتعريف واجهة واحدة فقط. سنحدد شكلين؛ دائرة <code>Circle</code> ومربع <code>Square</code>، وسيُعرّف كلاهما تابعًا يُسمى المساحة <code>Area</code> يعيد المساحة الهندسية للشكل:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_29" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"math"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">Circle</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Radius</span><span class="pln"> </span><span class="typ">float64</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">c </span><span class="typ">Circle</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="typ">float64</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> math</span><span class="pun">.</span><span class="typ">Pi</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> math</span><span class="pun">.</span><span class="typ">Pow</span><span class="pun">(</span><span class="pln">c</span><span class="pun">.</span><span class="typ">Radius</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">Square</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Width</span><span class="pln">  </span><span class="typ">float64</span><span class="pln">
    </span><span class="typ">Height</span><span class="pln"> </span><span class="typ">float64</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">s </span><span class="typ">Square</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="typ">float64</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">Width</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">Height</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">Sizer</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="typ">float64</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    c </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Circle</span><span class="pun">{</span><span class="typ">Radius</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pun">}</span><span class="pln">
    s </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Square</span><span class="pun">{</span><span class="typ">Height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pun">}</span><span class="pln">

    l </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Less</span><span class="pun">(</span><span class="pln">c</span><span class="pun">,</span><span class="pln"> s</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%+v is the smallest\n"</span><span class="pun">,</span><span class="pln"> l</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">Less</span><span class="pun">(</span><span class="pln">s1</span><span class="pun">,</span><span class="pln"> s2 </span><span class="typ">Sizer</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Sizer</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> s1</span><span class="pun">.</span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> s2</span><span class="pun">.</span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> s1
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> s2
</span><span class="pun">}</span></pre>

<p>
	بما أن النوعين يُصرحان عن تابع <code>Area</code>، يمكننا إنشاء واجهة تحدد هذا السلوك. نُنشئ واجهة <code>Sizer</code> التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_31" style=""><span class="pun">...</span><span class="pln">
type </span><span class="typ">Sizer</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="typ">float64</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">…</span></pre>

<p>
	نعرّف بعد ذلك دالةً تسمى <code>Less</code> تأخذ واجهتين <code>Sizer</code> مثل وسيطين وتعيد أصغر واحدة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_33" style=""><span class="pun">...</span><span class="pln">
func </span><span class="typ">Less</span><span class="pun">(</span><span class="pln">s1</span><span class="pun">,</span><span class="pln"> s2 </span><span class="typ">Sizer</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Sizer</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> s1</span><span class="pun">.</span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> s2</span><span class="pun">.</span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> s1
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> s2
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	لاحظ أن معاملات الدالة وكذلك القيمة المُعادة هي من النوع <code>Sizer</code>، وهذا يعني أننا لا نعيد مربعًا أو دائرة، بل نعيد واجهة <code>Sizer</code>.
</p>

<p>
	والآن نطبع الوسيط الذي لديه أصغر مساحة:
</p>

<pre class="ipsCode">{Width:5 Height:10} is the smallest
</pre>

<p>
	سنضيف الآن سلوكًا آخر لكل نوع، وسنضيف التابع <code>()String</code> الذي يعيد سلسلةً نصيةً، وهذا بدوره سيؤدي إلى تحقيق الواجهة <code>fmt.Stringer</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_35" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"math"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">Circle</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Radius</span><span class="pln"> </span><span class="typ">float64</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">c </span><span class="typ">Circle</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="typ">float64</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> math</span><span class="pun">.</span><span class="typ">Pi</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> math</span><span class="pun">.</span><span class="typ">Pow</span><span class="pun">(</span><span class="pln">c</span><span class="pun">.</span><span class="typ">Radius</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">c </span><span class="typ">Circle</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"Circle {Radius: %.2f}"</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="typ">Radius</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">Square</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Width</span><span class="pln">  </span><span class="typ">float64</span><span class="pln">
    </span><span class="typ">Height</span><span class="pln"> </span><span class="typ">float64</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">s </span><span class="typ">Square</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="typ">float64</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">Width</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">Height</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">s </span><span class="typ">Square</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"Square {Width: %.2f, Height: %.2f}"</span><span class="pun">,</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">Width</span><span class="pun">,</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">Height</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">Sizer</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="typ">float64</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">Shaper</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Sizer</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Stringer</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    c </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Circle</span><span class="pun">{</span><span class="typ">Radius</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pun">}</span><span class="pln">
    </span><span class="typ">PrintArea</span><span class="pun">(</span><span class="pln">c</span><span class="pun">)</span><span class="pln">

    s </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Square</span><span class="pun">{</span><span class="typ">Height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pun">}</span><span class="pln">
    </span><span class="typ">PrintArea</span><span class="pun">(</span><span class="pln">s</span><span class="pun">)</span><span class="pln">

    l </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Less</span><span class="pun">(</span><span class="pln">c</span><span class="pun">,</span><span class="pln"> s</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%v is the smallest\n"</span><span class="pun">,</span><span class="pln"> l</span><span class="pun">)</span><span class="pln">

</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">Less</span><span class="pun">(</span><span class="pln">s1</span><span class="pun">,</span><span class="pln"> s2 </span><span class="typ">Sizer</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Sizer</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> s1</span><span class="pun">.</span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> s2</span><span class="pun">.</span><span class="typ">Area</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> s1
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> s2
</span><span class="pun">}</span><span class="pln">

func </span><span class="typ">PrintArea</span><span class="pun">(</span><span class="pln">s </span><span class="typ">Shaper</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"area of %s is %.2f\n"</span><span class="pun">,</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(),</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">Area</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بما أن النوعين <code>Circle</code> و <code>Square</code> ينفذّان التابعين <code>Area</code> و <code>String</code>، سيكون بالإمكان إنشاء واجهة أخرى لوصف تلك المجموعة الأوسع من السلوك. لأجل ذلك سننشئ واجهةً تسمى <code>Shaper</code> من واجهة <code>Sizer</code> وواجهة <code>fmt.Stringer</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_37" style=""><span class="pun">...</span><span class="pln">
type </span><span class="typ">Shaper</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Sizer</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Stringer</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	<strong>ملاحظة</strong>: حبذا أن ينتهي اسم الواجهة بالحرفين <code>er</code> مثل <code>fmt.Stringer</code> و <code>io.Writer</code>، إلخ. ولهذا السبب أطلقنا على واجهتنا اسم <code>Shaper</code>، وليس <code>Shape</code>.
</p>

<p>
	يمكننا الآن إنشاء دالة تسمى <code>PrintArea</code> تأخذ وسيطًا من النوع <code>Shaper</code>. هذا يعني أنه يمكننا استدعاء التابعين على القيمة التي تُمرّر لكل من <code>Area</code> و <code>String</code>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_913_39" style=""><span class="pun">...</span><span class="pln">
func </span><span class="typ">PrintArea</span><span class="pun">(</span><span class="pln">s </span><span class="typ">Shaper</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"area of %s is %.2f\n"</span><span class="pun">,</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">String</span><span class="pun">(),</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">Area</span><span class="pun">())</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ستحصل عند تشغيل البرنامج على الخرج التالي:
</p>

<pre class="ipsCode">area of Circle {Radius: 10.00} is 314.16
area of Square {Width: 5.00, Height: 10.00} is 50.00
Square {Width: 5.00, Height: 10.00} is the smallest
</pre>

<p>
	رأينا كيف يمكننا إنشاء واجهات أصغر وبناء واجهات أكبر حسب الحاجة. كان بإمكاننا البدء بالواجهة الأكبر وتمريرها إلى جميع الدوال، إلا أن الممارسات الجيدة تقتضي إرسال أصغر واجهة فقط إلى الدالة المطلوبة، إذ أن ذلك ضروري لجعل الشيفرة واضحة أكثر، فإذا كانت الدالة تهدف لإجراء سلوك محدد، وهذا السلوك مُعرّف في واجهة أصغر، فحبذا أن تُمرر الواجهة الأصغر وليس الواجهة الأكبر (التي تحتوي الواجهة الأصغر).
</p>

<p>
	إذا مررنا مثلًا الواجهة <code>Shaper</code> إلى الدالة <code>Less</code>، فهنا نفترض أنها ستستدعي كلًا من التابعين <code>Area</code> و <code>String</code>، لكنها لا تستدعي إلا التابع <code>Area</code>، وهذا سيجعل الدالة أقل وضوحًا، كما أننا نعلم أنه يمكننا فقط استدعاء التابع <code>Area</code> لأي وسيط يُمرّر إليه.
</p>

<h2>
	خاتمة
</h2>

<p>
	تعلمنا في هذا المقال كيفية إنشاء واجهات صغيرة وكيفية توسيعها لتصبح واجهات أكبر، وكيفية مشاركة الأشياء التي نريدها فقط مع دالة أو تابع من خلال تلك الواجهات. تعلمنا أيضًا كيفية تركيب واجهة من واجهات أصغر أو من واجهات موجودة في حزم أخرى وليس فقط الحزمة التي نعمل ضمنها.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-interfaces-in-go" rel="external nofollow">How To Use Interfaces in Go</a> لصاحبه Gopher Guides.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%B3%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%86%D9%8A%D8%A9-struct-tags-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1998/" rel="">استخدام وسوم البنية Struct Tags في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-methods-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1977/" rel="">تعريف التوابع Methods في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%A8%D9%86%D9%89-structs-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1976/" rel="">البنى Structs في لغة جو Go</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1999</guid><pubDate>Sun, 18 Jun 2023 13:00:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x648;&#x633;&#x648;&#x645; &#x627;&#x644;&#x628;&#x646;&#x64A;&#x629; Struct Tags &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648; Go</title><link>https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%B3%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%86%D9%8A%D8%A9-struct-tags-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1998/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_06/---Struct-Tags---.png.f14c2cdd51f43a67181d9e82693fd7f5.png" /></p>
<p>
	تُستخدم <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%A8%D9%86%D9%89-structs-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1976/" rel="">البنى structs</a> لجمع أجزاء متعددة من المعلومات معًا ضمن كتلة واحدة، وتُستخدم مجموعات المعلومات هذه لوصف المفاهيم ذات المستوى الأعلى، مثل العنوان المكوَّن من شارع ومدينة وولاية ورمز بريدي. عندما تقرأ هذه المعلومات من أنظمة، مثل قواعد البيانات، أو <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-api-r1314/" rel="">واجهات برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a>، يمكنك استخدام وسوم البنية للتحكم في كيفية تخصيص هذه المعلومات لحقول البنية. وسوم البنية هي أجزاء صغيرة من البيانات الوصفية المرفقة بحقول البنية التي توفر إرشادات إلى شيفرة جو أُخرى تعمل مع البنية.
</p>

<h2>
	كيف يبدو شكل وسم البنية؟
</h2>

<p>
	وسم البنية في لغة جو هو "توضيح" يُكتب بعد نوع الحقل داخل البنية، ويتكون كل وسم من زوج <code>"key:"value</code> أي مفتاح مرتبط بقيمة مقابلة ويوضع ضمن علامتين اقتباس مائلة (`) كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_8" style=""><span class="pln">type </span><span class="typ">User</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln"> string </span><span class="pun">`</span><span class="pln">example</span><span class="pun">:</span><span class="str">"name"</span><span class="pun">`</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	من خلال هذا التعريف، ستكون هناك شيفرة جو أخرى قادرة على فحص هذه البنية واستخراج القيم المخصصة لمفاتيح معينة، وبدون هذه الشيفرة الأخرى لن تؤثر وسوم البنية على تعليماتك البرمجية. جرّب هذا المثال لترى كيف يبدو وسم البنية، وكيف سيكون عديم التأثير دون الشيفرة الأخرى.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_10" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">User</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln"> string </span><span class="pun">`</span><span class="pln">example</span><span class="pun">:</span><span class="str">"name"</span><span class="pun">`</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">u </span><span class="pun">*</span><span class="typ">User</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"Hi! My name is %s"</span><span class="pun">,</span><span class="pln"> u</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    u </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">User</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">u</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Hi! My name is Sammy
</pre>

<p>
	يعرّف هذا المثال نوع بيانات باسم <code>User</code> يمتلك حقلًا اسمه <code>Name</code>، وأعطينا هذا الحقل وسم بنية <code>"example:"name</code> مُتمثّل بالمفتاح <code>example</code> والقيمة <code>"name"</code> للحقل <code>Name</code>. عرّفنا التابع <code>()String</code> في البنية <code>User</code>، والذي تحتاجه الواجهة <code>fmt.Stringer</code>، وبالتالي سيُستدعى تلقائيًا عندما نُمرّر هذا النوع إلى <code>fmt.Println</code> وبالتالي نحصل على طباعة مُرتبة للبنية.
</p>

<p>
	نأخذ في الدالة <code>main</code> متغيرًا من النوع <code>User</code> ونمرّره إلى دالة الطباعة <code>fmt.Println</code>. على الرغم من أنه لدينا وسم بنية، إلا أننا لن نلحظ أي فرق في الخرج، أي كما لو أنها غير موجودة. لكي نحصل على تأثير وسم البنية، يجب كتابة شيفرة أخرى تفحص البنية في وقت التشغيل runtime. تحتوي المكتبة القياسية على حزم تستخدم وسوم البنية كأنها جزء من عملها، وأكثرها شيوعًا هي حزمة <code>encoding/json</code>.
</p>

<div class="banner-container ipsBox ipsPadding">
	<div class="inner-banner-container">
		<p class="banner-heading">
			دورة تطوير واجهات المستخدم
		</p>

		<p class="banner-subtitle">
			ابدأ عملك الحر بتطوير واجهات المواقع والمتاجر الإلكترونية فور انتهائك من الدورة
		</p>

		<div>
			<a class="ipsButton ipsButton_large ipsButton_primary ipsButton_important" href="https://academy.hsoub.com/learn/front-end-web-development/" rel="">اشترك الآن</a>
		</div>
	</div>

	<div class="banner-img">
		<img alt="دورة تطوير واجهات المستخدم" src="https://academy.hsoub.com/learn/assets/images/courses/front-end-web-development.png">
	</div>
</div>

<h2>
	ترميز JSON
</h2>

<p>
	<a href="https://academy.hsoub.com/programming/javascript/%D8%B5%D9%8A%D8%BA%D8%A9-json-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9%D9%87%D8%A7-r826/" rel="">جسون JSON</a> هي اختصار إلى "ترميز كائن باستخدام جافا سكريبت JavaScript Object Notation"، وهي تنسيق نصي لترميز مجموعات البيانات المنظمة وفق أسماء مفاتيح مختلفة. يُشاع استخدام جسون لربط البيانات بين البرامج المختلفة، إذ أن التنسيق بسيط ويوجد مكتبات جاهزة لفك ترميزه في العديد من اللغات البرمجية. فيما يلي مثال على جسون:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_12" style=""><span class="pun">{</span><span class="pln">
  </span><span class="str">"language"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Go"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"mascot"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Gopher"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يتضمن كائن جسون أعلاه مفتاحين؛ الأول <code>language</code> والثاني <code>mascot</code>، ولكل منهما قيمة مرتبطة به هما <code>Go</code> و <code>Gopher</code>.
</p>

<p>
	يستخدم مُرمّز encoder جسون في المكتبة القياسية وسوم البنية مثل "توصيفات annotations" تشير إلى الكيفية التي تريد بها تسمية الحقول الخاصة بك في خرج جسون. يمكن العثور على آليات ترميز وفك ترميز جسون في حزمة <code>encoding/json</code> من <a href="https://pkg.go.dev/encoding/json" rel="external nofollow">هنا</a>.
</p>

<p>
	جرب هذا المثال لترى كيف يكون ترميز جسون دون وسوم البنية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_14" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"encoding/json"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"log"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">User</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln">          string
    </span><span class="typ">Password</span><span class="pln">      string
    </span><span class="typ">PreferredFish</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">string
    </span><span class="typ">CreatedAt</span><span class="pln">     time</span><span class="pun">.</span><span class="typ">Time</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    u </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">User</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">      </span><span class="str">"Sammy the Shark"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Password</span><span class="pun">:</span><span class="pln">  </span><span class="str">"fisharegreat"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">CreatedAt</span><span class="pun">:</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">(),</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    out</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> json</span><span class="pun">.</span><span class="typ">MarshalIndent</span><span class="pun">(</span><span class="pln">u</span><span class="pun">,</span><span class="pln"> </span><span class="str">""</span><span class="pun">,</span><span class="pln"> </span><span class="str">"  "</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">string</span><span class="pun">(</span><span class="pln">out</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وسيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_16" style=""><span class="pun">{</span><span class="pln">
  </span><span class="str">"Name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy the Shark"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"Password"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"fisharegreat"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"CreatedAt"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2019-09-23T15:50:01.203059-04:00"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عرّفنا بنية تمثّل مُستخدم مع حقول تُدل على اسمه <code>Name</code> وكلمة المرور <code>Password</code> وتاريخ إنشاء الحساب <code>CreatedAt</code>. أخذنا داخل الدالة <code>main</code> متغيرًا من هذه البنية وأعطينا قيمًا لجميع حقوله عدا <code>PreferredFish</code> (يحب Sammy جميع الأسماك). بعد ذلك، مرّرنا البنية إلى الدالة <code>json.MarshalIndent</code>، إذ تمكننا هذه الدالة من رؤية خرج جسون بسهولة ودون استخدام أي أداة خارجية. يمكن استبدال الاستدعاء السابق لهذه الدالة بالاستدعاء <code>(json.Marshal(u</code> وذلك لطباعة جسون بدون أي فراغات إضافية، إذ يتحكم الوسيطان الإضافيان للدالة <code>json.MarshalIndent</code> في بادئة الخرج، التي أهملناها مع السلسلة الفارغة، وبالمحارف المُراد استخدامها من أجل تمثيل المسافة البادئة (هنا فراغين).
</p>

<p>
	سُجّلت الأخطاء الناتجة عن <code>json.MarshalIndent</code> وأُنهي البرنامج باستخدام <code>(os.Exit(1</code>، وأخيرًا حوّلنا المصفوفة <code>byte[]</code> المُعادة من <code>json.MarshalIndent</code> إلى سلسلة ومررنا السلسلة الناتجة إلى <code>fmt.Println</code> للطباعة على الطرفية.
</p>

<p>
	تظهر حقول البنية تمامًا كما تُسمّى، وهذا ليس طبعًا نمط جسون النموذجي الذي يستخدم تنسيق "سنام الجمل Camel case" لأسماء الحقول. سنحقق ذلك في المثال التالي، إذ سنرى عند تشغيله أنه لن يعمل لأن أسماء الحقول المطلوبة تتعارض مع قواعد جو المتعلقة بأسماء الحقول المصدَّرة.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_18" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"encoding/json"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"log"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">User</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    name          string
    password      string
    preferredFish </span><span class="pun">[]</span><span class="pln">string
    createdAt     time</span><span class="pun">.</span><span class="typ">Time</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    u </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">User</span><span class="pun">{</span><span class="pln">
        name</span><span class="pun">:</span><span class="pln">      </span><span class="str">"Sammy the Shark"</span><span class="pun">,</span><span class="pln">
        password</span><span class="pun">:</span><span class="pln">  </span><span class="str">"fisharegreat"</span><span class="pun">,</span><span class="pln">
        createdAt</span><span class="pun">:</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">(),</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    out</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> json</span><span class="pun">.</span><span class="typ">MarshalIndent</span><span class="pun">(</span><span class="pln">u</span><span class="pun">,</span><span class="pln"> </span><span class="str">""</span><span class="pun">,</span><span class="pln"> </span><span class="str">"  "</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">string</span><span class="pun">(</span><span class="pln">out</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وسيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">{}
</pre>

<p>
	استبدلنا هذه المرة أسماء الحقول، بحيث تتوافق مع تنسيق سنام الجمل؛ فبدلًا من <code>Name</code> وضعنا <code>name</code> وبدلًا من <code>Password</code> وضعنا <code>password</code> وبدلًا من <code>CreatedAt</code> وضعنا <code>createdAt</code>، وعدّلنا داخل متن الدالة <code>main</code> أسماء الحقول أيضًا بحيث تتوافق مع الأسماء الجديدة، ومرّرنا البنية إلى الدالة <code>json.MarshalIndent</code> كما في السابق. الخرج كان عبارة عن كائن جسون فارغ <code>{}</code>.
</p>

<p>
	يفرض نمط سنام الجمل أن يكون أول حرف صغير دومًا، بينما لا تهتم جسون بذلك، ولغة جو صارمة مع حالة اﻷحرف؛ إذ تدل البداية بحرف كبير على أن الحقل غير مُصدّر وتدل البداية بحرف صغير على أنّه مُصدّر، وبما أن الحزمة <code>encoding/json</code> هي حزمة منفصلة عن حزمة <code>main</code> التي نستخدمها، يجب علينا كتابة الحرف الأول بأحرف كبيرة لجعله مرئيًا للحزمة <code>encoding/json</code>. حسنًا، يبدو أننا في طريق مسدود، فنحن بحاجة إلى طريقة ما لننقل إلى ترميز جسون ما نود تسمية هذا الحقل به.
</p>

<h3>
	استخدام وسوم البنية للتحكم بالترميز
</h3>

<p>
	يمكنك تعديل المثال السابق، بحيث تكون قادرًا على تصدير الحقول المتوافقة مع تنسيق سنام الجمل عن طريق إضافة وسم بنية لكل حقل. يجب أن يكون لدى وسوم البنية التي تتعرّف عليها الحزمة <code>encoding/json</code> مفتاحها <code>json</code> مع قيمة مُرافقة لهذا المفتاح تتحكم بالخرج.
</p>

<p>
	إذًا، من خلال استخدام تنسيق سنام الجمل لقيم المفاتيح، سيستخدم المرمّز هذه القيم مثل أسماء للحقول مع الإبقاء على الحقول مُصدّرة، وبالتالي نكون أنهينا المشاكل السابقة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_21" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"encoding/json"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"log"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">User</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln">          string    </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"name"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">Password</span><span class="pln">      string    </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"password"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">PreferredFish</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">string  </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"preferredFish"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">CreatedAt</span><span class="pln">     time</span><span class="pun">.</span><span class="typ">Time</span><span class="pln"> </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"createdAt"</span><span class="pun">`</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    u </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">User</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">      </span><span class="str">"Sammy the Shark"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Password</span><span class="pun">:</span><span class="pln">  </span><span class="str">"fisharegreat"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">CreatedAt</span><span class="pun">:</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">(),</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    out</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> json</span><span class="pun">.</span><span class="typ">MarshalIndent</span><span class="pun">(</span><span class="pln">u</span><span class="pun">,</span><span class="pln"> </span><span class="str">""</span><span class="pun">,</span><span class="pln"> </span><span class="str">"  "</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">string</span><span class="pun">(</span><span class="pln">out</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_23" style=""><span class="pun">{</span><span class="pln">
  </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy the Shark"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"password"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"fisharegreat"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"preferredFish"</span><span class="pun">:</span><span class="pln"> null</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"createdAt"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2019-09-23T18:16:17.57739-04:00"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لاحظ أننا تركنا أسماء الحقول تبدأ بأحرف كبيرة من أجل السماح بعملية التصدير، ولحل مشكلة تنسيق سنام الجمل استخدمنا وسوم بنية من الشكل <code>"json:"name</code>، إذ أن <code>"name"</code> هي القيمة التي نريد من <code>json.MarshalIndent</code> أن تعرضها عند طباعة كائن جسون.
</p>

<p>
	لاحظ أن الحقل <code>PreferredFish</code> لم نُعطه أي قيمة، وبالتالي قد لا نرغب بظهوره عند طباعة كائن جسون. فيما يلي سنتحدث حول هذا الموضوع.
</p>

<h3>
	حذف حقول جسون الفارغة
</h3>

<p>
	يُعد حذف حقول الخرج التي ليس لها قيمة في جسون أمرًا شائعًا، وبما أن جميع الأنواع في جو لها "قيمة صفرية" أو قيمة افتراضية مُهيّأة بها، تحتاج حزمة <code>encoding/json</code> إلى معلومات إضافية لتتمكن من معرفة أن بعض الحقول ينبغي عدّها غير مضبوطة، أي قيمتها صفرية في جو. هذه المعلومات هي في الحقيقة مجرد كلمة واحدة نضيفها إلى نهاية القيمة المرتبطة بمفتاح الحقل في وسم البنية؛ وهذه الكلمة هي <code>omitempty,</code>، إذ نُخبر جسون من خلال إضافة هذه الكلمة إلى الحقل بأننا لا نريد ظهوره عندما تكون قيمته صفرية. يوضح المثال التالي الأمر:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_25" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"encoding/json"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"log"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">User</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln">          string    </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"name"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">Password</span><span class="pln">      string    </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"password"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">PreferredFish</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">string  </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"preferredFish,omitempty"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">CreatedAt</span><span class="pln">     time</span><span class="pun">.</span><span class="typ">Time</span><span class="pln"> </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"createdAt"</span><span class="pun">`</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    u </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">User</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">      </span><span class="str">"Sammy the Shark"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Password</span><span class="pun">:</span><span class="pln">  </span><span class="str">"fisharegreat"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">CreatedAt</span><span class="pun">:</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">(),</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    out</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> json</span><span class="pun">.</span><span class="typ">MarshalIndent</span><span class="pun">(</span><span class="pln">u</span><span class="pun">,</span><span class="pln"> </span><span class="str">""</span><span class="pun">,</span><span class="pln"> </span><span class="str">"  "</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">string</span><span class="pun">(</span><span class="pln">out</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_27" style=""><span class="pun">{</span><span class="pln">
  </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy the Shark"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"password"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"fisharegreat"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"createdAt"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2019-09-23T18:21:53.863846-04:00"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عدّلنا الأمثلة السابقة بحيث أصبح حقل <code>PreferredFish</code> يحتوي على وسم البنية <code>"json:"preferredFish,omitempty</code>، إذ سيمنع وجود الكلمة <code>omitempty,</code> ظهور هذا الحقل في خرج <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">كائن جسون</a>.
</p>

<p>
	أصبحت الآن الأمور أفضل، لكن هناك مشكلة أخرى واضحة، وهي ظهور كلمة المرور، ولحل المشكلة تؤمن <code>encoding/json</code> طريقةً لتجاهل الحقول الخاصة تمامًا.
</p>

<h3>
	منع عرض الحقول الخاصة في خرج كائنات جسون
</h3>

<p>
	يجب تصدير بعض الحقول من البنى حتى تتمكن الحزم الأخرى من التفاعل بطريقة صحيحة مع النوع، لكن قد تكون المشكلة في حساسية طبيعة أحد هذه الحقول كما في حالة كلمة المرور في المثال السابق، لذا نود أن يتجاهل مُرمّز جسون الحقل تمامًا، حتى عند تهيئته بقيمة. يكون حل هذه المشكلة باستخدام المحرف <code>-</code> ليكون قيمةً لوسيط المفتاح الخاص بوسم البنية <code>:json</code>.
</p>

<p>
	يعمل هذا المثال على إصلاح مشكلة عرض كلمة مرور المستخدم.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_29" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"encoding/json"</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"log"</span><span class="pln">
    </span><span class="str">"os"</span><span class="pln">
    </span><span class="str">"time"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">User</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln">      string    </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"name"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">Password</span><span class="pln">  string    </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"-"</span><span class="pun">`</span><span class="pln">
    </span><span class="typ">CreatedAt</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Time</span><span class="pln"> </span><span class="pun">`</span><span class="pln">json</span><span class="pun">:</span><span class="str">"createdAt"</span><span class="pun">`</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    u </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">User</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">      </span><span class="str">"Sammy the Shark"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Password</span><span class="pun">:</span><span class="pln">  </span><span class="str">"fisharegreat"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">CreatedAt</span><span class="pun">:</span><span class="pln"> time</span><span class="pun">.</span><span class="typ">Now</span><span class="pun">(),</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    out</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> json</span><span class="pun">.</span><span class="typ">MarshalIndent</span><span class="pun">(</span><span class="pln">u</span><span class="pun">,</span><span class="pln"> </span><span class="str">""</span><span class="pun">,</span><span class="pln"> </span><span class="str">"  "</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
        os</span><span class="pun">.</span><span class="typ">Exit</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">string</span><span class="pun">(</span><span class="pln">out</span><span class="pun">))</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_57_31" style=""><span class="pun">{</span><span class="pln">
  </span><span class="str">"name"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy the Shark"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"createdAt"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"2019-09-23T16:08:21.124481-04:00"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الشيء الوحيد الذي تغير في هذا المثال عن المثال السابق هو أن حقل كلمة المرور يستخدم الآن القيمة الخاصة <code>"-"</code> لوسم البنية <code>:json</code>. يمكنك أن تلاحظ من الخرج السابق اختفاء كلمة المرور من الخرج.
</p>

<p>
	ميزتا التجاهل والإخفاء، أو حتى <a href="https://pkg.go.dev/encoding/json#Marshal" rel="external nofollow">باقي الخيارات</a> في حزمة <code>encoding/json</code> التي استخدمناها مع حقل <code>PreferredFish</code> و <code>Password</code>، ليستا قياسيتين، أي ليست كل الحزم تستخدم نفس الميزات ونفس بنية القواعد، لكن حزمة <code>encoding/json</code> هي حزمة مُضمّنة عمومًا في المكتبة القياسية، وبالتالي سيكون لدى الحزم الأخرى نفس الميزات ونفس العرض convention. مع ذلك، من المهم قراءة التوثيق الخاص بأي حزمة تابعة لجهة خارجية تستخدم وسوم البنية لمعرفة ما هو مدعوم وما هو غير مدعوم.
</p>

<h2>
	خاتمة
</h2>

<p>
	توفر وسوم البنية وسيلةً قويةً لتسهيل التعامل مع دوال الشيفرات التي تعمل مع البنى، كما توفر العديد من الحزم القياسية والخارجية طرقًا لتخصيص عملياتها من خلال استخدام هذه الوسوم. يوفر استخدام الوسوم بفعالية في التعليمات البرمجية الخاصة بك سلوك تخصيص ممتاز ويوثّق بإيجاز كيفية استخدام هذه الحقول للمطوّرين المستقبليين.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-use-struct-tags-in-go" rel="external nofollow">How To Use Struct Tags in Go</a> لصاحبه Gopher Guides.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D9%88%D8%A8%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-%D9%88%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87%D8%A7-r1996/" rel="">بناء البرامج المكتوبة بلغة جو Go وتثبيتها</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%B3%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%AA%D8%AE%D8%B5%D9%8A%D8%B5-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D9%86%D9%81%D9%8A%D8%B0%D9%8A%D8%A9-binaries-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1970/" rel="">استخدام وسوم البناء لتخصيص الملفات التنفيذية Binaries في لغة جو Go</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1998</guid><pubDate>Sun, 11 Jun 2023 13:02:00 +0000</pubDate></item><item><title>&#x628;&#x646;&#x627;&#x621; &#x627;&#x644;&#x628;&#x631;&#x627;&#x645;&#x62C; &#x627;&#x644;&#x645;&#x643;&#x62A;&#x648;&#x628;&#x629; &#x628;&#x644;&#x63A;&#x629; &#x62C;&#x648; Go &#x648;&#x62A;&#x62B;&#x628;&#x64A;&#x62A;&#x647;&#x627;</title><link>https://academy.hsoub.com/programming/go/%D8%A8%D9%86%D8%A7%D8%A1-%D8%A7%D9%84%D8%A8%D8%B1%D8%A7%D9%85%D8%AC-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D9%88%D8%A8%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-%D9%88%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87%D8%A7-r1996/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_06/------Go.png.495fa54671c8ed75b7e96657d6054cbc.png" /></p>
<p>
	استخدمنا خلال هذه السلسلة <a href="https://academy.hsoub.com/tags/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9%20%D8%A8%D9%84%D8%BA%D8%A9%20go/" rel="">البرمجة بلغة GO</a> الأمر <code>go run</code> كثيرًا، وكان الهدف منه تشغيل شيفرة البرنامج الخاص بنا، إذ أنه يُصرّف شفرة المصدر تلقائيًا ويُشغّل الملف التنفيذي الناتج أي القابل للتنفيذ executable. يُعد هذا الأمر مفيدًا عندما تحتاج إلى اختبار برنامجك من خلال سطر الأوامر command line، لكن عندما ترغب بنشر تطبيقك، سيتطلب منك ذلك بناء تعليماتك البرمجية في ملف تنفيذي، أو ملف واحد يحتوي على شيفرة محمولة أو تنفيذية (يُطلق عليه أيضًا الكود-باء p-code، وهو شكل من أشكال مجموعة التعليمات المصممة للتنفيذ الفعّال بواسطة مُصرّف برمجي) يمكنها تشغيل تطبيقك. يمكنك لإنجاز ذلك استخدام سلسلة أدوات جو لبناء البرنامج وتثبيته.
</p>

<p>
	تسمى عملية <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%B3%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%AA%D8%AE%D8%B5%D9%8A%D8%B5-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D9%86%D9%81%D9%8A%D8%B0%D9%8A%D8%A9-binaries-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1970/" rel="">ترجمة التعليمات البرمجية المصدر إلى ملف تنفيذي</a> في لغة جو بالبناء؛ فعند بناء هذا الملف التنفيذي ستُضاف إليه الشيفرة اللازمة لتنفيذ البرنامج التنفيذي الثنائي binary على النظام الأساسي المُستهدف. هذا يعني أن جو التنفيذي Go binary (خادم مفتوح المصدر أو حزمة برمجية تسمح للمستخدمين الذين لا يستخدمون جو بتثبيت الأدوات المكتوبة بلغة جو بسرعة، دون تثبيت مُصرّف جو أو مدير الحزم - كل ما تحتاجه هو curl) لا يحتاج إلى اعتماديات dependencies النظام مثل أدوات جو للتشغيل على نظام جديد. سيسمح وضع هذه الملفات التنفيذية ضمن مسار ملف تنفيذي على نظامك، بتشغيل البرنامج من أي مكان في نظامك؛ أي كما لو أنك تُثبّت أي برنامج عادي على نظام التشغيل الخاص بك.
</p>

<p>
	ستتعلم في هذا المقال كيفية استخدام سلسلة أدوات لغة جو Go toolchain لتشغيل وبناء وتثبيت برنامج "!Hello، World" لفهم كيفية استخدام التطبيقات البرمجية وتوزيعها ونشرها بفعالية.
</p>

<h2>
	المتطلبات
</h2>

<p>
	أن يكون لديك مساحة عمل خاصة في لغة جو، وإذا لم يكن لديك ذلك اتبع سلسلة المقالات التالية، فقد تحدّثنا عن ذلك في بداية السلسلة "<a href="https://academy.hsoub.com/tags/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9%20%D8%A8%D9%84%D8%BA%D8%A9%20go/" rel="">البرمجة بلغة Go</a>:
</p>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D9%85%D8%A7%D9%83-macos-r1767/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك أو إس macOS</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-r1768/" rel="">تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز</a>.
	</li>
</ul>

<h2>
	إعداد وتشغيل جو التنفيذي Go Binary
</h2>

<p>
	سننشئ بدايةً تطبيقًا بسيطًا بلغة جو يطبع العبارة الترحيبية "!Hello، World"، وذلك لتوضيح سلسلة أدوات جو، وانظر مقال <a href="https://academy.hsoub.com/programming/go/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC%D9%83-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D9%81%D9%8A-%D8%AC%D9%88-go-r1788/" rel="">كتابة برنامجك الأول في جو Go</a> إن لم تتطلع عليه مسبقًا.
</p>

<p>
	أنشئ مجلد "greeter" داخل المجلد "src":
</p>

<pre class="ipsCode">$ mkdir greeter
</pre>

<p>
	بعد ذلك أنشئ ملف "main.go" بعد الانتقال إلى هذا المجلد الذي أنشأته للتو، ويمكنك استخدام أي محرر نصوص تختاره ﻹنشاء الملف، هنا استخدمنا محرر نانو nano:
</p>

<pre class="ipsCode">$ cd greeter
$ nano main.go
</pre>

<p>
	بعد فتح الملف، ضِف المحتويات التالية إليه:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_2390_11" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Hello, World!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عند تشغيل هذا البرنامج سيطبع العبارة "!Hello, World" ثم سينتهي البرنامج. احفظ الملف الآن واغلقه.
</p>

<p>
	استخدم الأمر <code>go run</code> لاختبار البرنامج:
</p>

<pre class="ipsCode">$ go run main.go
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Hello, World!
</pre>

<p>
	كما تحدّثنا سابقًا؛ يبني الأمر <code>go run</code> الملف المصدري الخاص بك في ملف تنفيذي، ثم يُصرّفه ويعرض الناتج. نريد هنا أن نتعلم كيفية بناء الملف التنفيذي بطريقة تمكننا من توزيع ونشر برامجنا، ولذلك سنستخدم الأمر <code>go build</code> في الخطوة التالية.
</p>

<h2>
	إنشاء وحدة جو من أجل Go binary
</h2>

<p>
	بُنيت برامج ومكتبات جو وفق المفهوم الأساسي "للوحدة module"، إذ تحتوي الوحدة على معلومات حول المكتبات التي يستخدمها برنامجك وإصدارات هذه المكتبات التي يجب استخدامها. لتخبر جو أن مجلدًا ما هو وحدة، ستحتاج إلى إنشاء هذا المجلد باستخدام الأمر <code>go mod</code>:
</p>

<pre class="ipsCode">$ go mod init greeter
</pre>

<p>
	سيؤدي ذلك إلى إنشاء الملف go.mod الذي يتضمن اسم الوحدة ونسخة جو المستخدمة في إنشائها.
</p>

<pre class="ipsCode">go: creating new go.mod: module greeter
go: to add module requirements and sums:
    go mod tidy
</pre>

<p>
	سيطالبك جو بتشغيل <code>go mod tidy</code> لتحديث متطلبات هذه الوحدة إذا تغيرت في المستقبل، ولن يكون لتشغيله الآن أي تأثير إضافي.
</p>

<h2>
	بناء الملفات التنفيذية باستخدام الأمر go build
</h2>

<p>
	يمكنك بناء ملف تنفيذي باستخدام الأمر <code>go build</code> من أجل تطبيق مكتوب بلغة جو، مما يسمح لك بتوزيعه ونشره في المكان الذي تريده.
</p>

<p>
	سنجرب ذلك مع ملف main.go. من داخل المجلد greeter من خلال تنفيذ الأمر التالي:
</p>

<pre class="ipsCode">$ go build
</pre>

<p>
	إذا لم تُقدم وسيطًا لهذا الأمر، سيُصرّف الأمر <code>go build</code> تلقائيًا برنامج main.go في مجلدك الحالي، وسيتضمن ذلك أيضًا كل الملفات التي يكون امتدادها <code>go.*</code> في هذا المجلد. سيبني أيضًا جميع التعليمات البرمجية الداعمة واللازمة لتكون قادرًا على تنفيذ البرنامج التنفيذي على أي جهاز حاسوب له نفس معمارية النظام، بغض النظر عما إذا كان هذا النظام يحتوي على أدوات جو أو مُصرّف جو أو ملفاته المصدرية.
</p>

<p>
	إذًا، فقد بنيت تطبيق الترحيب الخاص بك في ملف تنفيذي أُضيف إلى مجلدك الحالي. تحقق من ذلك عن طريق تشغيل الأمر التالي:
</p>

<pre class="ipsCode">$ ls
</pre>

<p>
	إذا كنت تستخدم نظام ماك أو إس macOS أو لينكس Linux، فستجد ملفًا تنفيذيًا جديدًا مُسمّى على اسم المجلد الذي بنيت فيه برنامجك:
</p>

<pre class="ipsCode">greeter  main.go  go.mod
</pre>

<p>
	<strong>ملاحظة:</strong> في <a href="https://academy.hsoub.com/apps/operating-systems/windows/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%88%D9%8A%D9%86%D8%AF%D9%88%D8%B2-11-%D9%88%D8%B7%D8%B1%D9%8A%D9%82%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87-r704/" rel="">نظام التشغيل ويندوز</a>، سيكون الملف التنفيذي باسم "greeter.exe".
</p>

<p>
	سيُنشئ الأمر<code>go build</code> افتراضيًا ملفًا تنفيذيًا للنظام الأساسي والمعمارية الحاليين. على سبيل المثال، إذا بُنيَ على نظام تشغيل linux / 386، سيكون الملف التنفيذي متوافقًا مع أي نظام Linux / 386 آخر، حتى إذا لم يكن جو مُثبّتًا على ذلك النظام. تدعم لغة جو إمكانية البناء على الأنظمة والمعماريات الأخرى.
</p>

<p>
	بعد أن أنشأت ملفك التنفيذي، يمكنك تشغيله للتأكد من أنه قد بُنيَ بطريقة سليمة. في نظام ماك أو إس أو لينكس، شغّل الأمر التالي:
</p>

<pre class="ipsCode">$ ./greeter
</pre>

<p>
	أما في ويندوز نفّذ الأمر:
</p>

<pre class="ipsCode">$ greeter.exe
</pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Hello, World!
</pre>

<p>
	بذلك تكون قد أنشأت ملفًا تنفيذيًّا يحتوي على برنامجك وعلى شيفرة النظام المطلوبة لتشغيل هذا الملف التنفيذي. يمكنك الآن توزيع هذا البرنامج على أنظمة جديدة أو نشره على خادم، مع العلم أن الملف سيعمل دائمًا على نفس البرنامج.
</p>

<p>
	سنشرح فيما يلي كيفية تسمية ملف تنفيذي وكيفية تعديله بحيث يمكنك التحكم أكثر في عملية بناء البرنامج.
</p>

<h2>
	تغيير اسم الملف التنفيذي
</h2>

<p>
	بعد أن عرفت كيفية إنشاء ملف تنفيذي، ستكون الخطوة التالية هي تحديد كيفية اختيار جو اسمًا للملف التنفيذي وكيفية تخصيص هذا الاسم لمشروعك.
</p>

<p>
	يقرر جو تلقائيًا اسم الملف التنفيذي الذي بُنيَ عند تشغيل الأمر <code>go build</code>، وذلك اعتمادًا على الوحدة التي أنشأتها. عندما نفّذنا الأمر <code>go mod init greeter</code> منذ قليل، أُنشئت وحدة باسم <code>greeter</code>، وهذا هو سبب تسمية الملف التنفيذي الثنائي binary الذي أُنشئ باسم "greeter" بدوره.
</p>

<p>
	إذا فتحت ملف go.mod (الذي يُفترض أن يكون ضمن مجلد مشروعك)، وكان يتضمن التصريح التالي:
</p>

<pre class="ipsCode">module github.com/sammy/shark
</pre>

<p>
	وهذا يعني أن الاسم الافتراضي للملف التنفيذي الذي بُنيَ هو shark.
</p>

<p>
	لن تكون هذه التسميات الافتراضية دائمًا الخيار الأفضل لتسمية الملف التنفيذي الخاص بك في البرامج الأكثر تعقيدًا التي تتطلب تسميات اصطلاحية محددة، وسيكون من الأفضل تحديد أسماء مُخصصة من خلال الراية <code>o-</code>.
</p>

<p>
	سنغيّر الآن اسم الملف التنفيذي الذي أنشأناه في القسم السابق إلى الاسم "hello" ونضعه في مجلد فرعي يسمى "bin". لن نحتاج إلى إنشاء هذا المجلد (bin)، إذ ستتكفل جو بذلك أثناء عملية البناء. نفّذ الأمر <code>go build</code> مع الراية<code>o-</code>:
</p>

<pre class="ipsCode">$ go build -o bin/hello
</pre>

<p>
	تُخبر الراية <code>o-</code> مُصرّف جو أن عليه مُطابقة خرج الأمر <code>go build</code> مع الوسيط المُحدد بعدها والمتمثّل بالعبارة bin/hello. بعبارةٍ أوضح؛ تُخبر هذه الراية المُصرّف أن الملف التنفيذي السابق يجب أن يكون اسمه "hello" وأن يكون ضمن مجلد اسمه "bin"، وفي حال لم يكن هذا المجلد موجودًا فعليك إنشاؤه تلقائيًّا.
</p>

<p>
	لاختبار الملف التنفيذي الجديد، انتقل إلى المجلد الجديد وشغّل الملف التنفيذي:
</p>

<pre class="ipsCode">$ cd bin
$ ./hello
</pre>

<p>
	ستحصل على الخرج التالي:
</p>

<pre class="ipsCode">Hello, World!
</pre>

<p>
	يمكنك الآن اختيار اسم الملف التنفيذي ليناسب احتياجات مشروعك.
</p>

<p>
	إلى الآن لا تزال مقيدًا بتشغيل ملفك التنفيذي من المجلد الحالي، وإذا أردت استخدام الملفات التنفيذية التي بنيتها من أي مكان على نظامك، يجب عليك تثبيتها باستخدام الأمر <code>go install</code>.
</p>

<h2>
	تثبيت برامج جو باستخدام الأمر go install
</h2>

<p>
	ناقشنا حتى الآن كيفية إنشاء ملفات تنفيذية من ملفات مصدرية بامتداد "go."، هذه الملفات التنفيذية مفيدة من أجل التوزيع والنشر والاختبار، ولكن لا يمكن تنفيذها من خارج المجلدات المصدرية الموجودة ضمنها. قد تكون هذه مشكلة إذا كنت تريد استخدام برنامجك باستمرار ضمن <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%B3%D9%83%D8%B1%D8%A8%D8%AA%D8%A7%D8%AA-%D8%A7%D9%84%D8%B5%D8%AF%D9%81%D8%A9-shell-scripts-r252/" rel="">سكريبتات الصدفة shell scripts</a> أو في مهام أخرى. لتسهيل استخدام البرامج، يمكنك تثبيتها في نظامك والوصول إليها من أي مكان. لتوضيح الفكرة سنستخدم الأمر <code>go install</code> لتثبيت البرنامج الذي نعمل عليه.
</p>

<p>
	يعمل الأمر <code>go install</code> على نحوٍ مماثل تقريبًا للأمر <code>go build</code>، ولكن بدلًا من ترك الملف التنفيذي في المجلد الحالي أو مجلد محدد بواسطة الراية <code>o-</code>، فإنه يضعه في المجلد "GOPATH/bin$".
</p>

<p>
	لمعرفة مكان وجود مجلد "GOPATH$" الخاص بك، شغّل الأمر التالي:
</p>

<pre class="ipsCode">$ go env GOPATH
</pre>

<p>
	قد يختلف الخرج الذي تتلقاه، ولكن يُفترض أن يكون ضمن مجلد go الموجود داخل مجلد HOME$:
</p>

<pre class="ipsCode">$HOME/go
</pre>

<p>
	نظرًا لأن <code>go install</code> سيضع الملفات التنفيذية التي أُنشئت في مجلد فرعي للمجلد GOPATH$ اسمه bin، يجب إضافة هذا المجلد إلى متغير البيئة PATH$. تحدّثنا عن هذه المواضيع في مقالة <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%A3%D8%A8%D9%88%D9%86%D8%AA%D9%88-r1766/" rel="">كيفية تثبيت جو وإعداد بيئة برمجة محلية</a>.
</p>

<p>
	بعد إعداد المجلد GOPATH/bin$، ارجع إلى مجلد greeter:
</p>

<pre class="ipsCode">$ cd ..
</pre>

<p>
	شغّل الآن أمر التثبيت:
</p>

<pre class="ipsCode">$ go install
</pre>

<p>
	سيؤدي هذا إلى بناء ملفك التنفيذي ووضعه في GOPATH/bin$. شغّل الأمر التالي لاختبار ذلك:
</p>

<pre class="ipsCode">$ ls $GOPATH/bin
</pre>

<p>
	سيسرد لك هذا الأمر محتويات المجلد GOPATH/bin$:
</p>

<pre class="ipsCode">greeter
</pre>

<p>
	<strong>ملاحظة</strong>: لا يدعم الأمر <code>go install</code> الراية <code>o-</code>، لذلك سيستخدم الاسم الافتراضي الذي تحدّثنا عنه سابقًا لتسمية الملف التنفيذي.
</p>

<p>
	تحقق الآن ما إذا كان البرنامج سيعمل من خارج المجلد المصدر. ارجع أولًا إلى المجلد HOME:
</p>

<pre class="ipsCode">$ cd $HOME
</pre>

<p>
	استخدم ما يلي لتشغيل البرنامج:
</p>

<pre class="ipsCode">$ greeter
</pre>

<p>
	ستحصل على الخرج التالي:
</p>

<pre class="ipsCode">Hello, World!
</pre>

<p>
	يمكنك الآن تثبيت البرامج التي تكتبها في نظامك، مما يتيح لك استخدامها من أي مكان، ومتى احتجت إليها.
</p>

<h2>
	خاتمة
</h2>

<p>
	أوضحنا في هذا المقال كيف تسهل سلسلة أدوات جو عملية إنشاء ملفات تنفيذية ثنائية من التعليمات البرمجية المصدرية، ويمكن توزيعها لتعمل على أنظمة أخرى، حتى لو لم تحتوي على أدوات وبيئات للغة جو.
</p>

<p>
	استخدمنا أيضًا الأمر <code>go install</code> لبناء برامجنا وتثبيتها تلقائيًا مثل ملفات تنفيذية ضمن متغير البيئة <code>PATH$</code> الخاص بالنظام. ستستطيع من خلال الأمرين <code>go install</code> و <code>go build</code> مشاركة واستخدام التطبيق الخاص بك كما تشاء.
</p>

<p>
	الآن بعد أن تعرفت على أساسيات <code>go build</code>، يمكنك استكشاف كيفية إنشاء شيفرة مصدر معيارية من خلال المقالة <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%88%D8%B3%D9%88%D9%85-%D8%A7%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%AA%D8%AE%D8%B5%D9%8A%D8%B5-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D9%86%D9%81%D9%8A%D8%B0%D9%8A%D8%A9-binaries-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1970/" rel="">استخدام وسوم البناء لتخصيص الملفات التنفيذية في لغة جو Go</a>.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-build-and-install-go-programs" rel="external nofollow">How To Build and Install GOPrograms</a> لصاحبه Gopher Guides.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-methods-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1977/" rel="">تعريف التوابع Methods في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC%D9%83-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D9%81%D9%8A-%D8%AC%D9%88-go-r1788/" rel="">كتابة برنامجك الأول في جو Go</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1996</guid><pubDate>Sun, 04 Jun 2023 13:00:00 +0000</pubDate></item><item><title>&#x62A;&#x639;&#x631;&#x64A;&#x641; &#x627;&#x644;&#x62A;&#x648;&#x627;&#x628;&#x639; Methods &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648; Go</title><link>https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%AA%D9%88%D8%A7%D8%A8%D8%B9-methods-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1977/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_05/-----Go.png.0553fbd3ee1ea31f94073f9d524e8118.png" /></p>
<p>
	تُمكّنك <span ipsnoautolink="true">الدوال Functions</span> من تنظيم منطق مُحدد ضمن إجراءات procedures قابلة للتكرار يمكنها استخدام وسطاء مختلفة في كل مرة تُنفّذ فيها. تعمل عدة دوال غالبًا على نفس الجزء من البيانات في كل مرة. يُمكن لغة جو التعرّف على هذه الأنماط، وتسمح لك بتعريف دوال خاصة تُسمى "التوابع Methods"، وتجري عمليات على نوع بيانات مُحدد يُشار إليه باسم "المُستقبل Receiver".
</p>

<h2>
	تعريف تابع
</h2>

<p>
	قواعد كتابة التوابع مُشابهة لقواعد كتابة <a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D9%88%D8%A7%D8%B3%D8%AA%D8%AF%D8%B9%D8%A7%D8%A1-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1962/" rel="">الدوال</a>، والاختلاف الوحيد هو إضافة معامل يُوضع بعد الكلمة المفتاحية <code>func</code> لتحديد مُستقبل التابع. المُستقبل هو تصريح لنوع البيانات الذي تُريد تعريف التابع من أجله. نُصرّح في المثال التالي عن تابع يُطبّق على نوع <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%A8%D9%86%D9%89-structs-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1976/" rel="">بنية struct</a>:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7729_8" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Creature</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln">     string
    </span><span class="typ">Greeting</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">c </span><span class="typ">Creature</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Greet</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%s says %s"</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="typ">Greeting</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    sammy </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Creature</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">     </span><span class="str">"Sammy"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Greeting</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Hello!"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="typ">Creature</span><span class="pun">.</span><span class="typ">Greet</span><span class="pun">(</span><span class="pln">sammy</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نحصل عند تشغيل الشيفرة على ما يلي:
</p>

<pre class="ipsCode">Sammy says Hello!
</pre>

<p>
	أنشأنا بنيةً أسميناها <code>Creature</code> تمتلك حقلين من النوع <code>string</code> باسم <code>Name</code> و <code>Greeting</code>. لدى <code>Creature</code> تابع وحيد مُعرّف يُسمى <code>Greet</code>. أسندنا أثناء التصريح عن المستقبل نسخةً من <code>Creature</code> إلى المتغير <code>c</code>، بحيث يُمكننا من خلالها الوصول إلى حقول <code>Creature</code> وطباعة محتوياته باستخدام دالة <code>fmt.Printf</code>.
</p>

<p>
	يُشار عادةً للمُستقبل في <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">لغات البرمجة</a> الأخرى بكلمات رئيسية مثل <code>self</code> كما في <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">لغة بايثون</a> أو <code>this</code> كما في <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D8%A7-%D9%87%D9%8A%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-java-r1515/" rel="">لغة جافا</a>. يُعد المستقبل في <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r534/" rel="">لغة جو</a> مُتغيرًا كما باقي <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%85%D8%AA%D8%BA%D9%8A%D8%B1%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%AB%D9%88%D8%A7%D8%A8%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1844/" rel="">المتغيرات</a>، وبالتالي يُمكنك تسميته كما تشاء. يُفضّل عمومًا تسميته بأول حرف من اسم نوع البيانات الذي تُريد أن يتعامل معه التابع، فقد سُمّي المعامل في المثال السابق بالاسم <code>c</code> لأن نوع بيانات المستقبل كان <code>Creature</code>.
</p>

<p>
	أنشأنا داخل الدالة <code>main</code> نسخةً من البنية <code>Creature</code> وأعطينا حقولها <code>Name</code> و <code>Greeting</code> القيم "Sammy" و "!Hello" على التوالي. استدعينا التابع <code>Greet</code> والذي أصبح مُعرّفًا على نوع البيانات <code>Creature</code> من خلال وضع نقطة <code>Creature.Greet</code>، ومرّرنا له النسخة التي أنشأناه للتو مثل وسيط. قد يبدو هذا الأسلوب في الاستدعاء مُربكًا قليلًا، سنعرض فيما يلي أسلوبًا آخر لاستدعاء التوابع:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7729_10" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Creature</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln">     string
    </span><span class="typ">Greeting</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">c </span><span class="typ">Creature</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Greet</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%s says %s"</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="typ">Greeting</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    sammy </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Creature</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">     </span><span class="str">"Sammy"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Greeting</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Hello!"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    sammy</span><span class="pun">.</span><span class="typ">Greet</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ستحصل عند تشغيل الشيفرة على نفس الخرج السابق:
</p>

<pre class="ipsCode">Sammy says Hello!
</pre>

<p>
	لا يختلف هذا المثال عن المثال السابق إلا في استدعاء التابع <code>Greet</code>، إذ استخدمنا هنا الأسلوب المختصر، بحيث نضع اسم المتغير الذي يُمثّل البنية <code>Creature</code> وهو <code>sammy</code> متبوعًا بنقطة ثم اسم التابع بين قوسين <code>()sammy.Greet</code>. يُحبّذ دومًا استخدام هذا الأسلوب الذي يُسمّى التدوين النقطي dot notation، فمن النادر أن يستخدم المطورون الأسلوب السابق الذي يُسمّى أسلوب الاستدعاء الوظيفي functional invocation style. يوضح المثال التالي أحد الأسباب التي تجعل هذا الأسلوب أكثر انتشارًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7729_12" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Creature</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln">     string
    </span><span class="typ">Greeting</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">c </span><span class="typ">Creature</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Greet</span><span class="pun">()</span><span class="pln"> </span><span class="typ">Creature</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"%s says %s!\n"</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="typ">Greeting</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> c
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">c </span><span class="typ">Creature</span><span class="pun">)</span><span class="pln"> </span><span class="typ">SayGoodbye</span><span class="pun">(</span><span class="pln">name string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Farewell"</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"!"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    sammy </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Creature</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">     </span><span class="str">"Sammy"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Greeting</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Hello!"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    sammy</span><span class="pun">.</span><span class="typ">Greet</span><span class="pun">().</span><span class="typ">SayGoodbye</span><span class="pun">(</span><span class="str">"gophers"</span><span class="pun">)</span><span class="pln">

    </span><span class="typ">Creature</span><span class="pun">.</span><span class="typ">SayGoodbye</span><span class="pun">(</span><span class="typ">Creature</span><span class="pun">.</span><span class="typ">Greet</span><span class="pun">(</span><span class="pln">sammy</span><span class="pun">),</span><span class="pln"> </span><span class="str">"gophers"</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	عند تشغيل الشيفرة السابقة سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Sammy says Hello!!
Farewell gophers !
Sammy says Hello!!
Farewell gophers !
</pre>

<p>
	عدّلنا الأمثلة السابقة لكي نُقدّم تابعًا جديدًا يُسمى <code>SayGoodbye</code>، وعدّلنا أيضًا تعريف التابع <code>Greet</code> بحيث يُعيد أيضًا المُعامل <code>c</code> الذي يُمثل <code>Creature</code>، وبالتالي سيكون لدينا إمكانية استخدام القيمة المُعادة من هذا التابع والمُتمثّلة بنسخة <code>Creature</code>. نستدعي في الدالة <code>main</code> التابعين <code>SayGoodbye</code> و <code>Greet</code> على المتغير <code>sammy</code> باستخدام أسلوبي التدوين النقطي والاستدعاء الوظيفي.
</p>

<p>
	يعطي الأسلوبان نفس النتائج في الخرج، لكن التدوين النُقطي أسهل للقراءة، كما أنه يعرض لنا عملية استدعاء التوابع مثل تسلسل، إذ نستدعي التابع <code>Greet</code> من خلال المتغير <code>sammy</code> الذي يُمثّل <code>Creature</code>، ثم نستدعي التابع <code>SayGoodbye</code> من خلال القيمة التي يُعيدها هذا التابع والتي تمثّل <code>Creature</code> أيضًا.لا يعكس أسلوب الاستدعاء الوظيفي هذا الترتيب بسبب إضافة معامل إلى استدعاء <code>SayGoodbye</code> يؤدي إلى حجب ترتيب الاستدعاءات. وضوح التدوين النقطي هو السبب في أنه النمط المفضل لاستدعاء التوابع في <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a>، سواءً في المكتبة القياسية أو في <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D9%8A%D8%B1%D8%A7%D8%AF-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1902/" rel="">الحزم الخارجية</a>.
</p>

<p>
	تعريف التوابع على <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1791/" rel="">نوع بيانات</a> مُحدد هو أمر مُختلف عن تعريف الدوال التي تعمل بناءً على قيمة ما، وهذا له أهمية خاصة في لغة جو لأنه مفهوم أساسي في الواجهات interfaces.
</p>

<h2>
	الواجهات interfaces
</h2>

<p>
	عندما تُعرّف أي تابع على نوع بيانات في لغة جو، يضُاف هذا التابع إلى مجموعة التوابع الخاصة بهذا النوع، وهي مجموعة من الدوال المرتبطة بهذا النوع ويستخدمها <a href="https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%AA%D8%B5%D8%B1%D9%8A%D9%81-compilation-%D9%81%D9%8A-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r976/" rel="">مُصرّف</a> لغة جو لتحديد إذا كان يمكن إسناد نوع ما لمتغير من نوع "واجهة interface"؛ وهذا النوع هو نهج يعتمده المُصرّف لضمان أن مُتغيّرًا من نوع البيانات المطلوب يُحقق التوابع التي تتضمنها الواجهة. يُعد أي نوع يمتلك توابع لها نفس الاسم ونفس المعلمات ونفس القيم المُعادة؛ مثل تلك الموجودة في تعريف الواجهة منفِّذًا لتلك الواجهة ويُسمح بإسناده إلى متغيرات من تلك الواجهة. نعرض فيما يلي تعريفًا لواجهة <code>fmt.Stringer</code> من المكتبة القياسية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7729_14" style=""><span class="pln">type </span><span class="typ">Stringer</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
  </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string
</span><span class="pun">}</span></pre>

<p>
	لاحظ هنا استخدام الكلمة المفتاحية <code>type</code> لتعريف نوع بيانات جديد يُمثّل "واجهة". لكي ينفّذ أي نوع واجهة <code>fmt.Stringer</code>، يجب أن يوفر تابعًا اسمه <code>()String</code> يُعيد سلسلة نصية. سيُمكّنك تنفيذ هذه الواجهة من طباعة "نوعك" تمامًا كما تريد ويُسمى ذلك "طباعة مُرتّبة pretty-printed"، وذلك عند تمرير نسخة من النوع الخاص بك إلى دوال محددة في حزمة <code>fmt</code>. يُعرّف المثال التالي نوعًا ينفّذ هذه الواجهة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7729_16" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">(</span><span class="pln">
    </span><span class="str">"fmt"</span><span class="pln">
    </span><span class="str">"strings"</span><span class="pln">
</span><span class="pun">)</span><span class="pln">

type </span><span class="typ">Ocean</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Creatures</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">string
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">o </span><span class="typ">Ocean</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> strings</span><span class="pun">.</span><span class="typ">Join</span><span class="pun">(</span><span class="pln">o</span><span class="pun">.</span><span class="typ">Creatures</span><span class="pun">,</span><span class="pln"> </span><span class="str">", "</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func log</span><span class="pun">(</span><span class="pln">header string</span><span class="pun">,</span><span class="pln"> s fmt</span><span class="pun">.</span><span class="typ">Stringer</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">header</span><span class="pun">,</span><span class="pln"> </span><span class="str">":"</span><span class="pun">,</span><span class="pln"> s</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    o </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Ocean</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Creatures</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">string</span><span class="pun">{</span><span class="pln">
            </span><span class="str">"sea urchin"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"lobster"</span><span class="pun">,</span><span class="pln">
            </span><span class="str">"shark"</span><span class="pun">,</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    log</span><span class="pun">(</span><span class="str">"ocean contains"</span><span class="pun">,</span><span class="pln"> o</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">ocean contains : sea urchin, lobster, shark
</pre>

<p>
	صرّحنا في هذا المثال عن نوع بيانات جديد يُمثّل بنيةً اسمها <code>Ocean</code>. يُمكننا القول أن هذه البنية تنفّذ الواجهة <code>fmt.Stringer</code> لأنها تعرِّف تابعًا اسمه <code>String</code> لا يأخذ أي وسطاء ويعيد سلسلةً نصية، أي تمامًا كما في الواجهة. عرّفنا داخل الدالة <code>main</code> متغيرًا <code>o</code> يُمثّل بنيةً <code>Ocean</code> ومرّرناه إلى الدالة <code>log</code> التي تطبع سلسة نصية تُمرّر لها، متبوعةً بأي شيء تُنفّذه وليكن <code>fmt.Stringer</code>.
</p>

<p>
	سيسمح لنا مُصرّف جو هنا أن نُمرّر البنية <code>o</code> لأنه يُنفّذ كل التوابع التي تطلبها الدالة <code>fmt.Stringer</code> (هنا يوجد تابع وحيد <code>String</code>). نستخدم داخل الدالة <code>log</code> دالة الطباعة <code>fmt.Println</code> التي تستدعي التابع <code>String</code> من البنية <code>Ocean</code> لأننا مررنا لها المعامل <code>s</code> من النوع <code>fmt.Stringer</code> في ترويستها (أي بمثابة أحد معاملاتها).
</p>

<p>
	إذا لم تنفّذ البنية <code>Ocean</code> التابع <code>String</code> سيُعطينا جو خطأً في التصريف، لأن الدالة <code>log</code> تتطلب تمرير وسيط من النوع <code>fmt.Stringer</code>، وسيكون الخطأ كما يلي:
</p>

<pre class="ipsCode">src/e4/main.go:24:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log:
        Ocean does not implement fmt.Stringer (missing String method)
</pre>

<p>
	سيتحقق مُصرّف لغة جو من مطابقة التابع <code>()String</code> للتابع المُستدعى من قِبل الواجهة <code>fmt.Stringer</code>، وإذا لم يكن مُطابقًا، سيعطي الخطأ:
</p>

<pre class="ipsCode">src/e4/main.go:26:6: cannot use o (type Ocean) as type fmt.Stringer in argument to log:
        Ocean does not implement fmt.Stringer (wrong type for String method)
                have String()
                want String() string
</pre>

<p>
	استخدمت التوابع المُعرّفة في الأمثلة السابقة "مُستقبل قيمة"، أي إذا استخدمنا أسلوب الاستدعاء الوظيفي للتوابع، سيكون المعامل الأول الذي يشير إلى النوع الذي عُرّف التابع عليه قيمةً من هذا النوع وليس مؤشرًا؛ وهذا يعني أن أي تعديل نُجريه على هذا النوع المُمثّل بالمعامل المُحدد (مثلًا في المثال السابق كان هذا المعامل هو <code>o</code>) سيكون محليًّا وسيُنفّذ على نسخة من البيانات ولن يؤثر على النسخة الأصلية. سنرى فيما يلي أمثلة تستخدم مُستقبلات مرجعية "مؤشر".
</p>

<h2>
	مستقبلات مثل مؤشرات
</h2>

<p>
	يُشبه استخدام المؤشرات مثل مستقبلات في التوابع إلى حد كبير استخدام "مستقبل القيمة"، والفرق الوحيد هو وضع علامة <code>*</code> قبل اسم النوع. يوضح المثال التالي تعريف تابع على مستقبل مؤشر إلى نوع:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7729_18" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Boat</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln"> string

    occupants </span><span class="pun">[]</span><span class="pln">string
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">b </span><span class="pun">*</span><span class="typ">Boat</span><span class="pun">)</span><span class="pln"> </span><span class="typ">AddOccupant</span><span class="pun">(</span><span class="pln">name string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="typ">Boat</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    b</span><span class="pun">.</span><span class="pln">occupants </span><span class="pun">=</span><span class="pln"> append</span><span class="pun">(</span><span class="pln">b</span><span class="pun">.</span><span class="pln">occupants</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> b
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">b </span><span class="typ">Boat</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Manifest</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"The"</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"has the following occupants:"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> _</span><span class="pun">,</span><span class="pln"> n </span><span class="pun">:=</span><span class="pln"> range b</span><span class="pun">.</span><span class="pln">occupants </span><span class="pun">{</span><span class="pln">
        fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"\t"</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    b </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">Boat</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"S.S. DigitalOcean"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    b</span><span class="pun">.</span><span class="typ">AddOccupant</span><span class="pun">(</span><span class="str">"Sammy the Shark"</span><span class="pun">)</span><span class="pln">
    b</span><span class="pun">.</span><span class="typ">AddOccupant</span><span class="pun">(</span><span class="str">"Larry the Lobster"</span><span class="pun">)</span><span class="pln">

    b</span><span class="pun">.</span><span class="typ">Manifest</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">The S.S. DigitalOcean has the following occupants:
     Sammy the Shark
     Larry the Lobster
</pre>

<p>
	يُعرّف هذا المثال نوعًا يُسمّى <code>Boat</code> يمتلك حقلين هما <code>Name</code> و <code>occupants</code>. نريد حماية الحقل <code>occupants</code> بحيث لا تتمكن الشيفرات الأخرى من خارج <a href="https://academy.hsoub.com/programming/go/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1903/" rel="">الحزمة</a> أن تُعدّل عليه إلا من خلال التابع <code>AddOccupant</code>، لهذا السبب جعلناه حقلًا غير مُصدّر، وذلك بجعل أول حرف صغيرًا. نريد أيضًا جعل التعديلات التي يُجريها التابع <code>AddOccupant</code> على نفس المتغير وبالتالي نحتاج إلى تمريره بالمرجع؛ أي يجب أن نُعرّف مُستقبل مؤشر <code>(b *Boat)</code> وليس مُستقبل قيمة، إذ سيعمل مُستقبل المؤشر على إجراء التعديلات على نفس بيانات المتغير الأصلي الموجودة في الذاكرة من خلال تمرير عنوانه. تعمل المؤشرات مثل مراجع إلى متغير من نوع محدد بدلاً من نسخة من هذا النوع، لذلك سيضمن تمرير عنوان متغير من النوع <code>Boat</code> إلى التابع <code>AddOccupant</code> تنفيذ التعديلات على المتغير نفسه وليس نسخةً منه.
</p>

<p>
	نُعرّف داخل الدالة <code>main</code> متغيرًا <code>b</code> يحمل عنوان بنية من النوع <code>Boat</code>، وذلك من خلال وضع <code>&amp;</code> قبل تعريف البنية <code>(Boat*)</code> كما في الشيفرة أعلاه. استدعينا التابع <code>AddOccupant</code> مرتين لإضافة عُنصرين.
</p>

<p>
	يُعرّف التابع <code>Manifest</code> على النوع <code>Boat</code> ويستخدم مُستقبل قيمة <code>(b Boat)</code>. ما زلنا قادرين على استدعاء <code>Manifest</code> داخل الدالة <code>main</code> لأن لغة جو قادرة على تحصيل dereference قيمة المؤشر تلقائيًا من <code>Boat</code>، إذ تكافئ <code>()b.Manifest</code> هنا <code>()b).Manifest*)</code>.
</p>

<p>
	بغض النظر عن نوع المستقبل الذي تُعرّفه لتابع ما؛ مستقبل مؤشر أو مستقبل القيمة، فإن له آثارًا مهمة عند محاولة إسناد قيم للمتغيرات من نوع واجهة.
</p>

<h2>
	المستقبلات مثل مؤشرات والواجهات
</h2>

<p>
	عندما تحاول إسناد قيمة متغير من نوع محدد إلى متغير نوعه واجهة، يتأكد مُصرّف جو ما إذا كان ذلك النوع يُنفّذ كل التوابع التي تتطلبها الواجهة. تختلف مجموعة التوابع لمستقبل المؤشر عن مستقبل القيمة لأن التوابع التي تتلقى مؤشرًا يمكنها التعديل على المُستقبل الخاص بها، بينما لا يمكن لتلك التي تتلقى قيمة فعل ذلك.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7729_20" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Submersible</span><span class="pln"> interface </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Dive</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

type </span><span class="typ">Shark</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln"> string

    isUnderwater </span><span class="kwd">bool</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">s </span><span class="typ">Shark</span><span class="pun">)</span><span class="pln"> </span><span class="typ">String</span><span class="pun">()</span><span class="pln"> string </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> s</span><span class="pun">.</span><span class="pln">isUnderwater </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"%s is underwater"</span><span class="pun">,</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"%s is on the surface"</span><span class="pun">,</span><span class="pln"> s</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func </span><span class="pun">(</span><span class="pln">s </span><span class="pun">*</span><span class="typ">Shark</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Dive</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    s</span><span class="pun">.</span><span class="pln">isUnderwater </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func submerge</span><span class="pun">(</span><span class="pln">s </span><span class="typ">Submersible</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    s</span><span class="pun">.</span><span class="typ">Dive</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    s </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">Shark</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">s</span><span class="pun">)</span><span class="pln">

    submerge</span><span class="pun">(</span><span class="pln">s</span><span class="pun">)</span><span class="pln">

    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">s</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Sammy is on the surface
Sammy is underwater
</pre>

<p>
	عرّفنا واجهة تُسمى <code>Submersible</code>، تقبل أنواعًا تُنفّذ التابع <code>()Dive</code> الخاص بها. عرّفنا أيضًا النوع <code>Shark</code> مع الحقل <code>Name</code> والتابع <code>isUnderwater</code> لتتبع حالة متغيرات هذا النوع. عرّفنا أيضًا التابع <code>()Dive</code> مع مُستقبل مؤشر من النوع <code>Shark</code>، إذ يُعدّل هذا التابع قيمة التابع <code>isUnderwater</code> لتصبح <code>true</code>. عرّفنا أيضًا التابع <code>()String</code> مع مُستقبل قيمة من النوع <code>Shark</code> لطباعة حالة <code>Shark</code> بأسلوب مُرتب باستخدام <code>fmt.Println</code> ومن خلال الواجهة <code>fmt.Stringer</code> التي تعرّفنا عليها مؤخرًا. عرّفنا أيضًا الدالة <code>submerge</code> التي تأخذ معاملًا من النوع <code>Submersible</code>.
</p>

<p>
	يسمح لنا استخدام الواجهة <code>Submersible</code> بدلًا من <code>Shark*</code> في الدالة <code>submerge</code> جعل هذه الدالة تعتمد فقط على السلوك الذي يوفره النوع، وبالتالي جعلها أكثر قابلية لإعادة الاستخدام، فلن تضطر إلى كتابة دوال <code>submerge</code> جديدة لحالات خاصة أخرى مثل <code>Submarine</code> أو <code>Whale</code> أو أي كائنات مائية أخرى في وقت لاحق، فطالما أنها تُعرّف التابع <code>()Dive</code> يمكنها أن تعمل مع الدالة <code>submerge</code>.
</p>

<p>
	نُعرّف داخل الدالة <code>main</code> المتغير <code>s</code> الذي يمثّل مؤشرًا إلى <code>Shark</code> ونطبعه مباشرةً باستخدام الدالة <code>fmt.Println</code> مما يؤدي إلى طباعة <code>Sammy is on the surface</code> على شاشة الخرج. نمرر بعدها المتغير <code>s</code> إلى الدالة <code>submerge</code> ثم نستدعي الدالة <code>fmt.Println</code> مرةً أخرى على المتغير <code>s</code> مما يؤدي لطباعة <code>Sammy is underwater</code>.
</p>

<p>
	إذا جعلنا <code>s</code> من النوع <code>Shark</code> بدلًا من <code>Shark*</code>، سيعطي المُصرّف الخطأ التالي:
</p>

<pre class="ipsCode">cannot use s (type Shark) as type Submersible in argument to submerge:
    Shark does not implement Submersible (Dive method has pointer receiver)
</pre>

<p>
	يخبرنا مُصرّف جو أن <code>Shark</code> تمتلك التابع <code>Dive</code> وأن ذلك معرّفٌ في مستقبل المؤشر. عندما ترى رسالة الخطأ هذه، فإن الحل هو تمرير مؤشر إلى نوع الواجهة باستخدام العامل <code>&amp;</code> قبل اسم المتغير الذي ترغب بإسناده لمتغير آخر.
</p>

<h2>
	خاتمة
</h2>

<p>
	لا يختلف التصريح عن توابع في لغة جو كثيرًا عن تعريف الدوال التي تستقبل أنواعًا مختلفة من المتغيرات، إذ تنطبق نفس قواعد العمل مع المؤشرات. توفر لغة جو الراحة في تعريف الدوال ويسمح بجمعها في مجموعات من التوابع التي يمكن تفسيرها من خلال أنواع الواجهة. سيسمح لك استخدام التوابع بطريقة فعّالة بالعمل مع الواجهات في شيفراتك البرمجية وتحسين إمكانية الاختبار، كما يجعل الشيفرة أكثر تنظيمًا وسهلة القراءة للمطورين الآخرين.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/defining-methods-in-go" rel="external nofollow">Defining Methods in Go</a> لصاحبه Gopher Guides.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%A8%D9%86%D9%89-structs-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1976/" rel="">البنى Structs في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1792/" rel="">التعامل مع السلاسل في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D8%B3%D9%84-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1792/" rel="">المؤشرات Pointers في لغة جو Go</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1977</guid><pubDate>Sat, 27 May 2023 16:00:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x628;&#x646;&#x649; Structs &#x641;&#x64A; &#x644;&#x63A;&#x629; &#x62C;&#x648; Go</title><link>https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%A8%D9%86%D9%89-structs-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1976/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_05/-Structs----Go.png.90b85cb2175a5ca7ad7b3fe945c382e1.png" /></p>
<p>
	يُعد بناء تجريدات abstractions لتفاصيل البرنامج الخاص بك أعظم أداة يمكن أن تقدمها لغة البرمجة للمطور، إذ تسمح البُنى structs لمطوري لغة جو بوصف العالم الذي يعمل فيه البرنامج؛ فبدلًا من استخدام السلاسل النصية strings لوصف أشياء، مثل الشارع Street والمدينة City والرمز البريدي PostalCode، يمكن استخدام بنية تجمع كل هذه الأشياء تحت مفهوم "العنوان Address".
</p>

<p>
	يمكن تعريف البنية على أنها <a href="https://academy.hsoub.com/programming/general/%D9%87%D9%8A%D8%A7%D9%83%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-data-structures/" rel="">هيكل بيانات</a> يُستخدم لتعريف <a href="https://academy.hsoub.com/programming/general/%d8%af%d9%84%d9%8a%d9%84%d9%83-%d8%a7%d9%84%d8%b4%d8%a7%d9%85%d9%84-%d8%a5%d9%84%d9%89-%d8%a3%d9%86%d9%88%d8%a7%d8%b9-%d8%a7%d9%84%d8%a8%d9%8a%d8%a7%d9%86%d8%a7%d8%aa-r1726/" rel="">نوع بيانات</a> جديد يحتوي مجموعةً محددةً من القيم مختلفة النوع، ويمكن الوصول لهذه العناصر أو القيم عن طريق اسمها. تساعد البنى المطورين المستقبليين (بما في ذلك نحن) بتحديد البيانات المهمة للبرامج الخاصة بنا وكيف يجب أن تستخدم الشيفرات المستقبلية هذه البيانات بالطريقة الصحيحة.
</p>

<p>
	يمكن تعريف البنى واستخدامها بعدة طرق مختلفة. سنلقي نظرةً في هذا المقال على كل من هذه التقنيات.
</p>

<h2>
	تعريف البنى
</h2>

<p>
	يمكنك أن تتخيل البنى مثل نماذج جوجل التي يُطلب منك ملؤها أحيانًا. تتضمّن هذه النماذج حقولًا يُطلب منك تعبئتها، مثل الاسم، أو العنوان، أو البريد الإلكتروني، أو مربعات فارغة يمكن تحديد إحداها لوصف حالتك الزوجية (أعزب، متزوج، أرمل) …إلخ. يمكن أن تتضمن البُنى أيضًا حقولًا يمكنك تعبئتها. تهيئة متغير ببنية جديدة، أشبه باستخراج نسخة من نموذج جاهز للتعبئة.
</p>

<p>
	لإنشاء بنية جديدة يجب أولاً إعطاء <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r534/" rel="">لغة جو</a> مُخططًا يصف الحقول التي تحتوي عليها البنية. يبدأ تعريف البنية بالكلمة المفتاحية <code>type</code> متبوعًا باسم البنية الذي تختاره <code>Creature</code> ثم الكلمة <code>struct</code> ثم قوسين <code>{}</code> نضع بداخلهما الحقول التي نريدها في البنية. يمكنك استخدام البنية بعد الانتهاء من التصريح عنها مع المتغيرات كما لو <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1791/" rel="">أنها نوع بيانات بحد ذاته</a>.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6048_10" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Creature</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    c </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Creature</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy the Shark"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">c</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ستحصل عند تشغيل البرنامج على الخرج التالي:
</p>

<pre class="ipsCode" id="ips_uid_6048_20">Sammy the Shark</pre>

<p>
	صرّحنا في هذا المثال أولًا عن بنية اسمها <code>Creature</code> تتضمّن حقلًا اسمه <code>Name</code> من نوع سلسلة نصية <code>string</code>. نُعرّف داخل الدالة <code>main</code> مُتغيرًا <code>c</code> من النوع <code>Creature</code> ونهيئ الحقل <code>Name</code> فيه بالقيمة "Sammy the Shark"، إذ نفتح قوسين <code>{}</code> ونضع هذه المعلومات بينهما كما في الشيفرة أعلاه. أخيرًا استدعينا الدالة <code>fmt.Println</code> وطبعنا من خلالها الحقل <code>Name</code> من خلال المتغير <code>c</code> وذلك عن طريق وضع اسم المتغير متبوعًا بنقطة <code>.</code> ومتبوعًا باسم الحقل، مثل <code>c.Name</code>، الذي يعيد في هذه الحالة حقل <code>Name</code>.
</p>

<p>
	عندما نأخذ نسخةً من بنية، نذكر غالبًا اسم كل حقل ونسند له قيمة (كما فعلنا في المثال السابق). عمومًا، إذا كنت ستؤمن قيمة كل حقل أثناء تعريف نسخة من بنية فيمكنك عندها تجاهل كتابة أسماء الحقول، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6048_14" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Creature</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln"> string
    </span><span class="typ">Type</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    c </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Creature</span><span class="pun">{</span><span class="str">"Sammy"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Shark"</span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">c</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"the"</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="typ">Type</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وسيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6048_16" style=""><span class="typ">Sammy</span><span class="pln"> the </span><span class="typ">Shark</span></pre>

<p>
	أضفنا حقلًا جديدًا للبنية <code>Creature</code> باسم <code>Type</code> وحددنا نوعه <code>string</code>. أنشأنا داخل الدالة <code>main</code> نسخةً من هذه البنية وهيّأنا قيم الحقلين <code>Name</code> و <code>Type</code> بالقيمتين "Sammy" و "shark" على التوالي من خلال التعليمة:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6048_22" style=""><span class="typ">Creature</span><span class="pun">{</span><span class="str">"Sammy"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Shark"</span><span class="pun">}</span></pre>

<p>
	واستغنينا عن كتابة أسماء الحقول صراحةً.
</p>

<p>
	نُسمي هذه الطريقة بالطريقة المُختصرة للتصريح عن نسخة من بنية، وهذه الطريقة غير شائعة كثيرًا لأنها تتضمن عيوبًا مثل ضرورة تحديد قيم لجميع الحقول وبالترتيب وعدم نسيان أي حقل. نستنتج سريعًا أن استخدام هذه الطريقة لا يكون جيدًا عندما يكون لدينا عددٌ كبيرٌ من الحقول لأننا سنكون عُرضةً للخطأ والنسيان والتشتت عندما نقرأ الشيفرة مرةً أخرى. إذًا، يُفضّل استخدام هذه الطريقة فقط عندما يكون عدد الحقول قليل.
</p>

<p>
	ربما لاحظت أننا نبدأ أسماء جميع الحقول بحرف كبير، وهذا مهم جدًا لأنه يلعب دورًا في تحديد إمكانية الوصول إلى هذه الحقول؛ فعندما نبدأ اسم الحقل بحرف كبير، فهذا يعني إمكانية الوصول إليه من خارج <a href="https://academy.hsoub.com/programming/go/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1903/" rel="">الحزمة</a>، أما الحرف الصغير فلا يمكن الوصول إليها من خارج الحزمة.
</p>

<h2>
	تصدير حقول البنية
</h2>

<p>
	يعتمد تصدير حقول البنية على نفس قواعد تصدير المكونات الأخرى في <a href="https://academy.hsoub.com/programming/go/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-go-r222/" rel="">لغة جو</a>؛ فإذا بدأ اسم الحقل بحرف كبير، فسيكون قابلاً للقراءة والتعديل بواسطة شيفرة من خارج الحزمة التي صُرّح عنه فيها؛ أما إذا بدأ الحقل بحرف صغير، فلن تتمكن من قراءة وتعديل هذا الحقل إلا من شيفرة من داخل الحزمة التي صُرّح عنه فيها. يوضّح المثال التالي الأمر:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6048_24" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

type </span><span class="typ">Creature</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">Name</span><span class="pln"> string
    </span><span class="typ">Type</span><span class="pln"> string

    password string
</span><span class="pun">}</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    c </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">Creature</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Shark"</span><span class="pun">,</span><span class="pln">

        password</span><span class="pun">:</span><span class="pln"> </span><span class="str">"secret"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">c</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"the"</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="typ">Type</span><span class="pun">)</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="str">"Password is"</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="pln">password</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وسيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode">Sammy the Shark
Password is secret
</pre>

<p>
	أضفنا إلى البنية السابقة حقلًا جديدًا <code>secret</code>، وهو حقل من النوع <code>string</code> ويبدأ بحرف صغير؛ أي أنه غير مُصدّر، وأي حزمة أخرى تحاول إنشاء نسخة من هذه البنية <code>Creature</code> لن تتمكن من الوصول إلى حقل <code>secret</code>. عمومًا، يمكننا الوصول إلى هذا الحقل ضمن نطاق الحزمة، لذا إذا حاولنا الوصول إلى هذا الحقل من داخل الدالة <code>main</code> والتي بدورها موجودة ضمن نفس الحزمة بالتأكيد، فيمكننا الرجوع لهذا الحقل <code>c.password</code> والحصول على القيمة المُخزنة فيه. وجود حقول غير مُصدّرة أمر شائع في البنى مع إمكانية وصول بواسطة توابع مُصدّرة exported.
</p>

<h2>
	البنى المضمنة Inline
</h2>

<p>
	تُسمى أيضًا البنى السريعة. تُمكّنك لغة جو من تعريف بُنى في أي وقت تريده وفي أي مكان ودون الحاجة للتصريح عنها على أنها نوع بيانات جديد بحد ذاته، وهذا مفيد في الحالات التي تكون فيها بحاجة إلى استخدام بنية مرةً واحدةً فقط (أي لن تحتاج إلى إنشاء أكثر من نسخة)، فمثلًا، تستخدم الاختبارات غالبًا بنيةً لتعريف جميع المعاملات التي تُشكل حالة اختبار معينة. سيكون ابتكار أسماء جديدة مثل <code>CreatureNamePrintingTestCase</code> مرهقًا لدى استخدام هذه البنية في مكان واحد فقط.
</p>

<p>
	يكون كتابة البنى المُضمّنة بنفس طريقة كتابة البنى العادية تقريبًا، إذ نكتب الكلمة المفتاحية <code>struct</code> متبوعةً بقوسين <code>{}</code> نضع بينهما الحقول وعلى يمين اسم المتغير. يجب أيضًا وضع قيم لهذه الحقول مباشرةً من خلال استخدام قوسين آخرين <code>{}</code> كما هو موضح في الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6048_26" style=""><span class="pln">package main

</span><span class="kwd">import</span><span class="pln"> </span><span class="str">"fmt"</span><span class="pln">

func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    c </span><span class="pun">:=</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pln"> string
        </span><span class="typ">Type</span><span class="pln"> string
    </span><span class="pun">}{</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Sammy"</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Shark"</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    fmt</span><span class="pun">.</span><span class="typ">Println</span><span class="pun">(</span><span class="pln">c</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"the"</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="typ">Type</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ويكون الخرج كما يلي:
</p>

<pre class="ipsCode">Sammy the Shark
</pre>

<p>
	لاحظ هنا أننا لم نحتاج إلى تعريف نوع بيانات جديد لتمثيل البنية، وبالتالي لم نكتب الكلمة المفتاحية <code>type</code>، فكل ما نحتاجه هنا هو استخدام الكلمة المفتاحية <code>struct</code> إشارةً إلى البنية، وإلى معامل الإسناد القصير <code>=:</code>. نحتاج أيضًا إلى تعريف قيم الحقول مباشرةً كما فعلنا في الشيفرة أعلاه. الآن أصبح لدينا متغيرًا اسمه <code>c</code> يُمثّل بنيةً يمكن الوصول لحقولها من خلال النقطة <code>.</code> كما هو معتاد. سترى البنى المُضمّنة غالبًا في الاختبارات، إذ تُعرّف البنى الفردية بصورةٍ متكررة لاحتواء البيانات والتوقعات لحالة اختبار معينة.
</p>

<h2>
	خاتمة
</h2>

<p>
	البُنى هي كتل بيانات غير متجانسة (أي أنها تضم حقولًا أو عناصر من أنواع بيانات مختلفة) يعرّفها المبرمجون لتنظيم المعلومات. تتعامل معظم البرامج مع أحجام هائلة من البيانات، وبدون البنى سيكون من الصعب تذكر أي من <a href="https://academy.hsoub.com/programming/go/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D9%85%D8%AA%D8%BA%D9%8A%D8%B1%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%AB%D9%88%D8%A7%D8%A8%D8%AA-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1844/" rel="">المتغيرات</a> ترتبط معًا وأيّها غير مرتبطة أو أيّها من نوع <code>string</code> وأيها من نوع <code>int</code>. لذلك إذا كنت تتعامل مع مجموعة من المتغيرات، اسأل نفسك عما إذا كان تجميعها ضمن بنية سيكون أفضل، إذ من الممكن أن تصف هذه المتغيرات مفهومًا عالي المستوى، فيمكن مثلًا أن يشير أحد المتغيرات إلى عنوان شركة حسوب وهناك متغيّر آخر يخص عنوان شركة أُخرى.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/defining-structs-in-g" rel="external nofollow">Defining Structs in Go</a> لصاحبه Gopher Guides.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/go/%D8%A7%D9%84%D9%85%D8%A4%D8%B4%D8%B1%D8%A7%D8%AA-pointers-%D9%81%D9%8A-%D9%84%D8%BA%D8%A9-%D8%AC%D9%88-go-r1975/" rel="">المؤشرات Pointers في لغة جو Go</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D9%87%D9%8A%D8%A7%D9%83%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-data-structures/" rel="">هياكل البيانات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/java/%D8%A7%D9%84%D9%85%D9%83%D8%AF%D8%B3-stack-%D9%88%D8%A7%D9%84%D8%B1%D8%AA%D9%84-queue-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AC%D8%B1%D8%AF%D8%A9-adt-r1396/" rel="">المكدس Stack والرتل Queue وأنواع البيانات المجردة ADT</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1976</guid><pubDate>Sat, 20 May 2023 16:03:01 +0000</pubDate></item></channel></rss>
