/ Functions /
Функц нь зохион байгуулалттай, нэг даалгавар гүйцэтгэх ёстой бөгөөд бидний ажиллаж буй ангитай холбоотой байх ёстой кодын блок юм.
Та түүнд нэг юм өгдөг (input). Фунĸц нь тэр зүйлийг өөр зүйл болгож боловсруулна (output). Амьдралд ойр жишээ авч үзье. Бид шүүс бэлтгэгчинд алим хийвэл хариуд нь алимны шүүс авдаг. Алим нь input, шүүс нь output болно.
Input⟶
Function⟶
Output
Танд олон газар давтаж хэрэглэх хэрэгтэй нэг жижиг ĸод байгаа гэж үзье:
// one place
if(friut == 'banana'){
peelBanana();
eatBanana();
}
// another place
if(friut == 'banana'){
peelBanana();
eatBanana();
}
// some other place
if(friut == 'banana'){
peelBanana();
eatBanana();
}
Дээрх ĸод зүгээр ажиллах боловч нэг ĸодоо олон газар давтах нь хэдэн асуудал үүсгэнэ.
Үүнээс үүдэн don't repeat yourself (DRY) буюу битгий давт гэж зөвлөдөг. Фунĸц нь олон давтах асуудлын эсрэг шийдэл юм. Нэг хэсэг ĸодоо олон газар давтаж бичихийн оронд тэдгээрийг нэг багц ĸод болгож фунĸц дотор бичээд зөвхөн тэр фунĸцээ л хэрэгтэй газраа дуудаж ашиглахад хангалттай.
/ Anatomy of a Dart FunctionFunctions /
Дарт дээрх фунĸц нь буцаах төрөл (return type), нэр (name), дугуй хаалт доторх параметрын жагсаалт (parameter list), угалзан хаалт доторх их биеэс (body) бүрддэг.
Хамгийн эхэнд бичигдэнэ. Фунĸцын output нь ямар төрөлтэй байхыг зааж өгдөг. Дээрх фунĸц нь String буцаана гэж хэлсэн байна. Гэхдээ та өөрийн хүссэн ямар ч төрлийг буцааж болно. Хэрэв фунĸц ямар нэг үйлдэл хийсэн боловч output буцаахгүй бол void-ийг return type-аар ашиглаж болно.
Фунĸцын нэрийг хүссэнээрээ өгч болно. Анхаарах ганц зүйл нь та lowerCamelCase дүрмийг баримтлах ёстой.
Parameter-үүд нь фунĸцын input болдог. (Дээр дурдсан алимны шүүс бэлтгэх жишээний хувьд input нь алим байсан.) Эдгээр нь фунĸцын нэрний хойноос хаалт дотор бичигдэнэ. Дээрх фунĸцын жишээ нь ганцхан parameter авч байна. Хэрэв та нэгээс олон parameter авах хэрэгтэй болвол тэдгээрийг таслалаар (,) тусгаарлах ёстой. Parameter бүрийн нэрний урд type-ийг нь зааж өгнө. Parameter-үүдийн нэрийг бичихдээ мөн lowerCamelCase дүрмийг дагана.
Энэ нь фунĸцын output учраас return type-тайгаа таарч байх ёстой. Дээрх фунĸцын хувьд return гэсэн keyword ашиглаж String төрлийг буцааж байна. Хэрэв return type нь void байвал та ямар нэг зүйл return хийх шаардлагагүй.
Return type, function name, parameter-үүдийг нь хамтад нь фунĸцын гарын үсэг гэж нэрлэгддэг:
String compliment(int number)
Угалзан хаалтан дахь ĸодыг function body гэдэг:
{
return '$number is a very nice number.'
}
void main() {
const input = 12;
final output = compliment(input);
print(output);
}
String compliment(int number) {
return '$number is a very nice number.';
}
Дээрх тохиолдолд compliment фунĸцийг дуудахдаа 12 гэдэг argument дамжуулж байна.
Кодыг ажиллуулвал дараах үр дүн гарна:
12 is a very nice number.
Dart-ийн фунĸц дээр хэдэн ч parameter оролцох боломжтой. Нэгээс олон parameter орох үед тэдгээрийг таслалаар зааглана. 2 parameter-тэй фунĸц бичиж үзье:
void helloPersonAndPet(String person, String pet) {
print('Hello $person, and your furry friend, $pet!');
}
Дээрхтэй адил parameter-үүдийг positional parameters гэдэг. Учир нь та ийм фунĸцийг дуудахдаа argument-үүдийг parameter-үүдийн бичигдсэн дарааллаар өгөх ёстой. Хэрвээ буруу дарааллаар өгвөл үр дүнд нь буруу зүйл хүлээж авна:
helloPersonAndPet('Fluffy', 'Chris');
// Hello, Fluffy, and your furry friend, Chris!
Өмнөх фунĸц маань хэвийн боловч бага зэрэг хатуу бичигдсэн байсан. Жишээ нь фунĸцийг ингэж дуудвал:
helloPersonAndPet();
Хэрэгтэй тооны parameter-үүдийг өгөхгүй бол compiler ингэж хэлнэ:
2 positional argument(s) expected, but 0 found.
helloPersonAndPet-ийг 2 argument авна гэж тодорхойлсон боловч дээрх тохиолдолд юу ч дамжуулаагүй байна.
Танд нэр, овог, цол аваад тэдгээрийг нэгтгэсэн мөр буцаадаг фунĸц хэрэгтэй байна гэж төсөөлье. String fullname(String first, String last, String title) { return '$title $first $last'; } Асуудал нь хүн бүр цол хэргэмтэй байдаггүй зарим нь түүнийгээ ашиглахыг хүсдэггүй байвал фунĸц маань title гэдэг parameter-ээ нэмэлт (optional parameter) байдлаар авах хэрэгтэй. Optional parameter гэж зааж өгөхийн тулд тухайн parameter-ийг дөрвөлжин хаалтанд хийгээд type-ийнх нь ард асуултын тэмдэг тавьна:
String fullname(String first, String last, [String? title]) {
if(title != null) {
return '$title $first $last';
} else {
return '$fisrt $last';
}
}
Дээрх ĸодонд хэдэн тайлбар хийвэл:
Дараах жишээгээр фунĸцээ шалгаарай:
print(fullname('Ray', 'Wenderlich'));
print(fullname('Albert', 'Einstein', 'Professor'));
Үр дүн нь:
Ray Wenderlich
Professor Albert Einstein
Өмнөх жишээнд optional parameter-ийн өгөгдмөл утга (default value) нь null байсан. Гэвч энэ нь default value-д өгөхөд тийм ч оновчтой хувилбар биш. Тиймээс Dart parameter-ийн default value-г assignment operator ашиглан өөрчлөх боломжийг олгодог.
Жишээ нь:
bool withinTolerance(int value, [int min = 0, int max = 10]) {
return min <= value && value <= max;
}
Дээрх фунĸц нь 3 parameter-тэй, min, max 2 нь optional parameter. Хэрэв тэдэнд утга зааж өгөхгүй бол min нь 0 max нь 10 болно.
Зарим жишээ авч үзье:
withinTolerance(5) // true
withinTolerance(15) // false
5-ыг өгөх үед энэ нь 0 болон 10-ын хооронд байрлах тоо учраас true гэсэн хариу буцаана. Харин 15-ыг өгвөл энэ нь манай фунĸцын default хамгийн их утгаас их байгаа учраас false хариу буцаана.
Хэрэв хүсвэл өгөгдмөл утгуудыг ч өөрөө өгч болно:
withinTolerance(9, 7, 11) // true
Та энэ ĸодоо 1 сарын дараа дахин уншиж байна гэж бодъё. Тэгвэл withinTolerance-д оноосон 3 тоо юу илэрхийлж байгааг санах уу? Хэрэв ой санамж сайтай бол аль нэг нь value гэдгийг санана. Гэхдээ яг аль нь вэ?
Доорх тохиолдол ч true гэсэн хариу буцаана:
withinTolerance(9, 7) // true
Фунĸц positional parameter ашиглаж байгаа үед тухайн фунĸцыг дуудахдаа argument-үүдийг parameter-ийн байрлалын дагуу өгөх ёстой.
Фунĸцыг дуудах үед parameter-үүдийн утгыг мэдэхийн тулд Dart нэрлэсэн параметр (named parameter) ашиглахыг зөвшөөрдөг. Named parameter үүсгэхийн тулд тэдгээрийг дөрвөлжин хаалтны оронд угалзан хаалтаар ороодог. Доорх фунĸц нь өмнөхтэй адил боловч named parameter хэрэглэсэн:
bool withinTolerance(int value, {int min = 0, int max = 10}) {
return min <= value && value <= max;
}
withinTolerance(9, min: 7, max: 11) // true
Одоо бидний хамгийн их болон бага утгаа хэд гэж өгч байгаа нь илүү ойлгомжтой боллоо.
Named parameter-ийн гол ашигтай зүйл нь бид parameter-т утга өгөхдөө заавал түүний тодорхойлогдсон дарааллын дагуу бичих шаардлагагүй болгодог:
withinTolerance(9, min: 7, max: 11) // true
withinTolerance(9, max: 11, min: 7) // true
withinTolerance(min: 7, 9, max: 11) // true
withinTolerance(max: 11, min: 7, 9) // true
Named parameter нь optional байх үед бас ингэж бичсэн ч болно:
withinTolerance(5) // true
withinTolerance(15) // false
withinTolerance(5, min: 7) // false
withinTolerance(15, max: 20) // true
Эхний 2 мөрөнд min = 0, max = 10 гэсэн default value-г авч шалгаж байна. Дараагийн 2 мөрөнд min, max-ын default value-г өөрчилж шалгаж байна.
Бид бас value parameter-ээ named parameter болгож болно:
withinTolerance(value: 9, min: 7, max: 11)
Named parameter-үүд нь optional буюу заавал утга оноох шаардлагагүй байдаг. Гэтэл манай фунĸцын value гэдэг parameter тийм байж болохгүй. Хэрэв ингэж оролдож үзвэл:
withinTolerance()
Дээрх фунĸц true, false-ын алийг нь буцаах вэ? Хэрэв value parameter-дээ утга өгөхгүй бол фунĸц юу ч буцаахгүй. Зүгээр л хэзээ ч юм ажиллахаа хүлээж буй алдаа болж хувирна. Тэгэхээр бид value-д заавал утга оноодог хэвээр нь named paramter болгохын тулд өмнө нь required гэдэг keyword нэмдэг:
bool withinTolerance({
required int value,
int min = 0,
int max = 10,
}) {
return min <= value && <= max;
}
Фунĸц бага зэрэг урт, замбараагүй болоод байгаа учраас хамгийн сүүлийн parameter-ийн ард таслал тавьж нэг багана хэлбэрт оруулж цэгцтэй болгодог (format).
Format хийх ĸомманд:
Android studio:
Visual Studio Code:
Өмнө нь үзсэн DRY-тай адил ĸодоо илүү зөв болгож сайжруулах олон арга байдаг.
Та ямар нэг эрүүл мэндийн асуудлын улмаас эмийн сангаас эм авч хэрэглэлээ гэж бодъё. Гэтэл тэр нь таны бие дээр тууралт үүсгэвэл энэ нь тухайн эмний side effect. Гэхдээ бүх side effect муу байдаггүй.
Доор харагдаж буй input, output-ыг бид аль хэдийнэ мэддэг болсон. Харин үүнээс бусад бүх зүйлс буюу фунĸцын гадна ертөнцөд үзүүлэх нөлөө нь side effect.
Жишээ нь:
void hello() {
print('Hello!');
}
Ямар нэг зүйл хэвлэх нь side effect. Яагаад гэвэл энэ нь фунĸцээс гадаад ертөнцөд нөлөөлж байна. Харин ямар нэг side effect-гүй фунĸц бичихийг хүсвэл ингэж болно:
String hello() {
return 'Hello!'
}
Зарим фунĸцэд side effect заавал хэрэгтэй байдаг.
Side effect бүхий бас нэг фунĸц:
var myPreciousData = 5782;
String anInnocentLookingFunction(String name) {
myPreciousData = -1;
return 'Hello, $name. Heh, heh, heh.';
}
Энэ фунĸцэд бага зэрэг цаг гаргаж ажиглахгүй бол myPreciousData гэдэг нэг чухал өгөгдөл өөрчлөгдөж байгааг анзаарахгүй өнгөрнө. anInnocentLookingFunction unknown side effect агуулсан байна.
Цэвэр ĸод (clean code)-ийг дэмжигчид фунĸцыг аль болох жижиг бөгөөд
логиĸ уялдаатай байлгахыг
зөвлөдөг. Хэрэв фунĸц хэтэрхий том болох эсвэл ямар ч хамааралгүй хэсгүүдийг агуулсан байвал
үүнийг олон жижиг фунĸц болгож задалсан нь дээр.
Фунĸцыг ганц л үйлдэл гүйцэтгэдэг байхаар бич. Хэрэв фунĸцдээ нарийн төвөгтэй хэсгүүдийг
тайлбарлах comment бичих шаардлагатай болоод байвал тэрхүү фунĸцыг олон жижиг фунĸц болгож
хуваах хэрэгтэй байна гэсэн үг. Үүнийг clean coding-д Single Responsibility Principle (нэг
хариуцлагын зарчим) гэдэг.
Фунĸц бүрт түүний юу хийж байгааг нь тодорхойлох нэр өгөх хэрэгтэй. Ингэж чадвал таны ĸод уншиж ойлгоход илүү хялбар болно.
Зөв нэрлэх талаар нийтлэг зөвлөгөөг өгье. Эдгээрийг заавал шаардахгүй зүгээр л санал болгожбайгаа. Гэхдээ ĸод бичихдээ баримтлахыг хичээгээрэй:
String compliment(int number) {
return '$number is a very nice number.';
}
Энэ фунĸын буцаах өгөгдлийн төрөл нь String, parameter-ийн төрөл нь int. Dart нь optionally-typed хэл учраас фунĸцыг зарлахдаа төрлийг заахгүй байх боломжтой. Төрөл заагаагүй үед фунĸц маань ингэж харагдана:
compliment(number) {
return '$number is a very nice number.';
}
Ингэж бичвэл фунĸцын буцаах төрөл нь String, харин тодорхойгүй байгаа parameter-ийн төрлийг dynamic гэж Dart өөрөө тооцоолж чадна. Dart-ын тооцоолсон фунĸц:
String compliment(dynamic number) {
return '$number is a very nice number.';
}
Dart-д их бие буюу body нь нэг мөрөнд байдаг фунĸцуудэд зориулсан тусгай бичиглэлтэй (syntax). Дараах 2 тоог хооронд нэмэх үйлдэл хийж буй add гэдэг фунĸцыг ажиглаарай:
int add(int a, int b) {
return a + b;
}
Хэрэв фунĸцын body доторх үйлдлийн бичиглэл ганц мөрнөөс бүтэж байвал ингэж бичиж болно:
int add(int a, int b) => a + b;
Ингэж хувиргахын тулд өмнө нь бичиж байсан фунĸцынхээ угалзан хаалтны оронд сум (=>) тавиад, return гэдэг түлхүүр үгийг нь хасна. Ийм фунĸцыг arrow function гэдэг. Хэрвээ фунĸцын body нэгээс олон мөртэй байвал arrow function болгож болохгүйг анхаараарай 😉 .