“Effective Java (3rd Edition)” سے بہترین طریقے

Joshua Bloch کی “Effective Java (3rd Edition) ” سے اقتباس

1. null کے بجائے خالی collections یا arrays واپس کریں

اگر میتھڈ Set واپس کرتا ہے تو خالی نتیجے کے لیے return null; نہ لکھیں – اس کے بجائے return Set.of();۔

2. کنسٹرکٹرز کے بجائے اسٹیٹک فیکٹری میتھڈز پر غور کریں

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

یہ ہمیشہ بہتر نہیں ہوتے، لیکن کچھ صورتوں میں بے شمار فوائد دیتے ہیں:

نقصانات:

3. جب کنسٹرکٹر کے پیرامیٹرز بہت زیادہ ہوں تو Builder پر غور کریں

اسٹیٹک فیکٹریز اور کنسٹرکٹرز میں ایک حد مشترک ہے: بہت سے اختیاری پیرامیٹرز کے ساتھ یہ اچھی طرح نہیں چلتے۔ میرے کچھ پروجیکٹس کو اس مسئلے نے بہت بری طرح متاثر کیا۔

4. Singleton خاصیت کو پرائیویٹ کنسٹرکٹر یا enum ٹائپ کے ذریعے نافذ کریں

جس چیز کے متعدد انسٹینسز کی ضرورت نہ ہو، اسے صرف ایک بار بنایا جانا چاہیے – چاہے @ApplicationScoped کے ذریعے ہو یا کسی اور Singleton پیٹرن سے۔

5. پرائیویٹ کنسٹرکٹر کے ذریعے انسٹینس بنانے سے روکیں

کبھی کبھار آپ ایسی کلاس لکھنا چاہیں گے جو صرف اسٹیٹک میتھڈز اور اسٹیٹک فیلڈز کا مجموعہ ہو۔ ایسی کلاسز بدنام ہو چکی ہیں کیونکہ کچھ لوگ آبجیکٹس کے انداز میں سوچنے سے بچنے کے لیے ان کا غلط استعمال کرتے ہیں، لیکن ان کے جائز استعمال بھی ہیں۔

private MyStaticClass() {
    // private constructor to prevent instantiation
}

6. غیر ضروری آبجیکٹس بنانے سے بچیں

String s = new String("bikini");  // DON'T DO THIS!

یا:

// Hideously slow! Can you spot the object creation?
private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;
    return sum;
}

حل: یہ پروگرام درست جواب دیتا ہے، لیکن ایک حرف کی ٹائپنگ کی غلطی کی وجہ سے جتنا ہونا چاہیے اس سے کہیں زیادہ سست ہے۔ ویری ایبل sum کو long کے بجائے Long کے طور پر ڈکلیئر کیا گیا ہے، جس کا مطلب ہے کہ پروگرام تقریباً 2^31 غیر ضروری Long انسٹینسز بناتا ہے (تقریباً ہر اس بار ایک، جب long i کو Long sum میں جمع کیا جاتا ہے)۔

7. Finalizers اور Cleaners سے بچیں

Finalizers ناقابلِ پیشگوئی، اکثر خطرناک اور عموماً غیر ضروری ہوتے ہیں۔ ان کا استعمال بے ترتیب رویے، کمزور پرفارمنس اور پورٹیبلٹی کے مسائل کا سبب بن سکتا ہے۔

8. try-finally کے بجائے try-with-resources کو ترجیح دیں

// try-with-resources - the best way to close resources!
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
}

9. Comparable نافذ کرنے پر غور کریں

Comparable نافذ کر کے آپ اپنی کلاس کو ان تمام generic الگورتھمز اور collection نفاذ کے ساتھ کام کرنے دیتے ہیں جو اس انٹرفیس پر انحصار کرتے ہیں۔

10. کلاسز اور ممبرز تک رسائی کو کم سے کم رکھیں

ایک اچھے ڈیزائن والے کمپوننٹ کو برے ڈیزائن والے سے ممتاز کرنے والا سب سے اہم عنصر یہ ہے کہ وہ کمپوننٹ اپنے اندرونی ڈیٹا اور عمل درآمد کی دیگر تفصیلات کو دوسرے کمپوننٹس سے کس حد تک چھپاتا ہے۔ اچھے ڈیزائن والا کمپوننٹ اپنی تمام تفصیلات چھپاتا ہے اور اپنے API کو اپنے نفاذ سے صاف طور پر الگ رکھتا ہے۔ پھر کمپوننٹس صرف اپنے APIs کے ذریعے بات چیت کرتے ہیں اور ایک دوسرے کے اندرونی کام کاج سے بے خبر رہتے ہیں۔ یہ تصور، جسے information hiding یا انکیپسولیشن کہا جاتا ہے، سافٹ ویئر ڈیزائن کا بنیادی اصول ہے۔

11. پبلک کلاسز میں پبلک فیلڈز کے بجائے accessor میتھڈز استعمال کریں

کبھی کبھار آپ کا دل چاہ سکتا ہے کہ ایسی بے کار کلاسز لکھیں جن کا مقصد صرف انسٹینس فیلڈز کو اکٹھا کرنا ہو:

// Degenerate classes like this should not be public!
class Point {
    public double x;
    public double y;
}

چونکہ ایسی کلاسز کے ڈیٹا فیلڈز تک براہِ راست رسائی ہوتی ہے، اس لیے یہ کلاسز انکیپسولیشن کے فوائد نہیں دیتیں۔ آپ API بدلے بغیر اندرونی نمائندگی نہیں بدل سکتے، invariants نافذ نہیں کر سکتے، اور کسی فیلڈ تک رسائی پر کوئی معاون کارروائی نہیں کر سکتے۔ سخت گیر آبجیکٹ اورینٹڈ پروگرامرز سمجھتے ہیں کہ ایسی کلاسز ناقابلِ قبول ہیں اور انہیں ہمیشہ ایسی کلاسز سے بدلنا چاہیے جن میں پرائیویٹ فیلڈز اور پبلک accessor میتھڈز (getters) ہوں، اور تبدیل ہونے والی کلاسز کے لیے mutators (setters):

// Encapsulation of data by accessor methods and mutators
class Point {

    private double x;
    private double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() { return x; }
    public double getY() { return y; }

    public void setX(double x) { this.x = x; }
    public void setY(double y) { this.y = y; }
}

یقیناً پبلک کلاسز کے معاملے میں سخت گیر لوگ درست ہیں: اگر کوئی کلاس اپنے پیکیج سے باہر قابلِ رسائی ہے، تو accessor میتھڈز فراہم کریں تاکہ کلاس کی اندرونی نمائندگی بدلنے کی لچک برقرار رہے۔ اگر کوئی پبلک کلاس اپنے ڈیٹا فیلڈز ظاہر کر دے، تو اس کی نمائندگی بدلنے کی ہر امید ختم ہو جاتی ہے، کیونکہ کلائنٹ کوڈ دور دور تک پھیل چکا ہو سکتا ہے۔

البتہ اگر کوئی کلاس package-private ہو یا پرائیویٹ nested کلاس ہو، تو اس کے ڈیٹا فیلڈز ظاہر کرنے میں بنیادی طور پر کوئی برائی نہیں – بشرطیکہ وہ کلاس کے فراہم کردہ abstraction کو مناسب طور پر بیان کرتے ہوں۔

12. تبدیلی پذیری (mutability) کو کم سے کم رکھیں

immutable کلاس بس ایک ایسی کلاس ہے جس کے انسٹینسز میں تبدیلی نہیں کی جا سکتی۔ ہر انسٹینس میں موجود تمام معلومات آبجیکٹ کی پوری زندگی کے لیے مقرر ہوتی ہیں، اس لیے کوئی تبدیلی کبھی دیکھی نہیں جا سکتی۔ Java پلیٹ فارم کی لائبریریوں میں بہت سی immutable کلاسز ہیں، جن میں String، boxed primitive کلاسز، اور BigInteger اور BigDecimal شامل ہیں۔ اس کی کئی اچھی وجوہات ہیں: immutable کلاسز کو ڈیزائن کرنا، نافذ کرنا اور استعمال کرنا mutable کلاسز سے آسان ہے۔ ان میں غلطیوں کا امکان کم اور سیکیورٹی زیادہ ہوتی ہے۔ کسی کلاس کو immutable بنانے کے لیے ان پانچ اصولوں پر عمل کریں:

  1. ایسے میتھڈز فراہم نہ کریں جو آبجیکٹ کی اسٹیٹ بدلیں (جنہیں mutators کہا جاتا ہے)۔
  2. یقینی بنائیں کہ کلاس کو extend نہ کیا جا سکے۔ یہ لاپرواہ یا بدنیت سب کلاسز کو اس بات سے روکتا ہے کہ وہ ایسا برتاؤ کر کے کلاس کے immutable رویے کو نقصان پہنچائیں گویا آبجیکٹ کی اسٹیٹ بدل گئی ہو۔ سب کلاس بنانے سے روکنا عموماً کلاس کو مثلاً final بنا کر کیا جاتا ہے۔
  3. تمام فیلڈز کو final بنائیں۔ یہ آپ کے ارادے کو ایسے انداز میں واضح کرتا ہے جسے سسٹم نافذ کرتا ہے۔ نیز یہ درست رویے کو یقینی بنانے کے لیے ضروری ہے اگر کسی نئے بنائے گئے انسٹینس کا ریفرنس synchronization کے بغیر ایک تھریڈ سے دوسرے تھریڈ کو دیا جائے، جیسا کہ میموری ماڈل میں بیان کیا گیا ہے۔
  4. تمام فیلڈز کو private بنائیں۔ یہ کلائنٹس کو ان mutable آبجیکٹس تک رسائی حاصل کرنے اور انہیں براہِ راست بدلنے سے روکتا ہے جن کی طرف فیلڈز اشارہ کرتے ہیں۔ اگرچہ تکنیکی طور پر immutable کلاسز کے لیے جائز ہے کہ ان میں public final فیلڈز ہوں جن میں primitive ویلیوز یا immutable آبجیکٹس کے ریفرنسز ہوں، لیکن یہ تجویز نہیں کیا جاتا کیونکہ اس سے بعد کے ریلیز میں اندرونی نمائندگی بدلنا ناممکن ہو جاتا ہے۔
  5. کسی بھی mutable کمپوننٹ تک خصوصی رسائی یقینی بنائیں۔ اگر آپ کی کلاس میں ایسے فیلڈز ہیں جو mutable آبجیکٹس کی طرف اشارہ کرتے ہیں، تو یقینی بنائیں کہ کلاس کے کلائنٹس ان آبجیکٹس کے ریفرنسز حاصل نہ کر سکیں۔ ایسے فیلڈ کو کبھی کلائنٹ کے دیے گئے آبجیکٹ ریفرنس سے initialize نہ کریں اور نہ ہی کسی accessor سے وہ فیلڈ واپس کریں۔ کنسٹرکٹرز، accessors اور readObject میتھڈز میں دفاعی کاپیاں بنائیں۔

13. وراثت کے بجائے کمپوزیشن کو ترجیح دیں

وراثت کوڈ کو دوبارہ استعمال کرنے کا ایک طاقتور طریقہ ہے، لیکن یہ ہمیشہ اس کام کے لیے بہترین ٹول نہیں۔ غلط استعمال ہو تو یہ کمزور سافٹ ویئر کا باعث بنتی ہے۔ کسی پیکیج کے اندر وراثت کا استعمال محفوظ ہے، جہاں سب کلاس اور سپر کلاس کا نفاذ انہی پروگرامرز کے اختیار میں ہو۔ اسی طرح ان کلاسز کو extend کرنے کے لیے وراثت کا استعمال بھی محفوظ ہے جو خاص طور پر extension کے لیے ڈیزائن اور دستاویزی کی گئی ہوں۔ لیکن پیکیج کی حدود کے پار عام concrete کلاسز سے وراثت لینا خطرناک ہے۔

میتھڈ کال کے برعکس، وراثت انکیپسولیشن کی خلاف ورزی کرتی ہے۔ آپ سوچ سکتے ہیں کہ اگر آپ صرف نئے میتھڈز شامل کریں اور موجودہ میتھڈز کو override کرنے سے گریز کریں تو کلاس کو extend کرنا محفوظ ہے۔ اگرچہ اس قسم کی extension کہیں زیادہ محفوظ ہے، مگر یہ خطرے سے خالی نہیں۔ اگر سپر کلاس کو بعد کے ریلیز میں کوئی نیا میتھڈ مل جائے اور آپ کی بدقسمتی سے آپ نے سب کلاس کو اسی signature اور مختلف ریٹرن ٹائپ والا میتھڈ دے رکھا ہو، تو آپ کی سب کلاس کمپائل ہی نہیں ہوگی۔ اگر آپ نے سب کلاس کو نئے سپر کلاس میتھڈ جیسا ہی signature اور ریٹرن ٹائپ والا میتھڈ دیا ہو، تو اب آپ اسے override کر رہے ہیں، اور یوں آپ پہلے بیان کیے گئے مسائل کی زد میں ہیں۔ مزید یہ کہ اس میں شک ہے کہ آپ کا میتھڈ نئے سپر کلاس میتھڈ کے کنٹریکٹ کو پورا کرے گا، کیونکہ جب آپ نے سب کلاس کا میتھڈ لکھا تھا تب وہ کنٹریکٹ ابھی لکھا ہی نہیں گیا تھا۔

خوش قسمتی سے، اوپر بیان کیے گئے تمام مسائل سے بچنے کا ایک طریقہ ہے۔ کسی موجودہ کلاس کو extend کرنے کے بجائے، اپنی نئی کلاس کو ایک پرائیویٹ فیلڈ دیں جو موجودہ کلاس کے ایک انسٹینس کا ریفرنس رکھے۔ اس ڈیزائن کو کمپوزیشن کہا جاتا ہے کیونکہ موجودہ کلاس نئی کلاس کا ایک جزو بن جاتی ہے۔ نئی کلاس کا ہر انسٹینس میتھڈ موجودہ کلاس کے موجود انسٹینس پر متعلقہ میتھڈ کو کال کرتا ہے اور نتائج واپس کرتا ہے۔ اسے forwarding کہا جاتا ہے، اور نئی کلاس کے میتھڈز کو forwarding میتھڈز کہا جاتا ہے۔ نتیجے میں بننے والی کلاس چٹان کی طرح مضبوط ہوگی، اور موجودہ کلاس کے نفاذ کی تفصیلات پر اس کا کوئی انحصار نہیں ہوگا۔ موجودہ کلاس میں نئے میتھڈز شامل کرنے کا بھی نئی کلاس پر کوئی اثر نہیں ہوگا۔

14. ابسٹریکٹ کلاسز کے بجائے انٹرفیسز کو ترجیح دیں

Java میں ایسی ٹائپ کی تعریف کے دو طریقے ہیں جو متعدد نفاذ کی اجازت دے: انٹرفیسز اور ابسٹریکٹ کلاسز۔ Java 8 میں انٹرفیسز کے لیے default methods متعارف ہونے کے بعد سے، دونوں طریقے آپ کو کچھ انسٹینس میتھڈز کے نفاذ فراہم کرنے دیتے ہیں۔

ایک بڑا فرق یہ ہے کہ کسی ابسٹریکٹ کلاس کی بیان کردہ ٹائپ کو نافذ کرنے کے لیے کلاس کو اس ابسٹریکٹ کلاس کی سب کلاس ہونا چاہیے۔ چونکہ Java صرف single inheritance کی اجازت دیتی ہے، اس لیے ابسٹریکٹ کلاسز پر یہ پابندی ٹائپ کی تعریف کے طور پر ان کے استعمال کو سختی سے محدود کر دیتی ہے۔ جبکہ ہر وہ کلاس جو تمام مطلوبہ میتھڈز بیان کرے اور عمومی کنٹریکٹ کی پابندی کرے، کسی انٹرفیس کو نافذ کر سکتی ہے، چاہے کلاس hierarchy میں وہ کہیں بھی ہو۔ موجودہ کلاسز کو آسانی سے نیا انٹرفیس نافذ کرنے کے قابل بنایا جا سکتا ہے۔

15. Generics: raw types استعمال نہ کریں

آپ کو غلطی کا پتا رن ٹائم پر ہی چلتا ہے، اس کے ہونے کے بہت بعد، اور ایسے کوڈ میں جو غلطی والے کوڈ سے بہت دور ہو سکتا ہے۔ جب آپ ClassCastException دیکھیں گے، تو آپ کو پورے codebase میں وہ میتھڈ کال ڈھونڈنی ہوگی جس نے سکہ ٹکٹوں کے مجموعے میں ڈال دیا۔ کمپائلر آپ کی مدد نہیں کر سکتا۔

16. Generics: unchecked وارننگز ختم کریں

جب آپ generics کے ساتھ پروگرامنگ کرتے ہیں، تو آپ کو کمپائلر کی بہت سی وارننگز نظر آئیں گی:

generics کا جتنا زیادہ تجربہ حاصل کریں گے، اتنی کم وارننگز ملیں گی، لیکن یہ توقع نہ رکھیں کہ نیا لکھا ہوا کوڈ بغیر وارننگ کے کمپائل ہو جائے گا۔ بہت سی unchecked وارننگز آسانی سے ختم کی جا سکتی ہیں۔ مثال کے طور پر، فرض کریں آپ غلطی سے یہ declaration لکھ دیتے ہیں:

Set<Lark> exaltation = new HashSet();

اسے یوں بدلیں:

Set<Lark> exaltation = new HashSet<>();

کچھ وارننگز کو ختم کرنا کہیں زیادہ مشکل ہوگا۔ جب ایسی وارننگز ملیں جن پر سوچنا پڑے، تو ڈٹے رہیں! ہر وہ unchecked وارننگ ختم کریں جو آپ کر سکتے ہیں۔

اگر آپ تمام وارننگز ختم کر دیں، تو آپ کو یقین ہو جاتا ہے کہ آپ کا کوڈ typesafe ہے، جو بہت اچھی بات ہے۔ اس کا مطلب ہے کہ آپ کو رن ٹائم پر ClassCastException نہیں ملے گا، اور آپ کا اعتماد بڑھتا ہے کہ آپ کا پروگرام آپ کی منشا کے مطابق چلے گا۔ اگر آپ کوئی وارننگ ختم نہیں کر سکتے، لیکن ثابت کر سکتے ہیں کہ جس کوڈ نے وارننگ پیدا کی وہ typesafe ہے، تو تب (اور صرف تب) اس وارننگ کو @SuppressWarnings("unchecked") اینوٹیشن سے دبا دیں۔ اگر آپ پہلے یہ ثابت کیے بغیر کہ کوڈ typesafe ہے وارننگز دبا دیں، تو آپ خود کو تحفظ کا ایک جھوٹا احساس دے رہے ہیں۔ کوڈ کسی وارننگ کے بغیر کمپائل ہو سکتا ہے، لیکن پھر بھی رن ٹائم پر ClassCastException پھینک سکتا ہے۔

جب بھی آپ @SuppressWarnings("unchecked") اینوٹیشن استعمال کریں، ایک کمنٹ شامل کریں جس میں لکھا ہو کہ ایسا کرنا کیوں محفوظ ہے۔

17. Generics: جب یہ معقول ہوں

اُف – ایک کلاس جسے generics کی شدید ضرورت ہے:

ایک ایسی کلاس کی مثال جسے generics کی ضرورت ہے

ایسی چیز کو زیادہ سے زیادہ merge request کے مرحلے پر پکڑ لیا جانا چاہیے!

18. Generics: ایسے نہیں کرنا

معذرت، کچھ کہنے کی ضرورت نہیں۔ براہِ کرم اسے کبھی merge نہ کریں:

public abstract class JpaLearningCardBaseContentService<LC extends LearningCardBaseContent<L, LC, AC>, J extends JpaLearningCardBaseContent<L, LC, AC>, L extends
        LearningCardBase<LC, A>, A extends AnswerOptionBase<L, AC>, AC extends AnswerOptionBaseContent<LC, AC, A>, AS extends AnswerOptionBaseService<A, AC, L, LC>, ACS extends
        AnswerOptionBaseContentService<AC, A, L, LC>, LS extends LearningCardBaseService<L>>
    extends JpaLightPersistenceService<LC, J, Long> implements LearningCardBaseContentService<LC, L, A, AC> {
    // ...
}

صرف ایک کلاس declaration کو سمجھنے میں کبھی 5 منٹ نہیں لگنے چاہییں۔

19. arrays کے بجائے lists کو ترجیح دیں

arrays دو اہم طریقوں سے generic ٹائپس سے مختلف ہیں۔ پہلا، arrays covariant ہوتی ہیں۔ اس ڈراؤنے لفظ کا مطلب بس یہ ہے کہ اگر Sub، Super کی سب ٹائپ ہے، تو array ٹائپ Sub[]، array ٹائپ Super[] کی سب ٹائپ ہے۔ اس کے برعکس generics invariant ہیں: کسی بھی دو مختلف ٹائپس Type1 اور Type2 کے لیے، List<Type1> نہ تو List<Type2> کی سب ٹائپ ہے اور نہ سپر ٹائپ۔

20. anonymous کلاسز کے بجائے lambdas کو ترجیح دیں

// Anonymous class instance as a function object - obsolete!
Collections.sort(words, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return Integer.compare(s1.length(), s2.length());
    }
});

اس کے بجائے یہ کریں:

// Lambda expression as function object (replaces anonymous class)
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));

21. لوکل ویری ایبلز کا دائرہ کم سے کم رکھیں

کسی لوکل ویری ایبل کا دائرہ کم کرنے کی سب سے طاقتور تکنیک یہ ہے کہ اسے وہیں ڈکلیئر کریں جہاں وہ پہلی بار استعمال ہو...

22. روایتی for لوپس کے بجائے for-each لوپس کو ترجیح دیں

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    ... // Do something with e
}

23. لائبریریوں کو جانیں اور استعمال کریں

فرض کریں آپ صفر اور کسی بالائی حد کے درمیان random integers بنانا چاہتے ہیں۔ اس عام کام کا سامنا ہونے پر بہت سے پروگرامرز ایک چھوٹا سا میتھڈ لکھیں گے جو کچھ یوں دکھتا ہے:

// Common but deeply flawed!
static Random rnd = new Random();

static int random(int n) {
    return Math.abs(rnd.nextInt()) % n;
}

یہ میتھڈ اچھا لگ سکتا ہے، لیکن اس میں تین خامیاں ہیں۔ پہلی یہ کہ اگر n دو کی کوئی چھوٹی طاقت ہو، تو random نمبرز کا سلسلہ کافی مختصر وقفے کے بعد خود کو دہرانے لگے گا۔ دوسری خامی یہ ہے کہ اگر n دو کی طاقت نہ ہو، تو اوسطاً کچھ نمبرز دوسروں کی نسبت زیادہ بار واپس آئیں گے۔ اگر n بڑا ہو، تو یہ اثر کافی نمایاں ہو سکتا ہے۔

random میتھڈ کی تیسری خامی یہ ہے کہ یہ کبھی کبھار تباہ کن طور پر ناکام ہو سکتا ہے اور مقررہ حد سے باہر کا نمبر واپس کر سکتا ہے۔ اس کی وجہ یہ ہے کہ میتھڈ rnd.nextInt() کی واپس کردہ ویلیو کو Math.abs کال کر کے غیر منفی int میں بدلنے کی کوشش کرتا ہے۔ اگر nextInt() Integer.MIN_VALUE واپس کرے، تو Math.abs بھی Integer.MIN_VALUE واپس کرے گا، اور remainder آپریٹر (%) ایک منفی نمبر واپس کرے گا، یہ فرض کرتے ہوئے کہ n دو کی طاقت نہیں۔ یہ تقریباً یقینی طور پر آپ کے پروگرام کو ناکام کر دے گا، اور اس ناکامی کو دوبارہ پیدا کرنا مشکل ہو سکتا ہے۔

24. اگر درست جوابات درکار ہوں تو float اور double سے بچیں

رقم کی مقداروں کے لیے BigDecimal استعمال کریں۔ float اور double راؤنڈنگ کی غلطیاں پیدا کرتے ہیں۔

مثال کے طور پر، فرض کریں آپ کی جیب میں 1.03 ڈالر ہیں، اور آپ 42 سینٹ خرچ کرتے ہیں۔ آپ کے پاس کتنے پیسے بچے؟ یہ رہا ایک سادہ لوح پروگرام کا ٹکڑا جو اس سوال کا جواب دینے کی کوشش کرتا ہے:

System.out.println(1.03 - 0.42);

بدقسمتی سے یہ 0.6100000000000001 پرنٹ کرتا ہے۔ یہ کوئی اکیلا واقعہ نہیں۔ فرض کریں آپ کی جیب میں ایک ڈالر ہے، اور آپ دس دس سینٹ والے نو واشر خریدتے ہیں۔ آپ کو کتنی ریزگاری واپس ملے گی؟

System.out.println(1.00 - 9 * 0.10);

اس پروگرام کے ٹکڑے کے مطابق آپ کو $0.09999999999999998 ملتے ہیں۔

آپ سوچ سکتے ہیں کہ مسئلہ صرف پرنٹ کرنے سے پہلے نتائج کو round کر کے حل ہو سکتا ہے، لیکن بدقسمتی سے یہ ہمیشہ کام نہیں کرتا۔ مثال کے طور پر، فرض کریں آپ کی جیب میں ایک ڈالر ہے، اور آپ ایک شیلف دیکھتے ہیں جس پر مزیدار ٹافیوں کی قطار ہے جن کی قیمتیں 10 سینٹ، 20 سینٹ، 30 سینٹ اور اسی طرح ایک ڈالر تک ہیں۔ آپ ہر ٹافی میں سے ایک خریدتے ہیں، 10 سینٹ والی سے شروع کرتے ہوئے، یہاں تک کہ شیلف پر اگلی ٹافی خریدنے کی استطاعت نہ رہے۔

25. boxed primitives کے بجائے primitive ٹائپس کو ترجیح دیں

// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);

26. آبجیکٹس کا حوالہ ان کے انٹرفیسز کے ذریعے دیں

اچھی مثال:

Set<Son> sonSet = new LinkedHashSet<>();

بری مثال:

LinkedHashSet<Son> sonSet = new LinkedHashSet<>();

27. reflection کے بجائے انٹرفیسز کو ترجیح دیں

ورنہ:

28. عام طور پر تسلیم شدہ نام رکھنے کے اصولوں کی پابندی کریں

Java پلیٹ فارم میں نام رکھنے کے اصولوں کا ایک مستحکم مجموعہ ہے، جن میں سے بہت سے The Java Language Specification [JLS, 6.1] میں درج ہیں۔

29. exceptions صرف غیر معمولی حالات کے لیے استعمال کریں

// Horrible abuse of exceptions. Don't ever do this!
try {
    int i = 0;
    while (true) {
        range[i++].climb();
    }
} catch (ArrayIndexOutOfBoundsException e) {
    // catch it
}

یہ کوڈ کیا کرتا ہے؟ دیکھنے سے یہ بالکل واضح نہیں، اور اسے استعمال نہ کرنے کے لیے یہی وجہ کافی ہے۔ پتا چلتا ہے کہ یہ کسی array کے عناصر پر لوپ چلانے کا ایک انتہائی بے ڈھنگا طریقہ ہے۔ لامتناہی لوپ تب ختم ہوتا ہے جب array کی حدود سے باہر پہلے عنصر تک رسائی کی کوشش پر ایک ArrayIndexOutOfBoundsException پھینکا، پکڑا اور نظر انداز کیا جاتا ہے۔ یہ array پر لوپ چلانے کے اس معیاری طریقے کے برابر ہونا چاہیے، جسے ہر Java پروگرامر فوراً پہچان لیتا ہے:

for (Mountain m : range) {
    m.climb();
}

exceptions، جیسا کہ ان کے نام سے ظاہر ہے، صرف غیر معمولی حالات کے لیے استعمال ہونی چاہییں؛ انہیں کبھی بھی عام control flow کے لیے استعمال نہیں کرنا چاہیے۔

30. قابلِ بحالی حالات کے لیے checked exceptions اور پروگرامنگ کی غلطیوں کے لیے runtime exceptions استعمال کریں

ان حالات کے لیے checked exceptions استعمال کریں جن سے کال کرنے والے کے بحال ہونے کی معقول توقع کی جا سکے۔ صارف کو checked exception کا سامنا کروا کر API ڈیزائنر اسے اس حالت سے بحال ہونے کا پابند کرتا ہے۔ صارف exception کو پکڑ کر نظر انداز کر کے اس پابندی کو نظر انداز کر سکتا ہے، لیکن یہ عموماً بری بات ہے۔

پروگرامنگ کی غلطیوں کی نشاندہی کے لیے runtime exceptions استعمال کریں۔ زیادہ تر runtime exceptions پیشگی شرائط (preconditions) کی خلاف ورزی کی نشاندہی کرتی ہیں۔ پیشگی شرط کی خلاف ورزی کا مطلب بس یہ ہے کہ کسی API کا کلائنٹ API اسپیسیفیکیشن کے طے کردہ کنٹریکٹ کی پابندی میں ناکام رہا۔ مثال کے طور پر، array تک رسائی کا کنٹریکٹ کہتا ہے کہ array انڈیکس صفر اور array کی لمبائی منفی ایک کے درمیان، دونوں سمیت، ہونا چاہیے۔ ArrayIndexOutOfBoundsException بتاتی ہے کہ اس پیشگی شرط کی خلاف ورزی ہوئی۔

31. checked exceptions کے غیر ضروری استعمال سے بچیں

خلاصہ یہ کہ کفایت سے استعمال کی جائیں تو checked exceptions پروگراموں کی وثوق پذیری بڑھا سکتی ہیں؛ حد سے زیادہ استعمال ہوں تو APIs کا استعمال تکلیف دہ بنا دیتی ہیں۔

Pair<Boolean, LearningCardGroup> answerLearningCard(@NotNull LearningCardGroup learningCardGroup,
                                                      LearningCardAssignment learningCardAssignment,
                                                      @NotNull Collection<AnswerOption> answers)
      throws InvalidTrainingStateException, PersistenceException, LearningCardGroupAlreadyCompletedException,
             ConsecutiveCorrectCountExceededException,
             PreviousLearningCardGroupUncompletedException, LearningCardGroupCooldownException,
             LearningCardGroupHasUncompletedLearningCardsException, InvalidParticipantException, TrainingNotSignedException; // but why ???
     // this is a mistake seen a lot with junior devs
     // exceptions are exceptionally slow and should be avoided for mere validation
     // the performance gets even worse if a stacktrace is printed, since logging and printing is one of the slowest possible actions

32. معیاری exceptions کے استعمال کو ترجیح دیں

ماہر پروگرامرز کو کم تجربہ کار پروگرامرز سے ممتاز کرنے والی ایک خوبی یہ ہے کہ ماہرین کوڈ کے دوبارہ استعمال کی اعلیٰ سطح کے لیے کوشش کرتے ہیں اور عموماً اسے حاصل بھی کر لیتے ہیں۔

exceptions اس اصول سے مستثنیٰ نہیں کہ کوڈ کا دوبارہ استعمال اچھی چیز ہے۔ Java لائبریریاں exceptions کا ایک مجموعہ فراہم کرتی ہیں جو زیادہ تر APIs کی exception پھینکنے کی زیادہ تر ضروریات پوری کرتا ہے۔ معیاری exceptions کو دوبارہ استعمال کرنے کے کئی فوائد ہیں۔ ان میں سب سے اہم یہ ہے کہ اس سے آپ کا API سیکھنا اور استعمال کرنا آسان ہو جاتا ہے کیونکہ یہ ان مستحکم روایات سے مطابقت رکھتا ہے جن سے پروگرامرز پہلے ہی واقف ہیں۔ اس کے بعد قریب ترین فائدہ یہ ہے کہ آپ کا API استعمال کرنے والے پروگرام پڑھنے میں آسان ہوتے ہیں کیونکہ وہ غیر مانوس exceptions سے بھرے نہیں ہوتے۔ آخر میں (اور سب سے کم اہم)، کم exception کلاسز کا مطلب ہے کم میموری کا استعمال اور کلاسز لوڈ کرنے میں کم وقت۔

سب سے زیادہ دوبارہ استعمال ہونے والی exception ٹائپ IllegalArgumentException ہے۔ عموماً یہی exception پھینکی جاتی ہے جب کال کرنے والا ایسا آرگیومنٹ بھیجے جس کی ویلیو نامناسب ہو۔ مثال کے طور پر، اگر کال کرنے والا کسی ایسے پیرامیٹر میں منفی نمبر بھیجے جو کسی عمل کے دہرائے جانے کی تعداد کو ظاہر کرتا ہو، تو یہی exception پھینکی جائے گی۔ ایک اور عام طور پر دوبارہ استعمال ہونے والی exception IllegalStateException ہے۔ عموماً یہ exception تب پھینکی جاتی ہے جب وصول کرنے والے آبجیکٹ کی اسٹیٹ کی وجہ سے کال غیر قانونی ہو۔ مثال کے طور پر، یہی exception پھینکی جائے گی اگر کال کرنے والا کسی آبجیکٹ کو اس کے درست طور پر initialize ہونے سے پہلے استعمال کرنے کی کوشش کرے۔

کہا جا سکتا ہے کہ ہر غلط میتھڈ کال آخرکار غیر قانونی آرگیومنٹ یا اسٹیٹ پر ہی منتج ہوتی ہے، لیکن کچھ مخصوص قسم کے غیر قانونی آرگیومنٹس اور اسٹیٹس کے لیے دوسری exceptions معیاری طور پر استعمال ہوتی ہیں۔ اگر کال کرنے والا کسی ایسے پیرامیٹر میں null بھیجے جس میں null ویلیوز ممنوع ہوں، تو روایت یہ ہے کہ IllegalArgumentException کے بجائے NullPointerException پھینکی جائے۔ اسی طرح، اگر کال کرنے والا کسی sequence میں انڈیکس ظاہر کرنے والے پیرامیٹر میں حد سے باہر کی ویلیو بھیجے، تو IllegalArgumentException کے بجائے IndexOutOfBoundsException پھینکی جانی چاہیے۔

Exception، RuntimeException، Throwable یا Error کو براہِ راست دوبارہ استعمال نہ کریں۔ ان کلاسز کو ایسے سمجھیں جیسے یہ abstract ہوں۔ البتہ RuntimeException استعمال کی جا سکتی ہے اگر آپ کسی checked exception کو runtime exception میں بدلنا چاہیں، اور آپ واقعی جانتے ہوں کہ آپ کیا کر رہے ہیں۔

33. exceptions کو نظر انداز نہ کریں

// Empty catch block ignores exception - Highly suspect!
try {
    ...
} catch (SomeException e) {
}

خالی catch بلاک exceptions کا مقصد ہی ختم کر دیتا ہے، جو یہ ہے کہ آپ کو غیر معمولی حالات سے نمٹنے پر مجبور کیا جائے۔ کسی exception کو نظر انداز کرنا فائر الارم کو نظر انداز کرنے جیسا ہے – اور اسے بند بھی کر دینا تاکہ کسی اور کو یہ دیکھنے کا موقع نہ ملے کہ واقعی آگ لگی ہے یا نہیں۔