Наскоро имах да направя следното, елементарно нещо, в проект:
За дадена поръчка има отстъпка в зависимост от сумата:
- до 1000 лева отстъпка 0%
- от 1000 – 3000 лева отстъпка 2%
- от 3000 – 5000 лева отстъпка 3%
- от 5000 – 10000 лева отстъпка 4 %
- над 10000 лева отстъпк 5%
Нищо сложно и преди съм правил такива неща, под 2 минути работа.
Тъй като беше края на работния ден, имах малко свободно време и си помислих, как бях написал кода за подобна задача преди време. Затова измъкнах от “прашните” архиви няколко проекта в които имаше подобни задачи и успях да синтезирам четири варианта, които съм писал преди време. Като ги видях така събрани на едно място един след друг, реших да пусна един пост, и дано някой да ги намери за полезен. И така започвам ги по ред на номерата.
Вариант 1 преди около 5 години
function getDiscountPercent($sum){
$percent = 0;
if ($sum >= 1000 && $sum < 3000){
$percent = 2;
} else if ($sum >= 3000 && $sum < 5000){
$percent = 3;
} else if ($sum >= 5000 && $sum < 10000){
$percent = 4;
} else if ($sum >= 10000){
$percent = 5;
}
return $percent;
}
Това беше в началото на моя кариера, и като цяло е много добро решение, което доста хора биха използвали. Лесно се вижда какво става в кода и е доста по-просто е от варианти 2 и 3. Общо взето само излишното дефиниране на променливата $percent е по-дразнещо в случая.
Вариант 2 преди около 3 години
function getDiscountPercent($sum){
$points = array(1000, 3000, 5000, 10000);
$percent = 0;
foreach($points as $key => $value){
if (isset($points[$key+1])){
if ($sum >= $value && $sum < $points[$key+1]){
$percent = $key + 2;
break;
}
}
}
return $percent;
}
Това несъмнено е най-лошото решение от изброените. Доста трудно ми беше да го адаптирам за дадената задача, защото в оригинал се ползваше с доста по страни връзки между цените и изглеждаше още по-зле. Много е неясно какво става, да не говорим че и е доста бавно решение. Идеята е била добра – да се автоматизира цялото нещо и само от един масив да се подават стойностите и отстъпките, но просто за такъв случаи си е чист overkill. Така че това се маркира като перфектен пример, как не се прави такъв тип задача.
Вариант 3 преди около 1 година
function getDiscountPercent($sum){
return $sum < 1000 ? 0 : ($sum < 3000 ? 2 : ($sum < 5000 ? 3 : ($sum < 10000 ? 4 : 5)));
}
Само да вметна тук, че в оригиналната за проекта версия са само четири вида отстъпки, кода който съм показал сега приспособен за горната задача. Това е бързичко и кратко решение, поне аз лесно си го чета, но надали някой друг ще има същия успех.
Поне до колкото си спомням в този случаи в началото беше с 2 вида отстъпки, после се увеличиха до горе споменатите четири вида отстъпки. Което и проблема на този вариант при повече варианти на отстъпките, става мазало.
Вариант 4 преди около 4~5 месеца
function getDiscountPercent($sum){
if ($sum < 1000){
return 0;
}
if ($sum < 3000){
return 2;
}
if ($sum < 5000){
return 3;
}
if ($sum < 10000){
return 4;
}
return 5;
}
Това решение представлява просто разпънат Вариант 3, което от своя страна си е чист Вариант 1 без излишния шум. Има два аспекта тук, което е добре да се отбележат. Първо променливата от Вариант 1 – $percent, е премахната, за сметка на това в кода в момента в който се разбере колко е отстъпката , тя се връща което прави кода много ясен и бърз. Второто нещо, го имаше във Вариант 3(просто не се виждаше), е доста по-малкия брои логически операции, докато във Вариант 1 се проверяваха всички граници тук просто се гледа горната граница. Така това е най-доброто решение до сега, но винаги може и по-добре.
Финален вариант преди около няколко дни
function getDiscountPercent($sum){
if ($sum < 1000) return 0;
if ($sum < 3000) return 2;
if ($sum < 5000) return 3;
if ($sum < 10000) return 4;
return 5;
}
Това същност е кода който написах, и който започна този пост. Като функционалност си е едно към едно с Вариант 4, единствената разлика е че съм го подредил малко по-добре(според мен), така че от пръв поглед да виждам от каква сума каква отстъпка става.
Много ми е интересно след 2 – 3 години как бих написал подобен код (да се надяваме на ruby
). Ако някой има идеи как може още по-добре да се напише това нека ги сподели.

May 21st, 2009 at 1:55 am
Въпроса е “Какво правим ако имаме точна стойност, пример : 1000 лева ?”
May 21st, 2009 at 2:36 am
Добър въпрос
Обикновено както и в случая питам клиента изрично да упомене дали границата не е 999,9 или е 1000,9, в този случай е както е написано от 1000 почва – 2%, от 3000 – 3% и … и т.н.
May 21st, 2009 at 8:18 am
Аз съм противник на всякакви твърдо зададени стойности във функциите
Аз бих направил един key=>value масив в който да са подредени стойностите така — праг=>процент – и след това ще изцикля масива отзад напред докато не намеря стойност която да е по-голяма от подадения аргумент ($sum). С такова решение може лесно да се променят праговете без да се бута кода на функцията, както и да се добавят нови прагове (стига винаги да са провилно подредени в масива)
May 21st, 2009 at 8:30 am
Освен ако няма някаква ала-хайку версия на switch (позволяваща сравнения в case-овете), не виждам как по-кратко би могло да бъде записано.
May 21st, 2009 at 10:07 am
Може да съм ръждясъл малко, но със switch/case как ще стане след като не се сравняват стойности, а интервали от стойности ?
May 21st, 2009 at 1:49 pm
По принцип и аз като видя в кода си string или number, ги гледам с известна доза недоверие. Така че може да се върна и да се пренапиша вариант 2 както казваш ( праг => процент ) и ще стане доста добре от оригиналния вариант 2, което може да е варианта след 1 – 2 месеца
А за switch в php още няма за Range проверки, както в ruby, от типа 0…999
June 19th, 2009 at 5:51 pm
За да бъдем максимално изчерпателни, трябва да включим и switch, който е доста подходящ за подобни случай. Пример:
function getDiscountPercent($sum){ switch ($sum) { case ($sum <= 1000): $discount = 0; break; case ($sum <= 3000): $discount = 2; break; case ($sum <= 5000): $discount = 3; break; case ($sum 10000): $discount = 5; break; } return $discount; }June 19th, 2009 at 11:33 pm
Не знаех, че PHP позволява такива номера със switch/case-a (то като се замисля рядко ползвам switch/case). Но, винаги е хубаво да научиш нещо ново
June 20th, 2009 at 1:08 am
последния case трябва да се коригира на case ($sum > 1000):
поздрави