Pull to refresh

Comments 411

Заголовок помягче бы, видимо вас это ооочень сильно достало =)
UFO just landed and posted this here
Пункт 7 — java-стиль. Считается что в случае необходимости изменения механизма присваивания/получения этот способ является более гибким.
В .NET он уже не требуется — там за обычным присваиванием/получением можно скрыть произвольную реализацию (методы get/set).
Проблема в том что в случае дотнета теряется осознание разработчика использующего объект, что он работает с методами а не полями. Из-за этого вылезают ошибки. Как минимум.)
UFO just landed and posted this here
Заменять стилем кодирования ограничения языка — очень, очень плохой стиль. =)

Есть правила синтаксиса а есть авторские запятые.

А вообще… Вспомним для чего была создана концепция JavaBeans изначально? Для работы с компонентами UI. Теперь представим себе изменяемое свойство… Дальше понятно.

И к слову. Если мы говори о свойствах полях и методах. Когда разработчик пишет obj.a из этого кода он не может понять изменяемое это поле или нет. Рефакторинг и вообще изучение кода усложняется. Как это реализовано в дотенете? Возможно ли понять в месте использования свойства изменяемое ли это свойство?
UFO just landed and posted this here
Хм. Стиль может быть понятен или не понятен другим разработчикам. Поддерживающим проект после команды. Команды меняются код остается… Это известные принципы. Время вхождения все дела…

На тему студии. Мы же говорим не только о дотнете разве нет? ;) Если только то статья должна называться по другому, и речь должна идти о стиле работы с дотнетом исключительно. Я очень рад что в дотнете есть свойства объектов и программистам удобно их использовать вместо сеттеров и геттеров. =)
Решарпер вам может подсказать, когда вы хотите readonly свойству присвоить значение. Либо комментарии.
Да и не только Решарпер — и компилятор ругается гадскими словами :)
И кроме того, программист сам должен бы знать, какое свойство он использует (привет тем, кто не документирует API).
Ну и не забываем про средства — все таки не в блокноте пишут на C# — в Студии отлично работает IntelliSense, показывающий типы свойств в виде иконок.
UFO just landed and posted this here
Почему это свойства — нарушения инкапсуляции?
Публичные свойства это нарушение инкапсуляции. Потому что дают внутренне пользователю представление черного ящика, вместо интерфейса взаимодействия с ним.
Хм… Свойства и поля — это совсем разные вещи. Хотя да, есть ЯП, где поля принято называть свойствами, что не совсем верно, полагаю
Согласен, если свойства считать синтаксическим сахаром призванным заменить теже гетеры, сетеры, к в as3 к примеру. То тогда о нарушении инкапсуляции говорить не приходится. Но я как явист, сторонник подхода что первичен интерфейс. И гетеры естественно вписываются в этот подход в отличии от свойств.
Ну, дык, чего тогда спорить? Собственно, автор статьи про это и говорит, а не призывает вытаскивать наружу данные.
Ж) как обычно спор свелся к синхорнизации терминов и понятию что все правы. что радует.

Противоречие у автора на мой взгляд в том что он видит абстрактный язык без учета его идеологии и истории развития. И где-то свойства прекрасно вписываются, как в php as3 python, то в java или c++ они будут смотреться чужеродно и уродливо.
UFO just landed and posted this here
Свойство может быть частью интефейса, а интерфейс инкапсулирует объект вчистую.

Открытие внутреннего поля — нарушение инкапсуляции.
Потому что это классика ООП, а то что советует автор нарушает классические правила ООП.
Почему свойства не являются классикой ООП?

Они используются почти во всех ООП языках: C#, Delphi, Pethon, Ruby…

Поддержки свойств нет только в C++, Java и, к сожалению, в Javascript.

Только в этих языках приходится либо:

использовать открытые поля — что совсем плохо;

либо явно вызывать методы доступа к данным: obj.getProperty () и obj.setProperty ().
Свойства есть не во всех. public методы есть во всех… Вывод?=)
> Вывод?=)
Использовать свойства там, где они есть, и методы, там где нет свойств. То, что не во всех языках есть свойства, не означает, что их не надо использовать.
Тогда почему статья столь нетерпима? =) Удобное использование свойств есть еще в меньшем подмножестве языков чем чем то в котором свойства есть вообще. Я уже очертил в комментариях проблему, когда разработчику придется либо лезть в чужой код либо компилить в ожидании ошибки либо лезть в документацию исключительно что бы узнать изменяем свойство или нет.
Вроде бы в JAVA7 обещали.
очень жаль :(
а где можно прочитать про отказ и его причины?
>к сожалению, в Javascript.
А можно поподробнее? У меня есть объект Х я не могу получить доступ к свойству Y этого объекта?!
Вы можете получить доступ к полю, а не к свойству.
Видимо я что-то пропустил, либо не так мы друг друга не так понимаем. Что есть поле и главное как выглядит его создание для объекта?
Поле — это то, что вы называете свойством.
Настоящие же свойства позволяют писать код, который будет вызываться при чтении или записи поля. Этот код, например, может проверять, что устанавливаемое значение входит в нужный диапазон, и выбрасывать исключение, если это не так.
Странная у вас какая терминология… В JS есть объект, есть его методы и свойства. От того, что мы через метод объекта производим/не_производим изменения его свойства вовсе не значит, что у объекта есть какие то там «поля».

Если я где-то ошибся, то прошу ссылку на спецификацию/любой_другой_нормативный_ документ в котором бы говорилось, что у объекта есть поле и разьясняется значение данного термина.
Я согласен, что в терминологии Javascript поле называется свойством, но в других языках обычно используется другая терминология. Например, поле versus свойство в Википедии.
Я задал вопрос в контексте Javascript. Задал я его Alik_Kirillovich-у. Задал потому что достаточно хорошо знаю Javascript.

Если вы на это не обратили внимание, зачем было лезть и минусовать? Я не лезу в рассуждения о полях/свойствах того же Delphi потому с ним не работаю и тем более не владею используемой в нем терминологией.

ЗЫ. Поля/свойства/методы, все это на самом деле не имеет значение. А нормативное значение имеет англоязычная терминология. Так что стоило бы приводить именно терминологии данного языка.
> Задал потому что достаточно хорошо знаю Javascript.
Похоже, что это единственный язык программирования, который вы знаете :)

>Если вы на это не обратили внимание, зачем было лезть и минусовать?
Почему вы решили, что это именно я вас минусовал? Минусы я ставлю только если вижу неадекватные сообщения. А если просто не согласен с кем-то, то обычно пытаюсь объяснить, как в этом случае. Видимо зря.

>Я задал вопрос в контексте Javascript.
А статья не была привязана к какому-либо языку программирования. Допустим, в одной деревне в хозяйстве никто не держит коров, но держат коз. И коз в этой деревне почему-то называют коровами. Разве это даёт право говорить, что в этой деревне есть коровы?

>ЗЫ. Поля/свойства/методы, все это на самом деле не имеет значение. А нормативное значение имеет англоязычная терминология. Так что стоило бы приводить именно терминологии данного языка.

Ну хорошо, вот в этой статье говорится, что в Javascript нет property getter-ов и setter-ов.
>Похоже, что это единственный язык программирования,
> который вы знаете :)
Да вам бы в кадровом ахэнствэ работать, с таким наметанным глазом. Пару постов увидел и уже бросаемся выводами. На будущее. Для начала стоило хотя бы посмотреть профайл ;)

>А если просто не согласен с кем-то, то обычно пытаюсь объяснить,
Я собственно это и пытался сделать.

>А статья не была привязана к какому-либо языку программирования.
А причем тут статья? Обращаю особое внимание, что над каждым постом есть такая кнопочка которая называется «Ответ на». Я некомментировал статью. Мой камент относился к сообщению habrahabr.ru/blogs/development/59570/#comment_1617768 и то не со всему, а к некоторой его части. К сожалению Alik_Kirillovich от диалога как то устранился.

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

> Я знаю, для чего нужны свойства. Дело не в этом.
Понятно, сорри за мой пост.


В данной ситуации меня радует только одно. Ваша явная адекватность и готовность к конструктивному диалогу.
Извиняюсь за жирный, но хабр так отформатировал.
Вопрос от нуба (яп Delphi): выходит, свойство — это property (и пара его методов get и set), а поле — любая var переменная (public, protected или private)?
Прошу прощения за задержку с ответом.

В классической терминологии ООП «свойством» называют интерфейс для доступа к данным объекта.

При использовании которого вызываются методы, но вызываются они «прозрачно» для разработчика.

Например, при вызове array.length--, не только уменьшится значение поля length, но и «прозрачно» вызовется метод, который удалит последний элемент массива.

То, что есть в Javascript, в классической терминологии ООП принято называть «публичным полем».

Однако, в терминологии Javascript эти публичные почему-то поля называются «свойствами». Вот цитата из официальной спецификации Ecma-262:
An Object is an unordered collection of properties.
Так что, здесь Вы правы.
Ну слабо богу разобрались. А то исходя из топика выше складывается впечатление, что в Javascript свойств нет. Имхо не стоит вносить в этот еще большую неразбериху, у 4 человек прочевших эту ветку мозги в этом аспекте и так клинит.
Лол! Чем то, что написал Alik_Kirillovich отличается от того, что писал я чуть выше?
А ты не понимаешь? Вопрос был в контексте Javascript, ты же ударился в общие рассуждения. Он же четко обрисовал оба контекста (Javascript контекст и не-Javascript).
Для C++ особо рьяные авторы рекомендуют прокси объекты, а менее рьяные — несколько упрощённый подход:

private: int age;
public: void Age(int newage) { age = newage; }
int Age() { return age; }

Записи вида o.Age(10); и a = o.Age() всё же выглядят не так уродско, как гет/сет.
Методы как и функции принято начинать с глагола, т.к. метод и функция ДЕЛАЮТ (ПОЛУЧАЮТ-get/УСТАНАВЛИВАЮТ — set). Потому такой подход ничем не красив.
Я бы сказал, что в данном случае как раз глагольный подход некрасив. Давайте сведём к очевидному.
x = 15;
лучше, чем
set(x, 15);

Если бы это было не так, мы бы все и писали — set(x, 15)

Если я хочу присвоить некое значение некому полю, почему синтаксис должен отличаться от «x = 15»? Нам говорят, что это нарушает инкапсуляцию и предлагают гет/сет. Но ведь авторы и Делфи, и C# явно тоже не любят гет-сет — и неслучайно ведь не любят, и неслучайно придумали properties? Или будете говорить, что это всё ненужные выдумки?

Так вот, если уж в используемом языке нет properties я думаю, что как раз запись o.Age(10) стоит стилистически посередине между o.Age = 10 и o.setAge(10).
«Так вот, если уж в используемом языке нет properties я думаю, что как раз запись o.Age(10) стоит стилистически посередине между o.Age = 10 и o.setAge(10).»
Не согласен, если уж в языке нет properties, то лучше get\set. Если o.Age() похоже на геттер, то o.Age(10) — в общем случае может делать все что угодно, в то время как o.setAge(10) явно говорит об изменении внутреннего поля.
На самом деле идеального подхода нет.

Вот ещё один (я его тоже всерьёз рассматриваю):

int Age() { return age; }
int& rAge() { return age; }

Тогда можно писать
o.rAge() = 10;

При этом у нас всё равно rAge() — полноценная функция, в её теле можно следать какие-либо внутренние действия.
Честно говоря не очень хорошо помню С++.
Однако если это и работает, то выглядит не совсем обычно «o.rAge() = 10;».
Но и это не главное, насколько я понимаю чтобы вы в rAge не сделали, то все равно после вызова age = 10, а это уже мало чем отличается от раскрытия внутренних полей объекта(присвоить можно все что хочешь).

PS: Поправьте, если я что-то не так понял, последний раз на с++ писал в университете.
Да, присвоить можно всё, что хочешь. Там, где я читал об этой идее, утверждалось, что не так уж и часто нужно ограничивать присваивание — чаще нужно как-то отреагировать на сам факт присваивания, а возможность это сделать у нас есть.

В С++ можно сэмулировать почти что полноценные properties, но это довольно хитрая методика, и применять её направо и налево я бы не стал.

Допустим, нам нужно написать геттер-сеттер на объект класса T. Создаётся новый класс TProxy, для которого есть перегруженный оператор = и операция приведения к типу Т. Далее это всё описывается таким образом, чтобы при попытке присваивания объекту вызывался оператор = (вот он наш сеттер), а при попытке считывания — приведение к типу (вот геттер).

Т.е. в принципе это всё работает, но на каждом шагу эту методику использовать слишком сложно.
В MVC++ есть declspec(propget) и declspec(propset) или вроде того.
Это не в c++, это в непонятной поделке, под названием managed c++.
Как насчет ссылочки на стандарт?

Я с таким же успехом могу Вам написать про свойства в Qt…
Какой нафиг стандарт?

Я же русским языком написал — не в С++, а в MVC++. Microsoft Visual C++.

И в MSDN кстати тоже это же написано — это все Microsoft specific.
Я все равно не понимаю.

То, что это — Microsoft specific не мешает применять данный метод в реальных приложениях, которые не требуют кроссплатформенности.
Да, простите. Я вечно забываю, что кроссплатформенность важна не для всех продуктов :-[

В любом случае, я считаю, что навешивать на и без того сложный и перегруженный С++ конструкции типа
__declspec(property(get = getprop, put = putprop)) int the_prop;
ради сомнительной пользы да и еще с потерей кроссплатформенности — это перебор.

Мне нравятся свойства в C#, потому как это изначально встроенная фича языка, но в С++ можно прекрасно обходиться и без этого.
Нормальная, к слову сказать, конструкция, и ее очень удобно использовать, когда пишешь ActiveX. Все аксесоры и мутаторы уже определены, вешается declspec и вуаля — доступ к плюсовым свойствам из плюсов же облегчается.
UFO just landed and posted this here
Для справок: у Delphi и C# автор по большому счету один — Андерс Хейлсберг.
Делаем выводы.
Первый, который напрашивается — C#, как и Delphi, надолго поселится в умах и сердцах большого количества разработчиков. Так же, как php и vb
(простите, случайно нажал минус, не хотел!)

Вывод один — человек умный :)
По сути считывание-установка — это просто перегруженная операция =.
В С++ есть определённый дефект: нельзя простым способом с помощью перегруженной операции указать один алгоритм для считывания, а другой — для присваивания, считывают объект или присваивают. Можно проблему решить с помощью прокси-объектов, но это сложно. А вот пропертис — разумное решение.
>Но ведь авторы и Делфи, и C# явно тоже не любят гет-сет
множественное число тут не уместно, это один и тот же человек
Я предлагаю вместо obj.getProperty() и obj.setProperty(value); использовать свойства.

Не открытые поля, а именно свойства.

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

class Person
 {
 private long _money;
 public long money
  {
  get
   {
   /*
   !!!!!!!!!!!!!
   Метод для получения данных
   */
   return (_money);
   }
  set
   {
   /*
   !!!!!!!!!!!!!
   Метод для записи данных
   */
   _money = value;
   }
  }
 }

/*
Вызов методов происходит прозрачно
*/
Person psnBillGates = new Person ();
lngOldRiches = psnBillGates.money; //Чтение
psnBillGates.money = lngNewRiches; //Запись
psnBillGates.money += 1000000000; //Инкрементация


Понимает ли программист исследующий точки использование свойств изменяемые ли они или нет? =)
Честно говоря, ни разу не возникало такой проблемы на практике.
В самом крайнем случае, компилятор и API documentation говорят об этом.
Хотелось бы услышать грамотный контр-аргумент. И то, какие альтернативные методы позволяют делать тоже самое, но еще проще и «со всеми нужными фичами». Ибо, сдается мне, эти свойства (а также, например, в C#, еще и неявное определение стоящих за ними приватных переменных) не просто так придумали.
Безусловно не просто так, и я собственно не спорю с тем что использование свойств в дотнете в окружении VS удобно. Но использование свойств в других случаях и на других IDE, не обязательно будет удобно. Это простейший контр аргумент.

Компилировать большой класс исключительно что бы узнать есть ли ошибка в коде или нет это слегка так php-стиль, вам не кажется? А API documentation есть не всегда. В случае если другая часть проектной команды разрабатывала класс? =)
И чем в вашем примере это лучше, чем использование открытых полей?

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

Апофеозом свойств является такая реализация метода get, которая возвращает новый объект при каждом обращении, так что в коде

someObj.SomeProp == someObj.SomeProp

вычисляется в false!
геттер/сеттер сгенерить — дело пары минут. Зато если понадобится что-то править — заготовки уже есть. :) В С++ тоже можно сделать данные-члены открытыми — однако так никто (надеюсь) не делает.
В с# автоматические get-тер / set-тер. И если надо что-то менять, их все равно придется сильно-сильно править. Так что я считаю, что ценность свойств весьма завышена.
Свойства, даже тривиальные, нужны, чтобы можно была возможность в будущем их изменять, не меняя интерфейс класса. Если вы будете использовать поля вместо свойств, а потом вам потребуется добавить дополнительную логику при установке или чтении какого-то поля, то вам придётся переделывать его в свойство. Даже если имя нового свойства будет совпадать со старым именем поля, то всё равно придётся перекомпилировать весь код, который его использует.
Я знаю, для чего нужны свойства. Дело не в этом.

Прочтите, пожалуйста, внимательно, мой исходный вопрос.
«чем в вашем примере это лучше, чем использование открытых полей?»
> Я знаю, для чего нужны свойства. Дело не в этом.
Понятно, сорри за мой пост.

Согласен, что если класс Person и его поле money используются только в пределах одной сборки, то нет особого смысла делать его свойством. Но если он используется во внешнем интерфейсе сборки, т.е. может вызываться из других сборок, то Money IMHO должно быть свойством.
Если поле не является immutable, то его копированние в геттере является стандартной практикой.

А оператор == можно переопределить.
>>И чем в вашем примере это лучше, чем использование открытых полей?
>>И чем в вашем примере это лучше, чем использование открытых полей?
В данном примере ничем, а в более общем случае setter (как и getter) может попутно много сего ещё делать, например, записывать в базу данных или перещитывать зависимые от этого свойства другие поля и т.п.
Прежде всего, прочтите мой ответ выше (http://habrahabr.ru/blogs/development/59570/#comment_1618086). Я вас уверяю — я знаю, зачем они нужны.

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

Во-вторых, ваш пример с БД также неудачен — я первым оторву руки тому, кто в сеттере или геттере напишет код доступа к данным в БД. Свойства имитируют поля, поэтому им следует копировать и семантику полей, а именно:

— они не должны обладать серьезными side-эффектами, типа запись чего-то в БД или отправка email на Марс;
— должно выполняться равенство (obj.SomeProperty == obj.SomeProperty), в противном случае очень легко нарваться на долгие ночные бдения под отладчиком.

Говорю вам как архитектор/техлидер проекта (C# + Spring.NET + NHibernate)
Вы оба правы:

В некоторых случаях лучше использовать свойства.

В некоторых — явные методы (например, как правильно заметил sse, при наличие сильных побочных эффектов при доступе к данным).

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

Пример с БД, конечно-же, неудачен — мне лень было придумывать удачный пример ночью, а все исходники на работе :)
Можно немного переделать ваш пример с БД — допустим, в сеттере свойства у объекта выставляется признак IsDirty, который говорит о том, что объект был изменен и теперь отличается от того, который счас лежит в БД. Не суть. Ответ я получил :)
UFO just landed and posted this here
Я бы сказал — недопустимо.
Для такой семантики лучше сделать AddDays(), AddMoths() и т.д.
UFO just landed and posted this here
Ну отличный пример — свойство Visible.
Когда оно меняется, до хрена всего возможно надо сделать :)
UFO just landed and posted this here
Да, так.

Day — хорошее свойство, но только для чтения.
Как длина строки (Length).
UFO just landed and posted this here
Опять-таки, чтобы подчеркнуть простоту и дешевизну «узнать свойство» по сравнению с «получить результат метода».
Т.е. String.getReversedString() и String.Length — имеют разницу :)

Опять-таки имеет ли смысл для caller вводить временную переменную:
string reversed = string1.getReversedString()
foo1(reversed1);
foo2(reversed2);
А с «дешевым свойством» так делать не надо:
bar1(string1.Length);
bar2(string1.Length);
UFO just landed and posted this here
Потому что операция AddDays логически изменяет не только это свойство, но и весь объект в целом. Клиент объекта со свойством ожидает, что методы могут менять состояние объекта как угодно, а операции над полем — только это поле. Не стоит его разочаровывать :)
UFO just landed and posted this here
UFO just landed and posted this here
Если вам нужно значительно изменять состояние объекта, лучше это сделать методом. Если же нужно изменить только это свойство, и никаких side-эффектов то лучше — свойством.
Вы только представьте: У нас есть функция в 300 строк кода [2]. Где-нибудь на 200-й строке нам надо поменять две переменные местами. Для этого мы лезем на 200 сток выше в начало функции, объявляем переменную temp, которая не имеет никакого отношения ко всей функции, а используется только один раз в одном месте, потом опять возвращаемся к 200-й строке и меняем переменные местами… По-моему, это просто кошмар.


Т.е. 200 строк пролистать в поисках переменной — это плохо, а пролистать всю статью в поисках второй сноски и найти её вообще на другом сайте — это нормально? ;)
К сожалению, на Хабре есть какое-то непонятное ограничение на объем статьи: поэтому сноски не уместились.

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

Прошу прощения за доставленные неудобства (возникшие по независящей от меня причине).
UFO just landed and posted this here
ага, особенно если учесть, что размер всей этой страницы (включая камменты) 46,16 КБ

К.О. намекает, что существуют еще MEDIUMTEXT(MEDIUMBLOB) и LONGTEXT(LONGBLOB)
В действительности размер страницы на порядок больше. GZip.
да, не могу не согласится. мое упущение. но все равно, ожидаем перехода с TEXT на LONGTEXT.

P.S. (на правах рекламы) Антикризисное предложение: включите gzip и экономьте деньги на трафике!
UFO just landed and posted this here
пусть переходят на postgres
Кста, а функция в 300 строк кода — это нормально? ;) Обычно в учебниках пишут, что если функция вылезает за одну-две страницы — уже пора задуматься.
А писать функцию в 300 строк — это хорошо?
Спросите у автора этой функции :)
за 300 строк фунции нада по рукам бить. или очень убедительно обосновать такое решение.
>А писать функцию в 300 строк — это хорошо?

Да, Вы правы.

В сноске, которая, к сожалению, не уместилась из-за ограничений на объем статьи, я написал:
Вообще, надо стараться избегать длинных функций и методов. Одно из правил рефакторинга: «если текст метода не умещается на экране без прокрутки — это уже повод разбить его на несколько более коротких методов».

Однако, это далеко не всегда удается, например при генерации отчетов или при реализации сложных вычисления.
С другой стороны в примерах выделенных в сноске, вся функция будет единым блоком кода.
UFO just landed and posted this here
Особенность языка, к сожалению. Функция в 2000 строк выполнится быстрее чем 10 в 200
Это особенность плохого оптимизатора. Который написан на C. А на C (читай выше) особенно не развернёшься.
Насчёт пункта 6. В PHP, например, при использовании циклов нужно хранить размер массива в переменной, а не использовать count() в условии, т.к. он вычисляется на каждой итерации.

Насчёт пункта 7. Опять на PHP. В классе A объявляется protected свойство x. Класс B наследуется от класса A. Задача получить значение свойства x решается через объявление в классе B public метода getX(). Думаю не нужно объяснять, для чего это применяется.
> Думаю не нужно объяснять, для чего это применяется.
объясните мне, пожалуйста.
Читайте про инкапсуляцию и доступ к данным.
А расскажите задачу в которой требуется такая структура? Правда интересно зачем создавать геттер только в B если он отсутствует в А.
Это, вообще говоря, сильно упрощенный пример, чтобы не растекаться мыслью по древу, так сказать.

Но, допустим, есть базовый класс — сборщик неких данных. Нужно дать интерфейс для получения этих данных без возможности их изменения.

<?php
interface AB
{
public function getX();
}
class A
{
private $x = 1;
protected function getX()
{
return( $this->x );
}
}
class B extends A implements AB
{
public function getX()
{
return( parent::getX() );
}
}
$b = new B();
echo( $b->getX() );
?>
Упс, простите за форматирование. Здесь как-то можно красиво вставлять код?
Ну реализация и так понятна. Но по каким причинам запрещен прямой вызов getX в базовом классе?
А он и не должен просто возвращать данные. Возможно, я хочу как-то фильтровать их для клиента. Возможно, по какому-то параметру. Так что я ограничиваю доступ к исходнику и выдаю только то, что считаю нужным.
О теперь понятно. Например набор классов-потомков с различными фильтрами. Спасибо. Такое действительно может встретится и достаточно часто.
А в базовом классе метода getX() может не быть. Ну, не нужен он там.
Кстати, нет. Если я не объявлю этот метод, то не смогу получить доступ к private x напрямую. Если же я объявлю его как protected — смогу изменить из потомка.
Интерфейс получения данных без их изменения сильно удобнее реализовать с помощью паттерна Proxy.
Разница, вобщем-то, не сильно велика.
это вы лучше почитайте про инкапсуляцию и доступ данных, приведенный вами пример часто упоминается под названием Паблик Морозов. подобная конструкция никогда не нужна в нормально спроектированных системах.
Тогда читайте ещё раз и внимательнее, попробуйте найти отличия от примера по Вашей ссылке в моих комментариях. Больше я Вам, увы, ничем помочь не могу.
Согласен на все 100% в ЯП, где нету свойств для доступа к переменным класса, использование напрямую переменных класса (что значит вывод их в область действия public является страшным злом и нарушением инкасуляции)
Пункт 3, кстати, очевидно не относится к объектно-ориентированным языкам.
Пункт 3 [Отсутствие локальных функций], кстати, очевидно не относится к объектно-ориентированным языкам.
В основном да, но не совсем: например в C++ нет полноценных локальных функций.
А разве это не решается классами и пространствами имён? Я уже давно не силён в C++, если что.
В C++ нельзя объявить функцию в теле другой функции, например, как в Javascript:

function main ()
 {
 //...
 function sub1 ()
  {
  /*
  Вот это локальная функция sub1, объявленная в теле функции main
  */
  }
 function sub2 ()
  {
  }
 }
Так вопрос не в том, можно ли принципиально их использовать или нет, а в том, чтобы создать правильную структуру модулей. Классы для этого и нужны.
потому что в C++ нет замыканий и функции внутри функций просто не нужны, вот и все. Хоть этим не стали язык перегружать)
этим=функциями внутри функций, за замыкания я обеими руками за
В избранное! Надо подробно разобраться с этой темой на досуге! спасиббо!
По части пунктов выходит, что, если я программирую на Си, то я жуткий быдлокодер. :-)
Вы не правы :)…
Просто Автор не программирует на Си ;)
Насчет функций-аксессоров — момент спорный. Если аксессор только возвращает значение внутренней переменной (то есть нужен исключительно для инкапсуляции), то он может быть свойством. Если же он делает какую-то дополнительную работу — можно и нередко нужно определить его как метод.
Если же он делает какую-то дополнительную работу — можно и нередко нужно определить его как метод.

фокус в том, что метод это неудобно и на замену ему предлашаются get/set методы реализующие необходимый функционал но внешне выглядящие как свойства. это есть в c#, в AS2/3(!) и до сих пор нет в Java :(
Что такое удобно в данном контексте? Человеку работающему со сторонним фреймворком зачастую полезно знать что он вызывает метод.
Человеку работающему со сторонним фреймворком зачастую полезно знать что он вызывает метод.

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

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

Представьте ситуацию, когда я пишу
for i = 1 to 100000 do a[i].field = i; next i

а потом оказывается, что при за присваиванием скрывается хитрая функция, которая всё тормозит. Обидно :(
А главное — поди сообрази.
Самое обидное, если это всё ещё и выдаёт exception где-нибудь в глубине методов, которые вызываются в во время этого присваивания.
Да, именно об этом я и говорю.

Т.е. в C#, Delphi, Pethon, Ruby мы можем писать:

Person psnBillGates = new Person ();
lngOldRiches = psnBillGates.money; //Чтение
psnBillGates.money = lngNewRiches; //Запись
psnBillGates.money += 1000000000; //Инкрементация


А методы доступа к данным вызываются сами, «прозрачно» для разработчика.
А в случае, когда psnBillGates.money лежит в БД и каждое изменение должно выполняться транзакционно в реальном времени?

Тут начинаются проблемы — разработчик, видя свойство, думает, что оно будет работать быстро и красиво. А оно, мало того, что тормозит, так еще и выкидывает DbConnectionNotAvalibleException.
За написание Pethon, этот самый Питон вас загипнотизирует, удушит и съест.
var a = someInstance.GetField();
var a = someInstance.Field;

someInstance.SetField(value);
someInstance.Field = value;

Разницы в удобстве не вижу.
Пример №2 с авторизацией косячный — часть кода из другого места вставлена.
Щит, всё там верно. Неверно прочитал код как раз из-за описываемой проблемы и неправильной иерархией (((-:
Всегда удивляет когда при написании «правил» люди сами их нарушают: "… страшное зло[1]..." и далее по тексту, в первую очередь отсутствие «разъяснения» сносок в блоках к которым они относятся (что следует из первого вашего правила по аналогии), во-вторых отсутствие их в принципе… копипаст?
UFO just landed and posted this here
Да, но не того почему сноски не находились в блоке к которому они относятся, что следует из первого правила его топика. Из его ответа следует что «сноски» не вместились соответственно они были в конце о чем дополнительно свидетельствует их нумерация, об этом мой и коммент…
Возврат результата функции через ее параметр
Цитата:
Слава богу, в других языках классы можно описывать прямо внутри функции, а, например в Javascript можно просто возвратить объект, нигде отдельно не описывая его структуру.

Вот это настоящая красота!

Готов поспорить, если последнее предложение про красоту не есть шутка.
С помощью такого приема нарушить «хрупкую красоту кода» проще некуда!

Понять, объект какого класса функция вернет без инспекции кода самой функции невозможно.
если хотите понять структуру, вам в любом случае нужно проводить инспекцию — в функции или где-то в отдельном месте. Но в большинстве случаев это и не нужно — по описанию или названию функции понятно что она возвращает.
Не, «хрупкая красота кода» так не нарушается. Все красиво. Легко нарушается «хрупкая работоспособность кода»
Нет, тут я думаю автор имел ввиду динамический созданный объект в функции пример:

function _Math (a, b) {

return {
sum = a + b,
dub = a — b,
div = a / b
}

}

Этот способ на самом деле очень удобный и красивый. Результат от функции разберет любой дебагер и даже alert(_Math(5,1).toSoure()) в FF
Да, но автор забраковал С++ по причине того, что там такого нет. Не думаю, что в C++ такой механизм был бы уместен.
это красиво для яваскрипта. В нормальных же ОО языках программисты будут матюкаться, получая на выходе объект без интерфейса…
Для нормальных языков результат от функции лекго отслеживаем по дебагеру, а иногда легче заглянуть в дебагер чем в многотомные доки :). Но в целом я согласен — абстрактные объекты тоже зло, особенно в типизируемых азыках. Но на каждый случай есть исключение.
Приплели сюда зачем-то «нормальные ОО языки», как будто javascript ущербный какой-то.
ОО тут вообще не при чем, важно то, допускает ли язык динамическую типизацию.
Извините, если вас не так понял.
Всё верно кроме:

«Доступ к свойствам объекта через obj.getProperty() и obj.setProperty(value);»
— негодую! Это как раз правильный подход, особенно, если программа многопоточная и эта проперти неатомарная.

Для себя я решил — доступ к полям напрямую допустим только если это
1) Структура атомарных данных при невозможности испортить их из разных потоков одновременно.
2) Обращение идёт из самого объекта и поле нестатическое.

я бы сказал «Это правильный подход _только_ если программа многопоточная и это проперти неатомарное и используется между потоками...». Во всех остальных случаях это куча ненужных букоф.
А как же органичения, ленивая загрузка и прочие прелести?
Если производятся какие-то дополнительные действия — это уже по логике вызов функции, а не просто узнавание параметра.
Хотя например на Питоне можно сделать так что функция будет вызываться при обращении к аттрибуту.
в Java например нельзя.
И как быть если неожиданно понадобилось добавить логику?
Капитан Очевидность сказал бы что нужно добавить функцию, и соответственно её вызов. ;)
Ну к этому и вернулись.

Можно ещё переформулировать вопрос позаковырестее, кстати:
«И как быть если неожиданно понадобилось добавить логику классу, производному от класса С ЗАКРЫТЫМИ ИСХОДНИКАМИ в котором объявлено это поле?»
Если вы берете свойство, то должно браться только свойство, и ничего больше. Если происходит что-то другое, нужна функция с другим названием в любом случае. Я еще могу понять сеттер, в котором какие-то проверки, но тогда они должны быть сразу сделаны в исходном классе. Конечно можно представить себе задачи в котором без геттера и сеттера не обойтись, но я говорю об общем случае.
Если уж на то пошло, в общем случае не всегда понятно, нужна ли будет многопоточность в будущем. Хотя бы поэтому стоит сделать пару методов доступа.
Во все сто тысяч мест откуда идет обращение к этому полю, ага.
А в чем проблема? Страх перед рефакторингом — прямой путь к некрасивому коду. Всё равно с самого начала всего не предусмотришь и что-то придется менять.
А ещё не следует бояться рефакторинга кода третьих лиц, который строится на основе вашего кода, так что-ли?
зачем, мы же не нарушаем обратную совместимость. А если «третьи лица» хотят новые фичи — пусть тоже меняют код.
Они не смогут изменить наш код.

Например:

class A (наш — исходник сокрыт) {
public getX() { return m_x; }
}

class B (чужой) extends A {
public getX() { return m_x + 1; }
}

и тогда можно будет сохранить вызов x = obj.getX() во всех случаях.

Если же в классе А нет метода getX(), то придётся делать так:
if (obj instanceof B) {
x = obj.getX();
} else {
x = obj.m_x;
}

И так каждый раз при необходимости получить m_x
Нарушается принцип ООП.
я прекрасно понял о чем вы. Просто я считаю что перегружать getX само по себе некрасиво, если оно призвано просто брать поле класса. Если предполагается что может браться что-то еще — тогда конечно другое дело.
представляю радость человека, который ревьюит результаты вашего рефакторинга
Это куча букв только в случаях «objObject.setProperty (objObject.getProperty () + 1);»
Иначе objObject.getProperty() не на много длиннее objObject.property.

«objObject.property1++;»
Конечно так удобнее, но тут уж от языка зависит — в конечном счете, это все равно геттер и сеттер, если это не public поле.
Только вот эта операция выглядит, как атомарная, но на самом деле таковой не является.

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

В случае, если блокировка на таблицу была наложена между операцией получения свойства и операцией установки нового свойства — вы получите плавающую ошибку, которую невероятно трудно обнаружить.
Имеется ввиду objObject.property1++; конечно.
Это может быть поле быза данных, ширина окна или просто любое целое число.
Соглашение о геттерах и сеттерах отлично работает и достаточно удобно.
Однако тут все зависит от объекта, если сразу видно, что над полем будут производиться операции ++, --, +=х, то не вижу ничего плохого при использовании свойств (за которыми реально стоят get и set), если язык это позволяет и не нарушает логику работы. Как впрочем и в отсутсвии свойств не вижу трагедии.
Я не против свойств, я против подхода «никаких аксессоров — только свойства».
А кто вам гарантирует, что i++ атомарная? Это может быть не так даже в случае встроенного типа. Например, у нас 32битный процессор и мы делаем ++ 64 битное переменной.
Существует гарантия, что i++ для int, например, выполнится всегда, не выдавая ошибку переполнения стека, отсутствия памяти, отсутствия соединения с БД и прочее.
Есть.
От нормальной проперти мы тоже такого же поведения ожидаем.
Я в этом месте окунул голову в ведро с валидолом:-)
Несколько return-ов у функции.

function Fa(a, b)
{
if (a < b){
return 1;
}else
{
return 2;
}
}

Красивее так
function Fa(a, b)
{
if (a < b){
result = 1;
}else
{
result = 2;
}
return result;
}

Спорно. Если уж говорить о приведенном примере, то лучше return a < b? 1: 2. А вообще иногда вовремя вставленная точка возврата — это возможность избежать пары if'ов размером почти со всю функцию.
Пример был дан для краткости. Конечно его можно написать и более простым способом.
Да, я не умею писать & lt; вместо знака меньше. И теперь не знаю, как можно прочитать ответ. Даже в коде страницы не нашел.

НЛО, опубликуй вместо моей писанины надпись, пожалуйста.
парсер, несомненно, лох.
но предпросмотр рулит.
Читал его статью в свое время. Для меня это один из принципов хорошего программирования. И если перечислять ряд приемов разрушающих красоту, то это один из них.
Сильно зависит от функции. Если большая и сразу невозможно ее всю охватить взглядом, тогда да. Если функция умещается в 10-15 строк (как в Вашем примере), то не вижу смысла вводить дополнительную переменную для результата: в данном случае она только захламляет код; а возможные точки выхода и без того будут видны.
Скорее всего если функция уместиться в 10-15 строк, то несколько return-ов можно избежать или они там вообще ненужны.
А с исключениями также поступать? Типа,
function Fa(a, b)
{
exception = undefined;
if ( a < b ) { exception = new Less() }
elsif ( a > b ) { exception = new More () }

if ( exception ) { throw exception; }
return «success»;
}

Бред? Зато в одном месте точка выхода и киданий исключений. :)
Согласен бред. Но исключительная ситуация на то и исключительная, что можно и не следовать этому правилу. Кроме того в моем комментарии про исключение ни слова :) я говорил только про return. Хотя exception тоже своего рода выход из функции.
return в вышеуказанном примере тоже, по сути, кидание исключения особого рода (с той особенностью, что оно заранее предусмотрено сигнатурой функции, а не работает через механизм исключений языка).

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

object bRes1 = null;
object bRes2 = null;
try
{
bRes1 = ShellInfoManagerFactory.GetManager(first).GetPropDataByColumnIndex(columnNumber);
bRes2 = ShellInfoManagerFactory.GetManager(second).GetPropDataByColumnIndex(columnNumber);
}
catch (NullReferenceException)
{
return 0;
}

if (bRes1 == null)
{
if (bRes2 == null)
return 0;
else
return 1;
}

IComparable cmpRes1 = (IComparable)bRes1;
return cmpRes1.CompareTo(bRes2);
А вот за это — можно и убить :)

catch (NullReferenceException)
Потому что NullReferenceException (NRE) свидетельствует о том, что это — ошибка дизайна системы, исполнение кода по ветви, не предусмотренной программистом.

Нужно не отлавливать их, а исправить причину.
То есть мне выбросить исключение такого типа нельзя?
Можно, руки же вам никто не связывает :) Но это будет некорректно.
NRE — одно из немногих исключений, которое используется рантаймом (бросается при выполнении IL-инструкций, а не при throw new XXXX()) как «последнее предупреждение» программисту о том, что его код пытается выполнить недопустимые с точки зрения CLR действия.
Насколько я понял функция получает номер столбца, получает значения причем непонятного какого-типа и сравнивает их?
Думаю тут скорее не return 0 или 1 нужен, а скорее генерировать исключительные ситуации, когда не получен одно из значений.
В принципе, именно так. А return 0, 1 или -1 — это стандартные коды операции compare.
Хорошая статья, и особенно понравилось оформление.

Вот и выросло счастливое поколение программистов, никогда не писавших на голом «C», и считающих Java «классическим» языком. ;)

Но есть ещё закрытые заповедники, в которых приходится писать и поддерживать приложения, работающие на зоопарке из 3-4 и более аппаратных платформ. В том числе 16-битных. В том числе с ограниченным размером памяти и крошечным стеком. И приходится бедным старым неграм кодерам объявлять все переменные в начале функций, передавать структуры по указателям и даже (слабонервным дальше не читать) вызывать функции через массивы указателей.

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

Простой пример:
int ai,bi;
float af,bf;
char *ap,*bp;


Неправильно:
void swap_something(int what) {
int tmpi;
float tmpf;
char *tmpp;
switch(what) {
case 0:
tmpi=ai; ai=bi; bi=tmpi;
break;
case 1:
tmpf=af; af=bf; bf=tmpf;
break;
case 2:
tmpp=ap; ap=bp; bp=tmpp;
break;
}
}


Правильно
void swap_something(int what) {
switch(what) {
case 0:
{ int tmpi=ai; ai=bi; bi=tmpi; }
break;
case 1:
{ float tmpf=af; af=bf; bf=tmpf; }
break;
case 2:
{ char* tmpp=ap; ap=bp; bp=tmpp; }
break;
}
}


Во втором случае на стеке не выделяется память для ненужных переменных, да и вообще код становится читабельнее.
я это прочитал. очень интересно. есть что извлечь для себя
Рекомендую на эту тему классический труд Стива нашего Макконнелла «Code Complete»
Да, в не уместившихся здесь сносках я как раз порекомендовал книгу «Совершенный код».

Кстати, насчет 1-го пункта («Объявление всех переменных в начале программы») Макконелл еще более радикален чем я. Он пишет
В идеальном случае сразу объявляйте и определяйте каждую переменную непосредственно перед первым обращением к ней
Помнится Effective C++ тоже самое рекомендует.
Я лично придерживаюсь правила, что если у нас нет разумного начального значения для переменной, ее пока не надо объявлять.
UFO just landed and posted this here
У того же Макконела, вроде, написано, что функция более двух «экранов» — плохая.
да у всех это написано =) и не зря написано
самое грустное что первому пункту правил нас учат в школе и университете, на уроках по структурам данных и основам алгоритмов
в универе, видимо, так проверять проще =)
А почему грустное? Вы считаете умение контролировать ресурсы (в данном случае — память) обучающимся «основам алгоритмов» плохим?
Нет. Просто после этих основ не рассказывают как делать красивее и правильнее. Благо есть умные книги от умных людей:-)
Верно. Надо просто правильно отноститься к учебникам и учебным программам (и к учебным языкам, вроде Бейсика и Паскаля). Реальная жизнь всегда будет сложнее и запутаннее, а соответственно, и приёмы программирования в ней другие, более сложные.
>К счастью, поддержка локальных функций есть почти во всех «новых» языках, как динамических (Javascript, Python), так и классических (Java)

в яве нет локальных функций
Зато есть анонимные классы
как их использовать, чтобы получить вложенные функции?
Если у вас есть некоторый объект, например экземляр анонимного класса, то как вам использовать его метод? Так же, как и методы любого другого объекта.
такой огород — создавать отдельный класс, а в нём функцию — совершенно неоправдан в плане украшения кода. так что будем считать, что в яве с этим направлением не всё хорошо
Да, согласен, это неполноценные вложенные функции, и так их обычно не используют. Но в качестве closures анонимные классы очень удобны. Я имею в виду, когда надо передать в какой-то метод объект, реализующий определённый интерфейс.
Возможно, имелись в виду inner classes.
Если быть точнее их комбинация. Согласен что это тот еще огород, но такова идеология языка, где объект ключевая единица.
Если кто еще не читал — хорошая книга, применительно к красивости и правильности писания на C++:
Греб Саттер, Андрей Александреску — Стандарты программирования на C++
ISBN 5-8459-0859-0
Сколько пафоса и нетерпимости. setProperty/getProperty — хороший стиль. Позволяющий работать с черными ящиками. Это ОЧЕНЬ хороший стиль. )

/me завзятый явист.

;)
это точно
к тому же, в этих чёрных ящиках на этапе set очень часто производится проверка входных данных

но стиль qt мне нравится больше:
setProperty() => setProperty()
getProperty() => property()
С «черными ящиками» можно работать использую свойства.

Т.е. красиво писать:

Person psnBillGates = new Person ();
lngOldRiches = psnBillGates.money; //Чтение
psnBillGates.money = lngNewRiches; //Запись
psnBillGates.money += 1000000000; //Инкрементация


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

См. этот мой комментарий.
Я уже очертил одну проблему использования свойств. И вторую о том что удобно их использовать исключительно в IDE MSVS под дотнетом. =) Чуть выше еще одна проблема неожиданная медленность получения свойства например и возможные exceptions при просто прозрачном получении свойства. Вы готовы ожидать что a = obj.b выкинет исключение? Я нет.
Так a = obj.b не должно кидать исключение, как правило. Если нет — это плохой дизайн :)
Собственно, в этом и разница между свойствами и геттерами/сеттерами. Свойство должно работать «почти как переменная» — дешево и безопасно.

Например, пусть у объекта string есть длина (length) и перевернутая версия (ReversedCopy).
Длина есть всегда, узнать ее относительно дешево и абсолютно безопасно, если у нас есть нормальный объект string. Ее мы делаем свойством.
ReversedCopy сделать дорого (надо кучу всего делать) и не безопасно (выделяется новая память, можем упасть). Таким образом тут мы делаем getReversedVersion()
Собственно, вот и все.
Вот тут я с вами абсолютно согласен.
Но разве у топикстартера не написано «Использование get/set вместо свойств дурной тон»? =) Использование геттеров и сеттеров полезно и удобно. Использование свойств полезно и удобно. =)

пс. Очень хотелось в комментарии по рассуждать о спецификации JavaBeans и POJO и о том как удобно с ними работать… Но удержался. Выбор технологии зависит от задачи все таки.
Использование геттеров и сеттеров полезно и удобно.
Использование свойств полезно и удобно.

И оба эти утверждения не исключают «Использование get/set вместо свойств дурной тон» :) Как и наоборот, впрочем.
Ну тогда уж для полноты картины «Использование get/set вместо свойств там где они реализованы — дурной тон». ;) Как и наоборот да.
UFO just landed and posted this here
Радикально, но имхо неполно и ряд мест спорны.
Например, на счет локальных функций — все зависит от конкретного случая. Иногда их использование скорее вредит, нежели наоборот.
Например, на счет локальных функций — все зависит от конкретного случая. Иногда их использование скорее вредит, нежели наоборот.

Совершенно согласен.

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

Но плохо, если язык не дает нам использовать локальные функции в том случае, когда они действительно требуются.
а когда они действительно требуются? Если не брать в расчет уже десять раз повторенные мной замыкания?
Вообще то что перечислено в статье, на мой взгляд является достаточно экзотическими антипаттернами.
Самые распространенные проблемы, с которыми сталкиваюсь я — это:
1. Телеграфный стиль: ses=stmt.getSess()
2. Магические константы.
3. Обильное комментирование вместо выделения метода
4. Смешивание в одном методе кода из разных уровней абстракции.
5. Разнузданное форматирование.

Это то что относится именно к красоте кода.
Что есть магическая константа?
Число, назначение которого непонятно из контекста.
Обычно — это число, используемое в коде в «в голом виде» и значение которого не очевидно.

См. например в Wikipedia
Обычно — это число, используемое в коде в «в голом виде» и значение которого не очевидно.

См. например в Wikipedia
Хорошая статья, ППКС. Что характерно, многие популярные языки не позволяют напрямую писать красивый код по этим правилам.
И приходится писать красивый код по другим правилам :)
Статья действительно интересная. Для себя взял несколько уроков.
p.s.
улыбнуло: println («Число глюков: „+ objWinVista.bugsCount); //1 000 000 000 :-)
не согласен насчет объявления переменных в середине кода
может быть логически это и правильнее, но наглядность и понятность кода для постороннего программиста ухудшается в разы

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

по поводу else..if — я бы рекомендовал стараться обходиться вообще без else, везде где это возможно
это сделает ваш код еще проще для изучения другими программистами
к слову говоря, конструкции типа if… else if… else if или switch() какие-нибудь — уже сигнал к рефакторингу кода
забавно, заминусовали дядьку Фаулера)
А как бы вы (или дядка Фаулер :) ) отрефакторили тот пример в статье, чтобы не было else if-ов?
а никак) вообще весь ООП на простых примерах выглядит очень глупо.
Вместо каждого switch совать «стратегию» или «состояние»? :)
На самом деле, зависит от сложности выполняемой работы и от религиозных воззрений программиста.

Я, например, очень люблю caseless-стиль написания кода, и поэтому пихаю везде вместо case простой hashtable с лямбдами-условиями и лямдбами-«стратегиями».
Мне кажется, что все эти приемы характерны скорее для процедурного подхода, чем для правильного ООП. Местами его советы помогают бороться со следствием, а не с причиной.

Функция на 200 строк кода — это же дичь! Никакие объявления переменных в середине не способны ее улучшить, скорее могут только отсрочить столь необходимый рефакторинг. Вывод: пишем небольшими завершенными кусками кода и объявляем переменные где душе угодно.

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

А в целом интересно, отбрасывает лет так на 10 назад, ностальгия))
>Возврат значения через параметр, длинный список аргументов функций — все туда же, изучить ООП и паттерны проектирования

И каким образом это относится к ООП? Там как-то по-другому вызываются функции что-ли?
Часто длинный список параметров функций получается из-за того, что ей надо знать контекст. В случае объектов он инкапсулируется, и метод уже знает большинство своих параметров.
даже если у функции 2-3 параметра, гораздо приятнее когда они называются по именам при вызове.
По мне так Math.pow(2, 5) тоже нормально, а Math.pow(base=2, exponent=5) — это не обязательно.
Но согласен в том, что хорошо, когда такая возможность присутствует, и радует то, что в питоне она есть.
ну это общеизвестная математическая функция, конечно в ней можно обойтись.
Излишне категорично всё.

Возврат результата функции через ее параметр

Иногда это вполне оправдано. В том же C#

public static bool TryParse(
string s,
out int result // это передача по ссылке
)
— всё получается очень красиво.

int i;
if (int.TryParce(SomeString))
{
// Success code
}
else…

Возвращать _в данном случае_ структуру с флагом и значением не так хорошо.

Отсутствие именованных параметров функции

Тоже не так однозначно. У нас(в примере) прямоугольник. Он ОБЯЗАН иметь 4 угла. И createPoligon(point1,point2,point3,point4) как-то гарантирует, что при его создании у нас они будут. Вместо длинного и череватого ошибками(просто по невнимательности)

poligon.point1 = point1;
poligon.point2 = point2;
poligon.point4 = point4;
// упс какая лажа вышла…

Использование рекурсиидля вычисления факториалов и Чисел Фибоначчи

Ну вы выбрали такую частовстречающуюся задачу… Вот каждый день миллионы программистов пишут свои собственные методы для факториалов… А уж числа Фиббоначи обычно вообще берут из заранее подготовленных таблиц.

P.S. Чё-то теги не работают.
Код, который вы привели не красивый если его сравнивать с Maybe монадой в Haskell, или, что более ближе .NET программисту с F# или Nemerle:

// аналог полиморфной структуры (есть в стандартной библиотеке)
variant Option['a]
{
  | Success { value : 'a }
  | None
}

// подозрительная функция, как TryParse
def SuspiciousFunction (data : string) : Option[double] { ... }

//использование
match(SuspiciousFunction("qwerty"))
{
  | Success(result) => ... // все хорошо
  | None => ... // плохой аргумент
}
Это аналогично возврату nullable-значения из Parse.

Если бы метод был определен как int? Parse(), то было бы все хорошо.

int? val = str.Parse();
if(val.HasValue)
{ ...}
else
{ ...}

К сожалению, функция Parse появилась раньше nullable-типов.
Отсутствие именованных параметров функции

Тоже не так однозначно. У нас(в примере) прямоугольник. Он ОБЯЗАН иметь 4 угла. И createPoligon(point1,point2,point3,point4) как-то гарантирует, что при его создании у нас они будут.

Полностью согласен и не вижу предмета спора.

Я ведь писал, что именованные параметры НЕ требуются использовать, когда:

1) Параметров немного, и их предназначение очевидно.

Например: Math.pow (2, 5)

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

Например: Math.summ (3, 7, 18, -2, 11, 2.3)

Ваш пример с прямоугольником как раз подпадает под оба этих случая.

Однако, в случае такой вот каши, отсутствие именованных параметров уже некрасиво:

RotationInterpolator rotator = new RotationInterpolator (
  new Alpha (-1, Alpha.DECREASING_ENABLE, 0, 0, 8000, 0, 0, 0, 0, 0),
  xformGroup, axis, 0.0f, (float)Math.PI*2.0f);

Выужу тезисы и повешу нашим программистам на стенку :)

Спасибо, написано очень интересно.
Не боитесь побойки и синяков? :)
У нас в палате все смирные ребята. :)
Везет же :( у нас после попытки code review истерики были. Я теперь в раздумьи, как быть дальше :)
Ваша категоричность по поводу getValue и setValue не всегда правильна.
Пример: Objective-C. Существует методика под названием Key Value Coding (на которой основаны Cocoa bindings), где в качестве геттера используется property, а в качестве сеттера как раз setProperty. Вы считаете что инженеры Apple(и NeXT) не правы? Я думаю они дольше вас обдумывали этот вопрос и их решение на чем-то, да основано. Я лично пока еще в ObjC и Cocoa не видел очевидно непродуманных решений…
Так в obj-с как раз пришли к dot syntax, который вызывает геттеры/сеттеры и поддерживает конструкции вроде property++.
dot syntax добавили, но KVC от этого не стал работать через него.
KVC и KVO это как-бы уже больше на reflection смахивает. Ну или в тех местах, где надо что-то автоматизировать. Или в тех, где по-другому — никак(те же самые Bindings).

Согласитесь, что в коде логичнее писать
int a = [self age];

[self setAge:20];

чем:
int a = [self valueForKey:@«age»];

[self setValue:10 forKey:@«age»];
У себя в коде я так и напишу! Но при этом у меня будет работать KVC для какавной магии. Автор же предлагает писать [self age:20]; он как раз борится с set и get.
автор предлагает писать self.age = 20; Что и было сделано в ObjC2.0
И еще в Objective-C решен п.9 — имена параметров являются практически частью имени функции.

-(void) setMaskToX:(float)x Y:(float)y staticBody:(cpBody *) staticBody
{… }

соответственно вызов:

[… setMaskToX:10 Y:20 staticBody:staticBody]

Ну это из SmallTalk еще, я это очень люблю.
как по мне так это глупо — объявлять такие правила (как авторские, так и те что он опровергает).
Почему глупо? Это просто советы, которые по мнению автору (и я с ним в целом согласен) помогают писать более качественный код. Вы можете ими пользоваться, а можете и нет, вам решать.
Первым правилом он вывел из игры с-программистов.
Вторым com-программистов
третие в ооп излишне.
четвертое и опровергать ему не стоило, т.к. оно изначально бредятина.
в пятом — данные стоит хранить так как этого требует решаемая задача.
шестое — хранить размер массива отдельно точно не стоит. (т.е. правило изначально бредятина)
седьмое — обращаться к свойствам через set и put, стоит только когда эти свойства нужно контролируемо изменять.
восьмое — стек(в плане памяти) нужно любить и не насиловать его рекурсией.
девятое — тут черт ногу сломит, что автор хотел сказать. но использование структуры в качестве параметров функции изначально бредовая идея. да и за временными объектами нужно следить внимательно.
десятое — черт знает что он тут имел ввиду
десятое -
Я согласен, что если какой-то язык или технология не поддерживает некоторые из пунктов, то нужно пользоваться имеющимися возможностями, и ничего страшного в этом нет. Я думаю, что автор описывал идеализированный сценарий.
Красота и производительность зачастую бывают по разные стороны баррикад. Что выберет каждый — думаю решать не вам.
Да, к сожалению часто бывает так. В не уместившейся здесь заключительной части я написал:
Ну и не стоит забывать, что отсутствие в коде приведенных «уродских приемов» — лишь идеальная ситуация, к которой надо стремиться, но приходится жертвовать в определенных ситуациях (например, в целях производительности).
Простите конечно — но тихий ужас…
2. Возврат результата функции через ее параметр;
— Ну да, пользователи у нас все пугливые, и документацию не читают, к подсказкам не присматриваются, сигнатуры не видят. Не увидят разницу между const std::string &name и std::string *name. Внимание, вопрос — а зачем нам заново выделять память, если мы можем использовать объект, который уже использует пользователь? И менять в нём только часть полей.
3. Отсутствие локальных функций;
— прекрасно (более-менее) эмулируется локальными классами.
4. Отсутствие else if;
— используйте функции, и будет вам счастье.
5. Использование параллельных массивов;
— структуры данных вас спасут. И нас спасут.
6. Хранение размера массива в отдельной переменной;
— отсутствует раздел.
7. Доступ к свойствам объекта через obj.getProperty() и obj.setProperty(value);
— про многопоточность уже писали выше. сеттеры/геттеры — штука очень полезная. Чётко показывает, что класс отвечает за то, что мы присвоим. Это совсем не смертельный недостаток. И ещё — а зачем вам удобный доступ к данным-членам класса?
8. Использование рекурсии для вычисления факториалов и Чисел Фибоначчи;
— т.е. возможность использования уже недостаток?
9. Отсутствие именованных параметров функции;
— структуры данных вам в помощь, хоть вы про них и писали. А понятные имена надо давать параметрам, чтобы программисту было проще понять, что он пишет.
10. Невозможность объявления объектов «на лету».
— отсутствует раздел
статья хорошая, но разработчики на brainfuck & perl regexp могут её не осилить ;-)
Красиво это когда мало кода вообще и «синтаксического шума» в частности.

Автор в ряде пунктов (по крайней мере 'Отсутствие именованных параметров функции', 'Использование рекурсиидля...') противоречит моим воззрениям.
Во многом согласен. Но вот nested функции очень сурово глаз режут. Если есть кусок кода, который реюзаешь — вынеси его наружу.
А если этот кусок кода используется только в этой функции, а в других нафиг не нужен, то зачем засорять внешнее пространство имён? Если всё-таки он потом в другой функции понадобится, то всегда можно будет его вытащить наверх.
К тому же этот кусок кода может использовать локальные данные функции.
Садись, двойка! ;)

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

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

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

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

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

Замыкания — это неизбежное зло, с которым приходится мириться, потому что некоторые языки, не предоставляют некоторых возможностей. Например, невозможно в Perl-е скрыть потроха объекта иначе, как замыканием.

Кроме того, это зло давно уже стало паттерном.

Но за веши вроде:

int someshit() {
  int theData;
  ...
  int doFoo() {
    # делаем что-то c theData
    ...
  }
  ...
  int doBar() {
    # делаем еще что-то с theData
    ...
  }
  ...
  doFoo();
  ...
  doBar();
  ...
  return theData;
}


надо пороть.
Вдогонку про Perl и потроха: можно еще вывернутые (inside out) объекты использовать. Но, фактически, вывернутый объект — это набор параллельных массивов, что тоже, в общем случае, есть зло, несмотря на то, что паттерн.

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

>Замыкания — это неизбежное зло,

Более аргументированное мнение будет?

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

Какие языки и каких возможностей?

>Например, невозможно в Perl-е скрыть потроха объекта иначе, как замыканием.

И как из этого следует, что замыкание — зло? Противоречит вашей религии?
>Более аргументированное мнение будет?

Конечно, будет.
Почему не вернуть объект вместо функи, которая неким магическим образом обрабатывает данные данные лежащие за ее пределами и при этом не переданные ей в качестве аргументов?

>И как из этого следует, что замыкание — зло? Противоречит вашей религии?

Из этого следует его неизбежность.
>Почему не вернуть объект вместо функи

Разницы между первоклассной функцией и объектом не сильно много.

>которая неким магическим образом обрабатывает данные данные лежащие за ее пределами и при этом не переданные ей в качестве аргументов?

Лол. Тоже самое можно сказать и про объекты: они неким магическим образом обрабатывают данные лежащие за их пределами. Объекты — зло!!11

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

Разница в читабельности кода.

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

Речь шла о локальных функциях, модифицирующих внешние данные. Уже после кто-то передернул тему на замыкания, а я повелся…
Менять внешние данные — зло, потому что если функа, которая их модифицирует длиннее 1 строки, или этих фунок несколько (о ужас!), то искать кто и когда поменял эти данные придется грепом или отладчиком, а не глазами.

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

int a=0;
void incA(void) {
  a++
}
incA();


нельзя. Так же как вывернутые объекты можно, а паралелльные массивы нельзя.

> Лол. Тоже самое можно сказать и про объекты: они неким магическим образом обрабатывают данные лежащие за их пределами. Объекты — зло!!11

И что же это они модифицируют за их пределами??777 ;)
> Менять внешние данные — зло, потому что если функа, которая их модифицирует длиннее 1 строки, или этих фунок несколько (о ужас!), то искать кто и когда поменял эти данные придется грепом или отладчиком, а не глазами.

Отсюда простой вывод: не менять внешние данные. Лучше вообще не менять никакие данные.

> int a=0;
> void incA(void) {
> a++;
> }
> incA();

На самом деле, вся императивщина (и ООП) держится на вот этом вот. :)
>Разница в читабельности кода.

Читабельность — понятие субъективное.

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

Описанное выше есть пример глобального состояния и неочевидного потока данных. Замыкания тут вообще не при чем.
Можно точно так же из методов объекта глобальные данные менять. И что же теперь — объекты зло?

>И что же это они модифицируют за их пределами??777 ;)

Данные же!!1
>Можно точно так же из методов объекта глобальные данные менять. И что же теперь — объекты зло?

Вы сами-то поняли, что хотели сказать? ;)
Поясню для непонятливых.

int bar = 0;
class Foo {
  void doBar() {
    bar++; // ZOMG mutable variables!!11
  }
}

Мне непонятен выод. %)
Глобальные данные можно из чего угодно менять, только не стоит этого делать без крайней необходимости. А эта крайняя необходимость приходит крайне редко. Из чего можно сделать вывод, что если для реализации какого-либо алгоритма приходится использовать глобальные данные, то либо Вы хотите чего-то странного, либо стоит переформулировать алгоритм.
>Мне непонятен выод. %)

Окей, давайте разберемся.

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

Я говорю:
>Описанное выше есть пример глобального состояния и неочевидного потока данных. Замыкания тут вообще не при чем.

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

Другое дело, что пример какой-то абстрактный и обычно так никто не пишет.

Пишут так:
int someshit(params) {
  int theData;
  ... #
  int doFoo() {
    # делаем что-то c theData и/или с params
    ...
  }
  return doFoo;
}

И вот тут вложенные функции с замыканиями к месту. И именно так и пишут на языках, которые это поддерживают. Замыкания — не зло, зло — это недостаток знаний)
При условии, что someshit() и doFoo() тривиальны, читабельность кода, практически, не страдает. В любом другом случае размотать клубок из модифицируемых переменных, определенных одним или (не дай бог) двумя уровнями выше будет сложно.
Потому что глаз режет. Функи удобнее искать на одном логическом уровне. Но это чисто мое субъективное мнение. Вложенные функи меня реально сбивают с толку.
Я ничего не понимаю в программировании и зашел сюда случайно :) Но не думаю поставил плюс — не по наслышке знаю, чего стоит ТАК оформить ТАКОЙ пост ;)
вообще имхо лучше читать книжки типа Совершенного кода Макконнелла да Фаулера и Кериевски о рефакторинге и паттернах. А дальше каждый сам решит, что он может позволить писать в своем коде, а что нет.
Хорошая статья, спасибо. Однако, как мне показалось, люди начали совершенно необоснованно ругаться на пункт 7. Видимо, просто не прочли про то, что следует использовать встроенный в язык механизм свойств, а не выпячивать наружу публичные поля класса. Кстати, это возможно делать и в PHP :-) Я считаю, что в данном вопросе нужно всё-таки не мыслить догмами. Скорее, свойства стоит использовать, если и геттер и сеттер производят небольшие действия. Если же это что-то, что работает очень долго и включает нетривиальную логику, то следует использовать методы. Кстати, ещё один случай, когда стоит предпочесть метод — это отсутствие самоочевидной семантики свойств. Например, если такой код:
if (foo.bar == 1)
    foo.bar = 1;

что-то делает (ну, разве что кроме, например, ленивой загрузки, кэширования и т.п.), то bar — никакое не свойство.

И, кстати, раз уж статья рассказывает о несовершенстве языков, приводящем к «некрасивому коду», то могу напомнить ещё один недостаток. Касается он отсутствия возможности писать расширения к существующим классам. Выливается это в то, что приходится писать всевозможные helper-классы и напрямую писать что-то вроде:
Helper.ext(obj, foo, bar)

вместо
obj.ext(foo, bar)


В качестве «реальной» задачи для иллюстрации данного замечания могу предложить следующий пример. Дана последовательность целых чисел. Требуется найти квадраты нечётных чисел. Сравните пример на Haskell:
foo = map (**2) $ filter odd

и на C# 3.0
public static IEnumerable<int> Foo(IEnumerable<int> sequence)
{
    return sequence.Where(x => x % 2 == 1).Select(x => x * x);
}

с примером на Python
def foo(sequence):
    return map(lambda x: x * x, filter(lambda x: x % 2 == 1, sequence))

Как видите, без нагромождения скобок программа выглядит гораздо изящнее
В примере на Haskell ошибся и написал $, хотя уместно. Либо же сделать foo комбинатором и добавить xs после foo и после odd
вроде на питоне проще можно:
def foo(sequence):
    return [x*x for x in sequence if x%2]

гвидо вообще ругается на лямбды, map и filter всякие, убрать их хочет)
Собственно, и в C# есть специальный синтаксис для LINQ, и в Haskell есть List Comprehension. Но дело-то совсем в другом…
кстати, не понял, как относится замечание о невозможности расширения существующих классов (это monkey-patching или что?) к примерам с вычислением квадратов. Поясните, пожалуйста.
Не совсем. Во-первых, monkey patching относится только к динамическим языкам. Я же привёл в примере C#, который статически типизирован (и решает проблему с помощью extension methods). Во-вторых, я говорил не про изменение кода, а лишь про наполнение существующего класса (или прототипа) новыми «общими» функциями. Так, например, гораздо изящнее реализуется то, что зовётся «обобщённым программированием».
Все равно не понял, какое отношение функция, возвращающая список (смотрю на примеры в haskell и python), имеет к наполнению существующего класса новыми функциями.

Ну да, в питоне (и haskell?) есть duck typing, а в С# его вроде бы нет, но есть шаблоны, я хоть в том направлении думаю?))
Дело не в функции. Посмотри, как сделано на C#. Select и Where не являются методами интерфейса IEnumerable. Они объявлены в статическом классе Enumerable. Благодаря extension methods можно записывать так, как я показал, а не что-то вроде:
return Enumerable.Where(Enumerable.Select(sequence, x => x * x), x => x % 2 == 1);

Это в некотором смысле аналог пункта 4 статьи.

Аналогично, в JS можно добавить методы map и filter в Array.prototype и так же пользоваться себе наздоровье точечной нотацией без увеличения уровня вложенности и дублирования названия helper-класса.

Haskell тут вообще не совсем уместен, так как он не ООП. Но вот и в нём есть средство (в виде операции $), которая позволяет избегать лишних скобок и записывать производимые операции последовательно, а не внутри друг друга.
Теперь вроде понятно.
Но позаступаюсь за питон все ж немного)
а) в питоне можно добавлять методы ко всему чему угодно, кроме встроенных типов (всякие int и тд);
б) для определения функций вида x(y(b)) в питоне тоже существует сокращенный вариант без скобок — это декораторы.
> б) для определения функций вида x(y(b)) в питоне тоже существует сокращенный вариант без скобок — это декораторы.

Это обычная композиция функций:

x. y == lambda b: x(y(b))

Можно ли это выразить в Питоне?

ЗЫ я ни в коем случае не наезжаю на питон.
Конкретно то, что вы спрашиваете — нет, насколько я знаю. У декораторов немного другой, более узкий смысл. Декораторы — синтаксический сахар для упрощения кода вида
def y:
  ...
y = f(y)


Вместо этого можно писать так:
@f
def y:
  ...

Вообщем штука стимулирует писать (и использовать) реюзабельные функции-модификаторы других функций. И эта возможность довольно активно используется там, где нет необходимости поддерживать совместимость с python 2.3.
Есть ли другие use case'ы для декораторов?

>def y:
>…
> y = f(y)

Здесь точно нет ошибки? Fix-point умышленный?

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

а "..." — это тело функции, мог бы написать «lala» или «blabla», это не языковая конструкция
Linq выглядит вполне изящно)
Молодец. Я еще до конца не дочитал, но страницу сохранил, чтобы потом, когда будет выделено время на развитие и тренировку програминг-способностей совего организма вернуться к сей отличной статье. Респект и плюс за работу. Особенно примеры, очень классно оформлены.
Вообще-то функции зачастую имеют несколько результатов (в человеческом смысле, т. е. измененных объектов или переменных). Программным же результатом функции принято устанавливать код ошибки (0, если функция выполнена успешно). Другое дело, что оправдано это далеко не всегда.
(и да, фанаты функциональных языков готовы растерзать разработчиков нефункциональных за употребление священного слова «функция» в качестве «процедуры с результирующей переменной»:))
Нет никакой проблемы вернуть кортеж или массив. А ошибки нужно обрабатывать исключениями.
Очень хорошая статья, все лаконично и хорошо описано (за примеры особенный респект). Честно говоря тоже думал, что сейчас будет лабуда какая-нибудь, но я прям вчитался. Многие из «правил» уже знакомы и стараюсь их придерживать, но порой бывает забиваешься одной мыслью и делаешь глупейшие вещи, а потом разбираешься в своей же писанине и думаешь «откуда это взялось?» и даже комменты не помогают )

Статью в табы )
— Хранение размера массива в отдельной переменной;
зря, производительность программы храмает.

цитата: «Избегайте выполнения лишних действий

Достаточно абстрактное утверждение, но тем не мение постоянное напоминание себе о нем может избавить Вас от совершения массы ошибок. Самой широкораспространенной является наверное вызов какой-либо функции (чаще всего count(); или strlen();) в проверке условия выхода из цикла. Когда-нибудь доводилось писать видеть в собственном или чужом коде выражение вида for($i = 0; $i < count($array); ++$i) {… }? А задумываться о последовательности выполнения действий при его обработке? Стоит только немного начать размышлять и ошибка становится очевидной: count(); выполняется при каждой итерации цикла, что приводит к подсчету количества элементов массива при каждой проверки условия выхода из цикла — почему бы не посчитать это значение заранее и сравнивать значения индекса с переменной, а не с результатом выполнения функции?»

www.insight-it.ru/programming/php/na-puti-k-idealu/ ©
Только хотел привести пример кода с for по этому пункту, как увидел что вы это уже затронули. Поставил бы вашему комментарию +1, да уже кармы нет совсем :)
С точки зрения красоты кода — все верно.
Но, например, параллельные массивы используют не от хорошей жизни. Очень часто такой способ хранения более оптимально использует кэш. В математических алгоритмах его очень часто применяют, прирост производительности весьма значительный.
Интересно, а как параллельные массивы помогают в производительности? Если мы, например, обрабатываем объекты в цикле, в каждом объекте работая с несколькими полями, то вариант, когда все поля объекта хранятся в соседних областях памяти должен давать меньше кэш-промахов, чем если поля разбросаны по разным массивам, разве нет?
Во-первых, если язык не поддерживает value-типы, то вместо массива объектов получается массив ссылок на объекты, раскиданные по всему хипу.
Во-вторых, иногда нужно выполнять какую-то операцию над значением конкретного поля сразу у кучи объектов. В таком случае параллельные массивы эффективнее. Кроме того, при итерации по двум массивам процессор может выделить под это дело не одну, а две строки n-way-кэша.
UFO just landed and posted this here
Есть ещё куча других гайдлайнов. И гугл тут далеко не идеал.
Отличная статья, но по моему автор сильно обобщает эти правила. Многие правила просто невозможно выполнять в определенных условиях и это не потому что разработчики не хотят красивого кода, а потому что их сфера производства, например микроконтроллеры заставляет их писать на языках, имеющих строгий синтаксис, они не вымерли как динозавры.
Поддерживаю. Просто автор предложил своё видение «идеально оформленного кода», но как он оговорился, это его взгляд.
Кстати, если речь идет о высокотехнологичных, новых языках и средах разработки к ним, то надо учитывать и новые средства работы с кодом, такие как MS IntelliSense, XML комментирование в .NET языках, доп. средствах, таких как Visual Assist X. Это в частности избавляет от проблемы №9 (отсутствие именованных параметров функции), как при написании, так и при просмотре кода.
А я не согласен с первым пунктом. В результате получается каша когда смотришь код. Гораздо удобнее, когда все переменные определены в одном блоке (не обязательно сначала).
Кому-то удобнее, когда переменные объявлены там, где они используются, а кому-то удобнее, когда они все объявления сгруппированы в одном месте.

В таком случае, идеальный язык программирования — это тот, который требует, чтобы все переменные объявлялись одновременно и там и там :P
Всё гораздо проще: если метод помещается на экран(оставим в стороне, каким шрифтом и на каком разрешении :), довольны будут все. С первопричиной надо бороться.
браво! Хоть я и не профессиональный программер — но интуитивно треть советов использую :)
Попытаюсь применять и остальные)
UFO just landed and posted this here
есть пожелание к автору — добавьте в конец статьи что-то типа «Кто заинтересовался вопросом качества кода так же рекомендую прочитать
www.ozon.ru/context/detail/id/3159814/ „Совершенный код“ С. Макконнелл
www.ozon.ru/context/detail/id/1308678/ „Рефакторинг. Улучшение существующего кода“ Мартин Фаулер
www.ozon.ru/context/detail/id/2909721/ „Рефакторинг с использованием шаблонов“ Джошуа Кериевски»

А то судя по количеству комментариев вида «очень познавательно» некоторые вообще не слышали про эти замечательные книги, и тем самым очень много потеряли
За одно только визуальное оформление статьи и четкости мыслей Вас надо ставить примером. Уважаю.
К сожалению, лишь немногие языки поддерживают именованные параметры функций (я могу вспомнить только динамические Perl, Python и Ruby, может быть есть еще).
Фортран поддерживает, как это ни странно :)
По-моему, многие советы актуальны только при чтении программы с листа бумаги. Да и понятие красоты весьма растяжимо. Красивым может быть и ассемблерный код и программа на лиспе, хотя приемы там весьма разные. Но все равно прокомментирую, выскажу свое видение красоты.
1. А не все ли равно. Метод должен быть максимально коротким и содержать минимум переменных. Если метод умещается на экране целиком, то мне все равно где там объявлены переменные в начале или в середине. Если метод длинный и переменных много, то объявление переменной ближе к месту ее использования мало поможет, если переменная используется много раз. Например в середине и в конце метода используется одна переменная, и нам все равно придется из конца метода, скажем с 500 строки лезть на 250 строк вверх, что бы поменять, например, тип переменной. В таком случае поможет только хороший редактор.
2. В общем согласен, особенно если функция не имеет побочных эффектов.
3. Согласен. После делфи в с++ мне не хватало такой возможности. Хотя она и редко используется.
4 — 6. Согласен. Безусловно.
7. Согласен. Хотя прочитав коментарии, возникли сомнения. Вы ведь не против того, что бы использовать гетеры/сеттеры? По-моему это красиво, когда можно сделать Man.Age=99 и значение сохранится в базу.
8. А по-моему красиво
fac(n<0)= raise 'Факториала от отрицательных чисел я не посчитаю'
fac(0)=1
fac(n) = n * fac(n-1)
Красивее чем

function Fac(n:Integer):Integer;
var i:integer;
begin
if n<0 then raise 'Факториала от отрицательных чисел я не посчитаю';
Result := 1;
for i:=1 to n do
Result := Result * i;
end;
Или
int factorial(int n) {
if( n < 0 ) raise 'Факториала от отрицательных чисел я не посчитаю';
int result = 1;
for (int i=1;i<=n;i++) result++i;
return result;
}

Непойму, чем вам не нравится. То что в некоторых языках/компиляторах это не эффективно — это другой вопрос. Это уже претензии к компиляторам, а не к рекурсивному способу вычисления факториала.

9. Категорически против обязательного использования именованных переменных. Мне лень писать лишнее, когда это не нужно.
Опять же при наличии редактора всегда можно увидеть как называются параметры функции и какого они типа. При желании можно даже описание вставить.
На самом деле вы могли бы иметь ввиду два вида именованных параметров функции.
Первый — это когда, например функция объявлена как func(int x, y )
и можно делать так func(x:11, y:22) или func(y:22, x:11). Т.е. использовать только имена которые были заданы в описании функции. При наличии нормального редактора кода эта возможность мало полезна. Ну если только нужно задать переменные в ином порядке, чем в описании функции. Чего практически никогда не требуется.

Второй — это когда можно задать произвольные имена параметров, например
func(длина:22, ширина:11). Эта возможность тоже бесполезна. Ибо лучше убрать магию и сделать так
длина = 22
ширина = 11
func(длина, ширина). А так можно делать во многих ЯП.
Так что, по-моему, именованные параметры фунций — это бесполезная вещь.

Второй — это когда можно задать произвольные имена параметров, например
func(длина:22, ширина:11). Эта возможность тоже бесполезна.


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

А чо за странный такой формат отступов + скобочек, да и то взаимно несогласованный

Тут так:

People *leaders [7] = 
    {
      new People ("Ленин",    "Владимир",   "Ильич"),
      new People ("Сталин",   "Иосиф",      "Виссарионович"),
      new People ("Хрущев",   "Никита",     "Сергеевич"),
      new People ("Брежнев",  "Леонид",     "Ильич"),
      new People ("Андропов", "Юрий",       "Владимирович"),
      new People ("Черненко", "Константин", "Устинович"),
      new People ("Горбачев", "Михаил",     "Сергеевич")
    };


а тут так

function quadraticEquation (numA, numb, numC)
 {
 //...
 return ({
     count: intRootsCount,
     x1: intX1,
     x2: intX2
     });
 }


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

Да и сами скобочки нарушают хрупкую красоту кода.
У разных языков разный «официальный» codestyle. Для C++ допустимы 3 варианта, для C# «официальным» считается вариант со скобкой на новой строке и переводом строки после нее, для Actionscript — скобка ставится на уровне инструкции, блок которой она открывает.
Да мне вобщем-то пофигу. Я привыкаю к тому что принято в данном месте. С моей точки зрения лучше вообще без скобок как в питоне, а если они есть лучше на них не тратить дополнительных строчек, как в яве.

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

Стандарта нет, я не просто так взял это в кавычки.
Чтобы сравнить надо сначала привыкнуть к обоим вариантам. Например чуть чуть пописать на Python, Haskell или F# :)
Статья написана прямо в соответствии с изложенными habrahabr.ru/blogs/humour/43926/ соображениями о идеальном топике.
Даже несмотря на то что статья длинная, а я не программист, прочитал всю с интересом и думаю что информация пригодится даже мне, как дизайнеру. Автору Спасибо!
Хотел бы заступиться за C++.
Можно и локальные функции определять, например так:
#include <iostream>
int main(char** argv) {
    struct {
        void hello() {
            std::cout << "Hello, world!" << std::endl;
        }
    } x;
    x.hello();
    return 0;
}

Ну и конечно же есть Boost Libraries с lambda и другими вкусностями.
for (long numBugNumber = 0; numBugNumber < objWinVista.bugsCount; numBugNumber++)
  {
  println (objWinVista.bugs [numBugNumber]);
  }
<pre>


На самом деле этот код хочет быть таким

<pre>
for (var bug in winVista.bugs)
    {
    print(bug) 
    }


а numBugNumber — это просто дань C++ сномй отсутствию оператора for each

Я стараюсь делать эту дань как можно меньшего размера.

i очень хорошее и общепринятое название для текущей переменной итератора.

for (long i = 0; i < WinVista.bugsCount; i++)
  {
  println (objWinVista.bugs [i]);
  }
<pre>

Что конкретное делает i понятно благодяря этому соглашению и тому что цикл маленький.

А про венгерскую нотацию читайте programmer's stone
Вот-вот!
Или, кстати:

winVista.bugs.foreach(i => print(i));
Начет пункта «Отсутствие else if» есть хороший прием, который позволяет избежать сложных ветвлений и сделать код более понятным. Одиночный цикл с постусловием.
do {
if (<условие_ошибки_1>){
// что-то делаем
break;
}
if (<условие_ошибки_2>){
// что-то делаем
break;
}
// код обработки
} while(false);
прекрасно оформленная статья! :)

вот мое имхо:

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

2) по поводу того что функция должна возвращать значение в качестве своего результата, а не в переменную по ссылке:
полностью согласен! но дело в том, что есть функции, которые должны помимо своего значения вернуть что-то типа «кода возврата», чтобы понять — отработала функция нормально или в ней произошла ошибка. в таких случаях очень удобно возвращать результат в параметр по ссылке. Само собой это относится к языкам со строгой типизацией данных (например, С/C++, Pascal). в языках без строгой типизации(например, Perl, PHP) можно обойтись только возвращаемым значением функции (думаю понятно почему)

:)
Для C++ можно воспользоваться std::pair<T1, T2>, например так:

std::pair<bool, int> some_complex_calculation() {
  //......
  return std::make_pair(true, 1234);
}
....
std::pair<bool, int> result = some_complex_calculation();
if (result.first) {
  // do something useful
}


В таком случае можно вернуть и код возврата и результат вычислений.
Есть ещё boost::tuple, для случаев когда нужно вернуть более 2х переменных.
безусловно.
я бы даже сказал так можно сделать в любом языке с поддержкой сложных типов данных (затрудняюсь привести пример языка со строгой типизацией и без поддержки сложных типов данных)

вопрос в лаконичности, удобстве и вкусе :)

ой как не хочется спорить по такому пустяку)

по поводу вашего примера — а не лучше ли просто использовать структуру? (это конечно если в коде не используется STL для других целей)
По моему как раз в таких случаях смысла обьявлять структуру нет.
Естественно это всё очень субьективно :)
в хороших языках со сттррогой типизацией (например C# или F#) можно вернуть tuple или анонимную запись
Спасибо за статью, и что особенно приятно, в обсуждении практически нет холиваров.
Жесть, такой бред только ламер написать мог…
АААА… Знакомый автор холиварщег ;))
ОФФТОП: А считать определитель матрицы как Вы написал вряд ли получится из-за экспоненциальный сложности, точнее получится только для маленькой размерности. А для больших матриц есть полиномиальные алгоритмы (O(n^3))
Со всеми пунктами (почти) согласен, но сразу же первое — 200 строк, говорите? Ни одной даже близко подобной функции не было в системе. Обязательно нужно декомпозировать.
>> Объявление всех переменных в начале программы.

Я не соглашусь лишь потому, что иногда лучше делать, как описанно у автора, а иногда, в начале.
Во втором случае — это будет являться как-бы Оглавлением в книге. Посмотрев просто на переменные, можно уже будет понять, что будет использоваться в функции.
>> В процедурных языках (вроде C или Pascal) проблема вызова функций с большим количеством малопонятных параметров стоит особенно остро.

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

Но больше возникает проблемм при конвертации одних параметров в другие. Да, для этого можно написать функции типа BorderToRectangle (одна структура данных в другую), чтобы не писать в коде МНОЖЕСТВЕННЫЕ, ЗАГРЕЗНЯЮЩИЕ КОД «переименования» rectangle.x=border.x; rectangle.y=border.y;…
А теперь представим, что для КАЖДОЙ функции у нас будет использоваться своя структура? Вы не охренеете всё это переводить? Вы не охренеете писать функции для перевода, чтобы оно выглядело нормально?
Не проще ли оставить нормально как есть? DrawRectangle(x, y, len, hgt, angle, alpha, color); итд…
Совет к первому пункту, помельче ваших: удаляйте временные переменные после использования.
Временные переменные часто называют короткими именами. После чего они мешаются, например, в подсказках IDE и усложняют понимание, нужна ли эта переменная далее.
пример:
args, x, xx, defaults = inspect.getargsspec(method)
del x, xx
=
Вы только представьте: У нас есть функция в 300 строк кода [2]. Где-нибудь на 200-й строке нам надо поменять две переменные местами. Для этого мы лезем на 200 сток выше в начало функции, объявляем переменную temp, которая не имеет никакого отношения ко всей функции, а используется только один раз в одном месте, потом опять возвращаемся к 200-й строке и меняем переменные местами… По-моему, это просто кошмар.
=
имхо, за такой пример уже можно отрывать руки-ноги программисту.
А вообще — статья вроде интересная, полезная и хорошая. Но как-то получается: а) здесь хорошо, но паскаль так не умеет; б) это хорошо, но так умеет только ява/джава. Как говорится — что имею, то и меня имеет (с) Работаем с тем, что есть.

А автору — плюс, перечитаю статью еще не раз
Спасибо, за статью.

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

PS
Не знаю как другим, а мне «sirname» немного режет глаз. Там u должно быть.
А я сначала подумал, что sirName — это «имя сэра» :)
«Сэр Иванович» — это просто ад. :)
По поводу объявления переменных категорически не согласен. К тому же, некотрые стандарты(например, C90 для Си) запрещают объявлять переменные посреди функции.
Что-то верстка на странице поплыла, видимо автор не закрыл какой-то тег. А вообще уровень текста и материалов к статье поражает. Читать — одно удовольствие.
applay -> apply видимо. Автор, поправьте.
Насчёт пункта 5 — согласен, но тут всё зависит исключительно от знания программистом принципов ООП и умение мыслить абстрактно. Если он это умеет, то сделает всё правильно, не задумываясь о красоте кода.
>Возврат результата функции через ее параметр
>…
>Этот прием ужасен. При его использовании не видно, от чего функция зависит, а что возвращает.

из COM ты никак по другому не вернёшь.
а чтобы было понятно от чего функция зависит, а что возвращает — есть словечки in и out

в остальном согласен.
вот это я понимаю качественный контент. приятно читать, анализировать, запоминать. спасибо!
> Ну, в функциональных языках (таких как Lisp или Haskell) все понятно: рекурсия применяется всегда, когда надо выполнить любые повторяющиеся действия.

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

(loop for item in '(1 2 3 4 5) do (print item))

Можно применить некоторое действие к элементам списка и вернуть новый список:

(loop for item in '(1 2 3 4 5) collect (+ 2 item) into new_list return new_list)

Loop — очень мощная штука, полного аналога которой я ни в одном распространенном языке я не знаю.
(Хаскель распространенным считать никак нельзя. К сожалению). Дополнительный плюс — описание цикла похоже на обычный английский язык, что удобно.
Иногда, правда, лучше использовать чего-нибудь попроще.
Есть 2 варианта:

(dolist (item '(1 2 3 4 5))
(print item))

(defparameter *list* '(1 2 3 4 5))
(dotimes (i 5)
(print (nth i *list)))

Первый — примерный аналог foreach, второй — похож на обычный обход массива C-style, т.е. через for.
По поводу рекурсии. Оптимизировать хвостовую рекурсию предельно просто и эта оптимизация реализована, наверное, везде. Как-раз факториал будет иметь вид хвостовой рекурсии.

Вы пишете, что алгоритм фибоначчи является итеративным(это как?), хотя само определение алгоритма рекурсивно. Другое дело, что «не рекурсивный» алгоритм вычисления чисел фибоначчи относится к классу методов динамического программирования.
ISO C89 требует объявления переменных в начале блока
>Использование методов в качестве акцессора и мутатора поля — уродство.

использование публичных свойств, без геттеров и сеттеров — муда**ство.

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

Articles