Чистий код - чи слід змінити буквальний 1 на постійний?


14

Щоб уникнути магічних чисел, ми часто чуємо, що нам слід дати буквально значущу назву. Як от:

//THIS CODE COMES FROM THE CLEAN CODE BOOK
for (int j = 0; j < 34; j++) {
    s += (t[j] * 4) / 5;
}

-------------------- Change to --------------------

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

У мене такий метод манекена:

Поясніть: я припускаю, що у мене є список людей, на який потрібно обслуговувати, і за замовчуванням ми витрачаємо 5 доларів на покупку лише їжі, але коли у нас більше однієї людини, нам потрібно купувати воду та продукти харчування, ми повинні витрачати більше грошей, можливо 6 доларів. Я зміню свій код, будь ласка, зосередиться на буквальному 1 , моє запитання про нього.

public int getMoneyByPersons(){
    if(persons.size() == 1){ 
        // TODO - return money for one person
    } else {
        // TODO - calculate and return money for people.
    }

}

Коли я попросив своїх друзів переглянути мій код, один сказав, що давати ім’я для значення 1 дасть чистіший код, а другий сказав, що нам тут не потрібно постійного імені, оскільки значення має значення саме по собі.

Отже, моє запитання: чи потрібно назвати буквальне значення 1? Коли значення є магічним числом, а коли ні? Як я можу розрізнити контекст, щоб вибрати найкраще рішення?


Звідки береться personsі що в ньому описується? Ваш код не має жодних коментарів, тому важко здогадатися, що він робить.
Hubert Grzeskowiak

7
Він не стає більш чітким, ніж 1. Я б змінив "розмір" на "порахувати", хоча. А грошіОбслуговування нерозумні, як і є, вони повинні мати можливість вирішити, скільки грошей повернути залежно від колекції осіб. Тож я передавав би людям, щоб дістатиMoney, і дозволю розібратися у будь-яких виняткових випадках.
Мартін Мейт

4
Ви також можете витягнути логічний вираз із клавіші if до нового методу. Можливо, назвіть це IsSinglePerson (). Таким чином ви витягнете трохи більше, ніж просто цю змінну, але також зробите пункт if трохи читабельнішим ...
selmaohneh


2
Можливо, ця логіка буде краще підходити в moneyService.getMoney ()? Чи буде коли-небудь момент, коли вам потрібно буде зателефонувати getMoney у випадку 1 людини? Але я погодився б із загальними думками, що 1 зрозуміло. Чарівні числа - це числа, за якими вам доведеться подряпати голову, запитуючи, як програміст прийшов до цього числа .. тобтоif(getErrorCode().equals(4095)) ...
Ніл

Відповіді:


23

Ні. У цьому прикладі 1 є цілком значущим.

Однак що робити, якщо person.size () дорівнює нулю? Здається дивним, що persons.getMoney()працює для 0 і 2, але не для 1.


Я згоден, що 1 має значення. Спасибі, до речі, я оновив своє запитання. Ви можете побачити його знову, щоб отримати мою думку.
Джек

3
Фраза, яку я неодноразово чула, полягає в тому, що магічні числа - це будь-які безіменні константи, відмінні від 0 і 1. Хоча я потенційно можу придумати приклади, де ці числа повинні бути названі, це досить просте правило слідувати 99% час.
Балдрік

@Baldrickk Іноді за іменем не повинні ховатися й інші числові літерали.
Дедуплікатор

@ Дедуплікатор якісь приклади приходять вам на думку?
Балдрікк

@Baldrickk Див . Амон .
Дедуплікатор

18

Чому фрагмент коду містить саме таке буквальне значення?

  • Чи має це значення особливе значення в проблемній області ?
  • Або це значення є лише деталізацією реалізації , де це значення є прямим наслідком оточуючого коду?

Якщо буквальне значення має значення, яке не зрозуміло з контексту, тоді так, надання цього значення ім'ям через константу або змінну є корисним. Пізніше, коли початковий контекст буде забутий, код із значущими назвами змінних стане більш ретельним. Пам’ятайте, аудиторія вашого коду - це не в першу чергу компілятор (компілятор із задоволенням працюватиме з жахливим кодом), а майбутні підтримувачі цього коду - хто оцінить оцінку, якщо код дещо самостійно пояснить.

  • У першому прикладі, значення літералів , таких як 34, 4, 5не очевидно з контексту. Натомість деякі з цих значень мають особливе значення у вашій проблемній області. Тому було добре давати їм імена.

  • У вашому другому прикладі сенс буквального 1дуже зрозумілий з контексту. Введення імені не корисне.

Насправді, введення імен для очевидних значень також може бути поганим, оскільки приховує фактичне значення.

  • Це може приховати помилки, якщо назване значення було змінено або було неправильним, особливо якщо та сама змінна повторно використовується у непов'язаних фрагментах коду.

  • Елемент коду може також працювати добре для певного значення, але може бути неправильним у загальному випадку. Вводячи непотрібну абстракцію, код більше не є правильним.

Не існує обмеження розміру для "очевидних" літералів, оскільки це повністю залежить від контексту. Наприклад, літерал 1024може бути цілком очевидним у контексті обчислення розміру файлу, або літералу 31в контексті хеш-функції, або літералу padding: 0.5emв контексті таблиці стилів CSS.


8

З цим фрагментом коду є кілька проблем, які, до речі, можна скоротити так:

public List<Money> getMoneyByPersons() {
    return persons.size() == 1 ?
        moneyService.getMoneyIfHasOnePerson() :
        moneyService.getMoney(); 
}
  1. Незрозуміло, чому одна людина - особлива справа. Я припускаю, що існує певне правило бізнесу, яке говорить про те, що отримання грошей від однієї людини кардинально відрізняється від отримання грошей від кількох людей. Однак я мушу зайти і зазирнути всередину обох, getMoneyIfHasOnePersonі getMoney, сподіваючись зрозуміти, чому існують окремі випадки.

  2. Назва getMoneyIfHasOnePersonне виглядає правильно. Від імені я б очікував, що метод перевірить, чи є одна людина, і, якщо це так, отримати гроші від нього; інакше нічого не робіть. З вашого коду, це не те, що відбувається (або ви виконуєте умову двічі).

  3. Чи є причина повернути List<Money>колекцію, а не колекцію?

Поверніться до свого запитання, оскільки незрозуміло, чому існує спеціальна обробка для однієї людини, цифру слід замінити на постійну, якщо не існує іншого способу зробити правила явними. Тут одне не сильно відрізняється від будь-якого іншого магічного числа. У вас можуть бути ділові правила, які говорять про те, що спеціальний режим стосується однієї, двох або трьох осіб або лише більше ніж для дванадцяти осіб.

Як я можу розрізнити контекст, щоб вибрати найкраще рішення?

Ви робите все, що робить ваш код більш явним.

Приклад 1

Уявіть наступний фрагмент коду:

if (sequence.size() == 0) {
    return null;
}

return this.processSequence(sequence);

Чи нуль тут магічне значення? Код досить зрозумілий: якщо в послідовності немає елементів, не давайте обробляти його і повертати особливе значення. Але цей код також можна переписати так:

if (sequence.isEmpty()) {
    return null;
}

return this.processSequence(sequence);

Тут більше немає постійних, і код ще чіткіший.

Приклад 2

Візьміть ще один фрагмент коду:

const result = Math.round(input * 1000) / 1000;

Це не займе багато часу, щоб зрозуміти, що він робить у таких мовах, як JavaScript, які не мають round(value, precision)перевантаження.

Тепер, якщо ви хочете ввести константу, як би це називалося? Найближчий термін ви можете отримати Precision. Так:

const precision = 1000;
const result = Math.round(input * precision) / precision;

Чи покращує це читабельність? Це може бути. Тут значення константи досить обмежене, і ви можете запитати себе, чи дійсно вам потрібно виконати рефакторинг. Приємним є те, що зараз точність оголошується лише один раз, тому якщо вона зміниться, ви не ризикуєте зробити помилку, наприклад:

const result = Math.round(input * 100) / 1000;

змінивши значення в одному місці та забувши зробити це в іншому.

Приклад 3

З цих прикладів може скластись враження, що числа слід замінювати константами в кожному випадку . Це не правда. У деяких ситуаціях наявність константи не призводить до поліпшення коду.

Візьміть такий код:

class Point
{
    ...
    public void Reset()
    {
        x, y = (0, 0);
    }
}

Якщо ви спробуєте замінити нулі змінною, складність полягатиме в тому, щоб знайти значущу назву. Як би ви його назвали? ZeroPosition? Base? Default? Введення константи тут ні в якому разі не покращує код. Це зробило б це трохи довше, і саме так.

Однак такі випадки рідкісні. Тому будь-коли, коли ви знайдете номер у коді, докладайте зусиль, намагаючись знайти, як можна відновити код. Запитайте себе, чи є ділове значення для номера. Якщо так, константа є обов'язковою. Якщо ні, як би ви назвали номер? Якщо ви знайдете значущу назву, це чудово. Якщо ні, швидше за все, ви знайдете випадок, коли константа непотрібна.


Я додав би 3а: Не використовуйте слово гроші у назвах змінних, не використовуйте суму, баланс тощо. Колекція грошей не має сенсу, збирання сум або залишків робить. (Гаразд, у мене є невелика колекція іноземних грошей (а точніше монет), але це вже інше питання, що не програмує).
Бент

3

Ви можете зробити функцію, яка бере один параметр і повертає чотири рази, поділених на п'ять, що пропонує чистий псевдонім у тому, що він робить, використовуючи перший приклад.

Я просто пропоную свою власну звичайну стратегію, але, можливо, я теж щось навчусь.

Про що я думаю.

// Just an example name  
function normalize_task_value(task) {  
    return (task * 4) / 5;  // or to `return (task * 4) / NUMBER_OF_TASKS` but it really matters what logic you want to convey and there are reasons to many ways to make this logical. A comment would be the greatest improvement

}  

// Is it possible to just use tasks.length or something like that?  
// this NUMBER_OF_TASKS is the only one thats actually tricky to   
// understand right now how it plays its role.  

function normalize_all_task_values(tasks, accumulator) {  
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {  
        accumulator += normalize_task_value(tasks[i]);
    }
}  

Вибачте, якщо я не є базовою, але я просто розробник javascript. Я не впевнений, якою композицією я це виправдав, я думаю, не все повинно бути в масиві чи списку, але .length має багато сенсу. І * 4 зробить добру постійною, оскільки її походження є неясним.


5
Але що означають ці значення 4 і 5? Якщо одному з них колись потрібно змінитись, чи зможете ви знайти всі місця, де число використовується в подібному контексті, і оновити ці місця відповідно? У цьому полягає суть оригінального питання.
Барт ван Інген Шенау

Я думаю, що перший приклад був досить звисаючий до пояснення, так що може бути не один, на який я можу відповісти. Операція не повинна змінюватися в майбутньому, коли вам потрібно написати новий, щоб виконати те, що потрібно. Коментарі - це, на мою думку, найбільше корисне для цієї мети. Це дзвінок на одну лінію для скорочення моєї мови. Наскільки важливо зробити абсолютно зрозумілим для лайперсона?
etisdew

@BartvanIngenSchenau Вся функція неправильно названа. Я вважаю за краще кращу назву функції, коментар із специфікацією, яку функцію слід повернути, та коментар, чому * 4/5 цього домагається. Постійні імена насправді не потрібні.
gnasher729

@ gnasher729: Якщо константи відображаються рівно один раз у вихідному коді добре названої, добре задокументованої функції, то, можливо, не знадобиться використовувати названу константу. Як тільки константа з’являється кілька разів з одним і тим же значенням, давання цієї константи імені гарантує, що вам не доведеться з'ясовувати, чи означають усі ці екземпляри буквального 42 одне й те саме.
Барт ван Іґен Шенау

В основі цього, якщо ви очікуєте, вам може знадобитися змінити значення так. Я б змінив це на const, щоб представити змінну. Я не думаю, що це так, і це просто повинна бути проміжною змінною, що знаходиться в області над нею. Я не хочу копіювати і сказати зробити все параметром функції, але якщо він може змінитись, але рідко я зробив би це внутрішнім параметром тієї функції або області об’єкта / структури, де ви зараз працюєте, щоб зробити цю зміну залишаючи глобальна сфера застосування.
etisdew

2

Це число 1, чи може це бути інше число? Це може бути 2, або 3, чи є логічні причини, чому він повинен бути 1? Якщо вона повинна бути 1, то використовувати 1 - це добре. В іншому випадку можна визначити константу. Не називайте це постійним ОДНИМ. (Я бачив, що зроблено).

60 секунд за хвилину - вам потрібна константа? Ну, це 60 секунд, а не 50 або 70. І все це знають. Так що це може залишитися число.

60 елементів, надрукованих на сторінці - це число могло легко бути 59 або 55 або 70. Насправді, якщо змінити розмір шрифту, він може стати 55 або 70. Тож тут більше вимагається значуща константа.

Це також питання, наскільки зрозумілий сенс. Якщо ви пишете "хвилини = секунди / 60", це зрозуміло. Якщо ви пишете "x = y / 60", це не ясно. Десь мають бути якісь значущі імена.

Є одне абсолютне правило: немає абсолютних правил. З практикою ви зрозумієте, коли використовувати цифри та коли використовувати названі константи. Не робіть цього, тому що книга говорить так - поки ви не зрозумієте, чому це говорить.


"60 секунд за хвилину ... І всі це знають. Так що це може залишитися число". - Для мене не вагомий аргумент. Що робити, якщо в коді у мене 200 місць, де використовується "60"? Як я можу знайти місця, де він використовується як секунди до хвилини порівняно з іншими, можливо, однаково «природними» способами використання? - Однак на практиці я теж наважуюся використовувати minutes*60, іноді навіть hours*3600тоді, коли мені це потрібно, не оголошуючи додаткових констант. Цілими днями я б, напевно, писав d*24*3600або d*24*60*60тому, що 86400знаходиться близько до краю, де хтось із першого погляду не впізнає це магічне число.
JimmyB

@JimmyB Якщо ви використовуєте "60" на 200 місцях у своєму коді, дуже ймовірно, що рішення переробляє функцію, а не вводить константу.
клот

0

Я бачив неабияку кількість коду, як у (модифікованому) ОП у пошукових запитах БД. Запит повертає список, але бізнес-правила говорять, що може бути лише один елемент. І тоді, звичайно, щось змінилося для "лише цього одного випадку" до списку з більш ніж одним пунктом. (Так, я казав досить багато разів. Це майже як вони ... нм)

Тому замість того, щоб потім створювати константу, я б (у методі чистого коду) створив метод, щоб дати ім’я або уточнив, що умовне призначене для виявлення (та інкапсулювати, як він це визначає):

public int getMoneyByPersons(){
  if(isSingleDepartmentHead()){ 
    // TODO - return money for one person
  } else {
    // TODO - calculate and return money for people.
  }
}

public boolean isSingleDepartmentHead() {
   return persons.size() == 1;
}

Набагато краще уніфікувати обробку. Список n елементів може бути оброблений рівномірно незалежно від n.
Дедуплікатор

Я бачив, як це не те, де один випадок насправді був іншим (граничним) значенням, ніж це було б, якби той самий користувач був частиною списку n-розмірів. У добре виконаному ООС це може не бути проблемою, але, маючи справу зі застарілими системами, хороша реалізація ОО не завжди є здійсненною. Найкраще, що ми отримали - це розумний фасад OO за логікою DAO.
Крістіан Н
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.