تا الان پروژه های بزرگی رو دیدم و روشون کار کردم که توی ایران چندین میلیون کاربر دارن، آوردن اسمشون ممکن نیست ولی از من قبول کنین که واقعا اینطور بوده، یکی از بزرگترین مشکلاتی که با اون مواجه بودم، عدم رعایت Type Safety بوده. من خودمم از این قضیه جدا نبودم و خیلی وقتا این اشتباهو میکردم تا زمانیکه مقاله فرزاد (تیم لیدمون توی شرکت تپسی) رو خوندم و خودمو ملزم به رعایت این ویژگی کردم که باعث شد حس بهتری نسبت به خودم داشته باشم.
وقتی به عنوان یه برنامه نویس تازه شروع به کارمون میکنیم، به این چیزا توجهی نمیکنیم و فقط سعی میکنیم برنامه دقیقا کاری که ما مد نظرمون هست رو انجام بده و اینکه چطوری این کارو انجام میده زیاد توجهی بش نمیشه. این مشکل زمانی بزرگتر میشه که یه نفر به خودش اینقدر اعتماد پیدا میکنه که میاد و توی یه شرکت به هر نحوی که شده کاری رو میگیره و استخدام میشه. برای مثال یه شرکت تازه میخواد یه اپلیکیشن iOS رو به سیستمش اضافه کنه و معمولا اولین کار چیه؟ با هزینه کمتر اپلیکیشنمون رو پیاده سازی کنیم. پس به یکی از برنامه نویس های قدیمیشون که به نظر خودشون کارش خوبه اینو میگن و اونم برای اینکه یه کار جدید و یه زبان جدید رو یاد بگیره، سرشو بالا میگیره و میگه چرا یه نفر استخدام کنین؟ بیمه، حقوق، عیدی، دردسر و ... خودم براتون این پروژه رو مینویسم و با توجه به دانش قبلی که داره به سرعت شروع به کار میکنه. یه کتاب در اون رابطه میگیره و میخونه و برنامه نویسی اون اپلیکیشن رو شروع میکنه. هر دردسری که داره یه مسئله ای که باش مواجه میشه رو به سرعت توی StackOverflow جستجو میکنه و در نهایت یه اپلیکیشن که داره کار میکنه رو میاره بالا و مدیر هم خیلی خوشحال میشه که به به افرین، چه بی دردسر و چه خوب.
این میشه که یکی از بزرگترین اپلیکیشن های موجود در بازار که کاربرانش (به اجبار) برای استفاده از خدمات اون سرویس دهنده دارن ازش استفاده میکنن، بدترین، فجیح ترین و بی ارزشترین کدی هستش که تو عمرم باش کار کردم. شاید اگر این همه کاربر نداشت، اینقدر ناراحت نبودم ولی الان واقعا وقتی بش فکر میکنم که این همه اعتبار و پول بابت چیزی که واقعا ارزش کدش به اندازه ۱ میلیون تومن هم نیستش هزینه شده، قلبم درد میگیره.
یکی از مشکلاتی که همه برنامه نویس های تازه کار تا متوسط ( یا حرفه ای هایی که تازه با زبان جدیدی مثل سویفت یا کاتلین یا زبان های کامپایلری) اشنا شدن باش مواجه هستند اینه که با مفهوم Type Safety غریبگی میکنند. بذارین مثالی بزنم از این مشکل:
// example 1 func setImage(imageName: String) { img_logo.image = UIImage(named: imageName) } // example 2 let username: String = Result["data"]["username"]
خیلی وقتا با همچین کدهایی مواجه شدیم ولی چه مشکلی با این کد ها هستش؟ در نگاه اول هیچی ولی در نگاه افراد با تجربه، این کدها ما رو به سمت مشکلات نا خواسته میبرند. چه طور؟ مثلا در مثال اول، اگر اسم فایل اشتباهی به این تابع ارسال بشه، هیچ عکس پیدا نمیشه ولی ایا این مشکل از تابع ما هستش؟ یعنی شما وقتی میخواید ایراد این کد رو پیدا کنین، دو مورد رو باید چک کنین، ایا این تابع اصلا کار کرده؟ دوم اینکه ایا عکسه پیدا شده یا نه؟ خب چرا این کارو بکنیم؟ چرا Type Safe نباشیم؟ چرا تابعمون بجای اینکه اسم عکس رو بگیره، خوده عکس رو نگیره؟ مثلا اینجوری
func setImage(image: UIImage) { img_logo.image = image }
اینجوری وقتی مشکلی باشه مطمئنیم که فقط از همونجایی هستش که داره عکس رو درست میکنه و مطمئنیم که تابع ما درست کار میکنه. حالا برای مثال دوم چی؟ باز هم همین ایراد وجود داره، کدهایی مثل بالا سر تا سر برنامه پخش میشن و میشه تصور کرد وقتی یه جایی مشکل پیش میاد ما چقدر باید بگردیم تا ایراد رو پیدا کنیم. اگر یه API اسم فیلدهاش عوض شه چقدر باید تلاش کنیم تا همه جا ایراد های مربوط به اون فیلد ها رو پیدا کنیم و تصحیح کنیم
خب حالا میرسیم به این سوال که
اگر بخوام به صورت خلاصه بگم، Type Safety یعنی کمک گرفتن از کامپایلر برای جلوگیری از بوجود اومد خطاها، هرجا که میتونیم. سخت بود؟ معنی این حرفم اینه که تا میتونیم اجازه بدیم کامپایلر بدونه چی از چه نوعی هستش و چطور میتونه اونو مدیریت کنه، از کامپایلر استفاده کنیم برای به وجود اوردن خطا ها. جالبه؟ چرا باید خطا داشته باشیم؟ به این دلیل که هرچی بتونیم خطاها رو در زمان کامپایل داشته باشیم برنامه پایدار تر و استیبلتری در زمان اجرا داریم. حالا این خطا به وجود اوردن یعنی چی؟ یعنی نذاریم کسی Type اشتباه بمون بده و همون موقع بش خطا بدیم ( کامپایلر بش خطا بده) و بگیم که این تایپ چیزی نیست که ما انتظارشو داریم. برای مثال
// wrong let intergerNumber: Int = Int(apiData["numberOfRows"])! // correct struct APIData: Codable { let numberOfRows: Int } let intergerNumber: Int = apiData.numberOfRows
فرق کد های بالا خیلی مشخصه و مطمئنم هممون از روش اول استفاده کردیم. ولی چرا روش دوم مطمئن تر هستش (Type Safe)؟ وقتی این مورد توی کل اپلیکیشن پخش میشه، ما با داده هایی سرو کار پیدا میکنیم که از مقدار درونشون مطمئن نیستیم و اگه برنامه نویس خوبی باشیم باید همیشه چک کنیم ایا داده مورد نظر امن هستش یا نه؟(یا مثه برنامه نویس های بد، همونجوری استفاده کنیم و به امید خدا، اطمینان از دادهای ارسالی رو بذاریم به عهده بقیه) مثلا تو مثال بالا اگر ما ۱۰ جا از numberOfRows استفاده کرده باشیم و سرور اشتباها به جای عدد ۱۰، عدد ۱۰.۰۰ رو ارسال کنه، برنامه ما با خطا مواجه میشه، چرا؟ چون ما بجای اینکه چک کنیم که داده ای که داریم میگیریم Int باشه، String میگیریم مستقیما اونو به زور به تبدیل Int تبدیل میکنیم. حالا فرض کنین این توی صفحه اول برناممون هستش، ما اینو فیکس میکنیم و میریم میبینیم بعد ۱ هفته دوباره همین ارور به وجود اومده ولی ایندفعه توی صفحات درونی (چون تا الان کسی سراغشون نرفته بود و حالا که رفته، با خطا مواجه شده)
روش دوم توی مثال قبل به ما کمک میکنه تا همه برنامه از یک Type استفاده کنند و همچنین کسایی که میخوان با این ابجکت کار کنن دقیقا میدونن چی باید بدن و چی ازش بخوان و کامپایلر کمک میکنه که اگر دیتای صحیحی به این ابجکت نرسه، اخطار بده و اپلیکیشن اصلا همون موقع کامپایل تایم با خطا روبه رو میشه.
پس بیاین و برای همیشه از هر Type دقیقا برای خودش استفاده کنیم و از استفاده String بجای Int یا Any بجای String و ... به شدت پرهیز کنیم تا هم کامپایلر از ما راضی باشه و هم خودمون از خودمون
حالا که میدونیم Type Safety چی هستش، میتوینم یه قدم جلوتر بریم. (این بخش چیزیه که فرزاد بش اشاره کرده و منم خیلی باش موافق هستم.)
تا حالا شده از String به جای ID استفاده کنین؟ مطمئنم شده، تا حالا شده از String بجای نام و نام خانوادگی، شماره تلفن و ادرس استفاده کنین؟ مطمئنم شده. به جمله قبل من توجه کردین؟ از String بجای نام----نام خانوادگی------تلفن------موبایل------آدرس-----آدرس عکس------آدرس وب سایت------- .... استفاده کردیم ولی ایا String تمام چیزی که ما باید بدونیم رو به ما داره میگه؟ یعنی کسی با دیدن این String دقیقا میدونه که باید چیکار کنه مثلا
struct User { let ID: String let name: String let family: String let tel: String let mobile: String let address: String let website: String }
توجه به این مورد چیزی هستش که ما رو یه قدم جلو تر میبره. با خودمون فکر کردیم که شاید باید Type های با معنی تری می داشتیم؟ مثلا کی گفته تلفن یه String خالیه؟ اگر بخوایم برای این تلفن Validation بذاریم، باید چیکار کنیم؟ فرض کنیم که ما از فیلد Tel توی چندتا ابجکت دیگه هم داریم استفاده میکنیم، باید برای همشون یکی یکی Validation بنویسیم و کپی پیست کنیم؟ اصلا وقتی به بقیه میگیم تلفن یه String هستش یعنی اینکه تو هرچی میخوای میتونی به من بدی مثلا "dsfsfsfsfsdfsd" این هم یه رشته هستش که میشه درون همه اون فیلدهای ابجکت بالا قرار داد ولی چرا این مشکل به وجود میاد؟ ما میتونیم پا رو یه قدم فرارتر بذاریم. میتونیم یه قدم Type Safe تر بشیم و میتونیم یه قدم حرفه ای تر رفتار کنیم. مثلا برای همین تلفن میتونیم اینجوری عمل کنیم:
struct Phone { let localCode: String let number: String } struct Address { let city: String let province: String let address: String } struct User { .... let phone: Phone let address: Address .... }
این رفتار به ما کمک میکنه تا اطلاعات بیشتری درباره هر Type داشته باشیم و همچنین یه Type مشترک سر تا سر اپلیکیشنمون داشته باشیم . تمام Validation ها رو یه جا بنویسیم و تمام تغییرات رو از یه جا اعمال کنیم.
وقتی میخوایم احساس خوبی نسبت به خودمون پیدا کنیم، وقتی میخوایم از یه برنامه نویس تازه کار به یه برنامه نویس با تجربه تبدیل شیم و وقتی میخوایم کدمون بهتر رفتار کنه، میتونیم قدم های صحیحی به سمت جلو برداریم که میتونه تاپیک های زیادی رو شامل شه که قطعا یکی از اون ها اهمیت دادن به Type Safety و رعایت این موارد هستش. پس سعی کنیم از اینجا به بعد مدل هامونو درست تعریف کنیم و دقیق تر استفاده کنیم.