Pull to refresh

Comments 75

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

Один напишет абстрактную фабрику по созданию абстрактных фабрик для создания абстрактных молотков.
А другой напишет удобную расширяемую систему, с которой приятно работать.
Одно дело — не применять инструмент потому, что знаешь, что всегда можешь применить его, или другие аналогичные, позже, и другое — не применять потму, что не знаешь о существовании таких инструментов. Хотя на первый взгляд оба случая похожи.
UFO just landed and posted this here

Вообще, эта позиция плохо сочетается с A Pattern Language Александера, который и заложил формат "проблема — контекст — решение". Даже если говорить о распознавании, то оно здесь применимо к "проблемной" части, а не к решению. Но на самом же деле, pattern у Александера — это не что-то, по чему распознают. Это образец, по которому решают проблему.


Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice

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

UFO just landed and posted this here
В большей степени соглашусь, хотя в своей книге «Применение шаблонов проектирования: дополнительные штрихи» Влиссидес критикует определение Александера, называя его первым в числе основных заблуждений о шаблонах

Можно сколько угодно критиковать определение Александера, но отрицать тот факт, что GoF построен по тому же принципу, достаточно сложно.


Т.е. применимость шаблонов для распознавания заложена в них изначально, по их определению и способу появления на свет.

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


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

Влечет. Можно увидеть проблему, которой не было.

UFO just landed and posted this here
Он не зависит от языка.

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

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

В мире ООП одни подходы, описанные в статье, в функциональном мире — другие, а в js как получится =)
Вот к примеру в js любой callback — это реализация паттерна стратегия, хоть и выглядит совсем не так, но суть у них одна.
А система обработки событий — это ничто иное как chain of responsibility.
UFO just landed and posted this here
Вот именно, это соглашение как писать код и в каждом языке своя идеология написания кода. То, что на ООП делается через классы, в ФП делается через коллбеки, замыкания и керринг. А в js через классы, коллбеки, замыкание и керринг.

Перенося паттерны из одной идеологии в другую они видоизменяются и часто даже называются иначе.
UFO just landed and posted this here

Да не, дело скорее не в терминах.


Есть определение паттерна Фабрика (в чем суть проблемы, и что делать), и есть его реализация. Так вот, реализация очень сильно языковозависима. Вплоть до полного ее отсутствия за ненужностью, потому что в конкретном языке именно эта проблема не актуальна/тривиальна.

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

Погуглите скажем несколько заметок Mario Fusco, о том, во что вырождаются ряд паттернов GoF после появления в Java 8 лямбд.

UFO just landed and posted this here
Публикации на тему вырождения дизайн-паттернов в нечто тривиальное в функциональных ЯП были сделаны Петером Норвигом ещё в 1996, всего лишь через два года после книги GoF.

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


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

Вот к примеру в js любой callback — это реализация паттерна стратегия
С объектно-ориентированной точки зрения, всё функциональное программирование — это применение паттерна «стратегия». А асинхронная обработка с continuation-passing всегда будет напоминать chain of responsibility.
Да, оно то похоже, но все же есть пара замечаний.
Паттерны «стратегия», если простыми словами, — это когда аргумент функции (метода) принимает на вход «кусок кода» (функцию или имплементацию стратегии в ООП), который будет выполняться в работе данной функции.

Т.е. когда есть конкретная функция, которая вызывает другие конкретные функции — это не будет стратегией.
Функция, которая вызывает другую функцию для получения третьей (к примеру керринг) — это не будет стратегией.
А вот если в функцию передается другая функция — это уже будет стратегией.

> асинхронная обработка с continuation-passing
Это больше похоже на стратегию. Вызывается асинхронный метод, и в него передается callback функция, в которую будет выполнен возврат после выполнения асинхронного кода.

Chain of responsibility — это механизм, лежащий в основе pub/sub.
Dispatcher (на него подписываются и через него кидают event) внутри содержит хеш-таблицу сообщение -> список слушателей. Когда кто-то публикует сообщение, диспатчер берет список слушателей и поочередно выполняет их.

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

Ну я бы не называл это костылями, по одной простой причине — это все-таки изначально некие над-языковые конструкции, которые описывают, как решать типовую проблему, имея в наличии некоторый набор инструментов. Ну т.е., грубо говоря, есть список чисел, нужно получить "сумму". На выходе для одного языка цикл, а для другого скажем какой-то вариант fold/reduce. И кто из них костыли?


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


Иными словами — "что-то совсем очевидное" не перестает быть решением типовой проблемы. Оно просто становится простым решением.

Скажем так: это некие внеязыковые конструкции, которые описывают, как решать типовую проблему.

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

Т.е. паттерн в широком смысле — полезная внеязыковая конструкция. Паттерн в узком смысле — полезная внеязыковая конструкция, не совсем тривиальная в реализации.
Шел 2016 год… Многие до сих пор не знают чем отличается массив от списка, абстрактный класс от интерфейса и как устроена хеш-таблица…
я бы сказал, что есть обратная тенденция: современные задачи зачастую требуют не глубоких (или хотя-бы поверхностных) знаний, а возможности накодить здесь и сейчас.
> абстрактный класс от интерфейса

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

Второй важный момент: интерфейс работает как концепт. Фактически, он требует, чтобы класс имел определённый набор методов и свойств, при этом не имеет значения, где они реализованы: в текущем классе или базовых, ничего не знающих об интерфейсе. Такого эффекта невозможно достичь в языках, допускающих множественное наследование.

Всё остальное — действительно техническая ерунда. Важно только понимать, что интерфейсы дешёвые, они не замусоривают vtable и накладные расходы при их использовании ниже, чем при использовании полноценных классов. Особенно актуально это для C++, где виртуальное множественное наследование, используемое для эмуляции интерфейсов, приводит к раздуванию размеров объектов.
Простите, за, возможно, глупый вопрос, но как с технической стороны сделаны «быстрые» интерфейсы с полиморфизмом (времени выполнения) без vtable или более дорогих средств?
Звучит очень круто, хотелось бы узнать. В голову ничего не лезет, а запросы в гугл ничего не дали.
Нет, vtable, естественно, используются, без них никуда. Разница же такая:

1. Разная стратегия использования при наследовании. Если вы хотите иметь возможность перезаписать метод в классе-потомке, то делаете его виртуальным и оставляете открытым. При этом любой вызов открытых виртуальных методов будет осуществляться через vtable.

Методы интерфейса же являются автоматически virtual и sealed. Если хотите перезаписать метод интерфейса, то просто наследуете его второй раз, при этом старые методы будут неявно объявлены во второй раз. Это приводит к тому, что при вызове метода компилятор генерит оптимальный код, а не лезет в vtable.

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

2. Огромная разница в потреблении памяти. В C++ создаётся по отдельному полю vtable для каждого из множественно наследуемых классов, дополнительная память тратится на разруливание виртуального наследования. В C# информация о множественном наследовании сидит в единственном vtable на каждый объект в виде ссылок на vtable интерфейсов.

Обычно такая двойная адресация не вызывает проблем, т.к. выполняется только один раз. Единственное исключение: generic-методы с interface constraint, где двойная адресация происходит при каждом вызове интерфейсного метода.

В C++ же такое сделать попросту невозможно, т.к. объекты при множественном наследовании не могут располагаться по одному адресу. Из-за этого для каждого из базовых классов и приходится заводить по vtable.

P.S. Подумал — а ведь если это развить, то на отдельную публикацию пойдёт, надо бы заняться. Реально куча интересных нюансов вылезает.
Разница в причине использования.
Интерфейс — это в первую очередь соглашение о том, в какой роли может быть использован объект. Это контракт, по которому работают с объектом, реализовавшим его. И нет никакой связи с реализацией. Иногда даже это просто маркер (интерфейс без методов).
К предыдущим двум ответам, стоит лишь добавить, что Абстрактный класс определяет иерархию («это есть», «является»), в то время, как Интерфейс лишь говорит о возможности («может»).

Самолет — это механизм.
Птица — это живое существо.
Самолет и Птица может летать (летающий объект).
В небе можно увидеть Летающие объекты.
Не знают. А что удивительного? Где-то рождаются дети. Кто-то осваивает новую специальность или специализацию. Новые люди, Карл!
Между тем вся страна электрифицирована, а коммунизм не наступил, хотя должен был, кажется.
Даже если принять как догму, что «все в отрасли должны это знать» (кому, кстати, должны-то?) — в отрасли всегда полно и новичков, и кустарей «на пару строк».
А когда все программисты до одного будут это знать — это будет признаком застоя и печали.
А ведь не только новички не знают. Вот к новичкам претензий нету. Многие, у кого за плечами уже по 2-4 года не знают ответов на указанные вопросы.

А на счет застоя, я не соглашусь. Когда все будут знать ответы на базовые вопросы, тогда произойдет качественный скачек развития всего ИТ мира.
Вот только чтобы знать ответы на все базовые вопросы, нужно очень много времени потратить. «Программирование — это не работа, это образ жизни».
(Я знаю как устроена хеш-таблица)
Сколько раз вам за карьеру программиста пригодилось знание о том как устроенна хеш-таблица? То что редко используется — забывается, это естественный процесс. В нужный момент прочитать/вспомнить — не составит проблем.
Вот есть UI поток, а есть другие Background потоки. И вы такой, на зная сложность вставки в хэштаблицу, решили добавить 1 запись туда. А она взяла и пересоздалась и начала пересчитывать хэши для корзин, так как места не хватало уже и UI подвис.
Или привыкли вы за O(1) забирать из ArrayList в UI, а сейчас решили из середины LinkedList достать и снова UI подвис.
Или удалить из середины ArrayList запись, или, что еще хуже, прямо в UI перебрать все элементы в нем и удалить по фильтру что-то.
Продолжать примеры?
Хорошего разработчика от посредственного эти вещи и отличают — он знает, что использовать и как это использовать.
Я бы сказал, что это особенно критично для сетевых игр, где огромное значение имеет не время в среднем, а наихудший сценарий.

Массив от списка — ладно, вставка/удаление О(n) против О(1), индексация O(1) против O(n). Хотя неплохо бы ещё знать про константные составляющие, которые на современных процессорах в некоторых случаях делают выгодным использование массива вместо списка.


Устройство хеш-таблицы? Хм. Вообще-то их много разных. Достаточно знать, что при большом количестве коллизий производительность падает.


А общее отличие абстрактного класса от интерфейса зачем? Слишком зависит от языка. В С++ абстрактные классы и интерфейсы различаются только наличием non-pure methods. В C# интерфейсы — это отдельная языковая сущность. В Rust вообще абстрактных классов нет.

UFO just landed and posted this here
Именно так. Код первичен, паттерн вторичен, а не наоборот.

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

Фабрики фабрик вообще идут параллельно и растут корнями из TDD.
По сути все паттерны сводятся так или иначе к идее введения дополнительного уровня абстракции. Различаются только способы взаимодействия между уровнями. По идее эти способы могут быть очень гибкими и не ограничиваться только общепринятыми шаблонами. Почему и зачем насаждаются конкретные реализации, под которые люди начинают подгонять свой код (вместо того, чтобы на основе базовой идеи создать наиболее подходящее в конкретном случае взаимодействие) — мне лично не очень понятно.
На самом деле никто не насаждает писать только через описанные паттерны. Более того, никто даже не обязывает реализовывать паттерны 1 в 1 как в примерах. Пример написан лишь для понимания как это может выглядеть.
У всего хорошего всего есть побочный эффект, если этим злоупотреблять. Витамины в большом количестве тоже причиняют вред. Понимание когда нужно использовать конкретный паттерн — это показатель зрелости специалиста.
UFO just landed and posted this here
Серьёзно? Базовая статья про паттерны в 2016-м… через 20 лет после книги «Банды четырёх»?
Что дальше? Туториал по использованию колеса? Или добротная обзорная статья о всех плюсах и минусах использования огня?
Серьёзно? Вы думаете, в отрасли застой и нет новых людей, том числе без нормального образования? Может, и школы с банальным 1+1 стоит закрыть? Мы-то всё уже выучили!
Хорошо, принимается. Честно говоря, я сразу не заметил, что код в статье на PHP.
Словно в других языках полнейший застой, и новых разработчиков не прибывает.
Ключевой момент не в том, что в PHP нет застоя, а в том, что в нём приток новых людей обеспечивается в основном за счёт людей без нормального образования (которые даже про паттерны не в курсе). В других языках такой проблемы либо нет, либо она стоит не настолько остро. Я, например, не представляю себе человека, который окончил бы университет по специальности Computer Science, а затем вдруг взял и выбрал PHP в качестве точки приложения полученных знаний и навыков. Это был бы нонсенс. Образование помешало бы ему совершить подобный выбор. Но такой выбор совершенно спокойно делают люди без образования. Он не кажется им ошибочным. В общем, PHP — это Basic наших дней.
Наверное для вас будет новостью, но фейсбук и вконтакте написаны на PHP.
Мыслите стереотипно. Я провел множество собеседований по Java, почти все люди с высшим образованием в сфере it. Но и имея 5-летний опыт работы люди могли не знать, как работает Хэш Таблица, не знать, что такое шаблон Адаптер и т.д.
Про фейсбук и вконтакт я в курсе. Они активно используют дешёвую рабочую силу. Можно сказать, это главная составляющая их успеха. По крайней мере, была на начальном этапе. Сейчас-то они уже могут себе позволить и более квалифицированные кадры, но генотип так просто не изменишь. Приходится работать с тем, что есть. Это суровая реальность. Эффектно взлететь и быстро раскрутиться, сварганив прототип в собственном гараже, можно только на дешёвом ресурсе. И, к сожалению, эта основа останется в проекте навсегда. Если ты построил в гараже моторизированный велосипед, то в дальнейшем можно будет только увеличивать его размер, наращивать мощность двигателя и прикручивать к нему всякие штуки типа полифонического гудка с поддержкой mp3. Развить этот велосипед в атомную подводную лодку никак не получится. У такой лодки совершенно иной цикл развития. И начинается он не в гараже.

И, кстати, мыслить стереотипно не так уж и плохо. Стереотипы — это шаблоны мышления. С их помощью очень легко делать выводы, не погружаясь глубоко в детали. Иногда выводы получаются неверными, но не в данном случае. Если человек не знает, как работает хэш-таблица, то про него никак нельзя сказать, что у него есть профильное образование. Возможно, у него есть какой-то документ, который позволяет ему вводить окружающих в заблуждение, но не более того. О действительном наличии того, что называется словом «образование», речь в данном случае не идёт.
Пусть и не поддерживаю насмешку над php, но хочу немного поддержать. Действительно и вк и фейсбук в дальнейшем были вынуждены писать свои трансляторы и движки для php (если не ошибаюсь, это про компиляцию в с/с++), чтобы не выкидывать тонну существующего кода. Однако перетестировать его все равно пришлось по новой (разве что повторное юнит тестирование не требовалось).
На самом деле, никакой насмешки у меня тоже не было. Только трезвый и хладнокровный анализ ситуации. Не понимаю, как здравомыслящий человек может отрицать то, что PHP — это современный Basic, а Java — современный Visual Basic. Самое смешное в том, что когда я выбирал движок для блога, я выбрал WordPress, а не Octopress, который написан на Ruby. При том, что Ruby как язык намного круче PHP. Но ключевую роль при выборе сыграло то, что комьюнити вокруг WordPress намного больше.
Кстати, интересный вопрос, почему так получилось. Было ли это предопределено с самого начала или просто случайность?

Ответ на этот вопрос содержится в цитате самого Расмуса Лердорфа: «Я ненастоящий программист. Я компоную всякие штуки, пока это не начинает работать. Потом иду дальше. Настоящий программист скажет: „Ок, это работает, но тут утечка памяти, надо пофиксить“. А я просто перезапускаю Apache каждые 10 запросов»
Аналогии странные. Как трансляция языка связана с уровнем языка — не понимаю. Еще и на оскорбления переходить «не понимаю, как здравомыслящий человек...» — потому не удивлен вашему рейтингу.

Есть C# to Native от unity3d — теперь язык плохой? Есть даже JS to Native. Есть множество JVM для Java, в том числе и AoT под Android. Так чего удивляться существованию Php to Navite?
Ну, если у молодых горячих разработчиков это считается оскорблением, то я тогда тоже не удивлён.

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

Я и не говорю, что php плох. Я ничего про уровень языка не писал. Все языки хороши по своему. Но у каждого свой скоуп применения.
Разве кто-то сменил технологию? Транслятор сменили и всё. Как писали на php, так и пишут.
Да и Windows множество участков кода имеет на c# — нет проблем в таком подходе.
Разве кто-то сменил технологию? Транслятор сменили и всё. Как писали на php, так и пишут.

Ну это смотря что под технологией понимать. К тому же насколько помню сам язык тоже притерпел некоторые модификации.

Разве кто-то сменил технологию? Транслятор сменили и всё. Как писали на php, так и пишут.
Да и Windows множество участков кода имеет на c# — нет проблем в таком подходе.

Я выделил жирным проблему. Windows начал использовать c# не вместо, а вместе с существующим кодом. И к тому моменту c# уже протестирован. Ну совсем разные вещи. Попробуйте взглянуть на ситуацию с точки зрения бизнеса.
В паттерне «фабричный метод» в примере я бы пихнул стат. метод createButton в абстрактный класс, а класс ButtonFactory грохнул бы.
Зачем плодить полупустые сущности, раз там все равно тупой статический метод?
Этот подход даже имеет свое название: Static Factory Method.
И это очень плохо, так как базовый класс знает, как будут создаваться наследники. И что еще хуже, он будет знать, куда этот объект будет сохраняться или откуда будут браться данные. Например, есть у вас статичный метод, принимающий код ошибки, а возвращающий строку с её описанием для UI. И лезет он за этими данными в BD/Network/XML/Etc. — вот тут жесткая связь данных и их способа хранения, никогда так не делайте :)
> И это очень плохо, так как базовый класс знает, как будут создаваться наследники.
Согласен с этим.
Но как это знание тут может помешать на практике? Т.е. грубо говоря, что в случае с отдельным классом, что в случае с тем же классом — при добавлении наследников в иерархию, статический метод должен дорабатываться.

> Например, есть у вас статичный метод, принимающий код ошибки, а возвращающий строку с её описанием для UI
А при чем тут фабрика вообще, если возвращается строка?
Я в синтаксисе php не силен и мои рассуждения строятся на том, что я реализую некий интерфейс, чего со статик методами не сделать. Какая будет реализация — уже абстракция.
Т.е. я могу прокинуть любую фабрику для инстанциирования объекта.

Возвращается объект типа String — каким он будет, определяет фабрика.
Если на выходе будет не String, а Product, как в примерах из википедии — суть не изменится, это фабрика.
Порой такой компромиссный подход может сработать.
Но, по мере усложнения приложения, как правило не долго.
Разделение ответственности: Инстанцирование кнопки — одна ответственность. Реализация функциональности кнопки — другая.
Т.о. отделив создание объекта от него самого, через ввод фабрики, вы сможете
а). Вводить в приложение различные фабрики, например на этапе исполнения, либо через конфигурацию
б). Замочить сам процесс создания кнопки, что позволит оттестировать код с разных сторон.

У static factory method немного другой смысл. Он предназначен, чтобы создавать закрытые библиотеки / фреймворки, который можно замокать, но нельзя расширить.
Это облегчает жизнь, когда нужно поддерживать обратную совместимость. Появляется возможность безболезненно менять строение внутренних классов, главное, чтобы они соответствовали интерфейсу. Можно добавлять, удалять, изменять имплементации (они внутренние и никто не может на них завязаться).

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

В зависимости от количества элементов в Enum, этот класс может выдать разные реализации, некоторые будут построены на битовой маске int, некоторые на битовой маске long, если 32 или 62 бита не хватит — можно использовать массив. Нужно будет создать множество из одного элемента — пожалуйста. Если тесты производительности покажут, что Конкретная имплементация для 2 элементов будет работать лучше, чем имплементация с битовой маской — можно добавить ее и просто обновив версию пользователь получит прирост производительности без каких либо изменений своего кода.
угу, только для простых случаев
Если убрать номера строк примерах, есть шанс, что будет работать подсветка синтаксиса (не уверен, как она работает на хабре).

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

В статье у паттерна «Адаптер» пример реализации паттерна «Фасад».

Шаблон фасад (англ. Facade) — структурный шаблон проектирования, позволяющий скрыть сложность системы путём сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы.

и к тому же вообще не раскрыта суть Адаптера, который приводит интерфейсы несовместимых типов к единому интерфейсу)

Но, по факту, это не вина автора хаба, так как это только перевод.
Only those users with full accounts are able to leave comments. Log in, please.