Как улучшить C.R.A.P. индекс для переключающей функции?

У меня очень типичный Переключатель типа функция, которая возвращает классификацию для данного входного значения (в данном случае индекса массы тела). (Я работаю с этой функцией, но это может быть любой другой аналогичный характер)

На данный момент это примерно так:

// ...

const TYPE_SEVERE_THINNESS = -3;
const TYPE_MODERATE_THINNESS = -2;
const TYPE_MILD_THINNESS = -1;
const TYPE_REGULAR = 0;
const TYPE_OVERWEIGHT = 1;
const TYPE_PRE_OBESE = 2;
const TYPE_OBESE_GRADE_I = 3;
const TYPE_OBESE_GRADE_II = 4;
const TYPE_OBESE_GRADE_III = 5;

// ...

public static function classification(float $bmi) : int
{
if ($bmi <= 16.00) {
return self::TYPE_SEVERE_THINNESS;
}

if ($bmi <= 16.99) {
return self::TYPE_MODERATE_THINNESS;
}

if ($bmi <= 18.49) {
return self::TYPE_MILD_THINNESS;
}

if ($bmi <= 24.99) {
return self::TYPE_REGULAR;
}

if ($bmi <= 27.49) {
return self::TYPE_OVERWEIGHT;
}

if ($bmi <= 29.99) {
return self::TYPE_PRE_OBESE;
}

if ($bmi <= 34.99) {
return self::TYPE_OBESE_GRADE_I;
}

if ($bmi <= 39.99) {
return self::TYPE_OBESE_GRADE_II;
}

if ($bmi >= 40) {
return self::TYPE_OBESE_GRADE_III;
}
}

Я собираюсь в раунд рефакторинга и думаю обо всех возможных улучшениях этой функции, особенно для снижения C.R.A.P. индекс (Изменить анти-паттерны риска) что в данный момент возвращает значение 110.00,

Конечно, может быть много возможных улучшений. Не стесняйтесь предлагать.

Но мой вопрос, в частности, о снижении циколматической сложности,

а) Есть ли другой способ структурировать этот код так, чтобы C.R.A.P. Индекс понижается?
б) Чтобы правильно протестировать эту функцию, я должен сгенерировать один тест, который утверждает каждый случай, или выполнить много тестов для рассмотрения каждого возможного случая? (Теперь я могу ответить здесь: «Это зависит от вас», но, возможно, существует лучший способ уменьшить цикломатическую сложность и, таким образом, дать место меньшему количеству тестов, которые все еще охватывают все или большинство возможных сценариев.)

Если бы мне нужно было сопоставить одинаковые значения, я бы просто использовал hashmap (массив значений ключей), но, поскольку я оцениваю диапазоны, подход может быть другим.

Обновить: После создания тестового примера с примерами каждого сценария индекс CRAP снизился до 10.01, Тем не менее, я считаю, что существует другой способ поиска значений.

/**
* Test it returns a valid WHO classification for BMI type
*
* @return void
*/
public function test_it_returns_a_valid_who_classification_for_bmi_type()
{
// Sample bmi => expected type
// Key must be a string later converted to float
$testMatrix = [
"15" => BMILevel::TYPE_SEVERE_THINNESS,
"16.5" => BMILevel::TYPE_MODERATE_THINNESS,
"18" => BMILevel::TYPE_MILD_THINNESS,
"24" => BMILevel::TYPE_REGULAR,
"27" => BMILevel::TYPE_OVERWEIGHT,
"29" => BMILevel::TYPE_PRE_OBESE,
"34" => BMILevel::TYPE_OBESE_GRADE_I,
"39" => BMILevel::TYPE_OBESE_GRADE_II,
"41" => BMILevel::TYPE_OBESE_GRADE_III,
];

foreach ($testMatrix as $bmi => $categoryCheck) {
$type = BMILevel::classification(floatval($bmi));

$this->assertEquals($type, $categoryCheck);
}
}

0

Решение

Тесты обычно позволяют отразить существующую (и, возможно, устаревшую) реализацию:

const TYPE_SEVERE_THINNESS = -3;
const TYPE_MODERATE_THINNESS = -2;
const TYPE_MILD_THINNESS = -1;
const TYPE_REGULAR = 0;
const TYPE_OVERWEIGHT = 1;
const TYPE_PRE_OBESE = 2;
const TYPE_OBESE_GRADE_I = 3;
const TYPE_OBESE_GRADE_II = 4;
const TYPE_OBESE_GRADE_III = 5;

так что вы идете сюда упорядоченный список (сверху вниз):

const TYPES = [-3, -2, -1, 0, 1, 2, 3, 4, 5];

или возможно:

const TYPES = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const TYPE_REFERENCE = 4;

и соответствующие значения (как не ключ, а не проблема с преобразованием):

const VALUES = [15, 16.5, 18, 24, 27, 29, 34, 39, 41];

и вы можете предоставить ярлыки:

const LABELS = ["Severe Thinness", "Moderate Thinness", "Mild Thinness",
"Regular", "Overweight", "Pre-Obese", "Obese Grade I",
"Obese Grade II", "Obese Grade III"];

Так что легко представить, что это больше всего не код, а данные для настройки кода. Затем модульное тестирование может проверять различные наборы данных, которые не только увеличат стабильность тестируемой системы, но также
проверить, какие расширения (изменения) легко применять с существующим кодом.

Использование провайдеров данных в тестах часто показывает, что сама кодовая база, скорее всего, должна иметь поставщики данных и не так много жесткой кодировки.

1

Другие решения

Хорошо, мне удалось получить довольно разумный C.R.A.P. индекс после некоторого рефакторинга при сохранении тестов зеленого цвета.

Я превратил функцию в поиск с верхними границами (снизу вверх). Мне нужно было добавить дополнительный случай для значений вне диапазона и покрыть этот случай.

Код:

public static function classification(float $bmi) : int
{
$classifications = [
['limit' => 16.0 , 'type' => self::TYPE_SEVERE_THINNESS],
['limit' => 16.99, 'type' => self::TYPE_MODERATE_THINNESS],
['limit' => 18.49, 'type' => self::TYPE_MILD_THINNESS],
['limit' => 24.99, 'type' => self::TYPE_REGULAR],
['limit' => 27.49, 'type' => self::TYPE_OVERWEIGHT],
['limit' => 29.99, 'type' => self::TYPE_PRE_OBESE],
['limit' => 34.99, 'type' => self::TYPE_OBESE_GRADE_I],
['limit' => 39.99, 'type' => self::TYPE_OBESE_GRADE_II],
['limit' => 60   , 'type' => self::TYPE_OBESE_GRADE_III],
];

foreach ($classifications as $classification) {
if ($bmi <= $classification['limit']) {
return $classification['type'];
}
}

return self::TYPE_OBESE_GRADE_III;
}

тесты:

/**
* Test it returns a valid WHO classification for BMI type
*
* @return void
*/
public function test_it_returns_a_valid_who_classification_for_bmi_type()
{
// Sample bmi => expected type
// Key must be a string later converted to float
$testMatrix = [
"15" => BMILevel::TYPE_SEVERE_THINNESS,
"16.5" => BMILevel::TYPE_MODERATE_THINNESS,
"18" => BMILevel::TYPE_MILD_THINNESS,
"24" => BMILevel::TYPE_REGULAR,
"27" => BMILevel::TYPE_OVERWEIGHT,
"29" => BMILevel::TYPE_PRE_OBESE,
"34" => BMILevel::TYPE_OBESE_GRADE_I,
"39" => BMILevel::TYPE_OBESE_GRADE_II,
"41" => BMILevel::TYPE_OBESE_GRADE_III,
"100" => BMILevel::TYPE_OBESE_GRADE_III, // After upper bound limit
];

foreach ($testMatrix as $bmi => $categoryCheck) {
$type = BMILevel::classification(floatval($bmi));

$this->assertEquals($type, $categoryCheck);
}
}

Советы по улучшению функции все еще приветствуются.

0

По вопросам рекламы [email protected]