زندگی، فلسفه، تکنولوژی صنعت <- اندیشه ها، احساسات

۳ مطلب در مهر ۱۳۹۶ ثبت شده است

پیچیدگی ذاتی، پیچیدگی تصادفی

در فرایند تولید نرم افزار دو نوع پیچیدگی وجود دارد. پیچیدگی ذاتی (essential complexity) به نوعی از پیچیدگی گفته می شود که در ذات برنامه است و از مساله ای که حل می شود نشعت می گیرد. مثلا اگر قرار است برنامه شما سه مساله خاص را حل کند، این سه مساله از پیچیدگی های ذاتی برنامه هستند. نوع دیگر پیچیدگی نوع تصادفی است (accidental complexity) که به پیچیدگی هایی گفته می شود که مهندسان تولید کرده و در ذات مساله قرار ندارند. مثلا پیچیدگی استفاده از زبان های سطح پایین مثل assembly، ویرایش یک فایل با format بسیار پیچیده و یا طول کشیدن فرایند تست از پیچیدگی های تصادفی هستند.

این مطلب در مقاله بسیار مشهور no silver bullet فرد بروکس مورد بررسی قرار می گیرد. بروکس مطرح می کند که پیچیدگی های ذاتی برنامه ها تا حد زیادی حل شده اند و برای پیشرفت های شدید در هر ده برابر بهتر شدن، باید چندین متد مختلف و با قدرت زیاد تولید شوند.

مساله ای که من در این جا قصد مطرح کردنش را دارم مطالب درون مقاله نیست، بلکه می خواهم در باره اهمیت این موضوع صحبت کنم که ما به عنوان برنامه نویس بتوانیم پیچیدگی های تصادفی را تشخیص دهیم و در مواقع لازم ابزار های مناسب برای رفع این پیچیدگی ها را تولید کنیم. اگر پیچیدگی های تصادفی جلوی پیشرفت کار را بگیرند، ما در رقابت از کسانی که این پیچیدگی ها را برای خود حل کرده اند عقب خواهیم ماند و همچنین تولید نرم افزار برایمان کند خواهد شد. استفاده از  ابزار های نوشته شده خاص برای پروژه، نوشتن تست، Continuous integration ، استفاده از زبان های مناسب و framework ها مناسب برای مساله ای که آن را حل می کنیم و بسیاری موارد دیگر از این دسته ملاحظات هستند.

برنامه نویس های مختلفی را دیدم که از ابزار های اشتباه و روش هایی برای انجام کاری استفاده می کنند و آن وقت به علت اشتباه خود مساله را پیچیده می یابند. برنامه نویسی دیدم که چون با زبان جاوا کد می زد و کامپایل کردن یک پکیج برایش سخت شده بود. به این دلیل تصور می کرد که برنامه نویس بسیار خوبی است و کارهای پیچیده ای انجام داده است. همچنین برنامه نویسی دیگر را در جایی دیدم که به جای انجام یک کار تکراری به شکل خودکار با یک ابزار آن را دستی انجام می داد و ایراد می گرفت که او نباید این کار را انجام دهد. به خاطر می آورم که در گذشته خودم کارهای مختلفی را از روش سخت انجام داده ام و به همین دلیل پروسه انجام کار برایم سخت به نظر آمده بوده است. مثلا یک بار به جای visualize کردن یک سری داده برای درک بهتر با نوشتن آن در فایل به شکل آرایه ای دو بعدی از اعداد سعی می کردم آن را debug کنم که یافتن مشکلی ساده را برایم سخت کرد. و یا همین چند روز پیش به علت انتخاب کردن config اشتباه در visual studio هنگام کار با پروژه ، حدود یک ساعت تمام log های visual studio را برای یافتن اشکالی که اصلا وجود نداشت طلف کردم. اگر کدی نوشته بودم که پروژه ام انتخاب config اشتباه را به من گزارش دهد، امکان وجود این خطا اصلا وجود نداشت.

به همین دلیل یادگیری کار با سیستم های build محیطی که با آن کار می کنید و editor های مربوطه مثل Unity و visual studio برای هر برنامه نویس جدی لازم است. من خود چندان با ساخت extension برای visual studio آشنا نیستم. اندکی اطلاعات دارم ولی هرگز چیز جدی با آن نساخته ام. قصد دارم به زودی ضعف خود را در این زمینه جبران کنم. خوشبختانه در Unity تجربه نسبتا زیادی در ساخت اسکریپت های editor دارم و بارها برای کارهای مختلف در پروژه های مختلف ابزار های، ویرایش داده، ساخت build و ... ساخته ام. این کار به کم شدن اشتباهات و از بین بردن پیچیدگی های تصادفی کمک می کند.

۱۳ مهر ۹۶ ، ۰۱:۴۳ ۰ نظر موافقین ۰ مخالفین ۰
اشکان سعیدی مزده

توابع محض (pure functions)

بیشتر وقت برنامه نویسان به debug کردن برنامه ها می گذرد. متوسط تعداد خط کد نوشته شده توسط بیشتر برنامه نویسان حدود 20 خط در روز است. دلیل این امر صرف شدن بیشتر وقت برای debugging می باشد. دلایل مختلفی باعث به وجود آمدن ایراد در برنامه می شود و روش های مختلفی برای رفع کردن این ایراد ها وجود دارد.

یکی از مهمترین دلایل ایراد در برنامه ها که در این پست راجع به آن صحبت می کنم وضعیت جاری نادرست در برنامه است. یعنی مثلا یک تابع فرض می کند که مقدار متغیری عمومی یا متغیری در یک نمونه از یک کلاس (instance)، باید عدد خاصی باشد ولی در شرایط فعلی به دلیلی مقدار اشتباهی در این متغیر قرار دارد. برای جلوگیری از این مشکل باید ابتدا مفهوم side-effect را بدانید. side-effect به معنای آن است که گاهی اوقات یک تابع علاوه بر گرفتن ورودی و دادن خروجی اثری بر روی متغیر های دیگر برنامه می گذارد. مثلا خواندن ورودی یک عمل دارای side-effect می باشد. اگر تابعی داشته باشید که برای شما زمان سیستم را بخواند و برگرداند و به علاوه مقدار متغیر latestReadTime را به روز کند که در توابع مختلف قابل دسترسی است، این تابع دارای side-effect است. اگر بخشی دیگر از برنامه با این فرض اجرا شود که مقدار درون lastReadTime حد اکثر 10 ثانیه کوچکتر از زمان حقیقی فعلی است و این زمان بیش از 10 ثانیه قبل به روز شده باشد، برنامه شما دچار مشکل خواهد شد.

همچنین اگر برای انجام کاری از متغیر های عمومی استفاده کنیم مشکل بالا رخ خواهد داد. مثلا همان lastReadTime یک متغیر عمومی است. اگر به جای استفاده از آن هر تابعی که به زمان نیاز دارد، مقدار زمان سیستم را بخواند، دیگر این مشکل پیش نخواهد آمد و دیگر هم نیازی نیست تابع خواندن زمان side-effect داشته باشد. به طور کل به توابعی که مقدار خروجیشان فقط و فقط از روی داده های ورودی مشخص می شود توابع محض و یا pure گفته می شود. این توابع نه متغیری عمومی در برنامه یا کلاس را عوض می کنند و نه با استفاده از متغیر های عمومی و کلاس خروجی خود را مشخص می کنند. مثلا تابع توان یا سینوس کاملا pure هستند. یافتن ایراد در توابع محض بسیار ساده است زیرا مقدار ورودی بررسی می شود و روش محاسبه خروجی نیز مشخص است. اگر تابع از state کلاس یا عمومی استفاده کند آن وقت باید اول متوجه شوید اثر متغیر عمومی در تابع چیست و مقدار متغیر عمومی به چه دلیل مقدار خاصی را گرفته است. یافتن رشته توابعی که باعث پیش آمدن حالت فعلی شده اند همیشه کار ساده ای نیست. این کار به خصوص در برنامه های شبیه سازی و بازی ها و هر برنامه concurrent دیگری بسیار مشکل است.

خوب است سعی کنیم از توابع محض برای بیشتر محاسباتمان استفاده کنیم، آبجکت هایی که به شکل مرجع (pass by reference) به ما داده می شوند را تا حد ممکن تغییر ندهیم و کاری کنیم هر تابع به خودی خود با ورودی هایش کار خود را انجام دهد. البته نوشتن کل برنامه به شکل تابع های محض نیاز به تکنیک های زبان های functional دارد که سرعت اجرا را پایین می آورند و سختی های خاص خود را نیز دارند ولی خیلی خوب است سعی کنیم تا جای ممکن از این توابع استفاده کنیم. من از زمانی که به این موضوع توجه کرده ام بسیار ایراد های کمتری به خاطر state اشتباه در برنامه هایم داشته ام. در زیر مثالی را می آورم که شاید مساله را روشنتر کند.

مثلا اگر می خواهیم آسیب ناشی از حمله به یک دشمن را حاسبه کنیم می توانیم تابع را درون دشمن نوشته و مقدار آخرین تیر خورده به دشمن و میزان healthش را در کلاس دشمن ذخیره کنیم و این تابع با استفاده از این متغیر ها میزان آسیب را حساب کند. در این شرایط شما نمی توایند دقیقا بفهمید چرا مقدار متغیر تیر خورده به دشمن مقدار خاصی دارد و چرا میزان health به حد کافی کم نشده اما اگر این مقدار ها به عنوان ورودی به تابع مربوط به محاسبه آسیب داده شوند آن گاه این تابع بدون توجه به وضعیت فعلی هر آبجکت دیگری می تواند مقدارش را حساب کند و درستی آن را بدون در نظر گرفتن وضعیت کل برنامه می توان امتحان کرد. بدتر از این مثال های بسیار پیچیده ای هستند که مقدار زیادی از state در متغیر های مختلف عمومی قرار داشته باشند که توابع آن ها را می خوانند و با آن ها کار می کنند. این کار باعث مشکل شدن debug برنامه می شود. باید سعی کنیم کلاس هایی کوچک با تعداد متغیر کلاس member variable بسیار کم داشته باشیم و همه توابع مقدارهای لازمشان را از ورودی بگیرند تا پس از یافتن جریان داده ها در برنامه (data flow) ، (که برای درک برنامه الزامی است) بتوانیم بفهمیم کجای سیستم آن طور که باید کار نمی کند. برای جلوگیری از مشکلات مربوط به performance بهتر است آبجکت های بزرگ را به شکل reference به توابع پاس دهیم ولی مقدار آن ها را تغییر ندهیم. در بعضی زبان ها مثل سی پلاس پلاس به علت امکان قرار دادن کلمه کلیدی const در آرگومان های تابع، می توانیم تغییرات را در زمان کامپایل تشخیص دهیم ولی در برخی زبان های دیگر یا باید دیسیپلین داشته باشیم و یا از ساختار های کتابخانه ها استفاده کنیم (مثل Immutable در سی شارپ).

در پست های آینده در باره روش های دیگر نوشتن کد کم خطا و مدیریت state بیشتر خواهم گفت.

۰۶ مهر ۹۶ ، ۰۰:۱۵ ۰ نظر موافقین ۰ مخالفین ۰
اشکان سعیدی مزده

روان شناسی برنامه نویسی

تا به حال شده است با فکر کردن به کدتی که قبلا نوشته اید و یا پروسه ی فکرتان برای حل یک مساله یا برطرف کردن یک باگ فکر کرده باشید؟ آیا تا به حال به نوع ارتباطتان با برنامه نویسان دیگر هنگام خواندن و نوشتن کد و کامنت ها فکر کرده اید؟ آیا حس کرده اید که در قالب برخی زبان ها بهتر فکر می کنید و از بعضی ابزار ها استفاده بیشتری می کنید؟ همه این موضوع ها به روان شناسی برنامه نویسی مربوط می شود.

قصد داشتم در کنفرانس TGC یکی از دو مطلب ساخت سیستم های scalable و توضیع شده و یا روان شناسی برنامه نویسی را ارایه دهم که داوران موضوع اول را پذیرفتند و موضوع دوم مورد پذیرش قرار نگرفت. دوست دارم در این پست و شاید چند پست دیگر راجع به این موضوع که از موضوعات مورد علاقه من و از نظرم از موضوعات بسیار مفید برای برنامه نویسی خوب است، کمی صحبت کنم.

به تازگی با یکی از دوستانم بحث های فنی مختلفی راجع به انواع روش برنامه نویسی انجام می دهیم که کمک شایانی در زمینه فکر به این موضوعات به من کرده است. ابتدا با روان شناسی برنامه نویسی به شکل رسمی هنگام خواندن کتاب Code Complete آشنا شدم. این کتاب فصلی در باره اثر شخصیت بر برنامه نویسی داشت که توجه من را به این موضوع جلب کرد و کتاب بسیار عالی Psychology of computer programming را نیز به عنوان مرجعی جذاب معرفی کرد. همچنین کتاب The mythical man month هم از کتاب های خیلی خوب در این زمینه است که البته من این کتاب آخر را نخوانده ام و در لیست خواندنی هایم قرار دارد.

روان شناسی برنامه نویسی تلاش می کند تا کمک کند برنامه ها با هزینه کمتر، به موقع و با خطای کمتر تولید شوند، کارا باشند و قابل نگهداری باشند و در مقابل تغییر مقاومت نشان ندهند. برنامه نویسی یک کار ذهنی است و در واقع کمتر شغلی پیدا می شود که از علوم کامپیوتر و مهندسی نرم افزار کمتر فیزیکی باشد. برنامه نویسی فرایندی است که شامل خواندن و نوشتن کد، تفکر ، حل مساله، ارتباط برقرار کردن با دیگران، یادگیری، فهم و درک و ... است. برای بهتر برنامه نوشتن می توانیم برنامه نویسی و فهمیدن برنامه را از نظر ذهنی و روانی بررسی کنیم. این کار از دهه هفتاد میلادی آغاز شده است و در واقع psychology of computer programming و کتاب های دیگر به خوبی آن را تشریح می کنند. کتاب مهم دیگری به نام peopleware هم هست که بسیار زیبا این مطلب را مورد بررسی قرار می دهد. در زیر به برخی موارد جذاب راجع به روان شناسی برنامه نویسی اشاره می کنم و امیدوارم دوستان علاقه مند کتاب های مربوطه را بخوانند و از آن ها لذت ببرند.

باید بدون غرور برنامه نوشت egoless programming : این بدان معنا است که وقتی برنامه می نویسیم نباید فرض کنیم ایراد در کد ما یا پرسیدن سوال از دیگران شخصیتمان را پایین می آورد. باید فروتن باشیم و اگر چیزی را بهتر از بقیه می دانستیم آن را به دیگران آموزش داده و بدون تحقیر کردنشان به آن ها کمک کنیم. باید وقتی چیزی را نمی دانیم بی خود نظر های الکی ندهیم و رویش پا فشاری نکنیم. باید خودمان را موقع کد زدن فراموش کنیم و فقط به پیشرفت کارمان و پروژه که نهایتا به پیشرفت شخصیمان هم منتهی می شود فکر کنیم. باید عاشق کدمان و کارمان باشیم ولی خودمان را با کارمان و اشکالات و خبی هایش دقیقا یکی فرض نکنیم. حد اقل موقع تعامل با دیگران راجع به برنامه این کار را نکنیم :)

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

کد رسانه worm است: write once read many times هنگام نوشتن کد همیشه تصور کنید که کسی که آن را می خواند چه حسی خواهد داشت، آیا کد قابل فهم خواهد بود. کد بسیار بیش از آن چه نوشته می شود خوانده می شود، توسط نویسنده ای به جز نفر اصلی تغییر داده می شود. یک راه جالب این است که برنامه نویس بعدی یک بیمار روانی قاتل است که آدرس خانه شما را دارد. برنامه را طوری بنویسید که ناراحتش نکنید. خودتان فردا برای خودتان همان روانی مریض خواهید بود. بعد از یک ماه کد قدیمیتان را بخوانید تا ببینید طقریبا یک کد جدید محسوب می شود. ذهن سریع این چیز ها را فراموش می کند. ذهن ما در زمانی که درون یک مساله عمیق است همه جزیاتش را در حد توان در خود نگه می دارد اما این بخش از ذهن کند و کم حجم است و هر لحظه کارتان با مساله تمام شود پاک خواهد شد.

حواستان به خودتان باشد: شاید مهمترین حرفی که در این پست می زنم این باشد. به نظر من این عامل از مهمترین عامل های برنامه نویس بهتری شدن یا نشدن یک فرد است. ما باید به عنوان برنامه نویس سعی کنیم حواسمان باشد که داریم چه کاری را به چه گونه ای انجام می دهیم. مثلا چرا یک باگ را سریع فیکس کردیم و یا چرا نتوانستیم چیزی را سریع یاد بگیریم. فلان باگ را چه طور تولید کردم؟ اگر چه کار می کردم تولید نمی شد؟ باید چه کار می کردم که سریعتر بفهمم چه طور حلش کنم؟ این کار که به آن خود نگری یا Introspection گفته می شود از تکنیک های بسیار قوی برای برنامه نویس بهتری شدن است و توسط روان شناسان هم مورد استفاده قرار می گیرد. البته با introspection نمی توان آزمایش معتبر طراحی کرد ولی گاهی به ما سر نخ های بسیار مفیدی می دهد. بسیاری از روان شناسان موفق از این تکنیک سود زیادی برده اند زیرا ما بیش از آن که فکر می کنیم شبیه هم هستیم. با این کار برنامه نویسان دیگر و دلیل روش کد زدنشان، مدل برخوردشان با انتقاد و ... را نیز بهتر می فهمیم.

مساله آخر که بسیار مهم است صداقت است. یک برنامه نویس اگر صادق نباشد باید او را دور بیندازید. با او کار نکنید :) از بین ببریدش!! :)) دور از شوخی کار با کسی که صداقت ندارد. برای باگ هایش بهانه پیدا می کند. دلایل دروغین برای خوبی الگوریتمش پیدا می کند و الکی وانمود می کند چیزی را بلد است که بلد نیست، نظرات احمقانه می دهد و اگر نظراتش را قبول نکنید سعی می کند شخصیتتان را در تیم خراب کند کار بسیار سخت است. Intelectual honesty مساله بسیار بسیار مهمی است که در باره آن پستی مخصوص خواهم نوشت.

دوست دارم نظرات و تجربیات دیگران را در زمینه برخورد روان شناسانه با برنامه نویسی و نگاه به آن به عنوان یک علم انسانی را بدانم.

۰۱ مهر ۹۶ ، ۲۳:۲۴ ۱ نظر موافقین ۰ مخالفین ۰
اشکان سعیدی مزده