تایپ Golang مانند یک ارشد

معرفی
این پست ترجمهای است از پست Write Go مانند یک مهندس ارشد، وقتی آن را خواندم برایم جالب بود که آن را از عنوان به اشتراک بگذارم، زیرا هنگام برنامهنویسی در Go به چندین نکته مهم اشاره میکند که عمیقتر کردن آن جالب است. به این خواندن و نظرات، مثل همیشه، چند نکته را بر اساس تجربه ام اضافه می کنم.
پارامترها در Go توسط مقدار ارسال می شوند
به طور پیش فرض، golang بلیط ها را بر اساس مقدار در توابع خود می پذیرد، به این معنی که آنچه در تابع دریافت می شود کپی مقداری که پاس می کنیم. این امر از قطع شدن مقدار ساختاری که در حال ارسال آن هستیم در داخل تابعی که فراخوانی می کنیم جلوگیری می کند.
بیایید یک مثال را ببینیم:
package main
import "fmt"
type Operation struct {
Value float64
}
// Recibimos la operación y le "agregamos los impuestos"
func addTaxes(operation Operation) {
operation.Value += operation.Value * 0.21
}
func main() {
operation := Operation{Value: 100}
// Llamamos a la función que agrega los impuestos
addTaxes(operation)
fmt.Printf("The total amount is: %f \n", operation.Value)
}
در پایان این تابع، تشخیص میدهیم که مقدار تغییر نمیکند، با این کار نشان داده میشود که پاساژ براساس مقدار است. در عوض، اگر میخواهیم مقدار عملیات را تغییر دهیم، باید اشارهگر (ارجاع به ساختار) را دریافت کنیم. operation
) همانطور که در زیر می بینیم:
package main
import "fmt"
type Operation struct {
Value float64
}
// Ahora la función recibe la referencia a una `Operation`
func addTaxes(operation *Operation) {
operation.Value += operation.Value * 0.21
}
func main() {
operation := Operation{Value: 100}
// Ahora se envia la direccion de memoria de `operation`
addTaxes(&operation)
fmt.Printf("The total amount is: %f \n", operation.Value)
}
در این مورد می بینیم که مقدار تغییر می کند زیرا ما در حال تغییر مقدار مرجع هستیم و نه یک کپی.
از اشاره گر استفاده کنید (اما سوء استفاده نکنید)
اپراتور *
و اپراتور &
همانطور که در مثال قبل دیدیم، هنگام برخورد با اشاره گرها، می توانیم از نمادها استفاده کنیم *
y &
اما هر کدام به چه معناست؟
اپراتور *
اعلان یک اشاره گر است، برای مثال می توانیم اعلان زیر را داشته باشیم:
var operation *Operation
به این معنی که متغیر عملیات از نوع خود است نشانگر عملیات.
در عوض، نماد &
یعنی انتقال مقدار یک اشاره گر متغیر یا مقدار دهی اولیه یک اشاره گر ساختار:
// Aca creamos un puntero a una nueva estructura del tipo Operation
operation = &Operation{Value: 100}
// Aca pasamos la referencia, o locación de memoria de la variable operation
addTaxes(&operation)
اگرچه در پست اصلی می گوید
اگر تا به حال به این فکر کرده اید که “آیا باید از یک اشاره گر در اینجا استفاده کنم؟” پاسخ احتمالاً “بله” است. وقتی شک دارید، از یک اشاره گر استفاده کنید.
من ترجیح می دهم از اشاره گر پرهیز کنم، این را در پست بعدی توضیح خواهم داد زیرا پارچه ای برای برش وجود دارد.
شجاعت nil
ارزش nil
مقدار صفر اشاره گرها در Go است که یکی از دلایل آن است وحشت in go، زیرا گاهی اوقات با اشاره گرهای تهی بدون علامت کار می کنید. این یکی از دلایلی است که من سعی می کنم از نشانگرها اجتناب کنم، زیرا آن بررسی قبل از اجرا مورد نیاز است:
// `operation` es `nil`
var operation *Operation
// Al no estar chequeado, en el método nos arroja el panic
addTaxes(operation)
برای جلوگیری از این امر، باید بررسی کنیم و مطمئن شویم که نشانگر به صورت زیر خالی نخواهد بود:
// `operation` es `nil`
var operation *Operation
// Estamos chequeando para evitar `operation == nil`
if operation != nil {
addTaxes(operation)
fmt.Printf("The total amount is: %f \n", operation.Value)
}
اینترفیس ها را در جایی که از آنها استفاده خواهید کرد، اعلام کنید
اینترفیس ها در Golang صریح هستند، بنابراین لازم نیست اعلام کنیم که از کجا گسترش یافته اند، به این معنی که برای گسترش یک رابط، فقط باید متدهای آن را پیاده سازی کرد، بیایید یک مثال را ببینیم:
// UserRepository es el contrato que debemos cumplir
type UserRepository interface {
FindByID(id string) User
Save(user User)
}
// Esta será nuestra estructura que herede de la interfaz,
// notemos que no es necesario aclarar que extiende
// de UserRepository
type SQLUserRepository struct {
}
// FindByID y Save son los métodos que implementamos para poder extender
func (S SQLUserRepository) FindByID(id string) User {
panic("implement me")
}
func (S SQLUserRepository) Save(user User) {
panic("implement me")
}
این راه ایجاد رابط است، چگونه می توانیم مطمئن شویم که کلاس ما قرارداد را انجام می دهد، ما 2 گزینه داریم:
// La primera que suelo hacer, es crear un constructor
func NewSQL() UserRepository { // ← this is the line
return SQLUserRepository{}
}
// La segunda, es una recomendación escrita en el post
var _ UserRepository = (*SQLUserRepository)(nil)
تست های روی میزها را ترجیح دهید، اما زیاده روی نکنید
در تجربه خود در توسعه Golang با این تست ها برخورد کردم و مجبور شدم با این تست ها دست و پنجه نرم کنم، نه به این دلیل که آنها بد انجام شده اند، بلکه به این دلیل که اگر منطق روش ها پیچیده باشد، حفظ آنها در طول زمان بسیار دشوار است. بله، برای تست ورودی ها و خروجی های تست ها به خوبی کار می کنند:
func Multiply(n1, n2 int) int {
return n1 * n2
}
// Test de Multiply
func TestMultiply(t *testing.T) {
cases := []struct {
name string
n1 int
n2 int
expectedResult int
}{
{
name: "Multiply 2 numbers",
n1: 2,
n2: 4,
expectedResult: 8,
},
{
name: "Multiply by 0",
n1: 0,
n2: 10,
expectedResult: 0,
},
}
for _, it := range cases {
t.Run(it.name, func(t *testing.T) {
result := Multiply(it.n1, it.n2)
if result != it.expectedResult {
t.Error("Result different, it'll fail")
t.Fail()
}
})
}
}
در این صورت می توانید یک تست ساده را مشاهده کنید که با اضافه کردن موارد، کد آنقدر تغییر نخواهد کرد. بنابراین برای این موارد، من فکر می کنم که آنها عالی کار می کنند.
چه زمانی از جدول آزمون اجتناب کنیم؟
پیدا کردن لحظاتی که می توان از جداول تست اجتناب کرد بسیار آسان است، به خصوص زمانی که پیچیدگی روش زیاد باشد. در تجربهام با جداول آزمایشی مواجه شدهام که در آنها عناصر موارد استفاده مختلف شروع به تمسخر میکنند و هر مورد به چیزی متفاوت اشاره میکند. دیگر با ورودی یا خروجی کافی نیست، حالا باید چیز دیگری را پیکربندی کنیم! در اینجا یک مثال است که من با آن برخورد کردم:
func TestUserSaver_Execute(t *testing.T) {
cases := []struct {
name string
user User
saver UserSaver
expectedErr string
}{
//...
}
for _, it := range cases {
t.Run(it.name, func(t *testing.T) {
err := it.saver.Execute(it.user)
if len(it.expectedErr) > 0 {
if err != nil {
if err.Error() != it.expectedErr {
t.Errorf("We expected the error: %s", it.expectedErr)
t.Fail()
}
} else {
t.Errorf("An error was expected %s", it.expectedErr)
t.Fail()
}
}
})
}
}
در این مورد منطق واقعاً پیچیده است، برای روشی که واقعاً ساده است. از سوی دیگر، اگر مورد استفاده همچنان به افزایش وابستگی خود به سایر اجزا ادامه دهد، تست ها اصلاح خواهند شد. در یک نقطه، اگر بخواهیم اشکالی را پیدا کنیم، از تجربه خودم، بسیار پیچیده است که بتوانیم تستها را بفهمیم و بتوانیم آن مشکل را اصلاح کنیم.
منابع برای ادامه بهبود
همانطور که در پست اصلی، من می خواهم یکی از بهترین راهنماها را برای نوشتن توصیه کنم:
نتیجه
هدف این پست یادگیری است، و برای ادامه یادگیری، به نظر من منبع خوبی برای به اشتراک گذاشتن با هر کسی که میخواهد روز به روز زبان خود را بهبود بخشد، به نکاتی اشاره میکند که من با آنها برخورد کردهام و با آنها دست و پنجه نرم کردهام. برای آن قسمتی از تجربه من باقی مانده در صورتی که به کسی کمک کند. اگر نکات دیگری را می دانید که باید اضافه کنید، خوش آمدید، از واکنش های شما برای ایجاد انگیزه برای ادامه به اشتراک گذاشتن این دانش با همه اهالی جامعه اسپانیایی تبار تشکر می کنم.