Pull to refresh

Comments 60

Уберите текст под хабракат плиз.
Напишите об этом автору в личку, зачем это всем читать?
Но ведь это же хабро-традиция.
Какой интересный акцент — «запрографировать», «фотограммы» =)
-графия — деятельность (полиграфия, порнография, электрокардиография, прография)
-грамма — результат (полиграмма, порнограмма, электрокардиограмма, программа)
-граф — инструмент (полиграф, порнограф, электрокардиограф, програф)
-графист — деятель (полиграфист, порнографист, электрокардиографист, прографист)
Угу.

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

Вы смешиваете слова из разных парадигм (и с разным происхождением).
Фотография — деятельность, фотограмма (также: фотокарточка; неправ.: фотография) — результат; фотогр́аф (также: фотоаппарат) — инструмент; фотография (неправ.: фот́ограф) — деятель.

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

Язык — это устоявшаяся система, которая не любит насилия. Чтобы навести в нем порядок, надо начать с самого начала, устраняя «мешанину», которая возникает благодаря заимствованиям и естественной эволюции языка. По сути — надо создать искусственный язык.

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

Вы можете «делать, что хотите», но только учитывайте эффект, который это производит. В частности то, что вас будут неправильно понимать.

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

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

Почему для добавления голоса мы приводим объект к одному интерфейсу, а для получения списка голосов — к другому?

Ведь очевидно, что и «добавить голос» и «получить список голосов» — это методы одного и того же семантического уровня.

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

И добавление голоса (addVote), и получение голосов (getVotes) — это методы Rating_Object, к которому мы получаем доступ через один и тот же интерфейс Rating_Ratable. Получение итоговой оценки на основании списка голосов — это уже задача не Rating_Object, а Rating_Strategy.

Мы можем, например, в какой-то момент заменить Rating_Strategy_Binary_Arithmetic на Rating_Strategy_Binary_Rational, не затрагивая совершенно Rating_Object.

Метод addVote и вообще Rating_Object ничего не знает о стратегии; это не даёт нам возможность контролировать попадание добавляемой оценки в выбранную шкалу. Грубо говоря, Rating_Object занимается только сбором и хранением голосов.

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

На основании стратегии же мы выводим для пользователя текущий рейтинг.
«это методы Rating_Object, к которому мы получаем доступ через один и тот же интерфейс Rating_Ratable. Получение итоговой оценки на основании списка голосов — это уже задача не Rating_Object, а Rating_Strategy.»
Это излишнее усложнение. Когда у меня есть объект рейтинга, я хочу иметь возможность узнать его рейтинг, не задумываясь о том, как он его считает. Иными словами, стратегия должна быть от меня спрятана.

«Метод addVote и вообще Rating_Object ничего не знает о стратегии; это не даёт нам возможность контролировать попадание добавляемой оценки в выбранную шкалу. Грубо говоря, Rating_Object занимается только сбором и хранением голосов.»
Вот это и есть ошибка дизайна. Зачем регистрировать некорректные голоса?

«На основании стратегии мы выводим для пользователя набор кнопок (форму), валидируем полученные данные — и затем вызывает методы объекта, чтобы этот голос записать.»
Таким образом вы легко можете получить несогласованные данные, выведя для отрисовки одну форму, а для отображения результата (спустя неделю) — другую.
(1) Делаем публичным только интерфейс RatingObject, с методами добавить/получить список/получить статистику/получить рейтинг. Все внешние взаимодействия ведутся через него.

(2) Привязываем конкретный RatingObject к конкретной стратегии рейтингования. В этом случае описанные выше методы RatingObject начинают работать по приблизительно одному шаблону: получить чистые данные (от пользователя или из базы) — валидировать/обработать стратегией — передать дальше (в базу или пользователю).

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

(3) Все вышенаписанно исходит из того, что стратегия рейтингования атомарна, то есть нет смысла делить ее на стратегию сбора (бинарные голоса/голоса по шкале, например) и стратегию показа (усреднение шкалы одним типом/усреднение шкалы другим типом). Если было бы нужно, то надо стратегию, хранимую в объекте, считать стратегией сбора, а для отображения использовать _совместимую_ (это важно) стратегию показа (например, выбирая ее из ratingStrategy->getDisplayStrategies).
Каким образом мы привяжем Rating_Object к Rating_Strategy?
Зависит от деталей реализации и отображения на хранилище.

Если мы говорим о некоей абстрактной объектной системе, то я бы делал RatingObject where T: RatingStrategy.

Если бы я отображал это на базу, то у меня в базе в таблице RatingObject лежало бы поле RatingStrategyId, по которому бы доставалась конкретная стратегия для этого объекта. Причем это поле, очевидно, было бы immutable.

(собственно, первый пункт даже не противоречит второму)
Каждый экземпляр Rating_Object соответствует строке в базе данных; а Rating_Strategy не хранятся в базе данных вовсе. Стратегии — классы, а не экземпляры классов, их не реализуешь как active record. Что будет записано в RatingStrategyId?
Идентификатор, по которому мы определим, какой именно класс надо подгрузить.
И как вы предлагаете это реализовать? Хранить имя класса? Определять к каждом классе числовую константу? Вести где-то пронумерованный реестр классов? Вы правда думаете, что это будет лучше?
А это уже будет зависеть от конкретной реализации маппера, СУБД и фабрики стратегий. (По большому счету, мы здесь имеем дело с частным и очень специфическим случаем ORM-сценария, предполагающего хранение в одной таблице различных классов, и этот сценарий решается.)

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

Фаулер пишет, что чем сложнее объектная модель, тем более вероятно, что ее придется реализовывать автономно от базы, а потом делать маппер.
Та статья забавная, она заставила меня вспомнить об этой моей разработке. Если хотите, здесь я предлагаю в противовес пример разумного, хотя и неочевидного, усложнения. Мне не хотелось бы, чтобы после прочтения статьи о факториале люди бросались в крайности и начинали считать, что чем проще и прямолинейнее, тем лучше, а где появляется больше одного (двух, трёх) классов — всё плохо и нет пути.
Помоему без бутылки и без указания фреймворка, для которого написан этот код, здесь не разобраться. Что находится в $this->rating_object? Где объявлены addVote и getVotes? Сколько запросов будет к базе данных, если у нас список из 20 объектов и для каждого мы вызовем $article->asRatingObject()->getVotes()?
В $this->rating_object находится объект класса Rating_Object, связанный по rating_object_id с данной записью. Методы addVote и getVotes определены в Rating_Object («А в классе Rating_Object предусмотреть методы для оперирования связанными Rating_Vote»). Статья предназначена для людей, знакомых с active record и способных по вышеприведённой строке немедленно представить в голове код этих двух методов.

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

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

Это такое классическое «программист решил, что в будущем может понадобиться». МакКоннел хорошо пишет об этом: «программисты обычно плохо предсказывают будущее». Вкупе с тем, что это усложняет разработку сейчас (и усложняет понимание кода и его структуру), это очевидный bad bet.

(ну либо _сначала_ пишите весь набор требований, а потом будем думать, как это реализовать)

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

А пост вы пишете о некоем обобщенном случае, и на основании этого обобщения читатель начинает думать, что _все_ голосования надо делать так (даже те, в которых в ближайшие десять лет не будет двух стратегий оценки).
Действительно, все оценивания можно (нужно ли, пусть читатель сам решит) делать так, и это не создат проблем. Точно так же, как можно делать сайт при помощи фреймворка, используя только два десятка из сотни включённых в него классов.
«это не создат проблем»
Вот это — ошибочно.

Ваш код сложнее понять, чем простое голосование без объектной модели (точнее, с вырожденной объектной моделью) и без стратегий. Его сложнее разработать, его сложнее поддерживать.

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

Например, если мне нужен логгер, я не хочу инициализировать десяток других компонентов — DI, exception handling strategy, авторизацию, DAL и так далее. Логгер должен запускаться (в самом простом сценарии) двумя строчками в коде, где первая — его инициализация, а вторая — запись. Все остальное должно быть от меня спрятано.
Не следует использовать что-то, не поняв его сперва.
Не стоит писать вещи, которые требуют чрезмерных усилий для понимания. Код, который легко поддерживать, должен быть в первую очередь легко читаем и прозрачен.
Всякий код сложен в той или иной степени, требует той или иной подготовки для чтения и понимания.
Именно поэтому я и написал «чрезмерных» усилий.
Как замутно, а чем плохо иметь свойство raiting у объекта. И иметь где-нибудь функцию
countRaiting(obj):int?
Что будет делать эта функция?

Вы говорите о случае, когда у Article есть rating: integer и метода increaseRating, decreaseRating, getRating?
Чрезмерно сложное «идеальное» решение. Я считаю, что код по большей части должен писаться под конкретные задачи, а у вас тут больше половины «на всякий случай».
Давайте рассмотрим конкретную, реальную практическую задачу.

У нас есть сайт наподобие bash.org.ru — публикуются цитаты, и посетители голосуют: хорошая или плохая. Регистрации пользователей нет.

В таблице есть поле rating: integer, при голосовании оно увеличивается или уменьшается на единицу.

В один прекрасный день мы наконец осознаём всю порочность такого способа подсчёта рейтинга и хотим сделать рейтинг как pos / (pos + neg).
а если в итоге, осознав порочность и нового метода, мы решим делать рейтинг на основе более сложной формулы? Например, когда каждый голос с увеличением его возраста на день увеличивает вес на 1%? Я думаю и эту возможность нужно заложить изначально.

Не хочу повторяться, но не усложняйте ваш код. Пишите ровно столько кода, сколько необходимо сейчас, а не через год, два или пять. И пребудет с вами сила.
Вовсе не обязательно эту возможность закладывать изначально. Мы вполне можем в тот момент, когда решим её реализовать:
1) добавить поле timestamp к Rating_Vote и заполнить его для существующих голосов текущим моментом (а почему нет?);
2) в методе addVote добавить заполнение поля timestamp;
3) написать стратегию, учитывающую timestamp при подсчёте.

Пишите ровно столько кода, сколько позволит вам сделать что угодно и сейчас, и через год, и через пять. Повторю: не предусматривайте все функциональные возможности, но предусматривайте возможность добавления любых новых возможностей.
Да хватит уже отвечать короткими набросками реализаций на конструктивные замечания и советы. То что у подхода существует реализация не доказывает что подход удобен, верен и оптимален.
Веб-разработка — это динамичный процесс с высокими требованиями к скорости внедрения. Надо выпускать работающий продукт с разумным «запасом прочности» в кротчайшие сроки, а не продумывать все возможные «заначки на чёрный день», так как это грозит прогрессирующим нарастанием бесполезного кода, а при плохом раскладе и полным крахом проекта.
За 5 лет слишком многое меняется, чтобы прогнозировать функционал «про запас»… Если бы все разработчики работали по таким принципам — мы бы все до сих пор сидели в громоздкой консоли с миллиардом команд, но огромным запасом потенциально необходимых функций.
Полтора года назад уже популярно высказался по поводу этой схемы. Сейчас же добавлю только то, что нужно быть проще и не предусматривать заранее все возможные варианты использования…
После прочтения топика KISS вместе с YAGNI покончили с собой.
а велосипедные фабрики решили выпускать тостеры.
Вот поэтоу мне стыдно признаваться на собеседовании, что знаю ООП и паттерны, сразу приходиться оправдываться что я не совсем болен оопизмом головного мозга.

ужасное желание хранить каждый голос как запись в отдельной таблице, когда можно обойтись парой полей непосредственно айтема. а ещё получается, что перед апдейтом вы делаете ненужный толстый селект и можете внести ошибку при одновременных голосованиях, не говоря уже о дополнительной нагрузке.
А как вы предлагаете проконтролировать, чтобы один субъект мог голосовать за один объект только один раз? (Пускай даже менять в будущем голос можно, но нельзя два раза поставить плюсик статье.)
в том то и дело, что нигде не увидел чтобы вы решали данную проблему.
Rating_Vote: object_id, subject_id. На уровне таблицы БД по этой паре — unique key. Метод addVote, естественно, проверяет, есть ли уже такой голос, или же обходится средствами SQL вроде INSERT IGNORE или REPLACE.
Просим-просим! Не в смысле «а слабо?», а в практическом — я как раз разрабатываю свой велосипед в этом направлении, было бы полезно познакомиться с практическим опытом людей, уже преуспевших на этой стезе
Да, конечно, чем смогу помогу. Если есть сформировавшиеся вопросы — спрашивайте
Мм, ну для начала хотелось бы увидеть ваш, более простой вариант построения задачи, по сравнению с топикстартером — что лишнее, на что обратить внимание, подводные камни тсзать…
Как вы решили проблему вхождение новичка? Вес голоса человека накопившего себе карму равен голосу только что зарегистрировавшегося?
Я не занимался реализацией взвешенных голосов, но ничто не мешает это сделать. Как именно — вопрос задачи, а не реализации.
Sign up to leave a comment.

Articles