زمانی که از کد تمیز صحبت می کنیم منظورمان همان نقاط استرسی است که برایتان توضیح دادم اما زمانی که صحبت از معماری تمیز می شود منظور نحوه ساختاردهی به پروژه هایتان است، اینکه کدها را «کجا» بنویسیم و چطور «ساختاردهی» کنیم (به طور مثال آیا از dependency injection استفاده کنیم یا خیر و مثال اینچنینی).
با این حساب کد تمیز و معماری تمیز دو مسئله جداگانه هستند. معماری تمیز به شدت وابسته به پارادایم برنامه نویسی شما (مثلا آیا از برنامه نویسی شیء گرا استفاده می کنید؟) و زبان برنامه نویسی شما است. استرس ما در کد تمیز روی یک مشکل خاص مانند یک تابع خاص است اما استرس ما در معماری تمیز روی تمامیت و ساختار کلی پروژه (مدل دهی و توزیع داده ها) است.
به طور مثال نحوه دسترسی به پایگاه داده مفهومی است که در بحث معماری پروژه مطرح می شود. این مسئله به این بستگی دارد که شما چه نوع API ای را طراحی کرده اید؟ آیا از GraphQL استفاده می کنید یا از REST API استفاده خواهید کرد؟ آیا از پایگاه های داده NoSQL مانند MongoDB استفاده می کنید یا از پایگاه های داده SQL مانند MySQL استفاده خواهید کرد؟ آیا از پارادایم برنامه نویسی شیء گرا استفاده می کنید یا به سراغ برنامه نویسی تابع گرا می روید؟ پوشه بندی و ساختار پروژه شما بر چه اساسی خواهد بود؟ پاسخ به هر کدام از این سوالات باعث ایجاد سوالات دیگری می شود و در نهایت به معماری پروژه شما می انجامد بنابراین نمی توانیم چنین مباحثی را به نوشتن کد تمیز و خوانا ربط بدهیم.
ما در این دوره با معماری تمیزکاری نداریم بلکه مبحث مورد نظر ما کد تمیز است. دوست داشتم قبل از اینکه این دوره را شروع کنید، این تفاوت را درک کنید و انتظارات صحیحی را در ذهنتان داشته باشید.
در همین ابتدا باید در مورد Clean Code یا کد تمیز صحبت کنیم. به نظر شما چه کدی تمیز محسوب می شود؟ فرض کنید شما کدی نوشته اید و حالا آن را به من یا یک برنامه نویس دیگر می دهید. آیا این برنامه نویس می تواند بدون دردسر اضافی متوجه کدهای شما بشود؟ به کدی کد تمیز می گوییم که درک آن برای انسان ها ساده باشد و از ترفندهای عجیب برای نوشتن آن استفاده نشده باشد. طبیعتا کد تمیز یک طیف بزرگ است؛ یعنی یک کد شاید تمیز باشد اما هنوز هم می تواند تمیز تر بشود.
بگذارید یک مثال برایتان بزنم. به کد پایتون زیر توجه کنید:
def create(m, n): if m == 'Max': return lambda v: v < n elif m == 'Min': return lambda v: v > n max = create('Max', 31) print(max(15)) print(max(32))
به نظر شما آیا این کد یک کد تمیز است؟ برای پاسخ به این سوال باید به کد بالا نگاهی بیندازید و ببینید آیا می توانید متوجه آن بشوید یا خیر؟ فرض من این است که شما یک برنامه نویس هستید اما زبان پایتون را نمی دانید. آیا متوجه کد بالا می شوید؟ ما در این کد یک تابع به نام create داریم که خودش توابع دیگری را می سازد. به توابعی که یک تابع دیگر را می سازند factory function یا higher order function می گویند.
این تابع در ابتدا بر اساس اینکه ما max یا min را انتخاب کرده باشیم یک مقدار خاص را می سازد (متغیر max) و سپس می توانیم مقادیر دیگر را بر اساس آن مقایسه کنیم. در مثال بالا من عدد 31 را ساخته ام و سپس اعداد 15 و 32 را با آن مقایسه کرده ام. ما Max را به تابع Create پاس داده بودیم بنابراین اعداد 15 و 32 باید کوچک تر از عدد اصلی (31) باشند، که در این صورت true را دریافت می کنیم اما اگر اینطور نباشد false را دریافت می کنیم.
احتمالا شما نیز حدس می زنید که این کد به هیچ تمیز یا clean به حساب نمی آید. چرا؟ به دلیل اینکه درک این کد (چه پایتون را بلد باشید و چه بلد نباشید) چندین دقیقه طول خواهد کشید و شاید مجبور به اجرای آن شوید تا بفهمید ماجرا از چه قرار است. اگر برای درک یک کد مثل کد بالا بیشتر از یک دقیقه نیاز به فکر داشته باشید یعنی آن کد تمیز نیست. شما به عنوان برنامه نویس نباید تمام هدف خود را روی کارایی کد بگذارید. کد بالا بدون خطا کار می کند و از نظر عملکرد هیچ مشکلی ندارد اما از نظر خوانایی (readability) مشکلات زیادی دارد. یکی از وظایف اصلی شما به عنوان برنامه نویس خواندن کد است بنابراین نوشتن کد تمیز برای همه یک ضرورت است. بیایید کد بالا را به یک کد تمیز تبدیل کنیم:
def create_validator(mode, number): if mode == 'Max': return lambda value: value < number elif mode == 'Min': return lambda value: value > number is_below_max = create_validator('Max', 31) print(is_below_max(15)) print(is_below_max(32))
حالا که نام متغیرها و آرگومان ها را به صورت توصیفی نوشته ایم، درک این کد بسیار ساده تر است. هر کس که زبان انگلیسی بلد باشد می تواند به سرعت متوجه روش کار این کد بشود. در اینجا عدد 31 را داریم که Max یا حداکثر است و اعداد دیگر باید کمتر از آن باشند. آیا متوجه صحبت من در ابتدای این مقاله شدید؟ من به شما گفتم که نوشتن کد تمیز تا حد زیادی به دانش شما از زبان انگلیسی وابسته است و منظورم دقیقا همین مسئله بود. یکی از فاکتور های نوشتن کد تمیز، نوشتن کد توصیفی است؛ یعنی تمام آرگومان ها، پارامترها و متغیرها باید نام کاملی داشته باشند. از آنجایی که نمی توانیم نام متغیرها را با زبان فارسی بنویسیم پیشنهاد می کنم حتما کمی روی زبان انگلیسی خود کار کنید.
البته ما می توانستیم کد بالا را به شکل دیگری نیز بنویسیم. مثلا از کلاس ها استفاده کنیم:
class ValidatableNumber: def __init__(self, number): self.number = number def is_bigger_than(self, other_number): return other_number < self.number def is_smaller_than(self, other_number): return other_number > self.number input_number = ValidatableNumber(31) print(input_number.is_bigger_than(15)) print(input_number.is_smaller_than(32))
همانطور که می بینید این کد نیز یک کد تمیز است و شاید برای بسیاری از افراد از روش اول نیز تمیز تر باشد. مسئله مهم اینجاست که همیشه راه حل های مختلفی برای رسیدن به کد تمیز وجود دارد و اینطور نیست که فقط یک پاسخ صحیح برای چنین سوالی داشته باشیم. بنابراین اگر بخواهیم کد تمیز را خلاصه کنیم، نکات زیر را ذکر خواهیم کرد:
برای نوشتن کد تمیز باید به کدها به عنوان یک داستان یا یک انشاء نگاه کنید، انگار که فردی می خواهد کتاب داستانی بخواند که شما نویسنده آن هستید. هدف شما این است!
تعیین نوع داده در زبان های مختلف
همانطور که قبلا نیز گفتم ما در این دوره از زبان های پایتون، جاوا اسکریپت و تایپ اسکریپت استفاده خواهیم کرد. زبان هایی مانند جاوا اسکریپت weakly typed هستند یعنی نوع داده ها به صورت پویا و توسط خود مفسر تعیین می شود اما می توانیم متغیرها را به یک تایپ کاملا جداگانه تغییر بدهیم. زبان هایی مانند پایتون نیز تا حدی strongly typed و dynamically typed است (نکات ریزی در مورد آن وجود دارد که برای این دوره مهم نیست). مثلا در کدی که بالاتر با هم دیدیم، من نوع داده ها را مشخص نکرده ام اما زبانی مانند تایپ اسکریپت (که در اصل افزونه ای برای جاوا اسکریپت است و زبان برنامه نویسی جداگانه ای نیست) یک زبان strongly typed است که یعنی می توانیم خودمان نوع داده ها را به صورت صریح مشخص کنیم. من مثال بخش قبلی را با زبان تایپ اسکریپت برایتان بازنویسی کرده ام:
function createValidator(mode: 'Max' | 'Min', number: number) { if (mode == 'Max') { return (value: number) => value < number; } else if (mode == 'Min') { return (value: number) => value > number; } } const isBelowMax = createValidator('Max', 31) console.log(isBelowMax(15)) console.log(isBelowMax(32))
همانطور که می بینید علاوه بر انتخاب نام پارامترها، نوع داده آن ها را نیز مشخص کرده ایم. به طور مثال در number: number قسمت اول نام پارامتر است و قسمتی که پس از علامت دو نقطه (:) می آید نوع آن پارامتر است (number یعنی این پارامتر باید از نوع عددی باشد). این مسئله معمولا برای جلوگیری از ایجاد خطاهای احتمالی در برنامه است اما ممکن است تا حدی نیز به کدنویسی تمیز کمک کند چرا که نوع داده ها را برایمان مشخص می کند. من دوست داشتم به این موضوع نیز اشاره کنم تا دچار سردرگمی نشوید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.