اہم نکات
1. پیچیدگی سافٹ ویئر ڈیزائن کے چیلنجز کی جڑ ہے
پیچیدگی انحصار اور غیر واضحیت کے جمع ہونے سے پیدا ہوتی ہے۔
پیچیدگی بتدریج جمع ہوتی ہے۔ جیسے جیسے سافٹ ویئر سسٹمز بڑھتے ہیں، ان میں اجزاء کے درمیان انحصار اور غیر واضح کوڈ سیکشنز کے بتدریج جمع ہونے کی وجہ سے پیچیدگی بڑھتی جاتی ہے۔ یہ پیچیدگی تین بنیادی طریقوں سے ظاہر ہوتی ہے:
- تبدیلی کی شدت: چھوٹی تبدیلیاں کئی جگہوں پر ترمیم کی متقاضی ہوتی ہیں
- ذہنی بوجھ: تبدیلیاں کرنے کے لیے ڈویلپرز کو بڑی مقدار میں معلومات کو سمجھنا ہوتا ہے
- نامعلوم نامعلوم: یہ واضح نہیں ہوتا کہ کون سا کوڈ ترمیم کی ضرورت ہے یا کون سی معلومات متعلقہ ہیں
سادگی اس کا علاج ہے۔ پیچیدگی کا مقابلہ کرنے کے لیے، سافٹ ویئر ڈیزائنرز کو سادہ، واضح ڈیزائن بنانے پر توجہ دینی چاہیے جو انحصار اور غیر واضحیت کو کم کریں۔ اس میں شامل ہے:
- ماڈیولر ڈیزائن: سسٹمز کو آزاد ماڈیولز میں تقسیم کرنا
- معلومات کو چھپانا: ماڈیولز کے اندر عمل درآمد کی تفصیلات کو چھپانا
- واضح تجریدات: سادہ انٹرفیس فراہم کرنا جو بنیادی پیچیدگی کو چھپاتے ہیں
2. اسٹریٹجک پروگرامنگ ٹیکٹیکل طریقوں پر غالب ہے
بہترین طریقہ یہ ہے کہ مسلسل بنیادوں پر چھوٹی سرمایہ کاری کی جائے۔
طویل مدتی سوچ بہتر نتائج دیتی ہے۔ اسٹریٹجک پروگرامنگ کا مقصد ایک عظیم ڈیزائن بنانا ہے جو کام کرتا ہے، نہ کہ صرف کوڈ کو کام کرنا۔ اس طریقہ کار میں شامل ہے:
- ڈیزائن میں پہلے سے وقت کی سرمایہ کاری کرنا
- مسلسل چھوٹی بہتریاں کرنا
- صاف ڈیزائن کو برقرار رکھنے کے لیے کوڈ کو دوبارہ ترتیب دینا
ٹیکٹیکل پروگرامنگ تکنیکی قرض کی طرف لے جاتی ہے۔ جبکہ ٹیکٹیکل طریقے مختصر مدت میں تیز تر نظر آ سکتے ہیں، وہ اکثر نتیجہ دیتے ہیں:
- فوری حل اور ہیکس کا جمع ہونا
- وقت کے ساتھ تبدیلیاں کرنے میں بڑھتی ہوئی مشکل
- طویل مدتی ترقیاتی اخراجات میں اضافہ
اسٹریٹجک ذہنیت کو اپنانے سے، ڈویلپرز ایسے سسٹمز بنا سکتے ہیں جو برقرار رکھنے اور ترقی دینے میں آسان ہوں، بالآخر طویل مدت میں وقت اور محنت کی بچت کرتے ہیں۔
3. ماڈیولز کو گہرا ہونا چاہیے، سطحی نہیں
بہترین ماڈیولز وہ ہیں جو طاقتور فعالیت فراہم کرتے ہیں لیکن ان کے انٹرفیس سادہ ہوتے ہیں۔
گہرائی تجرید پیدا کرتی ہے۔ گہرے ماڈیولز سادہ انٹرفیس کے پیچھے اہم عمل درآمد کی پیچیدگی کو چھپاتے ہیں۔ یہ طریقہ:
- ماڈیول کے صارفین کے لیے ذہنی بوجھ کو کم کرتا ہے
- عمل درآمد میں ترمیم کو آسان بناتا ہے
- معلومات کو چھپانے اور انکیپسولیشن کو فروغ دیتا ہے
سطحی ماڈیولز پیچیدگی میں اضافہ کرتے ہیں۔ ماڈیولز جن کے انٹرفیس ان کی فعالیت کے مقابلے میں پیچیدہ ہوتے ہیں، سطحی سمجھے جاتے ہیں۔ یہ ماڈیولز:
- مجموعی سسٹم کی پیچیدگی میں اضافہ کرتے ہیں
- غیر ضروری عمل درآمد کی تفصیلات کو ظاہر کرتے ہیں
- سسٹم کو سمجھنے اور ترمیم کرنے میں مشکل بناتے ہیں
گہرے ماڈیولز بنانے کے لیے، سادہ، بدیہی انٹرفیس ڈیزائن کرنے پر توجہ مرکوز کریں جو بنیادی پیچیدگی کو تجرید کرتے ہیں۔ فعالیت سے انٹرفیس کی پیچیدگی کے تناسب کو زیادہ سے زیادہ کرنے کی کوشش کریں۔
4. اچھے انٹرفیس پیچیدگی کو منظم کرنے کی کلید ہیں
ماڈیول کے انٹرفیس میں دو قسم کی معلومات ہوتی ہیں: رسمی اور غیر رسمی۔
اچھی طرح سے ڈیزائن کردہ انٹرفیس سسٹمز کو آسان بناتے ہیں۔ اچھے انٹرفیس ماڈیول کی فعالیت کی واضح تجرید فراہم کرتے ہیں بغیر غیر ضروری تفصیلات کو ظاہر کیے۔ انہیں چاہیے:
- استعمال میں سادہ اور بدیہی ہونا
- عمل درآمد کی پیچیدگیوں کو چھپانا
- رسمی (مثلاً، طریقہ کار کے دستخط) اور غیر رسمی (مثلاً، اعلی سطحی رویے کی وضاحتیں) دونوں معلومات فراہم کرنا
انٹرفیس کو سوچ سمجھ کر تیار ہونا چاہیے۔ موجودہ کوڈ میں ترمیم کرتے وقت:
- ماڈیول کے انٹرفیس پر اثرات پر غور کریں
- عمل درآمد کی تفصیلات کو ظاہر کرنے سے گریز کریں
- انٹرفیس کی فراہم کردہ تجرید کو برقرار رکھنے یا بہتر بنانے کی کوشش کریں
اچھے انٹرفیس بنانے اور برقرار رکھنے پر توجہ مرکوز کرکے، ڈویلپرز پیچیدگی کو منظم کر سکتے ہیں اور اپنے سسٹمز کو زیادہ ماڈیولر اور سمجھنے میں آسان بنا سکتے ہیں۔
5. تبصرے تجریدات بنانے کے لیے اہم ہیں
تبصرے تجریدات کو مکمل طور پر حاصل کرنے کا واحد طریقہ فراہم کرتے ہیں، اور اچھی تجریدات اچھے سسٹم ڈیزائن کے لیے بنیادی ہیں۔
تبصرے تجریدات کو مکمل کرتے ہیں۔ جبکہ کوڈ عمل درآمد کی تفصیلات کا اظہار کر سکتا ہے، تبصرے ضروری ہیں:
- اعلی سطحی ڈیزائن کے فیصلے
- انتخاب کے پیچھے منطق
- توقعات اور پابندیاں
- تجریدات جو کوڈ سے واضح نہیں ہیں
پہلے تبصرے لکھیں۔ کوڈ کو نافذ کرنے سے پہلے تبصرے لکھ کر:
- آپ ڈیزائن کے بارے میں اپنی سوچ کو واضح کرتے ہیں
- آپ تجریدات کا جلدی جائزہ لے سکتے ہیں اور انہیں بہتر بنا سکتے ہیں
- آپ اس بات کو یقینی بناتے ہیں کہ دستاویزات ہمیشہ تازہ ترین رہیں
کیا اور کیوں پر توجہ مرکوز کریں، کیسے نہیں۔ اچھے تبصرے چاہیے:
- ایسی چیزوں کی وضاحت کریں جو کوڈ سے واضح نہیں ہیں
- کوڈ کے مقصد اور اعلی سطحی رویے کی وضاحت کریں
- صرف وہی دہرانے سے گریز کریں جو کوڈ کرتا ہے
واضح، معلوماتی تبصروں کو ترجیح دے کر، ڈویلپرز بہتر تجریدات بنا سکتے ہیں اور اپنے سسٹمز کے مجموعی ڈیزائن کو بہتر بنا سکتے ہیں۔
6. مستقل نام اور فارمیٹنگ پڑھنے کی صلاحیت کو بڑھاتے ہیں
اچھے نام دستاویزات کی ایک شکل ہیں: وہ کوڈ کو سمجھنے میں آسان بناتے ہیں۔
مستقل مزاجی ذہنی بوجھ کو کم کرتی ہے۔ نام دینے اور فارمیٹنگ کے لیے کنونشنز قائم کرکے اور ان کی پیروی کرکے، ڈویلپرز کر سکتے ہیں:
- کوڈ کو زیادہ پیش گوئی اور پڑھنے میں آسان بنائیں
- کوڈ کو سمجھنے کے لیے درکار ذہنی کوشش کو کم کریں
- تضادات کو اجاگر کریں جو بگز یا ڈیزائن کے مسائل کی نشاندہی کر سکتے ہیں
ناموں کا احتیاط سے انتخاب کریں۔ اچھے نام چاہیے:
- درست اور غیر مبہم ہونا
- نامزد کردہ ہستی کی واضح تصویر بنانا
- کوڈ بیس میں مستقل طور پر استعمال کیا جائے
فارمیٹنگ اہمیت رکھتی ہے۔ مستقل فارمیٹنگ مدد کرتی ہے:
- کوڈ کی ساخت کو زیادہ واضح بنانا
- متعلقہ عناصر کو بصری طور پر گروپ کرنا
- اہم معلومات پر زور دینا
نام دینے اور فارمیٹنگ پر توجہ دے کر، ڈویلپرز اپنے کوڈ کی پڑھنے کی صلاحیت اور برقرار رکھنے کی صلاحیت کو نمایاں طور پر بہتر بنا سکتے ہیں۔
7. صاف ڈیزائن کو برقرار رکھنے کے لیے مسلسل بہتری ضروری ہے
اگر آپ ایک صاف سافٹ ویئر ڈھانچہ چاہتے ہیں، جو آپ کو طویل مدتی میں مؤثر طریقے سے کام کرنے کی اجازت دے گا، تو آپ کو اس ڈھانچے کو بنانے کے لیے کچھ اضافی وقت لینا ہوگا۔
ڈیزائن ایک جاری عمل ہے۔ صاف سافٹ ویئر ڈیزائن کی ضرورت ہوتی ہے:
- موجودہ کوڈ کو بہتر بنانے کے لیے باقاعدہ ریفیکٹرنگ
- ڈیزائن کے فیصلوں کا مسلسل جائزہ
- جیسے جیسے سسٹم تیار ہوتا ہے تبدیلیاں کرنے کی آمادگی
بہتری میں سرمایہ کاری کریں۔ صاف ڈیزائن کو برقرار رکھنے کے لیے:
- صفائی اور ریفیکٹرنگ کے لیے وقت مختص کریں
- ڈیزائن کے مسائل کو فوری طور پر حل کریں، اس سے پہلے کہ وہ مرکب ہوں
- ہر کوڈ تبدیلی کو مجموعی ڈیزائن کو بہتر بنانے کے موقع کے طور پر دیکھیں
کمال اور پیشرفت کے درمیان توازن۔ صاف ڈیزائن کے لیے کوشش کرتے وقت:
- تسلیم کریں کہ کچھ سمجھوتے ضروری ہو سکتے ہیں
- بتدریج بہتریاں کرنے پر توجہ مرکوز کریں
- ان تبدیلیوں کو ترجیح دیں جو سب سے زیادہ اہم فوائد فراہم کرتی ہیں
ڈیزائن کو بہتری کے ایک مسلسل عمل کے طور پر دیکھ کر، ڈویلپرز اپنے سسٹمز کو صاف اور قابل انتظام رکھ سکتے ہیں جیسے جیسے وہ بڑھتے اور تیار ہوتے ہیں۔
8. غلطی سے نمٹنے کو آسان بنایا جانا چاہیے، نہ کہ پھیلایا جائے
استثنائی ہینڈلنگ کی پیچیدگی کو ختم کرنے کا بہترین طریقہ یہ ہے کہ اپنے APIs کو اس طرح سے بیان کریں کہ ہینڈل کرنے کے لیے کوئی استثنا نہ ہو: غلطیوں کو وجود سے باہر بیان کریں۔
استثنائی کیسز کو کم کریں۔ غلطی سے نمٹنے کو آسان بنانے کے لیے:
- APIs کو اس طرح ڈیزائن کریں کہ غیر معمولی حالات کو کم سے کم کیا جا سکے
- عام کنارے کے کیسز کو ہینڈل کرنے کے لیے ڈیفالٹ رویے استعمال کریں
- غور کریں کہ آیا استثنائی واقعی ضروری ہیں
غلطی سے نمٹنے کو جمع کریں۔ جب استثنائی ناگزیر ہوں:
- جہاں ممکن ہو، ایک ہی جگہ پر متعدد استثنائی کو ہینڈل کریں
- متعلقہ غلطیوں کی ہینڈلنگ کو آسان بنانے کے لیے استثنائی درجہ بندی کا استعمال کریں
- ان استثنائی کو پکڑنے سے گریز کریں جنہیں آپ بامعنی طور پر ہینڈل نہیں کر سکتے
عام کیسز کو آسان بنائیں۔ اپنے کوڈ کے ذریعے عام، غلطی سے پاک راستہ کو جتنا ممکن ہو سادہ اور واضح بنانے پر توجہ مرکوز کریں۔ یہ طریقہ:
- ڈویلپرز پر ذہنی بوجھ کو کم کرتا ہے
- بگز متعارف کرانے کے امکانات کو کم کرتا ہے
- کوڈ کو سمجھنے اور برقرار رکھنے میں آسان بناتا ہے
غلطی سے نمٹنے کو آسان بنا کر، ڈویلپرز زیادہ مضبوط اور سمجھنے میں آسان سسٹمز بنا سکتے ہیں۔
9. عمومی مقصد کا کوڈ عام طور پر خاص مقصد کے حل سے بہتر ہوتا ہے
یہاں تک کہ اگر آپ کسی کلاس کو خاص مقصد کے طریقے سے استعمال کرتے ہیں، تو اسے عمومی مقصد کے طریقے سے بنانا کم کام ہے۔
عمومیت دوبارہ استعمال کو فروغ دیتی ہے۔ عمومی مقصد کا کوڈ:
- مسائل کی وسیع رینج پر لاگو کیا جا سکتا ہے
- اکثر سادہ اور زیادہ تجریدی ہوتا ہے
- صاف انٹرفیس رکھنے کا رجحان رکھتا ہے
قبل از وقت تخصص سے گریز کریں۔ نئی فعالیت کو ڈیزائن کرتے وقت:
- کسی حد تک عمومی مقصد کے طریقے سے شروع کریں
- مخصوص استعمال کے کیسز کے لیے بہت جلد اصلاح کرنے کی خواہش کی مزاحمت کریں
- اصل استعمال کے نمونوں کی بنیاد پر ڈیزائن کو تیار ہونے دیں
عمومیت اور سادگی کے درمیان توازن۔ عمومی مقصد کے حل کے لیے کوشش کرتے وقت:
- زیادہ انجینئرنگ یا غیر ضروری پیچیدگی شامل کرنے سے گریز کریں
- اس بات کو یقینی بنائیں کہ عمومی مقصد کا ڈیزائن عام کیسز کے لیے استعمال میں آسان ہے
- جب واقعی ضروری ہو تو خصوصی حل بنانے کے لیے تیار رہیں
عمومی مقصد کے ڈیزائن کو ترجیح دے کر، ڈویلپرز زیادہ لچکدار اور برقرار رکھنے کے قابل سسٹمز بنا سکتے ہیں جو مستقبل کی ضروریات کو پورا کرنے کے لیے بہتر طور پر لیس ہوں۔
10. کوڈ کو پڑھنے کی صلاحیت کے لیے لکھیں، لکھنے میں آسانی کے لیے نہیں
سافٹ ویئر کو پڑھنے میں آسانی کے لیے ڈیزائن کیا جانا چاہیے، لکھنے میں آسانی کے لیے نہیں۔
طویل مدتی برقرار رکھنے کی صلاحیت کو ترجیح دیں۔ کوڈ لکھتے وقت:
- مستقبل کے قارئین کے لیے اسے سمجھنے میں آسان بنانے پر توجہ مرکوز کریں
- شارٹ کٹس یا چالاک چالوں سے گریز کریں جو کوڈ کے مقصد کو غیر واضح کرتے ہیں
- واضح تجریدات اور دستاویزات بنانے میں وقت لگائیں
کوڈ کو واضح بنائیں۔ کوڈ لکھنے کی کوشش کریں جو:
- کم سے کم ذہنی کوشش کے ساتھ جلدی سمجھا جا سکتا ہے
- واضح اور مستقل نام دینے کے کنونشنز کا استعمال کرتا ہے
- ایک منطقی اور آسانی سے پیروی کرنے والی ساخت رکھتا ہے
وضاحت کے لیے ریفیکٹر کریں۔ موجودہ کوڈ کا باقاعدگی سے جائزہ لیں اور اسے بہتر بنائیں:
- پیچیدہ سیکشنز کو آسان بنانے کے مواقع تلاش کریں
- طویل طریقوں کو چھوٹے، زیادہ مرکوز حصوں میں تقسیم کریں
- نقل اور تضادات کو ختم کریں
پڑھنے کی صلاحیت کو لکھنے میں آسانی پر ترجیح دے کر، ڈویلپرز ایسے سسٹمز بنا سکتے ہیں جو وقت کے ساتھ برقرار رکھنے، ڈیبگ کرنے، اور بڑھانے میں آسان ہوں۔ یہ طریقہ کار ابتدائی طور پر زیادہ محنت کا تقاضا کر سکتا ہے لیکن طویل مدتی پیچیدگی کو کم کرنے اور ٹیم کی پیداواری صلاحیت کو بہتر بنانے میں فائدہ مند ثابت ہوتا ہے۔
آخری تازہ کاری:
FAQ
What's "A Philosophy of Software Design" about?
- Focus on Complexity: The book addresses the core problem of software design, which is managing complexity. It emphasizes that complexity is the primary challenge in building and maintaining software systems.
- Design Principles: John Ousterhout presents a set of high-level design principles aimed at reducing complexity, such as creating deep modules and defining errors out of existence.
- Practical Advice: The book offers practical advice for software developers on how to think strategically about design, rather than just focusing on getting code to work.
- Educational Approach: It is based on Ousterhout's experience teaching a software design course at Stanford, where students learn through iterative design and code reviews.
Why should I read "A Philosophy of Software Design"?
- Improve Design Skills: The book provides insights into improving software design skills, which can lead to more maintainable and efficient code.
- Strategic Mindset: It encourages a strategic approach to programming, focusing on long-term design quality rather than short-term fixes.
- Real-World Examples: The book includes numerous real-world examples and case studies that illustrate the principles in action.
- Philosophical Insights: It offers philosophical insights into the nature of software design, making it valuable for both novice and experienced developers.
What are the key takeaways of "A Philosophy of Software Design"?
- Complexity is Incremental: Complexity builds up in small increments, and managing it requires constant vigilance and strategic thinking.
- Deep Modules: Modules should have simple interfaces but provide significant functionality, hiding complexity from the rest of the system.
- Information Hiding: Effective information hiding reduces dependencies and makes systems easier to modify and understand.
- Design it Twice: Consider multiple design options before settling on one, as the first idea is rarely the best.
What is the "Design it Twice" principle in "A Philosophy of Software Design"?
- Multiple Options: The principle suggests considering multiple design options for each major decision, rather than settling on the first idea.
- Radical Differences: It encourages exploring radically different approaches to understand the strengths and weaknesses of each.
- Improved Design: By comparing alternatives, you can identify the best design or combine features from multiple designs for a superior solution.
- Learning Opportunity: This process also enhances your design skills by teaching you what makes designs better or worse.
How does "A Philosophy of Software Design" define complexity?
- Practical Definition: Complexity is anything in the software structure that makes it hard to understand and modify.
- Symptoms: It manifests as change amplification, cognitive load, and unknown unknowns, making development tasks more difficult.
- Causes: Complexity arises from dependencies and obscurity, which can be minimized through good design practices.
- Incremental Nature: Complexity accumulates in small increments, requiring a zero-tolerance approach to prevent it from becoming overwhelming.
What is the importance of "Deep Modules" in "A Philosophy of Software Design"?
- Simple Interfaces: Deep modules have simple interfaces that hide the complexity of their implementations, reducing the cognitive load on developers.
- Functionality vs. Interface: They provide significant functionality relative to the complexity of their interfaces, offering a high benefit-to-cost ratio.
- Information Hiding: Deep modules effectively hide information, making it easier to evolve the system without affecting other modules.
- Examples: The book uses examples like Unix I/O and garbage collectors to illustrate the concept of deep modules.
How does "A Philosophy of Software Design" suggest handling exceptions?
- Define Errors Out of Existence: Redefine operations to eliminate error conditions, reducing the need for exception handling.
- Mask Exceptions: Handle exceptions at a low level to prevent them from propagating and complicating higher-level code.
- Aggregate Exceptions: Use a single handler to manage multiple exceptions, simplifying the code and reducing duplication.
- Just Crash: For certain errors, it may be more practical to crash the application rather than handle the exception, especially if recovery is complex.
What role do comments play according to "A Philosophy of Software Design"?
- Essential for Abstraction: Comments are crucial for defining abstractions, as they provide information that can't be captured in code.
- Describe Non-Obvious Information: They should describe things that aren't obvious from the code, such as design rationale and usage constraints.
- Part of Design Process: Writing comments early in the design process can improve both the design and the quality of the comments.
- Avoid Repetition: Comments should not repeat the code but instead provide additional insights and context.
What is the "Investment Mindset" in "A Philosophy of Software Design"?
- Long-Term Focus: The investment mindset emphasizes spending time on design improvements that will pay off in the long run.
- Continuous Improvement: It involves making continual small investments in the system's design to prevent complexity from accumulating.
- Strategic Programming: Developers should prioritize creating a great design over just getting code to work, even if it takes longer initially.
- Payback Period: The book suggests that the benefits of a strategic approach will outweigh the initial costs within 6–18 months.
How does "A Philosophy of Software Design" address the issue of naming?
- Create an Image: Names should create a clear image of what the entity represents, providing precise and intuitive information.
- Consistency: Use names consistently across the codebase to reduce cognitive load and prevent misunderstandings.
- Avoid Vague Names: Names should be specific and avoid generic terms that can lead to ambiguity and errors.
- Impact on Complexity: Good naming practices can significantly reduce complexity and improve code readability.
What are some best quotes from "A Philosophy of Software Design" and what do they mean?
- "Complexity is incremental": This quote highlights the idea that complexity builds up gradually, requiring constant attention to manage.
- "Working code isn’t enough": It emphasizes that simply getting code to work is not sufficient; good design is crucial for long-term success.
- "Modules should be deep": This quote underscores the importance of creating modules with simple interfaces that hide complexity.
- "Define errors out of existence": It suggests redefining operations to eliminate error conditions, simplifying exception handling.
How does "A Philosophy of Software Design" suggest dealing with performance concerns?
- Natural Efficiency: Choose design alternatives that are naturally efficient without sacrificing simplicity.
- Measure Performance: Before optimizing, measure the system to identify the true bottlenecks and ensure changes have a measurable impact.
- Critical Path Design: Focus on optimizing the critical path, the smallest amount of code that must be executed in the common case.
- Simplicity and Speed: Simpler code tends to run faster, and clean design often leads to better performance.
جائزے
سافٹ ویئر ڈیزائن کی فلسفہ، دوسری ایڈیشن کو مختلف آراء ملتی ہیں۔ بہت سے لوگ اس کی پیچیدگی کے انتظام اور گہرے ماڈیولز کے ڈیزائن پر بصیرت کی تعریف کرتے ہیں، جبکہ کچھ اس کی تبصروں پر زور دینے اور بعض شعبوں میں گہرائی کی کمی پر تنقید کرتے ہیں۔ قارئین واضح تحریر اور عملی مشوروں کی قدر کرتے ہیں، خاص طور پر نئے ڈویلپرز کے لیے۔ تاہم، کچھ تجربہ کار پروگرامرز اسے بہت بنیادی سمجھتے ہیں یا بعض سفارشات سے اختلاف کرتے ہیں۔ کتاب کا توجہ آبجیکٹ اورینٹڈ پروگرامنگ اور تعلیمی نقطہ نظر پر ہے، جبکہ کچھ مزید متنوع زبان کے مثالوں اور حقیقی دنیا کی ایپلیکیشنز کی خواہش کرتے ہیں۔
Similar Books







