Pull to refresh

Comments 148

Вот это настоящая оптимизация, рефакторинг сознания программиста, если выражаться высокопарно.

Спасибо за перевод.
Вам спасибо. Старался. :) После просмотра доклада, сильно упростил собственную библиотеку.
Скорее бы в Java замыкания добавили… :-)
А вообще я с Вами полностью согласен: множество классов сильно усложняют чтение.
UFO just landed and posted this here
мне кажется снижение кол-ва классов не есть настоящая оптимизация.
В статье приводится контр пример: как куча классов и 660 строк кода сжимаются в 3 строки, и оно работает так же. Вполне себе оптимизация. Работа с таким инструментом проще.
По-моему тут скорей «не создавайте новые классы вместо примитивов» но не буду спорить — возможно в данном случае и проще. Но из причин по которым мне когда либо хотелось рефакторить код маленькие классы не несущие особой логики — в самом низу — особо они не мешают, а в случае чего их можно дорастить.
не часто, но бывает раз в год. Помех я от них я впринципе не вижу — места в оперативной системе для файлов мне не жалко, а нарастить естественней чем создавать новые.

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

То есть скажем в данном примере (если я правильно понял) ячейка может иметь только значение да\нет поэтому вся полезная инфа в ней в том что такие-то координаты в сетке заняты живой клеткой. А если «внезапно» окажется что у клетки может быть 4 состояния — придется всётаки создавать класс. А так бы он хоть примитивный но был уже.

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

Где, простите?
в MFT — не знаю как аналог в юниксе называется
Оптимизация была бы, если бы заработало быстрее. А «так же» улучшило читаемость и упростило код, но не оптимизировало.

Пример из кучи классов и 660 строк оторван от жизни. Обычно классы с одним единственным методом создаются ради шаблона чёрного ящика.
Как же он оторван от жизни, если он из жизни?
Оптимизировать можно разные параметры. Оптимизация быстродействия — лишь частный случай.
В питоне все исходники всегда открыты.
Если набрать в любом поисковике «парадигма ООП провалилась», то таких примеров очень много, это уже мем, краткое описание которого можно почитать на blogerator.ru/page/oop_why-objects-have-failed

На самом деле применение ООП довольно неплохо в тех архитектурных решениях, когда проект прозрачен на самом первоначальном этапе его написания. То есть сразу понятны основные объекты или в том случае, когда руководитель программист и понимает что куда сувать в дальнейшем и сколько времени можно потратить на рефакторинг.
Но суровая практика показывает, что «игроки в гольф», как именуют менеджеров высшего звена за пару месяцев могут превратить любую изначальную идею в стройную кашу, хаотичную разработку. Причём не от глупости, как часто считают программеры, а от степени осознавания проекта, когда сакми уже начинают осознавать что получилось или по отзывам клиентов. ООП предназначался скорее для джедаев, сверхмозгов, которыми считали программистов. Редкий программист способен предугадать истинное развитие проекта на практике, мы же тоже люди.
Достаточно часто частные решения «банды четырёх» спасают, как самостоятельные решения внутри любого не ООП кода в частности, proxy. А в общем, поосторожнее надо с чистым ООП, пустое это.
Я больше скажу, почти любая парадигма в чистом виде неприменима. Любая теория разбивается о колючий гравий реальности. Поэтому разумный человек программист всегда ищет компромисс в виде комбинации решений из разных парадигм.
ООП, безусловно, служит верой и правдой уже несколько десятков лет. Но люди еще дольше считали, что Солнце вращается вокруг Земли
ООП служит верой и правдой уже несколько десятков лет. А люди считают, что Земля вращается вокруг Солнца вот уже несколько сотен лет — и ничего, вроде до сих пор считают и ещё хрен знает сколько будут считать.

Ну что, попробуете доказать, что ваша аналогия лучше моей?
Mezomish, не собираюсь с Вами соревноваться.
bakaneko, никто никуда не проваливался.
Хабралюди, ставьте минусы, но когда-нибудь придумают то, что круче ООП
Нет понятия «круче ООП» или «не круче ООП». Есть парадигмы программирования, подходящие или не подходящие под данную конкретную задачу.
Много чего круче ООП

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

Однако, это не имеет ровно никакого отношения к утверждению «Земля вращается вокруг Солнца».
и ничего, вроде до сих пор считают и ещё хрен знает сколько будут считать

Ну для простых людям что там вокруг чего вращается неважно, а специалистам неплохо бы знать, что утверждение "Земля вращается вокруг Солнца" неверно.

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

Ну да, парадигма ООП провалилась, а Windows умирает.
Да что там Windows — монолитные ядра умирают все поголовно! :)
С чего вдруг провалилось то? Да, и с чего вдруг «чистое ООП»? Может, «Right Tool For The Right Job»?
Класс! Получил искреннее удовольствие.
Только что-то мне подсказывает, что ключевую мысль можно выразить короче ;-)
Интересно, почему автор не написал что-нибудь типа
def greeter(greet):
   return lambda name: "%s, %s" % (greet, name)

Всяко короче и проще, чем у него.
«Важна читаемость» :-)
То есть, мой пример менее читаем, чем
import functools
greet = functools.partial(greet, 'превед')
greet('красавчик')

Я понял :)
Да все ж относительно :)
Это удобнее тем, что функция остаётся одна. Если вашу функцию надо будет вызвать 1 раз, получится:

greeter('превед')('кросавчег')
Ииии… что? Вы правда считаете, что питоновский костыль для реализации каррирования при этом лучше? Давайте, я его тоже запишу подобным образом:
import functools
functools.partial(greeter, 'превед')('красавчик')


Я так, чисто на всякий случай напомню, что изначальная задача была — иметь по сути шаблонную функцию с заданными свойствами (hello). Автор ломает этот функционал, и говорит нам: «Нет, у вас будет просто функция без свойств. Если вам очень хочется, используйте functools, которые в общем-то инкапсулируют эту функцию в объект-функтор». То есть, сделает ещё более сложную иерархию, чем была сначала. Это — упрощение? Это — борьба с классами?
Функция со свойством не была целью. Цель — написать работающий код как можно компактнее. Поэтому функция тут лучше, она работает и для одиночного вызова:

greet('A', 'B')

и для множественного:

g = partial(greet, 'A')
g('B')
g('C')
g('D')


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

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

Более того, если бы вдруг захотелось где-то поиспользовать одиночные вызовы, то, даю слово, здесь это тоже можно было бы реализовать:
def pgreeter(a, b):
   return greeter(a)(b)

На всё про всё 4 строчки, никаких дополнительных импортов, никаких классов, который ненавидит, но использует автор.
Ну не знаю, что он хотел. У вас в итоге получились 2 сложных функции, а у него 1 простая и одна «спрятанная». :) Ну в любом случае ваш пример тоже проще, чем класс.

Кстати, он не ненавидит классы. Он говорит прямо и приводит пример с heapq, где класс как раз нужен. Просто классами часто злоупотребляют.

Я б сейчас это всё переписал, потому что соль его заявлений — в середине. Но уже из песни слов не выкинешь.
Даже так
greeter = lambda greet: return lambda name: "%s, %s" % (greet, name)

И для любителей сокращений как в математике
r = lambda g: return lambda n: "%s, %s" % (g, n)
лишнее слово «return» затесалось :)
там, видимо, print подразумевался (во втором примере он появляется).
Ко всему всегда нужно подходить с умом, иногда и классы в два метода имеют право на существование.
Декомпозиция временами требует таких вещей, но всегда нужно соблюдать баланс.
Иногда бывает видишь код и думаешь — нафига все это, тут же все в две строчки можно.
А иногда видишь две строчки и рвешь волосы, т. к. эти из-за этих двух строчек в большой системе, придется очень много всего переделать для внесения нового функционала.

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

«Сделать HTTP запрос» это не объект, а вот Board и Cell вполне себе объекты. И по мере того, как они используются в программе, могут добавляться различные их разновидности (подклассы), и исходный класс может делегировать часть логики из методов neigbors/advance в подклассы прозрачно для пользователя…

Например, если нам надо поддерживать 2 вида клеток: 1) которые считают соседями клетки сверху-снизу; 2) которые считают соседями все клетки, включая диагонали.

С классом Cell метод neigbors переопределяется на раз без изменения API, с отдельным же методом neigbors надо менять API и способ вызова. А рефакторинг в большой системе на питоне еще то удовольствие…
Сделать в дальнейшем из функции класс не проблема :)
Нужно просто понимать когда нужен класс, а когда функция.
> Сделать в дальнейшем из функции класс не проблема :)
без изменения клиентского кода?

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

Это как раз и мастерство: знать, когда можно «срезать углы» и обойтись функцией, а когда надо и «соломки подстелить» и добавить возможность расширения…
UFO just landed and posted this here
Только не функционал, а функциональность. Функционал — это что-то из математики :)
Что режет глаза в переводе — «функционал». Наверное, речь всё-таки о функциоанльности(functionality).
Там в оригинале — «фичи». :)
Вот от этого:
Если вы видите класс с двумя методами, включая __init__, это не класс.
К этому:
На этом месте надо сказать «стоп»: у нас есть класс Поле, в котором 2 метода: __init__ и «сделать ход». В нём одно свойство — словарь, значит со словарём и надо работать. Заметьте, что не надо хранить соседей точки, они уже и так есть в словаре. Живая точка или нет — это просто булевое значение, поэтому будем хранить координаты только живых клеток. А раз в словаре хранятся только True, нужен не словарь а просто множество (set) координат. Наконец, новое состояние не нужно, можно просто заново создать список живых клеток.
… дорога лет в 10 для среднестатистического человека. Я лично уже наверное никогда не дойду.
Конечно, сделать такую оптимизацию можно только плотно поработав над задачей. Но она и так поэтапная:

1. убираем класс
2. смотрим, что в словаре True и False, и нам нужны только True, следовательно убираем False
3. понимаем, что словарь только из True бессмысленен, заменяем на set.
Неужто в школе не так писали? На каком-нибудь турбо паскале.
UFO just landed and posted this here
А теперь убираем код отвечаюзий за запуск глайдера — остается 14 строк.
В классах нет управляющего кода, то есть ещё надо добавить строк.

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

import itertools


def neighbors(point):
    x, y = point
    for i, j in itertools.product(range(-1, 2), repeat=2):
        if any((i, j)):
            yield (x + i, y + j)


def advance(board):
    newstate = set()
    recalc = set(itertools.chain(*map(neighbors, board)))
    for point in recalc:
        count = sum((neigh in board)
                for neigh in neighbors(point))
        if count == 3 or (count == 2 and point in board):
            newstate.add(point)
    return newstate

glider = set([(0, 0), (1, 0), (2, 0), (0, 1), (1, 2)])
for i in range(10):
    glider = advance(glider)
print glider
опечатка была. Как вы делаете подсветку синтаксиса?
<source lang=python>
    print u"Как-то так"
</source>
Очень рекомендую сделать. Кстати, по поводу опечатки — self тоже стоит удалить
point
раскрывать не обязательно, можно по индексам достать.
Сколько самоуверенного апломба и неопытности…

Приведенный пример с игрой «Жизнь» очень характерен. Да, в виде такого вот отдельного примерчика все выглядит замечательно. Но если представить себе, что это — кусок настоящего серьезного проекта, то дальше последует классическое:
— Теперь нам надо вариант «Жизни» с 4 цветами, по поколениям;
— Состояние игры надо сохранять…
— … в XML…
— А теперь пусть будет вариант в котором есть 8 полей рядом и на каждом игра идет по своим правилам
— И вообще надо сделать поле с шестиугольными ячейками.

Так вот, грамотно сделанные классы дают нужную гибкость и расширяемость. А вот предложенное «элегантное решение» нужно будет рефакторить, преобразовывать в классы — и хорошо, если рядом будет более опытный разработчик, который заставит это сделать сразу. Иначе получатся такие спагетти, что мама не горюй…
«Неопытности» — о да! 15 лет стажа, ключевой разработчик Питона. Господин Гейтс, я вас узнал, что вы скрываетесь под какими-то никами?!
Как он показал, все заделы под «понадобится» оказываются ненужными.
Мне кажется, что проблема в том, что автор доклада, как и те, кого он осуждает — все ударяются в крайности.
Ну так автор и не говорит, что классы не нужны. Он рекомендует думать мозгами, прежде чем что-то делать. Я считаю это хорошим советом и совсем не крайностью.
Ага.

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

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

С другой стороны в некоторых задачах объектный подход — это как глоток чистого воздуха, когда у тебя реальные объекты (а не абстракции N-ного уровня), они есть и все тут.
Поясню про накладные расходы. Речь идет даже не только о количестве строк, но и о быстродействии. Если стоит какая-нибудь злая расчетная задача, то лучше побольше работать с примитивами. При сравнении двух строк, например в c#, простая обертка символа в класс, уменьшает быстродействие в полтора раза (это то что я лично тестил).
> Как он показал, все заделы под «понадобится» оказываются ненужными.

В оторванном от контекста примере — да. А если эта «жизнь» будет на самом деле библиотекой, которую будет использовать куча народа и каждый третий будет просить её немножко допилить? Про это ключевой разрабочик питона ничего не написал?
Разве библиотека Гугла — это не «жизнь»?
Мой аргумент (чтобы было понятно, о чём спорим):

"… те сущности, которые являются объектами, лучше объектами и оставить. «Сделать HTTP запрос» это не объект, а вот Board и Cell вполне себе объекты. "

Если вы с этим не согласны, аргументируйте плиз. Заодно со ссылкой на «библиотеку гугла», где функциональность, эквивалентная классовым методам, вынесена в отдельные функции.
Потрудитесь прочитать статью прежде чем спорить. Вам всё ответил автор доклада.
Я прочитал, не согласился и объяснил, почему и в каких случаях я не согласен (см мои комментарии по ветке выше).

Жду Ваших собственных аргументов и ссылки на «библиотеку гугла» с этим замечательным подходом…
Знаете, я бы очень хотел в моих проектах использовалось как можно меньше классов. Это заставляет писать проще и не впадать в истерику overengineering'га. Большинство проектов которые у меня были — были необоснованно сложные.

Могу привести пример из текущего проекта. Я тренирую классификаторы. Они вызываются одной функцией. Чтобы подключить их мне бы хватило пары строк кода. Но у босса были далекоидущие планы, и это все было обернуто в систему плагинов. +500 строк кода. Планы не оправдались. Но теперь, чтобы подключить его и отладить работу — мне нужен целый день. Вместо пяти минут.

Классы — это не всегда плохо. Необоснованная сложность — это плохо всегда.
> Классы — это не всегда плохо. Необоснованная сложность — это плохо всегда.

Абсолютно согласен, осталось немного: понять когда сложность «необоснованная» :)

Класс вместо функции в учебном примере — необоснован.
Система плагинов вместо 3х строчек кода/простой фабрики — необоснована.
Классы Board/Cell в реальной библиотеке для игры «Жизнь» — мне кажутся обоснованными.
Зависит от задачи. Если бы я проектировал игру жизнь, я бы сделал нечто такое:

>> import life
>> b = life.simulate(a, 1)

a, b — двумерные массивы интов или булианов. a — исходное поле, b — после симуляции за 1 шаг. simulate — функция, которая бы обсчитала поле N шагов.

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

Я не искал ничего про автора. Что ж, значит с неопытностью я ошибся.
… по неопытности. Или в силу самоуверенного апломба?
Авторитеты авторитетами, но слепо верить нельзя никому. Стаж и должность не являются критериями истинности. Нужно всё обдумывать своей головой, тестировать на практике, и только тогда соглашаться или нет.
Угу, не понравилось что этот авторитет ничего не сказал об абстрактности, что является ключевой особенностью ооп. И жаль не включили в перевод вопросы из зала и ответы. После них я почему-то перестал верить Джеку.
Заказчику иногда нужно уметь говорить — нет. И думать о том, как лучше реализовать то, что он просит. Тогда и не будет шестиугольных квадратов
Не реагируйте. Некоторые жены плачут из-за мужей-алкоголиков, а есть люди, которые плачут из-за заказчиков-мудаков. И тем и другим нужно иногда поплакать и рассказать всем вокруг, как же им тяжело.
С заказчиком, как иногда бывает, может такое случиться: платят за четырёхугольники, просят оставить заделы под шестиугольники. Потом заделы остаются ненужными.
1)Кому плохо от этих заделов?
2)Это лучше чем заказчик платил за четырехугольники, нужны им шестиугольники, а программисты сделали квадраты потому что так классов меньше.

Да, код который гонят потоком менее элегантен и выверен чем штучный.

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

Это плохо что программисты заранее не обладают четким представлением о том что нужно (и может стать нужным) заказчику в будущем.

Но при балансе между «написать лишний класс» и «соптимизировать лишний класс» я в большинстве случаев выберу первый кейс потому что если есть сомнения значит лучше перебдеть чем недобдеть.
1) Читайте текст. Там приведены примеры.
2) Если заказчик платит за простое решение, а хочет сложное — это обман испонителя.
Прочитал. Понял что говорим о несколько разных вещах, верней автор писал о двух разных вещах, а я прочитал лишь об одной.
Вот с этим
В той было 660 строк, в этой — 15. Всё, что делает этот код — пользуется методами стандартной библиотеки.

я согласен — выпендриваться и создавать свой URL или String с блекджеком не имея понятия зачем это нужно не стоит.

Однако в случае когда у нас есть бизнес-сущность «ячейка» и «доска» их стоит создать даже если они будут делегировать всё банальному boolean и boolean[][] соответственно потому что мне проще понимать программу находясь на уровне абстракции который скрывает от меня примитивные типы.

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

я не настолько высоко сижу чтоб меня волновало выбивание денег из заказчика, пока в случае CR заказчик платит. Но лучше когда простое с т.з. заказчика изменения является таким и для разработчика.
Вот потом и торчат канализационные трубы посередине комнаты для гостей: «задел на всякий случай».
Весело :)
Но автор коммента говорит о полиморфизме,
а картинка повествует о «прелестях» god object.
Я ожидал перевода этого доклада! Думал, не дождусь и сяду за перевод сам. Ан, нет, он появился. Спасибо вам, автор :)

Могу подписаться под каждым словом. По моему опыту, все еще гораздо хуже. Как будто у программистов психологический дискомфорт, если в коде нет класса. Думается, ООП лучше учить после двух-трех лет процедурного или функционального программирования. Чтобы гвозди в мозг загонялись постепенно, и инструмент не превращался в религию.
Я думал, что его успеют сюда запостить до меня. :)
И хорошо, что я не начал. Так оформить у меня бы не хватило терпения.

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

Для такого первичного обучения js отлично подошел бы (если бы не гемморойный scope и убогая работа с массивами)
А я после того как прослушал лекцию по Model Thinking в которой рассказывалось как раз про игру «Жизнь» написал решение на JS только того чтобы решить задачу) Было интересно!)
А вообще я сам свидетель того как у одного нашего сотрудника, который ну просто очень любит писать классы (PHP), доходило до того, что какой-то один класс состоит из двух методов. Вот я задал ему вопрос, а нафига? Можно же написать 2 функции и не делать мозги с классами. На что получил ответ: «Мне так удобнее».
Возможно он прав.
Вопрос в чем содержится структура проекта?
Если она сосредоточена в классах и шаблонах, как это обычно бывает в C++ и Java, то делать отдельную определенную функцию по меньшей мере странно (за это в приличной команде и побить могут). Как ее вписать в общую структуру? И в этом случае класс с одной функцией может быть лучшим решением.

Парадигма ООП провалилась. Нет. Она просто достигла границ применения. Строить большие библиотеки для создания интерфейсов удобнее в рамках ООП.

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

PS: Я лично часто встречался с тем, что классы используют только для изоляции кода. Это плохо, ибо это не функция класса, это функция namespace, внедрить которую можно в любой момент даже после разработки кода.
Меня не сильно тревожит каким образом пишется код, главное, чтобы работало!) Но, ИМХО, в некоторых случаях проще написать пару функций, чем городить классы. Тем более, что эти функции во всё проекте используются всего 1-2 раза.
Пример, допустим генерация таблицы с клиентами и ещё парой параметров для них. Используется 1 раз во всё проекте, писать для этого класс чисто под этот функционал, ИМХО, немного расточительно.
Но если работает, то зачем дёргать человека, чтобы переделал скрипт с учётом моих прибабахов?) Вот я и не трогаю)
Согласен «про бритву Оккама» и замечание про специфику инструмента (С++, Java) также!
я вот не понимаю, если это перевод, почему он как хабратопик опубликован?
Я учусь Python, но уже заметил этот маразм с классами. Приводят простенький пример и сразу класс, там где можно было бы обойтись функцией. А для новичка это особенно непонятно. Нужно ли действительно использовать класс или можно бы обойтись функцией? Только после пары экспериментов, понимаешь, что автор очень любит ООП в больших и малых количествах.
у ООП подхода есть «накладные расходы» из-за них при малом объеме функциональных требований проседает кпд что действительно может конфузить — зачем использовать метод который явно трудоемкий и неповоротливый (и при этом ещё убеждать что он простой и гибкий).

Однако с ростом объемов проекта эти затраты (на создание классов) пропорционально уменьшаются и на первый план выходят другие — уже упоминавшийся выше God object например. Я видел классы с более чем десятком тысяч строк кода и когда видишь такое понимаеш что лучше бы авторы придумали как разбить этот класс на десяток других, поменьше.
Забавно, что человек, опубликовавший TimSort говорит, что:
Простое лучше сложного;
Плоское лучше вложенного;
Важна читаемость;
Если программу трудно объяснить, она плохая.
UFO just landed and posted this here
Пустые классы тоже напрягают, но зачастую это издержки «программирования интерфейсами». Про задел на будущее уже сказали, что оно может никогда не наступить, но тут я бы сказал, что хороший интерфейс + всего 1 класс — лучше, чем 1 компактный класс в плане будущего расширения. Ведь когда идёт проектирование системы, в голове держится много аспектов и решить задачу хорошего интерфейса проще, чем сделать компактный класс, но в последствии вносить изменения в архитектуру — так или иначе придётся выделить интерфейс, и не факт, что в нём будут учтены все аспекты, которые могли быть учтены при начальном проектировании, вплоть до банальной причины — изначальный архитектор ушёл и спросить не у кого. Да и постоянное перепроектирование не есть гут.
Плюс, лично у меня, как PHP-программиста есть трудность в переключении контекста — обычно с функциями используются простые переменные ($x = sqrt($y)), а объекты чаще всего взаимодействуют с объектами, поэтому код $userChoice = prj_user_choice([User ]$user) будет выглядеть несколько обманчиво — подсознательно от функции я ожидаю простую переменную. Со статическими методами проще, название класса настраивает на то, что метод скорее всего вернёт связанную с этим классом информацию. И, кстати, интересное наблюдение — функция, возвращающая объект — необычно; метод, возвращающий простую переменную (число, например) — ок.
Действительно один из лучших в плане повседневного применения докладов с последнего PyCon.
Автор статьи видимо не в курсе существования статических методов класса? Лучше свалить в глобальное пространство имен все функции скопом?

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

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

Конечно, есть немало примеров оверинжиниринга, когда ради 2 простых действий используется куча геттеров, сеттеров, наследование, абстрактные интерфейсы и фабрика отдельным классом. Или ради сложения дат делают отдельный класс. Но разве это говорит о недостатках ООП подхода? Нет.

Использование Ооп никак не противоречи KISS, DRY и подобным рекомендациям.

Давайте возьмем любой проект, над которым работает хотя бы человек 5, или в котором планируется большрой объем фнукционала, и посмотрим, с какой скоростью как у них будут при таком «первобытном» подходе появляться конфликты имен, дублирование кода, лапша и баги. Ваши костыли не масштабируются. День школоты на Хабре, блин.
Может быть я ошибаюсь, но мне казалось, что статические методы как раз таки являются языковым хаком для выражения обычных процедур в парадигме ООП. Да, они изолированы, да, пространство имен разделено, но объекты как таковые никто не объявляет и не создает. Собственно, статья именно об этом.
В питоне нормальные неймспейсы и система импортов, статические методы классов поэтому там редко когда нужны.

C восторгом про ООП обычно отзываются те, кто его не так давно познал (=2-4 года с ООП, из них год-другой с паттернами), и при этом всякие SCIP не читал. Есть, конечно, другая крайность — люди, которые пописали с классами-паттренами что-то java-подобное лет 5-10, потом только вот прочитали SCIP и «прозрели», тут все вообще на функциях переписывать начинают, с map/reduce/curry/рекурсией где нужно и где ненужно. Возможно, потом этап «по дефолту — функции, если где-то это жмет — класс» наступает. Но я лично без понятия, как правильней и лучше всего) imho оно и есть imho
В питоне нормальные неймспейсы и система импортов, статические методы классов поэтому там редко когда нужны.

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

думаю, хорошим контр-примером здесь служит Django, которая очень активно пытается использовать систему питонячих модулей, для структурирования ООП-кода, и каким заморочкам это приводит на практике. мм. т.е. известно к каким — при таком подходе к зависимостям от интерфейса, добавляются зависимости от неймспейса (читай — конкретных библиотек) — в результате чего код намертво привязывается к конкретной реализации.
Ага, с этой проблемой сталкивался в django-fab-deploy: есть набор команд, которые как-то взаимодействуют, и если делать их отдельными функциями, а не методами одного класса, то переопределить отдельный шаг проблематично становится. Там если на функциях все оставлять, то нужно наборы коллбэков всюду передавать, классы тут более удобный синтаксис предоставляют. Ситуацию усугубляло то, что в fabric «задачи на классах» реализованы как «одна задача — один класс», а не «метод класса — задача», так что основанные на классах задачи fabric тут ровным счетом ничего не давали (в итоге пришлось делать чудной хак — у класса есть метод, который добавляет методы-задачи класса (помеченные декоратором) в модуль как отдельные функции — т.к. fabric умеет задач-функции и задачи-классы, но не умеет задачи-методы классов).

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

Другое дело, что часто даже для несвязанных друг с другом функций классы используются в качестве неймспейсов, для таксономии, организации кода. Вот в этом смысла вроде нет (я по крайней мере, придумать его не могу — может знаешь?), и вот тут питоньи неймспейсы делают то же самое, только проще. Т.е. классы, как мне кажется, лучше использовать, когда они дают какие-то конкретные преимущества, а не «по умолчанию» просто для организации кода.
Можно еще вернуться к изначальному примеру от egorinsk (утилита на 5-6 функций). Если все эти 5-6 функций заменить на статические методы, то это не даст ровным счетом ничего, даже если они вызывают друг друга как-то, т.к. в статических методах класс будет точно так же зашит, как и неймспейс. В C++ (и вроде в Java) статические методы работают как staticmethod в питоне (это если рассуждать, какое значение термина «статический метод» более употребимо).
Давайте возьмем любой проект, над которым работает хотя бы человек 5, или в котором планируется большрой объем фнукционала, и посмотрим, с какой скоростью как у них будут при таком «первобытном» подходе появляться конфликты имен, дублирование кода, лапша и баги

Использование статических методов класса там, где требуется обычный namespace — это и есть всовывание ООП куда не надо. Именно об этом и была статья.
Автор статьи видимо не в курсе существования статических методов класса? Лучше свалить в глобальное пространство имен все функции скопом?

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

Объектно ориентированное программирование — это мышление объектами. Представляем себе объекты реального мира и манипулируем ими в своей программе. Вместо того, чтобы передавать айдишники — передаём объекты и т.п.

Где-то читал, что когда ООП появилось — многие говорили, что это лишняя надстройка и всё можно сделать функциями. Но это совсем не так — это другой подход, и синтаксис тут не причём.

В языке TCL до того, как появилась там нормальная поддержка ООП — люди умудрялись функциями программировать объектно.
Читал противоположный совет: не мыслить реальными объектами. ООП нужно для абстракции кода и хранения данных вместе (и насчёт передачи объекта вместо id вы правы). Собственно, если несколько функций начинают работать с теми же параметрами или с теми же глобальными переменными, это значит, что напрашивается класс. Так я упаковал в класс несколько функций в файловом конвертере. После чего посмотрел на это, увидел, что объект получается одноразовый, и просто сохранил все функции и переменные в замыкании.
О проектировании иерархии классов говорили все, кому не лень — одни по делу, другие болтали «об имитации объектов реального мира».
Jeff Alger, «C++ for real programmers»
Я бы так сказал — объекты реального мира — это трюк, который поможет вникнуть в ООП.

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

А вот когда пишешь игру какую-нибудь? Например, мортал комбат :) здесь сразу понятно — объект — персонаж, у него руки — объекты, ноги — объекты, пальцы — также объекты :) Этакая получается композиция из объектов.

Теперь снова возьмём сайт. Допустим — он доставляет посылки из одной страны в другую. У нас такие объекты:
Страна
Посылка
Склад

У посылки можно выделить метод getCountry() — вернёт объект страны, в которой теперь сейчас посылка. Или сложнее — у посылки вызвать getWarehouse() — вернёт объект склада, а у объекта склада можно сделать getCountry(). Потом чтобы послать посылку можно вызвать метод sendToCountry и передать ему страну назначения. При желании, можно даже взвесить посылку :)

Я сам втыкал в ООП довольно долго и был одним из тех, кто также не мог понять чем классы всё-таки отличаются от функций. В итоге, наверное, яваскрипт помог — когда видишь на экране эти самые объекты, которые двигаешь и т.п.
это был ответ на коммент siberiano
UFO just landed and posted this here
>> одного из клчюевых разработчиков языка Питон
Python назван так в честь крутых британских комиков — Monty Python, а не в честь змеи. Так что на русском правильнее писать Пайтон, так как именно так читается название оригинального скетч-шоу. Извините за занудство.
Честно говоря, всегда называю их «монтипитонами», несмотря на «МонтИ Пайтонзза Фллаинь Сёа… каззззсс!!». Проще звучит.
А его фамилия ничего общего со змеёй не имеет? Или с латинским словом Python? А то и с греческим Πυθών? Да и вообще правил заимствования однозначных нет, заимствоваются слова так, как народу удобнее, а не так как они читаются на языке оригинала, да ещё в случае если слово по другому читается на других языках. Классический пример Лондон или Ватсон. Да и мы вроде не возражаем, что нашу столицу они называют Москоу, а не Москва.
Ватсон и Москоу это последствия латыни. Напомню еще «И сможет собственных Невтонов...».
Ну так и «Питон» тоже последствия латыни. Когда-то русский язык заимствовал латинское «python» как «питон». Теперь и английское «Python» (скорее всего имеющее корни в латинском) заимствует как «Питон». Тем более, что в английском правила чтения, мягко говоря, неоднозначные. А большинство программистов читают, а не слушают, пишут, а не произносят.
Да, тут вы правы. Я ошибался в допущении, что Монти Пайтона все называют именно Пайтоном. Но как выяснилось выше, это не так.
В целом, конечно, интересно, но автор мешает в кучу совершенно разнородные понятия, сравнивая теплое с мягким. Да, да, я помню, что «Он умнее и вас, и меня». И из за этого еще более странно.

Автор берет неудачный ООП-API, или просто неудачный пример использования ООП, и выставляет это как недостаток всего подхода. WTF? И да, не нужно про «ООП головного мозга» тут, я прекрасно понимаю, что есть место и ООП, и функциональным языкам, и процедурным, и так далее. Просто для каждой задачи — свой «tool». Не нужно говорить, что космический корабль не решает своей задачи, заставив его рыть землю. Не нужно человеку, потерявшемуся на 2 дня в пустыне и только вернувшемуся, протягивать печенье. Не нужно применять ООП там, где очевидно требуется функциональный подход.

> «Я не против классов в принципе. Классы бывают нужны — когда много меняющихся данных и связанных с ними функций.»
Ну очень спорное заявление от всемогущего суперумного чувака. Класс нужен там, где есть «состояние» данных (некий state), которое нужно хранить, контролировать life-time, соблюдать инкапсуляцию.
> Класс нужен там, где есть «состояние» данных (некий state), которое нужно хранить, контролировать life-time, соблюдать инкапсуляцию.

«соблюдать инкапсуляцию» не должно являться целью
Самоцелью — нет, но для предотвращения невалидных/несуществующих состояний объекта — да.
Бритва О́ккама — «Не следует множить сущее без необходимости»
К вводу функции в проект, где всё построено на классах это тоже относится? :)
Функциональность класса достигается функциями. Поэтому класс в любом случае будет являться более высокой абстракцией чем функция и может быть излишним.
Так множит ли сущности введение абстракции нижнего уровня в программу, где всё уже построено на абстракциях более высокого уровня? По сути то же самое, что в программу на 1С ввести ассемблерную вставку.
Вроде логично, что класс с одним методом можно заменить функцией. Но вот если велика вероятность, что метод долго одним не будет, а будет вводиться второй, третий и т. п., то всё же лучше сразу ввести класс.

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

Если же есть объект с методами и состоянием, легко завязать его методы на состояние, легче появляются неявные связи и зависимости между методами, сложнее понимать, как что работает (т.к. чтоб понять, как работает метод, недостаточно посмотреть в этот метод, нужно еще понимать, откуда берется состояние — а оно, возможно, суперклассом вычисляется или еще где-то, даже в сам класс посмотреть недостаточно бывает, чтоб понять, как работает какая-то его часть).

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

У классов есть преимущества — например, более удобный синтаксис для переопределения какой-то одной части вычислений при сложном взаимодействии (через наследование), или более удобный/привычный синтаксис, когда состояние все-таки есть, оно неизбежно и с ним нужно работать как-то. Короче, баланс какой-то искать всегда лучше — но плюсов у функций много.
Мне кажется, что автор не очень удачно выразил то же самое, о чём писал Игорь Ткачёв в своей статье «закон сохранения сложности». Да, классы имеют некоторые накладные расходы. Да, иногда эти расходы выглядят неадекватно большими и их хочется избежать. Такое бывает и это нормально. Потому что в каком-то смысле классы были эволюционным ответом на проблемы структурного программирования. Если почитать Ткачёва, то становится ясно, что классы переносят одни виды сложности в другие. (Снизили сложность количественную и сложность изменения, а повысили структурную сложность и порог вхождения.)
Ну че, неплохо он набросил. Я разделяю точку зрения автора о том, что здравомыслие не помешает, но на каждый из его примеров в голову легко приходит контрпример, так что все зависит от задачи и проекта.

У Гугла же я вообще наблюдаю деградацию решений и API, ну тот же android и wp7 сравнить, в wp7 все раза в полтора компактнее. Да, есть некий стереотип google = лучшие кодеры, но у меня есть подозрения что лучшие кодеры у них со временем будут просто сидеть, смотреть по сторонам и перманентно о… ть.
Эх. Было дело, работал в одном проекте, где всё начиналось «как проще». Классы только в необходимых местах, всё в простых функциях. Логика исключений вообще элементарная. Джек был бы рад.

Вот только тесты в это «как проще» не вошли.

Вот только проект «пошёл» и стал бурно расти и расширяться во всех местах. Даже там, где и в голову раньше бы не пришло.

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

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

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

Поэтому ярых последователей бритвы Оккама, не понимаю также как и тех, кто каждый аргумент в отдельный класс оборачивает. Зачем такие крайности?
UFO just landed and posted this here
Рефакторинг, это не переписывание. Переписывать намного сложнее.

Кроме того между рефакторингом без тестов и рефакторингом с тестами тоже огромная разница.

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

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

Когда люди, руководствуясь KISS, бритвами Оккама, вырезают всё, что не нужно прямо сейчас, они усложняют будущее. Не могу сказать, что это плохо. Чем меньше работы в начале, тем более вероятен успех и меньше потери в случае провала. Много думать на перёд рискованно.

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

По-моему, вам при таком подходе совсем не яйца мешали… не помогла бы и крутая ОО модель.
Просто не надо строить из себя Вангу, при написании кода. Не надо смотреть на 10 лет вперед, смотрите на шаг.
И в итоге, ваше ПО будет разрабатываться быстро (а это деньги) и его будет легче поддерживать чем всемогущего монстра.
Сколько раз вы тратили час на то, чтобы разобраться как аккуратно интегрироваться своим модулем в чужой проект, затем еще час это все отладить, вместо того чтобы за 15 минут написать этот функционал и за столько же его проверить?
Будьте проще, и форки к вам подтянуться.
В конце видео прозвучало несколько вопросов от зрителей, думаю есть смысл добавить и их в перевод.
Эта статья давно меня заинтересовала… Изначально, я бесился из-за того, что в моей команде принято было писать статические методы вместо простых функций. Я понимаю, что в основном автор против маскирования функций за оболочкой класса (правило 2-х методов). Но позвольте:
Мы задаём эту задачу на собеседовании (игру Жизнь), потому что если вы не умеете такого — нам не о чем разговаривать.

Ребята, а сколько из вас реально понимают эти 10 строчек кода с полной реализацией игры Жизнь? Честно говоря, я не понимаю. Я читал это 2 года назад — не понимал. И сейчас не понимаю. Это реализация рисования пикселей на экране, а не игры! Что уж говорить про собеседование — я бы сразу ушёл((

Задача об игре Жизнь не укладывалась в моей голове, а самооценка сильно болела, пока я не убил денёк на свою реализацию — Жизнь/Core. И вот какой парадокс… Мой вариант не доработанный, с безумными абстракциями, на тучу строчек кода для меня выглядит намного естественней! Я знаю, как поменять правила, как замкнуть поле, как играть в трехмерном пространстве… Как запустить по этому полю Динозавра, откладывающего яйца!

Я написал эти классы не для того, чтобы сделать что-то наперёд. Я сделал это так, потому что мыслю такими категориями. И моя голова не может привязать живую клетку к системе координат. Возможно, если бы меня попросили раскрашивать пиксели, я бы смог в 10 строк, а может и не смог бы. Но слово «жизнь» слишком воздействует на моё воображение.

И вот у меня вопрос, а не делятся ли разработчики на два типа:
1) Математики — жонглируют в голове 10 мячами, долго переключаются между абстракциями, пишут про «хватит классы».
2) Философы — быстро переключаются между абстракциями, не могут жонглировать более чем 3-мя мячами, пишут про ООП.

Может есть исследования какие-то в эту сторону? Ну не все могут в голове легко четырёхзначные числа перемножать, и наоборот — кому-то слишком легко говорить метафорами.

Статья в целом по делу, но в тему с “жизнью” автор зря полез. Его реализация работает на моём компьютере более чем в 1500 раз медленнее написанного в лоб с использованием подходящих инструментальных средств алгоритма с массивами (примерно 270 тысяч ячеек в секунду против 433 миллионов). Что должен иллюстрировать такой пример? Что можно быстро написать короткую, малопонятную и очень тормозную программу?

Sign up to leave a comment.

Articles