Чи подібний код - «аварія поїзда» (з порушенням Закону про Деметер)?


23

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

service.setLocation(this.configuration.getLocation().toString());

У цьому випадку serviceє змінною екземпляра відомого типу, оголошеною в межах методу. this.configurationпоходить від передачі до конструктора класів і є екземпляром класу, що реалізує певний інтерфейс (який мандатує публічний getLocation()метод). Отже, this.configuration.getLocation()відомий зворотний тип виразу ; конкретно в цьому випадку він є java.net.URL, тоді як service.setLocation()хоче a String. Оскільки два типи String і URL не є безпосередньо сумісними, для встановлення квадратного кілка в круглий отвір потрібне певне перетворення.

Однак , в Відповідно до Закону про Деметрі , як цитується в чистому кодексі , метод F в класі C повинен викликати тільки методи на C , об'єкти , створені або передаються в якості аргументів е , і об'єкти проведені в змінних екземпляра C . Все, що є поза цим (остаточне toString()в моєму конкретному випадку вище, якщо ви не вважаєте тимчасовим об'єктом, створеним в результаті виклику методу, і в цьому випадку весь Закон здається суперечливим) заборонено.

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

Якби я реалізувати метод , URLToString()який просто викликає toString()на URLоб'єкт (наприклад, що повертається getLocation()) , переданого їй у якості параметра, і повертає результат, я міг би обернути getLocation()виклик в ній для досягнення точно такий же результат; Ефективно, я просто переміщу конверсію на крок назовні. Це зробило б це якось прийнятним? (Мені здається , інтуїтивно зрозуміло, що це не має жодних змін і в іншому випадку, оскільки все, що робиться, - це трохи перемістити речі. Однак, йдучи за цитатою літери Закону про Деметер, це було б прийнятно, оскільки я Тоді він буде працювати безпосередньо над параметром функції.)

Чи має щось значення, якби мова йшла про щось дещо екзотичніше, ніж запрошення toString()стандартного типу?

Відповідаючи, майте на увазі, що зміна поведінки або API типу, яким є serviceзмінна, не є практичним. Також, для аргументації, скажімо, що зміна типу повернення getLocation()також недоцільно.

Відповіді:


34

Проблема тут - підпис setLocation. Це строго набрано .

Для уточнення: навіщо це очікувати String? A Stringявляє собою будь-який вид текстових даних . Потенційно це може бути будь-що, крім дійсного місця.

Насправді це ставить питання: що таке місцезнаходження? Як мені знати, не дивлячись на ваш код? Якби це було, URLя б знав набагато більше про те, що очікує цей метод.
Можливо, було б більше сенсу, щоб це був спеціальний клас Location. Гаразд, я б не знав спочатку, що це таке, але в якийсь момент (напевно, перед тим, як написати, this.configuration.getLocation()мені знадобиться хвилина, щоб зрозуміти, що це за метод повертається).
Зрозуміло, в обох випадках мені потрібно шукати десь інше, щоб зрозуміти, що очікується. Однак в останньому випадку, якщо я розумію, що Locationтаке, я можу використовувати ваш API, в першому випадку, якщо я розумію, що Stringтаке (що можна очікувати), я все ще не знаю, що очікує ваш API.

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

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

Можна стверджувати, що останній дзвінок - toStringце менше дзвінка, ніж насправді просто шум, породжений вимогою Java на явність. Ви називаєте метод, який має будь-який об’єкт Java, тому ви фактично не кодуєте ніяких знань про "віддалену одиницю" і тим самим не порушуєте Принцип найменших знань. Немає способу, незалежно від того, що getLocationповертається, що у нього немає toStringметоду.

Але, будь ласка, не використовуйте рядки, якщо вони справді не є найбільш природним вибором (або якщо ви не використовуєте мову, яка навіть не має перерахунків ... там).


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

1
@jhocking: Він не сказав, що не може. Він сказав, що це недоцільно. Я не погоджуюсь. Спроба застосувати кращі практики до коду, який усуває недоліки в дизайні API насправді має сенс лише в тому випадку, якщо такі обхідні шляхи є єдино можливим варіантом. Однак тут налаштування API прямо - найкращий варіант. Усунути недоліки завжди краще, ніж працювати над ними.
back2dos

ну +1 у будь-якому випадку для "менше дзвінка, ніж насправді просто шуму, породженого вимогою Java на
явність

1
Я б дав цей +1, навіть якщо тільки для "строго набраного". У своєму коді я намагаюся якомога більше передавати типи, які виражають значення / намір, але під час роботи з сторонніми бібліотеками та API, іноді ви заважаєте використовувати те, що вирішили автори цього API. І IIRC, місце розташування справді може бути "будь-яким текстовим даним", але для реалізації, над якою я працюю, розташування без URL-адреси є абсолютно безглуздим.
CVn

1
Щодо зміни API; це комп'ютерне програмне забезпечення, тому змінити речі практично завжди можливо . (Якщо нічого іншого, ви завжди можете написати шар абстракції.) Однак іноді це вимагає занадто великих зусиль, як коротко-, так і довгострокових, щоб бути виправданим. Тоді ці зміни можна зробити цілком можливо, але все ж недоцільно.
CVn

21

Закон Деметра - це орієнтир дизайну, а не закон, якого слід дотримуватися релігійно.

Якщо ви вважаєте, що ваші заняття достатньо відокремлені, ніж немає нічого поганого в рядку, this.configuration.getLocation()особливо якщо, як ви кажете, міняти інші частини API не доцільно.

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


1
Оскільки this.configurationє змінною екземпляра типу відомого інтерфейсу, виклик методу на ньому, який визначений цим інтерфейсом, здається непоганим навіть за суворої інтерпретації. Так, я знаю, що це керівництво, як і KISS, SOLID, YAGNI тощо. Взагалі є мало, якщо взагалі є якісь «закони» (в юридичному розумінні) в розробці програмного забезпечення.
CVn

4
+1 за те, що він прагматичний ;-)
Треб

1
Я не думаю, що Закон Демінтера повинен застосовуватися навіть у такому випадку - конфігурація - це в основному контейнер. У кожному API, який я коли-небудь працював, заглядаючи в контейнери, це нормальна та очікувана поведінка.
Лорен Печтел

2

Єдине, що я можу подумати, щоб не писати код, як це, це те, що якщо this.configuration.getLocation()повертає null? Це залежить від вашого оточуючого коду та цільової аудиторії, яка використовує цей код. Але як каже Марко - закон Деметра - це головне правило - добре слідувати, але не ламайте спину, роблячи це без потреби.


Якщо ми this.configuration.getLocation()повернемо null, ми або (a) швидше за все ніколи б не дійшли так далеко, або (b) тимчасове сталося катастрофічне, і в такому випадку я б хотів, щоб код не стався . Отже, хоча це, безумовно, справедливий пункт в цілому, в цьому конкретному випадку можна сказати, що це не стосується. Крім того, оточення всього цього та багато іншого - це обробник винятків, спеціально розроблений для вирішення подібних несподіваних збоїв.
CVn

2

Якщо суворо дотримуватися Закону Деметра, це означає, що ви повинні реалізувати такий метод в об'єкт конфігурації, як:

function getLocationAsString() {
  return getLocation().toString();
}

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


1
Це те саме, що запропоновано тут: c2.com/cgi/wiki?TrainWreck "Створіть метод, який відображає бажану поведінку і підказує клієнту, що робити. Це слідує принципу" скажи, не запитай "."
heltonbiker
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.