Как я могу уменьшить цикломатическую сложность функции, которая возвращает значение, зависящее от декартового произведения 3 логических значений? Как я могу сделать следующий код более чистым?
Это для школьного проекта и не является обязательным требованием для самого задания, но я обычно нахожу себя пишущим функции, которые полагаются на — иногда — довольно сложную таблицу истинности. Я думаю, что это не может быть лучшим способом сделать это.
public function getDiscount( $values ) {
$res = new stdClass();
$res->code = 400;
if ( ! is_bool( $values['new_customer'] ) || ! is_bool( $values['loyalty_card'] ) || ! is_bool( $values['coupon'] ) ) {
$res->data = "Missing inputs";
return $res;
}
if ( $values['new_customer'] == true && $values['loyalty_card'] == true && $values['coupon'] == true ) {
$res->data = "Invalid input";
return $res;
}
if ( $values['new_customer'] == true && $values['loyalty_card'] == true && $values['coupon'] == false ) {
$res->data = "Invalid input";
return $res;
}
$res->code = 200;
if ( $values['new_customer'] == true && $values['loyalty_card'] == false && $values['coupon'] == true ) {
$res->data = 20;
return $res;
}
if ( $values['new_customer'] == true && $values['loyalty_card'] == false && $values['coupon'] == false ) {
$res->data = 15;
return $res;
}
if ( $values['new_customer'] == false && $values['loyalty_card'] == true && $values['coupon'] == true ) {
$res->data = 30;
return $res;
}
if ( $values['new_customer'] == false && $values['loyalty_card'] == true && $values['coupon'] == false ) {
$res->data = 10;
return $res;
}
if ( $values['new_customer'] == false && $values['loyalty_card'] == false && $values['coupon'] == true ) {
$res->data = 20;
return $res;
}
if ( $values['new_customer'] == false && $values['loyalty_card'] == false && $values['coupon'] == false ) {
$res->data = 0;
return $res;
}
$res->code = 400;
$res->data = "Invalid input";
return $res;
}
Это независимая от языка парадигма, и вы обнаружите, что разные языки будут иметь разные встроенные функции и операторы для облегчения обработки таблиц истинности.
Мой Фаворит — это новое сопоставление с образцом в операторах переключения C # 7.0 https://visualstudiomagazine.com/articles/2017/02/01/pattern-matching.aspx
Перво-наперво, определите вашу таблицу истинности
CASE New Customer Loyalty Coupon Output
1 Yes Yes Yes 'Invalid Input'
2 Yes Yes No 'Invalid Input'
3 Yes No Yes 20
4 Yes No No 15
5 No Yes Yes 30
6 No Yes No 10
7 No No Yes 20
8 No No No 0
Сразу видно, что случаи 1 и 2 не зависят от условия купона, так что это ваша первая оптимизация.
Второе условие, на которое следует обратить внимание, состоит в том, что не бывает случаев, когда новый клиент является истинным, и у него есть карта лояльности, это имеет смысл.
Отсюда проще всего визуально следовать в коде и программировать — использовать вложенное ветвление, таким образом, мы проверяем каждую проверку условий только один раз.
public function getDiscount( $values ) {
$res = new stdClass();
$res->code = 400;
if ( ! is_bool( $values['new_customer'] ) || ! is_bool( $values['loyalty_card'] ) || ! is_bool( $values['coupon'] ) ) {
$res->data = "Missing inputs";
return $res;
}
if ( $values['new_customer'] == true && $values['loyalty_card'] == true ) {
$res->data = "Invalid input";
return $res;
}
$res->code = 200;
// Check New Customer conditions
if ( $values['new_customer'] == true) {
if( $values['coupon'] == true )
$res->data = 20;
else
$res->data = 15;
}
// Check existing customer conditions
else {
// Has Loyalty Card
if ( $values['loyalty_card'] == true ) {
// Has Coupon
if( $values['coupon'] == true )
$res->data = 30;
else
$res->data = 10;
}
// No Loyalty Card
else {
// Has Coupon
if( $values['coupon'] == true )
$res->data = 20;
else
$res->data = 0;
}
}
// Don't need a default fail condition here, because we have covered all possible combinations
return $res;
}
Возможно, было бы проще использовать добавление значений, таблица истинности — хороший способ представить следующий блок кода:
CASE New Customer Loyalty Coupon Output
1 Yes Yes Yes 'Invalid Input'
2 Yes Yes No 'Invalid Input'
3 Yes +15 No Yes +5 =20
4 Yes +15 No No =15
5 No Yes +10 Yes +20 =30
6 No Yes +10 No =10
7 No No Yes +20 =20
8 No No No 0
public function getDiscount( $values ) {
$res = new stdClass();
$res->code = 400;
if ( ! is_bool( $values['new_customer'] ) || ! is_bool( $values['loyalty_card'] ) || ! is_bool( $values['coupon'] ) ) {
$res->data = "Missing inputs";
return $res;
}
if ( $values['new_customer'] == true && $values['loyalty_card'] == true ) {
$res->data = "Invalid input";
return $res;
}
$res->code = 200;
$res->data = 0;
// Check New Customer conditions
if ( $values['new_customer'] == true) {
$res->data += 15;
if( $values['coupon'] == true )
$res->data += 5;
}
// Check existing customer conditions
else {
// Has Loyalty Card
if ( $values['loyalty_card'] == true )
$res->data += 10;
// Has Coupon
if( $values['coupon'] == true )
$res->data += 20;
}
// Don't need a default fail condition here, because we have covered all possible combinations
return $res;
}
Есть много способов обрезать эту кошку 🙂 В приведенных выше примерах кода показано, как можно использовать логику ветвления для оценки каждого условия только один раз. Хотя в этом сценарии это тривиально, оценка некоторых условий в будущем может привести к серьезным последствиям для производительности, поэтому вы можете оценить ее только один раз.
Других решений пока нет …