May 21

Наскоро имах да направя следното, елементарно нещо, в проект:

За дадена поръчка има отстъпка в зависимост от сумата:

  • до 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 ;) ). Ако някой има идеи как може още по-добре да се напише това нека ги сподели.

9 коментара за "Поглед назад"

  1. Mitko Kosto каза:

    Въпроса е “Какво правим ако имаме точна стойност, пример : 1000 лева ?” :)

  2. Radoslav Stankov каза:

    Добър въпрос :)
    Обикновено както и в случая питам клиента изрично да упомене дали границата не е 999,9 или е 1000,9, в този случай е както е написано от 1000 почва – 2%, от 3000 – 3% и … и т.н.

  3. Калоян К. Цветков каза:

    Аз съм противник на всякакви твърдо зададени стойности във функциите ;) Аз бих направил един key=>value масив в който да са подредени стойностите така — праг=>процент – и след това ще изцикля масива отзад напред докато не намеря стойност която да е по-голяма от подадения аргумент ($sum). С такова решение може лесно да се променят праговете без да се бута кода на функцията, както и да се добавят нови прагове (стига винаги да са провилно подредени в масива)

  4. Иван каза:

    Освен ако няма някаква ала-хайку версия на switch (позволяваща сравнения в case-овете), не виждам как по-кратко би могло да бъде записано.

  5. Калоян К. Цветков каза:

    Може да съм ръждясъл малко, но със switch/case как ще стане след като не се сравняват стойности, а интервали от стойности ?

  6. Radoslav Stankov каза:

    По принцип и аз като видя в кода си string или number, ги гледам с известна доза недоверие. Така че може да се върна и да се пренапиша вариант 2 както казваш ( праг => процент ) и ще стане доста добре от оригиналния вариант 2, което може да е варианта след 1 – 2 месеца :)
    А за switch в php още няма за Range проверки, както в ruby, от типа 0…999

  7. Васил Даков каза:

    За да бъдем максимално изчерпателни, трябва да включим и 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;
    }
    
  8. Radoslav Stankov каза:

    Не знаех, че PHP позволява такива номера със switch/case-a (то като се замисля рядко ползвам switch/case). Но, винаги е хубаво да научиш нещо ново :)

  9. Васил Даков каза:

    последния case трябва да се коригира на case ($sum > 1000):

    поздрави :)

Какво мислите по въпроса