مرحبا بالعالم!

يمكنك العثور على جميع الشيفرة لهذا الفصل هنا

من التقليد أن يكون برنامجك الأول في اي لغة جديدة مرحبًا بالعالم.

  • قم بإنشاء مجلد أينما تريد
  • ضع ملفًا جديدًا فيه بالاسم hello.go وضع الشيفرة التالية داخله
hello.go
package main

import "fmt"

func main() {
	fmt.Println("Hello, world")
}

لتشغيله، اكتب go run hello.go

كيف تم تشغيلة

عندما تكتب برنامجًا في Go، ستكون لديك حزمة main معرفة مع دالة main داخلها. الحزم هي طرق لتجميع الشيفرة الخاصة بـ Go ذات الصلة معًا.

كلمة func هي كيفية تعريف دالة باسم ومحتوى.

باستخدام import "fmt" نقوم بتوريد حزمة تحتوي على دالة Println التي نستخدمها للطباعة.

كيفية الاختبار

كيف يمكنك اختبار هذا؟ من الجيد فصل شيفرتك “المجالية” عن العالم الخارجي (الآثار الجانبية). fmt.Println هو اثر جانبي (الطباعة إلى stdout) والنص الذي نرسله هو مجالنا.

لذا دعنا نفصل هذه القضايا حتى يكون من الأسهل اختبارها.

hello.go
package main

import "fmt"

func Hello() string {
	return "Hello, world"
}

func main() {
	fmt.Println(Hello())
}

لقد قمنا بإنشاء دالة جديدة مرة أخرى باستخدام func ولكن هذه المرة قمنا بإضافة كلمة رئيسية أخرى string في التعريف. هذا يعني أن هذه الدالة تقوم بارجاع string.

string يعني سلسلة من الاحرف (نص)

الآن قم بإنشاء ملف جديد يسمى hello_test.go حيث سنكتب اختبارًا لدالتنا Hello.

hello_test.go
package main

import "testing"

func TestHello(t *testing.T) {
	got := Hello()
	want := "Hello, world"

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}

الوحدات في Go؟

الخطوة التالية هي تشغيل الاختبارات. أدخل go test في الطرفية الخاصة بك. إذا نجحت الاختبارات، فمن المحتمل أنك تستخدم نسخة سابقة من Go. ومع ذلك، إذا كنت تستخدم Go 1.16 أو أحدث، فمن المرجح أن الاختبارات لن تعمل على الإطلاق. بدلاً من ذلك، سترى رسالة خطأ مثل هذه في الطرفية:

terminal
$ go test
go: cannot find main module; see 'go help modules'

ما هي المشكلة؟ بكلمة واحدة، الوحدات. لحسن الحظ، من السهل حل المشكلة. أدخل

go mod init hello

في الطرفية الخاصة بك. سينشئ ذلك ملفًا جديدًا بالمحتويات التالية:

go.mod
module hello

go 1.16

يخبر هذا الملف أدوات go معلومات أساسية حول شيفرتك. إذا كنت تخطط لتوزيع تطبيقك، فستضمن حيث تكون الشيفرة متاحة للتنزيل بالإضافة إلى معلومات حول الاعتماديات. في الوقت الحالي، ملف الوحدة الخاص بك هو أدنى حد، ويمكنك تركه بهذه الحالة. لقراءة المزيد حول الوحدات، يمكنك التحقق من المرجع في توثيق Golang. يمكننا العودة إلى اختبار وتعلم Go الآن لأن الاختبارات يجب أن تعمل، حتى على Go 1.16.

في الفصول القادمة، ستحتاج إلى تشغيل go mod init SOMENAME في كل مجلد جديد قبل تشغيل الأوامر مثل go test أو go build.

العودة إلى الاختبار

قم بتشغيل go test في الطرفية الخاصة بك. يجب أن ينجح الاختبار! فقط للتحقق، حاول تعمدًا كسر الاختبار عن طريق تغيير النص في المتغير want.

لاحظ كيف لم تكن مضطرًا لاختيار بين إطارات اختبار متعددة ثم معرفة كيفية التثبيت. كل ما تحتاجه مدمج في لغة البرمجة والصيغة النحوية هي نفس الصيغة التي ستكتب بها بقية الشيفرة.

كتابة الاختبارات

كتابة اختبار هو مثل كتابة دالة، مع بعض القواعد

  • يجب أن يكون في ملف بأسم مثل xxx_test.go
  • يجب أن تبدأ دالة الاختبار بالكلمة Test
  • تأخذ دالة الاختبار معامل واحد فقط t *testing.T
  • من أجل استخدام نوع *testing.T، تحتاج إلى استيراد “testing”، كما فعلنا مع “fmt” في الملف الآخر

في الوقت الحالي، من الكافي معرفة أن t من نوع *testing.T هو مدخلك الخاص الى إطار الاختبار حتى تتمكن من فعل أشياء مثل t.Fail() عندما تريد ان يفشل التشغيل.

لقد غطينا بعض المواضيع الجديدة:

الشرط if

العبارات الشرطية if في Go تشبه إلى حد كبير لغات البرمجة الأخرى.

تعريف المتغيرات

نقوم بتعريف بعض المتغيرات بناءً على الصيغة varName := value، مما يتيح لنا إعادة استخدام بعض القيم في اختبارنا لتحسن مقرؤيتها.

التابعة t.Errorf

نحن نقوم باستدعاء التابعة Errorf على t الخاص بنا والتي ستقوم بطباعة رسالة وإخفاق الاختبار. الحرف f يشير إلى الصيغة التي تسمح لنا ببناء سلسلة نصية مع قيم مدخلة في القيم النائبة %q. عندما تجعل الاختبار يفشل، يجب أن يكون توضح كيفية عمله.

يمكنك قراءة المزيد حول القيم النائبة في مستندات fmt go. بالنسبة للاختبارات، %q مفيد جدًا حيث يضع قيمك في علامات اقتباس مزدوجة.

سنقوم لاحقًا باستكشاف الفرق بين التوابع (methods) والدوال (functions).

الامر Go doc

ميزة أخرى في Go هي الوثائق. يمكنك تشغيل الوثائق محليًا عن طريق تشغيل godoc -http :8000. إذا ذهبت إلى localhost:8000/pkg سترى جميع الحزم المثبتة على نظامك.

تمتلك الغالبية العظمى من المكتبات القياسية وثائق ممتازة مع أمثلة. من الجيد الانتقال إلى http://localhost:8000/pkg/testing/ لرؤية ما هو متاح لك.

إذا لم يكن لديك الأمر godoc، فقد تكون تستخدم الإصدار الأحدث من Go (1.14 أو أحدث) الذي لا يتضمن بعد godoc. يمكنك تثبيته يدويًا بتشغيل الامر go install golang.org/x/tools/cmd/godoc@latest في الطرفية.

مرحبًا، أنت

الآن بعد أن اصبح لدينا اختبار، يمكننا تكرار تطوير البرنامج بأمان.

في المثال السابق، كتبنا الاختبار بعد كتابة الشيفرة فقط لتحصل على مثال على كيفية كتابة اختبار وتعريف دالة. من هذه النقطة وما بعدها، سنقوم بكتابة الاختبارات أولاً.

متطلبنا التالي هو السماح بتحديد مستلم للتحية.

لنبدأ بتوثيق هذه المتطلبات في اختبار. هذا هو التطوير القائم او المعتمد على الاختبارات والذي يسمح لنا بالتأكد من أن الاختبار الخاص بنا يختبر بالفعل ما نريد. عندما تكتب الاختبارات بشكل متأخر، هناك خطر أن يستمر اختبارك في النجاح حتى لو لم تعمل الشيفرة كما هو مقصود لها ان تعمل.

hello_test.go
package main

import "testing"

func TestHello(t *testing.T) {
	got := Hello("Chris")
	want := "Hello, Chris"

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}

الآن قم بتشغيل go test، يجب أن يحدث خطأ في الترجمة

terminal
./hello_test.go:6:18: too many arguments in call to Hello
    have (string)
    want ()

عند استخدام لغة محددة النوع (statically typed) مثل Go، من المهم أن تستمع إلى المترجم.

في هذه الحالة، المترجم يخبرك بما تحتاج إلى القيام به للمتابعة. علينا تغيير دالتنا Hello لتقبل معامل من نوع string.

قم بتحرير دالة Hello لتقبل معامل من نوع string.

hello.go
func Hello(name string) string {
	return "Hello, world"
}

إذا حاولت تشغيل الاختبارات مرة أخرى، ستفشل الترجمة لملف hello.go لأنك لم تقم بتمرير معامل name. قم بإرسال “world” لتجعلها تترجم بنجاح.

hello.go
func main() {
	fmt.Println(Hello("world"))
}

الان عندما تقوم بتشغيل الاختبار يجب ان ترى شيئاً مماثل لـ:

terminal
hello_test.go:10: got 'Hello, world' want 'Hello, Chris''

لدينا الآن برنامج يترجم بنجاح ولكنه لا يلبي متطلباتنا وفقًا للاختبار.

لنجعل الاختبار ينجح بواسطة استخدام معامل الاسم ودمجه مع Hello

hello.go
func Hello(name string) string {
	return "Hello, " + name
}

عند تشغيل الاختبارات، يجب أن تنجح الآن. عادةً كجزء من دورة TDD، يجب أن نقوم الآن بالـ تنقيح (اعادة الكتابة).

ملاحظة حول التحكم في الشيفرة المصدرية

في هذه المرحلة، إذا كنت تستخدم التحكم في الشيفرة المصدرية git (الذي يجب عليك استخدامه!)، يجب أن تقوم بـ commit للشيفرة كما هي. لدينا برنامج يعمل معتمد على اختبار.

لكن لا يجب عليك دفع الشيفرة إلى (main) حتى الآن، سنقوم بالتنقيح واعادة الكتابة بعد ذلك. من الجميل أن تقوم بـ commit في هذه المرحلة في حالة دخولك في فوضى أثناء اعادة الكتابة - يمكنك دائمًا العودة إلى النسخة التي كانت تعمل مسبقاً.

لا يوجد الكثير لاعادة كتابتة هنا، ولكن يمكننا أن نقدم ميزة لغوية أخرى في go، الثوابت (constants).

الثوابت

تُعرّف كما يلي:

hello.go
const englishHelloPrefix = "Hello, "

الان بأمكاننا اعادة كتابة شيفرتنا كالتالي

hello.go
const englishHelloPrefix = "Hello, "

func Hello(name string) string {
	return englishHelloPrefix + name
}

بعد اعادة الكتابة، أعد تشغيل الاختبارات للتأكد من أنك لم تقم بتخريب شيئًا في الشيفرة.

من الجيد التفكير في إنشاء الثوابت لتقديم معنى للقيم وأحيانًا لدعم الأداء.

مرحبًا، بالعالم… مجدداً

المتطلب التالي هو عندما يتم استدعاء دالتنا بمعلمة سلسلة فارغة، يجب أن تعيد بشكل افتراضي طباعة “Hello, World” بدلاً من “Hello, “.

ابدأ بكتابة اختبار جديد.

hello_test.go
func TestHello(t *testing.T) {
	t.Run("saying hello to people", func(t *testing.T) {
		got := Hello("Chris")
		want := "Hello, Chris"

		if got != want {
			t.Errorf("got %q want %q", got, want)
		}
	})
	t.Run("say 'Hello, World' when an empty string is supplied", func(t *testing.T) {
		got := Hello("")
		want := "Hello, World"

		if got != want {
			t.Errorf("got %q want %q", got, want)
		}
	})
}

هنا نقوم بإدخال أداة أخرى في مجموعة أدوات الاختبارات الخاصة بنا، وهي الاختبارات الفرعية. في بعض الأحيان، من المفيد تجميع الاختبارات حول “شيء” معين ثم إنشاء اختبارات فرعية تصف سيناريوهات مختلفة.

فائدة هذا النهج هو أنه يمكنك إعداد شيفرة مشتركة يمكن استخدامها في الاختبارات الأخرى.

بينما لدينا اختبار يفشل، دعنا نقوم بإصلاح الشيفرة باستخدام if.

hello.go
const englishHelloPrefix = "Hello, "

func Hello(name string) string {
	if name == "" {
		name = "World"
	}
	return englishHelloPrefix + name
}

إذا قمنا بتشغيل الاختبارات، يجب أن نرى أنها تلبي المتطلب الجديد وأننا لم نقم بتخريب الوظائف الأخرى بطريق الخطأ.

من المهم أن تكون اختباراتك واضحة المواصفات لما يجب على الشيفرة أن تفعله. ولكن هناك شيفرة متكررة عندما نتحقق من ما إذا كانت الرسالة هي ما نتوقعه.

اعادة الكتابة ليس للشيفرة الإنتاجية فقط!

الآن بعد أن تم تجاوز الاختبارات، يمكننا ويجب علينا أن نقوم بأعادة كتابة اختباراتنا ايضا.

hello_test.go
func TestHello(t *testing.T) {
	t.Run("saying hello to people", func(t *testing.T) {
		got := Hello("Chris")
		want := "Hello, Chris"
		assertCorrectMessage(t, got, want)
	})

	t.Run("empty string defaults to 'world'", func(t *testing.T) {
		got := Hello("")
		want := "Hello, World"
		assertCorrectMessage(t, got, want)
	})

}

func assertCorrectMessage(t testing.TB, got, want string) {
	t.Helper()
	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}

هنا قمنا باعادة كتابة شيفرة التأكد في دالة جديدة. هذا يقلل من التكرار ويحسن قراءة اختباراتنا. يجب أن نمرر t *testing.T لنستطيع إخبار شيفرة الاختبار بالاخفاق عند الحاجة.

بالنسبة للدوال المساعدة مثل التي كتبناها، يجب أن تقبل testing.TB وهي واجهة تُستوفى من قِبل *testing.T و *testing.B، بحيث يمكنك استدعاء الدوال المساعدة من اختبار، أو اختبار أداء (لا تقلق إذا كانت كلمات مثل “واجهة” لا تعني لك شيئًا الآن، سنتعرف على ذلك لاحقًا).

نحتاج t.Helper() هنا لإخبار اطار الاختبارات أن هذه الدالة هي مساعد. عند فشل الاختبار، بفعل ذلك عندما يخفق الاختبار ستقوم بأرجاع السطر البرمجي الذي حدث فيه الخطأ في داله الاختبار بدلاً من داخل مساعد الاختبار. سيُساعد ذلك المطورين الآخرين في تتبع المشاكل بشكل أسهل. إذا لم تزل لا تفهم، قم بتعليق الكود، اجعل اختبار يخفق، ولاحظ مخرجات الاختبار. التعليقات في Go هي طريقة رائعة لإضافة معلومات إضافية لشيفرتك، أو في هذه الحالة، طريقة سريعة لإخبار المترجم بتجاهل السطر. يمكنك تعليق الكود t.Helper() عن طريق إضافة شرطتين مائلتين // في بداية السطر. يجب أن ترى أن السطر يتحول إلى اللون الرمادي أو يتغير لونه إلى لون آخر غير لون الشيفرة الأخرى للدلالة على أنه تم تعليقه الآن.

التحكم في الشيفرة المصدرية مجدداً

الآن بعدما اصبحنا راضون عن الشيفرة، يمكننا التعديل على الـ commit السابق حتى نقوم بتحديث النسخة الممتازة من الشيفرة مع اختبارها.

الانضباط

لنراجع دورة العمل مرة أخرى:

  • كتابة اختبار
  • التأكد من ترجمة البرنامج من دون اخطاء
  • تشغيل الاختبار، رؤية اخفاقة والتأكد من أن رسالة الخطأ ذات معنى
  • كتابة الشيفرة الكافية لجعل الاختبار ينجح
  • اعادة الكتابة

يمكن أن يبدو هذا المسار مملًا، ولكن التمسك بدورة التغذية الراجعة هو أمر مهم.

لا يساعد ذلك فقط في التأكد من أن لديك اختبارات ذات صلة، بل يساعد أيضًا في ضمان تصميم برمجيات جيدة من خلال اعادة الكتابة متأمناً بالاختبارات.

رؤية اخفاق الاختبار هو فحص مهم لأنه يتيح لك أيضًا رؤية شكل رسالة الخطأ. بالنسبة للمطور، قد يكون من الصعب جدًا العمل مع شيفرة ما عندما لا توفر الاختبارات الغير ناجحة فكرة واضحة عن المشكلة.

من خلال التأكد من أن اختباراتك سريعة وإعداد أدواتك بحيث يكون تشغيل الاختبارات بسيطًا، يمكنك ذلك من كتابة البرنامج بشكل اسرع وافضل.

لكن عند عدم كتابة الاختبارات، فأنت تلتزم بفحص الشيفرة يدويًا عن طريق تشغيل البرنامج الخاص بك، مما سوف يجعلك بطيئاً في التطوير ولن توفر لنفسك أي وقت، خاصة على المدى الطويل.

لنستمر! المزيد من المتطلبات

لدينا المزيد من المتطلبات الآن. نحتاج الآن لدعم معلمة ثانية، تحديد لغة التحية. إذا تم تمرير لغة لم نتعرف عليها، سنقوم باخيار اللغة الإنجليزية بشكل افتراضي.

يجب أن نكون واثقين من أنه يمكننا استخدام TDD لتطوير هذه الوظيفة بسهولة!

اكتب اختبارًا لمستخدم يمرر الإسبانية. أضفه إلى الحزمة الاختبار السابق.

hello_test.go
	t.Run("in Spanish", func(t *testing.T) {
		got := Hello("Elodie", "Spanish")
		want := "Hola, Elodie"
		assertCorrectMessage(t, got, want)
	})

تذكر ألا تحتال! اكتب الاختبار أولاً. عندما تحاول تشغيل الاختبار، يجب أن يقوم المترجم بالشكوى لأنك تستدعي Hello بمعاملين بدلاً من واحد.

terminal
./hello_test.go:27:19: too many arguments in call to Hello
    have (string, string)
    want (string)

حل مشكلة الترجمة عن طريق إضافة معامل نصي إضافي إلى Hello اسمه language من نوع string.

hello.go
func Hello(name string, language string) string {
	if name == "" {
		name = "World"
	}
	return englishHelloPrefix + name
}

عندما تحاول تشغيل الاختبار مرة أخرى، سيشتكي من عدم تمرير عدد كافٍ من المعاملات إلى Hello في الاختبارات الأخرى وفي hello.go.

terminal
./hello.go:15:19: not enough arguments in call to Hello
    have (string)
    want (string, string)

قم بتصحيحها عن طريق تمرير نص فارغ. الآن يجب أن تعمل جميع الاختبارات، باستثناء المتطلب الجديد.

terminal
hello_test.go:29: got 'Hello, Elodie' want 'Hola, Elodie'

يمكننا استخدام if هنا للتحقق مما إذا كانت اللغة تساوي “Spanish”، وإذا كانت كذلك يتم تغيير الرسالة.

hello.go
func Hello(name string, language string) string {
	if name == "" {
		name = "World"
	}

	if language == "Spanish" {
		return "Hola, " + name
	}
	return englishHelloPrefix + name
}

الاختبارات يجب أن تنجح الآن.

الآن حان الوقت لاعادة الكتابة. ربما رأيت بعض المشاكل في الشيفرة، “نصوص سحرية”، بعضها متكرر. حاول تنقيحها بنفسك، ومع كل تغيير تأكد من إعادة تشغيل الاختبارات للتأكد من أن اعادة الكتابة لم تؤدي إلى تخريب أي شيء.

hello.go
	const spanish = "Spanish"
	const englishHelloPrefix = "Hello, "
	const spanishHelloPrefix = "Hola, "

	func Hello(name string, language string) string {
		if name == "" {
			name = "World"
		}

		if language == spanish {
			return spanishHelloPrefix + name
		}
		return englishHelloPrefix + name
	}

الفرنسية

  • اكتب اختبارًا يؤكد أنه إذا قمت بتمرير "French" ستحصل على "Bonjour, "
  • تأكد من فشل الاختبار، وتحقق من سهولة قراءة رسالة الخطأ
  • قم بأصغر تغيير معقول في الشيفرة

قد تكون كتبت شيئًا يبدو تقريبًا مثل هذا

hello.go
func Hello(name string, language string) string {
	if name == "" {
		name = "World"
	}

	if language == spanish {
		return spanishHelloPrefix + name
	}
	if language == french {
		return frenchHelloPrefix + name
	}
	return englishHelloPrefix + name
}

switch

عندما تكون هناك الكثير من السطور التي تحتوي على if التي تفحص قيمة معينة، فمن الشائع استخدام switch بدلاً من ذلك. يمكننا استخدام switch لاعادة كتابة الشيفرة لجعلها مقروءة أكثر وقابلة للتوسع إذا أردنا إضافة دعم لغات أخرى لاحقًا.

hello.go
func Hello(name string, language string) string {
	if name == "" {
		name = "World"
	}

	prefix := englishHelloPrefix

	switch language {
	case "French":
		prefix = frenchHelloPrefix
	case "Spanish":
		prefix = spanishHelloPrefix
	}

	return prefix + name
}

اكتب اختبارًا الآن يتضمن تحية بلغتك المفضلة وسترى مدى بساطة الاضافة الى دالتنا الرائعة.

آخر اعادة كتابة؟

يمكن أن نقول ربما دالتنا بدأت تصبح كبيرة قليلاً. أبسط اعادة كتابة لها سيكون باستخراج بعض الوظائف إلى دالة أخرى.

hello.go

const (
	french  = "French"
	spanish = "Spanish"

	englishHelloPrefix = "Hello, "
	spanishHelloPrefix = "Hola, "
	frenchHelloPrefix  = "Bonjour, "
)

func Hello(name string, language string) string {
	if name == "" {
		name = "World"
	}

	return greetingPrefix(language) + name
}

func greetingPrefix(language string) (prefix string) {
	switch language {
	case french:
		prefix = frenchHelloPrefix
	case spanish:
		prefix = spanishHelloPrefix
	default:
		prefix = englishHelloPrefix
	}
	return
}

بعض المفاهيم الجديدة:

  • في توقيع دالتنا، قمنا بإنشاء قيمة إرجاع مسماة (prefix string).
    • سيتم إنشاء متغير يسمى prefix في الدالة.
      • سيتم تعيينه بقيمة “صفر”. هذا يعتمد على نوع البيانات، على سبيل المثال، للأعداد الصحيحة int هو 0 والنصوص string هو "".
        • يمكنك إرجاع ما تم تعيينه إليه ببساطة باستدعاء return دون return prefix.
    • سيتم عرض هذا في Go Doc لدالتك مما يجعل مقصود الكود واضحاً.
  • الـ default في حالة switch سيتم التحول إليه إذا لم تتطابق أي من بيانات case الأخرى.
  • اسم الدالة يبدأ بحرف صغير. في Go، تبدأ الوظائف العامة (التي يمكن استخدامها من خارج الحزمة) بحرف كبير والخاصة تبدأ بحرف صغير. نحن لا نريد أن تكون تفاصيل خوارزميتنا معروضة للكل، لذا جعلنا هذه الدالة خاصة.
  • أيضًا، يمكننا تجميع الثوابت في كتلة بدلاً من تعريفها كل واحدة في سطر منفصل. من الجيد استخدام سطر بين مجموعات الثوابت ذات الصلة لتحسين مقرؤيتها.

ختامًا

من كان يتوقع أنه بالامكان الحصول على الكثير من برنامج بسيط مثل “Hello, world”؟

الآن يجب أن تكون لديك فهم بعض جوانب لغة Go فيما يتعلق بالـ:

كتابة الاختبارات

  • تعريف الدوال، مع المعلمات وأنواع الإرجاع
  • if، const و switch
  • تعريف المتغيرات والثوابت

عملية TDD ولماذا تلك الخطوات مهمة

  • كتابة اختبار فاشل ورؤية فشله حتى نتأكد من أننا قد كتبنا اختبارًا ذا صلة لمتطلباتنا ورأينا أنه ينتج وصفًا سهل الفهم الاخفاق
  • كتابة أقل كمية من الكود لجعل الاختبار ينجح حتى نتأكد من وجود برنامج يعمل
  • ثم اعادة الكتابة، مدعومين بأمان اختباراتنا للتأكد من وجود كود جيد المستوى وسهل العمل معه

في حالتنا، لقد تقدمنا من Hello() إلى Hello("name")، ثم إلى Hello("name", "French") في خطوات صغيرة وسهلة الفهم.

هذا بالطبع بسيط مقارنة بالبرمجيات في “العالم الحقيقي” لكن المبادئ تظل قائمة. TDD هو مهارة تحتاج إلى ممارسة لتطويرها، ولكن من خلال تقسيم المشكلات إلى مكونات أصغر يمكنك اختبارها، ستكون لديك سهولة أكبر في كتابة البرمجيات.