Есть ли веская причина сделать чистые функции закрытыми?

25

У меня была небольшая дискуссия с коллегой. Проще говоря, есть ли хорошая причина скрывать / инкапсулировать чистые функции?

Под «чистым» я подразумеваю определение википедии :

  • Всегда возвращает одинаковые результаты из одного и того же ввода. (Ради этого обсуждения Foo Create(){ return new Foo(); }считается нечистым, если Fooне имеет семантики значения.)
  • Не использует изменяемое состояние (кроме локальных переменных) или ввод / вывод.
  • Не вызывает побочных эффектов.
Telastyn
источник
26
Можно привести аргумент, чтобы вообще не делать такую ​​функцию членом класса.
Питер Б
6
@PieterB - действительно, хотя не все языки поддерживают это, и некоторые языки допускают внутренний модуль даже для бесплатных функций.
Теластин
3
Каково было ваше мнение? Я не думаю, что чистота функции связана с тем, принадлежит ли она в публичном API.
Андрес Ф.
@andresF. - на самом деле велись споры о том, следует ли обнародовать необязательное правило валидации. Я сделал аргумент, что, поскольку это была чистая функция, было мало вреда. В этом конкретном случае это помогало тестированию и, вероятно, использовалось повторно. Этот вопрос больше о том, насколько широко этот аргумент может / должен применяться.
Теластин
2
@Telastyn Если его можно использовать повторно, но он не входит в обязанности текущего класса, он, вероятно, должен находиться в отдельном классе / модуле / что угодно, что есть у вашего языка. Тогда, будучи частью ответственности нового класса / модуля, он обязательно будет обнародован. Поскольку вы упомянули о тестировании, я отмечу, что вам не обязательно макетировать метод, когда вы это делаете. Как «деталь реализации», насмешка во время тестов дает мало пользы.
jpmc26

Ответы:

76

Чистая функция все еще может быть деталью реализации. Хотя функция не может причинить вреда (с точки зрения не нарушения важных инвариантов / контрактов), разоблачая ее, автор и пользователи этого класса / модуля / пакета проигрывают. Автор проигрывает, потому что теперь он не может удалить его, даже если реализация изменится и функция больше не будет ему полезна. Пользователи проигрывают, потому что им приходится просеивать и игнорировать дополнительные функции, которые не имеют отношения к использованию API, чтобы понять его.

Doval
источник
33
+1. Общедоступными членами вашего класса является его API. Это не вопрос «Может ли это повредить мне, если оно общедоступное?», А скорее «Должно ли оно быть частью API?»
Ордос
1
Единственное, что я хотел бы добавить, это то, что если вы начнете видеть похожие функции, появляющиеся в других местах, реорганизуйте их в другой класс, чтобы его могли использовать несколько классов. Или, если это проблема для начала, определите ее в отдельном классе для начала.
jpmc26
1
Я бы запечатал публичные классы, которые также не предназначены для расширения.
День
+1 за указание на то, что скрытие или отображение функции (или класса) - это, прежде всего, вопросы защиты и поддержки API, а не связанные с типом кода, которым он является.
Марко
42

Вопрос задом наперед.

Вы не ищите причину сделать функцию непубличной. Это неправильное мышление для начала (на мой взгляд). Рассуждения должны идти другим путем.

Другими словами - не спрашивайте «зачем мне делать это приватным?». Спросите: «зачем мне это делать публичным?»

Если есть сомнения, не подвергайте это. Это похоже на бритву Оккама - не умножайте права сверх необходимости.

РЕДАКТИРОВАТЬ: Обращаясь к контраргументам, выдвинутым @Telastyn в комментариях (чтобы избежать расширенного обсуждения там):

Я слышал это со временем и даже поддерживал это в течение довольно долгого времени, но по моему опыту, вещи, как правило, слишком приватны.

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

Но protectedбыло бы достаточно - и это все еще не публично.

Это приводит к значительному дублированию кода и накладным расходам на «вещи, которые не должны быть публичными», но к которым в любом случае обращаются косвенно.

Если это становится проблематичным, то просто сделайте это публичным! Есть необходимость, о которой я говорил :)

Я хочу сказать, что вы не должны делать это на всякий случай (YAGNI и все).

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

Конрад Моравский
источник
Я слышал это со временем и даже поддерживал это в течение довольно долгого времени, но по моему опыту, вещи, как правило, слишком приватны. Это приводит к значительному дублированию кода и накладным расходам на «вещи, которые не должны быть публичными», но к которым в любом случае обращаются косвенно.
Теластин
3
@Telastyn ИМХО, похоже, что приложение страдает от некоторых ошибок в дизайне; если вы склонны выставлять метод, потому что вам нужно вызывать его по отдельным путям кода, это, вероятно, признак того, что этот метод должен быть разбит на собственный модуль, который может быть вставлен или включен в случае необходимости, особенно если это чистая функция ,
Лев
@leo - извини, я не слежу. Рассмотрим функцию типа integer меньше чем. Это хорошая чистая функция, которая предоставляется специально, потому что ее можно затем ввести и включить (или просто использовать), где это необходимо.
Теластин
5
@Telastyn На мой взгляд, это признак философии «все является объектом / классом» в сочетании с тем фактом, что некоторые языки ООП не имеют составных типов данных, кроме классов. Как только кто-то должен передать два значения одновременно, он заканчивает тем, что пишет класс, и тогда каждый сотрудник и программа проверки стиля скажут вам сделать эти поля закрытыми.
Довал
@Telastyn Верно. Я имею в виду, что если ваш метод integerLessThan начинался как инкапсулированная деталь реализации публичного метода, но вы обнаружили, что вам нужно вызывать приватный метод в других местах, это, вероятно, признак того, что приватный метод принадлежит другому модуль / пакет / класс, чтобы его можно было импортировать независимо от логики, которая его вызывает. Простая публикация метода «как есть» будет означать, что метод произвольно расположен в первом модуле, где вы нашли его полезным.
Лев
6

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

pllee
источник
5

Классы должны придерживаться принципа единой ответственности . В то время как классу может потребоваться использовать другие функциональные возможности для достижения своих целей, он должен раскрывать только те функции, которые являются частью его единственной ответственности.

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

Рассмотрим класс, который фробницирует виджеты. Возможно, как часть его кода frobnication ему нужна некоторая служебная функция, которая анализирует строку: возможно, ему нужно преобразовать имя виджета таким образом, который не поддерживаются стандартными строковыми функциями.

Поскольку это чистая функция (строка входит, каким-то образом ее преобразовывает, возвращает новую строку), она может быть публичной или частной без последствий. Или это может?

Если вы сделаете это общедоступным, теперь у вашего класса есть две обязанности: изменение виджетов и преобразование строк. Это нарушает SRP и может вызвать проблемы, если другие классы полагаются на функцию. Поскольку вы думаете, что это используется только внутри класса, возможно, вы измените его интерфейс или видимость. Теперь классы в других частях системы не работают.

Сохраняя функцию приватной, никто никогда не сможет полагаться на код, который не является частью единственной ответственности класса.


источник
2
Я бы сказал, что он должен содержать только функции, которые являются частью его единственной ответственности, а не только разоблачать.
Теластин
1
Я думаю, что есть серая зона. Вам действительно нужен отдельный StringTransformerкласс для инкапсуляции двух или трех строк кода, которые используются только в одном месте? Я согласен с тем, что если код используется в нескольких местах, лучше всего разделить его на новый класс с одной ответственностью, но есть компромисс.
Безусловно. Руководящие принципы - это руководящие принципы, а не правила.
Теластин
@ Snowman в не Java, вы просто делаете библиотеку функций.
Питер Б
@PieterB это не о Java v. Ничего другого. Чтобы сделать его действительно ОО, вам понадобится фабрика, несколько абстрактных классов и т. Д.