پرش به محتویات

سازمان‌دهی

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

- Main
- Page.Home
- Page.Search
- Page.Author

برای هر صفحه یک ماژول خواهم داشت که حول نوع داده Model متمرکز است. این ماژول‌ها از معماری Elm پیروی می‌کنند و شامل توابع init، update، view و هر تابع کمکی که نیاز دارید، هستند. در ادامه، فقط به گسترش این ماژول‌ها ادامه می‌دهم. انواع داده و توابعی که نیاز دارم را اضافه می‌کنم. اگر متوجه شوم که نوع داده سفارشی با چند تابع کمکی ایجاد کرده‌ام، ممکن است آن را به ماژول خود منتقل کنم.

قبل از اینکه چند نمونه ببینیم، می‌خواهم بر یک استراتژی مهم تاکید کنم.

از پیش برنامه‌ریزی نکنید

توجه کنید که ماژول‌های Page هیچ حدسی درباره آینده نمی‌زنند. سعی نمی‌کنم ماژول‌هایی تعریف کنم که در چندین مکان قابل استفاده باشند. سعی نمی‌کنم هیچ تابعی را به اشتراک بگذارم. این کار عمدی است!

در اوایل پروژه‌هایم، همیشه این طرح بزرگ را داشتم که چگونه همه چیز با هم هماهنگ خواهد شد. "صفحات برای ویرایش و مشاهده پست‌ها هر دو به پست اهمیت می‌دهند، بنابراین یک ماژول Post خواهم داشت!" اما وقتی برنامه را می‌نویسم، متوجه می‌شوم که فقط صفحه پست باید تاریخ انتشار داشته باشد. واقعا نیاز دارم که صفحه ویرایش را بطور متفاوتی پیگیری کنم تا داده‌ها را زمانی که زبانه‌ها بسته می‌شوند، کَش کنم. در نتیجه، آن‌ها واقعا باید بطور متفاوتی در سرور ذخیره‌سازی شوند. در نهایت، ماژول Post را به یک آشفتگی بزرگ تبدیل می‌کنم تا همه این نگرانی‌های متضاد را مدیریت کنم که این کار، مدیریت هر دو صفحه را بدتر می‌کند.

فقط با شروع از صفحات، بسیار آسان‌تر می‌شود که ببینیم چیزها مشابه هستند، اما یکسان نیستند. این نُرم رابط‌های کاربری است! بنابراین با مشاهده و ویرایش پست، به نظر می‌رسد که ممکن است به نوع داده ViewablePost و EditablePost برسیم، هر کدام با ساختار، توابع کمکی و دیکودِرهای JSON متفاوت. شاید این انواع داده به اندازه کافی پیچیده باشند که ماژول خود را داشته باشند، شاید هم نه! فقط کد را می‌نویسم و می‌بینم در ادامه چه اتفاقی می‌افتد.

این فرآیند کار می‌کند، زیرا کامپایلر امکان بازنویسی‌های بزرگ در کد را بسیار آسان می‌کند. اگر متوجه شوم که در ۲۰ فایل چیزی را به شدت اشتباه نوشته‌ام، فقط آن را اصلاح می‌کنم.

نمونه‌های واقعی

می‌توانید نمونه‌هایی از این ساختار را در پروژه‌های اوپن سورس زیر مشاهده کنید:

شوک فرهنگی

توسعه‌دهندگانی که از جاوااسکریپت می‌آیند، تمایل دارند عادات، انتظارات و اضطراب‌هایی را به همراه بیاورند که مخصوص آن پلتفرم است. این موارد، در آن پلتفرم واقعا مهم هستند، اما می‌توانند مشکلات جدی را هنگام انتقال به Elm ایجاد کنند.

غریزه‌های دفاعی

در ارایه The Life of a File به برخی از دانش‌های عامیانه جاوااسکریپت اشاره می‌کنم که شما را در Elm گمراه می‌کند:

  • "فایل‌های کوتاه‌تر را ترجیح دهید." در جاوااسکریپت، هر چه فایل شما طولانی‌تر باشد، احتمال اینکه یک تغییر پنهانی داشته باشید که باعث بروز یک باگ بسیار دشوار شود، بیشتر است. اما در Elm، این امکان‌پذیر نیست! فایل شما می‌تواند ۲۰۰۰ خط طول داشته باشد و این اتفاق نیفتد.
  • "معماری را از ابتدا درست کنید." در جاوااسکریپت، بازنویسی کد بسیار پر هزینه است. در بسیاری از موارد، بهتر است که آن را از ابتدا دوباره بنویسید. اما در Elm، بازنویسی کد کم هزینه و قابل اعتماد است! با اطمینان می‌توانید تغییرات را در ۲۰ فایل مختلف اعمال کنید.

این غریزه‌های دفاعی، شما را از مشکلاتی که در Elm وجود ندارد، محافظت می‌کنند. دانستن این موضوع به صورت ذهنی با درک آن به صورت شهودی، کاملا متفاوت است. مشاهده کرده‌ام که توسعه‌دهندگان جاوااسکریپت اغلب وقتی می‌بینند فایل‌ها از مرز ۴۰۰، ۶۰۰ یا ۸۰۰ خط عبور کرده‌اند، احساس درماندگی می‌کنند. بنابراین شما را تشویق می‌کنم که حد خود را در تعداد خطوط کد تحت فشار قرار دهید! ببینید چقدر می‌توانید پیش بروید. سعی کنید از کامِنت استفاده کنید، سعی کنید توابع کمکی بسازید، اما همه چیز را در یک فایل نگه دارید. داشتن این تجربه برای خودتان بسیار ارزشمند است!

MVC

برخی توسعه‌دهندگان، با مشاهده معماری Elm، بطور غریزی کد خود را به ماژول‌های جداگانه برای Model، View و Update تقسیم می‌کنند. این کار را نکنید!

این کار منجر به ایجاد مرزهای نامشخص و بحث برانگیز می‌شود. چه اتفاقی می‌افتد وقتی که Post.estimatedReadTime در هر دو تابع update و view استفاده می‌شود؟ کاملا منطقی است، اما به وضوح به یکی از آن‌ها تعلق ندارد. شاید به یک ماژول Utils نیاز داشته باشید؟ شاید واقعا یک نوع داده کنترلر باشد؟ معمولا سخت است که در این کد پیمایش کنید زیرا قرار دادن هر تابع اکنون یک پرسش هستی شناسانه است و همکاران شما نظریات متفاوتی دارند. واقعا estimatedReadTime چیست؟ جوهره آن چیست؟ تخمین؟ ریچارد چه فکری درباره آن می‌کند؟ زمان؟

اگر هر ماژول را حول یک نوع داده مرکزی بسازید، به ندرت با این نوع پرسش‌ها مواجه می‌شوید. یک ماژول Page.Home دارید که شامل توابع update و view است. توابع کمکی می‌نویسید. در نهایت یک نوع داده Post اضافه می‌کنید. یک تابع estimatedReadTime اضافه می‌کنید. شاید روزی تعدادی تابع کمکی درباره آن نوع داده Post وجود داشته باشد و شاید ارزش داشته باشد که به ماژول خود تقسیم شود. با این قرارداد، زمان بسیار کمتری را صرف بررسی و بازنگری مرزهای ماژول می‌کنید. در ادامه، کد نیز بسیار واضح‌تر می‌شود.

Component

توسعه‌دهندگانی که از React می‌آیند، انتظار دارند که همه چیز کامپونِنت باشد. تلاش فعالانه برای ساخت کامپونِنت در Elm یک دستورالعمل برای وقوع فاجعه است. مشکل اصلی این است که کامپونِنت، آبجِکت است:

  • کامپونِنت = حالت محلی + تابع
  • حالت محلی + تابع = آبجِکت

عجیب است که شروع به استفاده از Elm کنید و بپرسید "چگونه می‌توانم برنامه را با آبجِکت سازمان‌دهی کنم؟" در Elm چیزی بنام آبجِکت وجود ندارد! توصیه می‌شود بجای آن از نوع داده سفارشی و تابع استفاده کنید.

تفکر مبتنی بر کامپونِنت، شما را تشویق می‌کند که ماژول را بر اساس طراحی بصری برنامه‌تان ایجاد کنید. "یک نوار کناری وجود دارد، بنابراین به یک ماژول Sidebar نیاز دارم." بسیار آسان‌تر خواهد بود که فقط یک تابع viewSidebar بسازید و هر آرگومانی که نیاز دارد را به آن پاس بدهید. احتمالا، حتی هیچ وضعیتی هم نداشته باشد. شاید در حد یک یا دو فیلد؟ فقط آن را در Model صفحه قرار دهید. اگر واقعا ارزش تقسیم به ماژول خود را داشته باشد، متوجه آن می‌شوید زیرا یک نوع داده سفارشی با تعدادی تابع کمکی مرتبط خواهید داشت!

نکته این است که نوشتن یک تابع viewSidebar به این معنی نیست که نیاز به ایجاد فیلد در Model و شاخه در تابع update مربوطه دارید. در برابر این غریزه مقاومت کنید. فقط توابع کمکی که نیاز دارید را بنویسید.