Адекватный разработчик узнает ограничения, в которых было написано это решение. Здесь transform решает проблему вызова долгой функции два раза. Если у адекватного разработчика будет более симпатичный вариант решения, то он его предложит, а не будет заворачивать ревью.
Почему бы не отрефакторить и сделать «Без», но линейно и прозрачно?
reductor = dispatch_table.get(cls)
if reductor:
return reductor(x)
reductor = getattr(x, "__reduce_ex__", None)
if reductor:
return reductor(4)
reductor = getattr(x, "__reduce__", None)
if reductor:
return reductor()
raise Error("un(shallow)copyable object of type %s" % cls)
Сразу видно повторение структуры
reductor = some_get_reductor_strategy()
if reductor:
return reductor(something_or_nothing)
Которую частично можно заменить nullable object'ом
def dummy_reductor(*args): pass
result = dispatch_table.get(cls, dummy_reductor)(x)
if result:
return result
result = getattr(x, "__reduce_ex__", dummy_reductor)(4)
if result:
return result
result = getattr(x, "__reduce__", dummy_reductor)()
if result:
return result
raise Error("un(shallow)copyable object of type %s" % cls)
И избавиться от повторяющихся if'ов
def dummy_reductor(*args): pass
result = (
dispatch_table.get(cls, dummy_reductor)(x) or
getattr(x, '__reduce_ex__', dummy_reductor)(4) or
getattr(x, '__reduce__', dummy_reductor)()
)
assert result, 'un(shallow)copyable object of type %s' % cls
return result
Уверен, что код можно еще упростить, если знать контекст.
В list comprehension новая фича выглядит полезной, пока не приходит осознание, что при реальной необходимости её можно реализовать самостоятельно один раз на весь проект:
def transform(iterable, with_transform, and_filter):
result = []
for item in iterable:
item = with_transform(item)
if and_filter(item):
result.append(item)
return result
result = transform(
data,
with_transform=long_running_func,
and_filter=lambda x: x is not None
)
result = transform(data, long_running_func, lambda x: x is not None)
Представьте, что вы воспользовались сервисом по уборке квартиры. Они пришли, начали уборку, но, по ходу дела, всю мебель в квартире переставили и шторы из гостинной в ванну перевесили с аргументацией "в таких условиях уборщице было приятнее выполнять свою работу". Картинку с "WTF?!" можете домыслить сами.
Для программиста код — это как раз та квартира, в которой он живет 5 дней в неделю по 8 часов. И жить в ней должно быть комфортно. Программист в данном случае — это не уборщица. Бизнесу же плевать на этот комфорт. В данном примере скорее бизнес — это уборщица, которая зачем-то запрещает мне переставлять мебель в моей же квартире.
И просто хорошая цитата про рефакторинг:
for each desired change, make the change easy (warning: this may be hard), then make the easy change
— Kent Beck (@KentBeck) 25 сентября 2012 г.
Когда мы размышляем об объектах, то первое, что приходит в голову — это классы.
Видимо, зря придумали ключевое слово class, оно затмевает сознание и мешает думать об объектах. В JS можно создавать объекты вообще без this, new и class, а только с помощью фабрик, замыканий и, собственно, объектов:
function Queue() {
const store = [];
return {
enqueue(elem) { store.unshift(elem); },
dequeue() { return store.pop(); },
}
}
Большие куски кода трудно тестировать. Большие методы, использующие кучу данных, практически нереально покрыть Unit тестами, а интеграционные тесты оказываются огромными и очень сложными.
Если код приходится "покрывать" тестами, лучше забыть про Unit-тесты, иначе и в тестировании разочаруетесь, и руководителям нервы испортите. Напишите интеграционные или приемочные тесты. Главное, чтобы они позволили без страха рефакторить и добавлять новый функционал. А новый код пишите по TDD.
Большие куски кода сложно ревьюить.
Ревью — вообще проблема и один из самых больших bottleneck'ов в процессе доставки продукта конечному пользователю. И на больших, и на маленьких кусках кода. Человек, который делает ревью, не может адекватно оценить решение, потому что он не в контексте задачи. Обычно на ревью находят всякую стилистическую шелуху, которую при наличии тестов можно поправить в любой момент.
Быть в контексте задачи помогает парное программирование, но продать его руководству очень сложно.
Переиспользуйте код, выделяйте абстракции, не стесняйтесь.
Пожалуйста, не выделяйте абстракции, когда пишите код. Они будут дерьмом, всегда. Решите сначала свою задачу, а в фазе рефакторинга, если код действительно будет "кричать" о необходимости абстракции, выделяйте.
Чтобы писать меньше кода, нужно:
перестать реализовывать бесполезные фичи
решать с помощью кода только текущие проблемы, а не мечтать "когда мы будем как Facebook, такое простое решение работать не будет"
сначала написать падающий тест
потом заставить тест проходить
закончить рефакторингом
если рефакторинг не идет — оставьте код в покое, позже все равно поймете как сделать лучше
Рассматривали ли вы вариант использования монорепозитория? Сразу уберет описанные вами боли, так как в dev-ветке модули, которые не изменялись, будут current-версии.
CI, Agile, DevOps имеют между собой одну общую черту — их убил маркетинг. Теперь CI — это не способ улучшения коммуникации в командах разработки, а GitlabCI, Jenkins, Travis и прочие инструменты. Agile и DevOps — не набор ценностей и принципов, а Kanban, Scrum, Daily meeting, Docker и другие buzzwords.
Очень похоже на религию, когда вместо философии и духа учения соблюдаются только обряды.
1) Логировать можно с помощью классического паттерна декоратор: https://en.wikipedia.org/wiki/Decorator_pattern
3) Изменять свойство другого класса можно. Методы будут храниться там, где вы посчитаете нужным в зависимости от композиции своих объектов.
4) Вот так, например
export class Application {
@observable user = new User();
}
Вот как раз таки в текущей ситуации, когда фреймворки обновляются каждый год, оно и надо.
Понял это, когда проснулся на следующий день после написания комментария. Это действительно существенное преимущество.
Т.к. фасад в логике самого приложения не участвует, то вся логика находится в командах.
…
Команд не может быть меньше чем логических действий в приложении + 1(StartUpCommand). По своей сути эти команды ближе к UseCase из Clean Architecture.
В PureMVC у всех составляющих есть четкий и довольно ограниченный круг обязанностей, но мне осталось непонятным, почему любому объекту разрешается общаться со всеми остальными через общий фасад? Вы уже применяли этот подход, поэтому хочу спросить, не запутывается ли логика в клубок из-за общение через фасад?
Рассматривать React только как UI компоненты можно, но действительно ли такая абстракция принесет пользу, мне осталось непонятным. Безболезненно можно будет перейти на другую UI-библиотеку, но часто ли такой переход осуществляется?
Разве что будут Model-и, которые будет абстракциями над другими моделями, местами их композицией
Так и будет. В примере с TodoMVC TodoListModel содержит в себе список TodoItemModel. Но ведь модель списка задач и выглядит таким образом, как ее можно смоделировать иначе? Если модель предметной области сложная, то ее никуда не получится спрятать. Но здесь правильно будет задать себе вопрос: "Действительно ли модель мой предметной области такая сложная или я сам ее усложняю?"
Стало быть мы по боку пустили как производительность
onClick={() => any} можно поправить, это небольшая беда. forceUpdate не вызывает shallow compare, который происходит в shouldComponentUpdate у PureComponent. Поэтому разницу в производительности нужно замерять. Оценивая "на глаз", мы обычно ошибаемся
К чему я веду. От сложности при написании больших SPA вам никуда не деться.
Тут стоит задаться вопросом: "А действительно ли сайты и web-приложения, которым раньше с головой хватало Multi Page Application, нужно переводить на SPA? Может Turbolinks и MPA будут идеальными решениями для большинства наших проблем?"
А вас не смущает, что на вашей схеме «правильного» MVC НЕТ «вызова методов»? А есть как раз отправка сообщений (Action'ов).
Не смущает, потому что отправка сообщения в Smalltalk == вызов метода в современных ОО-языке.
Нет, контроллер тут, если уж выделять «контроллер» по вашей схеме — это рантайм браузера, обслуживающий события мыши, плюс экшн-креэйторы
Так и есть, но зачем в текущей задаче и в 90% рядовых задач выносить эту логику в отдельное место? Не поймите меня неправильно, я не против декомпозиции и SRP, но я пришел к выводу, что YAGNI — самый мощный принцип, причем не только в программировании, но и во всех остальных аспектах жизни. Если контроллерная логика ва вью будет мешать нам разрабатывать или тестировать, мы вынесем ее в отдельное место. Software гибкое, оно все перенесет, не нужно боятся его менять.
Рад, что сообществу стать оказалась полезной и меня не сожгли на костре.
View и Controller слил воедино, потому что они всегда идут парой. Когда я осознал, что события JavaScript обрабатывают все мои кейсы, для которых в оригинальном MVC нужен был отдельный Controller, то решил убрать лишнюю сущность. Если вам нужно контролировать более сложные пользовательские input'ы, то без отдельного Controller'а не обойтись.
Бегло просмотрел лендинг PureMVC, но не успел пока что осознать. Завтра вечером посмотрю внимательней и опишу свои впечатления.
Теперь про пример с фейсбуком. Дело в том, что помимо Chat есть так же SideBar, который так же должен отобразить эту цифру, и есть шапка — в итоге три места.
Chat, SideBar, Header — это все View, которые могут содержать в себе другие View. На словах можно рассказывать слишком долго, поэтому приведу пример кода, в котором буду использовать описанные в статье BaseView и BaseModel.
Я бы создал модель чата:
import BaseModel from "./Base/BaseModel";
import ThreadModel from "./ThreadModel";
export default class extends BaseModel {
threads: ThreadModel[] = [];
get unreadMessagesCount() {
return this.threads.reduce((count: number, thread: ThreadModel) => {
return count + thread.unreadMessagesCount
}, 0)
}
threadUpdated() { this.updateViews; }
}
А затем CounterView:
import * as React from "react";
import BaseView from "./Base/BaseView";
import ChatModel from "./ChatModel";
export default class extends BaseView <ChatModel, {}> {
render() { return (
<span>{this.model.unreadMessagesCount}</span>
); }
}
После чего инстанцировал бы все модели и общие компоненты на уровне PageView, например. А в HeaderView передал бы инстанс ChatView в качестве props'а:
import * as React from "react";
import BaseView from "./Base/BaseView";
import HeaderModel from "./HeaderModel";
import CounterView from "./CounterView";
export default class extends BaseView <HeaderModel, {counterView: CounterView}> {
render() { return (
<header>
<section>Facebook</section>
{this.props.counterView}
</header>
); }
}
Если CounterView нужно как-то кастомизировать внутри HeaderView, то можно в HeaderView передавать ChatModel через props'ы и инстанцировать CounterView уже внутри HeaderView. Или можно использовать ChatView как child внутри HeaderView. Возможно, есть более элегантное решение, но я бы сделал так.
Сразу видно повторение структуры
Которую частично можно заменить nullable object'ом
И избавиться от повторяющихся if'ов
Уверен, что код можно еще упростить, если знать контекст.
В list comprehension новая фича выглядит полезной, пока не приходит осознание, что при реальной необходимости её можно реализовать самостоятельно один раз на весь проект:
Для программиста код — это как раз та квартира, в которой он живет 5 дней в неделю по 8 часов. И жить в ней должно быть комфортно. Программист в данном случае — это не уборщица. Бизнесу же плевать на этот комфорт. В данном примере скорее бизнес — это уборщица, которая зачем-то запрещает мне переставлять мебель в моей же квартире.
И просто хорошая цитата про рефакторинг:
Вместо
Object.setPrototypeOf(self, q)
можно использоватьObject.assign({}, q, self)
.Вам действительно нужно наследование? Если без него действительно никак не обойтись, то можно реализовать вот так:
Из-за "мокнутых кусков" тестирование превращается в ад. В данном случае, если очень хочется, можно создать фэйковый Queue и использовать его в тестах:
Конкретно этот пример надуманный, но суть он показывает.
А зачем разрастаться, если объекты можно делать маленькими и разносить по разным файлам?
Напишите, мне будет интересно почитать.
Лично мне такой подход дает больше гибкости. А вот классы обычно подталкивают к мышлению в рамках классов, код из-за этого наоборот теряет в гибкости.
JS прекрасный ОО-язык, но его зачем-то превращают в еще одну Java, а рынок, к сожалению, этот тренд поддерживает.
Видимо, зря придумали ключевое слово
class
, оно затмевает сознание и мешает думать об объектах. В JS можно создавать объекты вообще безthis
,new
иclass
, а только с помощью фабрик, замыканий и, собственно, объектов:Чем такой способ создания объектов хуже классов?
Если код приходится "покрывать" тестами, лучше забыть про Unit-тесты, иначе и в тестировании разочаруетесь, и руководителям нервы испортите. Напишите интеграционные или приемочные тесты. Главное, чтобы они позволили без страха рефакторить и добавлять новый функционал. А новый код пишите по TDD.
Ревью — вообще проблема и один из самых больших bottleneck'ов в процессе доставки продукта конечному пользователю. И на больших, и на маленьких кусках кода. Человек, который делает ревью, не может адекватно оценить решение, потому что он не в контексте задачи. Обычно на ревью находят всякую стилистическую шелуху, которую при наличии тестов можно поправить в любой момент.
Быть в контексте задачи помогает парное программирование, но продать его руководству очень сложно.
Пожалуйста, не выделяйте абстракции, когда пишите код. Они будут дерьмом, всегда. Решите сначала свою задачу, а в фазе рефакторинга, если код действительно будет "кричать" о необходимости абстракции, выделяйте.
Чтобы писать меньше кода, нужно:
Рассматривали ли вы вариант использования монорепозитория? Сразу уберет описанные вами боли, так как в dev-ветке модули, которые не изменялись, будут current-версии.
CI, Agile, DevOps имеют между собой одну общую черту — их убил маркетинг. Теперь CI — это не способ улучшения коммуникации в командах разработки, а GitlabCI, Jenkins, Travis и прочие инструменты. Agile и DevOps — не набор ценностей и принципов, а Kanban, Scrum, Daily meeting, Docker и другие buzzwords.
Очень похоже на религию, когда вместо философии и духа учения соблюдаются только обряды.
Если хочется логировать глобально все события, то можно использовать spy, который идет вместе с Mobx.
А что не так с декораторами и console? :)

1) Логировать можно с помощью классического паттерна декоратор: https://en.wikipedia.org/wiki/Decorator_pattern
3) Изменять свойство другого класса можно. Методы будут храниться там, где вы посчитаете нужным в зависимости от композиции своих объектов.
4) Вот так, например
Понял это, когда проснулся на следующий день после написания комментария. Это действительно существенное преимущество.
Спасибо, теперь стало понятней.
В PureMVC у всех составляющих есть четкий и довольно ограниченный круг обязанностей, но мне осталось непонятным, почему любому объекту разрешается общаться со всеми остальными через общий фасад? Вы уже применяли этот подход, поэтому хочу спросить, не запутывается ли логика в клубок из-за общение через фасад?
Рассматривать React только как UI компоненты можно, но действительно ли такая абстракция принесет пользу, мне осталось непонятным. Безболезненно можно будет перейти на другую UI-библиотеку, но часто ли такой переход осуществляется?
У Redux другой подход, это понятно. Но в статье же речь идет об объектно-ориентированном MVC :)
И все это вместе называется объектом в ООП. Объекты они же про менеджмент своего локального состояния, так с чем же здесь проблема?
Так и будет. В примере с TodoMVC TodoListModel содержит в себе список TodoItemModel. Но ведь модель списка задач и выглядит таким образом, как ее можно смоделировать иначе? Если модель предметной области сложная, то ее никуда не получится спрятать. Но здесь правильно будет задать себе вопрос: "Действительно ли модель мой предметной области такая сложная или я сам ее усложняю?"
onClick={() => any}
можно поправить, это небольшая беда.forceUpdate
не вызывает shallow compare, который происходит вshouldComponentUpdate
уPureComponent
. Поэтому разницу в производительности нужно замерять. Оценивая "на глаз", мы обычно ошибаемсяТут стоит задаться вопросом: "А действительно ли сайты и web-приложения, которым раньше с головой хватало Multi Page Application, нужно переводить на SPA? Может Turbolinks и MPA будут идеальными решениями для большинства наших проблем?"
Не смущает, потому что отправка сообщения в Smalltalk == вызов метода в современных ОО-языке.
Так и есть, но зачем в текущей задаче и в 90% рядовых задач выносить эту логику в отдельное место? Не поймите меня неправильно, я не против декомпозиции и SRP, но я пришел к выводу, что YAGNI — самый мощный принцип, причем не только в программировании, но и во всех остальных аспектах жизни. Если контроллерная логика ва вью будет мешать нам разрабатывать или тестировать, мы вынесем ее в отдельное место. Software гибкое, оно все перенесет, не нужно боятся его менять.
Рад, что сообществу стать оказалась полезной и меня не сожгли на костре.
View и Controller слил воедино, потому что они всегда идут парой. Когда я осознал, что события JavaScript обрабатывают все мои кейсы, для которых в оригинальном MVC нужен был отдельный Controller, то решил убрать лишнюю сущность. Если вам нужно контролировать более сложные пользовательские input'ы, то без отдельного Controller'а не обойтись.
Бегло просмотрел лендинг PureMVC, но не успел пока что осознать. Завтра вечером посмотрю внимательней и опишу свои впечатления.
Chat, SideBar, Header — это все View, которые могут содержать в себе другие View. На словах можно рассказывать слишком долго, поэтому приведу пример кода, в котором буду использовать описанные в статье BaseView и BaseModel.
Я бы создал модель чата:
А затем CounterView:
После чего инстанцировал бы все модели и общие компоненты на уровне PageView, например. А в HeaderView передал бы инстанс ChatView в качестве props'а:
Если CounterView нужно как-то кастомизировать внутри HeaderView, то можно в HeaderView передавать ChatModel через props'ы и инстанцировать CounterView уже внутри HeaderView. Или можно использовать ChatView как child внутри HeaderView. Возможно, есть более элегантное решение, но я бы сделал так.
Интересная беседа получается, спасибо за нее.