Является ли подобный код «крушение поезда» (в нарушение закона Деметры)?

23

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

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

В этом случае serviceэто переменная экземпляра известного типа, объявленная в методе. this.configurationпроисходит от передачи в конструктор класса и является экземпляром класса, реализующего определенный интерфейс (который требует публичного getLocation()метода). Следовательно, возвращаемый тип выражения this.configuration.getLocation()известен; конкретно в этом случае это a java.net.URL, тогда как service.setLocation()хочет a String. Поскольку два типа String и URL не совместимы напрямую, требуется какое- то преобразование, чтобы поместить квадратный колышек в круглое отверстие.

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

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

Если бы я реализовать метод , URLToString()который просто вызывает toString()на URLобъект (например, возвращаемую getLocation()) , переданного ей в качестве параметра, и возвращает результат, я мог бы обернуть getLocation()вызов в ней для достижения точно такой же результат; по сути, я бы просто переместил конверсию на один шаг наружу. Это как-то сделает это приемлемым? (Мне кажется , интуитивно, что в любом случае это не должно иметь никакого значения, поскольку все, что нужно, - это немного изменить положение вещей. Однако, следуя приведенной выше букве закона Деметры, это было бы приемлемо, поскольку я будет работать непосредственно с параметром функции.)

Будет ли какая-то разница, если это будет что-то более экзотическое, чем вызов toString()стандартного типа?

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

CVn
источник

Ответы:

34

Проблема здесь заключается в подписи setLocation. Это строго типизировано .

Для уточнения: с чего бы это ожидать String? А Stringпредставляет собой любой вид текстовых данных . Это может быть что угодно, кроме действительного местоположения.

На самом деле, это ставит вопрос: что такое местоположение? Как мне узнать, не заглядывая в ваш код? Если бы это было так, URLя бы знал гораздо больше о том, что ожидает этот метод.
Возможно, было бы более разумно, чтобы это был пользовательский класс Location. Хорошо, я бы сначала не знал, что это такое, но в какой-то момент (вероятно, прежде чем писать, this.configuration.getLocation()я бы потратил минуту, чтобы выяснить, что возвращает этот метод).
Конечно, в обоих случаях мне нужно искать другое место, чтобы понять, что ожидается. Однако в последнем случае, если я понимаю, что это Locationтакое, я могу использовать ваш API, в первом случае, если я понимаю, что Stringесть (что можно ожидать), я все еще не знаю, чего ожидает ваш API.

В маловероятном сценарии, что местоположение - это любой вид текстовых данных, я бы интерпретировал это для любого типа данных, которые имеют текстовое представление . Учитывая тот факт, что у Objectнего есть toStringметод, вы можете пойти с этим, хотя это требует некоторого прыжка от клиентов вашего кода.

Также вы должны учитывать, что речь идет о Java, которая имеет очень мало возможностей по своему дизайну. Это то, что заставляет вас на самом деле позвонить toStringв конце.
Если вы возьмете, например, C #, который также статически типизирован, то вы фактически сможете опустить этот вызов, определив поведение для неявного приведения .
В динамически типизированных языках, таких как Objective-C, вам также не нужно преобразование, потому что пока значение ведет себя как строка, все довольны.

Можно утверждать, что последний вызов toString- это не просто вызов, а просто шум, вызванный требованием Java к явности. Вы вызываете метод, который есть у любого Java-объекта, поэтому вы на самом деле не кодируете какие-либо знания о «удаленной единице» и тем самым не нарушаете принцип наименьшего знания. Нет никакого способа, независимо от того getLocation, что возвращает, что у него нет toStringметода.

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

back2dos
источник
Я склонен согласиться с вами, но он уже сказал, что не может изменить API службы.
Джоккинг
1
@jhocking: Он не сказал, что не может. Он сказал, что это непрактично. Я не согласен. Попытка применить лучшие практики к коду, который работает с недостатками в дизайне API, имеет смысл, только если такие обходные пути являются единственно возможным вариантом. Однако здесь настройка API является лучшим вариантом. Устранение недостатков всегда предпочтительнее, чем обходить их.
back2dos
ну в любом случае +1 для «это скорее вызов, чем просто шум, сгенерированный требованием
явности в
1
Я бы дал это +1, даже если бы только для "строкового типа". В моем коде я стараюсь передавать типы, которые выражают значение / намерение в максимально возможной степени, но при работе со сторонними библиотеками и API-интерфейсами иногда вы застреваете, используя то, что решили авторы этого API. И IIRC, местоположение действительно может быть «любым видом текстовых данных», но для реализации, над которой я работаю, местоположение без URL-адреса совершенно бессмысленно.
CVN
1
Что касается изменения API; Это компьютерное программное обеспечение, поэтому практически всегда можно что-то изменить. (Если ничего другого, вы всегда можете написать слой абстракции.) Однако иногда это требует слишком больших усилий, как краткосрочных, так и долгосрочных, чтобы быть оправданным. Тогда вполне возможно сделать такие изменения, но все же непрактично.
CVn
21

Закон Деметры - это руководство к дизайну, а не закон, которому нужно неукоснительно следовать.

Если вы чувствуете, что ваши классы достаточно разделены, то в этой строке нет ничего плохого, this.configuration.getLocation()особенно если, как вы говорите, нецелесообразно изменять другие части API.

Я уверен, что клиент будет совершенно счастлив, даже если вы разложите Закон Деметры на куски, если вы будете доставлять то, что он хочет, и вовремя. Это не оправдание для создания плохого программного обеспечения, но напоминание о прагматичности при разработке программного обеспечения.

Trasplazio Garzuglio
источник
1
Поскольку this.configurationэто переменная экземпляра типа известного интерфейса, вызов метода для него, определенного этим интерфейсом, кажется вполне приемлемым даже в соответствии со строгой интерпретацией. Да, я знаю, что это руководство, как KISS, SOLID, YAGNI и так далее. В общем, очень мало, если вообще есть, «законов» (в юридическом смысле) в разработке программного обеспечения.
CVn
4
+1 за прагматичность ;-)
Треб
1
Я не думаю, что Закон Дементера должен даже применяться в таком случае - конфигурация - это в основном контейнер. В каждом API, с которым я когда-либо работал, просматривая контейнеры, это нормальное и ожидаемое поведение.
Лорен Печтел
2

Единственное, что я могу думать о том, чтобы не писать код, подобный этому, это что this.configuration.getLocation()делать, если возвращает ноль? Это зависит от вашего окружающего кода и целевой аудитории, использующей этот код. Но, как говорит Марко - закон Деметры - это правило большого пальца - хорошо следовать, но не ломайте себе спину, делая это без необходимости.

Martyn
источник
Если this.configuration.getLocation()бы он возвратил ноль, то мы либо (а), скорее всего, никогда бы не зашли так далеко, либо (б) что-то катастрофическое произошло в промежуточный период, и в этом случае я бы хотел, чтобы код не работал. Так что, хотя это определенно верный пункт в целом, в этом конкретном случае можно с уверенностью сказать, что он не применим. Кроме того, все это и многое другое - это обработчик исключений, специально предназначенный для устранения непредвиденных ошибок.
CVn
2

Строгое следование закону Деметры означало бы, что вы должны реализовать метод в объекте конфигурации, например:

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

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

jhocking
источник
1
То же самое предлагается здесь: c2.com/cgi/wiki?TrainWreck «Создайте метод, который представляет желаемое поведение и сообщает клиенту, что делать. Это следует принципу« говорите, не спрашивайте ».»
heltonbiker