მაღლიველი სტუდენტი და NASA-ს ჯავას სტანდარტი


წინა პოსტისთვის მასალების გადამოწმებას რომ ვცდილობდი, ნასას საიტზე სხვა საინტერესო რამეებსაც გადავაწყდი და პატარა პოსტს მივუძღვნი ბარემ 🙂

ერთი იყო ჭკუის სასწავლებელი შეცდომები არქივი, რომელსაც ჰქვია Lessons learned. და პრობლემები, სადაც ცხვირი წაიტეხეს, თავისი გადაჭრის გზებით არის აღწერილი.

მეორე იყო JPL ლაბორატორიის სტანდარტი, რომლითაც მათი ჯავა პროგრამისტები ხელმძღვანელობენ. იქ ჩამოთვლილი საკითხების უმრავლესობის წყაროდ ორი წიგნი მოეყვანათ – ჯავას ერთ-ერთი შემქმნელის – ჯოშუა ბლოხის Effective Java და იგივე ავტორის Java Puzzlers: Traps, Pitfalls, and Corner Cases.

ჩემი და ჯავას სიყვარულის ისტორია მეორე კურსიდან იწყება, როცა ორი ძალიან მაგარი ლექტორის, მიშა კაპანაძის და იოსებ ძმანაშვილის წყალობით საფუძვლიანი ჯავას და საერთოდ OOP-ის კურსი შემოგვემატა თსუ-ში (სხვა ლექტორები სხვა ჯგუფებში იყვნენ). დაახლოებით იმ პერიოდში ვკითხულობდი ჯოშუა ბლოხსაც დიდი აღფრთოვანებით, ზუსტად იმ ორ წიგნს. ჰოდა დიდი კი არაფერი, უბრალოდ აღნიშნვა მინდოდა, რომ რა მაგარ დროში ვცხოვრობთ და როგორ წაშლილია საზღვრები ინტერნეტის გამოისობით, რომ 17 წლის სტუდენტი სადღაც საქართველოში, სახლში იგივე წიგნებით “ხელმძღვანელობს” რითიც ნასას ინჟინერი. არ სჭირდება გამომცემლებისთვის ხელებში ყურება. აქვე უნდა გავიხსენო ერთი კარგი საქველმოქმედო პროექტი – “ჩართე” charte.ge , რომელიც ინტერნეტს ურთავს სოციალურად დაუცველ უფროსკლასელებს, იმიტომ რომ ჩემი აზრით ინტერნეტი ყველაზე კარგი რამ არის ადამიანის ცხოვრების შესაცვლელად. ახლა ნასაც არ არის იდეალური კომპანია (რომ გადავეკიდე), მაგრამ რაღაცებში ჩვენ კი გვისწრებენ ჯერჯერობით.

თვითონ სტანდარტს რაც შეეხება, რამდენიმე შედარებით საინტერესო შემთხვევა ამოვკრიბე (პასუხები იქვე დავმალე), დამწყებ ჯავას ხალხს შეიძლება მოეწონოს:

1. რას დაბეჭდავს მეორე ხაზი?

int i = 0; 
System.out.print(true ? 'x' : 0); // prints "x"
System.out.print(true ? 'x' : i); // prints ?

“120”
ეს იმიტომ რომ char დაყავს int-ზე. ამიტო ასეთ შემოკლებულ if ჩანაწერებში ჯობია ორივე მხარეს ერთი ტიპის ცვლადი იყოს.

2. როგორ შევადაროთ სწორად float ტიპის რიცხვები?

0.1 + 0.2 == 0.3

false
0.1 + 0.2 დაბეჭდავს 0.30000000000000004
მცოცავმძიმიან რიცხვებზე ოპერაციები ყოველთვის პრობლემაა და დიდი ისტორია აქვს, ახლა არ ჩავუღრმავდები (განსხვავებულად ინახება მეხსიერებაში). ენების უმრავლესობაში ჯობია, რომ სიზუსტე სადაც გვჭირდება long ტიპი ან შესაფერისი ბიბლიოთეკა გამოვიყენოთ. მაგალითად, ფული იყოს არა 1.43 ლარი არამედ 143 თეთრი.

თუ float ტიპის მნიშვნელობების შედარება გვჭირდება, გამოვიყენოთ პატარა ცდომილება. მაგალითად:

final double EPSILON = 0.001;
System.out.println(Math.abs((0.1 + 0.2) - 0.3) < EPSILON); // დაბეჭდავს true;

3. ვთქვათ გვაქვს ცვლადი, რომელსაც ციკლი უყურებს თავის პირობაში. ხოლო ამ ცვლადში სხვადასხვა ნაკადი წერს მნიშვნელობებს.

class Spin {
    public boolean done = false;
    public void spin() {
        while(!done){ 
	    // რაიმე ლოგიკა
        }
    }
}

რა პრობლემა აქვს ამ კოდს?

როდესაც ცვლადს რამდენიმე ნაკადი იყენებს და კოდში ის არ გვაქვს მონიშნული volatile keyword-ით, შეიძლება კომპილატორის სხვადასხვა ოპტიმიზაციების შედეგად მისი განახლება ვეღარ შეამჩნიონ ნაკადებმა. შეიძლება თითოეულმა მათგანმა ეს ცვლადი თავისთან ქეშში შეინახოს და იქიდან ჩატვირთოს ხოლმე. შედეგად უსასრულო ციკლს მივიღებთ.
სანაცვლოდ ან volatile უნდა გამოვიყენოთ ან უსასრულო ციკლის გარეშე დავწეროთ იგივე ლოგიკა ჯავას concurrent ბიბლიოთეკით.

4. ვთქვათ რიცხვის კენტობაზე შემოწმება გვსურს. რა პრობლემა აქვთ ამ პირობებს?

x % 2 == 1
x % 2 > 0

უარყოფითი რიცხვები.
ეს სწორად არ მუშაობს თუ x უარყოფითია. მაგალითად -5 % 2 არის -1.
ამიტომ ჯობია შევამოწმოთ ხოლმე ამ პირობით: x % 2 != 0 კენტობა, x % 2 == 0 ლუწობა.

გამოხმაურება

comments

4 thoughts on “მაღლიველი სტუდენტი და NASA-ს ჯავას სტანდარტი

  • 13 მარტი, 2018 at 04:28
    Permalink

    ყველა ენას აქვს ასეთი “gotcha” მომენტები. ჯავაში ის მიყვარს, რომ თუ დაფიქრდები რატომ ხდება ასე, მიხვდები ადვილად, იმიტომ რომ გააზრებული და კარგად დამუშავებული ენაა. შეადარე ჯავასკრიპტს ან პერლს. მთელი აიტი სფეროს ხუმრობების მიზანია, ისეთი აუხსნელი რამეები ხდება.

    > ეს იმიტომ რომ int დაყავს char-ზე
    პირიქით იგულისხმე მგონი.

    volatile-ს რაც შეეხება, ძალიან სუსტი დაცვაა და უმეტეს შემთხვევაში არ აკეთებს იმას, რაც სინამდვილეში გინდა. მაგისთვის შექმნეს atomic კლასები (AtomicInt, AtomicLong, etc) და ჯობია ყოველთვის ისინი გამოიყენო volatile-ის ნაცვლად.

    Reply
    • 13 მარტი, 2018 at 08:27
      Permalink

      დიდი მადლობა 🙂 სწორი შენიშვნაა, პირიქით უნდა დამეწერა char-ზე და ჩავასწორე.
      მეც იგივე აზრზე ვარ, რომ მშვენივრად დალაგებულია ენა და ფაქტიურად ყველაფერი ლოგიკურად აიხსნება.

      volatile-ს რაც შეეხება, როგორც მესმის იგივეს არ აკეთებს რასაც atomic კლასები. უბრალოდ უზრუნველყოფს რომ არ დაკეშირდეს ცვლადი და ნაკადები ახალ მნიშვნელობას ხედავდნენ. მარტო წაკითხვა არის ატომური, სხვა არაფერი და ატომური ოპერაციებისთვის არ გამოდგება. საერთოდ flag ტიპის ცვლადების გარდა ამის გამოყენება არ შემხვედრია.

      Reply
      • 17 ივლისი, 2018 at 15:28
        Permalink

        იდეაში, წაკითხვაც და ჩაწერაც, ცალცალკე, ატომური ოპერაციებია, volatile-ის შემთხვევაში პრობლემა სხვა რაღაცაშია. მაგალითად ინკრიმენტი არის ორი ოპერაცია: ძველი მნიშვნელობის წაკითხვა და ახალი მნიშვნელობის ჩაწერა. ამ ორ ოპერაციას შორის, volatile-ის შემთხვევაში, შეიძლება ცვლადის მნიშვნელობა უკვე სხვა ნაკადს ჰქონდეს შეცვლილი და საბოლოოდ არასწორი შედეგი მივიღოთ. მაგალითად ორ ნაკადში ვაინკრემენტებთ x ცვლადს, x++ საშუალებით, თეორიულად შესაძლებელია მოხდეს შემდეგი რამ:

        > volatile int x = 0
        > ნაკადი 1: წაიკითხა x = 0
        > სანამ პირველმა ნაკადმა ჩაწერა გაზრდილი მნიშვნელობა, მეორე ნაკადმაც წაიკითხა, რომ x = 0
        > ორივე ნაკადი ინკრემენტს უკეთებს 0-ს და x-ს ანიჭებს მნიშვნელობას 1, ჩვენი მოლოდინით კი x უნდა ყოფილიყო 2.

        ამის თავიდან ასაცილებლად საჭირო იქნებოდა synchronized ბლოკის გამოყენება. Atomic ცვლადები კი შეიძლება ითქვას, რომ volatile-იც არის და synchronized-იც. მაგალითად AtomicInteger-ს თუ ვეტყვით incrementAndGet() ინკრემენტაცია ერთ ოპერაციაში მოხდება და დარწმუნებული ვიქნებით, რომ ოპერაციის შესრულებამდე სხვა ნაკადი ცვლადის მნიშვნელობას ვერ შეცვლის. პროცესორებს აქვთ ატომური ოპერაციების მხარდაჭერა, რომლებსაც არ აქვთ, იმათთვის JVM თავისით აგვარებს, რომ სინქრონიზებული იყოს Atomic ცვლადებზე ოპერაციები.

        Reply
        • 29 ივლისი, 2018 at 23:19
          Permalink

          მადლობა კომენტარისთვის.

          მესმის კი 🙂 ზუსტად მაგას ვგულისხმობ მეც, რომ Atomic და volatile სულ სხვადასხვა დანიშნულებით გამოიყენება. volatile ორ-ნაბიჯიან ოპერაციას არ იცავს. ერთადერთი რისი გაკეთებაც შეუძლია, არის რომ არ დაკეშირდეს ცვლადის მნიშვნელობა და ყველა ნაკადი მის ახალ ვერსიას ხედავდეს (იმიტომ რომ ისე კეშირება ხდება ცვლადების).

          ‘მხოლოდ წაკითხვა არის ატომური’ ამაში ვგულისხმობ, რომ 32 ბიტზე დიდი ტიპები რაც არის, მაგალითად long და double, ერთიანად არ იკითხება. შეიძლება პირველი 32 ბიტი წაიკითხოს, მერე რამე ნაკადმა შეცვალოს მნიშვნელობა და ამან მეორე 32 ბიტი უკვე სხვა რაღაცისა აიღოს. volatile-ის დროს ამის დაზღვევა ხდება, რომ ატომურად მოხდეს მნიშვნელობის წაკითხვა.

          ინკრემენტს და სხვა მსგავს ოპერაციებს ვერ გასწვდება და სინქრონიზაცია ან Atomic იქნება ნამდვილად საჭირო. volatile მე მხოლოდ flag ტიპის ცვლადებზე შემხვედრია, სხვაგან არ ვიცი სად იყენებენ.

          Reply

გაიარეთ ავტორიზაცია კომენტარის დასამატებლად: admin კომენტარის გაუქმება

თქვენი ელფოსტის მისამართი გამოქვეყნებული არ იყო. აუცილებელი ველები მონიშნულია *