در گذشته تعداد زیادی زبان برنامهنویسی در صنعت توسعهٔ نرمافزار وجود نداشت که در عین حال محدودیتهای زیادی هم داشتند به طوری که مثلاً زبان Basic فقط از آرایهها به عنوان دیتا استراکچر استفاده میکرد و زبان معروف C نیز به آرایهها، پوینترها و لیستهای لینکشده محدود بود تا اینکه پیدایش زبانهای جدیدتری همچون ++Java ،Python ،C و دیگر زبانهای سطحبالا فیچرهای به مراتب پیشرفتهتری در اختیار دولوپرها قرار دارند. در حال حاضر، انواع و اقسام زبانهای برنامهنویسی با قابلیتهای منحصربهفردی در اختیار صنعت توسعهٔ نرمافزار قرار گرفته که توانستهاند فرایند توسعهٔ نرمافزاری را برای دولوپرها لذتبخشتر از گذشته کنند. جدای از قابلیتهای گوناگون زبانهای برنامهنویسی، یکسری تکنیکها هم هستند که اگر در زمان درست و مکان مناسب به کار گرفته شوند، میتوانند پتانسیل زبانهای برنامهنویسی سطحبالا را دوچندان سازند که در این مقاله قصد داریم به برخی از مهمترین آنها اشاره کنیم.
Immutability
به طور کلی، هرآنچه شما به عنوان Immutable (تغییرناپذیر) در نظر گرفتهاید، قادر به تغییر یافتن نیست به طوری که مثلاً در زبان سوئیفت هر متغیری که با کلیدواژهٔ var تعریف کرده باشید، قابلتغییر بوده و هر متغیری که با let تعریف شده باشد تغییرناپذیر است. در مثال زیر، کلاس Person دارای دو اتریبیوت تغییرناپذیر است که با کلیدواژهٔ let تعریف شدهاند و از آنجا که تمام فیلدها تغییرناپذیرند، آبجکت ایجاد شده از روی کلاس Person نیز تغییرناپذیر است:
class Person {
let firstName: String
let lastName: String
init(first: String, last: String) {
firstName = first
lastName = last
}
public func toString() -> String {
return "\(self.firstName) \(self.lastName)";
}
}
var man = Person(first:"David", last:"Bolton")
print( man.toString() )
تغییرناپذیر بودن متغیرها بسیار کارآمد است به طوری که دست کامپایلر را به منظور بهینهسازی کدهای نوشته شده به مراتب بازتر میگذارد. در برنامهنویسی به اصطلاح Multi-Thread دانستن اینکه یک متغیر تغییرناپذیر میباشد بدین معنا است دیگر نیازی به گرفتن جلوی هرگونه تغییر نیست چرا که هیچ چیزی نمیتواند آن را تغییر دهد و متغیر میتواند با خیال راحت میان تِرِدهای مختلف به اشتراک گذاشته شود.
Safe Calls
دانشمند معروف علوم کامپیوتری، Tony Hoare، یک بار عنوان کرد که ایجاد رفرنسهای null یک اشتباه به اصطلاح Billion Dollar (پُرهزینه) است به طوری که تلاش برای دسترسی به یک متغیر از طریق یک رفرنس null باعث ایجاد Null Reference Exception میشود و در صورتی که این اِکسپشن به درستی مدیریت نشود، باعث ایجاد کانفلیک در سایر بخشهای کد خواهد شد (لازم به ذکر است که زبانهایی مثل جاوا با قابلیت مدیریت اِکسپشنها میتوانند این مشکلات را مدیریت کنند.)
بسیاری از زبانهای برنامهنویسی چکهای امنیتی را به ویژگیهای خود افزودهاند تا از ارورهایی از این دست جلوگیری کنند. همانطور که در مثال زیر میبینیم، در زبان #C نسخهٔ 6 از سه مجموعهٔ (if (variable==null و else جلوگیری شده و شاید نزدیک به 10 خط کد را به ۱ خط کاهش داده است. در حقیقت، علامت ? بدین معنا است که اگر customers[0] ،customers یا customers[0].orders مقدار null داشتند، این مقدار به متغیر count اختصاص خواهد یافت و در غیر این صورت متد ()Count فراخوانی میشود (لازم به ذکر است که متغیر count باید از نوع int باشد که به اصطلاح Nullable است تا بتواند مقدار null را بگیرد):
int? count = customers?[0]?.Orders?.Count();
به طور کلی، یک کد خوب باید چک کند که آیا مقدار متغیر count برابر با null هست یا خیر که شما میتوانید از دستور (if (count.HasValue یا از عملگر ?? استفاده کنید تا در آینده به سادگی بتوانید متغیر count را مدیریت کنید (عملگر ?? یا Null Coalescing اگر مقدار count برابر با null باشد، مقدار مشخص شدهٔ پیشفرض را برمیگرداند.)
Lambda
با وجود نام عجیبوغریب، دستورات لامبدا فقط روشی برای تعریف و فراخوانی یک تابع بینام یا به اصطلاحاً Anonymous Function مورد استفاده قرار میگیرند که در برخی زبانها میتوانید یک فانکشن را به عنوان پارامتر به فانکشنی دیگر ارسال کرده و یا یک فانکشن را از فانکشن دیگری return کنید و این همان کاری است که لامبدا انجام میدهد. دستورات Lambda از زبانهای فانکشنالی مثل Lisp آغاز شد و زبانی همچون #C از سال 2007 این قابلیت را در خود گنجاند. سینتکس دستورات لامبدا چیزی شبیه به دستور زیر است:
()-> {code...}
زبانهایی که از لامبدا پشتیبانی میکنند شامل PHP (از نسخهٔ 5.3 به بعد)، Java (از نسخهٔ 8 به بعد)، JavaScript (گرچه در جاوااسکریپت این نوع دستورات لامبدا نامیده نمیشود)، Python و Swift هستند. در پاسخ به این سؤال که چرا از لامبدا استفاده کنیم، بایستی گفت که کوتاه و مختصر بودن این دستورات باعث میشود کدها کوتاهتر شوند و به آسانی قابلفهم باشند. برای مثال دو خط زیر یک لیست از اعداد فرد را میسازند:
List list = new List() { 1, 2, 3, 4, 5, 6, 7, 8 };
List oddNumbers = list.FindAll(x => (x % 2) != 0);
بعد از این دستور، oddNumbers شامل اعداد ۱ ،۳ ،۵ و ۷ است.
Closure
کلوژر یک فانکشن بینام یا یک بلوک کد است که به متغیرهایی که داخلش ساخته شدهاند دسترسی دارد. گرچه مفهوم کوژر پیچیده به نظر میرسد، اما ذکر یک نمونه کوژر در زبان سوئیفت میتواند کاربرد آن را روشنتر سازد:
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
print("\(incrementByTen())")
print("\(incrementByTen())")
print("\(incrementByTen())")
خروجی این کد 10، سپس 20 و در نهایت 30 خواهد بود. در حقیقت، تابع ()makeIncrementer با مقدار 10 فراخوانی میشود و این مقدار هر بار که ()incrementbyTen فراخوانی شود به total اضافه خواهد شد و این در حالی است که شما میتوانید به همین سادگی ()incrementer دیگری این بار با عدد 3 ایجاد کنید:
let incrementByThree = makeIncrementer(forIncrement: 3)
3 بار فراخوانی ()incrementbyThree خروجیهای 3، 6 و 9 را باز خواهد گرداند به طوری که در پشت صحنه، ()makeIncrementer نمونهای از کلاسی را ایجاد میکند که حاوی دیتایی است که باید افزوده شود.
از جمله مزایای استفاده از کوژرها میتوان به ساخت برنامهای قدرتمند با سورسکدی به مراتب سادهتر اشاره کرد به طوری که هر چیزی که اصطلاحاً Cognitive Load را کاهش دهد، باعث میشود سورسکد به آسانی قابلدرک شود که برای آشنایی با این اصطلاح میتوانید به مقالهٔ Cognitive Load: مقولهای که آشنایی با آن منجر به تسهیل پروسهٔ کدخوانی میشود مراجعه نمایید.
Concurrency
توجه داشته باشیم که Concurrency نباید با Parallel Computing اشتباه گرفته شود به طوری که از طریق Parallel Computing کد در پردازشگرهای مختلف به طور همزمان اجرا میشود اما این در حالی است که با استفاده از Concurrency میتوانید برنامهٔ خود را به چندین بخش مختلف که میتوانند بدون نوبت اجرا شوند، تقسیم کنید.
به طور کلی میتوان گفت که یکی از بزرگترین مزایای برنامههای به اصطلاح Asynchronous (نامتقارن) این است که فراخوانی وبسرویس، ایپیآی و یا هر سرویس به اصطلاح Third-party دیگری به همان اندازه طول میکشد که فراخوانی آن سرویسها به صورت Synchronous (متقارن) طول میکشد اما هرگز تِرِد مسدود نخواهد شد. به عبارت دیگر، در حین فراخوانی نامتقارن، یک تِرِد از پاسخ دادن به سایر درخواستها زمانی که منتظر کامل شدن اولین درخواست است مسدود نمیشود که در این صورت در کل به تعداد کمتری تِرِد نیاز خواهیم داشت. به عنوان نمونه داریم:
public async Task MethodAsync()
{
Task longRunningTask = LongRunningTaskAsync();
// any code here
int result = await longRunningTask;
DoSomething(result);
}
public async Task LongRunningTaskAsync() { // returns an int
await Task.Delay(1000);
return 1;
}
مثال فوق همزمانی را با استفاده از async/await در #C نشان میدهد. برای درک بهتر این موضوع، میتوان یک Crawler را مد نظر قرار داد (Crawler نرمافزاری است که مسئول جمعآوری دیتا از سراسر وب است.) در این دست نرمافزارها ممکن است همزمان برای استخراج دیتای چندین صفحه ریکوئست ارسال شود و به محض اینکه هر یک از ریسپانسها دریافت شدند، پردازش آن صفحه شروع خواهد شد و نیاز به توضیح نیست ترتیبی که دیتای صفحات گرفته میشود اصلاً مشخص نبوده و این دقیقاً همان کاری است که کانکارنسی میکند.
آنچه در این مقاله ارائه شد، نمونههایی از تکنیکهای کدنویسی پیشرفته است که دولوپرهای تازهکار با آشنایی با مواردی از این دست، میتوانند مهارتهای کدنویسی خود را ارتقاء داده و به برنامهنویسان حرفهایتری مبدل گردند.