- Mobile Engineering
- Flutter
- Architecture
کراسپلتفرم در برابر نیتیو موبایل: یک تصمیم مهندسی، نه یک شعار بازاریابی
شعار «یک کدبیس برای هر دو پلتفرم» جایی که نیتیو واقعاً لازم میشود را پنهان میکند. سؤال درست این نیست که فلاتر یا ریاکتنیتیو انتخاب کنیم، بلکه این است که کدام زیرسیستمها ما را مجبور به یک دریچهٔ فرار نیتیو میکنند.
۹ دقیقه مطالعه

شعار فروش کراسپلتفرم همیشه همین است: یک کدبیس بنویس، روی هر دو پلتفرم اجرا کن. برای رابط کاربری این حرف تا حد زیادی درست است. یک دکمه، یک لیست قابل اسکرول، یک فرم — اینها را فریمورک بهخوبی برایتان انتزاع میکند و تفاوتشان بین اندروید و iOS معمولاً چیزی نیست که کاربر متوجهش شود.
مشکل جایی شروع میشود که برنامه دیگر فقط پیکسل رسم نمیکند، بلکه با چیزی کار میکند که سیستمعامل مالکش است: میکروفون در حال تماس، صفحهای که قرار است روشن بماند وقتی برنامه در پسزمینه است، اعلانی که باید مثل یک تماس واقعی زنگ بزند، دوربینی که باید فریم زنده تحویل بدهد. اینها را سیستمعامل کنترل میکند، نه فریمورک، و سیاست سیستمعامل نسبت به آنها هر سال سختگیرانهتر میشود. سؤال درستی که باید هر تیمی از خودش بپرسد این نیست که «فلاتر برویم یا نیتیو»، بلکه این است: کدام زیرسیستمهای خاص محصول ما، ما را به یک دریچهٔ فرار نیتیو مجبور میکنند؟ در محصول تماس صوتی-تصویریای که خودمان ساختهایم، پاسخ این پرسش یکسان نبود برای همهٔ زیرسیستمها — و همین ناهمگونی، درس اصلی است.
چهار جایی که سیستمعامل تصمیم میگیرد، نه فریمورک
### مسیر صدا در حین تماس
یک پلاگین عمومی مدیریت جلسهٔ صوتی، بخش زیادی از راه را میرود: دستهبندی و حالت جلسهٔ صوتی روی iOS، ویژگیهای صوتی روی اندروید — اینها را میشود از سمت Dart تنظیم کرد و اغلب همین کافی است. اما وقتی زیر آن پلاگین یک کتابخانهٔ رسانهٔ بالغتر (مثل یک موتور WebRTC) نشسته که خودش هم برای مسیر صدا سیاست دارد — سوییچ بین بلندگو و گوشی، حفظ مسیر صدا بعد از یک وقفهٔ شبکهای — آنوقت دو لایهاند که هر دو فکر میکنند مالک مسیر صدا هستند. راهحل نوشتن کد نیتیو بیشتر نیست؛ فهمیدن این است که کدام لایه واقعاً باید تصمیم بگیرد و تسلیم شدن به همان یکی است. ما همین را در محصول خودمان یاد گرفتیم: تلاش برای کنترل مسیر صدا از دو جا همزمان، رفتار غیرقابلپیشبینی تولید میکند؛ راهحل، واگذاری کامل این تصمیم به لایهای بود که واقعاً موتور رسانه را میشناخت، نه به یک API عمومی سیستمعامل.
### اجرا در پسزمینه
اینجا نیتیو واقعی و بیواسطه لازم است. اندروید هر نسخه سختگیرتر از قبل دربارهٔ اینکه یک برنامهٔ پسزمینه چه کاری مجاز است بکند میشود؛ نگهداشتن یک تماس زنده وقتی صفحه قفل است دیگر با یک بستهٔ عمومیِ «زنده نگهدار» حل نمیشود، بلکه به یک سرویس پیشزمینهٔ واقعی با نوع اعلامشده (میکروفون، دوربین) نیاز دارد که باید مستقیماً به زبان بومی پلتفرم نوشته شود، چون سطح API سیستمعامل سریعتر از آن تغییر میکند که یک رَپِر کراسپلتفرم بتواند خودش را با آن هماهنگ نگه دارد. در سمت iOS، اعلام حالتهای پسزمینهٔ لازم (صدا، VoIP، اعلان از راه دور) اغلب کافی است و نیازی به کد بومی اضافه ندارد — عدم تقارن جالبی بین دو پلتفرم که ارزش دانستن دارد.
### پوش و لحظهٔ تماس ورودی
سنگینترین زیرسیستم از نظر نیتیو همین است. تبدیل یک پیام پوش به یک رابط زنگخوردن تمامصفحه — که روی صفحهٔ قفل نمایش داده شود، صفحه را روشن نگه دارد، و اکشنهایی مثل رد کردن تماس را بدون باز شدن برنامه اجرا کند — به کد بومی اختصاصی نیاز دارد، چون APIهایی که سیستمعامل برای «وقفهٔ کاربر برای یک تماس ورودی» ارائه میدهد (اینتنتهای تمامصفحه، ادغام با رابط تماس بومی سیستمعامل) سطوحی هستند که هیچ فریمورک کراسپلتفرمی خوب انتزاعشان نمیکند. یک تصمیم معتبر و آگاهانه این است که از عمیقترین سطح ادغام بومی (رابط تماس سیستمی) صرفنظر کنی و بهجایش از یک پوش معمولی استفاده کنی — این تصمیم هزینه دارد (رابط پاسخ/رد سیستمی روی آن پلتفرم را نداری) اما هزینهاش شناختهشده و قابلمدیریت است، و همین چیزی است که آن را به یک تصمیم مهندسی تبدیل میکند نه یک محدودیت پنهان.
### دوربین و ضبط رسانه
این یکی اغلب همان زیرسیستمی است که واقعاً کراسپلتفرم میماند، چون یک کتابخانهٔ رسانهٔ بومی بالغ که زیرِ پلاگین نشسته (مثل همان موتور WebRTC) از قبل ضبط دوربین بومی را با خودش حمل میکند، و باندینگ فلاتر فقط یک لایهٔ نازک روی آن است. اینجا نیازی به کد بومی دستساز نیست — چون کسی دیگر قبلاً آن مسئله را حل کرده و شما فقط دارید از حلشدنش استفاده میکنید.
چکلیست تصمیم
نکتهٔ آخر اینکه این چکلیست را نباید فقط یکبار، در جلسهٔ کلانطراحی، اجرا کنید. زیرسیستمهای حساس یک محصول در طول زمان عوض میشوند — امروز شاید زنگخوردن تماس مهمترین ریسک باشد، شش ماه بعد شاید مسیریابی صدا از طریق بلوتوث یا ضبط پسزمینه باشد که وقتی محصول را طراحی میکردید اصلاً روی نقشه نبود. پس پیش از انتخاب فریمورک، این پرسشها را برای هر زیرسیستم حساس محصول جداگانه بپرسید، نه یکبار برای کل برنامه — و آن را هر بار که یک قابلیت جدید و حساس به سیستمعامل به نقشهٔ راه اضافه میشود، دوباره اجرا کنید:
- آیا یک کتابخانهٔ بومی بالغ زیر پلاگین کراسپلتفرم نشسته که این مسئله را از قبل حل کرده؟ اگر بله، احتمالاً نیازی به دریچهٔ فرار نیست.
- آیا این زیرسیستم رفتاری است که سیاست سیستمعامل دربارهٔ آن سالبهسال سختگیرتر میشود (پسزمینه، مصرف باتری، حریم خصوصی)؟ اگر بله، فرض کن اکوسیستم پلاگین همیشه یک قدم عقبتر است.
- آیا این زیرسیستم به یک سطح رابط کاربری بومیِ سیستمعامل نیاز دارد (صفحهٔ قفل، رابط تماس سیستمی، ویجتهای بومی)؟ اگر بله، این همان جایی است که نیتیو واقعاً اجتنابناپذیر است.
- اگر تصمیم گرفتید عمیقترین سطح ادغام بومی را نخواهید، آیا هزینهٔ آن تصمیم را صریح نوشتهاید — نه بهعنوان یک محدودیت شرمآور بلکه بهعنوان یک تبادل شناختهشده؟
- آیا زیرسیستمهای حساس محصول را جداگانه ارزیابی کردهاید، یا فقط یکبار تصمیم «فلاتر یا نیتیو» را برای کل برنامه گرفتهاید؟
جواب درست تقریباً هرگز «همهچیز کراسپلتفرم» یا «همهچیز نیتیو» نیست. جواب درست نقشهای است از اینکه کدام زیرسیستمها به کدام لایه تعلق دارند — و این نقشه را نمیشود از روی اسلاید بازاریابی یک فریمورک کشید؛ باید آن را از روی رفتار واقعی سیستمعامل و رفتار واقعی محصولتان بیرون کشید.
هزینهای که این تصمیم واقعاً دارد
دریچهٔ فرار نیتیو رایگان نیست، حتی وقتی درست باشد. بهمحض اینکه یک سرویس پیشزمینهٔ اندرویدی یا یک اکتیویتی زنگخوردن بومی مینویسید، دیگر یک کدبیس ندارید که «یک نفر میفهمدش» — دو مسیر کد دارید که هرکدام باید توسط کسی نگهداری شود که زبان بومی همان پلتفرم را بلد است، و باید روی دستگاه واقعی همان پلتفرم تست شود، نه روی شبیهساز. این یعنی چرخهٔ انتشار شما دیگر «یک بیلد برای هر دو پلتفرم» نیست؛ یک تغییر در منطق زنگخوردن تماس ممکن است فقط یک پلتفرم را لمس کند اما همچنان باید تست رگرسیون کامل هر دو پلتفرم را از سر بگذراند، چون تعامل بین لایهٔ Dart و لایهٔ بومی دقیقاً همان جایی است که باگهای ظریف زندگی میکنند.
نتیجهگیری عملی این نیست که از دریچهٔ فرار پرهیز کنید — در جاهایی که سیستمعامل مالک واقعی رفتار است، اجتنابش گرانتر از پذیرفتنش تمام میشود. نتیجه این است که این هزینه را از اول در برنامهریزی تیم بگنجانید: اگر میدانید یک زیرسیستم به کد بومی نیاز دارد، برای آن مهارت بومی استخدام یا آموزش دهید، آن را در چرخهٔ QA بهعنوان یک مسیر جداگانه علامت بزنید، و آن را بهعنوان یک هزینهٔ مهندسی بشناسید نه یک استثنای موقت که «بعداً درستش میکنیم». تیمهایی که این هزینه را انکار میکنند، معمولاً همان تیمهایی هستند که یک سال بعد کشف میکنند نیمی از باگهای حساس محصولشان دقیقاً در همان مرز بین Dart و کد بومی زندگی میکند — جایی که هیچکدام از دو تیم واقعاً مسئولیتش را برعهده نگرفته بود.
قبل از تعهد به یک فریمورک، ریسکدارترین زیرسیستم را بسازید
بحث «فلاتر یا نیتیو» معمولاً خیلی زود و روی کاغذ تمام میشود — یک جدول مقایسه، چند لینک بلاگ، و یک تصمیم که ماهها بعد قرار است رویش زندگی کنید. راه بهتر این است که پیش از تعهد کامل، همان زیرسیستمی را که حدس میزنید بیشترین احتمال نیاز به دریچهٔ فرار را دارد — در محصول ما این زنگخوردن تماس ورودی روی صفحهٔ قفل بود — بهصورت یک اسپایک کوچک واقعاً بسازید. نه یک پروتوتایپ در حد اسلاید، بلکه چیزی که واقعاً روی دستگاه واقعی، با صفحهٔ قفل واقعی، تست شود. اگر آن اسپایک دو هفته طول کشید و کار کرد، تصمیمتان را با اطمینان میگیرید. اگر یک ماه طول کشید و هنوز شکننده بود، همان چیزی را کشف کردهاید که یک جدول مقایسه هرگز نشانتان نمیداد: هزینهٔ واقعی مرز بین لایهها، نه هزینهٔ نظریاش.
برای یک بنیانگذار یا مدیر فنی که این تصمیم را برای اولین بار میگیرد، وسوسهٔ بزرگ این است که شعار «یک کدبیس» را بهعنوان یک قانون کلی بپذیرد و بعد امیدوار باشد استثناها کم پیش بیایند. تجربهٔ ما این بود که استثناها کم نیستند — قابلپیشبینیاند. همان چهار زیرسیستمی که در بالا آمد، تقریباً در هر محصولی که با سختافزار و اعلانهای زمانحساس سروکار دارد دوباره ظاهر میشوند. فهرستکردن آنها زودتر، ارزانتر از کشفکردنشان در وسط توسعه است.
خواندنیهای دیگر

- Vector Search
- Retrieval
بازیابی برداری در محیط عملیاتی: کِی یک پایگاهدادهٔ برداری هزینهاش را جبران میکند
یک پایگاهدادهٔ برداری یک تصمیم معماری است، نه یک ارتقای خودکار برای جستوجو. اگر ندانید دقیقاً چه چیزی از جستوجوی متنی معمولی نمیگیرید، احتمالاً به آن نیاز ندارید.
۸ دقیقه مطالعه
- API Design
- Type Safety
قراردادهای تایپشده بین بکاند و کلاینت: چرا اسکیمای مشترک باگهای یکپارچهسازی را کم میکند
«در Postman که کار میکرد» جملهای است که تقریباً هر تیمی حداقل یکبار گفته و بعدش فهمیده مشکل واقعی جای دیگری بوده. قرارداد تایپشده بین بکاند و کلاینت این کلاس کامل از باگها ر…
۹ دقیقه مطالعه
- Trust
- News Systems
امتیازدهی به اعتبار خبر: طراحی سامانهای که ادعای بیطرفی ندارد
هر برچسب اعتبار، یک تصمیم سردبیری است که در کد نوشته شده — نه یک اندازهگیری. اگر این را از اول نپذیرید، سامانهتان بیطرفیای را وعده میدهد که هرگز نمیتواند تحویل دهد.
۹ دقیقه مطالعه
چیزی برای ساختن دارید؟
بگویید روی چه کار میکنید. صادقانه میگوییم که تیم درستی برایش هستیم یا نه.
یا برای ما ایمیل بزنید به hello@larsima.com
