Пользователь
0,0
рейтинг
23 марта 2012 в 17:33

Разработка → Перестаньте писать классы

Фото Джэка Дидриха из профиля на G+ Признак того, что объект не должен быть классом — если в нём всего 2 метода, и один из них — инициализация, __init__. Каждый раз видя это, подумайте: «наверное, мне нужна просто одна функция».

Каждый раз когда из написанного класса вы создаёте всего один экземпляр, используете только раз и тут же выбрасываете, следует думать: «ой, надо бы это отрефакторить! Можно сделать проще, намного проще!»

Перевод доклада Джэка Дидриха, одного из ключевых разработчиков языка Питон. Доклад прозвучал 9 марта 2012 на конференции PyCon US.

Все из вас читали Дзэн Питона, наверное много раз. Вот несколько пунктов из него:

  • Простое лучше сложного
  • Плоское лучше вложенного
  • Важна читаемость
  • Если программу трудно объяснить, она плохая
  • Если программу легко объяснить, возможно, она хорошá


Написал этот текст Тим Питерс. Он умнее и вас, и меня. Сколько вы знаете людей, в честь которых назвали алгоритм сортировки? Вот такой человек написал Дзэн Питона. И все пункты гласят: «Не делай сложно. Делай просто.» Именно об этом и пойдёт речь.

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

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

Классы



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

class Greeting(object):
    def __init__(self, word='Hello'):
        self.word = word

    def greet(self, name):
        return '%s, %s!' % (self.word, name)

>>> greeting = Greeting('Hola')
>>> greeting.greet('Jorge')
Hola, Jorge!


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

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

Каждый раз когда из написанного класса вы создаёте всего один экземпляр, используете только раз и тут же выбрасываете, следует думать: «ой, надо бы это отрефакторить! Можно сделать проще, намного проще!»

def greet(name):
    ob = Greeting('превед')
    print ob.greet(name)
    return


Эта функция состоит из 4 строк кода. А вот как можно сделать то же самое всего за 2 строки:

def greet(greeting, name):
    return '%s, %s!' % (greeting, name)

import functools
greet = functools.partial(greet, 'превед')
greet('красавчик')


Если вы всё время вызываете функцию с тем же первым аргументом, стандартной библиотеке есть инструмент! functools.partial. Вот посмотрите в код выше: добавляете аргумент, и результат можно вызывать многократно.

Не знаю, у скольких из вас диплом в ИТ, у меня он есть. Я учил такие понятия как

— разделение полномочий
— уменьшение связанности кода
— инкапсуляция
— изоляция реализации

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

Пример: брюки превращаются...



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

Сторонняя библиотека API, назовём её ShaurMail, включала 1 пакет, 22 модуля, 20 классов и 660 строк кода. Мне пришлось всё это прочитать прежде чем включить в продукт. Но это был их официальный API, поэтому мы пользовались им. Каждый раз когда приходили обновления API, приходилось просматривать диффы, потому что было неизвестно, что они поменяли. Вы посылали патчи — а в обновлении они появились?

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

Что такое злоупотребление классами? Часто люди думают, что им понадобится что-то в будущем.… Не понадобится. Напишите всё, когда потребуется. В библиотеке ШаурМаил есть модуль ШаурХэш, в котором 2 строки кода:

class ShaurHash(dict):
    pass


Кто-то решил, что позже понадобится надстройка над словарём. Она не понадобилась, но везде в коде остались строки, как первая:

my_hash = ShaurMail.ShaurHash.ShaurHash(id='cat')

d = dict(id='cat')

d = {'id': 'cat'}


Вторая и третья строки кода — никому не нужно объяснять их. Но там везде повторялась эта мантра «ШаурМаил-ШаурХэш-ШаурХэш». Троекратное повторение слова «Шаур» — ещё один признак излишества. От повторений всем только вред. Вы раздражаете пользователя, заставляя его писать «Шаур» три раза. (Это не настоящее имя компании, а вымышленное.)

Потом они уволили этого парня и наняли того, кто знал, что делает. Вот вторая версия API:

class API:
    def __init__(self, key):
        self.header = dict(apikey=key)

    def call(self, method, params):
        request = urllib2.Request(
            self.url + method[0] + '/' + method[1],
            urllib.urlencode(params),
            self.header
        )
        try:
            response = json.loads(urllib2.urlopen(request).read())
            return response
        except urllib2.HTTPError as error:
            return dict(Error=str(error))


В той было 660 строк, в этой — 15. Всё, что делает этот код — пользуется методами стандартной библиотеки. Он читается целиком, легко, за секунды, и можно сразу понять, что он делает. Кстати, в нём был ещё набор тестов из 20 строк. Вот как надо писать. Когда они обновляли API, я мог прочесть изменения буквально за пару секунд.

Но и здесь можно заметить проблему. В классе два метода, и один из них — __init__. Авторы этого не скрывали. Второй метод — call, «вызвать». Вот как этим API пользоваться:

ShaurMail.API(key='СЕКРЕТНЫЙ КЛЮЧ').call(('mailing', 'statistics'), {'id': 1})


Строка длинная, поэтому мы делаем алиас и вызываем его многократно:

ShaurMail.request = ShaurMail.API(key='СЕКРЕТНЫЙ КЛЮЧ').call
ShaurMail.request(('mailing', 'statistics'), {'id': 1})


Заметьте, мы пользуемся этим классом как функцией. Ею он и должен быть. Если видите подобное, знайте, класс тут не нужен. Поэтому я послал им третью версию API:

ShaurMail_API = url = 'https://api.shaurmail.com/%s/%s'
ShaurMail_API_KEY = 'СЕКРЕТНЫЙ КЛЮЧ'

def request(noun, verb, **params):
    headers = {'apikey': ShaurMail_API_KEY}
    request = urllib2.Request(ShaurMail_API % (noun, verb),
                              urllib.urlencode(params), headers)
    return json.loads(urllib2.urlopen(request).read())


Он вообще не создаёт файлов в нашем проекте, потому что я вставил его в тот модуль, где он используется. Он делает всё, что делал 15-строковый API, и всё, что делал 660-строковый API.

Вот с чего мы начали и к чему пришли:

  • 1 пакет + 20 модулей => 1 модуль
  • 20 классов => 1 класс => 0 классов
  • 130 методов => 2 метода => 1 функция
  • 660 строк кода => 15 строк => 5 строк


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

Стандартная библиотека



Кто пришёл из языка Java, возможно, считает, что пространства имён нужны для таксономии. Это неверно. Они нужны чтобы предотвратить совпадения имён. Если у вас глубокие иерархии пространств, это никому ничего не даёт. ShaurMail.ShaurHash.ShaurHash — всего лишь лишние слова, которые людям надо помнить и писать.

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

К нашему стыду, вот пример из нашего же кода, и те же грехи видно и здесь:

services.crawler.crawlerexceptions.ArticleNotFoundException


Пакет, в котором модуль из 2 строк, класс исключения и «pass». Чтобы использовать это исключение, надо дважды написать «crawler», дважды слово «exception». Имя ArticleNotFoundException само себя повторяет. Так не надо. Если вы называете исключения, пусть это будет EmptyBeer, BeerError, BeerNotFound, но BeerNotFoundError — это уже много.

Можно просто пользоваться исключениями из стандартной библиотеки. Они понятны всем. Если только вам не нужно выловить какое-то специфическое состояние, LookupError вполне подойдёт. Если вы получили отлуп по почте, всё равно придётся его читать, поэтому неважно, как называется исключение.

Кроме того, исключения в коде обычно идут после raise и except, и всем сразу понятно, что это исключения. Поэтому не нужно добавлять «Exception» в название.

В стандартной библиотеке Питона есть и ржавые детали, но она — очень хороший пример организации кода:

  • 200 000 строк кода
  • 200 модулей верхнего уровня
  • в среднем по 10 файлов в пакете
  • 165 исключений


10 файлов в пакете — это много, но только из-за некоторых сторонних проектов, добавленных в библиотеку, где были пакеты из всего 2 файлов. Если вам вздумается создать новое исключение, подумайте лучше, ведь в стандартной библиотеке обошлись 1 исключением на 1200 строк кода.

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

Единственное исключение в библиотеке Питона — модуль heapq. Heap queue, «очередь в куче» — это массив, который всегда отсортирован. В модуле heapq десяток методов, и они все работают с той же «кучей». Первый аргумент всегда остаётся тем же, что значит, здесь действительно напрашивается класс.

heapify(data)
pushleft(data, item)
popleft(data)
pushright(data, item)
popright(data)

и т.д.

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

class Heap(object):
    def __init__(self, data=None, key=lambda x: None):
        self.heap = data or []
        heapq.heapify(self.heap)
        self.key = key

    def pushleft(self, item):
        if self.key:
            item = (self.key(item), item)
        heapq.pushleft(self.heap, item)

    def popleft(self):
        return heapq.popleft(self.heap)[1]


Классы разрастаются как сорняки



Состояние OAuth в Питоне — неважное. Опять же, есть сторонние библиотеки, и прежде чем использовать в своём проекте, их нужно прочесть.

Я пытался использовать сокращатель урлов от Гугла: мне нужно было взять урлы и просто сократить их. У Гугла есть проект, в котором 10 000 строк кода. 115 модулей и 207 классов. Я написал отповедь об этом в Гугле+, но мало кто её видел, а Гвидо (Ван Россум — прим. пер.) прокомментировал: «Я снимаю с себя ответственность за гугловский код API.» 10 000 строк кода — там же обязательно найдётся какая-нибудь дрянь вроде ШаурМэйла. Вот, например, класс Flow («поток»), от которого наследуют другие.

class Flow(object):
    pass


class Storage(object):
    def put(self, data): _abstract()
    def get(self): _abstract()

def _abstract(): raise NotImplementedError


Он пустой. Но у него есть свой модуль, и каждый раз читая наследующий его класс, надо сходить, проверить тот файл и снова убедиться, что тот класс пуст. Кто-то глядел в будущее и решил: «Напишу-ка я 3 строчки кода сейчас, чтобы в будущем эти 3 строчки не менять.» И отнял время у всех, кто читает его библиотеку. Есть ещё класс Хранилище, (Storage) который почти ничего не делает. В нём правильно обрабатываются ошибки, используя стандартные исключения, но им делают алиасы, и опять же нужно ходить читать их код, чтобы выяснить, как это работает.

Чтобы внедрить OAuth2 мне понадобилась неделя. Пару дней заняло чтение десяти тысяч строк кода, после чего я стал искать другие библиотеки. Нашёл python-oauth2. Это вторая версия python-oauth, но она на самом деле не умеет работать с OAuth2, что не сразу удалось выяснить. Впрочем, эта библиотека немного лучше гугловской: только 540 строк и 15 классов.

Я переписал её ещё проще и назвал python-foauth2. 135 строк кода и 3 класса, и то всё равно много, я не достаточно её отрефакторил. Вот один из этих трёх классов:

class Error(Exception):
    pass


Срамота!

Жизнь



Пушка планеров Госпера Последний пример. Все вы видели игру «Жизнь» Конвэя, даже если не знаете её имени. Есть клетчатое поле, каждый ход вы считаете для каждой клетки соседние, и в зависимости от них она будет либо живой, либо мёртвой. И получаются такие красивые устойчивые узоры, как планер: клетки впереди оживают, а сзади умирают, и планер как будто летит по полю.

Пульсар Игра «жизнь» очень проста: поле и пара правил. Мы задаём эту задачу на собеседовании, потому что если вы не умеете такого — нам не о чем разговаривать. Многие сразу же говорят «Клетка — существительное. Класс надо.» Какие свойства в этом классе? Место, живая или нет, состояние в следующий ход, всё? Ещё есть соседи. Потом начинают описывать поле. Поле — это множество клеток, поэтому это сетка, у неё метод «подсчитать», который обсчитывает клетки внутри.

class Cell(object):
    def __init__(self, x, y, alive=True):
        self.x = x
        self.y = y
        self.alive = alive
        self.next = None

    def neigbors(self):
        for i, j in itertools.product(range(-1, 2), repeat=2):
            if (i, j) != (0, 0):
                yield (self.x + i, self.y + j)


class Board(object):
    def __init__(self):
        self.cells = {} # { (x, y): Cell() }

    def advance(self):
        for (x, y), cell in self.cells.items():
            alive_neighbors = len(cell.neighbors)
            cell.next = (alive_neighbors == 3 or (alive_neighbors == 2 and cell.alive))


На этом месте надо сказать «стоп»: у нас есть класс Поле, в котором 2 метода: __init__ и «сделать ход». В нём одно свойство — словарь, значит со словарём и надо работать. Заметьте, что не надо хранить соседей точки, они уже и так есть в словаре. Живая точка или нет — это просто булево значение, поэтому будем хранить координаты только живых клеток. А раз в словаре хранятся только True, нужен не словарь а просто множество (set) координат. Наконец, новое состояние не нужно, можно просто заново создать список живых клеток.

def neigbors(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 = board | 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(1000):
    glider = advance(glider)
print glider


Планер движется Получается очень простая, сжатая реализация игры. Двух классов тут не надо. Внизу — координаты планера, их вставляют в поле, и планер летит. Всё. Это полная реализация игры «жизнь».

Резюме



1. Если вы видите класс с двумя методами, включая __init__, это не класс.
2. Не создавайте новых исключений, если они не нужны (а они не нужны).
3. Упрощайте жёстче.

От переводчика: в комментариях я вижу, что многие восприняли доклад как полное отрицание ООП. Это ошибка. Пункт 1 из итогов чётко говорит, что такое не класс. Классы нужны, но суть доклада — в том, что не нужно ими злоупотреблять.
@siberiano
карма
92,7
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (144)

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

    Спасибо за перевод.
    • +3
      Вам спасибо. Старался. :) После просмотра доклада, сильно упростил собственную библиотеку.
      • 0
        Скорее бы в Java замыкания добавили… :-)
        А вообще я с Вами полностью согласен: множество классов сильно усложняют чтение.
    • НЛО прилетело и опубликовало эту надпись здесь
  • –1
    мне кажется снижение кол-ва классов не есть настоящая оптимизация.
    • +12
      В статье приводится контр пример: как куча классов и 660 строк кода сжимаются в 3 строки, и оно работает так же. Вполне себе оптимизация. Работа с таким инструментом проще.
      • +3
        По-моему тут скорей «не создавайте новые классы вместо примитивов» но не буду спорить — возможно в данном случае и проще. Но из причин по которым мне когда либо хотелось рефакторить код маленькие классы не несущие особой логики — в самом низу — особо они не мешают, а в случае чего их можно дорастить.
        • +5
          ну и часто вы наращивали их?
          • +2
            не часто, но бывает раз в год. Помех я от них я впринципе не вижу — места в оперативной системе для файлов мне не жалко, а нарастить естественней чем создавать новые.

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

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

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

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

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

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

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

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

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

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

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

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


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

              greet('A', 'B')

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

      1. убираем класс
      2. смотрим, что в словаре True и False, и нам нужны только True, следовательно убираем False
      3. понимаем, что словарь только из True бессмысленен, заменяем на set.
    • 0
      Неужто в школе не так писали? На каком-нибудь турбо паскале.
  • НЛО прилетело и опубликовало эту надпись здесь
    • +4
      А теперь убираем код отвечаюзий за запуск глайдера — остается 14 строк.
    • +3
      В классах нет управляющего кода, то есть ещё надо добавить строк.

      А 2 функции второго варианта, которые повторяют функционал классов, занимают всего 14 строк.
  • +1
    А это такая фишка, что финальный код 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
    
    • 0
      опечатка была. Как вы делаете подсветку синтаксиса?
      • +1
        <source lang=python>
            print u"Как-то так"
        </source>
      • +1
        Очень рекомендую сделать. Кстати, по поводу опечатки — self тоже стоит удалить
        • 0
          Попробую, спасибо!
    • 0
      point
      раскрывать не обязательно, можно по индексам достать.
  • –12
    Сколько самоуверенного апломба и неопытности…

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      По-моему, вам при таком подходе совсем не яйца мешали… не помогла бы и крутая ОО модель.
  • +1
    Просто не надо строить из себя Вангу, при написании кода. Не надо смотреть на 10 лет вперед, смотрите на шаг.
    И в итоге, ваше ПО будет разрабатываться быстро (а это деньги) и его будет легче поддерживать чем всемогущего монстра.
    Сколько раз вы тратили час на то, чтобы разобраться как аккуратно интегрироваться своим модулем в чужой проект, затем еще час это все отладить, вместо того чтобы за 15 минут написать этот функционал и за столько же его проверить?
    Будьте проще, и форки к вам подтянуться.
  • 0
    В конце видео прозвучало несколько вопросов от зрителей, думаю есть смысл добавить и их в перевод.

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