خلاصه کتاب Pragmatic Programmer. درس 29

درس 29: Juggling the Real Worldقدیما کامپیوترها منعطف نبودن و مجبور بودیم توی استفاده رفتارمون رو براساس محدودیت هاشون منطبق کنیم، اما امروزه انتظارمون خیلی بیشتر شده، اونها توی زندگیمون آمیخته شدن و اونم دنیای بی نظم ما، هر اتفاقی هر لحظه ممکنه بیفته، حتی مدلی که فکر میکردیم و نیازهای سیستم عوض بشه و ...توی این درس میخایم یکسری موضوعات مرتبط با سیستم های سازگار رو مطرح کنیم.Eventsیک ایونت اماده بودن یک دیتا یا انجام کاری رو نشون میده. همچنین میتونه از بیرون سیستم بیاد، از کلیک کاربر، بالارفتن ارزش یک سهم، یا داخلی باشه مثل آماده شدن نتیجه یک محاسبه، پایان یک جستجو و ... از هرجایی که تولید بشه، اگر ما برنامه رو مدلی بنویسیم که عکس العمل نشون بده به ایونت ها، و اون کاری که انتظار میره انجام بشه براساس اونها، این نرم افزار بهتری توی دنیای واقعی بهمون میده و کاربرا احساس درگیری بیشتری با سیستم می کنن و سیستم هم بهتر از منابع استفاده میکنه.اما چطور یک همچین نرم افزاری بنویسیم؟ بدون استراتژی قطعا گیژ و کد نامطلوب تری خاهیم نوشت.چهار استراتژی که توی این حوزه کمکمون میکنه بررسی میکنیم:- Finite State Machines- The Observer Pattern- Publish/Subscribe- Reactive Programming and Streams· Finite State Machinesاستفاده از FSM خیلی ساده است، برخلاف خیلی از دولوپرها که ازش طفره میرن یا فکر میکنن سخته، یا در مواقعی که با سخت افزار درگیر هستیم کارامد نیست، یا کتابخونه هایی که برای استفاده ازش هست سخت و نامفهومه، اینطور نیست و این تفکرات اشتباس.آناتومی یک FSM عملگرایک استیت ماشین در اصل مشخصات و اطلاعات نحوه هندل کردن ایونت ها رو نگهداری میکنه. شامل یکسری state هست که در لحظه فقط یکی شون استیت فعلی سیستمی رو مشخص میکنه، هر استیت لیست رویدادهایی که بهش مرتبطه رو نگه میداره. و هر کدوم از ایونت ها مشخص میکنن که استیت بعدی سیستم چی باشه.مثلا فرض کنید در یک سیستم ما مسیج هایی رو به صورت multipart از روی وب سوکت دریافت میکنیم. اولین مسیج هدر هست و مسیج بعدی تعداد دیتای مسیج ها و بعد هم بدنه مسیج ها میاد سمتمون. طبق مدل FSM ما از Initial State شروع میکنیم، اگر پیامی از نوع هدر دریافت کردیم، به استیت Reading Message میریم،همچنین اگر وقتی در استیت اولیه هستیم، چیزی غیر از مسیج هدر برامون بیاد به استیت Error میریم. وقتی در استیت Reading Message هستیم، میتونیم بادی مسیج ها رو اکسپت کنیم تا به استیت Done یا Error برسیم.این مدل FSMو خیلی تمیز به صورت دیتا میشه در آورد و جدولی در نظر بگیرید که هر ردیفش یک State رو مشخص میکنه و هر ستون مشخص کننده ایونت هست و داخل سلول مشخص میکنیم که اگر اون ایونت اجرا شد استیت جدید ماشین چی باشه.مدل کاملتر FSM اینجوری میشه که هر جابجایی بین استیت ها به همراه خودش اجرای یک Action رو داشته باشه و کدی اجرا بشه.· The Observer Patternتوی این پترن ما سورسی از ایونت ها داریم که بهش Observable میگیم، همچنین یکسری کلاینت برای اون ایونت ها داریم که بهش Observers میگیم. یک observer علاقه خودش رو برای رجیستر شدن در یک ایونت ثبت میکنه و وقتی اون ایونت رخ میده observable لیستی از تمامی observer هایی که برای اون ایونت رجیستر کردن رو میبینه و فانکشنهایی که پاس داده بودن اجرا میشه. مثلا کد روبی زیرو در نظر بگیرید ماژول Terminate وقتی که اپلیکیشن میخاد بسته بشه کال میشه، اما قبلش به تمام observer هاش اطلاع میده که اپلیکیشن در حال بسته شدنه تا منابع داده تمپشونو روی دیسک اعمال کنند و از این قبیل کارها:event/observer.rbmodule Terminator CALLBACKS = [] def self.register(callback) CALLBACKS << callback enddef self.exit(exit_status) CALLBACKS.each { |callback| callback.(exit_status) }exit!(exit_status) endend Terminator.register(-> (status) { puts "callback 1 sees #{status}" })Terminator.register(-> (status) { puts "callback 2 sees #{status}" })Terminator.exit(99)$ ruby event/observer.rb callback 1 sees 99callback 2 sees 99چیز بیشتری از این نمیخاد، شما رفرنسی از یک فانکشن رو به لیستی از فاکنشهایی که به یک ایونت رفرنس میشن اضافه میکنید، و وقتی اون ایونت raise میشه تمام اون فانکنشها رو براتون کال میکنه. این پترن سالهای سال بهمون کمک کرده مخصوصا توی سیستم های دارای رابط کاربری، اما یک مشکل هم داره، اینکه وابستگی رو توی سیستم بالا میبره coupling. همچنین بخاطر اینکه کال بک های رفرنس شده به یک ایونت به صورت سینکورنایز کال میشن از نظر پرفورمنسی هم مشکل ساز میشه.این مشکل با استراتژی بعدی حل میشه.· PUBLISH/SUBSCRIBEاین پترن با وجود اینکه امکانات پترن Observer رو تعمیم میده، مشکل پرفورمنسی و وابستگی که گفتیم رو حل کرده. توی این مدل (pubsub) ما یک سری منتشرکننده (publishers) و یکسری مشترک (subscribers) داریم که با کانالها (channels) بهم کانکت هستن. این کانالها معمولا در بدنه کد دیگه ای پیاده سازی میشن و یا اکثرا در کتابخونه های خارج پروژه رفرنس و استفاده میشن یا بخشی از یک ساختار distributed هستن. و پیاده سازی این کانالها کاملا بر شما و کدتون پوشیده است.هر کانالی یک اسم داره، مشترکین به یک یا چند کانال رجیتسر میشن، و منتشرکننده ها ایونت ها رو براشون روی کانال ها مینویسن. برخلاف مدل observer ارتباط بین منتشرکننده و مصرف کننده ایونت توی کد شما پیاده سازی نمیشه و عمدتا async هم هست. هرچند این مدلو میتونید خودتون هم پیاده سازی کنید ولی اکثر سرویس دهنده های ابری دارن این خدماتو میدن و شما میتونید با استفاده ازشون نرم افزار خودتونو به هر جای دنیا وصل کنید و به هر سیستم یا ماژول خارجی، همچنین همه زبونای برنامه نویسی چندین کتابخونه برای این مدل دارند.این پترن به خوبی مشکلات observer و پوشش داد، اما استفاده زیاد سوار کردن کل سیستم روی این پترن هم میتونه مشکلات وپیچیدگی هایی رو به دنبال داشته باشه، مثل دیباگ کردن سخت تر و عدم دبیاگ لحظه ای و ...· REACTIVE PROGRAMMING, STREAMS, AND EVENTSاگر با اکسل کار کرده باشید، با برنامه نویسی ری اکتیو آشنا هستید، اگر سلولی فرمولی داشته باشه که اون فرمول به یک سلول دیگه رفرنس زده، اونوقت تغییر در مقدار سلول دوم، مقدار سلول اول رو دستخوش تغییر میکنه. فریمورک هایی زیادی وجود داره که کمک میکنه این مدل ری اکشن بین داده ها رو پیاده کنیم. توی حوزه بروزرها، فریمورکهایی مثل React , Vue JS جزو همین فریمورکها هستن.درسته که از ایونتها میشه برای تریگر کردن ری اکشن های مختلف در کد استفاده کرد، اما بررسی کردیم ایونت ها سختی های خودشونو دارن، اینجاست که Streams میان وسط. استریم ها بهمون این امکانو میدن که مثل ایونت ها رفتارشونو پیاده کنیم، مواقعی که با کالکشنی از دیتا مواجهیم. خوبی استریم ها هم اینه که دقیقا مثل بقیه کالکشن ها میتونیم مدیریت شون کنیم، فیلتر کردن و ... همچنین async هستن و پرفورمنسمونو خراب نمیکنن.تو سایت http://reactivex.io یکسری تعاریف، داکیومنتها و پیاده سازی ها در این مورد اورده شده. در اینجا یک مثال با RxJs میزنیم که یکی از کتابخونه های جاوا اسکریپت هستش.import * as Observable from 'rxjs' import { mergeMap } from 'rxjs/operators' import { ajax } from 'rxjs/ajax'import { logValues } from "../rxcommon/logger.js" let users = Observable.of(3, 2, 1) let result = users.pipe( mergeMap((user) => ajax.getJSON(`https://reqres.in/api/users/${user}`)) ) result.subscribe( resp => logValues(JSON.stringify(resp.data)), err => console.error(JSON.stringify(err)) )ایونت استریم ها به این صورت تعریف می شوند که عموما observable شون میتونه اونها رو به صورت پارالل اجرا بکنه در صورت رخداد. توی این مثال اطلاعات کاربرا رو از یک apiرایگان گت میکنیم و کدمون قراره یوزرهای با شناسه 1 2 3 رو بگیره. با اجرای این کد سه ریکوئست از سه استریم به صورت همزمان اجرا میشن.ایونت ها همه جا هستندایونتها همه جای سیستم هستن، از کلیک کاربر روی یک باتن بگیرید تا اکسپایر شدن یک تایمر، لاگین شدن کاربر، مچ شدن پترن خاصی در یک فایل در حال خواندن و ... ما باید توی کد به سبکی هندلشون کنیم که کمترین وابستگی رو در بهترین پرفورمنس ایجاد کنیم.دروس مرتبط: 28, 36منبع کانال تلگرامی: https://t.me/pragmaticprogrammer_fa