طراحی های بد در ساختار زبان PHP

من اظهار میدارم که کیفیات زیر برای پربار ساختن یک زبان مهم هستند، و PHP به شکل بی پروایی از آنها تخلف میکند. اگر شما نمیتوانید با من موافق باشید که این چیزها مهم هستند، خب پس من نمیتوانم تصور کنم که ما هیچوقت روی چیز زیادی توافق داشته باشیم:

- یک زبان باید قابل پیشبینی باشد. آن وسیله ای برای بیان ایده های انسان و اجرا شدن آن ها توسط کامپیوتر است، بنابراین حیاتی است که درک انسان از یک برنامه عملا صحیح باشد.

- یک زبان باید سازگاری داشته باشد. چیزهای مشابه باید مشابه بنظر برسند، چیزهای متفاوت باید متفاوت بنظر برسند. دانستن بخشی از زبان باید در یادگیری و فهم بقیهء آن کمک کند.

- یک زبان باید موجز باشد. زبانهای جدید برای کاهش جزییات جانبی در زبانهای قدیمی بوجود آمده اند (ما همه میتوانستیم کد اسمبلی بنویسیم). یک زبان بنابراین باید تلاش کند از معرفی جزییات جانبی جدید توسط خودش اجتناب کند.

- یک زبان باید قابل اتکا باشد. زبانها ابزارهایی برای حل کردن مسائل هستند؛ آنها باید مسائل جدیدی را که خودشان ایجاد میکنند به حداقل برسانند. هر چیز غیرمنتظره، یک سردرگمی بزرگ است.

- یک زبان باید قابل دیباگ باشد. وقتی چیزی اشتباه پیش میرود، برنامه نویس باید آن را درست کند، و ما به تمام کمکی که میتوانیم بگیریم نیاز داریم.

من بسیار در جریان استدلالهای طرفداران PHP بوده ام. من یک عالمه استدلالهای مخالف عمومی میشنوم که برای آن طراحی شده اند تا فورا به مباحثه خاتمه دهند:

- به من نگویید که «برنامه نویس خوب میتواند با هر زبانی برنامهء خوب بنویسید»، یا «برنامه نویس بد در هر زبانی برنامهء بد مینویسید». آن هیچ معنایی ندارد. یک نجار خوب میتواند یک میخ را با یک سنگ یا چکش بکوبد، اما شما چه تعدادی نجار دیده اید که چیزها را با سنگ میکوبند؟ بخشی از آنچه یک توسعه دهندهء خوب را شکل میدهد، توانایی انتخاب ابزارهاییست که بهتر کار میکنند.

- به من نگوییید «آن وظیفهء برنامه نویس است که هزار استثنای عجیب و غریب و رفتارهای غیرمنتظره را بخاطر بسپارد». بله، این در هر سیستمی لازم است، چون کامپیوترها ابله هستند. اما آن به معنای این نیست که هیچ حداکثری برای میزان بلاهت در یک سیستم وجود ندارد. PHP چیزی نیست جز استثناء ها، و اینکه کشتی گرفتن با زبان تلاش بیشتری از اینکه عملا برنامهء خودتان را بنویسید صرف کند، چیز خوبی نیست. ابزارهای من نباید برای من اینقدر کار اضافه تولید کنند.

- به من نگویید «فیسبوک و ویکیپدیا با PHP درست شده اند». من مطلع هستم! آنها میتوانستند همچنین با زبان Brainfuck نوشته شوند، اما تا زمانیکه افراد بقدر کافی باهوشی وجود دارند که میتوانند چیزها را ردیف کنند، آنها میتوانند بر مشکلات پلتفرم غلبه یابند.

فلسفه:

PHP ابتدا صراحتا برای غیربرنامه نویسان (و غیربرنامه ها) طراحی شد. آن هنوز نتوانسته است از ریشه های خودش فرار کند. یک نقل قول متخب از مستندات رسمی PHP2 بعنوان سند این ادعا:
«… بخصوص برای چیزی مثل PHP که بیشتر اسکریپت ها نسبتا ساده خواهند بود و در بیشتر موارد توسط غیربرنامه نویسانی که زبانی با یک سینتاکس پایه که یادگیری آن بیش از حد دشوار و طولانی نباشد میخواهند»

PHP برای کار کردن در هر شرایطی به هر قیمتی ساخته شده است. وقتی PHP با این انتخاب روبرو میشود که عملی بی معنی انجام دهد یا با یک خطا برنامه را متوقف کند، آن کار بی معنی را انجام میدهد. از نظر PHP، هرچیزی بهتر از هیچ چیز است!

Weak typing در PHP (تبدیل خودکار بین رشته ها/اعداد، و غیره) آنقدر پیچیده است که هرچقدر تلاشهای کوچکی از برنامه نویس را کاهش میدهند، ارزشش را ندارد.

PHP یک اجتماع از آماتورهاست. تعداد خیلی کمی از افرادی که آن را طراحی میکنند، روی آن کار میکنند، یا در آن برنامه نویسی میکنند، بنظر میرسد که میدانند دارند چکار میکنند (اوه خوانندهء عزیز، شما البته یک استثنای کمیاب هستید!). آنهاییکه در این زمینه رشد میکنند، تمایل دارند تا به طرف پلتفرم های دیگر بروند، و بنابراین متوسط صلاحیت کل این مجموعه را کاهش میدهند. این بزرگترین مشکل با PHP است: کوری عصاکش کور دیگر!

عملگر == در PHP بلااستفاده است:

این دو مقایسه هر دو درست ارزیابی میشوند:

"foo" == TRUE

"foo" == 0

اما مسلما حتی در PHP، صفر با true برابر نیست.

این عبارت درست ارزیابی میشود:

123 == "123foo"

اما این عبارت غلط ارزیابی میشود:

"123" == "123foo"

این مقایسه ها درست ارزیابی میشوند:

"6" == " 6"

"4.2" == "4.20"

"133" == "0133"

اما این نه:

133 == 0133

این ها درست ارزیابی میشوند:

"0x10" == "16"

"1e3" == "1000"

هر دوی اینها درست ارزیابی میشوند:

NULL < -1

NULL == 0

===========================================

منبع: بخشهایی از http://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/

البته من با تمام نظرات و استدلال های این طرف موافق نیستم. بخاطر همین فقط موارد و بخشهایی رو که بنظرم واضحا با اطمینان زیادی درست هستن انتخاب کردم.
بعضی مواردی که طرف بعنوان ضعف های PHP بیان کرده بنظر من میتونن حتی مزیت و نقطهء قوت باشن.

—————————————————————-

نقل قول: «به نظرم شما با فلسفه اسکریپتی مشکل دارید. php بر اساس فلسفه اسکریپت ساخته شده تا بر نوع داده حساس نباشه.»

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

من خودم استفاده از عملگر == رو تقریبا بطور کامل کنار گذاشتم بجاش از === استفاده میکنم.
حتی پروژهء سیستم رجیستر و لاگین خودم رو که قبلا نوشته بودم برداشتم توی اون همه کد تقریبا تمام == ها رو به === تبدیل کردم، مگر یک مواردی که تغییرش به این سادگی نبود و کدهای دیگری هم باید بررسی و تغییر پیدا میکردن و مشکل جدی هم توش ندیدم.
ضمنا دقیقا یک مورد SQL Injection هم در برنامم شناسایی کردم که بخاطر همین عملگر بوجود آمده بود.
اصولا شما نمیتونید توی برنامه های جدی که خیلی ساده و کم حجم نباشن بصورت پیشفرض از عملگر == برای مقایسه هاتون استفاده کنید! چرا؟ چون استفاده از عملگر == میتونه به سادگی منجر به باگها و حفره های امنیتی در برنامتون بشه که پیشبینی و کنترل اینها از قبل تقریبا غیرممکنه! بخاطر اینکه رفتار عملگر == بیش از حد متنوع و پیچیده است و برنامه نویس باید برای هر مورد در ذهنش ده ها حالت رو بررسی و دقت بکنه در این باب، و این عمل در هنگام برنامه نویس سخت و زمانبر و کاملا مستعد خطاست. صرفه با اینه که از === بجای == استفاده کنیم تا نیازی به این همه دقت و صرف انرژی و متفرق شدن تمرکز برنامه نویس نباشه و در باب امنیت و باگهای برنامه ریسک نکنیم.
همونطور که این مقاله گفته، عملگر == عملا بلااستفاده شده!
انعطاف زبان برنامه نویسی و هوشمندبازی تا یه جایی یه حدی که عاقلانه باشه سودش در کل از ضررش بیشتر باشه، نه اینکه دیگه به اینجا برسه که مثل بمب هر لحظه امکان ترکوندن برنامه رو داشته باشه!!
گفتم زبانهای اسکریپتی دیگر زیاد هستن، ولی شما در هیچکدام این حد از سرخود عمل کردن و افرط رو مشاهده نمیکنید. خواستید میتونیم این نمونه ها رو در زبانهای اسکریپتی دیگری که در دسترس داریم عملا بررسی و تست کنیم تا ببینید که رفتارشون در کل معقول تر و امن تر از PHP است.

ضمنا بنده بعنوان یک متخصص در امور امنیت، دارم این نظر رو بصورت رسمی ابراز میکنم!
اگر به امنیت و کاهش باگهای برنامه هاتون علاقمند هستید، از عملگر == (و همچنین ‎!=‎) تا میتونید دیگه استفاده نکنید!

—————————————————————-

دوتا شرط که با هم متناقض هستن هر دو true ارزیابی میشن، بنظر شما معقوله؟
مثلا: ‎”foo” == TRUE و ‎”foo” == 0. چطور چیزی میتونه هم برابر true باشه و هم برابر صفر؟!
یا این: NULL < -1 و NULL == 0. چطور صفر کوچکتر از ‎-1 میشه؟!

فکر نمیکنم توی هیچ زبان دیگه ای چنین چیزی دیده شده باشه!

—————————————————————-

بطور کلی بنظر من تبدیل خودکار رشته ها به عدد درست نیست. یعنی دارم البته اینجا درموردی صحبت میکنم که دو طرف هر دو به شکل رشته باشن. حالا اینکه یک طرف رشته باشه یک طرف عدد، بحث و تحلیل جداگانه میطلبه بنظرم.
ببینید، حالا اگر دوتا رشته هر دو محتوی عدد بودن و نه چیز دیگر، باز جای بحث و شک داره، ولی یه موردی مثل “123″ == “123foo” بنظر بنده قطعا غلط و غیرقابل قبوله! هیچ شکی نیست که چنین کاری نباید انجام بشه. بعید میدونم توی هیچ زبان دیگری هم چنین چیزی دیده شده باشه. شما خودتون فکر کنید این چیزها چرا توی زبانهای دیگه دیده نشده تاحالا؟ یعنی به فکرشون نرسیده مثلا؟! البته شاید بگید خب چون PHP مخصوص برنامه نویسی وب است، ولی بازم من فکر نمیکنم حتی اینم بتونه عذر کافی باشه برای اینقدر بالا بردن احتمال باگ و حفره های امنیتی برنامه.

—————————————————————-

البته منم گفتم که با همهء نظرات طرف موافق نیستم. بنظر من همین مواردی که جدا کردم موارد کاملا روشن و جدی هستن. بقیه ایرادهایی که گرفته اونقدر مهم نیستن، مبهم و محل تردید هستن، یا حتی بعضی موارد بنظر من برعکس چیزهای خوبی هستن!
مثلا طرف به آرایه های PHP هم ایراد گرفته ظاهرا گفته مثلا نمیشه میزان حافظه ای رو که مصرف میکنن برآورد کرد بخاطر انعطاف و پیچیدگی زیادی که دارن. ولی بنظر من این ایراد مسخره ایه و انصافا آرایه های PHP بین تمام زبانهایی که تاحالا دیدم منعطف ترین ساختار رو دارن و بنوعی شاهکار هستن که توی برنامه نویسی کار آدم رو خیلی راحت میکنن آدم واقعا لذت میبره. ولی از اون طرف مثلا برآورد حافظه حالا توی این زبانهای سطح بالای اسکریپتی مگه چقدر مسئلهء ضروری و مهمی هست که طرف بهش گیر داده! بنظر من چنین حرفی یخورده مسخره میاد!! حالا اگر از 1000 نفر یکی هم نیاز به این کار داشت، بنظرم از طرق دیگری میتونه میزان حافظه رو برآورد کنه.

—————————————————————-

نقل قول: «خوب این مشکلیه که برنامه نویس داره بوجود میاره. وقتی برنامه نویس یه عدد رو با یه رشته میاد مقایسه میکنه، معلومه که منطقی نیست. بیچاره php چیکار کنه وقتی برنامه نویس داره یه مقایسه غیر منطقی انجام میده؟»

نه این کارها در زبانهای سطح بالا بخصوص اسکریپتی وبخصوص تحت وب، چیز عجیب و غیرمنطقی محسوب نمیشه.
تبدیل انواع خودکار چیزی هست که در اینطور زبانها هست. و اینکه میبینید متغییرها نوع فیکس شده ای ندارن شما میتونید یه متغییر رو اول بهش مقدار رشته بدید، بعد میتونید جای دیگه مقدار عددی بدید، و هر نوعی دیگری. ممکنه یه متغییر مقدارش بصورت رشته باشه اول (بخصوص وقتی از سمت کاربر از مرورگر یا منابع دیگری که مقادیر رو بصورت رشته دریافت میکنید باشه)، بعد شما اون رو مستقیما بدون اینکه نیاز باشه خودتون دستی و صریح تبدیلش کنید، توی یه محاسبهء ریاضی استفاده میکنید، و PHP بصورت خودکار رشته رو تبدیل به عدد میکنه تا محاسبات با بقیهء متغییرها و بخشهای فرمول رو روش انجام بده.
اینا چیزای بدی نیست به خودی خودش و به شکل ها و درجات مختلفی در زبانهای سطح بالا و بخصوص اسکریپتی مختلف وجود داره انحصاری PHP نیست این داستان. اینها در سطح و کاربردهایی که این زبانها دارن یک مزیت و کاربرد از قبل برنامه ریزی شده است. چون کار برنامه نویس رو کاهش میده، و توی اینطور برنامه ها در این سطح برنامه نویسی با اینطور داده های با نوع های مختلف از منابع مختلف زیاد سر و کار پیدا میشه.

پس چنین کاری از جانب برنامه نویس بنظر من به هیچ وجه غیرمنطقی نیست به خودی خودش. ضمنا ممکنه مقدار بصوت صریح نباشه و دو طرف متغییر باشن، حالا توی یکی ممکنه یک بار مقدار بصورت عددی باشه یک بار هم بصورت رشته ای. این مسائل به عللل مختلفی میتونه پیش بیاد. مثلا یک بار شرطی رو چک کرده و متغییر رو بدون اینکه روش محاسبه و تغییر دیگری انجام بده پاس کرده به این شرط، ولی یک بار دیگر ممکنه بر اساس شرطی اومده و روی متغییر یک محاسبهء ریاضی کرده خلاصه کاری کرده که در نتیجه نوع متغییر بصورت خودکار توسط PHP تغییر داده شده.

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

خب این افراط و سرخود کاری یعنی چی مثلا؟
خب همین کارهایی که مشاهده میفرمایید PHP انجام داده! مواردی که واضحه به ندرت پیش میاد منظور واقعی برنامه نویس اون بوده باشه و برنامه نویس غافلگیر میشه اگر چنان منطقی در برنامه اجرا بشه!
چیزی که در زبان پیاده میکنن باید مواردی باشه که کاربرد روتین و مورد انتظار داشته باشه و خیلی بیشتر از اونچه که بتونه منجر به باگ و مشکل بشه، کار برنامه نویس رو راحت بکنه.

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

———————————————————

نقل قول: «در php وقتي از == استفاده ميکنيد اول 2 طرف cast ميشود و بعد شرط بررسي ميشود. وقتي يک رشته را با عدد 0 بررسي ميکنيد هر 2 از نظر کستينگ برابر است چون مقدار رشته تبديل به int ميشود که همان 0 در نظر گرفته ميشود. اين مورد براي مقدارهاي Boolean هم صدق ميکنه. براي اينکه عمل کستينگ انجام نشود بايد از === استفاده کنيد يا اينکه قبل از چک کردن خودتان 2 طرف را هم نوع کنيد مثلا براي int با استفاده از تابع intval()»

تبديل نوع خودکار منحصر به PHP نيست و در زبانهاي ديگر هم وجود داره. بخصوص زبانهاي اسکريپتي.

ولي اين تبديلات اينطور نيست همينطور هر جور دلشون بخواد باشه. اصول داره.

مثلا اين مثال در جاوااسکريپت:
کد:
<script>
alert(3=="3");
</script>

نتيجه true است.
ولي اين مثال:
کد:
<script>
alert(3=="3foo");
</script>

نتيجه false است.

چرا اينطوره؟

چون رشتهء اول فقط متشکل از رقم است، اما رشتهء دوم حاوي کاراکترهاي غير رقمي است و وجود رقم در ابتداش ميتونه کاملا تصادفي بوده باشه.

چرا اين قاعده رو پياده کردن؟ خب چون مثل PHP اينقدر بي پروا و سرخود نبودن و فکر اينو کردن که بعيده برنامه نويس بخواد چنين چيزهايي رو با هم بصورت عددي مقايسه کنه، و اگر چنين شرايطي رخ ميده، به احتمال بيشتر تصادفيه و بنابراين منطق صحيح که موجب باگ در برنامه نشه اينه که اين عمل تبديل خودکار به چنين صورتي صورت نگيره.

اين فقط يه نمونه بود که خواستم درک کنيد منظور چيه معيار چيه. وگرنه مثال هاي بيشتر هست از زبانهاي مختلف، که نشون ميده همه از PHP محتاط تر و معقول تر عمل ميکنن.

ضمنا جاوااسکريپت هم عملگر === داره.
الان در اين مثال:
کد:
<script>
alert(3==="3");
</script>

نتيجه false است.

يعني متوجه ميشيم که جاوااسکريپت و PHP از اين نظر خيلي به هم نزديک هستن. تنها فرق اينه که عملگر == در PHP هيچ محدوديتي قائل نيست و به زور هم که شده ميخواد هرچي رو که دو طرف هستن تبديل و مقايسه کنه؛ حالا کاري نداره اين رفتار آيا اصلا معقول هست و به دردي ميخوره، آيا در کل بيشتر سودمنده يا مضر و باعث ايجاد باگ در برنامه ها!

بنظر من اين طراحي ها واقعا احمقانه هستن. چون به برنامه نويسي در کل بيشتر از اونچه که بتونن کمک کنن، آسيب ميرسونن.

حال در جاوااسکريپت ما اگر بصورت صريح خودمون درخواست کنيم که رشته به عدد تبديل بشه، مثلا با اين کد:
کد:
<script>
alert(parseInt("3foo"));
</script>

نتيجه و خروجي اين کد عدد 3 است.

اما نتيجهء اين کد:
کد:
<script>
alert(parseInt("foo"));
</script>

NaN ميباشد. يعني ميگه اين رشته قابل تبديل به عدد نيست.

اين مثال نشون ميده که واقعا حساب و قاعده اي اين وسط وجود داره مسائلي رو در نظر گرفتن که در PHP در نظر گرفته نشدن.
اون قاعده چيه؟ در مثال اول، چون برنامه نويس خودش صريحا درخواست کرده رشته به عدد تبديل بشه، بنابراين زبان ميتونه تلاش کنه تا رشته رو به هر صورت ممکن و معناداري به عدد تبديل کنه، و ميتونه اطمينان بيشتري داشته باشه که با اينکه کاراکترهاي غيررقمي هم بعد از کاراکتر رقمي وجود دارن اما برنامه نويس از اين مسئله مطلع بوده و بخاطر همين از اين تابع استفاده کرده.
اما جاوااسکريپت عاقل است، و در مقايسه ها (عملگر ==) اينطور مطمئن عمل نميکنه و مثلا 3foo رو به 3 تبديل نميکنه، چون در اونجا درخواست صريحي از جانب برنامه نويس وجود نداره و وجود 3 در ابتداي چنين رشته اي به احتمال بيشتر تصادفي است تا اينکه واقعا معنادار باشه.
و اما در مثال دوم، مشاهده ميکنيم که جاوااسکريپت حتي يک درجه از اين هم محتاط تر هست و رشته اي رو که حاوي هيچ کاراکتر رقمي نيست به صفر تبديل نميکنه. و شما اگر فکر کنيد هم، خب احتمال بيشتر اينه که برنامه نويس هم قصدش اين نبوده يه همچين رشته اي به عدد صفر تبديل بشه! اين رشته رو از کجا آورده و چرا ميخواد تبديل به عدد صفر بشه؟ بهرحال هم اگر هدف برنامه نويس واقعا اين بوده باشه که رشته هاي کاملا غيرعددي به صفر تبديل بشن، ميتونه خودش براي مقدار NaN چک کنه و اگر مقدار برگشتي NaN بود، مقدار صفر رو به متغيير بده. اينطوري عمل بصورت آگاهانه توسط خود برنامه نويس انجام ميشه و ريسک رفتار غيرمنتظره و ايجاد باگ در برنامه وجود نداره.

———————————————————

نقل قول: «وقتی یک رشته را با عدد 0 بررسی میکنید هر 2 از نظر کستینگ برابر است چون مقدار رشته تبدیل به int میشود که همان 0 در نظر گرفته میشود.»

خب چرا عدد رو به رشته تبدیل نمیکنه و دو طرف رو بصورت رشته ای مقایسه کنه؟
یعنی همینطوری رندوم تصمیم گرفتن که هرجا یک طرف رشته بود و یک طرف عدد، رشته رو به عدد تبدیل کنن و دو طرف رو بصورت عددی مقایسه کنن؟
بنظر من اگر تبدیل به رشته میکرد، خیلی امن تر میبود، چون اونوقت دیگه 3foo با 3 برابر نمیشد.

1 دیدگاه در “طراحی های بد در ساختار زبان PHP

  1. همیشه از مطالب شما استفاده میکنم… و از این بابت سپاسگزارم….
    اما به نطر بنده، بهتر است کمی هم در حوزه نگارش مطلب ،مطالعه داشته باشید
    به طور مثال برای بنده کتابهای نگارش سال هفتم و هشتم و نهم بسیار مفید بود
    http://www.chap.sch.ir/books/2908

    احتمالا منابع بیشتری هم در انتهای انها موجود باشد….

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

*

شما می‌توانید از این دستورات HTML استفاده کنید: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>