Правильний коментар для булевих аргументів функції, які є "помилковими"?


19

З деяких проектів з відкритим кодом я зібрав такий стиль кодування

void someFunction(bool forget);

void ourFunction() {
  someFunction(false /* forget */);
}    

Я завжди сумніваюся, що falseтут означає. Чи означає це "забути", чи "забудь" посилається на відповідний параметр (як у випадку вище), а "помилка" має намір заперечувати його?

Який стиль використовується найчастіше і який найкращий спосіб (або які кращі способи) уникнути неоднозначності?


38
використовуйте enums (навіть якщо є лише 2 варіанти) замість bools
Esailija

21
Деякі мови підтримують названі аргументи. Такими мовами ви можете користуватисяsomeFunction(forget: true);
Брайан,

3
Я відчуваю, що зобов’язаний надати аргументи Мартіна Фаулера щодо «Аргументів прапора» (також йдеться про булеві сетери). Загалом, намагайтеся їх уникати.
FGreg

3
Для відношення очевидних, коментарі можуть брехати. Таким чином, завжди краще , щоб зробити ваш код самодокументіруемим, тому що деякі з них будуть змінюватися , trueщоб falseі не оновлювати свій коментар. Якщо ви не можете змінити API, то найкращий спосіб прокоментувати цеsomeFunction( false /* true=forget, false=remember */)
Марк Лаката

1
@Bakuriu - насправді, я б, мабуть, все ще мав у публічному API два окремі методи ( sortAscendingі sortDescendingабо подібні). Тепер усередині вони можуть викликати той самий приватний метод, який може мати такий тип параметрів. Насправді, якби мова підтримувала це, можливо, те, що я передавав би, була б лямбда-функцією, яка містила напрям сортування ...
Clockwork-Muse

Відповіді:


33

У зразковому коді, який ви опублікували, він виглядає як forgetаргумент прапора. (Я не можу бути впевнений, оскільки функція суто гіпотетична.)

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

Щоб уникнути аргументу прапорця, розділіть функцію на дві функції, які пояснюють різницю в назві функції.

Аргумент прапора

serveIceCream(bool lowFat)

Немає аргументу прапор

serveTraditionalIceCream()
serveLowFatIceCream()

редагувати: В ідеалі взагалі не потрібно тримати функцію з параметром прапора. Існують випадки того, що Фоулер називає заплутану реалізацію, де повністю розділення функцій створює дублюваний код. Однак чим вища цикломатична складність параметризованої функції, тим сильнішим є аргумент для її позбавлення.


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


4
+17, шолом на. Як виглядають serveTraditionalIceCreamі serveLowFatIceCreamяк виглядають тіла ? У мене є перелік з 14 видами морозива.
JohnMark13

13
Для публічних методів ця конвенція є хорошою, але, як натякає ДжонМарк, інша половина СРП - "хороший метод повинен бути єдиним методом, який робить те, що робить". Я бачу, що існує N + 1 варіантів цього методу; N public без параметрів, усі вони викликають один приватний метод з параметром, який ви намагаєтеся уникати впливу. У якийсь момент ви просто здаєтеся і виставляєте проклятий параметр. Запахи коду не обов'язково означають, що код повинен бути відновлений; це лише речі, які заслуговують на повторний огляд коду.
KeithS

@Aaron Під "особливою заздрістю", що ти мав на увазі?
Geek

27

Словами мирянина:

  • false є буквальним.
  • ви переходите буквальне false
  • ви говорите someFunctionне забувати
  • ви говорите, someFunctionщо параметр забуття єfalse
  • ви говорите someFunctionпам'ятати

На мою думку, було б краще, якби функція була такою:

void someFunction(bool remember);

ви можете це назвати

void ourFunction() {
  someFunction(true);
} 

або збережіть стару назву, але змініть функцію обгортки на

void ourFunctionWithRemember() {
  someFunction(false);
} 

Редагувати:

Як заявив @Vorac, завжди прагніть використовувати позитивні слова. Подвійне заперечення заплутано.


15
+1 для ідеї завжди прагнути використовувати позитивні слова. Подвійне заперечення заплутано.
Vorac

1
Домовилися, чудова ідея. Говорити системі, щоб щось не робити, бентежить. Якщо ви приймаєте булевий параметр, висловіть це позитивно.
Брендон

У більшості випадків я думаю, що ви також хочете бути більш конкретними, ніж remember, якщо тільки ім'я функції не зробило значення remember дуже очевидним. rememberToCleanUp* or *persistабо щось.
йогобрюс

14

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

Використання позитивної назви змінної (запропонованої у Code Complete ) може бути найпростішою зміною, яка полегшить читання цього фрагмента, наприклад,

void someFunction(boolean remember);

потім ourFunctionстає:

void ourFunction() {
    someFunction(true /* remember */);
}

Однак використання enum робить виклик функції ще простішим для розуміння за рахунок деякого підтримуючого коду:

public enum RememberFoo {
    REMEMBER,
    FORGET
}

...

void someFunction(RememberFoo remember);

...

void ourFunction() {
    someFunction(RememberFoo.REMEMBER);
}

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

void someFunction(boolean remember);

...

void ourFunction() {
    boolean remember = false;
    someFunction(remember);
}

1
Установка rememberдля справжніх засобів забути (в вашому прикладі someFunction(true /* forget */);)?
Брайан

2
Це enum, безумовно, найкраще рішення. Тільки тому, що тип може бути представлений у вигляді, boolтобто вони ізоморфні - це не означає, що він повинен бути представлений як такий. Цей же аргумент стосується stringі навіть int.
Джон Перді

10

Перейменуйте змінну, щоб значення bool має сенс.

Це в мільйон разів краще, ніж додавати коментар для пояснення аргументів функції, оскільки назва неоднозначна.


3
Це не відповідає на запитання. Всі речі очевидні, коли метод визначений і викликається в одному файлі в 4 рядках. Але що робити, якщо все, що ви бачите на даний момент, викликає абонент? Що робити, якщо він має кілька булевих? Іноді простий внутрішній коментар проходить довгий шлях.
Брендон

@Brandon Це не аргумент проти виклику булевого doNotPersist (або, ще краще, персистського). Називати це "забути", не кажучи про те, що забути, відверто не є корисним. Ох, і метод, який сприймає кілька булів, як варіант, смердить на високе небо.
йогобрюс

5

Створіть локальний булева з більш описовою назвою та призначте їй значення. Таким чином сенс буде більш зрозумілим.

void ourFunction() {
    bool takeAction = false;  /* false means to forget */
    someFunction( takeAction );
}    

Якщо ви не можете перейменувати змінну, коментар повинен бути трохи виразнішим:

void ourFunction() {
    /* false means that the method should forget what was requested */
    someFunction( false );
}    

1
Безумовно, хороша порада, але я не думаю, що він вирішує питання, що /* forget */коментар є для вирішення, який полягає в тому, що без декларації функції прямо перед вами, може бути важко запам’ятати, що задано false. (Тому я вважаю, що порада @ Esailija додати перерахунок краще, і чому я люблю мови, які дозволяють називати параметри.)
Gort the Robot

@StevenBurnap - дякую! Ви маєте рацію в тому, що моя стара відповідь виявилася недостатньо чіткою у вирішенні питання ОП. Я відредагував це, щоб зробити це більш зрозумілим.

3

Є добра стаття, в якій згадується така точна ситуація, оскільки вона посилається на API Qt-Style. Там його називають «Лолова пара булевих параметрів», і його варто прочитати.

Суть її полягає в:

  1. Перевантаження функції так, що не потрібен bool, краще
  2. Використання переліків (як пропонує Есаїлія) найкраще

2

Це химерний коментар.

З точки зору компілятора, someFunction(false /* forget */);насправді someFunction(false);(коментар знятий). Отже, все, що робить цей рядок, - це виклик someFunctionз першим (і єдиним) аргументом, встановленим на false.

/* forget */- це лише назва параметра. Це, мабуть, не що інше, як швидке (і брудне) нагадування, що насправді не потрібно там бути. Просто використовуйте менш неоднозначне ім'я параметра, і коментар вам взагалі не знадобиться.


1

Один із порад чистого коду - мінімізувати кількість зайвих коментарів 1 (оскільки вони, як правило, гниють), і правильно називати функції та методи.

Після цього я просто видалю коментар. Зрештою, сучасні IDE (на зразок затемнення) з'являють вікно з кодом, коли ви кладете мишу над функцією. Побачення коду повинно усунути неоднозначність.


1 Коментувати складний код - це нормально.


btw Хто сказав щось подібне: "найгірша проблема програміста - як назвати змінну і відступити на одну"?
BЈович

4
Ви, ймовірно, шукаєте martinfowler.com/bliki/TwoHardThings.html як джерело цього. Я чув, що я переглянув "Є лише дві важкі речі в Комп'ютерній науці: недійсність кешу, іменування речей та вимкнення однієї помилки".

1

Для відношення очевидних, коментарі можуть брехати. Таким чином, завжди краще , щоб зробити ваш код самодокументіруемимі , не вдаючись до коментарів пояснити, бо який - то людина (можливо , ви) зміниться trueна , falseа не оновлювати свій коментар.

Якщо ви не можете змінити API, я вдаюсь до двох варіантів

  • Змініть коментар, щоб він завжди був вірним, незалежно від коду. Якщо ви зателефонували лише один раз, це хороше рішення, оскільки він зберігає документацію локально.
     someFunction (false / * true = забути, false = запам'ятати * /); `
  • Використовуйте #defines, особливо якщо ви називаєте його не раз.
     #define ЗАБЕЗПЕЧЕНО правда
     #define ПАМ'ЯТИ помилково
     деякіфункції (ПАМ'ЯТЬСЯ);

1

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

Ви повинні уникати використання літералів під час виклику методів. Локальні змінні, необов'язкові параметри, названі параметри, перерахунки - те, як ви найкраще їх уникнути, залежатиме від мови та наявних, але намагайтеся їх уникати. Літерали мають значення, але вони не мають значення.


-1

У C # я використовував названі параметри, щоб зробити це зрозумілішим

someFunction(forget: false);

Або enum:

enum Memory { Remember, Forget };

someFunction(Memory.Forget);

Або перевантаження:

someFunctionForget();

Або поліморфізм`:

var foo = new Elephantine();

foo.someFunction();

-2

Іменування завжди повинно вирішувати неоднозначність для булів. Я завжди називаю булеве щось на зразок "isThis" або "shouldDoThat", наприклад:

void printTree(Tree tree, bool shouldPrintPretty){ ... }

і так далі. Але коли ви посилаєтесь на чужий код, найкраще просто залишати коментар при передачі значень.


як це відповідає на поставлене запитання?
гнат

@gnat Я відповідав на його запитання щодо вирішення неоднозначності з булевими параметрами. Можливо, я неправильно прочитав його запитання.
дхетрі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.