📒 Ծրագրավորման ոճ

Դասընթացի շրջանակում մինչ այս գրած ձեր ծրագրերի ճնշող մեծամասնությունը չի գերազանցել անգամ 100 տողը։ Իրական կյանքում պատկերն այլ է։ Խոշոր ծրագրային համակարգերում կոդի տողերի քանակը կարող է հասնել հազարների, հարյուր հազարների և անգամ միլիոնների։ Այդպիսի ծրագրերը հիմնականում ստեղծվում են և անընդհատ կատարելագործվում են տասնյակ տարիների ընթացքում (օրինակ՝ windows օպերացիոն համակարգը ստեղծել է 1980-ականներին, մինչ օրս շարունակաբար կատարելագործվում է և պարունակում է մի քանի միլիոն տող կոդ)։ Այդպիսի խոշոր համակարգեր ստեղծվում են հարյուրավոր, հազարավոր մարդկանց համատեղ աշխատանքի շնորհիվ։
🤖
Կարևոր է ոչ միայն գրել կոդ, որը հասկանալի կլինի համակարգչին, այլ նաև գրել այնպիսի կոդ, որը հասկանալի կլինի այլ մարդկանց։
Ընթեռնելի կոդ գրելու գործում մեծ դեր են խաղում ֆունկցիաները։ Դուք կարող եք բաժանել ծրագիրը տրամաբանական հատվածների, այդ հատվածներին տալ իմաստալից անուններ և այդպիսով դարձնել ձեր կոդը ավելի ընթեռնելի։
Դիտարկենք հետևյալ օրինակը։ Փորձե՛ք կոդին նայելով հասկանալ, թե ի՞նչ է անում տվյալ ծրագիրը։
#include <iostream>
int main() {
     double a;
   std::cin >> a;
  double b = a * a * a;
     double c = 3.14 * b;
   double k = 4.0 * c/3.0;
   std::cout << k;
  return 0;
}
Առաջին հայացքից դժվար է հասկանալ անգամ այս փոքր ծրագրի ալգորիթմը, առավել ևս, եթե արդեն վաղուց մոռացել եք գնդի ծավալը հաշվելու բանաձևը։
 
Այժմ փորձենք լավացնել մեր ծրագրի ընթեռնելիությունը՝ օգտագործելով ֆունկցիա։
#include <iostream>
double sphereVolume(double a) {
double b = a * a * a;
     double c = 3.14 * b;
  double k = 4 * c/3;
return k; }

int main() {
   double a;
   std::cin >> a;
 double k = sphereVolume(a);
std::cout << k;
}
Բոլորդ էլ կհամաձայնեք, որ այժմ նայելով main ֆունկցիային միանգամից պարզ է դառնում, որ ծրագիրը հաշվում է գնդի ծավալը։ Ծրագիրը հասկանալու համար պարտադիր չէ անգամ հիշել, թե ի՞նչ բանաձևով է հաշվում գնդի ծավալը։ Միայն sphereVolume ֆունկցիայի անունին նայելը բավական է հասկանալու ծրագրի բուն իմաստը, կարիք չկա անգամ հասկանալու ֆունկցիայի իրականացման մանրամասները։
 
Ծրագիրը ընթեռնելի դարձնելու հաջորդ կարևոր քայլը միատեսակ ոճի պահպանումն է։ Օրինակ, ձևավոր փակագծերի շրջանակում բացատների քանակը ամեն տողում պետք է լինի հավասար։ Ֆունկցիաների և փոփոխականների անուները պետք է պահպանեն նույն ոճը։ Օրինակ, C++ լեզվում ընդունված է կիրառել այսպես կոչված camelCase ոճը, որտեղ ամեն հաջորդ բառը գրվում է մեծատառով (sphereVolume, computeResult, getValue և այլն)։
Գրեթե բոլոր ծրագրավորման լեզուների համար արդեն իսկ գոյություն ունեն ընդունված և լայնորեն կիրառվող ոճային ստանդարտներ (C++ ոճ՝ մշակված գուգլ ընկերության կողմից
Միանման և ճիշտ ոճ օգտագործելուց ծրագիրը դառնում է էլ ավելի ընթեռնելի։
#include <iostream>
double sphereVolume(double a) {
  double b = a * a * a;
  double c = 3.14 * b;
  double k = 4 * c/3;
  return k;
}

int main() {
  double a;
  std::cin >> a;
  double k = sphereVolume(a);
  std::cout << k;
}
 
Ծրագիրը ֆունկցիաների տրոհելուց և այդ ֆունկցիաներին իմաստալից անուններ տալուց բացի, պակաս կարևոր չէ նաև փոփոխականներին իմաստալից անուններ տալը։ Գրականության մեջ շառավիղը ամենուրեք նշվում է r տառով կամ radius բառով։ Եկեք փոխարինենք a փոփոխական անունը radius-ով, k փոփոխականի փոխարեն ընտրենք volume անունը։ Պարզեցնենք ծավալ հաշվելու բանաձևը։ Ավելացնենք նաև մուտքային տվյալի վավերացման կոդ։
#include <iostream>
double sphereVolume(double radius) {
  if (radius < 0) {
     std::cout << "Invalid negative radius";
     return -1;
  }
  double pi = 3.14;
  double volume = (4 * pi * radius * radius * radius ) / 3;
  return volume; 

}
int main() {
   double radius;
   std::cout << "Enter sphere radius:";
   std::cin >> radius;
   double volume = sphereVolume(radius);
   if (volume >= 0) {
     std::cout << "Sphere volume is: " << volume;
   }
}
Կարծում եմ՝ բոլորդ էլ կհամաձայնվեք, որ այժմ ծրագրի կոդը էլ ավելի ընթեռնելի և հասկանալի է։
 
Դիտարկենք ևս մեկ օրինակ։ Փորձե՛ք, կոդը կարդալով, հասկանալ, թե ի՞նչ է անում տվյալ ծրագիրը։ Այս ծրագրում պահպանված չեն ծրագրի ոճը, ամբողջ կոդը գրված է մեկ ֆունկցիայի մեջ, փոփոխականների անունները իմաստալից չեն։ Որպես արդյունք, ծրագրի կոդը գրեթե անընթեռնելի է։
#include <iostream>
#include <cmath>
int main () {
   double a, b, c;
std::cin >> a >> b >> c;
  double a1 = 0; 
    double a2 = 0;
   double k = b * b;
      double z = a * c * 4;
  double g = k - z;
  if  (g > 0){
a1 = (-b + sqrt(g)) / (2*a);
a2 = (-b - sqrt(g)) / (2*a);
       std::cout << a1 << " " << a2;
           }
     
  else
  if (g == 0) 
  {a1 = -b/(2*a);
      std::cout << a1;
  } else {
    std::cout << "No solution";
  }
}
 
Այժմ դիտարկենք նույն ծրագրի բարելավված տարբերակը։
#include <iostream>
#include <cmath>
void solveQuadtradicEquation(double a, double b, double c) {
    if (a == 0) {
      std::cout << "Invalid input, the equation is not quadratic"<<std::endl;
      return;
    }
    double discriminant = b * b - 4 * a * c; 
    if (discriminant > 0) {
       double x1 = (-b + sqrt(discriminant)) / (2*a);
       double x2 = (-b - sqrt(discriminant)) / (2*a);
       std::cout << "x1 = " << x1 << std::endl;
       std::cout << "x2 = " << x2 << std::endl;
    } else if(discriminant ==0 ) {
       double x1 = -b/(2*a);
       std::cout << "x1 = " << x1 << std::endl;
    } else {
       std::cout << "Equation has no real solutions";
    }
}

int main () {
   double a, b, c;
   std::cout << "Please enter quadratic equation coefficients a, b, and c:";
   // ax^2 + bx + c
   std::cin >> a >> b >> c;
   solveQuadtradicEquation(a, b, c);
}
Ամենուրեք պահպանված է միևնույն ոճը (բացատների քանակ, ձևավոր փակագծերի տեղակայում և այլն)։
main ֆունկցիան կարդալով միանգամից պարզ է դառնում է, որ ծրագիրը նախատեսված է քառակուսի հավասարում լուծելու համար։ Ծրագրում օգտագործված փոփոխականների անունները համընկնում են գրականության մեջ ամենուրեք ընդունված անունների հետ․քառակուսի հավասարման գործակիցները նշվում են a, b, c անուններով, իսկ քառակուսի հավասարման լուծումները նշվում են x1 և x2 անուններով։
solveQuadtradicEquation ֆունկցիայի անունից անմիջապես պարզ է դառնում ֆունկցիայի իմաստը։

Եզակի պատասխանատվության սկզբունք

Հաջորդ կարևոր սկզբունքը, որն անհրաժեշտ է կիրառել Ֆունկցիաներ գրելիս, հայտնի է եզակի պատասխանատվության սկզբունք (Single responsibility principle) անունով։ Եթե փորձեք փնտրել այս սկզբունքը համացանցում կտեսնեք, որ տարբեր կայքերում այն բացատրվում է ամենատարբեր եղանակներով և օրինակներով։ Սակայն, բոլոր դեպքերում սկզբունքի վերջնանպատակը մեկն է՝ յուրաքանչյուր ֆունկցիա պետք է ներկայացնի մեկ և միայն մեկ տրամաբանական կոդի հատված։ Այլ կերպ ասած՝ ֆունկցիան պետք է ունենա մեկ և միայն մեկ փոփոխման պատճառ կամ ֆունկցիան պետք է ունենա եզակի պատասխանատվություն։
Դիտարկենք մի քանի օրինակ՝
Առաջին օրինակում, parseTextAndContructResponse ֆունկցիան ակնհայտորեն պատասխանատու է մեկից ավել տրամաբանական գործողությունների համար։ Նախ այն մշակում է մուտքային տվյալը, ապա այդ տվյալի հիման վրա կառուցում է ինչ-որ պատասխան։
std::string parseTextAndConstructResponse (std::string input) {

   // some code to parse input data
   // some code to construct the response

}
Եզակի պատասխանատվության սկզբունքը կիրառելուց հետո, այս ֆունկցիան կտրոհվի երկու տարբեր ֆունցկիաների։
std::string parseText(std::string input) {

   // some code to parse input data
   // return parsed input text 
}

std::string constructResponse(std::string parsedInput) {
   // some code to construct the  response
   // return constructed report
}
 
Դիտարկենք ևս մեկ օրինակ՝
void applyPayment() {
   double price = /* some complex code to calculate the price, 
                   * apply individual discounts for the user,
                   * apply taxes based on country */
  
   //Some code to charge the price from the credit card. 
}
Առաջին հայացքից կարող է թվալ, որ տվյալ ֆունկցիան իսկապես ունի եզակի պատասխանատվություն, այն պատասխանատու է կրեդիտ քարտից վճարում իրականացնելու համար։ Սակայն իրականում այս ֆունկցիան նույնպես հարկավոր է տրոհել։ Պատկերացնենք իրավիճակ, երբ կազմակերպությունը, որի համար դուք գրել եք այս ծրագիրը, ինչ-որ պահի որոշում է փոփոխել իր զեղչային քաղաքականությունը։ Այդ դեպքում դուք պետք է փոփոխեք applyPayment ֆունկցիայի կոդը, որպեսզի այն համապատասխանի նոր զեղչային քաղաքականությանը։
Որոշ ժամանակ անց, կազմակերպությունը բացառիկ պայամանգիր է կնքում Visa վճարային համակարգի հետ, և այժմ հարկավոր է փոխել ծրագիրը այնպես, որ այն սպասարկի միայն Visa քարտեր։ Այս դեպքում նույնպես դուք պետք է փոփոխեք applyPayment ֆունկցիայի իրականացումը։
Այսինքն, applyPayment ֆունկցիայի կոդը փոփոխելու պատճառները մեկից ավել են, կամ որ նույնն է, ֆունկցիան պատասխանատու է մեկից ավելի տրամաբանական գործողություն կատարելու համար։ Տրոհենք ֆունկցիան այնպես, որ այն համապատասխանի եզակի պատասխանատվության սկզբունքին։
 
double calculatePrice() {
   double price = /* some complex code to calculate the price, 
                   * apply individual discounts for the user,
                   * apply taxes based on country */
   return price
}

void applyPayment(double price) {
   //Some code to charge the price from the credit card.  
}
 
Նկատեք, որ calcualtePrice ֆունկցիան պատասխանատու է միայն վճարման գնի վերջնական հաշվարկ կատարելու համար։ Այս ֆունկցիայի հետագա փոփոխության պատճառ կարող է դառնալ միայն հաշվարկի ալգորիթմի հետագա փոփոխությունը։ Նույն կերպ, applyPayment ֆունկցիան պատասխանատու է միայն կրեդիտ քարտից վճարում ապահովելու համար։
Եզակի պատասխանատվության սկզբունքը թույլ է տալիս գրել ոչ միայն ընթեռնելի կոդ, այլ նաև հեշտացնում է դրա հետագա փոփոխությունները։

Կախարդական թվեր (magic numbers)

Կոնստանտ թվերը, որոնք ուղղակիորեն օգտագործվում են կոդում, ընդունված է անվանել կախարդական թվեր։
Ընթեռնելի կոդ գրելու հաջորդ տարածված սկզբունքը, որ կդիտարկենք, կարելի է ձևակերպել հետևյալ կերպ՝
Խուսափեք կախարդական թվեր օգտագործելուց։
Սկզբունքը ավելի լավ հասկանալու համար, դիտարկենք մի քանի օրինակ՝
int main() {
   for (int i = 0; i < 12; ++i) {
       // some code here
   }
}
Կոդի այս օրինակում 12 թիվը կհամարվի կախարդական թիվ։ Կոդի այս հատվածից հասկանալի չէ ցիկլի իմաստը։
Ձևափոխենք կոդի հատվածը։ Երբ 12 թվին տալիս ենք իմաստալից անուն, կոդի հատվածին նայելուց միանգամից պարզ է դառնում, որ տվյալ կոդը աշխատում է տարվա ամիսների հետ։
int main() {
   const int MONTHS = 12; 
   for (int i = 0; i < MONTHS; ++i) {
       // some code here
   }
}
Դիտարկենք ևս մեկ օրինակ։ Կոդի այս օրինակում 52 -ը կհամարվի կախարդական թիվ։ Ինչպես և նախորդ օրինակում, այստեղ ևս հասկանալի չէ, թե ինչ է իրենից ներկայացնում 52 թիվը։
int main() {
   for (int i = 0; i < 52; ++i) {
       // some code here
   }
}
Ձևափոխենք կոդի հատվածը՝ 52 թվին տալով իմաստալից անուն։ Այժմ կոդին նայելով միանգամից հասկանալի է դառնում, որ կոդի հատվածը աշխատում է խաղաթղթերի հետ։
int main() {
   int CARD_COUNT;
   for (int i = 0; i < CARD_COUNT; ++i) {
       // some code here
   }
}
Վերանայեք մինչ այս լուծած խնդիրները։ Այդ խնդիրները լուծելիս, ամենայն հավանականությամբ, դուք օգտագործել եք կախարդական թվեր (աշտարակի բարձրությունը` Մարիոյի աշտարակ խնդրում)։

Ամփոփում

Դե ինչ, հուսով եմ բոլորդ էլ համուզվեցիք, որ ծրագրավորման ճիշտ ոճը, ֆունկցիաների ճիշտ օգտագործումը, փոփոխականներին, ֆունկցիաներին իմաստալից անուններ տալը չափազանց կարևոր են ընթեռնելի և հասկանալի կոդ գրելու համար։ Ստորև կամփոփենք մի քանի պարզ կանոններ՝
  1. Ամենուրեք օգտագործեք միևնույն ոճը։
  1. Օգտագործեք իմաստալից փոփոխականների անուններ։
  1. Խուսափեք «կախարդական թվեր» օգտագործելուց։
  1. Ֆունկցիաներ գրելիս օգտագործեք իմաստալից անուններ։ Հիշեք, որ եթե ֆունկցիայի անունը չի արտահայտում նրա իմաստը, ապա դա էլ ավելի է բարդացնում ծրագրի ընթեռնելիությունը։
  1. Ֆունկցիաները պետք է բավարարեն այսպես կոչված եզակի պատասխանատվության սկզբունքին (single responsibility principle)։ Ամեն ֆունկցիա պետք է ունենա մեկ և միայն մեկ պատասխանատվություն։
  1. Խուսափեք մեծ ֆունկցիաներ օգտագործելուց։ Եթե ֆունկցիայի մարմինը մեծ է 50-60 տողից, ապա մտածեք ֆունկցիան ավելի փոքր մասերի տրոհելու մասին. շատ հնարավոր է, որ ձեր ֆունկցիան չի համապատասխանում եզակի պատասխանատվության սկզբունքին։
  1. Եթե դուք չեք կարողանում ընտրել իմաստալից անուն ֆունկցիայի համար, ապա շատ հնարավոր է, որ ձեր ֆունկցիան չի համապատասխանում եզակի պատասխանատվության սկզբունքին։ Փորձե՛ք այն տրոհել մասերի։
 
 
To check your solution you need to sign in