Pull to refresh

Comments 27

А есть смысл обсуждать переведенную статью? Ответ от автора мы не получим.

Как мне кажется основная цель обсуждений — не диалог с автором. Это добавление информации к статье. Если вы не согласны с чем то и пишете это — вы пишете это не для автора, а для других людей, прочитавших статью и дальше пошедших читать обсуждения. Может они согласятся с вами и теперь под статьей будут контрпримеры и это дополнит статью. Либо они не согласятся с вами и, возможно, ответят на ваши аргументы своими в поддержку статьи или просто полезными знаниями/дополнительными примерами.

Не нужно вводить инкапсуляцию ради инкапсуляции. А если есть логика, то отдельный метод это правильное решение.

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

Ну вот тогда берется и делается рефакторинг, когда это реально понадобится.

Если этот код внешний по отношению кода, который тебе не подконтролен, лучше оставить определённый запас гибкости.

Нет тут универсальных критериев. Если вы пишете фреймворк, который будет переиспользоваться многими командами, то такие вопросы будут решены на этапе архитектурного планирования. А если достаточных требований нет со старта - то всё равно придется быть гибкими :)

Статья как-то осторожно написана. Неужели мысль о том, что геттеры и сеттеры не всегда полезны, является нынче настолько крамольной, что автор стесняется писать об этом прямо?

Если исходить из соображений краткости кода, лёгкости внесения в него изменений, и потенциала для совершения ошибок — то конечно, геттеры и сеттеры зло для структуроподобных объектов. Пользы от них никакой, а вред очевиден.
Да, именно так, не использование сеттеров является грубой ошибкой, по мнению идеологов ООП. Сеттер должен быть единой точкой доступа к полю, а то, что он является функцией, должно позволить повесить на него любой функционал, например, вы можете поставить логирование записи в поле, хеширование, сжатие, или любые проверки граничных условий, да всё что угодно. А «быстрый подход» прямого доступа тут же размазывает, например, логгирование по всему коду проекта, это принципиальный момент. На практике, конечно, всё несколько по другому, тем не менее, если вы поддерживаете чужой код, то вам дадут по рукам, если нет ссеторов и хорошо ещё, если не добавят пинка. Если же вы пишете свой личный проект, где вы царь и бог, то есть смысл прислушаться к статье ибо, если вы не сами решаете, что важнее производительность или гибкость и действуете строго по шаблону, то вы — плохой программист.
  1. Setter - это на самом деле не про отдельное поле. Это функция, которая переводит объект из одного инвариантного состояния в другое. В таком значении в тупом задании сеттеров по полям (по одному на каждое) и правда нет смысла. Лучше всего концепция проиллюстрирована в GraphQL mutations, кстати.

  2. А еще ныне популярна концепция immutability, в которой сеттеров нет вообще. И действительно, в реальном коде, если стараться все делать иммутабельно, сеттеров получается самый минимум.

  3. Есть еще такой паттерн как builder, вот он как раз позволяет конструировать такие иммутабельные объекты, но там не совсем сеттеры.

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

А по вашему мнению — является неиспользование геттеров/сеттеров грубой ошибкой или нет?
повесить на него любой функционал, например, вы можете поставить логирование записи в поле, хеширование, сжатие, или любые проверки граничных условий, да всё что угодно

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

Вы несёте существенные затраты: время на написание геттеров и сеттеров; рост размера кода в разы; лишние места, где можно допустить ошибку; падение быстродействия; затруднение понимания программы. И всё это ради решения каких-то проблем, которых сейчас нет, и неизвестно, появятся ли они в будущем. Но это же Overengineering в чистом виде.

Как по мне — то проще в будущем при необходимости добавить геттер и провести поиск/замену доступов к полю структуры на вызов геттера.
На практике, конечно, всё несколько по другому

А как у вас на практике происходит?
вам дадут по рукам, если нет ссеторов и хорошо ещё, если не добавят пинка

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

А по вашему мнению — является неиспользование геттеров/сеттеров грубой ошибкой или нет?

По моему мнению, сама концепция ООП не настолько ультимативна, что бы отказ от чего-либо делал её бессмыссленной в целом. Да и строго говоря, не имеют никакого отношения get и set к ООП, в классическом пониманиии, взаимодействие между объектами происходит через события. Поэтому я рассматриваю их не как закон, а как чей-то опыт, который просто принимаю к сведению, но в чужом монастыре буду строго придерживаться чужого устава.
А как у вас на практике происходит?

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

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

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

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

При разработке любой системы невозможно заранее предусмотреть все проблемы, которые возникнут в будущем. В моей практике попытки заранее предусмотреть и решить все возможные будущие проблемы приводили к существенной трате времени и сил на решение надуманных проблем, которые в будущем не возникали. А реально возникающие проблемы предугадать удавалось не все, так что приходилось тратить усилия на решение ещё и их. В итоге проекты, создаваемые с таким подходом, обычно терпели провал: у всех заканчивалось терпение ждать результата.

С тем, что любому инструменту есть своя область применения, согласен на 100%.

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

Допустим, у нас есть структура с полями прямого доступа:
struct A
{
    int a;
    double b;
    std::string c;
};

Требуется повесить функционал на запись в поле «b». Заводим новый класс:
class SmartDouble
{
public:
    double operator=(const double& nv) {printf("Write to double: %f\n", nv); v=nv; return nv;}
    operator double() { return v; }
private:
    double v;
}

И заменяем тип поля структуры «b» с double на SmartDouble. Рефакторинг остального кода с прямым доступом не нужен. Что скажете? Заработает?

К сожалению, на некоторые мои вопросы, подразумевающие простые ответы «да» или «нет», вы ответили как-то расплывчато. Если вас не затруднит, не могли бы вы уточнить ваши ответы на следующие вопросы:

1. «по вашему мнению — является неиспользование геттеров/сеттеров грубой ошибкой или нет?»
2. «Идеология геттеров/сеттеров является господствующей в умах начальства?»
1. В личном проекте — Нет, в проекте на который вас наняли — Да.
2. Да и это — нормально.

P. S. Это как с ЕГЭ, все признают что это зазубривание ничего не даёт, что само по себе это ущербно и т. д., но ответ так же будет, что требование хороших оценок по ЕГЭ иммеет место быть и это — нормально и не нужно с этим бороться. Если такая аналогия более понятна.
Спасибо за ответы.
и это — нормально и не нужно с этим бороться

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

Указанные явления суть не что иное, как обыкновенный догматизм. Буду знать, что нынче в моде сеттеры и геттеры. Ну а завтра им на смену придёт какая-нибудь другая «панацея». Технологии меняются, люди — нет.
Когда речь идёт о get и set чего-то, это идёт речь о безопасности а не о скорости. И таки да, городить кучу кода с желанием достигнуть производительности сопостовимой с прямым доступом смысла мало.

В случае с set данные часто проще проверить до вызова set.

Геттеры\Сеттеры концептуально реализуют поведение. Поведение декларируется интерфейсоv взаимодействия. Структура по сути своей является реализацией некоторого композитного хранения данных.
Вопрос о применимости интерфейса к композитному хранилищу выходит за рамки одной единственной структуры. Сами по себе геттеры и сеттеры этой структуре не нужны. Однако, если мы задаемся целью интеграции и абстрагирования поведения, то возможны варианты.


Ниже предельно искусственный пример, расширяющий предметную область.


// Интерфейс для некоторой задачи, требующей имя и фамилию
class named_entity_t{//пропущены абстрактные геттеры}; 

 // Интерфейс для установки фамилии как индекса поиска
class last_name_index_entry_t{//пропущены абстрактные сеттеры};

// Интерфейс для установки имени для целей базы данных
class first_name_db_entry_t{//пропущены абстрактные сеттеры}; 

// Интерфейс для получения и установки возраста сущности, человека, мебели, дерева в парке
class aged_entity_t{//пропущены абстрактные геттеры И сеттеры}; 

// Геттеры и сеттеры для имени и фамилии
struct Person : public named_entity_t, public last_name_index_entry_t, public first_name_db_entry_t
{ // пропущены тривиальные реализации, или даже наследование от миксин структур}

// Где-то в совершенно другом проекте с другими названиями интерфейсов

// Визуально те же геттеры и сеттеры, но с другой реализацией для другой задачи. 
struct Tree : public named_entry_t, public aged_entity_t{ //пропущено
}

Таким образом вопрос о геттерах и сеттерах в вакууме весьма холиварный. При рассмотрении задачи в предметной области и выбранного подхода геттеры могут реализовать динамическое вычисление полей, или абстрагировать нижележащие слои. Сеттеры могут помогать сохранять инвариант экземпляра структуры или проверять валидность данных. Геттеры и сеттеры не обязаны работать с единственным полем и могут использоваться для согласованных изменений совокупности полей.
Мутации экземпляра структуры само по себе усложняет логику кода. Озвученный пример с заменой А на Д — это насколько специфичен должен быть код бизнес-логики для такого требования, и учитывая требования по производительности — структура с геттерами и сеттерами уже не совсем подходящее решение.


А еще есть отдельный подход 'мухи отдельно, котлеты отдельно'. Когда структуры реализуют только композицию полей, а все операции и инварианты вынесены в отдельные сущности. И тогда геттеры и сеттеры вообще как бы внешние. Но иметь свободную функцию, устанавливающую отдельное поле переданной как аргумент структуры — это какая-то экзотика уже.

Вывод совершенно верный, вот только C++ тут в общем-то ни при чем. Если удариться в крайности, есть два варианта:
1. Низкоуровневая структура, представляющая собой отображение какой-то структуры в памяти и/или в файле (пример — структуры в WINAPI). Ни геттеры, ни сеттеры там никому не нужны (хотя их и можно было бы добавить)
2. Класс, реализующий какую-то высокоуровневую логику, напрямую не маппящийся в память и/или в файл — здесь скорее всего возникнет необходимость инкапсулировать какую-то логику, могут появиться get-only property (и, не дай бог, set-only), логика которых может быть (а может и не быть) чуть-чуть сложнее, чем return _value. Ничего плохого в этом нет, интерфейс отдельно, реализация отдельно.

Таким образом, нельзя на 100% сказать, что геттеры / сеттеры зло — разумный программист будет добавлять их там, где они имеют смысл, и не будет делать лишних движений там, где это никому не нужно.
В java-мире все функции виртуальные, а потому геттеры-сеттеры являются «хорошим» паттерном для записей, потому что многие библиотеки (ORM, например), генерируют от объектов наследников и прозрачно подменяют этими наследниками исходный объект. Либо аспектные библиотеки при обработке после компиляции внедряют свой код в функции геттера/сеттера. При этом, можно навесить любое поведение на обращение к полю: например, lazy-подгрузку из базы данных, или пометку объекта, как изменившийся, вы вызове сеттера, или уведомление UI, что нужно перерисовать контрол, привязанный к полю объекта.

В C++ в этом нет никакого практического смысла, но видимо, витает как карго-культ эта java-практика, что все поля должны быть с геттерами-сеттерами, а объяснения к этому потерялось.

Особенность Java и C# — возможность лёгкой кодогенерации в рантайме.
В C++ подобного функционала нет, зато хорошо развит статический полиморфизм (генерация классов при компиляции).

3-х геттеров (или даже 4-х): const lvalue, rvalue, const rvalue и, по вашему усмотрению, для неконстантного lvalue (даже если это уже просто очень странно звучит, так как проще использовать прямой доступ)

Насчет const rvalue немного неясно. Разве rvalue может быть const?

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

смысл в геттерах и сеттерах есть тогда, когда по каким-то причинам нужен нетривиальный доступ к переменной. Это в целом очевидно. Когда он нужен? Ну, когда нам может быть нужен pimpl/виртуальный доступ (например, это может требоваться для ускорения компиляции или сохранения бинарной совместимости между модулями); синхронизированный доступ (условные мьютексы); межъязыковое взаимодействие (Qt <-> QML, boost::python, protobuf...); или хотя бы чтобы что-то из вышеперечисленного было нужно в перспективе.

И я не вижу чтобы решение автора покрывало хоть какой-то из этих юзкейсов

Я иногда добавляю сеттеры для bebug билда, в которых находятся проверки присваиваемых данных. Помогает находить ошибки рантайма.

Интересно, что в случае с доступом к полю возраста (при доступе к полю имени код загроможден вызовами) код (по крайней мере, для ARM архитектуры) совершенно идентичен и никакого замедления быть не может, ЧЯДНТ?
Ну это совсем плохо — принимать решение, делать ли геттер/сеттер или прямой доступ на основании типа поля.
Но все таки дело не в структуроподобных объектах, а в конкретной реализации конкретной библиотеки.
Sign up to leave a comment.