Xpoint
   [напомнить пароль]

Так ли страшны public-поля, как их малюют?

Метки: [без меток]
[арх]
2006-06-05 09:53:07 [обр] Даниэль Алиевский(35/125)[досье]

Предлагаю немного обсудить стиль программирования в Java. По-моему, обмен мнениями в таком вопросе никогда не помешает.

Общее правило, разумеется, известно: никогда и ни при каких условиях не следует снабжать public-классы public-полями, кроме констант public static final.

Однако мне все-таки кажется, что из этого правила существуют исключения. Вот мне и стало интересно: пользовался ли кто-либо из уважаемых коллег public-полями в своих public-классах? Разумеется, в предположении, что это решение и сейчас представляется вам уместным и корректным (а не было сделано в процессе начального изучения Java).

Я до сих пор не пользовался практически ни разу - вернее, если и пользовался, то понимая неразумность своих действий :) Но недавно столкнулся с ситуацией, когда, вроде бы, public-поля уместны и удобны. Вот этот пример - конечно, упрощенный (в частности, без комментариев):

public class QualitySettings implements Cloneable {
    public Object clone() {
        try {
          return super.clone();
        } catch (CloneNotSupportedException e) {
          throw new InternalError(e.toString());
        }
    }
    public int deltaNorm = 5;
    public boolean useCentralPointForGradients = true;
    public double usingLengthFactor = 1.0;
    public void check() {
       if (deltaNorm < 1)
            throw new IllegalArgumentException("Illegal deltaNorm: should be > 1");
    }

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

    QualitySettings qs = new QualitySettings();
    qs.deltaNorm = 3;
    myQuality.calculate(matrix, seg, qs);

где matrix - исходное изображение, seg - некоторый отрезок (экземпляр специального класса Segment), myQuality - "качество отрезка на изображении", которое надлежит рассчитать (сложный объект с массой информации о проанализированном отрезке). Есть и другие классы, воспринимающие параметр типа QualitySettings или его наследников, например, "массив отрезков" с методом "улучшить качество всех своих отрезков, передвигая их".

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

По смыслу, QualitySettings никогда не будет содержать нетривиальных полей. Это ведь именно параметры, управляющие работой алгоритма и вводимые пользователем где-то в приложении. Т.е. почти наверняка все его поля и в при будущем развитии будут простыми типами, строками, может быть, какими-то несложными неизменяемыми классами (скажем, Color).
    
Клонирование позволяет скорректировать полученный откуда-то экземпляр QualitySettings перед передачей алгоритму, гарантируя отсутствие побочных эффектов.

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

  1. Заведомо известно, что структура класса всегда точно отражает представляемые данные (да нет там никакой структуры, это просто перечень параметров).
  2. Практически очевидно, что никогда не понадобится переделывать блок параметров в интерфейс - он может "обслуживать" только те алгоритмы, для которых создан, и не применим к алгоритмам, которые будут созданы для других целей. (Сравните: для представления "размеров" width/height вполне может понадобиться интерфейс с методами width()/height(), реализуемый самыми разными классами. Одна из причин, по которой лучше сразу делать методы доступа width()/height() - тогда программы, работающие с таким классом, будет легко переделать на использование интерфейса.)
  3. Делать класс неизменяемым тоже нельзя: ведь идея как раз в том, чтобы клиент, создав пустой экземпляр, настроил только часть полей, которые ему известны. (Тот же класс "размеры" лучше сделать неизменяемым.)

Так зачем в данной ситуации методы доступа?

Может быть, кто-нибудь назовет еще какие-нибудь примеры?

спустя 3 часа 3 минуты [обр] Давид Мзареулян(7/1003)[досье]
Ну, то есть Вы просто эмулируете пассивный набор данных, типа сишного struct или паскалевского record. Тогда, конечно, поля могут быть публичными, почему нет?
спустя 7 минут [обр] Даниэль Алиевский(35/125)[досье]

А Вы почитайте учебники по Java. Я еще ни разу не видел, чтобы это рекомендовалось.

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

public class Point {
    public double x, y;
}

Не надо ведь объяснять, чем это ужасно?

спустя 7 минут [обр] Данбала(2/63)[досье]
Для той задачи, которую вы описали, я бы просто использовал Map.
спустя 12 минут [обр] GRAy(14/259)[досье]
Плох тот struct который не мечтает стать объектом :) отсюда и ноги растут у таких рекомендаций ;).
спустя 15 минут [обр] Даниэль Алиевский(35/125)[досье]

Данбала[досье] Ну и зря, потому что тогда пришлось бы создавать enum-класс для всех возможных ключей. И даже тогда исчез бы контроль типов параметров (ведь они разнотипные). Впрочем, это не по теме.

GRAy[досье] Вот и вопрос, знаете ли вы такие случаи, когда struct (вернее, все-таки класс с public-полями) ничем не хуже класса с методами доступа? Относится ли сюда мой случай?

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

Приведу еще пример. Однажды я проектировал 2-мерное изображение (матрицу). Я знаю, что всякое изображение имеет целочисленные размеры sx и sy, причем изменять их у созданного класса нельзя. Я совершенно уверен, что ни в каком будущем эта ситуация не поменяется. Поэтому я объявил у класса "изображение" два public final поля типа int: sx и sy.

Но потом все же я понял, что это плохо, и вернулся к методам доступа sx(), sy(). Плохо, например, потому, что изображение без методов доступа никогда не сможет реализовать абстрактный интерфейс, описывающий какие-то частные особенности изображений, у которого объявлены методы sx(), sy() и который принимают на вход некоторые алгоритмы вместо полного класса "изображение". И даже если я дополню public-поля эквивалентными методами доступа (что уже уродливо), все равно это плохо. Ибо public-поля провоцируют создание кода, где они употреблены напрямую. Если в какой-то момент понадобится адаптировать этот код к другому классу или интерфейсу, менее "мощному", чем основной класс "изображение", то наличие public-полей может резко затруднить адаптацию. В то же время, если, скажем, у меня есть процедура работы с изображением, которой нужны только методы sx(), sy() и (условно говоря) getPixel(x,y), то я могу в будущем создать интерфейс с этими тремя методами, реализовать его в "изображении" и просто заменить тип аргумента процедуры на этот интерфейс.

спустя 11 минут [обр] GRAy(14/259)[досье]
Даниэль Алиевский[досье] Ну это же не аксиомы ;) Наверняка существуют ситуации, в которых public поля оправданы - просто это не массовые случаи. Рекомендации же, как правило, носят обобщённый характер. Не стоит над этим сильно голову ломать ИМХО.
спустя 2 минуты [обр] Данбала(2/63)[досье]

Даниэль Алиевский[досье]
Я хотел сказать, что для того чтобы объединить набор величин простых типов, существует множество списковых структур. Описывать class (класс объектов ==> наследование, инкапсуляция полиморфизм) избыточно. То есть, я считаю приведенный пример не иллюстрирующим ваше предположение.

Я поддерживаю традиционную точку зрения. Ведь у вас есть метод check(), не лучше ли было бы сделать deltaNorm protected? А в сеттере для него поставить проверку? То же и для других полей, где сеттер контролирует типы (в вашем случае Color), принадлежность аргумента заданному диапазону значений и т. п.

спустя 1 час 55 минут [обр] Давид Мзареулян(7/1003)[досье]
Не надо ведь объяснять, чем это ужасно?

А объясните:)

И вот, кстати, Данбала[досье] прав — если есть метод “check”, то deltaNorm не стоило бы делать публичным.

спустя 6 часов [обр] Даниэль Алиевский(35/125)[досье]

Данбала[досье]

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

Можете привести более удобный пример? Map явно менее удобен, и вроде бы не имеет преимуществ перед классом с public-полями.

Описывать class (класс объектов ==> наследование, инкапсуляция полиморфизм) избыточно.

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

Я поддерживаю традиционную точку зрения. Ведь у вас есть метод check(), не лучше ли было бы сделать deltaNorm protected? А в сеттере для него поставить проверку? То же и для других полей, где сеттер контролирует типы (в вашем случае Color), принадлежность аргумента заданному диапазону значений и т. п.

Да, есть - но при описании параметров алгоритма часто весьма неудобно выполнять проверки в set-методах. Бывает, что для проверки корректности надо иметь сразу набор параметров. Простейший пример - задание нижней и верхней границы какого-нибудь диапазона. Если сделать им независимые методы set, то при встраивании проверки в set-метод есть риск, что устанавливаемая новая нижняя граница вступит в противоречие с текущей заданной верхней. В сложном случае зависимости параметров, подлежащие проверке, могут быть очень нетривиальными. Проще и удобнее сделать одну "интегральную" пост-проверку, когда все параметры уже установлены. Если алгоритм хочет быть уверенным, что его параметры гарантированно верны, он может в начале работы склонировать блок параметров и вызвать для клона check(). Хотя на практике я не вижу нужды в такой "супернадежности".

Кстати, видимо, вы имели в виду не protected, а private.

GRAy[досье] Вот и интересно, когда же все-таки public-поля оправданы. Пока общепризнанных примеров я не видел. Почему компилятор не выдает warning-и или даже ошибки на public-поля, если они нигде не нужны?

Давид Мзареулян[досье]

А объясните:)

Так это из Блоха. Точка Point должна быть неизменяемой. Если некий класс имеет атрибут типа Point (скажем, конец отрезка или "горячая точка" курсора мышки), и он хочет иметь метод, возвращающий этот атрибут, то при использовании неизменяемого Point он может возвращать один и тот же экземпляр, пока этот атрибут не поменялся. А вот если у Point есть public-поля, то единственный выход - каждый раз создавать новый экземпляр Point.

Другие минусы public-полей x, y тоже очевидны. Почему не сделать интерфейс, назовем его DoublePoint? Тогда в методы, принимающие Point с методами доступа x(), y(), можно будет с таким же успехом передавать интерфейс с такими же методами. А интерфейс уже может быть реализован не только как точка с double-овской точностью, но и как целочисленная точка. И даже как класс, возвращающий конкретную k-ю точку массива точек, без реального создания экземпляра для каждой точки.

Далее, кроме координат, точка имеет и другие атрибуты - скажем, угол arctg(y/x) или расстояние до начала координат sqrt(x^2+y^2). Некоторым приложениям эти свойства, может быть, будут нужны чаще, чем координаты. Если мы сделаем абстрактный класс или интерфейс Point с методами доступа x(), y(), r(), fi(), мы можем передавать разным клиентам реализации с разным внутренним представлением, рассчитанные на использование декартовых или полярных координат.

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

спустя 2 часа 10 минут [обр] Данбала(2/63)[досье]

Даниэль Алиевский[досье]

Можете привести более удобный пример?

Могу

public class Context extends HashMap{
   public void setAttribute(String name, Object value){
       this.put(name, value);
   }

   public Object getAttribute(String name){
       return this.get(name);
   }
}

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

Как раз наследование у меня в данной ситуации используется,
Бывает, что для проверки корректности надо иметь сразу набор параметров.

Но ведь помимо сеттеров существуют еще и конструкторы, верно?

Кстати, видимо, вы имели в виду не protected, а private.

Как раз таки protected. Вы написали

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

Как быть, если "клиенту" захочется определить какое-нибудь ограничение для поля?
Вы же не можете заранее знать, как он захочет расширить функциональность вашего кода.
Если поле protected, и есть сеттер (конструктор), он просто расширит класс, переопределив
этот сеттер (конструктор). Если же поле public, то как его не переопределяй, возможность
установки значения напрямую останется, и ограничение можно будет обходить. Впрочем, не буду
настаивать на protected, пусть private, важно, что не public, а то уйдем в оффтоп.

PS Наверно, все таки, не метод доступа, а модификатор доступа?

спустя 10 часов [обр] Thirteensmay(0/157)[досье]
Даниэль Алиевский[досье] Также страшны как goto, указатели и пр. в этом духе. Т.е. все к месту/времени/задаче. Если программист понимает что это такое, он может это использовать по своему усмотрению, если нет - огребет проблем. В общем случае не концептуально, но реально ;) Если я целиком вижу задачу, она конечна, и прямой доступ к свойствам возможен/учитывается, то я сделаю obj.attr = true, а не obj.setattr(true), хотябы потому что это короче и проще.
спустя 1 час 48 минут [обр] Даниэль Алиевский(35/125)[досье]

Данбала[досье]
Ну и чем же ваш пример более удобный? Кстати, вы применили неподходящий паттерн - нужно было не наследовать от HashMap, а использовать фасад (вложить HashMap в ваш объект).

Если для задания параметров алгоритма использовать строки, это классический антипаттерн - не гарантируется уникальность ключей. Клиент, не знающий о некоем новом параметре алгоритма, может по ошибке его установить и получить совершенно неожиданные результаты. Ваше решение можно усовершенствовать, используя для ключей собственный неподделываемый класс, вероятнее всего - type-safe enum. Но и тогда полученное решение обладает всеми недостатками моего класса с public-полями, не обладая его достоинством: проверкой типов для каждого параметра.

Конструкторов у меня, разумеется, нет. Если все поля public, зачем конструкторы? И что в них передавать - не десяток же параметров (это еще один антипаттерн)?

Если ограничивать видимость полей, то, конечно же, private или friendly. Поле protected обладает всеми минусами public-поля, так как навсегда фиксирует реализацию. Если нужно переопределять поведение в наследниках, то для этого вызывают унаследованный сеттер.

И главное, все это о чем-то не о том :) У меня всего два вопроса - есть ли минусы у public-полей в моем случае и известны ли кому-либо другие случаи, где public-поля уместны? Мне пока неизвестны.

Thirteensmay[досье] Однако ж в Java, C#, JavaScript и других языках нет ни указателей, ни goto. Не поддерживаются на уровне языка, ибо не нужны ни в одной задаче. Public-поля поддерживаются. Спрашивается, зачем? У вас были задачи, где public-поля были уместны?

Если задача, как вы говорите, "конечна", то наверняка вам будет достаточно прямого доступа к полям в пределах пакета, решающего задачу, а это уровень friendly. Public-поля, по определению, нужны только для использования в других пакетах, решающих, вероятно, другие задачи и имеющих строго регламентированный и ограниченный доступ к средствам данного пакета. В моем примере это - задание параметров законченного алгоритма.

Раз уж все молчат, назову сам некоторые недостатки public-полей в моем примере. Мне-то кажется, что недостатки незначительны и не перевешивают удобства записи и простоты создания класса. Но, может быть, я не прав?

  1. Мой блок параметров потоково-небезопасен. Я должен быть уверен, что блок параметров, переданный в некий алгоритм, заведомо не будет модифицироваться ни в каком другом потоке параллельно с исполнением алгоритма. Иначе последствия могут быть любыми, вплоть до зацикливания. Идеально проблема решается immutable-блоком параметров с отдельным builder-ом: по очереди задаем все параметры, потом специальным методом получаем immutable-версию блока параметров и передаем в алгоритм именно ее. Но на практике, по-моему, проблема несущественна. При любом типичном использовании блок параметров создается непосредственно перед передачей алгоритму и более никому недоступен.
  1. Мой блок параметров нуждается в клонировании для модификаций. Если я получил блок параметров "извне" (в параметрах метода), а затем хочу изменить один параметр перед передачей алгоритму, я обязан его склонировать. Если я забуду, то я "испорчу" поведение кода, который мне передал этот блок. С immutable-блоком клонирование (или эквивалентная операция - переход к builder-у и обратно) было бы вынужденным, я бы не мог про него забыть. Пока сложно сказать, насколько часты будут подобные ошибки, мне это проблемой не кажется.

Обратная сторона проблемы - я не могу гарантировать сохранность блока параметров, если передаю ее в какую-либо процедуру, за корректность которой я не отвечаю. Иначе говоря, изменяемый блок параметров обязан быть "короткоживущим", как, например, StringBuffer: создали, настроили, отдали в процедуру (или вернули в результате) и забыли. Но именно для такого использования он и предназначен.

  1. Как уже заметил Данбала, я не могу поддерживать блок параметров всегда корректным, вставив проверки корректности в сеттеры. Но я могу вставить проверку в начало алгоритма, и если блок параметров не модифицируется параллельными потоками, то этого достаточно. У единой проверки есть очевидные плюсы - можно тестировать согласованность сколько угодно большого числа параметров, в то время как сеттер может проверить лишь свои аргументы.
спустя 1 час 1 минуту [обр] Данбала(2/63)[досье]

Даниэль Алиевский[досье]

Кстати, вы применили неподходящий паттерн - нужно было не наследовать от HashMap, а использовать фасад (вложить HashMap в ваш объект).

я же сказал, что не знаю, какую задачу вы решаете. Или вы утверждаете, что "использование фасада" всегда лучше, чем наследование?

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

Если вам непременно хочется что-то авторитетно провозгласить, то потрудитесь хоть немного разобраться в том, о чем говорите.

  1. Ключи в HashMap уникальны.
  2. Если пользователь решит установить параметр, о котором ничего не знает, то
    1. у этого пользователя не все в порядке с головой. Он решает свою задачу, или пытается установить все подряд?
    2. что будет, если вы добавите public поле, а пользователь не прочитав документации, захочет его установить?
    3. я примерно представляю, о каком антипаттерне вы говорите. Его здесь и рядом не было.
Но и тогда полученное решение обладает всеми недостатками моего класса с public-полями

Оно их не использует, т. е. не реализует антипаттерн. А это настоящий антипаттерн.

Конструкторов у меня, разумеется, нет. Если все поля public, зачем конструкторы?

Для того, чтобы сделать, как вы выразились, одну "интегральную" пост-проверку.

И что в них передавать - не десяток же параметров (это еще один антипаттерн)?

Нет, это далеко не всегда антипаттерн. Зависит от условий вашей задачи и от того, как вы ее решаете. Поэтому, вполне возможно, что, да, десяток параметров.

И главное, все это о чем-то не о том :) У меня всего два вопроса - есть ли минусы у public-полей в моем случае и известны ли кому-либо другие случаи, где public-поля уместны? Мне пока неизвестны.

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

Раз уж все молчат...

Ну что ж, пишите свои многотонные и очень авторитетно звучащие опусы, а потом перечитывайте их на досуге.
Удачи.

спустя 2 часа 51 минуту [обр] Thirteensmay(0/157)[досье]

Даниэль Алиевский[досье]:
Я писал:

Public также страшны как goto, указатели и пр. в этом духе. Т.е. все к месту/времени/задаче

Вы ответили:

Однако ж в Java, C#, JavaScript и других языках нет ни указателей, ни goto, ибо не нужны. Спрашивается зачем там Public ?

Так вот:
Public в Java тоже самое что goto в C ;) - оно там тоже есть, и тоже не приветствуется. Public в Java наследие C++, точно также как goto в C насление какогонибудь фортрана. Возникает вопрос, а че это эти наследия пооставляли ? - А потому что с одной строны еще вписывается в концепцию, а с другой - позволяет вытягивать поделку за уши по требованиям "серых масс" ;) Т.е. компромисс такой если хотите: "Вам нужно goto - да хрен с вами, получите, но мы вас предупреждаем что это не есть гуд, вот видите какая у нас замечательная среда, юзайте ее ;)" или "Хотите Public, незнаем, у нас концептдилемма, - пусть будет Public ;), видите какая симпатишная у нас жаба ;), правда в этом месте она может пукнуть, вы ее там лучше не трогайте". Так что с эим надеюсь все понятно, остается рассмотреть последний вопрос "А зачем рабочему классу жопа ?" Ответ прост: она ему нравится потому что можно по настоящему расслабиться, не без издержек конечно, но можно ;). Например пишу я сравнительно небольшой проект, ну скажем приблизительно оценил что кол-во классов будет чтото около 20 .. 30, что вы думаете я буду продумывать стратегию доступности атрибутов ? - нифига, еще я не парился ;), сделаю все глобальными и баста. Что неконцепт ? - да родственник я его, я не жабу пишу, мне работу через месяц сдавать, не первый год так работаю, еще ни разу особых проблем не испытывал. Наблюдаю такую картину: Сидит целый отдел, пыжит щеки, продумывает мегавешь, долго пыжит, процентов на 30 уже сделал, тут появляется засранец, впаривает за месяц с их точки зрения какашку, но о чудо, она реализует задачу ;), потом может начать вонять, но можно позвать засранца, он на нее дезиком попшикает, и о чудо, она опять работает. Пыжещекие в а.уе, засранца при.издили - поехало по новой. Т.е. чтото типа китайского подхода - дерьмо, но окупается. Такчто практика это не теория, и она показывает что в подавляющем большинстве случаев на концепт можно совершенно спокойно положить. И проблем потом почти не будет. Даниил, только не подумайте что я такой уж законченный урод/ра.пи.дяй, я тоже люблю когда все по полочкам разложено и пахнет приятно, но не всегда получается, что же теперь пиво (Public) запретить ? ;)

спустя 3 часа 11 минут [обр] Oleg(0/12)[досье]
Thirteensmay[досье]+1
спустя 2 часа 22 минуты [обр] Даниэль Алиевский(35/125)[досье]

Данбала[досье] Что ж вы такое пишете? Фасад не всегда лучше наследования. Но вы же хотели описать некий аналог класса с public-полями для задания параметров алгоритма, разве нет? Тогда зачем ему наследовать все методы HashMap, вроде удаления всех параметров? Это не только бессмысленно, но и чревато проблемами - алгоритм не обязан проверять, что каждый его параметр существует.

Задачу, вроде бы, я сформулировал. Задать сложный набор параметров для некоторого алгоритма. В моем примере - вычисление "качества" отрезка на изображении (насколько похоже, что в этой позиции на изображении действительно изображен или сфотографирован прямолинейный объект). Бывают полуэмпирические алгоритмы, работа которых описывается сотней числовых и логических параметров. Что именно тут неясно?

Ключи в HashMap уникальны.

Вы не поняли. Они подделываемы - если это строки. Я могу создать экземпляр строки, которая случайно окажется равной некоторому ключу. Сравните, например, с type-safe enum: если это final-класс, я никак не могу создать экземпляр класса, равный (в смысле equals) одной из констант шаблона enum, кроме как явно обратиться к самой этой константе. Тем более я не могу обратиться к полю, идентификатор которого я откуда-то получил - разве что я применю рефлексию.

Если пользователь решит установить параметр, о котором ничего не знает, то
  1. у этого пользователя не все в порядке с головой. Он решает свою задачу, или пытается установить все подряд?

Если параметр задается в виде ключа в таблице, то есть возможность откуда-то получить этот ключ, например, прочитать из текстового файла. Возможность, исключенная в случае public-поля или классического неподделываемого ключа type-safe enum (если не говорить о рефлексии). Более того, даже задавая ключ непосредственно в виде строчной константы, есть возможность ошибиться и случайно задать несуществующий параметр, не задав нужный, и компилятор ничего не скажет об этой ошибке. Например, в моем алгоритме распознавания окружностей есть параметр: minRelationOfIntensityPercentileOfOuterAndInnerArea. Кабы это было не поле класса, а строка, даже я ошибался бы весьма часто. Вроде бы очевидные вещи.

  1. что будет, если вы добавите public поле, а пользователь не прочитав документации, захочет его установить?

Если пользователь смог скомпилировать свой класс-клиент, значит, это поле уже существовало в момент компиляции. Значит, была и документация на это поле, которую можно было увидеть в любой нормальной оболочке, когда она "подсказывала" набор допустимых полей при наборе. Какая тут может быть ошибка?
Если же программа клиента успешно скомпилировалась, то в будущем я не смогу добавить другое public-поле с таким же именем.

Оно их не использует, т. е. не реализует антипаттерн. А это настоящий антипаттерн.

Я перечислил три известных мне недостатка решения с public-полями. Ваше решение с HashMap обладает ими всеми. Разве нет? Каких недостатков public-полей лишено решение, когда блок параметров описан в виде HashMap?

Для того, чтобы сделать, как вы выразились, одну "интегральную" пост-проверку.

Это возможно, только если передать в конструктор все параметры сразу. Тогда смысл "блока параметров" полностью теряется - эти же параметры можно передать непосредственно в алгоритмический метод. Смысл блока параметров - уменьшить число аргументов одной процедуры до стандартного предела 2-4 аргумента. Ибо если у метода (в том числе конструктора) более 4 параметров - это антипаттерн, о котором я говорил.
Впрочем, вы пишете:

Нет, это далеко не всегда антипаттерн. Зависит от условий вашей задачи и от того, как вы ее решаете. Поэтому, вполне возможно, что, да, десяток параметров.

Попробуйте разобраться в программе с десятком параметров у процедуры. Да, мне приходилось такие писать и читать - неприятно. И к тому же неэффективно. А если набор параметров все время меняется (алгоритм совершенствуется)? А если их не 10, а 30 или 50 (более реальные цифры для сложного алгоритма)?

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

Вот это - "Если же поле public, то как его не переопределяй, возможность установки значения напрямую останется, и ограничение можно будет обходить"? Я написал об этом: недостаток номер 3, со ссылкой на вас. Но разве этот минус серьезен? И, кроме того, решение, которое предложили вы (наследник HashMap), никоим образом не устраняет этот минус. Я всегда могу напрямую вызвать метод put и обойти любые ограничения, как и в случае public-поля. Вот если бы вы применили фасад, тогда в setAttribute можно было бы добавить проверки. Но все равно это было бы ничем не лучше, чем набор private-полей и сеттеров - тот стандартный вариант, с которым я и сравниваю свое решение.

Thirteensmay[досье]
В принципе-то я сам полностью разделяю ваш подход к программированию. Хорошо, что никто не знает, на что похожа моя любимая Perl-программа :) Но в данном случае все же остаются два основных момента.

  1. Как никто не требует правильной защиты доступа, так никто и не заставляет разбивать задачу из 20-30 классов на разные пакеты. А внутри одного пакета поля с friendly-доступом никем никогда не осуждались.
  2. Сами методы доступа ("сеттеры" и "геттеры") делаются весьма тривиально, хорошие оболочки это даже автоматизируют. Время, сэкономленное на применении public-полей, почти наверняка весьма ничтожно - даже для самой простой задачи оно не превысит 1% от всех прочих временных затрат. Так что простота - неубедительная причина сохранения public-полей, если у них есть хоть один осмысленный недостаток :)

А вот в случае с моим блоком параметров, похоже, скрытие полей и введение set- и get-методов не может изменить ситуацию. Следовательно, и смысла в отказе от public нет - у них нет недостатков по сравнению с методами доступа (кроме невозможности поддерживать набор самосогласованным, что неважно, если алгоритм начинается с вызова check()). Вот по сравнению с immutable-версией - есть. Но и она будет нуждаться в builder-е, а идеальным вариантом этого builder-а опять оказывается класс с набором public-полей. Или я чего-то не вижу?

спустя 11 часов [обр] Thirteensmay(0/157)[досье]
Даниэль Алиевский[досье]
Все видите, а чего хотите непойму. Я сразу сказал: "Если программист понимает что это такое, он может это использовать по своему усмотрению, если нет - огребет проблем." Public атрибуты есть, Вы их разумно используете, никто вам этого не запрещает. А что касается того факта что в основном этого не рекомендуют, так сами подумайте для кого предназначены эти рекомендации, их уровень, и уровень их задач.
спустя 54 минуты [обр] Даниэль Алиевский(35/125)[досье]

Thirteensmay[досье] Да просто хотел поинтересоваться, знает ли кто-нибудь другие спорные случаи, когда public-поля, может быть, и оправданы.

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

Отсутствие public-полей - одно таких правил. У Блоха немало и других рекомендаций, вызывающих сомнения - скажем, его нелюбовь к клонированию, восторги по поводу immutable-объектов (моя практика показывает, что они имеют обыкновение создавать проблемы при проектировании эффективных алгоритмов), и т.д. Я бы с удовольствием все это обсудил, для пробы решил задать вопрос про public-поля.

спустя 8 часов [обр] Thirteensmay(0/157)[досье]

Даниил, Вы не доконца осознали крайнее злодейство описанной мной ситуации, сначала приведу отвлеченный пример с целью пущего погружения, затем реально-актуальный:

Шесть лет назад мне сказали - Рома, надо сваять 7 некислых печатных отчетов по набранной базе, вот тебе структура, делай как хочеш, чтобы вчера работало. Ну сел я, сваял самый большой отчет, и что вы думаете сделал дальше ? - Ctrl+C/Ctrl+V 6 раз, поправил запросы, снес лишнюю визуализацию, переименовал поля и не более, т.е. в одном скрипте 7 одновременных наследований драйвера БД, драйвера подсистемы отчетов, все это круто замешено с VB (ASP), JS, HTML, CSS, SQL, ActiveX, COM, FoхPro, шаблонами CMS и пр., причем единовременно используется только один из 7 слегка похожих блоков, и в каждом из них порядка 30% кода не используется вообще никогда (лень разбираться/чистить), далее возникла необходимость наладить взаимодействие между этими отчетами... ;) Даниил, Вы только представьте себя на моем месте, какие такие friendly атрибуты ? ;) Я конечно мог дней за 5 это более менее облагоразумить, но когда желание начальства совпадает с ленью реализатора ;) Короче увязал кое-как пабликами, на том забыл, 7 год работает и ничего, нормально...

Рассмотрим еще один случай:
Т.к. ни в SQL, ни в перловом DBI нет полноценно настраиваемой сортировки, пришлось уже относительно недавно, делать свою обертку для DBI в т.ч. с такой функциональностью. Так вот, имеем классический recordset (результат запроса к БД), получился он у меня public (разбираться как его сделать private в perl и экспериментировать с my неохота, если бы писал на чем нибудь ООП-нормальном, конечно сделал бы private, а так прямой доступ использовать не собирался, просто помнил что он у меня public). В рамках самописанной помеси CMS/CMF работа получается такой:

  $cnn = MyDBI->new();
  $cnn->query('select name from...'...);
  $cnn->recsetsort('name'...);
  OutDBTable($cnn->{recset}, $cnn->{recsetfieldcount}...);

Все нормально, но тут вдруг надо немного подправить recset перед выводом ;) Что гласит нам теория ? - делайте отдельный метод подправки (доработайте $cnn->recsetsort) - чушь собачья, подправка уникальна и редка. Что еще она гласит ? - делайте геттеры/сеттеры - а зачем мне эта запарка, у меня ж recset - public ;). Мало того что их сначало нужно сделать, так потом еще и писать всевремя $cnn->setrecsetvalue($i, $cnn->getrecsetvalue($i) + 1); вместо $cnn->{recset}->[$i]++;. И это простейший случай. Конечно можно оптимизировать, но в любом случае геттеры/сеттеры длиннее, медленнее и замороченнее, пусть и ненамного, но ради чего ?, если толку от этого всеравно 0. Ктото тутже начинает кричать: "Ах за.ранец, это не концептуально, это тебе боком вылезет !". 13 лет занимаюсь программированием, ни разу не вылезло, обычно я таких называю уродами.

Примечание по поводу второго случая: Да, у меня тут не полная иерархия типа cnn->recset->record->field->value, потому как так оптимальнее, все вынесено почти наверх, задача ясна, да и вообще ООП здесь только с целью получения возможности работы с несколькими экземплярами одновременно. Конечно в больших системах полная иерархия бывает оправдана, но и геттеры/сеттеры там тоже становятся соответствующими, и раскрытие (with) что припарка.

Так что IMHO требование "Отсутствие public-полей" - это теоретическая романтика, ну или просто глупость снижающая быстродействие, читабельность кода, увеличивающая его объем, короче отсутствие прямого доступа одним словом, со всеми вытекающими. Есть конечно и неоспоримые преимущества, счастье беспечного программиста например ;) и внедрять его можно, на Ваш выбор, разработчики Вам его предоставили. Немогем писать оптимально - пишем не оптимально, избавление потомков от необходимости думать приводит к еще большей неоптимальности.

По поводу остального не скажу, ибо особо глубоко ООП не пользую ;)

спустя 2 часа 15 минут [обр] Даниэль Алиевский(35/125)[досье]

Thirteensmay[досье] Прочитал. Не убедили :) Во-первых, Perl - не Java, не уверен, что к нему применимы те же принципы. Во-вторых, в Java конверсия public-поля в пару get/set даже вручную занимает минуту. А оболочка может это еще и автоматизировать. Почему бы с самого начала не писать всегда таким манером?

Кстати, $cnn->{recset}->[$i]++ та же теория вполне разрешает превратить в $cnn->recset()->[$i++]. Массивы вроде никто не запрещал, и ниоткуда не следует, что метод не может вернуть массив :)

В то же время, как раз в ваших условиях - когда править надо быстро, чтобы "вчера работало" - защита от случайных ошибок весьма ценна. В свой "дикий" Perl-код WebWarper-а я никогда не полезу, не имея возможности настроиться на медитативный лад и никуда не торопиться :) Get/set позволяют отсечь хотя бы часть ошибок (неправильные значения полей) и помочь в отладке (можно сунуть туда отладочную печать). Я уж не говорю об immutable-классах, которые отсекают целые семейства ошибок.

Про эффективность public-полей мне песни петь не нужно, я сам могу спеть сколько угодно :) Общее правило - думать о микрооптимизации следует в последнюю очередь, когда задача решена, но заказчик хочет побыстрее. Исключительно редки те случаи, когда прямое обращение к полю может сэкономить хотя бы процент времени ожидания пользователя. И эти случаи на 100% покрываются доступом к private- или friendly-полям. Наоборот, наличие public-полей может потребовать неуместного клонирования, а это всегда небыстрая операция.

спустя 15 часов [обр] Thirteensmay(0/157)[досье]
Даниэль Алиевский[досье] Да еслиб я хотел Вас убедить, я б с Вами не разговаривал ;). Ничего не имею против Ваших доводов, просто у разных людей разные подходы, get/set в приведенной ситуации я бы писать не стал, потому что это лишние бесполезно задавленные бактерии ;), когда я изменяю переменную я обычно думаю, выделяю небольшие обособленные куски кода и не пишу в оболочках - это способствует пониманию и защищенности от ошибок, вот такая альтернатива get/set если хотите ;)
спустя 1 час 46 минут [обр] Даниэль Алиевский(35/125)[досье]

Thirteensmay[досье] Это, собственно, не мои доводы, а из учебника :) Мне как раз интересно испытать тезисы "учебников" на прочность - нет ли ситуации, где все же public-поля уместны. Вы-то говорите не то, что в вашей ситуации public-поля хороши и правильны, а то, что вам лень писать get/set, ибо в конкретной ситуации минусы public не так важны. Вот кабы вы сумели привести пример, когда public-поля со всех сторон хороши, независимо от лени или привычек - было бы другое дело. Мне, кажется, удалось наткнуться на такой пример, да и то меня точит червь сомнения...

А если public-поля всегда нехороши, то авторы языка спокойно могли бы их запретить. Тогда вы бы тоже писали get/set, уже по необходимости, так же, как обходитесь без goto.

Кстати, раз уж исследуем тему, давайте обозначим плюсы public-полей. ("Эффективность" или "быстрота написания" - это "лже-плюсы" :)) Я пока вижу один - высокий уровень самодокументированности. Грубо говоря, если у класса есть public-поле типа double, то не нужно никак специально документировать поведение его в многопотоковой среде (и так ясно, что он потоково-небезопасен), свойства синхронизации (единственная возможность - volatile), инварианты (если нет методов доступа, то и инвариантов быть не может). Ясно, что всякая модификация параметра метода такого типа требует клонирования (или будет побочный эффект). И т.д. Вообще, при обращении клиента к public-полю предельно ясно, что происходит - никаких вариантов быть не может. Это, безусловно, плюс при написании надежных программ (если, разумеется, он не перекрыт минусами public-полей).

Кто назовет еще хоть один плюс? :)

спустя 31 минуту [обр] Thirteensmay(0/157)[досье]
  1. Простота.
  2. Условная эффективность.
  3. Высокий уровень самодокументированности.
Собственно говоря 2 и 3 пункты в значительной мере являются следствиями первого.
Под "условной эффективностью" понимается возможность достижения при определенных условиях положительного баланса преимуществ/недостатков в сравнении с лучшими альтернативами.
спустя 2 часа 33 минуты [обр] Даниэль Алиевский(35/125)[досье]
  1. Нет тут преимущества :) Вы все время не учитываете, что даже для не-public поля обращение к get/set требуется лишь извне пакета. Т.е. это всегда некий межпакетный запрос. Почти всегда (в моей практике, кажется, в 100% случаев) это либо простой вызов set, чтобы задать параметры какому-то внешнему классу, либо простой вызов get, чтобы узнать у него что-то. Все нетривиальные манипуляции с полями (вроде моеПоле++) происходят внутри того же пакета, а чаще всего внутри того же класса. Разве что вы намеренно создадите кучу пакетов и беспорядочно раскидаете по ним классы :)

А в случае межпакетного запроса, обращение set смотрится не только не сложнее, но в чем-то и понятнее присваивания. Сразу ясно, что это именно законченный запрос на установку чего-то, а не какие-то промежуточные присваивания. Более того, при чтении неизменяемых полей я часто опускаю префикс get. Например, у моей "матрицы" размеры возвращаются методами sx(), sy(). Что тут сложного?

  1. Про "условную эффективность" я не понял. Зато могу прокомментировать обычную эффективность доступа :)

Вызов метода, по моим измерениям, требует порядка нескольких десятков тактов. Действительно, циклы, где все операции, помимо обращения к get/set класса из внешнего пакета, занимают всего десятки тактов, встречаются не часто. Но встречаются. В этом случае может оказаться полезным доступ к public-полю библиотечного класса, скажем, представляющего вектор. Правда, мне пока не встречались ситуации, когда это оправдано.

спустя 3 часа 2 минуты [обр] Thirteensmay(0/157)[досье]

Про преимущество простоты: А Вы всевремя не учитываете что я не всегда обязан тратить время на продумывание и реализацию стратегии доступа, если умею правильно пользоваться единой, универсальной, условно-эффективной моделью public/global значений. Зачем забивать себе голову осознанием факта наличия всяких friendly и т.п., думами по поводу уместности их применения, а также реализации геттеров/сеттеров, если я решаю конкретную конечную обособленную и небольшую задачу. Модульность/блочность в их широком смысле никто не отменял. Если переменная служебная - пусть будет private/local, если это свойство/настройка то пусть будет public/global. Так проще и думать и делать.

Про "условную эффективность": Cмотрите в контексте того примера про recset что я Вам привел. Если какой либо подход в реальной ситуации позволяет затратить наименьшее количество ресурсов, и при этом полноценно решает задачу - то я считаю его наиболее эффективным в данных условиях. Учитывайте все "жизненные" факторы - и лень, и время, и привычки, и квалификацию и пр, а не только концептуальную красоту.

Про "новые" идеи ;): Пожалуй можно определить еще одно достоинство - "приемственная универсальность". Чтобы это понять надо учесть что рассматривать ООП и процедурный подход можно в комплексе. Это можно считать кашей, а можно единым целым, смотря как у вас голова заточена. Идея относительной пагубности разделения наук надеюсь Вам известна. Так вот, public есть цемент, в нашем случае можно объединить ООП и процедуры не только на уровне кода программы, но еще и на уровне мышления программиста. Так быстрее переключаться с задачи на задачу когда они разного уровня. Т.е. чтото типа логического клея ;) Этот абзац также объясняет представленную в первом ситуацию.

P.S. Идеально структурировать все не возможно, т.к. внешний мир не дискретен, по мере ухода от него значение public/global можно уменьшать. Потеря это или нет ? - в частности определяется ответом на вопрос: А наша виртуальность будет лишь частью реальности или отделится ? Мы с вами между ними. Я не хочу ни туда, ни сюда, мне с public нормально. Блоха то ладно, у всех гуру крышу рвет, а Вас то чего ?

спустя 10 часов [обр] Даниэль Алиевский(35/125)[досье]

Thirteensmay[досье]
Про простоту - я таки не очень понял, в каких попугаях она у вас измерена :) Если уж вы не хотите продумывать стратегию доступа, то простейший путь - не разбивать на пакеты и вообще не заморачиваться модификаторами доступа. Модификатор public нужен как раз тогда, когда вы уже решили разбить задачу на блоки, т.е. проектируете нетривиальную архитектуру. А в этом случае, делая "не глядя" public-поля, вы создаете себе проблемы и в конечном счете тратите больше времени (и мозгов). Как раз о том, достаточно ли безопасно делать поле public, обычно нужно очень долго думать. Самое простое решение - "никого не пущать": открывать только методы и только тогда, когда без этого не обойтись. Чтобы написать для глобальной настройки пару get/set, нужна минута времени (или несколько секунд в хорошей оболочке). И так сделать, безусловно, проще - можно не задумываться о потенциальных будущих проблемах: если они таки возникнут, у вас будет возможность решать их, меняя поведение get/set.

Хотите пример? Буквально только что разработал "библиотечный" класс Segment2D, описывающий двумерный отрезок, задаваемый центральной точкой, длиной и углом поворота fi в радианах. Внутри угол конвертировался в единичный вектор nx=cos(fi), ny=sin(fi), чтобы посчитать координаты концов. Поля, естественно, private (точнее friendly, но это неважно); доступа на запись к единичному вектору не было. Написал с помощью этого класса программу, отладил, все заработало. И вот - решил оптимизировать: все-таки вычисление синуса и косинуса отнимало кучу времени, а в моей программе можно было посчитать и запомнить все эти параметры заранее. Создал дополнительный класс UnsafeSegment2D, в котором открыл доступ на запись к единичному вектору nx, ny, и стал пользоваться. Все немедленно сломалось :) Могу даже сказать где - в частности, в обменной сортировке (меняя отрезки местами, я забыл обменять также значения полей nx, ny, которые теперь тоже стали доступными). Полдня отлаживал, писал методы проверки инвариантов, распечатки промежуточных данных и т.п. А вы говорите - "проще". Кабы с самого начала поля были public, у меня сразу появился бы соблазн их использовать для эффективности, и проблемы пошли бы сразу же. А так - я сначала отладил программу, потом долго думал, открыл некий минимум в строго ограниченной форме (в отдельном классе) и использовал для оптимизации. В других приложениях, может быть (и даже скорее всего), этот специальный класс UnsafeSegment2D не понадобится, и проблем с инвариантами вообще не будет.

А что до "проще думать" - воспринимайте пару get/set как global property. В Java, в отличие от той де Delphi, property не поддерживаются языком, некоторые на это ругаются. Приходится привыкать: видишь get/set - думай об этом как о свойстве, а не как о методах.

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

Преемственная универсальность - интересный момент. Я бы только переформулировал чуть иначе: программы должны по возможности быть максимально легки для восприятия человеком, не знающим конкретный язык, и легко переводиться на другие языки/технологии. Только мы-то обсуждаем не то, плохи ли public-свойства, а то, как его следует реализовывать - public-парой get/set или public-полем. (При этом можно "держать в уме", что это на самом деле свойство.) Пара get/set - нормальный прием почти в любом языке и в любой технологии. В процедурном языке мне тоже ничто не мешает настраивать глобальные свойства модуля вызовом setXxxx. Поглядите хотя бы на WinAPI. Да и среди прерываний DOS/BIOS было полно "геттеров" и "сеттеров". Как раз наоборот, в незащищенном языке, где вопросы "безопасности" и "сохранения контрактов" не имею гарантированного решения, куда больше оснований применять get/set, чем простое присваивание. Хотя бы потому, что больше возможных точек для отладки и проверки целостности системы. Грубо говоря, в Java метод get для final-поля примитивного типа заведомо тривиален, а вот в Паскале есть прямой смысл проверить - а не испортилась ли "константа" из-за порчи памяти?

Кстати, знаете, где преемственная универсальность побуждает меня нарушать рекомендации для Java? Я никогда не объявляю автоматические переменные final, когда это не требуется для доступа из вложенных классов. Ибо в более "старых" языках такой подстраховки нет (кажется :)). Стараюсь, чтобы алгоритм выглядел примерно так же, как в записи на C или Perl. По аналогичной причине, бывает, не использую коллекции там, где достаточно массива (если, конечно, это не портит архитектуру).

Идеально структурировать все не возможно, т.к. внешний мир не дискретен, ...

А я с этим не спорю :) В этом обсуждении мое дело маленькое - что предпочесть: public-поле или public-методы get/set.

спустя 3 часа 56 минут [обр] Thirteensmay(0/157)[досье]

Даниэль Алиевский[досье] Ну вот, Вы все свели к единственному преимуществу геттеров/сеттеров - безопасности. Я изначально с этим и не спорил, просто указываю что у public тоже есть преимущества. Надеюсь Вы их хоть от части признали.

Рассмотрим еще одно - производительность. Замечены следующие высказывания:

Про эффективность public-полей мне песни петь не нужно, я сам могу спеть сколько угодно :) Вызов метода, по моим измерениям, требует порядка нескольких десятков тактов.

На какой архитектуре ? Для широкораспространенной I386 это сотня как минимум.
Приведем пример:

#include <iostream>
#include <ctime>
using namespace std;

class AccessMethodsMustDie
{
public:
  int attr;
  int getattr();
  void setattr(int sattr);
};

AccessMethodsMustDie AMMD;

int AccessMethodsMustDie::getattr()
{
  return attr;
}

void AccessMethodsMustDie::setattr(int sattr)
{
  attr = sattr;
}

int main()
{
  int i, ts, te;
  ts = time(0);
  cout << "PublicStart = " << ts << " sec.\n";

  for(i = 0; i < 100000000; i++) AMMD.attr++;

  te = time(0);
  cout << "PublicEnd = " << te << " sec.\n";
  cout << "PublicDuration = " << te - ts << " sec.\n\n";

  AMMD.attr = 0;
  ts = time(0);
  cout << "AccessMethodsStart = " << ts << " sec.\n";

  for(i = 0; i < 100000000; i++) AMMD.setattr(AMMD.getattr()+1);

  te = time(0);
  cout << "AccessMethodsEnd = " << te << " sec.\n";
  cout << "AccessMethodsDuration = " << te - ts << " sec.\n\n";

  return 0;
}

Одного этого достаточно чтобы признать право public на существование.

Мое дело маленькое - что предпочесть: public-поле или public-методы get/set.

- Предпочитайте по обстоятельствам ;)

Результат исполнения:

спустя 2 часа 29 минут [обр] Даниэль Алиевский(35/125)[досье]

Thirteensmay[досье] Пока признал только одно преимущество - самодокументированность. И до сих пор вижу лишь один случай, когда это преимущество перевешивает недостатки - тот, с которого я начал тему :)

А вот насчет эффективности, позвольте привести вам небольшой очень поучительный тестик, написанный на Java, которую мы обсуждаем, а не на C++. Мне результаты так понравились, что я даже занес его в свою библиотеку :)

package net.algart.algorithm.tests;

public class SpeedOfGetSet {
    static long nanoTime() {
        return System.nanoTime();
//        return net.algart.algorithm.SysUtils.nanoTime();
    }

    static String dec(double v, int d) {
        return String.format("%.8f", v);
//        return net.algart.algorithm.SysUtils.dec(v, d);
    }


    public static void main(String[] args) {
        Test as = new Test();
        final int n = 100000000;
        final int n10 = 10 * n;
        int v1 = 0, v2 = 0, v3 = 0, v4 = 0, v5 = 0, v6 = 0, v7 = 0, v8 = 0, v9 = 0, v10 = 0;
        for (int k = 0; k < n; k++) { // fill all possible caches
            as.setA(as.getA());
        }
        long t1 = nanoTime();
        for (int k = 0; k < n10; k++) {
            v1 = as.a;
        }
        long t11 = nanoTime();
        for (int k = 0; k < n; k++) {
            v1 = as.a;
            v2 = as.a;
            v3 = as.a;
            v4 = as.a;
            v5 = as.a;
            v6 = as.a;
            v7 = as.a;
            v8 = as.a;
            v9 = as.a;
            v10 = as.a;
        }
        long t2 = nanoTime();
        for (int k = 0; k < n10; k++) {
            v1 = as.getA();
        }
        long t21 = nanoTime();
        for (int k = 0; k < n; k++) {
            v1 = as.getA();
            v2 = as.getA();
            v3 = as.getA();
            v4 = as.getA();
            v5 = as.getA();
            v6 = as.getA();
            v7 = as.getA();
            v8 = as.getA();
            v9 = as.getA();
            v10 = as.getA();
        }
        long t3 = nanoTime();
        for (int k = 0; k < n10; k++) {
            as.setA(v1);
        }
        long t31 = nanoTime();
        for (int k = 0; k < n; k++) {
            as.setA(v1);
            as.setA(v2);
            as.setA(v3);
            as.setA(v4);
            as.setA(v5);
            as.setA(v6);
            as.setA(v7);
            as.setA(v8);
            as.setA(v9);
            as.setA(v10);
        }
        long t4 = nanoTime();
        for (int k = 0; k < n10; k++) {
            as.a += as.a;
        }
        long t41 = nanoTime();
        for (int k = 0; k < n; k++) {
            as.a += as.a;
            as.a += as.a;
            as.a += as.a;
            as.a += as.a;
            as.a += as.a;
            as.a += as.a;
            as.a += as.a;
            as.a += as.a;
            as.a += as.a;
            as.a += as.a;
        }
        long t5 = nanoTime();
        for (int k = 0; k < n10; k++) {
            as.setA(as.getA() + as.getA());
        }
        long t51 = nanoTime();
        for (int k = 0; k < n; k++) {
            as.setA(as.getA() + as.getA());
            as.setA(as.getA() + as.getA());
            as.setA(as.getA() + as.getA());
            as.setA(as.getA() + as.getA());
            as.setA(as.getA() + as.getA());
            as.setA(as.getA() + as.getA());
            as.setA(as.getA() + as.getA());
            as.setA(as.getA() + as.getA());
            as.setA(as.getA() + as.getA());
            as.setA(as.getA() + as.getA());
        }
        long t6 = nanoTime();
        System.out.println(dec((t11 - t1 + 0.0) / n10, 8) + " | "
            + dec((t2 - t11 + 0.0) / n10, 8)  + " ns field");
        System.out.println(dec((t21 - t2 + 0.0) / n10, 8) + " | "
            + dec((t3 - t21 + 0.0) / n10, 8)  + " ns get");
        System.out.println(dec((t31 - t3 + 0.0) / n10, 8) + " | "
            + dec((t4 - t31 + 0.0) / n10, 8)  + " ns set");
        System.out.println(dec((t41 - t4 + 0.0) / n10, 8) + " | "
            + dec((t5 - t41 + 0.0) / n10, 8)  + " ns a+=a");
        System.out.println(dec((t51 - t5 + 0.0) / n10, 8) + " | "
            + dec((t6 - t51 + 0.0) / n10, 8)  + " ns set(get()+get())");
    }
}

class Test {
    public int a = 1;
    public int getA() {
        return a;
    }
    public void setA(int value) {
        a = value;
    }
}

Закомментированные строки в функциях nanoTime и dec вызывают методы моего библиотечного класса SysUtils, которые реализуют аналогичную функциональность для JRE 1.4 и более младших. Эта версия требует для компиляции JDK 1.5.

Компилируем:
"D:\Program Files\Java\jdk1.5.0_06\bin\javac" net/algart/algorithm/tests/*.java
Исполняем:
"D:\Program Files\Java\jdk1.5.0_06\jre\bin\java" -server net.algart.algorithm.tests.SpeedOfGetSet

Получаем:

0.05143503 | 0.00004079 ns field
0.00001648 | 0.00001648 ns get
0.06292588 | 0.00631030 ns set
0.30849480 | 0.28584750 ns a+=a
0.29712659 | 0.29535458 ns set(get()+get())

Интересно, правда? У меня Pentium-IV 1.8 GHz, купленный примерно 4 года назад - по нынешним временам отнюдь не "крутой" компьютер.

Если запустить без ключа -server (что, в общем, не слишком разумно для запуска приложений - такой режим рассчитан скорее на апплеты), то получится:

2.63950223 | 0.80938918 ns field
3.80353006 | 0.88462835 ns get
3.48493230 | 1.04161954 ns set
3.78425666 | 11.32262508 ns a+=a
4.75450427 | 10.53405866 ns set(get()+get())

Кроме того, пользуясь своим SysUtils, я также повторил тот же тест на Java SDK 1.4.2_10 (скомпилировал и запустил). Результаты (с ключом -server и без него) примерно такие же.

Между прочим, вначале я пытался тестировать инкремент: as.a++ и as.setA(as.getA() + 1). Результаты получились совсем удивительные - что-то типа 30 инкрементов за такт (что для ++, что для get/set). Очевидно, оптимизатор начал исполнять цикл каким-то блоками. А вот в данной версии теста (поле постоянно удваивается) результаты похожи на реальность - полтакта на удвоение.

Итак, делаем выводы.

  1. JVM прекрасно оптимизирует доступ на чтение к полю через метод get(), если в действительности поле не менялось. Очевидно, цикл выполнился только один раз (или что-то в этом роде) - JVM сумела догадаться, что последующие итерации цикла пользы не принесут. А вот доступ на чтение к public-полю оптимизировался лишь в простой ситуации (цикл из 1 инструкции).
  2. Нетривиальная операция (a += a) выполняется с примерно одинаковой скоростью, что через оператор, что через пару вызовов get/set. Очевидно, JVM компилирует оба варианта "на лету" в похожий код.
  3. Расцикливание - это прием из прошлого. Лучше писать нормальный цикл, он будет оптимизирован и исполнен примерно так же (если заменить удвоение инкрементом, то даже лучше). При этом скорость простого целого удвоения достигает 2 операции за такт, как и положено Pentium.

Вот так-то... Приходится взять назад свои слова насчет "десятков тактов накладных расходов". Правильная оценка - 0 тактов :) Или, может быть, действительно несколько десятков тактов, если метод велик либо вызывается редко, и JVM решила не оптимизировать его (не вставлять код в точку вызова). Или - тот самый пример с ByteBuffer, который я тоже неоднократно измерял - когда метод является native, и JVM просто не имеет возможности заменить процедуру вставкой кода. Несколько десятков тактов - это примерное время, которое нужно процессору на операторы call, ret и организацию кадра стека (вроде push ebp; mov ebp,esp).

Кстати, вы-то на какой машине тестировали? 100 миллионов инкрементов за 1 секунду - это напоминает i486-DX4. Или ваш компилятор вообще ни к черту не годится :)

спустя 2 часа 8 минут [обр] Thirteensmay(0/157)[досье]

Это был PIII 500Mhz, задействованный компилятором MSVC++ 6 в рамках I386 ;) Что под руку попалось, тупо конечно, но истинную суть какраз и проявляет. Мой код перелег напрямую, Ваш - был модифицирован компилятором и за счет отимизации get и пр., серьезную часть отставания ликвидировал. Что может быть не всегда. Согласен, если использовать свежачек с полной оптимизацией под конкретное, более современное/расширенное железо, и алгоритмической оптимизацией, то разница уже не столь потрясает. Но один черт есть, впрочем я уверен что Вы это не признаете.

Да, интересно, почему такая разница в set-ах при ключе -server, поведайте что за ключик такой ? Почему set в первом случае такой медленный, по идее он даже в случае "лобового" подхода должен быть быстрее. И еще, как прокомментируете отставание методов во втором случае всего в 2.5 раза, почему set вдруг стал таким резвым ?

спустя 11 минут [обр] Thirteensmay(0/157)[досье]
Вообще, цифра 22 судя по напрягам Вас сильно возмутила, признаться я и сам не ожидал, думал что разница будет в разы. Благодаря Вам мы вернули все на свои места, спасибо ;)
спустя 34 минуты [обр] Даниэль Алиевский(35/125)[досье]

Thirteensmay[досье] Да какой там свежачок. Вся эта оптимизация была еще в JRE 1.4, т.е. несколько лет назад. А впервые появилась, кажется, еще в Java 1.1, только была похуже. Так, мой алгоритмический апплет на JRE 1.1.4 по сравнению с 1.5 проигрывает в полтора-два раза.

Железо, тем более, роли почти не играет. Понятно, что Sun оптимизирует свои коды для всех сколько-нибудь популярных версий процессоров. JVM, на которой я проверял, общая для всей Intel-платформы, а вовсе не для "конкретного железа". Вероятно, C# ведет себя похоже.

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

Не "серьезную часть ликвидировал", а стопроцентно свел на нет. Более того, в некоторых случаях инвертировал ситуацию. Я же говорил: если писать, "как положено", то и оптимизатор к такому коду "отнесется лучше".

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

Что может быть не всегда.

Именно что всегда. JVM всегда работает с оптимизацией (кроме специального отладочного режима). А программисту следует использовать наиболее "привычные" оптимизатору конструкции, т.е. наиболее общепринятые и естественные, чтобы конечный странслированный код оказался лучше.

Да, интересно, почему такая разница в set-ах при ключе -server, поведайте что за ключик такой ?

Вообще-то RTFM :) Суть-то простая. "-server" - это стандартный режим запуска standalone-приложений. При этом JVM, по возможности, старается оптимизировать код получше (деталями можно управлять - есть специальные ключи). Но, разумеется, это несколько замедляет загрузку классов, а вместе с тем и старт всего приложения и его модулей. Если с приложением предполагается работать долго (или, тем более, это сервлеты для круглосуточной работы), то всегда следует использовать "-server". Если же быстродействие (на уровне подобной оптимизации) не играет роли - скажем, это просто диалоговая утилита, редактор или Java-апплет - то можно ключ "-server" не использовать. Тогда будет применяться более элементарная оптимизация, практически не задерживающая загрузку классов.

И еще, как прокомментируете отставание методов во втором случае всего в 2.5 раза, почему set вдруг стал таким резвым?

Не понял - в каком втором случае?

Вообще, цифра 22 судя по напрягам Вас сильно возмутила, признаться я и сам не ожидал, думал что разница будет в разы. Благодаря Вам мы вернули все на свои места, спасибо ;)

Меня возмутила цифра 1 секунда. У меня удвоение элемента на "медленном" языке Java занимает полтакта. У вас - 10 наносекунд, т.е. 5 тактов даже на PIII-500. Даже на Паскале на i486 для такого действия удавалось в 1-2 такта укладываться.

спустя 3 часа 49 минут [обр] Thirteensmay(0/157)[досье]

Насчет получения 1 секунды, вполне возможно что просто на переход попал ? ;).
Попробовал на Delphi - получил разницу в 2.5 раза в пользу public.

Даже и незнаю какой делать вывод, попробую:
Честно говоря, мне тяжко сравнивать Ваш код, вопервых я на Java не пишу, вовторых в силу архитектуры и подходов она вообще слабо прогнозируема (асинхронный результат обеспечивает, а вот с методом достижения этого результата жопа). Посему, пожалуй нужно признать, что классические "линейные" логические подходы (ASM, Ada, Pascal, С, и частично С++/ObjPascal) в этом плане не уместны. В перечисленных языках производительность public очевидна. В Java - нет, в силу нелинейно-условной природы ее вычислительного процесса (в т.ч. нелинейно-условной логической оптимизации). Таким образом, учитывая кроссплатформенность, можно сказать, что рассматривать языковые (в т.ч. public), и некоторые другие характеристики Java относительно аппаратной основы ее реализации, в общем случае бессмысленно. Т.е. такие объективные характеристики как например производительность и время, преобретают степень относительности, ну или если хотите не могут рассматриваться относительно одной точки отсчета.

Уточним аргументы "ЗА" public относительно Java:

  1. Простота (идеи и реализации, высокий уровень самодокументированности).
  2. Приемственная универсальность.
  3. Условная эффективность.
спустя 29 минут [обр] Даниэль Алиевский(35/125)[досье]

Thirteensmay[досье] Так что ж вы спорите, если на Java не пишете :) Конечно, это совсем другой язык. С другой идеологией, чем у Pascal / C++ / Object Pascal. Если и сравнивать, то с C# и, вероятно, скриптовыми языками вроде JavaScript. Для Java есть важное понятие - гарантированность инвариантов, возможная лишь благодаря блокировке всех потенциальных путей порчи памяти. То, что для Delphi вопрос стиля (поле или методы доступа), для Java превращается в вопрос безопасности и надежности класса (принципиально недостижимой в языках с указателями). Правда, имеет смысл помнить, что и в Java остается "лазейка" для порчи памяти, связанная с рефлексией - если не установлен менеджер безопасности, то можно добраться и до private-полей. Но это, все же, экзотика.

Уточним аргументы "ЗА" public относительно Java:

Все же "public вообще" или "public-поля"? Что касается полей, я понимаю лишь самодокументированность (в основном, в плане поведения в многопоточной среде). Причем плюс весьма условный: всегда можно то же самое описать буквально парой фраз (полное отсутствие поддержки потоков + полное отсутствие контроля инвариантов). Все прочее - вопрос восприятия: если привыкнуть воспринимать пару public get/set как единое свойство, то это будет столь же просто, похоже на другие среды и эффективно с точки зрения трудозатрат, как и public-поле.

спустя 3 часа 19 минут [обр] GRAy(14/259)[досье]

Даниэль Алиевский[досье]
<offtopic>

принципиально недостижимой в языках с указателями

Вы наверное хотели сказать: "в языках с прямым доступом к указателям" ;) или я уже придираюсь.... :)
</offtopic>

спустя 9 часов [обр] Даниэль Алиевский(35/125)[досье]
GRAy[досье] Придираетесь :) Если уж аккуратно, то в языках, позволяющих сформировать указатель на область памяти, не соответствующую никаким объявленным в программе данным, либо на освобожденную память.
спустя 4 часа 44 минуты [обр] Thirteensmay(0/157)[досье]
"Так что ж вы спорите, если на Java не пишете :)" - Да где я с Вами спорю ? Сами предложили высказать Вам варианты, вот я и высказал несколько, и обосновал на сколько смог. С концептуальной Java точки зрения они может и не катят, ну так сами говорите что public использовать не рекомендуют по той же причине, так зачем рассматривать их полезность с этой точки зрения ? Чтобы доказать что они концептуально не нужны ? В рамках идеологии Java (приведенной вами) это понятно. Не ищите в них концепта, я сразу сказал - это как goto в C - местами полезный неконцепт. Сейчас Вы опять скажете, а в чем польза ?, и начнете искать концептуальную пользу. Нет ее (ну или мала), а вот неконцептуальная есть - про нее я и толкую. Есть такое слово "наследие", тоже сразу сказал. Или Вас всеже интересует концептуальная польза ? Ну может быть тогда это "поддержка классической ООП модели", т.е. не совсем так пафосно, а в рамках того что в основном принято выражать свойства объектов переменными-членами, и теория в принципе не запрещает делать их (свойства) публичными, а остальное это уж как говорится - следствие.
спустя 31 минуту [обр] Даниэль Алиевский(35/125)[досье]
Ну да, в сущности, все уже высказали друг другу :)
Наверно, других примеров полезности public никто не приведет...
спустя 1 день 23 часа [обр] elide[досье]
"поддержка классической ООП модели", т.е. ... теория в принципе не запрещает делать свойства публичными
теория вообще ничего не запрещает. в теории сказано, что All You Can Do Is Send A Message. вероятно я чего-то очень не понимаю, но как-то вот это Send A Message и публичные поля не очень согласуются...
по сути дискуссии могу сказать только одно: публичные поля - это совсем не страшно. но зачем они нужны, я так и не понял....
спустя 1 месяц 21 день [обр] Даниэль Алиевский(35/125)[досье]

elide[досье] Наверно, и правда, public-поля низачем не нужны. Мои аргументы в их пользу уж больно сомнительны.

Просто при создании языка не решились отказаться от этого наследия C++. Побоялись, что будут бить :)

Powered by POEM™ Engine Copyright © 2002-2005