CodeGym /Java Blog /यादृच्छिक /Java मध्ये रिफॅक्टरिंग कसे कार्य करते
John Squirrels
पातळी 41
San Francisco

Java मध्ये रिफॅक्टरिंग कसे कार्य करते

यादृच्छिक या ग्रुपमध्ये प्रकाशित केले
जसे तुम्ही प्रोग्रामिंग कसे करायचे ते शिकता, तुम्ही कोड लिहिण्यात बराच वेळ घालवता. बहुतेक सुरुवातीच्या विकसकांचा असा विश्वास आहे की ते भविष्यात हेच करतील. हे अंशतः खरे आहे, परंतु प्रोग्रामरच्या कामात कोड राखणे आणि रीफॅक्टर करणे देखील समाविष्ट आहे. आज आपण रिफॅक्टरिंगबद्दल बोलणार आहोत. जावामध्ये रिफॅक्टरिंग कसे कार्य करते - 1

CodeGym वर रिफॅक्टरिंग

कोडजिम कोर्समध्ये रिफॅक्टरिंग दोनदा समाविष्ट केले आहे: मोठे कार्य सरावाद्वारे वास्तविक रिफॅक्टरिंगशी परिचित होण्याची संधी प्रदान करते आणि IDEA मधील रिफॅक्टरिंगचा धडा तुम्हाला स्वयंचलित साधनांमध्ये जाण्यास मदत करतो ज्यामुळे तुमचे जीवन आश्चर्यकारकपणे सोपे होईल.

रिफॅक्टरिंग म्हणजे काय?

ते कोडची कार्यक्षमता न बदलता त्याची रचना बदलत आहे. उदाहरणार्थ, समजा आपल्याकडे एक पद्धत आहे जी 2 संख्यांची तुलना करते आणि जर पहिला मोठा असेल आणि चुकीचा असेल तर ती सत्य मिळवते अन्यथा:

    public boolean max(int a, int b) {
        if(a > b) {
            return true;
        } else if (a == b) {
            return false;
        } else {
            return false;
        }
    }
हा एक ऐवजी अनाठायी कोड आहे. अगदी नवशिक्या क्वचितच असे काहीतरी लिहितात, परंतु एक संधी आहे. if-elseजर तुम्ही 6-ओळींची पद्धत अधिक संक्षिप्तपणे लिहू शकत असाल तर ब्लॉक का वापरायचा ?

 public boolean max(int a, int b) {
      return a > b;
 }
आता आमच्याकडे एक सोपी आणि मोहक पद्धत आहे जी वरील उदाहरणाप्रमाणेच ऑपरेशन करते. रिफॅक्टरिंग अशा प्रकारे कार्य करते: तुम्ही कोडची रचना त्याच्या सारावर परिणाम न करता बदलता. रिफॅक्टरिंगच्या अनेक पद्धती आणि तंत्रे आहेत ज्यांचा आम्ही जवळून विचार करू.

तुम्हाला रिफॅक्टरिंगची गरज का आहे?

अनेक कारणे आहेत. उदाहरणार्थ, कोडमध्ये साधेपणा आणि संक्षिप्तता प्राप्त करण्यासाठी. या सिद्धांताच्या समर्थकांचा असा विश्वास आहे की कोड शक्य तितका संक्षिप्त असावा, जरी ते समजण्यासाठी अनेक डझन ओळी टिप्पण्या आवश्यक असतील. इतर डेव्हलपरना खात्री आहे की कोड कमीतकमी टिप्पण्यांसह समजण्यायोग्य बनवण्यासाठी ते रिफॅक्टर केले पाहिजे. प्रत्येक संघ स्वतःची स्थिती स्वीकारतो, परंतु लक्षात ठेवा की रिफॅक्टरिंगचा अर्थ कमी करणे नाही . कोडची रचना सुधारणे हा त्याचा मुख्य उद्देश आहे. या एकूण उद्देशामध्ये अनेक कार्ये समाविष्ट केली जाऊ शकतात:
  1. रिफॅक्टरिंग इतर विकसकांनी लिहिलेल्या कोडची समज सुधारते.
  2. हे बग शोधण्यात आणि निराकरण करण्यात मदत करते.
  3. हे सॉफ्टवेअर डेव्हलपमेंटची गती वाढवू शकते.
  4. एकूणच, हे सॉफ्टवेअर डिझाइन सुधारते.
जर रिफॅक्टरिंग बर्याच काळासाठी केले गेले नाही तर, विकासामध्ये अडचणी येऊ शकतात, ज्यामध्ये काम पूर्ण करणे देखील समाविष्ट आहे.

"कोडचा वास येतो"

जेव्हा कोडला रिफॅक्टरिंगची आवश्यकता असते तेव्हा त्याला "वास" असतो असे म्हणतात. अर्थात, शब्दशः नाही, परंतु असा कोड खरोखर फार आकर्षक दिसत नाही. खाली आम्ही सुरुवातीच्या टप्प्यासाठी मूलभूत रिफॅक्टरिंग तंत्र एक्सप्लोर करू.

अवास्तव मोठे वर्ग आणि पद्धती

वर्ग आणि पद्धती अवजड असू शकतात, त्यांच्या प्रचंड आकारामुळे प्रभावीपणे अचूकपणे कार्य करणे अशक्य आहे.

मोठा वर्ग

अशा वर्गात कोडच्या मोठ्या संख्येने ओळी आणि अनेक भिन्न पद्धती आहेत. विकासकासाठी नवीन तयार करण्याऐवजी विद्यमान वर्गामध्ये वैशिष्ट्य जोडणे सामान्यतः सोपे असते, त्यामुळे वर्ग वाढतो. नियमानुसार, अशा वर्गात खूप कार्यक्षमता असते. या प्रकरणात, कार्यक्षमतेचा काही भाग वेगळ्या वर्गात हलविण्यात मदत होते. रिफॅक्टरिंग तंत्रांवरील विभागात आम्ही याबद्दल अधिक तपशीलवार चर्चा करू.

लांब पद्धत

जेव्हा एखादा विकसक एखाद्या पद्धतीमध्ये नवीन कार्यक्षमता जोडतो तेव्हा हा "गंध" उद्भवतो: "मी येथे कोड लिहू शकलो तर मी वेगळ्या पद्धतीमध्ये पॅरामीटर का तपासावे?", "मला कमाल शोधण्यासाठी वेगळ्या शोध पद्धतीची आवश्यकता का आहे? अॅरेमधला घटक? तो इथे ठेवू या. अशा प्रकारे कोड अधिक स्पष्ट होईल", आणि असे इतर गैरसमज.

दीर्घ पद्धतीचे रीफॅक्टर करण्यासाठी दोन नियम आहेत:

  1. जर तुम्हाला एखादी पद्धत लिहिताना टिप्पणी जोडावीशी वाटत असेल, तर तुम्ही कार्यक्षमता वेगळ्या पद्धतीमध्ये ठेवावी.
  2. जर एखादी पद्धत 10-15 पेक्षा जास्त ओळी कोड घेत असेल, तर तुम्ही ती करत असलेली टास्क आणि सबटास्क ओळखा आणि सबटास्क वेगळ्या पद्धतीमध्ये ठेवण्याचा प्रयत्न करा.

लांब पद्धत दूर करण्याचे काही मार्ग आहेत:

  • पद्धतीच्या कार्यक्षमतेचा काही भाग वेगळ्या पद्धतीमध्ये हलवा
  • जर स्थानिक व्हेरिएबल्स तुम्हाला कार्यक्षमतेचा काही भाग हलवण्यापासून प्रतिबंधित करत असतील, तर तुम्ही संपूर्ण ऑब्जेक्ट दुसऱ्या पद्धतीमध्ये हलवू शकता.

बरेच आदिम डेटा प्रकार वापरणे

ही समस्या विशेषत: जेव्हा वर्गातील फील्डची संख्या कालांतराने वाढते तेव्हा उद्भवते. उदाहरणार्थ, जर तुम्ही सर्व काही (चलन, तारीख, फोन नंबर इ.) लहान वस्तूंच्या ऐवजी आदिम प्रकारात किंवा स्थिरांकांमध्ये साठवले तर. या प्रकरणात, फील्डचे तार्किक गट एका वेगळ्या वर्गात (एक्सट्रॅक्ट क्लास) हलवणे हा एक चांगला सराव असेल. डेटावर प्रक्रिया करण्यासाठी तुम्ही वर्गात पद्धती देखील जोडू शकता.

बरेच पॅरामीटर्स

ही एक सामान्य चूक आहे, विशेषत: दीर्घ पद्धतीच्या संयोजनात. सामान्यतः, जर एखाद्या पद्धतीमध्ये जास्त कार्यक्षमता असेल किंवा एखादी पद्धत एकाधिक अल्गोरिदम लागू करत असेल तर असे होते. पॅरामीटर्सच्या लांबलचक याद्या समजून घेणे खूप कठीण आहे आणि अशा याद्यांसह पद्धती वापरणे गैरसोयीचे आहे. परिणामी, संपूर्ण ऑब्जेक्ट पास करणे चांगले आहे. एखाद्या ऑब्जेक्टमध्ये पुरेसा डेटा नसल्यास, आपण अधिक सामान्य ऑब्जेक्ट वापरावे किंवा पद्धतीची कार्यक्षमता विभाजित करावी जेणेकरून प्रत्येक पद्धत तार्किकदृष्ट्या संबंधित डेटावर प्रक्रिया करेल.

डेटाचे गट

तार्किकदृष्ट्या संबंधित डेटाचे गट सहसा कोडमध्ये दिसतात. उदाहरणार्थ, डेटाबेस कनेक्शन पॅरामीटर्स (URL, वापरकर्तानाव, पासवर्ड, स्कीमा नाव इ.). फील्डच्या सूचीमधून एकही फील्ड काढता येत नसेल, तर ही फील्ड वेगळ्या क्लासमध्ये (एक्सट्रॅक्ट क्लास) हलवली पाहिजेत.

OOP तत्त्वांचे उल्लंघन करणारे उपाय

जेव्हा विकसक योग्य OOP डिझाइनचे उल्लंघन करतो तेव्हा हे "वास" येतात. जेव्हा त्याला किंवा तिला OOP क्षमता पूर्णपणे समजत नाहीत आणि त्यांचा पूर्णपणे किंवा योग्य वापर करण्यात अयशस्वी होतो तेव्हा असे घडते.

वारसा वापरण्यात अयशस्वी

जर सबक्लास पॅरेंट क्लासच्या फंक्शन्सचा फक्त एक छोटा उपसंच वापरत असेल, तर त्याला चुकीच्या पदानुक्रमाचा वास येतो. जेव्हा हे घडते, तेव्हा सामान्यतः अनावश्यक पद्धती केवळ अधिलिखित होत नाहीत किंवा ते अपवाद टाकतात. एक वर्ग दुसर्‍याचा वारसा घेतो हे सूचित करते की मूल वर्ग पालक वर्गाची जवळजवळ सर्व कार्यक्षमता वापरतो. योग्य पदानुक्रमाचे उदाहरण: Java मध्ये रिफॅक्टरिंग कसे कार्य करते - 2चुकीच्या पदानुक्रमाचे उदाहरण: Java मध्ये रिफॅक्टरिंग कसे कार्य करते - 3

स्टेटमेंट स्विच करा

विधानात चूक काय असू शकते switch? जेव्हा ते खूप गुंतागुंतीचे होते तेव्हा ते वाईट असते. संबंधित समस्या मोठ्या संख्येने नेस्टेड ifस्टेटमेंट आहे.

भिन्न इंटरफेससह पर्यायी वर्ग

अनेक वर्ग समान कार्य करतात, परंतु त्यांच्या पद्धतींना भिन्न नावे आहेत.

तात्पुरती फील्ड

जर एखाद्या वर्गामध्ये तात्पुरते फील्ड असेल ज्याची एखाद्या वस्तूची किंमत सेट केल्यावरच अधूनमधून आवश्यक असते आणि ती रिकामी असते किंवा, देवाने मना करू नये, nullउर्वरित वेळ, कोडचा वास येतो. हा एक शंकास्पद डिझाइन निर्णय आहे.

वास ज्यामुळे बदल करणे कठीण होते

हे वास अधिक गंभीर आहेत. इतर वासांमुळे मुख्यतः कोड समजणे कठिण होते, परंतु ते तुम्हाला त्यात बदल करण्यापासून प्रतिबंधित करतात. जेव्हा तुम्ही कोणतीही नवीन वैशिष्‍ट्ये सादर करण्‍याचा प्रयत्‍न करता, तेव्हा अर्धे विकसक सोडून देतात आणि अर्धे वेडे होतात.

समांतर वारसा पदानुक्रम

ही समस्या स्वतःच प्रकट होते जेव्हा वर्गाच्या उपवर्गासाठी तुम्हाला वेगळ्या वर्गासाठी दुसरा उपवर्ग तयार करावा लागतो.

एकसमान वितरीत अवलंबित्व

कोणत्याही बदलांसाठी तुम्हाला वर्गाचे सर्व उपयोग (अवलंबन) शोधणे आणि बरेच छोटे बदल करणे आवश्यक आहे. एक बदल — अनेक वर्गांमध्ये संपादने.

बदलांचे जटिल झाड

हा वास मागील एकाच्या उलट आहे: बदल एका वर्गात मोठ्या संख्येने पद्धतींवर परिणाम करतात. नियमानुसार, अशा कोडमध्ये कॅस्केडिंग अवलंबित्व असते: एक पद्धत बदलण्यासाठी आपल्याला दुसर्यामध्ये काहीतरी निश्चित करणे आवश्यक आहे आणि नंतर तिसर्यामध्ये आणि याप्रमाणे. एक वर्ग - अनेक बदल.

"कचऱ्याला वास येतो"

गंधांची एक ऐवजी अप्रिय श्रेणी ज्यामुळे डोकेदुखी होते. निरुपयोगी, अनावश्यक, जुना कोड. सुदैवाने, आधुनिक IDEs आणि linters अशा वासांबद्दल चेतावणी देण्यास शिकले आहेत.

एका पद्धतीमध्ये मोठ्या संख्येने टिप्पण्या

पद्धतीमध्ये जवळजवळ प्रत्येक ओळीवर अनेक स्पष्टीकरणात्मक टिप्पण्या असतात. हे सहसा जटिल अल्गोरिदममुळे होते, म्हणून कोडला अनेक लहान पद्धतींमध्ये विभाजित करणे आणि त्यांना स्पष्टीकरणात्मक नावे देणे चांगले आहे.

डुप्लिकेट कोड

भिन्न वर्ग किंवा पद्धती कोडचे समान ब्लॉक वापरतात.

आळशी वर्ग

वर्ग खूप कमी कार्यक्षमता घेते, जरी तो मोठा असण्याची योजना आखली गेली होती.

न वापरलेला कोड

कोडमध्ये वर्ग, पद्धत किंवा व्हेरिएबल वापरले जात नाही आणि ते मृत वजन आहे.

अत्यधिक कनेक्टिव्हिटी

गंधांची ही श्रेणी कोडमध्ये मोठ्या संख्येने अन्यायकारक संबंधांद्वारे दर्शविली जाते.

बाह्य पद्धती

पद्धत दुसर्‍या ऑब्जेक्टचा डेटा स्वतःच्या डेटापेक्षा जास्त वेळा वापरते.

अयोग्य आत्मीयता

एक वर्ग दुसऱ्या वर्गाच्या अंमलबजावणी तपशीलांवर अवलंबून असतो.

लांब वर्ग कॉल

एक वर्ग दुसऱ्याला कॉल करतो, जो तिसऱ्याकडून डेटाची विनंती करतो, जो चौथ्याकडून डेटा मिळवतो आणि असेच बरेच काही. कॉल्सच्या अशा दीर्घ साखळीचा अर्थ सध्याच्या वर्ग रचनेवर उच्च अवलंबित्व आहे.

टास्क-डीलर वर्ग

एखादे कार्य दुसऱ्या वर्गाला पाठवण्यासाठीच वर्ग आवश्यक आहे. कदाचित ते काढले पाहिजे?

रिफॅक्टरिंग तंत्र

खाली आम्ही मूलभूत रीफॅक्टरिंग तंत्रांवर चर्चा करू जे वर्णित कोड वास दूर करण्यात मदत करू शकतात.

वर्ग काढा

वर्ग खूप जास्त कार्ये करतो. त्यापैकी काहींना दुसऱ्या वर्गात हलवले पाहिजे. उदाहरणार्थ, समजा आपल्याकडे एक Humanवर्ग आहे जो घराचा पत्ता देखील संग्रहित करतो आणि पूर्ण पत्ता परत करणारी पद्धत आहे:

class Human {
    private String name;
    private String age;
    private String country;
    private String city;
    private String street;
    private String house;
    private String quarter;
 
    public String getFullAddress() {
        StringBuilder result = new StringBuilder();
        return result
                        .append(country)
                        .append(", ")
                        .append(city)
                        .append(", ")
                        .append(street)
                        .append(", ")
                        .append(house)
                        .append(" ")
                        .append(quarter).toString();
    }
 }
पत्त्याची माहिती आणि संबंधित पद्धत (डेटा प्रोसेसिंग वर्तन) वेगळ्या वर्गात ठेवणे चांगले आहे:

 class Human {
    private String name;
    private String age;
    private Address address;
 
    private String getFullAddress() {
        return address.getFullAddress();
    }
 }
 class Address {
    private String country;
    private String city;
    private String street;
    private String house;
    private String quarter;
 
    public String getFullAddress() {
        StringBuilder result = new StringBuilder();
        return result
                        .append(country)
                        .append(", ")
                        .append(city)
                        .append(", ")
                        .append(street)
                        .append(", ")
                        .append(house)
                        .append(" ")
                        .append(quarter).toString();
    }
 }

एक पद्धत काढा

जर एखाद्या पद्धतीमध्ये काही कार्यक्षमता असेल जी वेगळी केली जाऊ शकते, तर तुम्ही ती वेगळ्या पद्धतीने ठेवावी. उदाहरणार्थ, एक पद्धत जी चतुर्भुज समीकरणाच्या मुळांची गणना करते:

    public void calcQuadraticEq(double a, double b, double c) {
        double D = b * b - 4 * a * c;
        if (D > 0) {
            double x1, x2;
            x1 = (-b - Math.sqrt(D)) / (2 * a);
            x2 = (-b + Math.sqrt(D)) / (2 * a);
            System.out.println("x1 = " + x1 + ", x2 = " + x2);
        }
        else if (D == 0) {
            double x;
            x = -b / (2 * a);
            System.out.println("x = " + x);
        }
        else {
            System.out.println("Equation has no roots");
        }
    }
आम्ही तीन संभाव्य पर्यायांपैकी प्रत्येकाची स्वतंत्र पद्धतींमध्ये गणना करतो:

    public void calcQuadraticEq(double a, double b, double c) {
        double D = b * b - 4 * a * c;
        if (D > 0) {
            dGreaterThanZero(a, b, D);
        }
        else if (D == 0) {
            dEqualsZero(a, b);
        }
        else {
            dLessThanZero();
        }
    }
 
    public void dGreaterThanZero(double a, double b, double D) {
        double x1, x2;
        x1 = (-b - Math.sqrt(D)) / (2 * a);
        x2 = (-b + Math.sqrt(D)) / (2 * a);
        System.out.println("x1 = " + x1 + ", x2 = " + x2);
    }
 
    public void dEqualsZero(double a, double b) {
        double x;
        x = -b / (2 * a);
        System.out.println("x = " + x);
    }
 
    public void dLessThanZero() {
        System.out.println("Equation has no roots");
    }
प्रत्येक पद्धतीचा कोड खूपच लहान आणि समजण्यास सोपा झाला आहे.

संपूर्ण ऑब्जेक्ट पास करणे

जेव्हा पॅरामीटर्ससह पद्धत कॉल केली जाते, तेव्हा तुम्हाला काहीवेळा असा कोड दिसेल:

 public void employeeMethod(Employee employee) {
     // Some actions
     double yearlySalary = employee.getYearlySalary();
     double awards = employee.getAwards();
     double monthlySalary = getMonthlySalary(yearlySalary, awards);
     // Continue processing
 }
 
 public double getMonthlySalary(double yearlySalary, double awards) {
      return (yearlySalary + awards)/12;
 }
employeeMethodमूल्ये प्राप्त करण्यासाठी आणि त्यांना आदिम चलांमध्ये संचयित करण्यासाठी समर्पित 2 संपूर्ण ओळी आहेत . कधीकधी अशा बांधकामांना 10 ओळी लागू शकतात. ऑब्जेक्ट स्वतः पास करणे आणि आवश्यक डेटा काढण्यासाठी वापरणे खूप सोपे आहे:

 public void employeeMethod(Employee employee) {
     // Some actions
     double monthlySalary = getMonthlySalary(employee);
     // Continue processing
 }
 
 public double getMonthlySalary(Employee employee) {
     return (employee.getYearlySalary() + employee.getAwards())/12;
 }

साधे, संक्षिप्त आणि संक्षिप्त.

तार्किकदृष्ट्या फील्डचे गटबद्ध करणे आणि त्यांना वेगळे करणे classDespiteहे सत्य आहे की वरील उदाहरणे अगदी सोपी आहेत आणि जेव्हा तुम्ही ती पाहता तेव्हा तुमच्यापैकी बरेच जण विचारू शकतात, "हे कोण करते?", बरेच विकासक निष्काळजीपणामुळे अशा संरचनात्मक चुका करतात, कोड रिफॅक्टर करण्याची इच्छा नसणे किंवा फक्त "ते पुरेसे चांगले आहे" अशी वृत्ती.

रिफॅक्टरिंग प्रभावी का आहे

चांगल्या रिफॅक्टरिंगच्या परिणामी, प्रोग्राममध्ये वाचण्यास सोपा कोड आहे, त्याचे तर्कशास्त्र बदलण्याची शक्यता भयावह नाही आणि नवीन वैशिष्ट्ये सादर करणे हे कोड विश्लेषण नरक बनत नाही, परंतु त्याऐवजी काही दिवसांसाठी एक आनंददायी अनुभव आहे. . जर सुरवातीपासून प्रोग्राम लिहिणे सोपे असेल तर तुम्ही रिफॅक्टर करू नये. उदाहरणार्थ, समजा तुमच्या टीमचा असा अंदाज आहे की रीफॅक्टर कोड समजून घेण्यासाठी, विश्लेषण करण्यासाठी आणि रिफॅक्टर कोडसाठी लागणारे श्रम सुरवातीपासून समान कार्यक्षमतेची अंमलबजावणी करण्यापेक्षा जास्त असेल. किंवा रीफॅक्टर करण्‍याच्‍या कोडमध्‍ये पुष्कळ समस्‍या असल्‍यास डिबग करण्‍यास कठीण आहे. प्रोग्रामरच्या कामात कोडची रचना कशी सुधारायची हे जाणून घेणे आवश्यक आहे. आणि Java मध्ये प्रोग्राम शिकणे CodeGym वर उत्तम प्रकारे केले जाते, ऑनलाइन कोर्स जो सरावावर भर देतो. झटपट पडताळणीसह 1200+ कार्ये, सुमारे 20 लघु-प्रकल्प, गेम टास्क - हे सर्व तुम्हाला कोडिंगमध्ये आत्मविश्वास वाटण्यास मदत करेल. प्रारंभ करण्याची सर्वोत्तम वेळ ही आहे :)

रिफॅक्टरिंगमध्ये स्वतःला आणखी विसर्जित करण्यासाठी संसाधने

रिफॅक्टरिंगवरील सर्वात प्रसिद्ध पुस्तक "रिफॅक्टरिंग. इम्प्रूव्हिंग द डिझाईन ऑफ एक्सिस्टिंग कोड" हे मार्टिन फॉलरचे आहे. रिफॅक्टरिंगबद्दल एक मनोरंजक प्रकाशन देखील आहे, जो पूर्वीच्या पुस्तकावर आधारित आहे: जोशुआ केरिव्हस्कीच्या "रिफॅक्टरिंग युजिंग पॅटर्न". नमुन्यांबद्दल बोलणे... रिफॅक्टरिंग करताना, मूलभूत डिझाइन पॅटर्न जाणून घेणे नेहमीच उपयुक्त असते. ही उत्कृष्ट पुस्तके यासाठी मदत करतील: नमुन्यांबद्दल बोलणे... रिफॅक्टरिंग करताना, मूलभूत डिझाइन नमुने जाणून घेणे नेहमीच उपयुक्त असते. ही उत्कृष्ट पुस्तके यासाठी मदत करतील:
  1. हेड फर्स्ट मालिकेतील एरिक फ्रीमन, एलिझाबेथ रॉबसन, कॅथी सिएरा आणि बर्ट बेट्स यांचे "डिझाइन पॅटर्न"
  2. डस्टिन बॉसवेल आणि ट्रेव्हर फाऊचर द्वारे "वाचनीय कोडची कला".
  3. स्टीव्ह मॅककॉनेलचे "कोड पूर्ण", जे सुंदर आणि मोहक कोडसाठी तत्त्वे सेट करते.
टिप्पण्या
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION