سارا رضائی
سارا رضائی
خواندن ۷ دقیقه·۴ سال پیش

پنهان سازی اطلاعات

در نوشته ی مربوط به برنامه نویسی ماژولار، بحث "عمق ماژول" مطرح شد و بهترین ماژول ها، ماژول های عمیق معرفی شدند. در این نوشته می خواهیم در مورد تکنیکی برای ساخت ماژول های عمیق بحث کنیم.

پنهان سازی اطلاعات (Information hiding)

این تکنیک اولین بار توسط David Parnas مطرح شد. ایده ی اصلی این است که، هر ماژول، با توجه به تصمیماتِ طراحی، باید میزان مشخصی از دانش را کپسوله کند. این دانش، در پیاده سازی ماژول تعبیه می شود و نباید در واسط آن مشاهده شود، بنابراین از دید سایر ماژول ها پنهان است.

شما به عنوان یک سیستم، که بین خودتان و دنیای بیرون مرزهایی دارید، از این تکنیک بهره می برید. نام و سایر اطلاعات شخصی شما، در مغز شما مخفی است و امکان دسترسی مستقیم به آن وجود ندارد. برای دستیابی به آن اطلاعات، افراد باید از شما سوال بپرسند و شما هر میزان از اطلاعات را که مناسب تشخصی دهید به آن ها می گویید.
پنهان سازی اطلاعات یعنی، تنها جزئیاتی که برای جهانِ بیرون (از ماژول) ضروری است، به جهانِ بیرون نمایش داده شود و غیر از آن، سایر اطلاعات، از دنیای بیرون مخفی شود.

اطلاعاتی که پنهان می شود، معمولا شامل جزئیاتِ انجام یک کار است. برای مثال:

  • چگونه یک کار مشخص، توسط چند thread انجام شود
  • چطور یک TCP Connection ایجاد و مدیریت شود
  • چگونه یک فایل JSON پردازش شود و اطلاعات آن بازیابی گردد
  • داده ها چگونه در یک B-tree ذخیره شود

در واقع این اطلاعات، شامل ساختار داده ها و الگوریتم هایی است که به مکانیزمِ انجام کار مرتبط هستند.

این پنهان سازی از دو جهت به مدیریت پیچیدگی در نرم افزار کمک می کند:

  1. واسطِ ماژول ساده می شود و یک نمای انتزاعیِ بسیار ساده شده از ماژول را به دنیای بیرون نشان می دهد. در نتیجه برنامه نویس ها، بار شناختی کمتری در استفاده از این ماژول خواهند داشت. برای مثال، برنامه نویسی که از کلاس B-tree استفاده می کند، نیازی ندارد که در مورد چگونگی balance ماندن tree دانشی داشته باشد.
  2. توسعه ی سیستم ساده تر است. اطلاعاتی که درون ماژول پنهان شده اند، اگر نیازی به تغییر یا توسعه ی جدید داشته باشند، هیچ وابستگی و تاثیری روی ماژول های دیگر ندارند. مثلا اگر درون ماژول به جای استفاده از یک پروتکل برای ارتباط با شبکه استفاده می شده، و الان تصمیم به تغییر پروتکل داشته باشیم، استفاده کنندگان از این ماژول، از این تغییر متاثر نخواهند شد و اصلا تغییری احساس نخواهند کرد.

زمانی که یک ماژول را طراحی می کنید، باید با دقت، به اطلاعاتی که می توانند در ماژول پنهان شوند فکر کنید، هرچقدر مقداری بیشتری اطلاعات در ماژول مخفی شود، واسطِ آن ساده تر می شود و ماژول عمیق تر خواهد بود.

بهترین فرم پنهان سازی اطلاعات، زمانی است که اطلاعات، کاملا در پیاده سازی ماژول مخفی شده اند، به صورتی که به طور کلی، از دید استفاده کنندگان غیر قابل مشاهده هستند. با این حال، شرایطی هم وجود دارد که اطلاعات، فقط از دیدِ بخشی از استفاده کنندگان مخفی است، و سایرین، آن ها را می بینند. در این شرایط هم تا اندازه ای پنهان سازی اطلاعات وجود دارد، و به اندازه ی خودش باعث کم کردن وابستگی می شود و ارزشمند است.

پنهان سازی اطلاعات در سطح یک کلاس هم می تواند وجود داشته باشد. می توانیم متدهای private در کلاس تعریف کنیم، که هر یک دانشی را در خودش کپسوله می کند و از بقیه ی اعضای کلاس آن را مخفی می کند. همچنین اگر متغیرهایی در سطح کلاس دارید، تا جای ممکن، موارد استفاده از آن را محدود کنید.

دقت کنید، تنها تعریف متغیرها و متدها در داخل یک کلاس به صورت private، پنهان سازی اطلاعات نیست. البته که این کار می تواند به پنهان سازی اطلاعات کمک کند، چرا که دسترسی به آن آیتم ها از بیرون کلاس غیر ممکن می شود، اما هنوز هم می توان از طریق متدهای public کلاس، امکان مشاهده ی این متغیرها را از دنیای بیرون ممکن ساخت. اگر این اتفاق بیفتد، در واقع این متغیرها پنهان نشده اند و هیچ فرقی با متغیرهای public ندارند.

پنهان سازی اطلاعات تنها زمانی معنی دارد، که اطلاعاتِ مخفی شده، در بیرون از ماژول مورد نیاز نباشد. اگر اطلاعات در بیرون از ماژول مورد نیاز است، نباید پنهان شود. برای مثال، اگر پرفورمنس یک ماژول، بستگی به تنظیماتی دارد که از بیرون باید تعیین شود، این تنظیمات باید در واسط ماژول قرار گیرد، تا هر سیستم بیرونی، بتواند بر اساس نیاز خودش تنظیمات مناسب را به ماژول بدهد. با این حال ممکن است، راهی باشد تا این تنظیمات درون خود ماژول مدیریت شود و به سیستم بیرون نمایش داده نشود، در این صورت باید حتما از این روش استفاده کرد تا واسط ساده تر باشد.

تلاش ما به عنوان طراح نرم افزار باید این باشد که میزان اطلاعاتی را که از بیرون ماژول قابل مشاهده است به حداقل برسانیم.

نشت اطلاعات (Information leakage)

نشت اطلاعات زمانی اتفاق می افتد، که یک تصمیمِ طراحی، در چندین ماژول منعکس می شود و باعث ایجاد وابستگی بین ماژول ها می شود. در نتیجه، در آینده، هر تغییر در آن تصمیمِ طراحی، باعث تغییر در تمام آن ماژول ها خواهد شد.

به عنوان یک قانون کلی، اگر یک بخش از اطلاعات، در واسط ماژول قرار گیرد، بنابراین آن اطلاعات نشت کرده است. به همین دلیل است که اصرار بر ساده بودن واسطِ ماژول داریم.

البته ممکن است اطلاعات در واسطِ ماژول قرار نگیرد ولی باز هم نشت کند. برای مثال، تصور کنید دو کلاس داریم که هر دو در مورد کار با یک نوع خاص از فایل اطلاعات دارند، مثلا یکی فایل را می خواند و یکی روی آن می نویسد. با این که شاید هیچ اطلاعاتی از فایل، در واسط این دو کلاس مشاهده نشود، ولی هر دوی آن ها به فرمت فایل وابستگی دارند و اگر فرمت تغییر کند، هر دو کلاس باید تغییر کنند. این نوع از نشتِ اطلاعات، از نوعی که در واسط مشاهده می شود خطرناک تر است، چرا که واضح نیست و به راحتی می تواند از دید ما مخفی بماند.

یکی از Code Smell های معروف، Feature Envy است، که زمانی اتفاق می افتد که یک کلاس، بیشتر از این که با فیلد های یا متدهای خودش کار کند، با دیتای یک کلاس دیگر کار داشته باشد. در واقع به اطلاعاتِ آن کلاسِ دیگر، حسادت کند. به نظر من این مفهوم، با مفهومی که در حال توضیح آن هستیم، ارتباط نزدیکی دارد.
اگر Code Smell دیگری به ذهن شما می رسد که مرتبط به مفهوم نشت اطلاعات است، در قسمت نظرات بنویسید.

نشتِ اطلاعات، یکی از مهم ترین مسائلی است که ما به عنوان برنامه نویس باید به آن توجه کنیم و نسبت به آن حساسیت داشته باشیم. اگر در کد، با نشتِ اطلاعات بین کلاس ها مواجه شدید، این سوال را بپرسید: "چطور می توان این کلاس ها را دوباره طراحی کرد، به صورتی که این اطلاعاتِ خاص، تنها روی یک کلاس تاثیر بگذارد؟" اگر کلاس هایی که درگیر این اطلاعات هستند، کوچک هستند و وابستگی شدیدی به اطلاعاتِ نشت شده دارند، شاید ادغامِ آن ها به صورتِ یک کلاس، منطقی باشد. یک روش دیگر هم این است که اطلاعاتِ نشت شده را از هر دو کلاس خارج کرده و درون یک کلاس دیگر، کپسوله کنیم. ولی در استفاده از این روش، باید دقت کنیم که واسطِ کلاس جدید ساده باشد و شامل جزئیات نباشد. اگر کلاس جدید بخواهد مقدار زیادی جزئیات را در واسط خودش به بیرون معرفی کند، ارزشی ندارد، چرا که نشتِ اطلاعات هنوز هم وجود دارد (این بار از طریق واسط).

تفکیک زمانی

تفکیکِ زمانی، یک اشتباه طراحی است که می تواند باعث نشت اطلاعات شود، و موقعی اتفاق می افتد که طراحیِ سیستم، به ترتیب زمانیِ عملیاتِ اجرایی، بستگی داشته باشد. نرم افزاری را در نظر بگیرید که یک فایل را می خواند، محتویاتش را تغییر می دهد و فایل را ذخیره می کند. اگر این نرم افزار شامل سه کلاس باشد، یکی برای خواندن فایل، یکی برای اعمال تغییرات و یکی برای ذخیره ی نسخه ی جدید، دچار تفکیکِ زمانی شده ایم. اشکال اینجاست که، هر دو کلاسی که مرتبط با خواندن و ذخیره ی فایل هستند، درباره ی فرمت فایل دانش دارند و این یعنی نشتِ اطلاعات. راه حل این است که مکانیزم مربوط به خواندن فایل و ذخیره ی آن را در یک کلاس بگذاریم. این کلاس در فازهای خواندن و نوشتن فایل، مورد استفاده قرار می گیرد. در واقع باید به عملیات فکر کنیم، نه به ترتیب انجام کارها.

افتادن در تله ی تفکیکِ زمانی بسیار ساده است، به این علت که زمانِ نوشتن کد، چیزی که در ذهن شماست، ترتیب زمانی عملیات است. با این حال، نباید بگذاریم که ترتیبِ انجام کارها، در ساختار ماژول های ما تاثیر بگذارد.

زمانی که ماژول ها را طراحی می کنیم، باید به دانشی که برای انجام هر عملیات مورد نیاز است توجه کنیم، نه ترتیب زمانی عملیات.


خلاصه

ماژول های عمیق و پنهان سازی اطلاعات، مفاهیمی هستند که بسیار به هم مرتبط هستند. اگر یک ماژول، مقدار زیادی اطلاعات را پنهان کند، بنابراین پیاده سازی آن پیچیده و واسط آن ساده می شود، پس ماژول عمیقی است.

زمانی که ماژول ها را طراحی می کنید، سعی کنید از ترتیبِ اجرا شدنِ عملیات تاثیر نپذیرید. به جای آن، به دانشی که برای عملیاتِ نرم افزار نیاز است توجه کنید و هر ماژول را طوری طراحی کنید که تکه ای از این دانش را کپسوله سازد. در این صورت یک طراحی ساده و تمیز و ماژول هایی عمیق خواهید داشت.


منابع

A philosophy of software design

Information Hiding and Encapsulation in OOP

برنامه نویسی ماژولاربرنامه نویسیطراحی نرم افزارپنهان سازی اطلاعات
linkedin.com/in/sara-rez
شاید از این پست‌ها خوشتان بیاید