اصول توسعه – انجمن DEV

Summarize this content to 400 words in Persian Lang
برنامه نویسی سفری برای بهبود مستمر است. همانطور که تجربه به دست می آوریم، با روش ها و اصولی مواجه می شویم که به ما کمک می کند هنر خود را اصلاح کنیم و منجر به کد با کیفیت بالاتر می شود. این مقاله شما را از طریق اصول و روشهای کلیدی راهنمایی میکند که میتواند به شما کمک کند برنامهنویس بهتری شوید، از جمله اصول SOLID، الگوهای طراحی و استانداردهای کدنویسی. ما بررسی خواهیم کرد که چگونه این مفاهیم می توانند روند توسعه شما را بهبود بخشند.
نکته ای در مورد مثال ها: من Go را برای نمونه های کد به دلیل سادگی و خوانایی آن انتخاب کرده ام – اغلب به عنوان “شبه کد اجرایی” توصیف می شود. اگر با Go آشنا نیستید، نگران نباشید! این یک فرصت عالی برای یادگیری است.
همانطور که با نحو یا مفاهیم ناآشنا روبرو می شوید، لحظه ای به آنها نگاه کنید. این فرآیند کشف می تواند یک امتیاز سرگرم کننده برای تجربه یادگیری شما باشد. به یاد داشته باشید، اصولی که ما در مورد آن بحث می کنیم در بین زبان های برنامه نویسی اعمال می شود، بنابراین به جای ویژگی های دستور Go، بر درک مفاهیم تمرکز کنید.
1. استانداردهای برنامه نویسی را بپذیرید
یکی از اولین قدم ها برای تبدیل شدن به یک برنامه نویس بهتر، رعایت استانداردهای برنامه نویسی است. استانداردها دستورالعمل هایی را برای جنبه هایی مانند قراردادهای نامگذاری، سازماندهی کد و ساختار فایل ارائه می کنند. با پیروی از این قراردادها، اطمینان حاصل می کنید که کد شما سازگار و قابل خواندن است، که برای همکاری و نگهداری طولانی مدت بسیار مهم است.
هر زبان برنامه نویسی معمولاً مجموعه ای از قراردادهای خاص خود را دارد. برای Go، این موارد در راهنمای رسمی سبک Go آمده است. مهم است که استانداردهای مربوط به زمینه خود را یاد بگیرید و آنها را بپذیرید، چه کنوانسیون های تیم شما یا دستورالعمل های خاص زبان.
بیایید به مثالی نگاه کنیم که چگونه استانداردهای زیر می توانند خوانایی کد را بهبود بخشند:
// Before: Inconsistent styling
func dosomething(x int,y string)string{
if x>10{
return y+”is big”
}else{
return y+”is small”
}}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
این کد چندین مشکل دارد:
فاصله و تورفتگی ناسازگار، درک ساختار کد را دشوار می کند.
نام تابع از قرارداد CamelCase Go پیروی نمی کند.
غیر ضروری else بند پیچیدگی شناختی را اضافه می کند.
حال، بیایید این کد را مجدداً تغییر دهیم تا از استانداردهای Go پیروی کند:
// After: Following Go standards
func doSomething(x int, y string) string {
if x > 10 {
return y + ” is big”
}
return y + ” is small”
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
در این نسخه بهبود یافته:
تورفتگی مناسب به وضوح ساختار کد را نشان می دهد.
نام تابع از قرارداد نامگذاری Go پیروی می کند.
را else بند حذف می شود و منطق را ساده می کند.
این تغییرات فقط مربوط به زیبایی شناسی نیست. آنها به طور قابل توجهی خوانایی و قابلیت نگهداری کد را بهبود می بخشند. هنگام کار در یک تیم، استفاده مداوم از این استانداردها درک و کار با کد پایه را برای همه آسانتر میکند.
حتی اگر تیم شما استانداردهای ثابتی نداشته باشد، می توانید برای پیروی از کنوانسیون های پذیرفته شده در جامعه برنامه نویسی پیشقدم شوید. با گذشت زمان، این عمل به کد خوانا و قابل نگهداری بیشتر در پروژه های شما منجر می شود.
2. از اصول طراحی پیروی کنید
اصول طراحی برنامه نویسی دستورالعمل هایی هستند که به شما کمک می کنند کد بهتری بنویسید. این اصول را می توان نه تنها در معماری کد، بلکه در طراحی سیستم و حتی برخی از جنبه های فرآیندهای توسعه نیز به کار برد.اصول طراحی زیادی وجود دارد و برخی در زمینه های خاص مرتبط ترند. برخی دیگر مانند KISS (ساده، احمقانه نگه دارید) یا YAGNI (شما به آن نیاز ندارید) عمومی تر هستند.در میان این اصول کلی، اصول SOLID از تاثیرگذارترین آنها هستند. بیایید هر اصل را با تمرکز بر چگونگی بهبود کد شما بررسی کنیم.
اصل مسئولیت واحد (SRP)
این اصل شما را تشویق میکند که اجزا (توابع، کلاسها یا ماژولها) را با یک هدف واحد و کاملاً تعریف شده طراحی کنید. هنگامی که یک جزء دارای چندین مسئولیت باشد، درک، آزمایش و نگهداری آن دشوارتر می شود.
بیایید به مثالی از refactoring یک تابع برای پایبندی به SRP نگاه کنیم:
// Before: A function doing too much
func processOrder(order Order) error {
// Validate order
if order.Total 0 {
return errors.New(“invalid order total”)
}
// Save to database
db.Save(order)
// Send confirmation email
sendEmail(order.CustomerEmail, “Order Confirmation”, orderDetails(order))
// Update inventory
for _, item := range order.Items {
updateInventory(item.ID, item.Quantity)
}
return nil
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
این تابع چندین کار غیرمرتبط را انجام می دهد: تأیید سفارش، ذخیره آن در پایگاه داده، ارسال ایمیل، و به روز رسانی موجودی. بیایید آن را به توابع جداگانه تقسیم کنیم، که هر کدام یک مسئولیت دارند:
// After: Breaking it down into single responsibilities
func processOrder(order Order) error {
if err := validateOrder(order); err != nil {
return err
}
if err := saveOrder(order); err != nil {
return err
}
if err := sendOrderConfirmation(order); err != nil {
return err
}
return updateInventoryForOrder(order)
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
حالا بیایید هر یک از این توابع را پیاده سازی کنیم:
func validateOrder(order Order) error {
if order.Total 0 {
return errors.New(“invalid order total”)
}
return nil
}
func saveOrder(order Order) error {
return db.Save(order)
}
func sendOrderConfirmation(order Order) error {
return sendEmail(order.CustomerEmail, “Order Confirmation”, orderDetails(order))
}
func updateInventoryForOrder(order Order) error {
for _, item := range order.Items {
if err := updateInventory(item.ID, item.Quantity); err != nil {
return err
}
}
return nil
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
در این نسخه بازسازی شده:
هر عملکرد یک مسئولیت واحد و روشن دارد.
کد ماژولارتر است و آزمایش آن آسان تر است.
اجزای منفرد را می توان مجدداً مورد استفاده قرار داد یا تغییر داد بدون اینکه بر دیگران تأثیر بگذارد.
به من اعتماد کنید، آینده شما از شما برای این سطح از سازمان تشکر خواهد کرد.
اصل باز-بسته (OCP)
اصل بسته باز توصیه می کند که نهادهای نرم افزار باید برای توسعه باز باشند اما برای اصلاح بسته شوند. این بدان معنی است که شما باید بتوانید بدون تغییر کد موجود، عملکرد جدیدی اضافه کنید.
اصل جایگزینی لیسکوف (LSP)
LSP بیان می کند که اشیاء یک سوپرکلاس باید با اشیاء زیر کلاس های آن بدون تأثیر بر صحت برنامه قابل تعویض باشند. این تضمین می کند که سلسله مراتب وراثت به خوبی طراحی شده و قابل نگهداری است.
اصل جداسازی رابط (ISP)
ISP پیشنهاد می کند که هیچ کدی نباید مجبور شود به روش هایی که استفاده نمی کند وابسته باشد. در عمل، این اغلب به معنای ایجاد رابط های کوچکتر و متمرکزتر است. این ماژولار بودن مدیریت و آزمایش کد شما را آسان تر می کند.
در اینجا یک مثال برای نشان دادن ISP آورده شده است:
// Before: A large interface that many structs only partially implement
type Worker interface {
DoWork()
TakeBreak()
GetPaid()
FileTicket()
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
این رابط بزرگ، پیادهکنندهها را مجبور میکند تا روشهایی را که ممکن است به آنها نیاز نداشته باشند، تعریف کنند. بیایید آن را به رابط های کوچکتر و متمرکزتر تقسیم کنیم:
// After: Smaller, more focused interfaces
type Worker interface {
DoWork()
}
type BreakTaker interface {
TakeBreak()
}
type Payable interface {
GetPaid()
}
type TicketFiler interface {
FileTicket()
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
اکنون ساختارها فقط می توانند رابط های مورد نیاز خود را پیاده سازی کنند:
type Developer struct{}
func (d Developer) DoWork() {
fmt.Println(“Writing code”)
}
func (d Developer) TakeBreak() {
fmt.Println(“Browsing Reddit”)
}
func (d Developer) FileTicket() {
fmt.Println(“Creating a Jira ticket”)
}
type Contractor struct{}
func (c Contractor) DoWork() {
fmt.Println(“Completing assigned task”)
}
func (c Contractor) GetPaid() {
fmt.Println(“Invoicing for work done”)
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
این ماژولار بودن کد شما را برای مدیریت و آزمایش آسان تر می کند. در Go، رابطها به طور ضمنی پیادهسازی میشوند، که کاربرد این اصل را بسیار آسان میکند.
اصل وارونگی وابستگی (DIP)
DIP استفاده از انتزاعات را به جای اجرای عینی ترویج می کند. با بسته به رابطها یا کلاسهای انتزاعی، کد خود را جدا میکنید و انعطافپذیرتر و نگهداری آن را آسانتر میکنید. این همچنین با اجازه دادن به پیاده سازی های ساختگی، تست آسان تر را تسهیل می کند.
استفاده از اصول SOLID (ببینید من در آنجا چه کار کردم؟) منجر به کدهای ماژولار و جدا شده ای می شود که نگهداری، مقیاس، استفاده مجدد و آزمایش آسان تر است. دریافتهام که این اصول، اگرچه در ابتدا گاهی اوقات چالش برانگیز است، اما به طور مداوم به پایگاههای کد قویتر و انعطافپذیرتری منجر شدهاند.
در حالی که این اصول ارزشمند هستند، به یاد داشته باشید که آنها دستورالعمل هستند، نه قوانین سخت. همیشه استثناهایی برای قوانین وجود خواهد داشت، اما مهم است که به یاد داشته باشید که پیروی از این اصول یک فرآیند مستمر است و یک رویداد یکباره نیست. ایجاد عادات خوب به زمان و تلاش نیاز دارد، اما پاداش آن ارزشش را دارد.
3. از الگوهای طراحی استفاده کنید
الگوهای طراحی راه حل های قابل استفاده مجدد را برای مشکلات رایج برنامه نویسی ارائه می دهند. آنها پیاده سازی های سفت و سختی نیستند، بلکه قالب هایی هستند که می توانند با نیازهای خاص سازگار شوند. بسیاری از الگوهای طراحی با اصول SOLID مرتبط هستند و اغلب با هدف حفظ یک یا چند مورد از این اصول در طراحی خود هستند.
الگوهای طراحی معمولاً به سه نوع طبقه بندی می شوند:
الگوهای خلاقیت
این الگوها با مکانیسم های ایجاد شی سروکار دارند. یک مثال الگوی Factory Method است که در عین انتزاع منطق نمونه سازی، اشیاء را بر اساس مجموعه ای از معیارها ایجاد می کند.
بیایید به یک مثال ساده Factory Method در Go نگاه کنیم:
type PaymentMethod interface {
Pay(amount float64) string
}
type CashPayment struct{}
func (c CashPayment) Pay(amount float64) string {
return fmt.Sprintf(“Paid %.2f using cash”, amount)
}
type CreditCardPayment struct{}
func (cc CreditCardPayment) Pay(amount float64) string {
return fmt.Sprintf(“Paid %.2f using credit card”, amount)
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
در اینجا، a را تعریف می کنیم PaymentMethod رابط و دو پیاده سازی بتن. حالا بیایید یک تابع کارخانه ایجاد کنیم:
func GetPaymentMethod(method string) (PaymentMethod, error) {
switch method {
case “cash”:
return CashPayment{}, nil
case “credit”:
return CreditCardPayment{}, nil
default:
return nil, fmt.Errorf(“Payment method %s not supported”, method)
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
این تابع کارخانه روش پرداخت مناسب را بر اساس رشته ورودی ایجاد می کند. در اینجا نحوه استفاده از آن آمده است:
method, err := GetPaymentMethod(“cash”)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(method.Pay(42.42))
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
این الگو امکان گسترش آسان روش های پرداخت را بدون تغییر کد موجود فراهم می کند.
الگوهای ساختاری
الگوهای ساختاری با ترکیب اشیاء سروکار دارند و تعامل بهتری بین کلاسها را ترویج میکنند. برای مثال الگوی Adapter به رابط های ناسازگار اجازه می دهد تا با هم کار کنند.
الگوهای رفتاری
الگوهای رفتاری بر ارتباط بین اشیا تمرکز دارند. الگوی Observer یک الگوی رفتاری رایج است که مدل انتشار-اشتراک را تسهیل میکند و اشیا را قادر میسازد تا به رویدادها واکنش نشان دهند.
توجه به این نکته مهم است که الگوهای طراحی بسیار بیشتری وجود دارد و برخی در زمینه های خاص مرتبط تر هستند. به عنوان مثال، توسعه بازی ممکن است به شدت از الگوی Object Pool استفاده کند، در حالی که در توسعه وب کمتر رایج است.
الگوهای طراحی به حل مشکلات تکراری کمک می کند و واژگانی جهانی در بین توسعه دهندگان ایجاد می کند. با این حال، برای یادگیری همه الگوها به یکباره احساس فشار نکنید. در عوض، خود را با مفاهیم آشنا کنید، و هنگام مواجهه با یک مشکل جدید، الگوهای مربوطه را که ممکن است راه حلی ارائه دهند، مرور کنید. با گذشت زمان، شما به طور طبیعی این الگوها را در فرآیند طراحی خود وارد خواهید کرد.
4. قراردادهای نامگذاری خوب را تمرین کنید
قوانین نامگذاری واضح برای نوشتن کدهای قابل خواندن و نگهداری بسیار مهم هستند. این عمل ارتباط نزدیکی با استانداردهای برنامه نویسی دارد و شایسته توجه ویژه است.
از نام های توصیفی استفاده کنید
نام هایی را انتخاب کنید که به وضوح هدف متغیر، تابع یا کلاس را توصیف کنند. از رمزگذاری های غیر ضروری یا اختصارات مرموز خودداری کنید.
این تابع با نام ضعیف را در نظر بگیرید:
// Bad
func calc(a, b int) int {
return a + b
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
حال، بیایید آن را با نام های توصیفی تر بهبود دهیم:
// Good
func calculateSum(firstNumber, secondNumber int) int {
return firstNumber + secondNumber
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
با این حال، مراقب باشید که با نام های بیش از حد طولانی افراط نکنید:
// Too verbose
func calculateSumOfTwoIntegersAndReturnTheResult(firstInteger, secondInteger int) int {
return firstInteger + secondInteger
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
تعادل وضوح و مختصر
نام هایی را هدف قرار دهید که واضح باشند اما بیش از حد پرمخاطب نباشند. شیوههای خوب نامگذاری، کد شما را مستندسازی میکند و نیاز به نظرات بیش از حد را کاهش میدهد.
نام های خوب را به نظرات ترجیح دهید
اغلب، نیاز به نظرات از عناصر نامناسب در کد شما ناشی می شود. اگر میبینید که برای توضیح اینکه یک قطعه کد چه کار میکند، یک نظر مینویسید، در نظر بگیرید که آیا میتوانید نام متغیرها یا توابع را تغییر دهید تا کد خود توضیحی باشد.
مقادیر جادویی را با ثابت های نامگذاری شده جایگزین کنید
استفاده از ثابتهای نامگذاریشده بهجای مقادیر رمزگذاریشده، معنای آنها را روشن میکند و کمک میکند کد شما سازگار باشد.
// Bad
if user.Age >= 18 {
// Allow access
}
// Good
const LegalAge = 18
if user.Age >= LegalAge {
// Allow access
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
با پیروی از این قراردادهای نامگذاری، کدی ایجاد میکنید که خواندن، درک و نگهداری آن آسانتر است.
5. اولویت بندی تست
آزمایش یک تمرین ضروری برای اطمینان از اینکه کد شما مطابق انتظار عمل می کند، است. در حالی که نظرات ثابت زیادی در مورد روش های آزمایش وجود دارد، اشکالی ندارد که رویکردی را ایجاد کنید که برای شما و تیم شما بهترین کار را داشته باشد.
تست واحد
تست های واحد بر روی ماژول های فردی به صورت مجزا تمرکز می کنند. آنها بازخورد سریعی در مورد اینکه آیا بخش های خاصی از کد شما به درستی کار می کنند ارائه می دهند.
در اینجا یک مثال ساده از تست واحد در Go آورده شده است:
func TestCalculateSum(t *testing.T) {
result := calculateSum(3, 4)
expected := 7
if result != expected {
t.Errorf(“calculateSum(3, 4) = %d; want %d”, result, expected)
}
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
تست یکپارچه سازی
تست های یکپارچه سازی نحوه کار ماژول های مختلف با هم را بررسی می کنند. آنها به شناسایی مشکلاتی که ممکن است از تعامل بین بخشهای مختلف کد ایجاد شود کمک میکنند.
تست پایان به انتها
تست های انتها به انتها تعاملات کاربر با کل برنامه را شبیه سازی می کنند. آنها تأیید می کنند که سیستم به عنوان یک کل کار می کند و یک دید کاربر محور از عملکرد ارائه می دهد.
به یاد داشته باشید، کدهایی که به خوبی آزمایش شده اند نه تنها قابل اعتمادتر هستند، بلکه به مرور زمان بازنویسی و نگهداری آنها نیز آسان تر است. اصول SOLID که قبلاً مورد بحث قرار گرفتیم میتواند با تشویق کدهای ماژولار و جداشده که جداسازی و اعتبارسنجی آن سادهتر است، آزمایش را آسانتر کند.
6. برای برنامه ریزی و اجرا وقت بگذارید
در حالی که ممکن است عجله در انجام پروژه ها وسوسه انگیز باشد، به خصوص زمانی که ضرب الاجل ها محدود است، برنامه ریزی متفکرانه برای موفقیت بلندمدت بسیار مهم است. عجله می تواند منجر به بدهی فنی و چالش های نگهداری در آینده شود.
برای بررسی دقیق تصمیمات معماری و برنامه ریزی رویکرد خود وقت بگذارید. ایجاد یک پایه محکم در مراحل اولیه باعث صرفه جویی در زمان و تلاش در طولانی مدت می شود. با این حال، مراقب باشید بیش از حد برنامه ریزی نکنید – تعادلی را پیدا کنید که برای نیازهای پروژه شما کار کند.
پروژه های مختلف ممکن است به سطوح مختلفی از برنامه ریزی نیاز داشته باشند. یک نمونه اولیه کوچک ممکن است به حداقل طراحی اولیه نیاز داشته باشد، در حالی که یک سیستم بزرگ و پیچیده می تواند از برنامه ریزی گسترده تر بهره مند شود. نکته کلیدی این است که انعطاف پذیر باشید و رویکرد خود را بر اساس الزامات و محدودیت های پروژه تنظیم کنید.
نتیجه گیری
با رعایت این اصول و روش ها می توانید برنامه نویس بهتری شوید. بر روی نوشتن کدی تمرکز کنید که واضح، قابل نگهداری و با ساختار متفکرانه باشد. سخنان مارتین فاولر را به خاطر بسپارید: “هر احمقی می تواند کدی بنویسد که یک کامپیوتر بتواند آن را بفهمد. برنامه نویسان خوب کدهایی را می نویسند که انسان ها می توانند آن را درک کنند.”
تبدیل شدن به یک برنامه نویس بهتر یک فرآیند مداوم است. اگر فوراً همه چیز را درست انجام ندادید، ناامید نشوید. به یادگیری ادامه دهید، به تمرین ادامه دهید و مهمتر از همه، به کدنویسی ادامه دهید. با گذشت زمان و از خود گذشتگی، پیشرفت های قابل توجهی در مهارت ها و کیفیت کار خود خواهید دید.
در اینجا منابعی وجود دارد که ممکن است برای یادگیری بیشتر مفید باشد:
اکنون، مسلح به این اصول، شروع به اعمال آنها در پروژه های خود کنید. ایجاد یک وب API کوچک که فراتر از عملیات ساده CRUD است را در نظر بگیرید، یا ابزاری را توسعه دهید که مشکلی را که در کار روزانه با آن مواجه هستید را حل کند. به یاد داشته باشید، بهترین راه برای یادگیری، انجام دادن است. کد نویسی مبارک!
برنامه نویسی سفری برای بهبود مستمر است. همانطور که تجربه به دست می آوریم، با روش ها و اصولی مواجه می شویم که به ما کمک می کند هنر خود را اصلاح کنیم و منجر به کد با کیفیت بالاتر می شود. این مقاله شما را از طریق اصول و روشهای کلیدی راهنمایی میکند که میتواند به شما کمک کند برنامهنویس بهتری شوید، از جمله اصول SOLID، الگوهای طراحی و استانداردهای کدنویسی. ما بررسی خواهیم کرد که چگونه این مفاهیم می توانند روند توسعه شما را بهبود بخشند.
نکته ای در مورد مثال ها: من Go را برای نمونه های کد به دلیل سادگی و خوانایی آن انتخاب کرده ام – اغلب به عنوان “شبه کد اجرایی” توصیف می شود. اگر با Go آشنا نیستید، نگران نباشید! این یک فرصت عالی برای یادگیری است.
همانطور که با نحو یا مفاهیم ناآشنا روبرو می شوید، لحظه ای به آنها نگاه کنید. این فرآیند کشف می تواند یک امتیاز سرگرم کننده برای تجربه یادگیری شما باشد. به یاد داشته باشید، اصولی که ما در مورد آن بحث می کنیم در بین زبان های برنامه نویسی اعمال می شود، بنابراین به جای ویژگی های دستور Go، بر درک مفاهیم تمرکز کنید.
1. استانداردهای برنامه نویسی را بپذیرید
یکی از اولین قدم ها برای تبدیل شدن به یک برنامه نویس بهتر، رعایت استانداردهای برنامه نویسی است. استانداردها دستورالعمل هایی را برای جنبه هایی مانند قراردادهای نامگذاری، سازماندهی کد و ساختار فایل ارائه می کنند. با پیروی از این قراردادها، اطمینان حاصل می کنید که کد شما سازگار و قابل خواندن است، که برای همکاری و نگهداری طولانی مدت بسیار مهم است.
هر زبان برنامه نویسی معمولاً مجموعه ای از قراردادهای خاص خود را دارد. برای Go، این موارد در راهنمای رسمی سبک Go آمده است. مهم است که استانداردهای مربوط به زمینه خود را یاد بگیرید و آنها را بپذیرید، چه کنوانسیون های تیم شما یا دستورالعمل های خاص زبان.
بیایید به مثالی نگاه کنیم که چگونه استانداردهای زیر می توانند خوانایی کد را بهبود بخشند:
// Before: Inconsistent styling
func dosomething(x int,y string)string{
if x>10{
return y+"is big"
}else{
return y+"is small"
}}
این کد چندین مشکل دارد:
- فاصله و تورفتگی ناسازگار، درک ساختار کد را دشوار می کند.
- نام تابع از قرارداد CamelCase Go پیروی نمی کند.
- غیر ضروری
else
بند پیچیدگی شناختی را اضافه می کند.
حال، بیایید این کد را مجدداً تغییر دهیم تا از استانداردهای Go پیروی کند:
// After: Following Go standards
func doSomething(x int, y string) string {
if x > 10 {
return y + " is big"
}
return y + " is small"
}
در این نسخه بهبود یافته:
- تورفتگی مناسب به وضوح ساختار کد را نشان می دهد.
- نام تابع از قرارداد نامگذاری Go پیروی می کند.
- را
else
بند حذف می شود و منطق را ساده می کند.
این تغییرات فقط مربوط به زیبایی شناسی نیست. آنها به طور قابل توجهی خوانایی و قابلیت نگهداری کد را بهبود می بخشند. هنگام کار در یک تیم، استفاده مداوم از این استانداردها درک و کار با کد پایه را برای همه آسانتر میکند.
حتی اگر تیم شما استانداردهای ثابتی نداشته باشد، می توانید برای پیروی از کنوانسیون های پذیرفته شده در جامعه برنامه نویسی پیشقدم شوید. با گذشت زمان، این عمل به کد خوانا و قابل نگهداری بیشتر در پروژه های شما منجر می شود.
2. از اصول طراحی پیروی کنید
اصول طراحی برنامه نویسی دستورالعمل هایی هستند که به شما کمک می کنند کد بهتری بنویسید. این اصول را می توان نه تنها در معماری کد، بلکه در طراحی سیستم و حتی برخی از جنبه های فرآیندهای توسعه نیز به کار برد.
اصول طراحی زیادی وجود دارد و برخی در زمینه های خاص مرتبط ترند. برخی دیگر مانند KISS (ساده، احمقانه نگه دارید) یا YAGNI (شما به آن نیاز ندارید) عمومی تر هستند.
در میان این اصول کلی، اصول SOLID از تاثیرگذارترین آنها هستند. بیایید هر اصل را با تمرکز بر چگونگی بهبود کد شما بررسی کنیم.
اصل مسئولیت واحد (SRP)
این اصل شما را تشویق میکند که اجزا (توابع، کلاسها یا ماژولها) را با یک هدف واحد و کاملاً تعریف شده طراحی کنید. هنگامی که یک جزء دارای چندین مسئولیت باشد، درک، آزمایش و نگهداری آن دشوارتر می شود.
بیایید به مثالی از refactoring یک تابع برای پایبندی به SRP نگاه کنیم:
// Before: A function doing too much
func processOrder(order Order) error {
// Validate order
if order.Total 0 {
return errors.New("invalid order total")
}
// Save to database
db.Save(order)
// Send confirmation email
sendEmail(order.CustomerEmail, "Order Confirmation", orderDetails(order))
// Update inventory
for _, item := range order.Items {
updateInventory(item.ID, item.Quantity)
}
return nil
}
این تابع چندین کار غیرمرتبط را انجام می دهد: تأیید سفارش، ذخیره آن در پایگاه داده، ارسال ایمیل، و به روز رسانی موجودی. بیایید آن را به توابع جداگانه تقسیم کنیم، که هر کدام یک مسئولیت دارند:
// After: Breaking it down into single responsibilities
func processOrder(order Order) error {
if err := validateOrder(order); err != nil {
return err
}
if err := saveOrder(order); err != nil {
return err
}
if err := sendOrderConfirmation(order); err != nil {
return err
}
return updateInventoryForOrder(order)
}
حالا بیایید هر یک از این توابع را پیاده سازی کنیم:
func validateOrder(order Order) error {
if order.Total 0 {
return errors.New("invalid order total")
}
return nil
}
func saveOrder(order Order) error {
return db.Save(order)
}
func sendOrderConfirmation(order Order) error {
return sendEmail(order.CustomerEmail, "Order Confirmation", orderDetails(order))
}
func updateInventoryForOrder(order Order) error {
for _, item := range order.Items {
if err := updateInventory(item.ID, item.Quantity); err != nil {
return err
}
}
return nil
}
در این نسخه بازسازی شده:
- هر عملکرد یک مسئولیت واحد و روشن دارد.
- کد ماژولارتر است و آزمایش آن آسان تر است.
- اجزای منفرد را می توان مجدداً مورد استفاده قرار داد یا تغییر داد بدون اینکه بر دیگران تأثیر بگذارد.
به من اعتماد کنید، آینده شما از شما برای این سطح از سازمان تشکر خواهد کرد.
اصل باز-بسته (OCP)
اصل بسته باز توصیه می کند که نهادهای نرم افزار باید برای توسعه باز باشند اما برای اصلاح بسته شوند. این بدان معنی است که شما باید بتوانید بدون تغییر کد موجود، عملکرد جدیدی اضافه کنید.
اصل جایگزینی لیسکوف (LSP)
LSP بیان می کند که اشیاء یک سوپرکلاس باید با اشیاء زیر کلاس های آن بدون تأثیر بر صحت برنامه قابل تعویض باشند. این تضمین می کند که سلسله مراتب وراثت به خوبی طراحی شده و قابل نگهداری است.
اصل جداسازی رابط (ISP)
ISP پیشنهاد می کند که هیچ کدی نباید مجبور شود به روش هایی که استفاده نمی کند وابسته باشد. در عمل، این اغلب به معنای ایجاد رابط های کوچکتر و متمرکزتر است. این ماژولار بودن مدیریت و آزمایش کد شما را آسان تر می کند.
در اینجا یک مثال برای نشان دادن ISP آورده شده است:
// Before: A large interface that many structs only partially implement
type Worker interface {
DoWork()
TakeBreak()
GetPaid()
FileTicket()
}
این رابط بزرگ، پیادهکنندهها را مجبور میکند تا روشهایی را که ممکن است به آنها نیاز نداشته باشند، تعریف کنند. بیایید آن را به رابط های کوچکتر و متمرکزتر تقسیم کنیم:
// After: Smaller, more focused interfaces
type Worker interface {
DoWork()
}
type BreakTaker interface {
TakeBreak()
}
type Payable interface {
GetPaid()
}
type TicketFiler interface {
FileTicket()
}
اکنون ساختارها فقط می توانند رابط های مورد نیاز خود را پیاده سازی کنند:
type Developer struct{}
func (d Developer) DoWork() {
fmt.Println("Writing code")
}
func (d Developer) TakeBreak() {
fmt.Println("Browsing Reddit")
}
func (d Developer) FileTicket() {
fmt.Println("Creating a Jira ticket")
}
type Contractor struct{}
func (c Contractor) DoWork() {
fmt.Println("Completing assigned task")
}
func (c Contractor) GetPaid() {
fmt.Println("Invoicing for work done")
}
این ماژولار بودن کد شما را برای مدیریت و آزمایش آسان تر می کند. در Go، رابطها به طور ضمنی پیادهسازی میشوند، که کاربرد این اصل را بسیار آسان میکند.
اصل وارونگی وابستگی (DIP)
DIP استفاده از انتزاعات را به جای اجرای عینی ترویج می کند. با بسته به رابطها یا کلاسهای انتزاعی، کد خود را جدا میکنید و انعطافپذیرتر و نگهداری آن را آسانتر میکنید. این همچنین با اجازه دادن به پیاده سازی های ساختگی، تست آسان تر را تسهیل می کند.
استفاده از اصول SOLID (ببینید من در آنجا چه کار کردم؟) منجر به کدهای ماژولار و جدا شده ای می شود که نگهداری، مقیاس، استفاده مجدد و آزمایش آسان تر است. دریافتهام که این اصول، اگرچه در ابتدا گاهی اوقات چالش برانگیز است، اما به طور مداوم به پایگاههای کد قویتر و انعطافپذیرتری منجر شدهاند.
در حالی که این اصول ارزشمند هستند، به یاد داشته باشید که آنها دستورالعمل هستند، نه قوانین سخت. همیشه استثناهایی برای قوانین وجود خواهد داشت، اما مهم است که به یاد داشته باشید که پیروی از این اصول یک فرآیند مستمر است و یک رویداد یکباره نیست. ایجاد عادات خوب به زمان و تلاش نیاز دارد، اما پاداش آن ارزشش را دارد.
3. از الگوهای طراحی استفاده کنید
الگوهای طراحی راه حل های قابل استفاده مجدد را برای مشکلات رایج برنامه نویسی ارائه می دهند. آنها پیاده سازی های سفت و سختی نیستند، بلکه قالب هایی هستند که می توانند با نیازهای خاص سازگار شوند. بسیاری از الگوهای طراحی با اصول SOLID مرتبط هستند و اغلب با هدف حفظ یک یا چند مورد از این اصول در طراحی خود هستند.
الگوهای طراحی معمولاً به سه نوع طبقه بندی می شوند:
الگوهای خلاقیت
این الگوها با مکانیسم های ایجاد شی سروکار دارند. یک مثال الگوی Factory Method است که در عین انتزاع منطق نمونه سازی، اشیاء را بر اساس مجموعه ای از معیارها ایجاد می کند.
بیایید به یک مثال ساده Factory Method در Go نگاه کنیم:
type PaymentMethod interface {
Pay(amount float64) string
}
type CashPayment struct{}
func (c CashPayment) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f using cash", amount)
}
type CreditCardPayment struct{}
func (cc CreditCardPayment) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f using credit card", amount)
}
در اینجا، a را تعریف می کنیم PaymentMethod
رابط و دو پیاده سازی بتن. حالا بیایید یک تابع کارخانه ایجاد کنیم:
func GetPaymentMethod(method string) (PaymentMethod, error) {
switch method {
case "cash":
return CashPayment{}, nil
case "credit":
return CreditCardPayment{}, nil
default:
return nil, fmt.Errorf("Payment method %s not supported", method)
}
}
این تابع کارخانه روش پرداخت مناسب را بر اساس رشته ورودی ایجاد می کند. در اینجا نحوه استفاده از آن آمده است:
method, err := GetPaymentMethod("cash")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(method.Pay(42.42))
این الگو امکان گسترش آسان روش های پرداخت را بدون تغییر کد موجود فراهم می کند.
الگوهای ساختاری
الگوهای ساختاری با ترکیب اشیاء سروکار دارند و تعامل بهتری بین کلاسها را ترویج میکنند. برای مثال الگوی Adapter به رابط های ناسازگار اجازه می دهد تا با هم کار کنند.
الگوهای رفتاری
الگوهای رفتاری بر ارتباط بین اشیا تمرکز دارند. الگوی Observer یک الگوی رفتاری رایج است که مدل انتشار-اشتراک را تسهیل میکند و اشیا را قادر میسازد تا به رویدادها واکنش نشان دهند.
توجه به این نکته مهم است که الگوهای طراحی بسیار بیشتری وجود دارد و برخی در زمینه های خاص مرتبط تر هستند. به عنوان مثال، توسعه بازی ممکن است به شدت از الگوی Object Pool استفاده کند، در حالی که در توسعه وب کمتر رایج است.
الگوهای طراحی به حل مشکلات تکراری کمک می کند و واژگانی جهانی در بین توسعه دهندگان ایجاد می کند. با این حال، برای یادگیری همه الگوها به یکباره احساس فشار نکنید. در عوض، خود را با مفاهیم آشنا کنید، و هنگام مواجهه با یک مشکل جدید، الگوهای مربوطه را که ممکن است راه حلی ارائه دهند، مرور کنید. با گذشت زمان، شما به طور طبیعی این الگوها را در فرآیند طراحی خود وارد خواهید کرد.
4. قراردادهای نامگذاری خوب را تمرین کنید
قوانین نامگذاری واضح برای نوشتن کدهای قابل خواندن و نگهداری بسیار مهم هستند. این عمل ارتباط نزدیکی با استانداردهای برنامه نویسی دارد و شایسته توجه ویژه است.
از نام های توصیفی استفاده کنید
نام هایی را انتخاب کنید که به وضوح هدف متغیر، تابع یا کلاس را توصیف کنند. از رمزگذاری های غیر ضروری یا اختصارات مرموز خودداری کنید.
این تابع با نام ضعیف را در نظر بگیرید:
// Bad
func calc(a, b int) int {
return a + b
}
حال، بیایید آن را با نام های توصیفی تر بهبود دهیم:
// Good
func calculateSum(firstNumber, secondNumber int) int {
return firstNumber + secondNumber
}
با این حال، مراقب باشید که با نام های بیش از حد طولانی افراط نکنید:
// Too verbose
func calculateSumOfTwoIntegersAndReturnTheResult(firstInteger, secondInteger int) int {
return firstInteger + secondInteger
}
تعادل وضوح و مختصر
نام هایی را هدف قرار دهید که واضح باشند اما بیش از حد پرمخاطب نباشند. شیوههای خوب نامگذاری، کد شما را مستندسازی میکند و نیاز به نظرات بیش از حد را کاهش میدهد.
نام های خوب را به نظرات ترجیح دهید
اغلب، نیاز به نظرات از عناصر نامناسب در کد شما ناشی می شود. اگر میبینید که برای توضیح اینکه یک قطعه کد چه کار میکند، یک نظر مینویسید، در نظر بگیرید که آیا میتوانید نام متغیرها یا توابع را تغییر دهید تا کد خود توضیحی باشد.
مقادیر جادویی را با ثابت های نامگذاری شده جایگزین کنید
استفاده از ثابتهای نامگذاریشده بهجای مقادیر رمزگذاریشده، معنای آنها را روشن میکند و کمک میکند کد شما سازگار باشد.
// Bad
if user.Age >= 18 {
// Allow access
}
// Good
const LegalAge = 18
if user.Age >= LegalAge {
// Allow access
}
با پیروی از این قراردادهای نامگذاری، کدی ایجاد میکنید که خواندن، درک و نگهداری آن آسانتر است.
5. اولویت بندی تست
آزمایش یک تمرین ضروری برای اطمینان از اینکه کد شما مطابق انتظار عمل می کند، است. در حالی که نظرات ثابت زیادی در مورد روش های آزمایش وجود دارد، اشکالی ندارد که رویکردی را ایجاد کنید که برای شما و تیم شما بهترین کار را داشته باشد.
تست واحد
تست های واحد بر روی ماژول های فردی به صورت مجزا تمرکز می کنند. آنها بازخورد سریعی در مورد اینکه آیا بخش های خاصی از کد شما به درستی کار می کنند ارائه می دهند.
در اینجا یک مثال ساده از تست واحد در Go آورده شده است:
func TestCalculateSum(t *testing.T) {
result := calculateSum(3, 4)
expected := 7
if result != expected {
t.Errorf("calculateSum(3, 4) = %d; want %d", result, expected)
}
}
تست یکپارچه سازی
تست های یکپارچه سازی نحوه کار ماژول های مختلف با هم را بررسی می کنند. آنها به شناسایی مشکلاتی که ممکن است از تعامل بین بخشهای مختلف کد ایجاد شود کمک میکنند.
تست پایان به انتها
تست های انتها به انتها تعاملات کاربر با کل برنامه را شبیه سازی می کنند. آنها تأیید می کنند که سیستم به عنوان یک کل کار می کند و یک دید کاربر محور از عملکرد ارائه می دهد.
به یاد داشته باشید، کدهایی که به خوبی آزمایش شده اند نه تنها قابل اعتمادتر هستند، بلکه به مرور زمان بازنویسی و نگهداری آنها نیز آسان تر است. اصول SOLID که قبلاً مورد بحث قرار گرفتیم میتواند با تشویق کدهای ماژولار و جداشده که جداسازی و اعتبارسنجی آن سادهتر است، آزمایش را آسانتر کند.
6. برای برنامه ریزی و اجرا وقت بگذارید
در حالی که ممکن است عجله در انجام پروژه ها وسوسه انگیز باشد، به خصوص زمانی که ضرب الاجل ها محدود است، برنامه ریزی متفکرانه برای موفقیت بلندمدت بسیار مهم است. عجله می تواند منجر به بدهی فنی و چالش های نگهداری در آینده شود.
برای بررسی دقیق تصمیمات معماری و برنامه ریزی رویکرد خود وقت بگذارید. ایجاد یک پایه محکم در مراحل اولیه باعث صرفه جویی در زمان و تلاش در طولانی مدت می شود. با این حال، مراقب باشید بیش از حد برنامه ریزی نکنید – تعادلی را پیدا کنید که برای نیازهای پروژه شما کار کند.
پروژه های مختلف ممکن است به سطوح مختلفی از برنامه ریزی نیاز داشته باشند. یک نمونه اولیه کوچک ممکن است به حداقل طراحی اولیه نیاز داشته باشد، در حالی که یک سیستم بزرگ و پیچیده می تواند از برنامه ریزی گسترده تر بهره مند شود. نکته کلیدی این است که انعطاف پذیر باشید و رویکرد خود را بر اساس الزامات و محدودیت های پروژه تنظیم کنید.
نتیجه گیری
با رعایت این اصول و روش ها می توانید برنامه نویس بهتری شوید. بر روی نوشتن کدی تمرکز کنید که واضح، قابل نگهداری و با ساختار متفکرانه باشد. سخنان مارتین فاولر را به خاطر بسپارید: “هر احمقی می تواند کدی بنویسد که یک کامپیوتر بتواند آن را بفهمد. برنامه نویسان خوب کدهایی را می نویسند که انسان ها می توانند آن را درک کنند.”
تبدیل شدن به یک برنامه نویس بهتر یک فرآیند مداوم است. اگر فوراً همه چیز را درست انجام ندادید، ناامید نشوید. به یادگیری ادامه دهید، به تمرین ادامه دهید و مهمتر از همه، به کدنویسی ادامه دهید. با گذشت زمان و از خود گذشتگی، پیشرفت های قابل توجهی در مهارت ها و کیفیت کار خود خواهید دید.
در اینجا منابعی وجود دارد که ممکن است برای یادگیری بیشتر مفید باشد:
اکنون، مسلح به این اصول، شروع به اعمال آنها در پروژه های خود کنید. ایجاد یک وب API کوچک که فراتر از عملیات ساده CRUD است را در نظر بگیرید، یا ابزاری را توسعه دهید که مشکلی را که در کار روزانه با آن مواجه هستید را حل کند. به یاد داشته باشید، بهترین راه برای یادگیری، انجام دادن است. کد نویسی مبارک!