ყველაზე ძვირიანი კოდი, NASA-ს სტანდარტები და რისკიანი პროგრამისტები

როვერის ნაფეხურები მარსზე, რომელიც არსაიდან იწყება.

სულ მაინტერესებდა, როგორ წერენ უზარმაზარ წარმატებულ პროგრამებს შეცდომების გარეშე, მაგალითად მარს როვერი “Curiosity”, რომელიც მარსზე გაფრინდა, 8 თვეში გაიარა 350 მილიონი მილი, დაეშვა 6 მილის რადიუსში, უცხო გარემოში დადის და დედამიწაზე გზავნის მონაცემებს.

 

როგორ გაუშვეს როვერი მარსზე

ინტერნეტში არის ერთ-ერთი უიშვიათესი ტექნიკური ხასიათის გამოსვლა მარს როვერის შესახებ. ნასას JPL – Jet Propulsion Laboratory ლაბორატორიის ერთ-ერთი მთავარი მეცნიერი, Gerard Holzmann აღწერს, თუ როგორ ცდილობდნენ პროგრამირების პროცესის წარმართვას, რომ მაქსიმალურად აეცილებინათ პრობლემები.

მოკლედ ვიტყვი რამდენიმე ფაქტს ვიდეოდან:

  • დაახლოებით 4 მილიონი ხაზი კოდი, 100-ზე მეტი მოდული, 120 ნაკადი ერთ პროცესორზე (+1 ბეკაპი). ხუთი წელი და 40 პროგრამისტი. ყველაფერს კი ერთი მომხარებლისთვის და პირველივე გამოყენებაზე უნდა ემუშავა.
  • უფრო მეტი კოდი იყო, ვიდრე მთელი წინა მარსის მისიების ერთად აღებული. ასეთი ზომის გამო ადამიანური კოდის რევიუები აღარ მუშაობდა ეფექტურად.
  • შემოიღეს რისკების შესამცირებელი სტანდარტი და მუდმივად ამოწმებდნენ ავტომატური ხელსაწყოებით. იმის გამო, რომ ადამიანები არ კითხულობდნენ და იცავდნენ დოკუმენტებს ასობით წესებით, ჩაატარეს გამოკითხვა და გამოარჩიეს ათი ყველაზე მნიშვნელოვანი წესი: Power of ten
    მაგალითად: ნუ გამოიყენებთ რეკურსიას, goto-ს და მსვლელობის სხვა რთულ კონსტრუქციებს; მონაცემების ხედვის არე (scope) იყოს მინიმალური; ყველა ციკლს ჰქონდეს ფიქსირებული საზღვრები; პოინტერის მხოლოდ ერთი განმისამართება გააკეთეთ, ფუნქციის პოინტერებს ნუ გამოიყენებთ; ა.შ.
  • ყოველ ღამე ხდებოდა კოდის დაბილდვა, სტატიკური ანალიზი და სხვადასხვა ავტომატური ტესტირება. ღამე იმიტომ, რომ 15 საათი სჭირდებოდა ანალიზს.
  • ვინც ბილდს გააფუჭებდა, დაჯარიმდებოდა და ბრიტნი სპირსის პლაკატს გამოაკრავდა თავის კუბში. ხოლო ვინც ბევრ warning-ებს დატოვებდა კოდში, “სირცხვილის კედელზე” მოხვდებოდა. როგორც ჩანს ნასაშიც კი სჭირდებათ ადამიანებს მოტივაცია წესების დასაცავად 😀
  • სპეციალური ტრენინგის და სერტიფიცირების გარეშე არ უშვებდნენ კოდთან ადამიანს.
  • ითხოვდნენ 100% code coverage-ს. ვისაც გაუკეთებია ეს, მიხვდება თუ რა შრომატევადი ამოცანაა 100%, რადგან არარსებული სცენარების გატესტვაც მოუწევთ – მაგალითად if-ების ისეთი განშტოებები, რომელიც შეუძლებელია ოდესმე შესრულდეს.
  • კოდის რევიუ არ იყო ხანგრძლივი ჯგუფური შეკრება. იკრიბებოდნენ მხოლოდ უთანხმოების განსახილველად. შენიშვნები და მითითები ერთმანეთში სპეციალური პროგრამის გამოყენებით იცვლებოდა, რომელიც კოდის სხვადასხვა სტატუსებსაც აჩვენებდა წინა ღამის შემოწმებიდან.
  • კომპილატორიდან და სტატიკური ანალიზიდან 0 warning უნდა ყოფილიყო, რაც საკმაოდ რთული შესასრულებელი აღმოჩნდა და დიდი დრო დასჭირდა. უცნობია კორელაცია ამ პუნქტის შესრულებასა და პროექტის წარმატებულობას შორის, თუმცა ეს ყველაზე სუფთა კოდი იყო, წინა მისიებთან შედარებით.
  • კრიტიკულ ნაწილებში იცავდნენ MISRA პროგრამირების სტანდარტს – რომელიც ძრავების და სასიცოცხლო მნიშვნელობის აპარატურაში გამოიყენება.
  • კრიტიკული ქვესისტემებისთვის ლოგიკურ ვერიფიკაციას აკეთებდნენ – მათემატიკურად ამტკიცებდნენ ალგორითმის სისწორეს.

ამ მისიაში დაინტერესებულებისთვის კიდევ ერთი გამოსვლა არსებობს (თუმცა მე პირველი უფრო მომეწონა): CppCon 2014: Mark Maimone “C++ on Mars: Incorporating C++ into Mars Rover Flight Software”

 

სტანდარტის დარღვევაზედ

ინტერნეტში ცნობილი შემთხვევებიდან ყველაზე ძვირიანი კოდი Space Shuttle-ისთვის დაიწერა – 1000$/ხაზი. მაგრამ 2013 წელს ტოიოტამ სასამართლო წააგო და კომპენსაციის თანხით თუ დავთვლით დაახლოებით 1200$ დაუჯდა თითო ხაზი. ტოიოტას უნებური აჩქარების პრობლემა არაერთხელ მოხვდა სიახლეებში ავტო ავარიების და საჩივრების გამო. ხან ხალიჩები გაიწვიეს უკან, ხან გაზის პედლები, მაგრამ საკმარისი არ აღმოჩნდა. მერე NASA-ს გუნდმა შეამოწმა გაზის კონტროლის პროგრამა ტოიოტას მანქანაზე და, მართალია, თავიანთ სტანდარტთან შედარებით 243 დარღვევა ნახეს, მაგრამ საბოლოოდ ვერ დაადასტურეს, რომ პროგრამის შეცდომის ბრალი იყო. სასამართლოს გარე ექსპერტად ერთი ტიპი ჰყავდა, რომელმაც მიწასთან გაასწორა ტოიოტას კოდი, უწესო რეკურსიაო, სტეკ ოვერფლოუვო და კიდევ ათასი რაღაც.

თუმცა ბოლო ბოლო მგონი მაინც არ იყო ზუსტი განაჩენი გამოტანილი პროგრამისთვის. მთლიანი ქეისი აქ არის: A Case Study of Toyota Unintended Acceleration and Software Safety

 

ჩვენ, უბრალო მოკვდავი პროგრამისტები

კონსტრუქტორის არასწორად დაწერა ჯავასკრიპტში

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

არსებობს პროგრამები, რომელსაც შეცდომა თითქმის არ მოსდის, მაგრამ თუ მოუვა უზარმაზარს ზარალს მოიტანს. ანალოგიურად არსებობს ისეთებიც, რომელშიც ხშირად ხდება შეცდომა, მაგრამ მარტივი და იაფი ჯდება მისი გასწორება. დაახლოებით ისე, როგორც მანქანებში – იშვიათად გაფუჭებული BMW-ს შეკეთება ძვირია, ამერიკას კი ომის დროს ჰყავდა Willys MB ჯიპები, რომელსაც დაზიანებისას ძალიან სწრაფად აღადგენდნენ. საერთოდ შლიდნენ და ისე გადაჰქონდათ აქეთ-იქით. ვიდეოც არის ინტერნეტში, სადაც კანადელი ჯარისკაცები 4 წუთში შლიან და აწყობენ მთელ მანქანას.

ჩვენი პროგრამების უმრავლესობაც წესით ამ მეორე კატეგორიას მიეკუთვნება. მთავარია ცვლილების შეტანა სწრაფად და იაფად შევძლოთ, თორემ ეგეთი კოდიც თუ ძვირი დაუჯდათ, აღარ დაგვიკვეთავენ 😀

DevFest 2017 გამოსვლა: Continuous Integration-Delivery-Deployment

დეველოპერების ფესტივალის გამოსვლების გამოქვეყნება დაიწყო ^_^
ჩემს გამოსვლას აქ გავაზიარებ, რომ ბლოგზე მქონდეს. მიყვარს ეს ფესტივალი და მართალია დღეების ნაცვლად ერთი თვე მოვუნდი პრეზენტაციის მომზადებას პატარას ხელში :))) მაგრამ ძალიან მინდოდა მონაწილეობის მიღება.

ესეც დემოს ლინკი გიტჰაბზე:
https://github.com/elatsoshvili/DevFestDemo2017

Integration ტესტები მონაცემთა ბაზებით (Node.js + Mocha მაგალითზე)

კოდის ავტომატური ტესტები რამდენიმე ჯგუფად იყოფა. მოკლედ რომ ვთქვათ, Unit ტესტი გამოიყენება კოდის მცირე ფრაგმენტების გასატესტად. მაგალითად, თუ გვაქვს ტელეფონის ნომრის ფორმატირების ფუნქცია, მისთვის შეიძლება რამდენიმე unit ტესტი გვქონდეს სხვადასხვა შემთხვევის დასაფარად. მაგრამ თუ გვინდა შევამოწმოთ, როგორ რეგისტრირდება მომხმარებელი ამ ტელეფონის ნომრით და შემდეგ ავტორიზაციას გადის, უკვე რამდენიმე კომპონენტის ურთიერთობა უნდა მოვაქციოთ ჩვენს ტესტში – ეს integration ტესტია.

როგორც წესი, Integration ტესტთან გვაქვს საქმე, თუ ტესტი იყენებს:

  • მონაცემთა ბაზას
  • ქსელს
  • რაიმე გარე სისტემას (მაგ: მეილ სერვერს)
  • I/O ოპერაციებს

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

 

სერვისის იმიტაცია (Stubs, Mocks)

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

function sendMail(email, content) {
    console.log(‘Email sent to: ‘ + email);
    return true;
}

როდესაც ტესტირების რეჟიმში გავუშვებთ პროგრამას, მას ნამდვილი სერვისის ობიექტის ნაცვლად ჩვენი ყალბი სერვისი უნდა გამოვაყენებინოთ (ამის დეტალებს ცალკე პოსტში შემოგთავაზებთ).

 

მონაცემთა ბაზის გამოყენება

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

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

Node-ისა და Mocha-ს მაგალითზე გავაკეთებ ამას

წინა პოსტში სხვადასხვა გარემოს დაკონფიგურირებაზე ვსაუბრობდი. Mocha-ს ტესტებს მთლად ცალკე გარემოდ არ ვთვლი, რადგან შეიძლება გვქონდეს dev და test, ან კიდევ build სერვერები და ყველა მათგანზე ვუშვებდეთ ტესტებს. თუმცა ანალოგიურ მიდგომას – environment ცვლადებს გამოვიყენებ ტესტების გარემოს დასაკონფიგურირებლადაც და შევქმნი ფაილს .env.mocha

აქვე აღვნიშნავ: dotenv-ის აღწერაში თუ შეიხედავთ, იქ მუქად წერია, რომ არ არის რეკომენდირებული გვქონდეს .env, env.test, env.prod და ასე გამოყოფილი ფაილები, არამედ მხოლოდ ერთი .env ფაილი, რომელსაც სხვადასხვა სერვერზე სხვადასხვა შიგთავსი აქვს. ჩემი აზრით .env.mocha სულ სხვა მიზანს ემსახურება და ამ წესში არ შედის.

შემდეგი ნაბიჯია, რომ ტესტების გაშვების დროს ნამდვილის ნაცვლად .env.mocha ფაილი იყოს გამოყენებული. ინტერნეტში ამის კროს-პლატფორმული მუშა მაგალითი არ არის, მე კი ვინდოუსზე მიყვარს მუშაობა. ამიტომ ჩემს ვარიანტს შემოგთავაზებთ, სადაც ყველა ტესტ ფაილში არ მოგიწევთ კონფიგურაციის ჩატვირთვა:

  • პროექტის საქაღალდეში .env-ის გვერდით შევქმნათ .env.mocha და შევავსოთ სასურველი კონფიგურაციით.
  • test საქაღალდეში შევქმნათ ფაილი setup.js და შიგნით ჩავწეროთ ეს ხაზი:
    require('dotenv').config({path:__dirname + '/../.env.mocha'});
  • test საქაღალდეში შევქმნათ კიდევ ერთი ფაილი – mocha.opts და შიგნით ჩავწეროთ:
    --require test/setup.js

სულ ეს არის.
npm test-ს რომ გავუშვებთ პროექტზე, ტესტები ავტომატურად ყველგან .env.mocha კონფიგურაციას გამოიყენებენ.

თავი რომ დავიზღვიო და დარწმუნებული ვიყო რომ mocha-ს გაშვებისას ნამდვილად შესაბამისი კონფიგურაცია არის ჩატვირთული და სამუშაო ბაზა არ წავშალო, კიდევ რაიმე ცვლადს ჩავამატებ .env.mocha ფაილში და მხოლოდ მისი არსებობის შემთხვევაში გავაგრძელებ setup.js-ის შესრულებას (მაგალითად: MOCHA_CONFIG_LOADED=yes)

მე ასევე მინდა, რომ ყოველ გაშვებაზე ცხრილები დაცარიელდეს. Mocha-ს აქვს სხვადასხვა ჰუკები, მათ შორის before(), რომელსაც თუ describe-ის შიგნით აღვწერთ, მაშინ test suite-ის გაშვების წინ შესრულდება, ხოლო თუ გლობალურად აღვწერთ, მაშინ სულ ყველა ტესტამდე ერთხელ შესრულდება. სწორად მას გამოვიყენებ, რომ ტესტირების დაწყებისას ცხრილები დავაცარიელო. კარგი იქნებოდა, რომ setup.js ფაილშივე შეიძლებოდეს მისი აღწერა, მაგრამ თუ ცდით, ნახავთ რომ იქ mocha არ არის ჯერ ჩატვირთული და before ცვლადს ვერ იპოვის. ამიტომ test საქაღალდეში დავამატე ფაილი hooks.js და ამ ფაილში აღვწერე ჩემი გლობალური ჰუკები.

თუ Integration ტესტების შესრულებას დიდი დრო მიაქვს, შესაძლებელია ისე დააკონფიგურიროთ package.json-ში სკრიპტები, რომ unit ტესტებისა და integration ტესტების გაშვება (დირექტორიის დონეზე) ცალცალკე ბრძანებით ხდებოდეს.