Обслуговуваність булевої логіки - чи потрібно вкладати, якщо потрібні заяви?


11

Що з них краще для ремонту?

if (byteArrayVariable != null)
    if (byteArrayVariable .Length != 0)
         //Do something with byteArrayVariable 

АБО

if ((byteArrayVariable != null) && (byteArrayVariable.Length != 0))
  //Do something with byteArrayVariable 

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

Це тому, що ви покладаєтесь на мову, щоб не оцінювати другу частину, ifякщо перша частина помилкова, і не всі мови роблять це. (Друга частина буде кидати виняток, якщо оцінюватись з нулем byteArrayVariable.)

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

Дякую.


1
Перша форма має ризик зависання
JBRWilkinson

просто цікаво, якою мовою ви працюєте?
STW

@STW - ми використовуємо C # і маємо деякий застарілий код у Delphi.
Ваккано

2
добре знати. Я не знайомий з Delphi , але в C # ви можете бути впевнені в &&і ||операторів , що виконують оцінку короткого замикання. Перехід від однієї мови до іншої, як правило, має деякі проблеми, і добре бути твердим щодо того, щоб робити огляди коду, щоб допомогти вирішити ці невеликі проблеми.
STW

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

Відповіді:


30

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

Я пам'ятаю, як читати в коді, що робити такі речі погано для ремонту. Це тому, що ви покладаєтесь на мову, щоб не оцінювати другу частину, якщо перша частина помилкова, і не всі мови роблять це.

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


12
Домовились. Чому на землі я б не хвилювався, якщо мій код буде працювати однаково, коли скопійовано на іншу мову?
Тім Гудман

16

Я здивований, що ніхто не згадав про це (так що, сподіваюсь, я не неправильно читаю питання) - але обидва смоктані!

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

У цей момент ви зробите щось на кшталт:

if ((byteArrayVariable == null) || (byteArrayVariable.Length != 0)) {
  return; // nothing to be done, leaving
}

// Conditions met, do something

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

Якщо у вашому коді багато спагетті (методи луонг, багато вкладених ifs з логікою, розкиданою між ними тощо), тоді вам слід зосередитись на більшій проблемі щодо формування коду перед меншими стилістичними питаннями. Залежно від вашого оточення та тяжкості безладу, є кілька чудових інструментів, які допоможуть (наприклад, Visual Studio має функцію рефакторингу "Метод вилучення").


Як згадує Джим, ще є місце для дискусій щодо того, як структурувати достроковий вихід. Альтернативою тому, що було б вище, було б:

if (byteArrayVariable == null) 
  return; // nothing to be done, leaving

if (byteArrayVariable.Length != 0)
  return;

// Conditions met, do something

1
Я погоджуюсь із поверненням замість обговорення доброї логіки, але ті ж аргументи щодо використання || будуть використовувати ті самі аргументи як для &&.
МВС

14

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

if (byteArrayVariable == null) Exit;
if (byteArrayVariable.Length == 0) Exit;
// do something

Це дозволяє вам набагато простіше адаптувати код до нових умов

if (byteArrayVariable == null) Exit;
if (byteArrayVariable.Length == 0) Exit;
if (NewCondition == false) Exit;
if (NewerCondition == true) Exit;
// do something

2
+1 Юп, юп, юп. Логіка настільки проста, як ця не повинна спричинити складний код. Твоя кишка повинна відповідати тобі.
Робота

11

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

if ((byteArrayVariable != null) && (byteArrayVariable.Length != 0))
  //Do something with byteArrayVariable 

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

Я завжди буду підтримувати чітке повідомлення про свій намір через чиєсь дурне твердження, що "працює не на всіх мовах". Здогадайтесь, що ... throwтакож не працює на всіх мовах, чи це означає, що я не повинен використовувати його під час кодування в C # або Java, а замість цього слід покладатися на значення повернення, щоб сигналізувати, коли виникла проблема?

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


Більше двох умов можуть гарантувати власну функцію, яка повертає нуль.
Робота

3

У цьому випадку ваші два пропозиції безпосередньо пов'язані, тому 2-а форма є кращою.

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


1

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

Delphi дозволяє визначати, чи оцінюють булеві вирази чи "коротке замикання" через компілятори компілятора. Здійснення способу №1 (вкладене ifs) дозволяє переконатися, що другий пункт виконується тоді і лише тоді, коли (і строго після ) перший пункт оцінюється як істинний.


1
За 23 роки розвитку Delphi я ніколи не змінював параметр "Повний булевий евал". Якщо я посилаю код в іншому місці, я б помістив {$ BOOLEVAL OFF} або {$ B-} в одиницю.
Геррі

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

@Gerry: Погоджено. Єдина причина, по якій ви, можливо, хочете отримати повну оцінку - це побічні ефекти - а побічні ефекти в умовному випадку - це погана ідея!
Лорен Печтел

Просто перечитайте мій коментар. 23 років має бути 13! У мене немає Tardis
Геррі

1

Другий стиль може отримати зворотну реакцію в VBA, оскільки якщо перший тест - якщо об'єкт генерується, він все одно зробить другий тест і вийде з помилки. Щойно я звик в VBA, коли я мав справу з верифікацією об'єктів, щоб завжди використовувати перший метод.


2
Це тому, що VBA - жахлива мова
Falmarri

@Falmarri, у цьому є деякі жахливі речі (і деякі хороші речі). Я б поправив багато речей, якби мав свої барабани.

2
Відсутність булевої оцінки короткого замикання - скоріше спадщина, ніж усе. Поняття не маю, чому вони з цього не почали. "Виправлення" VB.NET OrElse та AndAlso начебто дратує, але, ймовірно, єдиний варіант вирішення цього питання, не порушуючи зворотної сумісності.
JohnFx

1

Друга форма є легшою для читання, особливо якщо ви використовуєте {} блоки навколо кожного пункту, оскільки перша форма призведе до вкладених {} блоків та декількох рівнів відступу, що може бути больовим для читання (ви повинні рахувати пробіли відступу щоб з'ясувати, де закінчується кожен блок).


0

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

У деяких мовах другий варіант працює краще, тому тоді був би зрозумілий вибір.


0

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

На мій погляд, небезпека першого способу це полягає в тому, що він залишає мене вразливим до випадкового введення коду поза цим вкладеним ifблоком, коли я цього не мав на увазі. Що, правда, навряд чи викликає велике занепокоєння, але здається більш розумним, ніж роздум над тим, чи мій код є мовним агностиком.


0

Я віддаю перевагу другому варіанту, тому що він легше читається.

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


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