برنامه نویسی سیستم چیست؟
ترکیب دو ایدهی برنامهنویسی سطح پائین (کار با جزئیات سختافزاری و پیادهسازی ماشین) و طراحی سیستم (ساخت و مدیریت یک مجموعهی پیچیده از مؤلفههای مرتبط) به نظر غیرضروری میرسد؛ اما این قضیه تا چه اندازه صحیح است؟ و از تعریف مجدد سیستمها به چه نتیجهای میتوان رسید؟
دههی ۱۹۷۰: پیشرفت اسمبلی
برای درک تکامل اصطلاح برنامهنویسی سیستم بازگشت به منشأ سیستمهای کامپیوتری مدرن ضروری است. دقیقاً مشخص نیست چه کسی این عبارت را اختراع کرده است اما براساس پژوهشها تلاشهای جدی برای تعریف سیستمهای کامپیوتری تقریباً از اوایل دههی ۷۰ آغاز شده است. در مقالهای با نام زبانهای برنامهنویسی سیستم به این تعریف اشاره شده است:
یک برنامهی سیستمی مجموعهای یکپارچه از برنامههای فرعی یا زیربرنامه است، زیربرنامهها یک مجموعهی یکپارچه و بزرگتر از مجموعه اجزا را تشکیل میدهند که اندازه و پیچیدگی آن فراتر از یک حد مشخص است. از نمونههای متداول میتوان به سیستمهایی برای برنامهنویسی چندگانه، ترجمه، شبیهسازی، مدیریت اطلاعات و اشتراکگذاری زمانی اشاره کرد. فهرست زیر مشخصات برنامههای سیستمی را ارائه میدهد که بعضی از آنها را میتوان در برنامههای غیرسیستمی هم پیدا کرد و البته ممکن است یک سیستم مشخص تمام این ویژگیها را به صورت یکجا نداشته باشد:
۱. مسئلهی قابل حل ماهیت گستردهای دارد و شامل تعداد زیادی مسائل فرعی و متنوع است.
۲. از برنامهی سیستمی برای پشتیبانی از برنامههای کاربردی و نرمافزاری دیگر استفاده میشود اما درعینحال میتواند بستهی کاملی از برنامهها هم باشد.
۳. برنامهی سیستمی برای تولید پیوسته طراحی شده است نه بهعنوان راهحلی یک جا برای حل یک مشکل در برنامهها
۴. برنامهی سیستمی از نظر تعداد و انواع ویژگیهای تحت پشتیبانی به صورت پیوسته در حال تکامل است.
۵. یک برنامهی سیستمی به یک ساختار یا برنامهی مشخص داخل و میان ماژولها (برای مثال برقراری ارتباط) نیاز دارد و معمولاً توسط بیش از یک شخص یا گروهی از اشخاص طراحی و پیادهسازی میشود.
این تعریف تا حدودی قابل قبول است. سیستمهای کامپیوتری معمولاً دارای مقیاس گسترده و کاربرد طولانی هستند و به مرور زمان تغییر میکنند. البته با این که این تعریف بیشتر توصیفی است اما چشمانداز اصلی آن جداسازی زبانهای سطح پائین از زبانهای سیستمی است (برای مثال مقایسهی اسمبلی با فرترن).
هدف از زبان برنامهنویسی سیستم فراهم کردن زبانی است که بتوان بدون نگرانی در مورد دستکاری بیتها از آن استفاده کرد و درعینحال به کدی دست یافت که عملکرد آن از کدهای دستی بهتر باشد. چنین زبانی باید اختصار و خوانایی زبانهای سطح بالا را با بازدهی فضا و زمان و دسترسی به امکانات سیستمعامل و ماشین زبان اسمبلر را ترکیب کند. زمان طراحی، نوشتن و اشکالزدایی باید بدون تحمیل سربار بر منابع سیستمی به حداقل برسند. پژوهشگرهای CMU زبانی به نام BLISS (زبانی برای برنامهنویسی سیستم) را منتشر کردهاند که به این صورت تعریف میشود:
BLISS یک زبان پیادهسازی است، البته با توجه به این که هدف تمام زبانهای کامپیوتری پیادهسازی است، این تعریف کمی مبهم است. اما در واقع مفهوم عمومی این اصطلاح مدنظر است یعنی زبانهای سطح بالایی که بیشتر بر یک برنامهی مشخص مثل نوشتن سیستمهای بزرگ نرمافزاری تولیدی برای یک ماشین مشخص تأکید میکنند.
مؤلفان، زبان پیادهسازی را بالاتر از اسمبلی و پائینتر از زبان طراحی میدانند
زبانهای هدفمند مثل کامپایلر، کامپایلرها در این دسته قرار نمیگیرند و البته لزوما مستقل از ماشین هم نیستند. در این تعریف بر اصطلاح پیادهسازی تأکید میشود و از کلماتی مثل طراحی و مستندسازی استفاده نشده است؛ بنابراین از یک زبان پیادهسازی انتظار نمیرود که طراحی یک سیستم بزرگ یا مستندسازی آن را توصیف کند. مفاهیمی مثل استقلال ماشین، توصیف مشابهی از طراحی و پیادهسازی، خودمستندسازی و مفاهیم دیگر دارند و معیارهایی برای ارزیابی زبانهای مختلف هستند.
در اینجا مؤلفان، زبان پیادهسازی را بالاتر از اسمبلی و پائینتر زبان طراحی میدانند. براساس پژوهشهای قبلی، طراحی و پیادهسازی سیستم هرکدام زبان مجزایی دارند. آخرین مدخل مربوط به برنامهنویسی سیستم را میتوان در یک متن آموزشی در مورد یادگیری برنامهنویسی سیستم مشاهده کرد که در ۱۹۷۲ نوشته شده است.
اما تعریف دقیق برنامهنویسی سیستم چیست؟
میتوان کامپیوتر را مثل جانداری درنظر گرفت که از تمام دستورها اطاعت میکند. براساس یک تصور دیگر، کامپیوترها انسانهایی هستند که از فلز ساخته شدهاند یا برعکس، انسانها کامپیوترهایی هستند که از گوشت و خون تشکیل شدهاند. بااینحال، با نگاهی دقیقتر به کامپیوترها میتوان به این نتیجه رسید که اساسا کامپیوترها ماشینهایی تابع دستورالعملهای مشخص و ابتدایی هستند.
در اولین روزهای اختراع کامپیوتر، مردم با دستورالعملهای ابتدایی بین دو حالت On و Off با کامپیوتر ارتباط برقرار میکردند. خیلی زود مردم بهدنبال دستورالعملهای پیچیدهتر رفتند. برای مثال میخواستند خروجی این مسئله را در کامپیوتر ببینند: X=30*Y؛ با توجه به این که Y=10 درنتیجه X کدام است؟ کامپیوترهای کنونی بدون برنامههای سیستمی قادر به درک چنین زبانی نیستند.
مبانی برنامهنویسی سیستم
برنامههای سیستمی (برای مثال کامپایلرها، لودرها، پردازندههای ماکرو، سیستمهای عامل) برای تطبیق بهتر کامپیوترها با نیازهای کاربران توسعه یافتند. علاوه بر این مردم بهدنبال کمک یا دستیارهایی برای آمادهسازی برنامههای خود بودند. این تعریف یادآوری میکند سیستمها در خدمت مردم هستند حتی اگر صرفاً زیرساختهایی باشند که ارتباط مستقیمی با کاربرها ندارند.
دههی ۱۹۹۰: ظهور اسکریپتنویسی
در دههی ۷۰ و ۸۰ اغلب پژوهشگرها برنامهنویسی سیستم را نقطهی مقابل برنامهنویسی اسمبلی میدانستند. در آن دوره ابزار خوبی برای ساخت سیستمها وجود نداشت (البته هیچ اطمینانی از وجود Lisp در میان این زبانها وجود ندارد هیچ کدام از منابع به Lisp اشاره نکردهاند، بااینحال ماشینهای Lisp وجود داشتند).
در اواسط دههی ۹۰، با ظهور زبانهای اسکریپتنویسی داینامیک تغییرات عمدهای در زبانهای برنامهنویسی رخ داد. بهبود سیستمهای اسکریپتنویسی مثل Bash، زبانهایی مثل پرل (۱۹۸۷)، Tcl ، پایتون (۱۹۹۰)، Ruby ، PHP و جاوا اسکریپت (۱۹۹۵) به توسعهی برنامهنویسی کمک کرد. این تغییرات در مقالهی تأثیرگذار اوسترهاوت با عنوان اسکریپت نویسی: برنامهنویسی سطح بالای قرن بیستویک (۱۹۹۸) به اوج خود رسیدند. به موج حاصل از این تغییرات دوگانگی اوسترهاوت بین زبانهای برنامهنویسی سیستمی و زبانهای اسکریپتنویسی گفته میشود.
زبانهای اسکریپتنویسی برای وظایفی متفاوت با زبانهای برنامهنویسی سیستمی طراحی شدهاند و همین مسئله ریشهی تفاوتهای بنیادی این دو زبان است. زبانهای برنامهنویسی سیستمی برای تولید ساختارهای دادهای و الگوریتمها از ابتداییترین عناصر کامپیوتری مثل کلمات حافظه طراحی شدهاند.
درمقابل، زبانهای اسکریپتنویسی برای چسباندن طراحی شدهاند: مجموعهای از مؤلفههای قدرتمند دارند و در اصل برای اتصال این مؤلفهها با یکدیگر در نظر گرفته شدهاند. زبانهای برنامهنویسی برای کمک به مدیریت پیچیدگی، Strongly Typed یا وابستهی زیاد به نوع هستند؛ مفهومی که درمقابل Weakly Typed یا وابسته کم به نوع قرار میگیرد. به این معنی که باید نوع متغیرها، ورودیها و خروجیها توابع و... دقیقاً تعیین شوند و کامپایلر پیش از اجرای کدها و رسیدن به مرحلهی اجرای Runtime و بیلد، این مورد را بررسی میکند. در حالیکه زبانهای اسکریپتنویسی Typeless (بدون نوع) هستند. برای مثال میتوان از تعریف متغیر بدون نوع در آنها استفاده کرد و کامپایلر تمام کارها را برعهده دارد. از اینرو برای سادهسازی روابط بین مؤلفهها و توسعهی سریع برنامهها از آنها استفاده میشود. گرایشهای جدید از جمله ماشینهای سریعتر، زبانهای اسکریپتنویسی بهتر، اهمیت فزایندهی واسطههای کاربری گرافیکی و معماریهای مؤلفهای و رشد اینترنت بهشدت کاربرد زبانهای اسکریپتنویسی را بالابردهاند.
در سطح تخصصی اوسترهاوت اسکریپتنویسی و سیستم را در راستای محورهای Type Safety (ایمنی نوع) و دستورالعمل به ازای هر عبارت مقایسه کرده است. Type Safety یا ایمنی نوع به قابلیت یا ویژگی یک زبان برنامهنویسی برای جلوگیری یا کاهش رخ دادن Type Errors یا خطاهای ناشی از عدم تطابق نوع گفته میشود. برای مثال تعریف متغیری از نوع اعشاری و به کارگیری آن به جای اعداد صحیح، منجر به وقوع یک Type Error میشود. اوسترهاوت در سطح طراحی بر نقشهای جدید هر کلاس زبانی تأکید میکند: برنامهنویسی سیستم برای ساخت مؤلفهها و اسکریپتنویسی برای چسباندن آنها به یکدیگر درنظر گرفته میشوند.
مقایسهی زبانهای برنامهنویسی براساس سطح و درجهی Typing آنها (زبانهای سطح بالاتر دستورالعملهای ماشین بیشتری را برای هر عبارت زبانی اجرای میکنند) زبانهای برنامهنویسی سیستم مثل C از نوع قوی و سطح متوسط هستند (۵ تا ۱۰ دستورالعمل به ازای هر عبارت). زبانهای اسکریپتنویسی مثل Tcl از نوع ضعیف و سطح بالا هستند (۱۰۰ تا ۱۰۰۰ دستورالعمل به ازای هر عبارت).
تقریباً در همین زمان بود که زبانهای موسوم به Garbage Collected به محبوبیت رسیدند. Garbage Collection، زبالهروبی یا بازیافت حافظه، نوعی مدیریت حافظهی خودکار است. در طی این فرایند فضایی از حافظهی کامپیوتر که قبلا درگیر نگهداری دادهی موردنیاز یک برنامهی کامپیوتری بوده است و اکنون آن برنامه دیگر نیازی به این داده ندارد، آزاد میشود و برای ذخیره و نگهداری دادهی جدید مورد استفاده قرار میگیرد.
در این دهه جاوا و #C به غولهای برنامهنویسی که امروزه میشناسیم تبدیل شدند. بااینحال این دو زبان از ابتدا در گروه زبانهای برنامهنویسی سیستمی قرار نگرفتند و از آنها برای طراحی تعداد زیادی از بزرگترین سیستمهای نرمافزاری دنیا استفاده شده است. اوسترهاوت بهطور آشکار توضیح میدهد که در دنیای اینترنت کنونی از جاوا برای برنامهنویسی سیستم استفاده میشود.
دههی ۲۰۱۰: مرزها محو میشوند
از دههی گذشته مرز بین زبانهای اسکریپتنویسی و زبانهای برنامهنویسی سیستمی در حال محو شدن است. شرکتهایی مثل Dropbox توانستند سیستمهای مقیاسپذیر و بزرگی را روی پایتون توسعه دهند. از جاوا اسکریپت برای تبدیل UI-های پیچیده و بلادرنگ (Real-Time) در میلیاردها صفحهی وب استفاده شده است. طبقهبندی تدریجی در پایتون، جاوا اسکریپت و دیگر زبانهای اسکریپتنویسی شدت پیدا کرد و به این صورت گذار از کد اولیه به کد تولید تنها با اضافه کردن اطلاعات نوع ایستا امکانپذیر شد.
درعینحال منابع انبوه مهندسی برای زبانهای ایستا (مثل جاوا اسکریپت) و زبانهای پویا (مثل LuaJIT از Lua یا V8 جاوا اسکریپت و PyPy پایتون) وارد کامپایلرهای JIT شدند و آنها را به رقیب عملکردی سیستمهای زبانهای برنامهنویسی سیستمی (C++، C) تبدیل کردند. سیستمهای توزیعشده و بزرگی مثل اسپارک در اسکالا نوشته شدند. زبانهای برنامهنویسی جدید مثل جولیا و سویفت هم محدودیتهایی را برای زبانهای زبالهروب (Garbage Collector) به وجود آوردند.
هیئتی به نام برنامهنویسی سیستم در سال ۲۰۱۴ و بعد از آن، شامل بزرگترین مغزهای زبانهای برنامهنویسی کنونی از جمله بیجارن استروستراپ (خالق ++C)، روب پایک (خالق Go)، آندری آلکساندرسکو (توسعهدهندهی D) و نیکو ماتساکیس (توسعهدهندهی Rust). زبان برنامهنویسی سیستم در سال ۲۰۱۴ را این گونه توصیف میکنند:
رابطهی برنامهنویسی سیستم با عملکرد بالا چیست؟ با محدودیتهای منابع و کنترل سختافزاری چطور؟ یا زیرساخت ابری؟ بهطورکلی به نظر میرسد زبانهایی مثل C ،C++ ،Rust و D از نظر سطح انتزاع و خلاصه بودن از ماشین متمایز میشوند. این زبانها جزئیات سختافزار مثل تخصیص حافظه یا قالب و مدیریت دقیق منابع را نمایش میدهند.
یک تعریف دیگر هم برای آن وجود دارد: در صورت روبهرو شدن با مشکل بازدهی یا بهینهسازی چه مقدار آزادی برای حل آن دارید؟ در زبانهای برنامهنویسی سطح پائین با کنترل دقیق جزئیات ماشین میتوانید هر مشکلی را حل کنید. میتوانید دستورالعمل را بر کل آرایهها اعمال کنید و ساختار دادهای بهدستآمده را در کش (Cache) ذخیره کنید. همانطور که انواع ایستا مثل جمع اعداد صحیح با اطمینان بیشتری اجرا میشوند زبانهای برنامهنویسی سطح پائین هم اجرای مطمئنتر دارند بهطوریکه کدها همانطور که تعریف میشوند اجرا میشوند.
درمقابل، بهینهسازی زبانهای تفسیر شده بسیار پیچیده است. بهراحتی نمیتوان از اجرای قابلانتظار کد توسط Runtime اطمینان حاصل کرد. همین مسئله در کامپایلرهای موازی خودکار هم وجود دارد (Vectorizaiton یا برنامهنویسی آرایهای یک مدل برنامهنویسی نیست. بلکه مانند نوشتن یک واسطه در پایتون است، برای مثال انتظار دارید یک تابع در صورت فراخوانی خروجی int (صحیح) تولید کند).
امروز: بنابراین برنامهنویسی سیستم چیست؟
اغلب به برنامهنویسی سطح پائین، برنامهنویسی سیستم میگویند (با اشاره به جزئیات ماشین)؛ اما معنی سیستم چیست؟ با بازگشت به تعریف ۱۹۷۲ میتوان گفت:
۱.مسئلهی قابل حل، ماهیت گستردهای دارد و شامل تعداد زیادی مسائل فرعی و متنوع است.
۲. از برنامهی سیستمی برای پشتیبانی از برنامههای کاربردی و نرمافزاری دیگر استفاده میشود اما درعینحال میتواند بستهی کاملی از برنامهها هم باشد.
۳. برنامهی سیستمی برای تولید پیوسته طراحی شده است نه بهعنوان راهحلی یک جا برای حل مشکلی در برنامهها.
۴. برنامهی سیستمی از نظر تعداد و انواع ویژگیهای تحت پشتیبانی به صورت پیوسته در حال تکامل است.
۵. یک برنامهی سیستمی به یک ساختار یا برنامهی مشخص داخل و میان ماژولها (برای مثال برقراری ارتباط) نیاز دارد و معمولاً توسط بیش از یک شخص یا گروهی از اشخاص طراحی و پیادهسازی میشود.
به نظر میرسد این گزینهها بیشتر به مشکلات مهندسی نرمافزار اشاره دارند (پیمانهای بودن، قابلیت استفادهی مجدد، تکامل کد) تا مشکلات عملکردی سطح پائین. این یعنی هر زبان برنامهنویسی که حل این مشکلات را در اولویت قرار دهد یک زبان برنامهنویسی سیستمی است! البته این گزینهها برای تعریف یک زبان برنامهنویسی سیستمی کافی نیستند؛ بنابراین میتوان گفت زبانهای برنامهنویسی پویا یا داینامیک از زبانهای سیستمی دور هستند.
اما مفهوم دقیق این تعریف چیست: زبانهای تابعی مثل Ocaml و Haskell بیشتر از زبانهای سطح پائین مثل C یا ++C به سیستم وابسته هستند. هنگام آموزش برنامهنویسی باید اصول برنامهنویسی تابعی مثل ارزش ثبات، تأثیر سیستمهای نوع غنی در بهبود طراحی واسطه و استفاده از توابع مرتبه بالاتر را درنظر گرفت. مدارس باید برنامهنویسی سیستم و سطح پائین را آموزش دهند.
بنابراین آیا تفاوتی بین برنامهنویسی سیستم و مهندسی نرمافزار وجود دارد؟ پاسخ منفی است اما مشکل اینجا است که مهندسی نرمافزار و برنامهنویسی سطح پائین اغلب اوقات به صورت مجزا تدریس میشوند. بااینحال اغلب کلاسهای مهندسی نرمافزار معمولاً بر شعار نوشتن واسطهها و تستهای مناسب جاوا متمرکز هستند، به همین دلیل لازم است روش طراحی سیستم با توجه به محدودیتهای زیاد منابع آموزش داده شود.
بهتر است به جای عبارت برنامهنویسی سیستم از برنامهنویسی سطح پائین استفاده شود
شاید به این دلیل برنامهنویسی سطح پائین را سیستم مینامند که جذابترین سیستمهای نرمافزاری از نوع سطح پائین هستند (برای مثال، پایگاه دادهها، شبکهها، سیستمهای عامل و...). ازآنجاکه سیستمهای سطح پائین محدودیتهای زیادی دارند، برای طراحی آنها نیاز به تفکر خلاق است.
در قدم بعدی، برنامهنویس زبان سطح پائین باید به این سؤال پاسخ دهد که کدام ایدههای طراحی سیستم را میتوان برای کار با سختافزار مدرن تطبیق داد. انجمن Rust در این رابطه عملکرد نوآورانهای داشته است، این انجمن چگونگی پیادهسازی اصول برنامهنویسی تابعی یا طراحی نرمافزاری بر مسائل سطح پائین را بررسی میکند (برای مثال مسائلی مثل قراردادها، کنترل خطا یا امنیت حافظه).
بهطور خلاصه بهتر است بهجای عبارت برنامهنویسی سیستم از برنامهنویسی سطح پائین استفاده کرد. اهمیت طراحی سیستمهای کامپیوتری بهعنوان یک رشته یا زمینه به خاطر نام آن نیست؛ بنابراین جداسازی این دو مفهوم، طراحی زبان برنامهنویسی را شفاف میکند و دیدگاههای مشترکی را نسبت به این دو حوزه به وجود میآورد: چگونه میتوان سیستمی را حول محور ماشین یا برعکس طراحی کرد؟