В каком фрайворке можно легко манипулировать полями?
Salesforce
Salesforceэто что?
гугл первой строкой выдает ссылку на какое-то говнище "without writing any code"
http://www.google.com/search?q=Salesforce&oq=Salesforce...
На какой платформе и в каком фрейворке можно легко добавлять, удалять, менять обработку поля?Ответ очевиден. C#, по сравнению с которым все языки - говно. ReSharper, без которого C# - тоже говно. И Controllable Query, без которого C#, даже с ReSharper - говно.
Ответ очевиден. C#, по сравнению с которым все языки - говно. ReSharper, без которого C# - тоже говно. И Controllable Query, без которого C#, даже с ReSharper - говно.На C# нет UI движка, который бы не забросили, поэтому твой "очевидный" ответ неправильный.
Предполагаю, многие архитекторы покажут свой уровень, если поговорить с ними на эту тему.
Под описание проходит qt?
Задача решена.
Еще, конечно, имплементировать Controllable Query для тайпскрипта, чтобы можно было писать npm install cotrollable-query.
Еще, конечно, имплементировать Controllable Query для тайпскрипта, чтобы можно было писать npm install cotrollable-query.а может быть обычный asm? ведь возможности безграничны!
айпскрипт, про который ты сам говорил, что программировать на нем не менее удобно чем на C#я вроде такого не говорил
что программировать на нем не менее удобно чем на C#Ничего подобного. ReTypscer ещё не вышел в релиз.
Вы вот смеетесь, а React + TypeScript вполне себе
Я ищу такой фреймворк для хуй знает какого языка под хуй знает какую-задачу, чтоб работал с хуй знает какой базой данной и чтобы работал с ним... oh wait
Берешь nodejsНа node.js можно писать без event-driven? Или это обязаловка? Проблемы высоких нагрузок и масштабируемости мало для кого действительно актуальны (для нас обычного железа и .NET даже без wait async хватает за глаза, если сами глупости не делать). Код от event-driven, как не крути, усложняется. Если event-driven не нужен, то это неоправданное усложнение кода. В энтерпрайзе хватит и своей сложности (в бизнес логике).
Еще, конечно, имплементировать Controllable Query для тайпскрипта, чтобы можно было писать npm install cotrollable-query.Во-первых, для того, чтобы реализовать Controllable Query для TS надо глубоко погрузиться в компилятор TS. У меня не так много времени.
Во-вторых, в node.js event-driven, который редко нужен.
Поэтому на сервере переходить на js пока не оправдано. Не хочется быть первопроходцем. Пусть другие наработаю шишки на применении node.js в энтерпрайзе.
Сейчас видится приемлемым вариант: на клиенте ts на сервере .NET. Придется написать кодогенератор для remote facade и DTO. Из UI движков сейчас html/js чувствует себя лучше всех. Существенный минус разных языков на клиенте и сервере это то, что нельзя шарить код между сервером и клиентом. Это обычно требуется для валидации и расчетов над данными. Придется для этого лишний раз обращаться на сервер. По моим оценкам это не очень критично для наших приложений.
Меня не покидает ощущение, почему проблему subj-а никто не решает целиком, все что-то пилят на своем отрезке, и преподносят это как инновации.
На node.js можно писать без event-driven? Или это обязаловка?В API ноды функции идут парами синхронная/асинхронная (readFileSync/readFile). В принципе, можно писать на ноде CGI или FCGI воркеры чисто на синхронных функциях.
Сейчас видится приемлемым вариант: на клиенте ts на сервере .NET. Придется написать кодогенератор для remote facade и DTO.Это, кстати, ошибка - делать клиент-серверное взаимодействие в виде RPC.
Нужен язык описания схемы данных, из которого одновременно выводился бы протокол синхронизации, учитывающий конкарренси, валидацию, батчинг и пуш с сервера, что-то в духе GraphQL/Relay.
Это, кстати, ошибка - делать клиент-серверное взаимодействие в виде RPC.Подозреваю это ошибка, если не хочется писать код. А код не хочется писать из-за того, что нужно быть внимательным и много чего помнить. Эти проблемы присущи динамическому js. На хорошем статически типизированном языке код не только просто писать, но код еще и помогает строить бизнес логику.
Нужен язык описания схемы данных, из которого одновременно выводился бы протокол синхронизации, учитывающий конкарренси, валидацию, батчинг и пуш с сервера, что-то в духе GraphQL/Relay.
GraphQL
Что-то это мне сильно напоминает ...... ORM на клиенте.
Слава богу, в мире .NET есть здравые люди, которые не скрывают реляционную базу под ORM. Например, вот. Ну и Controllable Query, конечно, освобождает от ORM.
Нас не заманишь в очередную реинкарнацию ORM. Мы любим SQL базы.
Незачем скрывать ни базу, ни сервер.
Использовать такие навороченные абстракции от Файсбука очень рискованно. Они то себе, если надо, что-то смогут поменять во врейворке. У тебя же не такой бюджет проекта. Тянуть тяжелые абстракции в проект надо очень осторожно.
Подозреваю это ошибка, если не хочется писать код. А код не хочется писать из-за того, что нужно быть внимательным и много чего помнить.Как ты реализуешь следующую функциональность - в форуме в обычном режиме выводится список тредов с рейтингом первого сообщения, в cyперлайт режиме без рейтинга? Как ты организуешь API?
Нас не заманишь в очередную реинкарнацию ORM. Мы любим SQL базы.GraphQL - это просто альтернативный синтаксис запросов, который прозрачно транслируется в SQL. Вот, к примеру, весь транслятор занимает 500 строк, при том что написан на не самом удобном для этого языке.
GraphQL - это просто альтернативный синтаксис запросов, который прозрачно транслируется в SQL.А зачем? В истории было так, сначала были базы на основе графов, потом их вытеснила красивая реляционка (1982 год). Теперь молодняк хочет пройти этим путем еще раз?
Зачем работать с графами? С отношениями же проще работать? Id-шники туда сюда можно гонять, красота же.
И, соответственно, не понятно нахера язык запросов, завязанный на графы? Чем няшный SQL, завязанный на отношения, не угодил?
Эээ, неуважаемый, а как же DevExpress?
Эээ, неуважаемый, а как же DevExpress?У DevExpress куча продуктов, ты, мудила, что имеешь ввиду?
GraphQLНа праздниках вышло обновление в блоге React.js про GraphQL. Там как раз твоя проблема адресуется:
GraphQL is strongly-typed. Given a query, tooling can ensure that the query is both syntactically correct and valid within the GraphQL type system before execution, i.e. at development time, and the server can make certain guarantees about the shape and nature of the response. This makes it easier to build high quality client tools.Надо отметить, что фейсбуковский стек GraphQL/Relay/FlowType решает проблему, которую невозожно решить в рамках дотнета и C#. Рассмотрим для примера наш форум с тредами. Где-то нужно получить список тредов, где-то список тредов плюс автора каждого треда, где-то список тредов и с первым постом в каждом треде. Сейчас единственный вариант - делать отдельную функцию в RPC, что, во-первых, приводит к комбинаторному взрыву, а во вторых каждая функция в RPC будет возвращать свой уникальный тип, и типы будут совершенно несовместимы друг с другом. В случае с GraphQL, каждый клиент (например web, iphone или android) для каждой страницы зафетчит именно те данные, которые нужны для этой страницы. Компилятор flowtype будет выводить тип прямо из graphql запроса на получение данных, так как в flowtype нет номинальных типов, все типы структурные.
Я, кстати, форуме про структурную типизацию в применении к оперденям.
проблему, которую невозожно решить в рамках дотнета и C#. Рассмотрим для примера наш форум с тредами.Не поверишь, но на C# можно написать форум.
Где-то нужно получить список тредов, где-то список тредов плюс автора каждого треда, где-то список тредов и с первым постом в каждом треде. Сейчас единственный вариант - делать отдельную функцию в RPC, что, во-первых, приводит к комбинаторному взрывуОпиши более ясно требования к программному обеспечения, которые порождают комбинаторный взрыв? Пока в твоем посте таких требований нет.
во вторых каждая функция в RPC будет возвращать свой уникальный тип, и типы будут совершенно несовместимы друг с другом.
Зачем совместимость для разных DTO?
Опиши более ясно требования к программному обеспечения, которые порождают комбинаторный взрыв? Пока в твоем посте таких требований нет.Я процитирую сразу инженеров фейсбука (добавлю от себя, что сейчас наблюдаю все описанное у себя на работе):
Invariably fields and additional data are added to REST endpoints as the system requirements change. However, old clients also receive this additional data as well, because the data fetching specification is encoded on the server rather than the client. As result, these payloads tend to grow over time for all clients. When this becomes a problem for a system, one solution is to overlay a versioning system onto the REST endpoints. Versioning also complicates a server, and results in code duplication, spaghetti code, or a sophisticated, hand-rolled infrastructure to manage it. Another solution to limit over-fetching is to provide multiple views – such as “compact” vs “full” – of the same REST endpoint, however this coarse granularity often does not offer adequate flexibility.
...
These systems typically define a custom endpoint per view. For systems with a wide surface area this can quickly grow into a maintenance nightmare of orphaned endpoints, inconsistent tooling, and massive server code duplication. Disciplined engineering organizations can mitigate these issues with great engineering practices, high quality abstractions, and custom tooling. However, given our experience we believe that custom endpoints tend to lead to entropic server codebases.
Зачем совместимость для разных DTO?
Простейший случай - это когда данные показываются в разных контекстах. Например, есть соцсеть, там можно искать пользователей по имени и видеть список, а можно смотреть друзей какого-то пользователя. Есть скажем, запрос GET /users?name=:name, который возвращает [{name, avatar}] и запрос GET /users/:id/friends, который возвращает [{name, avatar, friendSince}]. Мы хотим для обоих случаев использовать общий UI компонент UsersList. Но его невозможно написать, так как типы разные.
Я процитирую сразу инженеров фейсбука (добавлю от себя, что сейчас наблюдаю все описанное у себя на работе):Всё процитированное это описание проблем динамической типизации. И в Файсбуке и у тебя на работе динамика (и на сервере и на клиенте).
Ты же по неизвестной причине переносишь это на типизированный Remote Facade. Почему?
Простейший случай - это когда данные показываются в разных контекстах. Например, есть соцсеть, там можно искать пользователей по имени и видеть список, а можно смотреть друзей какого-то пользователя. Есть скажем, запрос GET /users?name=:name, который возвращает [{name, avatar}] и запрос GET /users/:id/friends, который возвращает [{name, avatar, friendSince}]. Мы хотим для обоих случаев использовать общий UI компонент UsersList. Но его невозможно написать, так как типы разные.
Откуда ты заключил, что невозможно?
Начинаешь писать UsersList, ага ему требуется такие-то данные, делаешь под эти требования тип XXX (типа view model, но я бы не заморачивался этой терминологией, исходил бы из реальных потребностей). Из DTO тем или иным способом делаешь XXX. В простейшем случае, просто DTO реализуют интерфейс XXX.
Всё процитированное это описание проблем динамической типизации.Динамика помогает срезать острые углы в задачах, с которыми традиционные системы типов не справляются. Представь форум, есть запрос GET /threads. В разных приложениях (айфон, андроид, веб, мобайл веб) для каждого треда могут выводиться (или не выводиться) свойтва author, firstPost, posts, viewsCount итд. Если мы достаем свойства, которые потом не отображаются в UI, то это оверфетчинг и мы зря напрягаем сервер. Иногда добавляют флаги типа GET /threads?withFirstPost=true или /threads?withAuthor=true. 3 опциональных атрибута дают комбинацию из 8 типов. Желаю удачи в написании 8 разных функций в RPC, с уникальным типом у каждой, и в дальнейшем ручном написании интерфейсов на клиенте, которые будут заворачивать каждый из этих типов.
UI на каждом устройстве свой
Проблема надуманная.
Иногда добавляют флаги типа GET /threads?withFirstPost=true или /threads?withAuthor=true. 3 опциональных атрибута дают комбинацию из 8 типов. Желаю удачи в написании 8 разных функций в RPCзачем 8 функций-то? для сложения двух чисел ты тоже на каждую комбинацию параметров по функции писать будешь?
зачем 8 функций-то? для сложения двух чисел ты тоже на каждую комбинацию параметров по функции писать будешь?он намекает, что тип возвращаемого значения разный
всего 4 устройства, поэтому методов должно быть меньше или равно 4Неверно.
Hint: В каждом устройстве форм отображения много больше, чем одна.
Неверно.Верно.
Hint: См. контекст разговора.
Новая презентация по GraphQL, которая объясняет, как работает обновление данных на сервере с точки зрения клиента GraphQL. Вот мой короткий пересказ.
До появления реакта UI кодировался следующим образом: пользователь совершал действие, мы на каждое действие смотрели, какие виджеты надо изменить по результатам этого действия, и вызывали API этих виджетов. Основная трудность была в том, что для каждого виджета надо было помнить, как изменения в этом виджете могут повлиять на другие виджеты, и неявно реализовывать эти зависимости в коде.
С реактом у нас другая парадигма программирования - у нас есть application state, который представляет собой набор данных, которыми оперирует программа, без дублирования данных и вычисляемых данных, и чистая функция render: application state -> UI. При любом событии мы меняем application state, и у нас вызывается функция render, которая вычисляет все вычисляемые данные и отображает их. Такой подход избавляет нас от необходимости помнить, что при нажатии на кнопку A надо задисейблить кнопку B - при нажатии на кнопку A мы просто обновляем application state, при этом весь интерфейс, включая кнопку B, перерисовывается, а при отрисовке B просто вызывается чистая функция isButtonBDisabled(application state).
Таким образом, убирается связь каждого виджета с каждым, которая квадратично раздувает код. В качестве "единого источника правды" выступает application state, а UI просто вычисляется на основе него чистой функцией.
Но похожая проблема остается, когда речь идет о взаимодействии с сервером. Рассмотрим простейший пример - отображается пост с кнопкой like и списком пользователей, которые лайкнули пост. Когда мы жмем like, то мы добавляемся в список лайков, и количество лайков увеличивается на единицу. Есть два способа реализовать такую функциональность.
Первый способ - после того как отработает запрос на лайк, мы вручную увеличим счетчик на единицу на клиенте, и вставим в массив пользователей, лайкнувших пост, текущего пользователя. Этот способ плох тем, что приходится дублировать логику, уже реализованную на сервере.
Второй способ - дождаться когда отработает запрос на лайк, и заново запросить все данные о посте. Этот способ плох тем, что все равно приходится делать ручную работу - искать старый пост в массиве постов и заменять его на новый пост, пришедший с сервера. Кроме того, происходит удвоение latency - сначала мы должны дождаться, когда отработает запрос на изменение данных, и только после этого заново запрашивать данные.
Фейсбук решает эту проблему следующим образом. Все персистентные данные, зафетченные на клиент, хранятся в некой клиентской базе данных. Эта база данных никогда не редактируется напрямую - сначала всегда делается запрос на сервер. Для каждого типа запроса (например лайк пользователя) прописывается, какие данные инвалидируются этим запросом. Например, запрос like инвалидирует параметры like_count и like_users поста. Это прописывается явным декларативным образом. На основе этих данных, после запроса на like, в респонсе возвращаются новые значения для этих полей, и клиентская база данных обновляется.
Таким образом, принцип unidirectional dataflow распространяется на все приложение целиком - пользователь делает действие в UI, отправляется запрос на сервер, сервер меняет данные и они автоматически обновляются на клиенте, после этого вызывается реакт который перерисовывает UI. Круг замкнулся.
Можно отметить, что в данном подходе все равно, кто инициировал обновление данных - сам пользователь, или кто-то еще - в любом случае, клиентское приложение никогда само не обновляет себе данные, это делает только сервер. Таким образом, получаем простую модель, в которой легко реализуется server push.
До появления реакта UI кодировался следующим образом: пользователь совершал действие, мы на каждое действие смотрели, какие виджеты надо изменить по результатам этого действия, и вызывали API этих виджетов. Основная трудность была в том, что для каждого виджета надо было помнить, как изменения в этом виджете могут повлиять на другие виджеты, и неявно реализовывать эти зависимости в коде.
С реактом у нас другая парадигма программирования - у нас есть application state, который представляет собой набор данных, которыми оперирует программа, без дублирования данных и вычисляемых данных, и чистая функция render: application state -> UI. При любом событии мы меняем application state, и у нас вызывается функция render, которая вычисляет все вычисляемые данные и отображает их. Такой подход избавляет нас от необходимости помнить, что при нажатии на кнопку A надо задисейблить кнопку B - при нажатии на кнопку A мы просто обновляем application state, при этом весь интерфейс, включая кнопку B, перерисовывается, а при отрисовке B просто вызывается чистая функция isButtonBDisabled(application state).
Таким образом, убирается связь каждого виджета с каждым, которая квадратично раздувает код. В качестве "единого источника правды" выступает application state, а UI просто вычисляется на основе него чистой функцией.
Но похожая проблема остается, когда речь идет о взаимодействии с сервером. Рассмотрим простейший пример - отображается пост с кнопкой like и списком пользователей, которые лайкнули пост. Когда мы жмем like, то мы добавляемся в список лайков, и количество лайков увеличивается на единицу. Есть два способа реализовать такую функциональность.
Первый способ - после того как отработает запрос на лайк, мы вручную увеличим счетчик на единицу на клиенте, и вставим в массив пользователей, лайкнувших пост, текущего пользователя. Этот способ плох тем, что приходится дублировать логику, уже реализованную на сервере.
Второй способ - дождаться когда отработает запрос на лайк, и заново запросить все данные о посте. Этот способ плох тем, что все равно приходится делать ручную работу - искать старый пост в массиве постов и заменять его на новый пост, пришедший с сервера. Кроме того, происходит удвоение latency - сначала мы должны дождаться, когда отработает запрос на изменение данных, и только после этого заново запрашивать данные.
Фейсбук решает эту проблему следующим образом. Все персистентные данные, зафетченные на клиент, хранятся в некой клиентской базе данных. Эта база данных никогда не редактируется напрямую - сначала всегда делается запрос на сервер. Для каждого типа запроса (например лайк пользователя) прописывается, какие данные инвалидируются этим запросом. Например, запрос like инвалидирует параметры like_count и like_users поста. Это прописывается явным декларативным образом. На основе этих данных, после запроса на like, в респонсе возвращаются новые значения для этих полей, и клиентская база данных обновляется.
Таким образом, принцип unidirectional dataflow распространяется на все приложение целиком - пользователь делает действие в UI, отправляется запрос на сервер, сервер меняет данные и они автоматически обновляются на клиенте, после этого вызывается реакт который перерисовывает UI. Круг замкнулся.
Можно отметить, что в данном подходе все равно, кто инициировал обновление данных - сам пользователь, или кто-то еще - в любом случае, клиентское приложение никогда само не обновляет себе данные, это делает только сервер. Таким образом, получаем простую модель, в которой легко реализуется server push.
До появления реакта UI кодировался следующим образом: пользователь совершал действие, мы на каждое действие смотрели, какие виджеты надо изменить по результатам этого действия, и вызывали API этих виджетов. Основная трудность была в том, что для каждого виджета надо было помнить, как изменения в этом виджете могут повлиять на другие виджеты, и неявно реализовывать эти зависимости в коде.Твоя проблема в том, что ты раз за разом выдумываешь или находишь ситуации, которые возможны только при написании кода junior разработчиками без code review. Мне очень сложно представить, как C# заставляет меня помнить, какие изменения конкретного представления влияют на что-то, кроме этого самого представления. И тот факт, что какую-то задачу по неопытности можно решить именно так, говорит только о неопытности.
Учти, что собеседования в Facebook нереально олимпиадные. (Я сужу по careercup.) Олимпиадное программирование очень сильно расходится с инженерными буднями. По этой или другим причинам код в Facebook такой, что вызывает во мне первобытный ужас. Так что глупо отталкиваться от того, какие проблемы при разработке интерфейсов были у какой-то конкретной команды. Они сделали какую-то хорошую технологию, но это не значит, что какая-нибудь Java ограничена в качественной реализации схожего функционала.
Таким образом, убирается связь каждого виджета с каждым, которая квадратично раздувает код. В качестве "единого источника правды" выступает application state, а UI просто вычисляется на основе него чистой функцией.
Всё то же самое получается и при наличии stateful представлений. Только UI не вычисляется, а просто отображает своё состояние, которое не является источником правды ни для кого и ни для чего, кроме этого UI. Откуда берётся связь каждого виджета с каждым я вообще понять не смог, хоть и пытался.
Фейсбук решает эту проблему следующим образом. Все персистентные данные, зафетченные на клиент, хранятся в некой клиентской базе данных. Эта база данных никогда не редактируется напрямую - сначала всегда делается запрос на сервер. Для каждого типа запроса (например лайк пользователя) прописывается, какие данные инвалидируются этим запросом. Например, запрос like инвалидирует параметры like_count и like_users поста. Это прописывается явным декларативным образом. На основе этих данных, после запроса на like, в респонсе возвращаются новые значения для этих полей, и клиентская база данных обновляется.Это всё неплохо реализуется и на C# Под базой данных надо будет понимать иерархию объектов в модели представления. Частичное обновление этой модели можно реализовать как чисто ООП способом, так и через reflection. Правда я опять не понял, зачем явно прописывать, какие поля инвалидируются, когда их можно просто сразу вернуть в ответ на запрос.
код в Facebook такой, что вызывает во мне первобытный ужас.Этот код в публичном доступе? Если да, запости, пожалуйста, ссылку.
Этот код в публичном доступе? Если да, запости, пожалуйста, ссылку.В Facebook полно кода в публичном доступе. Зачем ждать от меня ссылку, если можно кликнуть первый же результат поиска? Вот, 3-4 клика от топа поиска, и я вижу такой код:
AndroidBinaryGraphEnhancer graphEnhancer = new AndroidBinaryGraphEnhancer(
params,
resolver,
compressionMode,
resourceFilter,
addFallbackLocales(args.locales.or(ImmutableSet.<String>of
args.manifest,
packageType,
ImmutableSet.copyOf(args.cpuFilters.get
args.buildStringSourceMap.or(false
shouldPreDex,
AndroidBinary.getPrimaryDexPath(params.getBuildTarget
dexSplitMode,
ImmutableSet.copyOf(args.noDx.or(ImmutableSet.<BuildTarget>of
/* resourcesToExclude */ ImmutableSet.<BuildTarget>of
args.skipCrunchPngs.or(false
javacOptions,
exopackageModes,
(Keystore) keystore,
args.buildConfigValues.get
args.buildConfigValuesFile,
nativePlatforms,
dxExecutorService);
Правда я сейчас ещё раз открыл код ко-ку и подумал, что у тебя вряд ли будут схожие с моими ощущения.
Вот, 3-4 клика от топа поиска, и я вижу такой код:Вроде типичная джава-параша, разве нет? Если сравнивать с каким-нибудь мавеном или антом, то даже лучше выглядит.
Вроде типичная джава-параша, разве нет? Если сравнивать с каким-нибудь мавеном или антом, то даже лучше выглядит.Найти плохой код можно на любом языке. В данном случае я искал что-то не очень красивое на Java.
Мне очень сложно представить, как C# заставляет меня помнить, какие изменения конкретного представления влияют на что-то, кроме этого самого представления.Вопрос не в C#, речь идет об архитектуре UI фреймворка, которую можно реализовать хоть на C#, хоть на js.
Откуда берётся связь каждого виджета с каждым я вообще понять не смог, хоть и пытался.
Правда я опять не понял, зачем явно прописывать, какие поля инвалидируются, когда их можно просто сразу вернуть в ответ на запрос.Они и возвращаются в ответ на запрос. Проблема в том, что количество данных, которое устарело после запроса, зависит от того, сколько данных клиент уже зафетчил. Например, если в данном примере клиент дополнительно запрашивал бы количество постов, которое лайкнул текущий пользователь, то после лайка эти данные то же бы устарели, и серверу пришлось бы также их возвращать. Таким образом, сервер сам по себе не знает, что слать в респонсе, это определяет клиент.
Пример кода с квадратичной связью.Это пример какого-то куска кода. На деле я не вижу, откуда эта связь берётся. Почему в ней есть необходимость.
Они и возвращаются в ответ на запрос. Проблема в том, что количество данных, которое устарело после запроса, зависит от того, сколько данных клиент уже зафетчил. Например, если в данном примере клиент дополнительно запрашивал бы количество постов, которое лайкнул текущий пользователь, то после лайка эти данные то же бы устарели, и серверу пришлось бы также их возвращать. Таким образом, сервер сам по себе не знает, что слать в респонсе, это определяет клиент.
Примера не понял. Ты написал, что запрос "сделать лайк" явно связан с данными, которые он инвалидирует. Сервер в данном случае знает столько же, сколько клиент.
то пример какого-то куска кода. На деле я не вижу, откуда эта связь берётся. Почему в ней есть необходимость.Как ты решишь эту задачу - 3 поля ввода, синхронизированных друг с другом?
Подозреваю, что ты предложишь что-то на тему publish/subscribe.
Как ты решишь эту задачу - 3 поля ввода, синхронизированных друг с другом?Ты предлагаешь мне написать кода в размере O(E^2 где E - количество полей ввода? Знаешь, даже если решить её с помощью такого кода, то для связи типа "каждый с каждым" кроме этих полей нужно обновлять все остальные компоненты интерфейса. Давай выражаться точнее: некоторые компоненты должны обновлять своё состояние, когда изменяется доменная модель. Ни о каком квадратичном раздувании кода речи на самом деле не идёт.
Подозреваю, что ты предложишь что-то на тему publish/subscribe.
Ты приводишь пример про кнопки, когда связи между компонентами интерфейса неявные. На практике такой код тяжелее поддерживать и развивать. Например, связать факт неправильного поведения кнопки B при нажатии на кнопку A можно только с помощью анализа data flow. Но этого нельзя сделать с помощью анализа control flow, что на порядки проще. Когда кнопка B вдруг перестала работать как надо, тебе нужно будет осмотреть как логику изменения модели, так и логику отрисовки отдельных компонентов. Да, ты избавился от необходимости помнить, что при нажатии на кнопку A надо обновить кнопку B. Но тебе нужно помнить, что при отрисовке кнопки B нужен корректно работающий метод isEnabled. А ещё тебе нужно помнить, что обработчик кнопки A должен менять какую-то конкретную часть состояния, при чём это знание является неявным, то есть восстанавливать его очень тяжело.
Та же самая ситуация намного легче анализируется и решается, например, с помощью MVP с вариацией Passive View. Presenter обрабатывает сообщение от кнопки A, меняет доменную модель и обновляет кнопку B. Просто поставив в этом месте брейкпоинт можно линейно пройти все шаги и сразу сказать, что происходит. С помощью этого же подхода можно синхронизировать три поля ввода. Если вести речь о разовом решении, то достаточно одного обработчика события, одного поля в доменной модели, и одного метода представления с тремя строчками кода. То есть количество кода получается O(E). Столько же, сколько его в твоём подходе, зато с плюсами наличия явного понимания происходящего.
Qt signal/slot не подойдут как варинат?
Qt signal/slot не подойдут как варинат?Как вариант чего? Получения сообщения от компонента редактирования текста? Подойдут.
Presenter обрабатывает сообщение от кнопки A, меняет доменную модель и обновляет кнопку B.Это плохой подход. Если доменная модель меняется в нескольких местах, то тебе в каждом месте надо помнить, что при смене доменной модели надо еще и кнопку B перерисовать. Потом, если у тебя появится кнопка C, то про нее тоже надо помнить. N мест, в которых меняется доменная модель, дают в совокупности c M кнопками сложность M*N. Очевидно, что надо делать decoupling изменятелей доменной модели от отображателей.
Qt signal/slot не подойдут как варинат?Это вариация на тему publish/subscribe, к тому же выраженная в рамках убогого языка (вроде требуют какого-то УГ препроцессора).
Это плохой подход. Если доменная модель меняется в нескольких местах, то тебе в каждом месте надо помнить, что при смене доменной модели надо еще и кнопку B перерисовать. Потом, если у тебя появится кнопка C, то про нее тоже надо помнить. N мест, в которых меняется доменная модель, дают в совокупности c M кнопками сложность M*N.Если доменная модель одинаково меняется в нескольких местах, то это выносится в одну функцию. Откуда ты берёшь M*N мне до сих пор не понятно. Если каждое из N изменений модели обновляет все M мнопок, то сложность одинаковая в обоих примерах, и в худшем случае она будет N + M. Только в моём примере как раз нужно намного меньше держать в голове. Причины описаны выше.
Очевидно, что надо делать decoupling изменятелей доменной модели от отображателей.Очевидно, что в моём случае это сделано.
N мест, в которых меняется доменная модель, дают в совокупности c M кнопками сложность M*N.Если нет доменной модели, нет этих N мест? Тем самым нет проблемы?
ты берёшь M*NЕсть M контролов, которые меняют косвенно N других контролов.
Если доменная модель одинаково меняется в нескольких местах, то это выносится в одну функцию.Ок, не мог бы ты показать свой код для примитивного калькулятора, по типу такого?
Насколько я понял, ты сделаешь какие-то функции onChangeFirst и onChangeSecond, в которых будешь вызывать функцию updateSum. Но тогда у тебя информация о связи между слагаемыми и суммой будет закодирована в двух местах: первый раз, явно, в функции вычисления суммы, и еще пару раз, менее явно, в функциях onChangeFirst и onChangeSecond.
PS: я специально написал код с большим количеством бойлерплейта, в реакте то же самое можно сделать меньшим количеством кода.
калькулятор со странностью, не работает привычное поведение, жму BackSpace, ожидаю, что поле ввода очиститься, вместо этого вылазит не стираемый NaN.
калькулятор со странностью, не работает привычное поведение, жму BackSpace, ожидаю, что поле ввода очиститься, вместо этого вылазит не стираемый NaN.Ты отлично продемонстрировал, чем плохи тяжеловесные, слишком умные фрейворки, неожиданно отваливается привычное поведение. И потом программист вынужден разбираться, думать как выкрутиться в парадигме фрейворка. Т.е. accidental complexity, которой нет в исходной задаче.
калькулятор со странностью, не работает привычное поведение, жму BackSpace, ожидаю, что поле ввода очиститься, вместо этого вылазит не стираемый NaN.Это аргумент Буратино:
— Предположим, что у вас в кармане два яблока. Некто взял у вас одно яблоко. Сколько у вас осталось яблок?
— Два.
— Подумайте хорошенько.
Буратино сморщился, — так здорово подумал.
— Два…
— Почему?
— Я же не отдам некту яблоко, хоть он дерись!
Разумеется, я написал минимальный код для демонстрации концепта и не рассматривал случаи ошибочного ввода, переполнения интов и прочую фигню.
Появляется NaN и дальше уже ничего нельзя напечатать.
Есть M контролов, которые меняют косвенно N других контролов.Есть M контролов, каждый из которых меняет N контролов, при чём каждый раз разные свойства этих N контролов? Даже здесь мне очень и очень сложно придумать решение за N*M. В конечном итоге, если у нас требования на такой долбанутый GUI, то можно написать одну функцию, полностью обновляющую представление.
Замечу, что в случае использования решения, которое пропагандирует DDD, в этом примере тоже мало хорошего. Каждый обработчик из M будет менять N свойств в доменной модели. Все эти свойства надо будет вычитывать во время рендеринга.
Насколько я понял, ты сделаешь какие-то функции onChangeFirst и onChangeSecond, в которых будешь вызывать функцию updateSum. Но тогда у тебя информация о связи между слагаемыми и суммой будет закодирована в двух местах: первый раз, явно, в функции вычисления суммы, и еще пару раз, менее явно, в функциях onChangeFirst и onChangeSecond.Я напишу псевдо код для твоего примера, чтобы было понятно. Вариантов масса, но я упоминал MVP Passive View, поэтому останусь в этих рамках. Код пишу в лоб, чтобы было максмимально понятно.
class Model
{
int first;
int second;
int Sum { get { return first + second; }
}
class Presenter
{
void OnChanged1(string val)
{
model.first = int.Parse(val);
view.SetSum(sum);
}
void OnChanged2(string val)
{
model.second = int.Parse(val);
view.SetSum(sum);
}
}
Это весьма слабый пример с точки зрения красоты, потому что в реальном проекте текстовые поля будут привязаны к данным. Поэтому вместо двух методов будет на самом деле один. Думаю, что в react нужно будет делать что-то похожее.
напишу псевдо кодНечестно. привел полный код. Приведи полный код на твоей любимой платформе.
Это вариация на тему publish/subscribe, к тому же выраженная в рамках убогого языка (вроде требуют какого-то УГ препроцессора).попрошу не обижать сигналы-слоты в qt
они там очень даже хорошо сделаны
Замечу, что в случае использования решения, которое пропагандирует DDD, в этом примере тоже мало хорошего. Каждый обработчик из M будет менять N свойств в доменной модели. Все эти свойства надо будет вычитывать во время рендеринга.Это дешево - на сервере вычитать еще раз все данные для всей страницы при каждом самом маленьком изменении. Важно, чтобы при этом страница у пользователя "не мигала".
Нечестно. привел полный код. Приведи полный код на твоей любимой платформе.Ага, щас запощу с .designer.cs
не надо думатьа в каком подходе и над чем надо думать?
Это дешево - на сервере вычитать еще раз все данные для всей страницы при каждом самом маленьком изменении.Это стоит столько же, сколько и при обычном stateful подходе с полным обновлением view.
- программисту не надо думать, какие контролы надо перерисовать после нажатия кнопки, а какие - не надо.Какие контролы надо перерисовывать - думать не надо. Зато ему надо думать, как писать рендеринг на основе большого количества свойств. То есть то же самое, что и в классическом подходе плюс куча неявных связей.
а в каком подходе и над чем надо думать?При классическом подходе (например, как у в примере) необходимо думать, что при изменении слагаемого необходимо дернуть перерисовку суммы. В примере для этого появился здоровый класс Presenter.
Ага, щас запощу с .designer.csДа, так будет честное сравнение.
Это стоит столько же, [..]Вот и выходит, что при подходе от Facebook-а получается кода в 1.5 раза меньше, а накладные расходы при исполнении те же самые.
[..]
То есть то же самое, что и в классическом подходе плюс куча неявных связей.
При классическом подходе (например, как у в примере) необходимо думать, что при изменении слагаемого необходимо дернуть перерисовку суммы. В примере для этого появился здоровый класс Presenter.А в другом подходе ты руками вычитал свойства модели, о чём тебе тоже надо было подумать. Например, по свойству isCritical нарисовать красную рамку, а по свойству text - содержимое контрола. Мало того, кроме неявных связей между компонентами, мы ещё и завязываем view на доменную модель.
Вот и выходит, что при подходе от Facebook-а получается кода в 1.5 раза меньше, а накладные расходы при исполнении те же самые.Нет, не выходит. Кода столько же. Расходы те же самые. Плюс сложности в поддержке. Я уже имел дело с таким набором компонентов - старый GUI в Unity очень близок к тому, что здесь описывается. Кроме неявных связей и коуплинга с моделью могу ещё добавить появление свойств, которые относятся только ко view. Например, текущий сфокусированный контрол, выбранный таб, и так далее. Эти свойства появляются в самых неожиданных местах и часто загромождают доменную модель.
Да, так будет честное сравнение.Если бы мы рассуждали об отрисовке контролов, то возможно, и то с огромной натяжкой. Но мы обсуждаем не это.
А в другом подходе ты руками вычитал свойства модели, о чём тебе тоже надо было подумать. Например, по свойству isCritical нарисовать красную рамку, а по свойству text - содержимое контрола. Мало того, кроме неявных связей между компонентами, мы ещё и завязываем view на доменную модель.1. Зачем руками? Вычитываться они будут тем же способом, что и при классике
2. Подход Facebook-а декомпозируется произвольным способом. Соедини View и Model через отдельный класс. Кто мешает?
При классическом подходе (например, как у в примере) необходимо думать, что при изменении слагаемого необходимо дернуть перерисовку суммы. В примере для этого появился здоровый класс Presenter.Самый прямолинейный вариант чем плох?
public Form1
{
arg1TextBox.TextChanged += delegate {
UpdateSum;
};
arg2TextBox.TextChanged += delegate {
UpdateSum;
};
UpdateSum;
}
void UpdateSum
{
string sum;
int arg1;
if (int.TryParse(arg1TextBox.Text, out arg1
{
int arg2;
if (int.TryParse(arg2TextBox.Text, out arg2
sum = (arg1 + arg2).ToString;
else
sum = нецелочисленныйАргумент;
}
else
sum = нецелочисленныйАргумент;
sumLabel.Text = sum;
}
const string нецелочисленныйАргумент = "нецелочисленный аргумент";
Кроме неявных связей и коуплинга с моделью могу ещё добавить появление свойств, которые относятся только ко view.Они будут храниться во ViewModel, а не в доменной модели.
Классический подход(указано какой код пишется для каждой формы без учета декомпозиции):
Screen <-- Model
Screen <-- ViewModel
Screen --> Model, UpdateAndRedrawControls(.., Ci, ..)
Screen --> ViewModel, UpdateAndRedrawControls(.., Cj, ..)
Facebook подход
Screen <-- Model
Screen <-- ViewModel
Screen --> Model
Screen --> ViewModel
1. Зачем руками? Вычитываться они будут тем же способом, что и при классикеТо есть в обычном подходе кода столько же, если не меньше. В моём случае это будет setText и setBorderColor; в другом случае это будет input value: appState.first border-color: getColor
2. Подход Facebook-а декомпозируется произвольным способом. Соедини View и Model через отдельный класс. Кто мешает?OK, то есть утверждение "появился громоздкий класс Presenter" снимается. А то ты написал, как будто этот класс появляется в этом паттерне только из-за того, что надо помнить про обновление представлений.
Самый прямолинейный вариант, чем плох?Пример слишком маленький(мало функционала чтобы заметить отличие.
Добавь, пожалуйста, для второго слагаемого замену текстового input-а на combobox(тег select) при нажатом checkbox-е и замену на input обратно при отжатом.
В combo-е значения 0, 1, 5, 10. Уже введенное значение копируется из input-а в combo и обратно. Если в combo-е такого значения нет, то оно временно добавляется.
Facebook подходFacebook подход другой:
Screen <-- Model, readPropertiesToRender
Screen <-- ViewModel, readPropertiesToRender
Screen --> Model, render
Screen --> ViewModel, render
Уже спать пора, код напишу завтра. Всё же любопытно, что там такого будет?
Классический подход(указано какой код пишется в каждом приложении без учета декомпозиции):
Screen <-- (sql-запрос) <-- DB
Screen <-- ViewModel
Screen --> DB, UpdateAndRedrawControls(.., Ci, ..)
Screen --> ViewModel, UpdateAndRedrawControls(.., Cj, ..)
Facebook подход
Screen <-- (graphql-запрос) <-- DB
Screen <-- ViewModel
Screen --> DB
Screen --> ViewModel
Мало кода, чтобы заметить отличие.Т.е. по принципу KISS мой вариант лучший?
Т.е. по принципу KISS мой вариант лучший?Переформулирую свою предыдущую ремарку. В примере слишком мало функционала, чтобы заметить отличие.
readPropertiesToRenderЯ вот о чём сейчас подумал. В классическом понимании у контролов десятки свойств: цвет рамки, цвет фона, цвет текста, фокус, шрифт текста, и так далее. Чтобы с этим справиться внутри render придётся написать функции рендеринга для всех стандартных контролов. А чтобы с этими функциями было удобно работать, придётся написать стандартные классы, которые будут хранить состояния этих контролов.
В самом деле, если у невалидных полей в какой-нибудь анкете надо рисовать рамку красным цветом, не будем же мы каждому контролу в render приписывать свой border-color: isControlXXXValidColor; а если нам потом придётся всем этим контролам ещё и поменять цвет текста, приписывать каждому text-color: isControlXXXValidTextColor
Переформулирую свою предыдущую ремарку. В примере слишком мало функционала, чтобы заметить отличие.Т.е. вы херово сформулировали проблему. Так сформулируйте нормально, в чем ваша проблема? А то глядишь и проблемы нет. Боритесь с ветряными мельницами.
Вот эпичное описание в 5 частях борьбы с ветряными мельницами:
http://habrahabr.ru/post/50830/
Я начал зевать, где-то на 3-ей части. Скукота.
В классическом понимании у контролов десятки свойств: цвет рамки, цвет фона, цвет текста, фокус, шрифт текста, и так далее.Это же всего два свойства - style и class. Зачем для них обертки делать?
если у невалидных полей в какой-нибудь анкете надо рисовать рамку красным цветом, не будем же мы каждому контролу в render приписывать свой border-color: isControlXXXValidColor;конечно не будем. Будет написано control.class += data.isInvalid? 'invalid' : null; один раз в обобщенном варианте View для поля.
Это же всего два свойства - style и class. Зачем для них обертки делать?
То есть для двух свойств, которые могут принимать по два значения, мы напишем 4 класса? Так, говоря об N^2... Или два, а потом будем объединять их через обёртки по отдельности?
конечно не будем. Будет написано control.class += data.isInvalid? 'invalid' : null; один раз в обобщенном варианте View для поля.То есть из stateless контролов мы сделали обычные классические stateful.
То есть из stateless контролов мы сделали обычные классические stateful.Это stateless код.
Это stateless код.Что такое data, и как там вычисляется isValid?
Что такое data, и как там вычисляется isValid?data - это ViewModel.
Два варианта:
1. Клиентская проверка. IsValid проверяет на какой-нибудь regexp.
2. Серверная проверка. IsValid делает запрос на сервер через graphql и проверяет что значение не конфликтует с БД.
1. Клиентская проверка. IsValid проверяет на какой-нибудь regexp.Если возраст от 18 до 85 лет, то ты будешь проверять это через regexp? А если для проверки нужны другие поля доменной модели?
Фактически, ты просто полностью вынес состояние контрола в другой stateful класс. И этот класс ты будешь обновлять точно так же, как ты обновлял бы содержимое классического контрола.
Фактически, ты просто полностью вынес состояние контрола в другой stateful класс. И этот класс ты будешь обновлять точно так же, как ты обновлял бы содержимое классического контрола.Не будет никакого statefull-а. Будет stateless (упрощенный вариант, оставляя только суть для понимания).
Кроме этой функции больше никакого кода для rendering-а или изменения отображения писаться не будет.
DomObject View(ViewModel viewModel)
{
var model = graph-ql(..);
var input = new Input;
input.class = 'age';
if (isInt(viewModel.age
{
var age = ToInt(viewModel.age);
if (model.IsPorno && age < 21)
{
input.class += 'invalid';
input.title = 'Таких маленьких на порно не пускают!";
}
}
else
{
input.class += 'invalid';
}
return input;
}
Не будет никакого statefull-а. Будет stateless (упрощенный вариант, оставляя только суть для понимания).Вариант настолько упрощённый, что пришёл к тому, что я говорил: для каждого текстового поля ты напишешь большой кусок специфичного кода: renderAge renderGender renderPassportNumber и так далее. Чтобы уйти от этого, нам нужно написать InputViewModel, которая фактически инкапсулирует состояние контрола.
Кроме этого в твоём примере остался коуплинг с доменной моделью.
Ты напираешь на то, что в преобразовании `Screen <-- DB` будет дублирующийся код. Хотя эта проблема, вообще, ортогональна тому, что предлагает facebook.
Ты напираешь на то, что в преобразовании `Screen <-- DB` будет дублирующийся код. Хотя эта проблема, вообще, ортогональна тому, что предлагает facebook.Я напираю на то, что в реальном проекте мы закончим написанием stateful моделей для всех контролов.
Я напираю на то, что в реальном проекте мы закончим написанием stateful моделей для всех контролов.Не будет этого. Отвлекись от проблемы дублирования кода и покажи в примере выше где там появляется stateful.
Не будет этого. Отвлекись от проблемы дублирования кода и покажи в примере выше где там появляется stateful.Проблема выше является примером, на котором я ничего показать не смогу. Ничего не бывает бесплатно, и в данном случае меня интересует, как ты предлагаешь рендерить Input. Либо ты пишешь огромное количество кода и огребаешь, когда нужно что-то доработать. Либо ты делаешь универсальную функицю, которая рендерит универсальную stateful модель. Я пока что не увидел у тебя никакого третьего варианта.
Я напираю на то, что в реальном проекте мы закончим написанием stateful моделей для всех контролов.Можешь сформулировать задачу? Я могу показать код (хочется поевангелировать за реакт в частности и за js вообще).
Можешь сформулировать задачу? Я могу показать код (хочется поевангелировать за реакт в частности и за js вообще).Задачи у меня в голове прямо сейчас нет. Я рассматриваю твой подход с точки зрения реализации большого многолетнего проекта. Очевидно, что рендерить каждый Input со всеми его свойствами, вычитывая их прямо в коде рендеринга конкретного GUI, в таком проекте глупо. Поэтому нам потребуется универсальная функция, которая сможет отрендерить все возможные Input. Сейчас мне интересно, как будет устроена эта функция.
Я пока что не увидел у тебя никакого третьего варианта.Третий вариант: делается универсальная функция, которая рендерит stateless-модель.
Третий вариант: делается универсальная функция, которая рендерит stateless-модель.Отлично. То есть каждый раз, когда эта модель меняет свойство isValid, мы создаём её заново и записываем в состояние какой-то другой stateful модели. Фактически, вместо вызова domain.firstInput.isValid = true, я должен буду сделать domain.firstInput = new InputModel(false, domain.isRequired, domain.firstValue)
Очевидно, что рендерить каждый Input со всеми его свойствами, вычитывая их прямо в коде рендеринга конкретного GUI, в таком проекте глупо.В большом проекте надо иметь UI гайдлайн и под него наделать стандартных компонентов. Можно сделать компонент MyInput со свойством valid. Этот компонент будет рендерить обычный React.DOM.input, но при valid=false будет ставить красную рамку и красный шрифт.
Самое главное, что у нас везде обычный джаваскрипт (или тайпскрипт код можно рефакторить и декомпозировать как хочешь. Нет выдуманного говна типа каких-то xml шаблонов и птичьих языков, которые надо писать внутри xml.
В большом проекте надо иметь UI гайдлайн и под него наделать стандартных компонентов. Можно сделать компонент MyInput со свойством valid. Этот компонент будет рендерить обычный React.DOM.input, но при valid=false будет ставить красную рамку и красный шрифт.Как только ты начинаешь добавлять свойства в компонент, он перестаёт идеологически отличаться от классических stateful компонентов. Об этом и речь.
Самое главное, что у нас везде обычный джаваскрипт (или тайпскрипт код можно рефакторить и декомпозировать как хочешь.Да, сам рендеринг сделан очень хорошо. Как раз для программистов.
То есть каждый раз, когда эта модель меняет свойство isValid, мы создаём её заново и записываем в состояние какой-то другой stateful моделиНет. На каждое изменение генеришь через stateless новый вариант страницы, а react.js прозрачно для пользователя заменяет старый вариант страницы на новый (делая diff и заменяя только те места, которые поменялись)
На каждое изменение генеришь через stateless новый вариант страницыТы сам-то понял, что написал?
Ты сам-то понял, что написал?Да. Там написано ровно то, что делает код.
Да. Там написано ровно то, что делает код.И так, я ввёл 1 в поле возраста. Я правильно понимаю, что в onValueChanged ты пишешь domainModel = new DomainModel?
И так, я ввёл 1 в поле возраста. Я правильно понимаю, что в onValueChanged ты пишешь domainModel = new DomainModel?В примерах выше не было ничего с названием domainModel.
В примерах выше не было ничего с названием domainModel.В примере выше у тебя доступ к доменной модели был просто через поле model. Но у тебя не было примера, который бы дал ответ на мой главный вопрос: как сделать универсальную функцию рендеринга всех Input.
как сделать универсальную функцию рендеринга всех Input.Также как и неуниверсальную.
DomObject UniversalView(ViewModel viewModel, GraphQlModel model, string field)
{
var input = new Input;
input.class = field;
if (isInt(viewModel[field]
{
input.class += 'invalid';
}
return input;
}
DomObject View(ViewModel viewModel)
{
var nonUniversalHandlers = new[]{new {field = 'age', graph-ql = graph-ql('is-porno' code = (viewModel, model, input)=>
{
if (model.IsPorno && viewModel['age'] < 21)
{
input.class += 'invalid';
input.title = 'Таких маленьких на порно не пускают!";
}
}
var fields = new[]{'age', 'weight', 'height', 'width'};
var model = graph-ql(graph-ql-merge(fields, nonUniversal.Select(_=>_.graph-ql;
var panel = new Panel;
foreach (var field in fields)
{
var input = UniversalView(viewModel, model, field);
var handler = nonUniversalHandlers.FirstOrDefault(_=>_.field == field);
if (handler != null)
handler.code(model, input);
panel.Add(input);
}
return panel;
}
isInt(viewModel[field])viewModel[field] - это состояние, которое может изменяться. Где и как ты собираешься это делать?
viewModel[field] - это состояние, которое может изменяться.Это временное состояние с длиной жизни в цикл перерисовки.
void redraw
{
var viewmodel = CollectViewModel(window);
var view = View(viewmodel);
react.redraw(window, view);
}
Это временное состояние с длиной жизни в цикл перерисовки.То есть на каждую нажатую клавишу, на каждое движение скролла я создаю целую иерархию классов только для отображения представления? Прекрасный способ получить тормозной сайт. Ровно то, о чём я писал выше: "Фактически, вместо вызова domain.firstInput.isValid = true, я должен буду сделать domain.firstInput = new InputModel(false, domain.isRequired, domain.firstValue)"
И потом, с какой стати временное состояние перестало быть состоянием?
То есть на каждую нажатую клавишу, на каждое движение скролла я создаю целую иерархию классов только для отображения представления?Что это у тебя за страница такая? На которой на столько много элементов ввода, что они: во-первых требуют иерархию классов, а во вторых - тормозят (получение их значений из контролов в объект - не укладываются в 10мс)?
Элементов ввода на странице обычно десяток. Максимум сотня.
И потом, с какой стати временное состояние перестало быть состоянием?Состояние имеет свойство изменчивости во времени. Временное состояние не обладает такой изменчивостью.
Можешь сформулировать задачу? Я могу показать код (хочется поевангелировать за реакт в частности и за js вообще).Опа, так у тебя самого нет хорошего примера, который показывает, почему надо использовать react.js, а не работать с DOM напрямую?
У хорошего евангелиста всегда есть такой пример.
view.SetSum(sum);а что такое sum? Это опечатка? Следует читать как model.Sum?
Что это у тебя за страница такая? На которой на столько много элементов ввода, что они: во-первых требуют иерархию классов, а во вторых - тормозят (получение их значений из контролов в объект - не укладываются в 10мс)?Задержки от 10мс заметны при наборе текста на клавиатуре. Я знаю это, потому что в своё время делал сложный контрол для ввода текста. Но я согласен, давай не будем обсуждать производительность.
Элементов ввода на странице обычно десяток. Максимум сотня.Элементов ввода - да, но там есть и другие компоненты. Мало того, эти компоненты могут добавляться динамически. А ещё это может быть spreadsheet типа Google Docs.
Состояние имеет свойство изменчивости во времени. Временное состояние не обладает такой изменчивостью.Во-первых, у тебя обычная доменная модель с состоянием. Тот факт, что ты используешь parameter object для передачи данных в какой-то фреймворк не делает твою систему stateless.
Во-вторых, ещё недавно ты предлагал:
И каким же образом эти mutable данные будут храниться в модели, которая создаётся 1 раз для рендеринга?Кроме неявных связей и коуплинга с моделью могу ещё добавить появление свойств, которые относятся только ко view.Они будут храниться во ViewModel, а не в доменной модели.
А ещё это может быть spreadsheet типа Google Docs.Компоненты с большим кол-ва ввода (редакторы текста, spreadsheet-ы) требуют отдельного подхода по множеству причин.
И каким же образом эти mutable данные будут храниться в модели, которая создаётся 1 раз для рендеринга?Mutable данные хранятся в data-аттрибутах контролов и собираются CollectViewModel.
Это делается для того, чтобы удаление контрола было атомарным и автоматически удалялось всё состояние, которое с ним связано.
Mutable данные хранятся в data-аттрибутах контролов и собираются CollectViewModel.То есть в итоге мы получаем обычные контролы с состоянием.
То есть в итоге мы получаем обычные контролы с состоянием.Не знаю как ответить на этот вопрос. Предложенное решение от facebook-а одновременно похоже на контролы с состоянием и не похоже: часть свойств решения совпадают, а часть - даёт новые выгоды.
ps
Предложи свой код для решения задачи
Добавь, пожалуйста, для второго слагаемого замену текстового input-а на combobox(тег select) при нажатом checkbox-е и замену на input обратно при отжатом.На примере будет проще показать в чём решения совпадают и какие проблемы решаются по новому.
В combo-е значения 0, 1, 5, 10. Уже введенное значение копируется из input-а в combo и обратно. Если в combo-е такого значения нет, то оно временно добавляется.
Если в combo-е такого значения нет, то оно временно добавляется.Уточни, что значит временно, значение добавляется, а потом исчезает? Когда исчезает?
Не знаю как ответить на этот вопрос. Предложенное решение от facebook-а одновременно похоже на контролы с состоянием и не похоже: часть свойств решения совпадают, а часть - даёт новые выгоды.Это не вопрос, а утверждение.
Предложи свой код для решения задачи
Зачем это теперь надо, если и ты, и DDD уже подтвердили наличие состояния у контролов. (Простые примеры вы всё равно будете писать без состояния, так что смысла нет.)
Можно сделать компонент MyInput со свойством valid. Этот компонент будет рендерить обычный React.DOM.input, но при valid=false будет ставить красную рамку и красный шрифт.
Именно про такие строчки Хельсберг пишет, что это ООП, которое создает проблемы:
model.first = int.Parse(val);
view.SetSum(model.Sum);
Does the object-oriented paradigm create problems?
Anders: You know, it depends on what you group under the object-oriented paradigm. Polymorphism and encapsulation and inheritance are as such not a problem, although functional languages typically have a different view of how you do polymorphism with their algebraic data types. Aside from that, I think the biggest problem typically with object-oriented programming is that people do their object-oriented programming in a very imperative manner where objects encapsulate mutable state and you call methods or send messages to objects that cause them to modify themselves unbeknownst to other people that are referencing these objects. Now you end up with side effects that surprise you that you can't analyze.
In that sense object-oriented programming is a problem, but you could do object-oriented programming with immutable objects. Then you wouldn't have these same problems. That's kind of what functional programming languages are doing, for example.
http://broadcast.oreilly.com/2009/04/an-interview-with-ander...
Зачем это теперь надо, если и ты, и DDD уже подтвердили наличие состояния у контролов.Чуда не бывает, как и контролов без состояния.
Соответственно, подход facebook-а не о том, как избавиться от состояния, а как организовать код так, чтобы раз и навсегда избавиться от проблем, которые несёт за собой наличие состояния.
Можно сделать компонент MyInputТакое решение не масштабируется.
Соответственно, подход facebook-а не о том, как избавиться от состояния, а как организовать код так, чтобы раз и навсегда избавиться от проблем, которые несёт за собой наличие состояния.Пока что мы договорились до того, что управление контролом размазано по двум местам. Для цвета рамки мы меняем свойство модели контрола, а для отображения данных пишем код в рендиринге. Если всё-таки инкапсулировать, то у нас появится полноценная модель контрола. От каких проблем мы избавляемся я так и не понял.
Такое решение не масштабируется.Это решение от DDD. Но я не понял, что значит "не масштабируется".
Именно про такие строчки Хельсберг пишет, что это ООП, которое создает проблемы:Ты собираешься привести пример immutable программы для ввода двух чисел и отображения их суммы?
Пока что мы договорились до того, что управление контролом размазано по двум местам.Не подтверждаю такой договоренности. Управление (в facebook подходе) всегда в одном месте.
Для цвета рамки мы меняем свойство модели контрола, а для отображения данных пишем код в рендиринге.и то, и другое делается в одном месте: рендеринге.
Не подтверждаю такой договоренности. Управление (в facebook подходе) всегда в одном месте.Только что ты говорил про наличие mutable view model для хранения состояния, которое нужно только для представления. Ты не согласен с тем, что это состояние изменяется в одном месте, а свойства для отображения используются в коде рендеринга? В твоём же примере будет как parameter object для рендиринга, так и модель с состоянием для контрола. Соответственно, данные parameter object ты получаешь из доменной модели, а mutable свойства меняешь в другом месте.
вроде требуют какого-то УГ препроцессораУже не обязательно.
view-состояние - это состояние, который не входит в model и которое необходимо для отображения UI. view-состояние включает в себя только такое состояние, которое не вычисляется на основе другого состояния.
Частные случаи view-состояния:
- позиция скроллбара
- позиция курсора
- выбранный элемент
- focus
- введенный текст
Model - это stateless реализация паттерна Репозиторий для доступа к model.
ViewModel - это stateless реализация паттерна Репозиторий для доступа к view-состоянию.
Само view-состояние хранится:
- или в контролах непосредственно (позиция скроллбара)
- или в data-аттрибутах (выбранный элемент (при реализации без checkbox-ов
Доступ к состоянию всегда делается через ViewModel, как на получение, так и на изменение.
Отображение (как первоначальное, так и перерисовка при изменении ViewModel и Model) делаются через связку React+Stateless Presenter.
Задача Stateless Presenter предоставить такой вид, который должен быть при текущем состоянии ViewModel и Model.
Задача React-а аккуратно изменить вид в браузере на предоставленный Presenter-ом.
Изменение ViewModel делается из множества мест: по кол-ву управляющих элементов.
Логика отображения на основе текущего состояния ViewModel+Model записана в одном месте: в Presenter-е.
Он заменяет классический сильно-связанный подход - Presenter(предыдущее отображение, ViewModel, Model) на слабо связанный: ReDraw(предыдущее отображение, новое отображение:Presenter(ViewMode, Model
vew-состояние включает в себя только такое состояние, которое не вычисляется на основе другого состояния.
Соответственно, цвет рамки и т.д. не входит во view-состояние.
Доступ к состоянию всегда делается через ViewModel, как на получение, так и на изменение.То есть ViewModel - это обычная модель с mutable состоянием. Никакой разницы с классическим подходом.
Остальную воду, которую ты налил, я пропускаю, потому что ты уехал в какие-то дебри, да ещё и запутываешь в определениях.
Соответственно, цвет рамки и т.д. не входит во view-состояние.Тогда снова встаёт вопрос, как ты напишешь универсальную функцию для отображения любого поля для ввода текста. У тебя цвет определяется разными isValidXXX для разных полей.
Своё текущее эмоциональное состояние могу передать только анекдотом
Привозит папа сына-дебила на море, привел его на пляж и объясняет:
П: Вот видишь, сынок, это море..
С: (тупо) Где?
П: Ну вот это плещется-это море...
С: (по прежнему тупо)Где?
П:(распаляясь)Ну вот мы сидим на таком желтом-это песок, а вон то синее-это море..
С:...Где?
П:(в бешенстве)Что ты идиот не понимаешь, вот это сухое, желтое-это песок, а вон то синее, плещется, мокрое-ЭТО МОРЕ!
С:(тупо)Где?
Папины нервы не выдерживают, он в крайней ярости хватает сына, и макает его головой в воду с криком "Вот море! Вот оно! Вот это море! Вот!" Сын захлебывается, кое-как вырывается и с трудом дыша спрашивает
-Папа, что это было?
-Море, %*"!
-ГДЕ?
Своё текущее эмоциональное состояние могу передать только анекдотомЭтим анекдотом ты передал моё эмоциональное состояние. Столько громких слов об immutable контролах, но стоило только немного копнуть в сторону реального проекта, как оказалось, что нам нужен универсальный способ рендеринга контролов. Вы предложиди два решения. Либо для каждого компонента сделать модель с mutable state, которая должна использоваться для рендеринга. Либо размазать state этой же модели на mutable и immutable части, которые находятся в разных классах; и работать с этими частями в разных местах кода.
Через декомпозицию Stateless Presenter-а на stateless SubPresenter-ы: обобщенные и частные.То есть для проверки имени у меня будет класс IsValidNamePresenter, а для проверки возраста IsValidAgePresenter?
Тогда снова встаёт вопрос, как ты напишешь универсальную функцию для отображения любого поля для ввода текста. У тебя цвет определяется разными isValidXXX для разных полей.Эта задача декомпозируется на stateless ValidPresenter и stateless Validator.
ValidPresenter отвечает за отображение валидности.
Validator отвечает за формирование статуса валидности данных.
ValidPresenter-а декомпозируется на независимые stateless ValidSubPresenter-ы. Каждый отвечает за свою часть отображения невалидности: один отображает рамочку, другой выводит ремарку, третий - подчеркивает текст.
Validator декомпозируется на stateless SubValidator-ы.
Задача Validator-а смержить состояния невалидности получаемое от SubValidator-ов
Столько громких слов об immutable контролах,Термин mutable/immutable в этом обсуждении ты первый употребил. Я вообще ничего подобного не упоминал.
Ты собираешься привести пример immutable программы для ввода двух чисел и отображения их суммы?вот в этих строчках
model.first = int.Parse(val);
view.SetSum(model.Sum);
у тебя mutable поле model.first. Без этого mutable поля можно обойтись. См. мой код выше.
у тебя mutable поле model.first. Без этого mutable поля можно обойтись. См. мой код выше.Твой код выше - это простой пример. Если ты точно так же пишешь код в больших коммерческих проектах, то у меня для тебя плохие новости.
То есть для проверки имени у меня будет класс IsValidNamePresenter, а для проверки возраста IsValidAgePresenter?Для случая универсальной проверки - это одна универсальная функция:
bool IsValid(string value)
{
return (value ? "").Trim.IsEmpty;
}
для различных проверок две разные функции
bool IsValid(int age)
{
return 0 <= age && age <= 150;
}
bool IsValid(string name)
{
return //сложная проверка валидности имени;
}
Термин mutable/immutable в этом обсуждении ты первый употребил. Я вообще ничего подобного не упоминал.Согласен, я в какой-то момент начал смешивать stateless и mutable. По итогам обсуждения нам нужны stateful mutable модели для универсального рендеринга компонентов в представлении.
Можно сделать компонент MyInputПочему не масштабируется?
Такое решение не масштабируется.
для различных проверок две разные функцииТо есть, говоря простым языком, ты создаёшь класс, в котором есть mutable state для использования только внутри view; и связь с доменной моделью, которую ты используешь для вычисления свойств типа isValid и value. Такая модель создаётся для всех контролов.
Больше того, в примере от DDD достаточно попросить сделать следующее изменение. Если я ввёл буквы или знак минус, то текст не должен заменяться на NaN, а должен оставаться ровно таким, как я его ввёл и поддерживать редактирование с помощью del/backspace. При этом во время редактирования поле суммы должно содержать предыдущее корректное значение.
Твой код выше - это простой пример. Если ты точно так же пишешь код в больших коммерческих проектах, то у меня для тебя плохие новости.Именно на больших проектах и проявляется вред от лишнего mutable состояния. На маленьких проектах человек еще может отслеживать состояние, на больших проектах требуется помощь со стороны машины. В твоем коде можно переставить строчки местами
view.SetSum(model.Sum);
model.first = int.Parse(val);
и компилятор нас ни о чем не предупредит.
Если бы mutable состояния не было, то такого нельзя было бы сделать. Более того, даже если в большом проекте эти строчки разъехались бы по разным методам, компилятор всё равно бы следил за правильным порядком.
Именно на больших проектах и проявляется вред от лишнего mutable состояния.Ещё раз повторю: если ты парсишь содержимое контролов каждый раз, когда тебе нужны данные, то у меня для тебя плохие новости. Хотя,
Ещё раз повторю: если ты парсишь содержимое контролов каждый раз, когда тебе нужны данные, то у меня для тебя плохие новости.При необходимости - это решается через паттерн Materialized View.
Больше того, в примере от DDD достаточно попросить сделать следующее изменение. Если я ввёл буквы или знак минус, то текст не должен заменяться на NaN, а должен оставаться ровно таким, как я его ввёл и поддерживать редактирование с помощью del/backspace. При этом во время редактирования поле суммы должно содержать предыдущее корректное значение.Я попозже напишу развернутый ответ
паттерн Materialized ViewЧто за паттерн?
я только в Oracle этот термин знаю.
я только в Oracle этот термин знаю.фактически - это оно и есть, только для произвольного применения.
Materialized View бывает двух видов: материализация делается при каждом изменении, или только после изменений при первом запросе.
при втором варианте делается следующее:
есть исходный код со следующими свойствами:
state - изменяемое состояние
f(state) - тяжелая функция, которая рассчитывается на основе state
f - вызывается много чаще, чем меняется state
при проблемах с производительностью делается следующее:
в state добавляется changeTick, который увеличивается при каждом изменении
f оборачивается в materialized:
materialized(f)
{
var cache = null;
var changeTick = -1;
return (state)=>
{
if (changeTick == state.changeTick)
return cache;
changeTick = state.changeTick;
cache = f(state);
return cache;
}
}
Я иногда применяю вот такую функцию:
public static Func<T, TResult> Lazy<T, TResult>(Func<T, TResult> func, Func<T, T, bool> equalFunc)
{
var valueIsSet = false;
var cachedArg = default (T);
var cachedValue = default (TResult);
return arg => {
if (valueIsSet == false || !equalFunc(arg, cachedArg
{
cachedValue = func(arg);
cachedArg = arg;
valueIsSet = true;
}
return cachedValue;
};
}
public static Func<string, TResult> Lazy<TResult>(Func<string, TResult> func)
{
return Lazy(func, (x, y) => x == y);
}
public static Func<int, TResult> Lazy<TResult>(Func<int, TResult> func)
{
return Lazy(func, (x, y) => x == y);
}
Я иногда применяю вот такую функцию:Lazy оптимальнее для ситуаций, когда state - маленький(функция сравнения дешевая). На больших state-ах(функция сравнения дорогая) оптимальнее вариант с changeTick-ом.
Lazy оптимальнее для ситуаций, когда state - маленький(функция сравнения дешевая). На больших state-ах(функция сравнения дорогая) оптимальнее вариант с changeTick-ом.Т.е. мы приходим к тому, что характеристика "большой комерческий проект"(с) не является точной. Надо детально рассматривать структуру состояния в проекте.
По моему опыту сложное состояние встречается достаточно редко.
Ещё раз повторю: если ты парсишь содержимое контролов каждый раз, когда тебе нужны данные, то у меня для тебя плохие новости. Хотя, OH, SHI....Вариант с оптимизацией парсинга:
readonly Func<string, Option<int>> arg1Parse = Func.Lazy(_ => _.ParseInt32;
readonly Func<string, Option<int>> arg2Parse = Func.Lazy(_ => _.ParseInt32;
public Form1
{
arg1TextBox.TextChanged += delegate {
UpdateSum;
};
arg2TextBox.TextChanged += delegate {
UpdateSum;
};
UpdateSum;
}
void UpdateSum
{
sumLabel.Text = (from arg1 in arg1Parse(arg1TextBox.Text)
from arg2 in arg2Parse(arg2TextBox.Text)
select (arg1 + arg2).ToString.ValueOr("нецелочисленный аргумент");
}
Но это, конечно, ненужная оптимизация.
Вариант с оптимизацией парсинга:... и с mutable состоянием.
... и с mutable состоянием.Ты так это говоришь, как будто всякий mutable - это проблема.
Mutable бывает разный - одни создают проблем, другие - нет.
Mutable бывает разный - одни создают проблем, другие - нет.Ага, давай ещё вводить градации mutable состояний и плодить больше обёрточного кода. Каждый int в отдельный класс с десятком свойств. Не удивительно, что потом появляются всякие высеры на тему убогости ООП. На деле mutable создаёт намного меньше проблем, чем лапша в control flow.
Ага, давай ещё вводить градации mutable состояний и плодить больше обёрточного кода. Каждый int в отдельный класс с десятком свойств.Как из первого следует второе?
Как из первого следует второе?Так, что Шуреск уже завернул пару интов в две обёртки, у которых суммарно около 16 полей. Я уже молчу о том, что он создал коуплинг между моделью и представлением и нагородил кода, который тяжело прочитать.
Так, что Шуреск уже завернул пару интов в две обёртки, у которых суммарно около 16 полей. Я уже молчу о том, что он создал коуплинг между моделью и представлением и нагородил кода, который тяжело прочитать.Приведи свой не псевдо код, а полностью рабочий без эксепшенов код. Мой код полностью рабочий. Не вижу смысла сравнивать нерабочий псевдо код с полстью рабочим вариантом. Посмотрим у кого больше оберток.
Так, что Шуреск уже завернул пару интов в две обёртки, у которых суммарно около 16 полей.Абсурдность подкрепляется тем, что вот тут наезд на перепутанный порядок следования строк, где компилятор нас не предупредит:
view.SetSum(model.Sum);
model.first = int.Parse(val);
А вот тут якобы спутать arg1Parse и arg2Parse никак не получится. Видимо, компилятор нас предупредит:
sumLabel.Text = (from arg1 in arg1Parse(arg1TextBox.Text)
from arg2 in arg2Parse(arg2TextBox.Text)
select (arg1 + arg2).ToString.ValueOr("нецелочисленный аргумент");
Приведи свой не псевдо код, а полностью рабочий без эксепшенов код.Сначала ты приведи свой не псевдо код, а полностью рабочий, который можно скомпилировать, покрытый юнит тестами код. До этого вообще не вижу смысла обсуждать с тобой проекты, отличные от твоих разовых интеграторских решений, которые все уже давно на помойке.
Абсурдность подкрепляется тем, что вот тут наезд на перепутанный порядок следования строк, где компилятор нас не предупредит:view.SetSum(model.Sum);model.first = int.Parse(val);А вот тут якобы спутать arg1Parse и arg2Parse никак не получится. Видимо, компилятор нас предупредит:Написать код первый раз не проблема. Один раз оттестировать и всё. Главная проблема при разработке ПО это внесение изменений так, чтобы ничего не поломать. Строки легко можно переставить при внесении изменений в уже работающий код. Ты же приводишь ошибку, которая отлавливается при первом тестировании и потом не возникает.
Сначала ты приведи свой не псевдо код, а полностью рабочий, который можно скомпилировать, покрытый юнит тестами код.Он уже в этом треде.
интеграторских решений,
никогда не работал в интеграторе
Главная проблема при разработке ПО это внесение изменений так, чтобы ничего не поломать.И для этого придумали термин "читаемость кода". Добавить третье слагаемое без длительных мысленных усилий в твоём случае не представляется возможным.
Ты же приводишь ошибку, которая отлавливается при первом тестировании и потом не возникает.Ты привёл точно такую же ошибку. Мало того, на эту отловленную ошибку нужно будет написать автоматический тест, который в твоём случае потребует ещё больших мысленных усилий.
Он уже в этом треде.Не вижу в этом треде кода, который можно скомпилировать и который покрыт юнит тестами.
ты тоже приводи без юнит тестов
ты тоже приводи без юнит тестовНе вижу смысла обсуждать подход, который не пригоден для сколько-нибудь серьёзной разработки. И код, который не пригоден для серьёзных коммерческих проектов.
Мало того, на эту отловленную ошибку нужно будет написать автоматический тест, который в твоём случае потребует ещё больших мысленных усилий.Вывести сумму на экран до того как сложить аргументы, в моем коде нельзя, такой бред компилятор пропустит только в ручной mutable модели. Т.е. такой ошибки просто не возникает.
Не вижу смысла обсуждать подход, который не пригоден для сколько-нибудь серьёзной разработки. И код, который не пригоден для серьёзных коммерческих проектов.Если бы ты еще аргументы написал, почему, на твой взгляд, он не пригоден.
Вывести сумму на экран до того как сложить аргументы, в моем коде нельзя, такой бред компилятор пропустит только в ручной mutable модели. Т.е. такой ошибки просто не возникает.Зато возникают другие ошибки, когда по какой-то причине число из одного поля всё время умножается на два вместо сложения с другим полем. Такой бред компилятор пропустит только в слишком сложной структуре кода, которую может прочитать машина, но не может прочитать человек.
Если бы ты еще аргументы написал, почему, на твой взгляд, он не пригоден.Это же очевидно. На всей Земле есть только один крутой разработчик. Он пишет на forumlocal под ником . Все остальные не умеют работать с кодом и делают там ошибки, количество и регрессия которых сокращается с помощью автоматических тестов.
Как это мимо тебя проходит не знаю, но о сокращении mutable состояния говорят многие.
Слушал какой-то подкаст, там чуваки даже из java признавались, что мол да java (еще до 8) не особо балует кратким синтаксисом для таких вещей, но когда они стали применять практику экономить mutable состояние, то жить стало намного проще.
Куча народу об этом говорит, вон тот же Хельсбер, я уже приводил ссылку.
Как это мимо тебя проходит не знаю, но о сокращении mutable состояния говорят многие.Мимо тебя совершенно точно проходит, что ещё больше говорят о повышении читаемости кода, автоматическом тестировании и сокращении boilerplate. Кстати, ты всё время сливаешься в спорах про языки с динамической типизацией, т.к. скудоумие не даёт тебе понять, что для некоторых групп разработчиков читаемость более приоритетна, чем все плюсы статической типизации вместе взятые.
arg1TextBox.TextChanged += delegate {зачем пишешь слово 'delegate'?
И да, слово я не пише, всё пишет решарпер.
А вот тут якобы спутать arg1Parse и arg2Parse никак не получится.Я тут подумал над этой проблемой и решил посмотреть, а как с этой проблемой в твоем коде?
Оказалось, что там еще хуже, в два раза хуже. Легко можно написать так
void OnChanged1(string val)
{
model.second = int.Parse(val);
view.SetSum(sum);
}
А еще у тебя где-то вызов метода OnChanged1, там тоже можно перепутать 1 и 2. Т.е. у тебя в два раза больше мест, где можно неверно написать 2 вместо 1 и наоборот.
Таким образом, одно место, где можно легко сделать ошибку указал я. Второе место указал ты, и там у тебя тоже больше ошибок. Получаем счет 2:0 в мою пользу. Можешь дальше искать такие места в моем коде. Есть подозрение, что у тебя будет всё либо так же либо хуже. От того ты и не приводишь полный код.
сокращении boilerplateПриведи полный код. Посмотрим сколько строчек (и сколько boilerplate) у мега архитектора занимает сложение двух чисел. Напомню мой кране прозрачный, хорошо читаемый, без boilerplate .
Получаем счет 2:0 в мою пользу.А теперь учитываем юнит тесты и читаемость и получаем +inf:2 в мою пользу. Самое смешное, что ты точно так же сливался в прошлом.
Сливаешься ты.
Приведи код и юнит тесты.Я не вижу в этом смысла, потому что не вижу смысла рассматривать твои кошмарные высеры в конктексте слова "код".
Ты о чем? самый прямолинейный, его поймет даже тот, кто вчера начал программировать.
Ты о чем?
Где ты увидел объекты в этом ?
Больше того, в примере от DDD достаточно попросить сделать следующее изменение. Если я ввёл буквы или знак минус, то текст не должен заменяться на NaN, а должен оставаться ровно таким, как я его ввёл и поддерживать редактирование с помощью del/backspace. При этом во время редактирования поле суммы должно содержать предыдущее корректное значение.Код
КодВ дискуссии с речь шла о создании универсального способа рендеринга для контрола.
Впрочем, в своём примере ты только подтвердил всё, о чём я писал выше. Тебе пришлось вынести редактируемое состояние контрола в state доменной модели, потому что типы данных доменной модели и модели контрола различаются. ( предлагал для этого использовать View Model.) А чтобы отобразить последнее правильное значение ты добавил mutable state с помощью lastGoodValue.
При этом во время редактирования поле суммы должно содержать предыдущее корректное значение.
Последние корректное значение не всегда существует. Первый раз форму логично отображать с пустыми текстовыми полями. По бизнес правилам поле может быть обязательным для заполнение пользователем. Таким образом, последнего корректного значения нет, что отображать в условии не сказано.
Последние корректное значение не всегда существует. Первый раз форму логично отображать с пустыми текстовыми полями. По бизнес правилам поле может быть обязательным для заполнение пользователем. Таким образом, последнего корректного значения нет, что отображать в условии не сказано.Сохраню, как образец хода мыслей, который по своей абсурдности не уступает женской логике.
Пользователь еще ничего не вводил, поэтому предыдущего значения еще нет.
Сохраню, как образец хода мыслей, который по своей абсурдности не уступает женской логике.Ололол, Шуреск расчехлил бота, чтобы ставить себе плюсики и минусовать неугодных. Сохраню как пример глубокой детской травмы и сублимации.
Простой пример с двумя полями оказался очень показательным. Реализация с лишним mutable состоянием начала просачиваться в видимые пользователю эффекты. Сначала появился термин "последнее корректное значение". Зачем появился? Ну да ладно, пока ничего плохого нет. Идем дальше. Оказалось, когда форма только открывается предыдущего значения нет. Надо дополнять правило. Дополняем: когда форма только открылась с пустыми слагаемыми на месте суммы отображаем 0 (или пусто). Идем дальше. Пользователь ввел целочисленные значения, а потом всё стер. На экране те же слагаемые, что и при открытии формы, а сумма другая. Не логично. Думаем, что же делать. Ага, надо в модели сделать поля nullable "int?". Пустые текстовые поля переводятся в null в полях модели. Получилось, что логика пользовательского интерфейса поменяла тип полей в модели. А декларировалось же, что модель вся такая из себя независимая.
Т.е. по существу сказать уже нечего? Пошли только обсуждения моей личности как часто ты это делаешь.Ты используешь бота, чтобы ставить себе плюсики. О чём с тобой теперь вообще можно говорить? Я могу только сказать тебе спасибо, за то, что поднял мне настроение на весь день.
Я могу только сказать тебе спасибо, за то, что поднял мне настроение на весь день.Блин, завидую. Так подниматься настроением от таких унылых вещей.
Ты используешь бота, чтобы ставить себе плюсики. О чём с тобой теперь вообще можно говорить? Я могу только сказать тебе спасибо, за то, что поднял мне настроение на весь день.Очень рад, что являюсь главным героям твоих фантазий.
Очень рад, что являюсь главным героям твоих фантазий.Вытри слёзки и поставь себе побольше плюсиков своим ботом. Чем больше поставишь, тем больше ты прав.
Ты так комплексуешь забавно. Пора уже осваивать понятие функции. Это не сложно, ты справишься.
Ты так комплексуешь забавно.Конечно, конечно, я полон комплексов. Ты главное напомни своему воображаемому другу, чтобы он не забыл принять таблетки.
Пример слишком маленький(мало функционала чтобы заметить отличие. Добавь, пожалуйста, для второго слагаемого замену текстового input-а на combobox(тег select) при нажатом checkbox-е и замену на input обратно при отжатом.В combo-е значения 0, 1, 5, 10. Уже введенное значение копируется из input-а в combo и обратно. Если в combo-е такого значения нет, то оно временно добавляется.Думаю, что в ближайшее время напишу код для этого примера. Пока ответь, что значит "временно", когда оно исчезает, когда выбрали другое значение?
Я не вижу в этом смысла, потому что не вижу смысла рассматривать твои кошмарные высеры в конктексте слова "код".Как ты выражаешься "мои высеры" вовсе не мои, более того, они написаны даже wikipedia.
Думаю, что в ближайшее время напишу код для этого примера. Пока ответь, что значит "временно", когда оно исчезает, когда выбрали другое значение?Если пользователь меняет в текстовом инпуте 13, а потом переключается на комбобокс, то в комбобоксе можно выбирать из значений 0, 1, 5, 10, 13
Это понятно. Требуется уточнить смысл слова "временно". Было 13. Потом пользователь выбрал 5. 13 должно исчезнуть из списка или остаться?
Потом пользователь выбрал 5. 13 должно исчезнуть из списка или остаться?Как я понял, остается там пока пользователь не переключится обратно с комбобокса в текстинпут.
13 должно исчезнуть из списка или остаться?Исчезнуть. Так код будет интереснее.
Спасибо за ответы, сегодня не получается найти время написать. Хотя очень хочется.
Выкладываю файл с паролем. Пароль опубликую, когда кто-нибудь еще реализует этот же функционал и выложит на форум. Иначе получается как с Майком, он высказывает претензии к коду, смотришь на его код, а там аналогичная проблема, причем, проблемных мест в два раза больше. Поэтому для сравнения надо иметь больше одного варианта работающего кода.
написал, модифицировав свой предыдущий код.
Я сделал компонент MegaInput, который рендерится как комбобокс или как текстинпут, в зависимости от чекбокса, и заменил input на MegaInput. Больше ничего не менял
Я Я сделал компонент MegaInput, который рендерится как комбобокс или как текстинпут, в зависимости от чекбокса, и заменил input на MegaInput. Больше ничего не менял
Код:
public partial class Form1 : Form
{
public Form1
{
InitializeComponent;
firstArgTextBox.TextChanged += delegate { RefreshResult; };
secondArgTextBox.TextChanged += delegate { RefreshResult; };
secondArgCheckBox.CheckedChanged += delegate {
RefreshSecondArgWidget;
RefreshResult;
};
secondArgComboBox.SelectedIndexChanged += delegate {
if (SelectedComboBoxItem == null || !SelectedComboBoxItem.Temporary)
RemoveTemporaryItem;
RefreshResult;
};
foreach (var item in new[] {0, 1, 5, 10})
secondArgComboBox.Items.Add(new ComboBoxItem(item.ToString false;
RefreshSecondArgWidget;
RefreshResult;
}
private void RefreshSecondArgWidget
{
secondArgTextBox.Visible = !secondArgCheckBox.Checked;
secondArgComboBox.Visible = secondArgCheckBox.Checked;
if (secondArgCheckBox.Checked)
{
RemoveTemporaryItem;
if (!string.IsNullOrEmpty(secondArgTextBox.Text
{
var item = secondArgComboBox.Items.Cast<ComboBoxItem>.SingleOrDefault(_ => _.Text == secondArgTextBox.Text);
if (item == null)
{
item = new ComboBoxItem(secondArgTextBox.Text, true);
secondArgComboBox.Items.Add(item);
}
secondArgComboBox.SelectedItem = item;
}
else
secondArgComboBox.SelectedItem = null;
}
else
secondArgTextBox.Text = SelectedComboBoxItem == null ? "" : SelectedComboBoxItem.Text;
}
private ComboBoxItem SelectedComboBoxItem
{
get { return (ComboBoxItem) secondArgComboBox.SelectedItem; }
}
private void RemoveTemporaryItem
{
var temporaryItem = secondArgComboBox.Items.Cast<ComboBoxItem>.SingleOrDefault(_ => _.Temporary);
if (temporaryItem != null) secondArgComboBox.Items.Remove(temporaryItem);
}
private void RefreshResult
{
sumLabel.Text = (from firstArg in firstArgTextBox.Text.ParseInt32
from secondArg in (secondArgCheckBox.Checked
? SelectedComboBoxItem == null ? new Option<string> : SelectedComboBoxItem.Text
: secondArgTextBox.Text).SelectMany(_ => _.ParseInt32
select (firstArg + secondArg).ToString.ValueOr("нецелочисленный аргумент");
}
private class ComboBoxItem
{
public readonly string Text;
public readonly bool Temporary;
public ComboBoxItem(string text, bool temporary)
{
Text = text;
Temporary = temporary;
}
public override string ToString
{
return Text;
}
}
}
Пока твой код посмотрел не полностью (кофескрипт читаю с трудом). Я так понимаю lift это такая своеобразная работа с maybe монадой?
Пока твой код посмотрел не полностью (кофескрипт читаю с трудом). Я так понимаю lift это такая своеобразная работа с maybe монадой?Вроде стандартная функция, тут хорошее объяснение.
варианте состояние было с целыми числами, в последнем варианте состояние со строками. Прокомментируй это изменение.
У тебя в первом Пример слишком маленький(мало функционала чтобы заметить отличие.
Реализовал я твой пример.
Небольшое преимущество реакта есть, но оно очень небольшое. Можно пример чтоб прям ух какая большая разница была? Если, как говорит , там квадратичная зависимость против линейной на реакте, то, наверное, не сложно придумать так, чтобы на реакте потребовалось 10 условных единиц усилий, а мне потребовалось 100 усилий.
Повторюсь, реакт оборачивает стандартный DOM, поэтому надо понимать насколько оправдана такая обертка.
Небольшое преимущество реакта есть, но оно очень небольшое.Ты используешь более оптимальный подход, чем классический. Отсюда и не очень большая разница с react-ом.
По классификации ниже ты используешь второй подход(слабо-связанный).
Классический сильно-связанный подход - Presenter(предыдущее отображение, ViewModel, ModelОсновное отличие твоего подхода от React-а: ты делаешь в ручную diff и его применение, а React делает это автоматически.
слабо связанный:
- новое отображение = Presenter(ViewMode, Model)
- diff = Diff(текущее отображение, новое отображение)
- Apply(предыдущее отображение, diff)
Соответственно, в react-е кода будет меньше только на эту разницу
Я тут подумал над этой проблемой и решил посмотреть, а как с этой проблемой в твоем коде?я тут еще немного подумал над этим, оптимизацию парсинга можно сделать с помощью такого общего метода:
Оказалось, что там еще хуже, в два раза хуже. Легко можно написать так
public static class TextBoxExtensions
{
public static Option<int> GetInt32(this TextBox it)
{
const string key = "ParseInt32";
var data = it.GetData(key);
Func<string, Option<int>> parse;
if (!data.HasValue)
{
parse = Func.Lazy(_ => _.ParseInt32;
it.SetData(key, parse);
}
else
parse = (Func<string, Option<int>>) data.Value;
return parse(it.Text);
}
}
public static class ControlExtensions
{
public static Option<object> GetData(this Control it, string key)
{
object value;
if (GetDictionary(it).TryGetValue(key, out value return value;
return new Option<object>
}
public static void SetData(this Control it, string key, object value)
{
GetDictionary(it)[key] = value;
}
private static Dictionary<string, object> GetDictionary(Control it)
{
if (it.Tag == null)
{
var dictionary = new Dictionary<string, object>
it.Tag = dictionary;
return dictionary;
}
else
return (Dictionary<string, object>) it.Tag;
}
}
Оптимизированный код выглядит следующим образом:
public Form1
{
firstArgTextBox.TextChanged += delegate { RefreshSum; };
secondArgTextBox.TextChanged += delegate { RefreshSum; };
RefreshSum;
}
void RefreshSum
{
sumLabel.Text = (from firstArg in firstArgTextBox.GetInt32
from secondArg in secondArgTextBox.GetInt32
select (firstArg + secondArg).ToString.ValueOr("нецелочисленный аргумент");
}
Теперь first и second не перепутаешь. А теперь ты попробуй у себя устранить проблему, на которую ты сам указал.
Добавить третье слагаемое без длительных мысленных усилий в твоём случае не представляется возможным.В чем проблема?
public Form1
{
firstArgTextBox.TextChanged += delegate { RefreshSum; };
secondArgTextBox.TextChanged += delegate { RefreshSum; };
thirdArgTextBox.TextChanged += delegate { RefreshSum; };
RefreshSum;
}
void RefreshSum
{
sumLabel.Text = (from firstArg in firstArgTextBox.GetInt32
from secondArg in secondArgTextBox.GetInt32
from thirdArg in thirdArgTextBox.GetInt32
select (firstArg + secondArg + thirdArg).ToString.ValueOr("нецелочисленный аргумент");
}
В чем проблема?Если ты сам не понимаешь, то боюсь, что тебе это уже никто не объяснит. Так и придётся мучаться до смерти твоему гению среди серости и обыденности.
Если ты сам не понимаешь, то боюсь, что тебе это уже никто не объяснит. Так и придётся мучаться до смерти твоему гению среди серости и обыденности.и работают с maybe монадой аналогичным образом.
и работают с maybe монадой аналогичным образом.Жалкие попытки повторить за истиным творцом!
sumLabel.Text = (from firstArg in firstArgTextBox.GetInt32Имхо, вот так будет понятнее
from secondArg in secondArgTextBox.GetInt32
from thirdArg in thirdArgTextBox.GetInt32
select (firstArg + secondArg + thirdArg).ToString.ValueOr("нецелочисленный аргумент");
Func<int, int> Add = (i,j)=>i+j;
sumLabel.Text = firstArgTextBox.GetInt32
.F(Add, secondArgTextBox.GetInt32
.F(Add, thirdArgTextBox.GetInt32
.OrDefault("нецелочисленный аргумент");
static class OptionHlp
{
public static Option<TTarget> F<TArg1, TArg2, TTarget>(this Option<TArg1> value1, Func<TArg1, TArg2, TTarget> f, Option<TArg2> value2)
{
if (value1.HasValue && value2.HasValue)
return f(value1, value2);
return Option.Empty;
}
}
Имхо, вот так будет понятнееа если надо
2*firstArg + decimal.Round(secondArg*thirdArg/100, MidpointRounding.AwayFromZero)
2.ToOption.F(Mul, firstArg)
.F(Add, secondArg.F(Mul, thirtArg).F(Div, 100).F(decimal.Round, MidpointRounding.AwayFromZero
ps
Еще есть вот такой вариант:
var res = OptionProcess=>2*firstArgTextBox.GetInt32.Mark
+ decimal.Round(secondArgTextBox.GetInt32.Mark*thirdArgTextBox.GetInt32.Mark/100, MidpointRounding.AwayFromZero;
static class OptionHlp
{
//fake-овая функция. Маркирует выражения для функции OptionEvaluate
static T Mark<T>(this Option<T> option)
{
return option.Value;
}
static Func<Option<T>> OptionProcess<T>(Expression<Func<T>> f)
{
//перекомпиливает функцию f. Ищет подвыражения помеченные Mark и выносит их в качестве аргументов.
//Вышеприведенное выражение будет перетранслировано в выражение вида:
return =>
{
var arg1 = firstArgTextBox.GetInt32;
var arg2 = secondArgTextBox.GetInt32;
var arg3 = thirtArgTextBox.GetInt32;
if(!arg1.HasValue || !arg2.HasValue || !arg3.HasValue)
return Option.Empty;
return 2*arg1 + decimal.Round(arg2*arg3/100, MidpointRounding.AwayFromZero);
}
}
}
Monadic comprehension syntax это часть языка C#. Человек пишет на C#, поэтому естественно, что он должен знать его синтаксис. В твоем варианте надо вникать в эти дополнительные специализированные библиотечные функции.
Давай посмотрим на это с точки зрения нового человека на проекте. Или с точки зрения человека, который вернулся к этому коду через год. Ты используешь более оптимальный подход, чем классический.Когда говорят "классическая литература" или "классическая музыка" можно легко привести ссылки на конкретные произведения. Ты можешь дать ссылки на то, что ты называешь "классический"?
Monadic comprehension syntax это часть языка C#.Имхо, в Code style стоит прописывать явный запрет на его использование. Данный синтаксис - мертворожденный костыль.И сделан был только для того, чтобы не испугать тех, кто переходил с написания sql-запросов на linq-запросы.
>> Monadic comprehension syntax
Если он задумывался как Monadic, то нафига в него затащили sql-ный синтаксис, который там ни к селу, ни к городу?
Ты можешь дать ссылки на то, что ты называешь "классический"?MV, MVC, MVVM, MVP
Где можно прочесть их классическое описание?
Где можно прочесть их классическое описание?вики, msdn, Fowler
Разных архитектур много, более того это список открытый:
Conclusion
The Model-View-Controller, Model-View-Presenter, and Presentation-Abstraction-Control patterns are similar in many ways, but have each evolved to address slightly different concerns. By becoming familiar with these patterns and other related architecture models, developers and architects will be better equipped in choosing an appropriate solution in their next design endeavor, or possibly in the creation of future architecture design patterns.
http://aspiringcraftsman.com/2007/08/25/interactive-applicat...
Разумные разработчики используют whatever works for you.
Таким образом, классики нет, есть множество конкурирующий подходов. Применение в данном случае слова "классический" это чистой воды софистика.
И сделан был только для того, чтобы не испугать тех, кто переходил с написания sql-запросов на linq-запросы.Причина глубже. Стандартный монадный синтаксис такой:
m1.bing(x1 => m2.bind(x2 => m3.bind(x3 => someFunction(x1, x2, x3
Но выяснилось, что вывод типов вместе с перегрузкой методов дают экспоненциальную сложность для компилятора.
Кто-то из команды C# догадался, что это можно переписать вот так:
m1.bing(x1 => m2.bind(x2 => new {x1, x2}.bind(temp => m3.bind(x3 => someFunction(temp.x1, temp.x2, x3
Количество вложенных лямбд теперь не растет.
Для вложенных лямб ввели специальный синтаксис без лишних скобочек, и который компилятором переписывается во второй вариант.
Если он задумывался как Monadic, то нафига в него затащили sql-ный синтаксис, который там ни к селу, ни к городу?
Если не обращать внимание на преемственность с другими языками, то какая разница "bind" или "SelectMany"? В Java назвали flatMap. К названиям можно привыкнуть, это не big deal.
вики, msdn, FowlerВот вспомнил у Фаулера есть то, что я использую Flow Synchronization.
То есть у меня тоже классика
Flow SynchronizationСобственно в control flow и вся суть. Язык с compile time проверками и с развитым инструментарием по обработке исходного кода позволяет без проблем писать и развивать control flow, который не становиться сложным при росте количества UI элементов.
После освоения языка в достаточной мере многие задачи распадаются на мелкие задачи естественным образом.
... и с mutable состоянием.Да, с mutable состоянием, но это состояние полностью инкапсулировано в функции Lazy. Внешний интерфейс совпадает с интерфейсом без изменяемого стояния.
В твоем коде такой инкапсуляции нет. У тебя выставлены наружу и гетер model.Sum и сетеры model.first, model.second. Программисту нужно помнить, что происходит в гетере model.Sum, и на основании этого знания вызывать model.Sum только после сетеров model.first, model.second. В коде без изменяемого состояния эти знания представлены в формальной структуре кода.
Да, с mutable состоянием, но это состояние полностью инкапсулировано в функции Lazy. Внешний интерфейс совпадает с интерфейсом без изменяемого стояния.Каждый байт, выполняемый процессором, должен превратиться в отдельный класс. Такая задача подсилу только во истину великим.
Ты осилишь оценить, при каких условиях потери, о которых ты говоришь, станут заметными?
Количество вложенных лямбд теперь не растет.Растёт кол-во пересоздаваемых объектов, что убивает производительность и напрямую, и косвенно (через повышенную нагрузку gc)
К названиям можно привыкнуть, это не big deal.Cам синтаксис чужеродный для C# и в этом проблема. Это всё равно, что brainfuck затащить в C#, писать и на нём, конечно, можно, но это убъект производительность программиста из-за множества переключений контекста.
нужны цифры, чтобы понять существенно это или нет. Подозреваю, что в Москве не пишут на .NET проектов, в которых это сколько-нибудь существенно.
Cам синтаксис чужеродный для C# и в этом проблема. Это всё равно, что brainfuck затащить в C#, писать и на нём, конечно, можно, но это убъект производительность программиста из-за множества переключений контекста.Не говори ерунды, LINQ пользуются, программисты знают этот синтаксис.
Повторюсь, реакт оборачивает стандартный DOMНа стандартном DOM далеко не уедешь. Нужна абстракция виджета и возможность строить дерево виджетов. Нужны аналоги реактовских колбэков componentDidMount и componentWillUnmount, иначе невозможно создавать виджеты, которым надо, например, навесить какой-то глобальный обработчик событий и убрать его при удалении.
Затем, в твоем коде видны зачатки проблем с утечками памяти. Есть виджет A, на события в нем подписан виджет B. Когда виджет B удаляется, то ссылка на него остается висеть в виджете A. Понимание этой проблемы на порядок сложнее, чем все API реакта вместе взятое.
У тебя в первом варианте состояние было с целыми числами, в последнем варианте состояние со строками. Прокомментируй это изменение.Был еще второй вариант, в котором я добавил валидацию.
На стандартном DOM далеко не уедешь. Нужна абстракция виджета и возможность строить дерево виджетов. Нужны аналоги реактовских колбэков componentDidMount и componentWillUnmount, иначе невозможно создавать виджеты, которым надо, например, навесить какой-то глобальный обработчик событий и убрать его при удалении.У меня нет утечек памяти. Описанный сценарий к моему коду не имеет ни малейшего отношения. Ни каких зачатков нет.
Затем, в твоем коде видны зачатки проблем с утечками памяти. Есть виджет A, на события в нем подписан виджет B. Когда виджет B удаляется, то ссылка на него остается висеть в виджете A. Понимание этой проблемы на порядок сложнее, чем все API реакта вместе взятое.
На стандартном DOM далеко не уедешь. Нужна абстракция виджета и возможность строить дерево виджетов. Нужны аналоги реактовских колбэков componentDidMount и componentWillUnmount, иначе невозможно создавать виджеты, которым надо, например, навесить какой-то глобальный обработчик событий и убрать его при удалении.Отличная логика. Нам нужна абстракция виджета. Если виджеты подписываются на события друг друга, то получается утечка памяти. Нужно решение этой проблемы: componentDidMount, componentWillUnmount.
Затем, в твоем коде видны зачатки проблем с утечками памяти. Есть виджет A, на события в нем подписан виджет B. Когда виджет B удаляется, то ссылка на него остается висеть в виджете A. Понимание этой проблемы на порядок сложнее, чем все API реакта вместе взятое.
В начале проблем не было. Ввели абстракцию. Получили проблему. Потребовалось решение проблемы. А может дырявая абстракция? И может она нам не очень нужна?
Самое время напомнить:
... выводить нужный уровень абстракции по требованию
Благодаря постепенному выводу абстракций, если в исходном коде не было проблем (например, утечек памяти то и в конечном коде таких проблем не будет (если при выводи мы ничего не добавляли).
На стандартном DOM далеко не уедешь.Смотря на чем ехать. Если язык позволяет легко управлять потоком исполнения в исходном коде, то ехать можно далеко и без проблем. В языке уже есть абстракция для построения правильного потока исполнения и для вывода правильных абстракций.
Абстракции типа реакта плохи тем, что практически закрывают нижележащий уровень (DOM).
VSCode построен с помощью TypeScript и без реакта. Реакт там не подойдет из-за большого оверхеда на построение виртуального dom и вычисления diff. А TypeScript справился с построением потока выполнения и без абстракций реакта.
На стандартном DOM далеко не уедешь. Нужна абстракция виджета и возможность строить дерево виджетов. Нужны аналоги реактовских колбэков componentDidMount и componentWillUnmount, иначе невозможно создавать виджеты, которым надо, например, навесить какой-то глобальный обработчик событий и убрать его при удалении.Про утечку памяти. В стандартном походе UI элементы не подписываются на события друг друга. На события от UI элементов подписывается третий объект X. Поэтому UI элементы можно просто удалять без утечки памяти.
Затем, в твоем коде видны зачатки проблем с утечками памяти. Есть виджет A, на события в нем подписан виджет B. Когда виджет B удаляется, то ссылка на него остается висеть в виджете A. Понимание этой проблемы на порядок сложнее, чем все API реакта вместе взятое.
События это, грубо говоря, список указателей на функции. Это mutable состояние. Если мы просто при старте формы подписываемся на события изменчивая природа состояния почти не проявляется, это близко к readonly переменным. Если же архитектура требует подписки и отписки от событий, тогда изменяемое состояние проявляет свои худшие стороны.
Ты привел пример как ООП подход приводит к плохому результату. На первый взгляд кажется "естественным" ввести понятие виджета. А в коде оно создает проблему, которой не было. Поэтому ООП, которое пропагандирует брать абстракции "из объектов реального мира", приводит к
accidental complexity. Лучше выводить абстракции из требований к продукту и из текста кода.
У меня нет утечек памяти. Описанный сценарий к моему коду не имеет ни малейшего отношения. Ни каких зачатков нет.У всех есть, а у тебя нет. Может быть ты просто не осознаешь проблему?
Как ты собрался реализовывать componentDidMount/componentWillUnmount ты тоже не объяснил. Все js фреймворки пытались его реализовать с переменным успехом.
У всех есть, а у тебя нет. Может быть ты просто не осознаешь проблему?Я ее осознаю ровно так как описано в первом ответе http://stackoverflow.com/a/4526840
Как ты собрался реализовывать componentDidMount/componentWillUnmount ты тоже не объяснил.
Мне они не нужны.
Все js фреймворки пытались его реализовать с переменным успехом.
Раньше IE не мог полностью отслеживать когда ссылки на объект пропадают и из DOM и из js машины. Это делали фрейворки. Сейчас это, вроде, уже история.
Приведи код с удаление элемента на ванильном js или jquery, я и покажу что ты не до конца осознаешь.
subscribe {
a.E1 += b.M1;
}
subscribe;
...
//удаляем b, создаем новый и для нового объекта подписываемся
b = new B; //ссылка на старый экземпляр (который был в b) осталась в E1
subscribe;
Если подписываться через третий объект, то проблемы не возникает:
a.E1 += => b.M1;
...
//удаляем b, создаем новый
b = new B; //старый экземпляр (который был в b) удаляется сборщиком мусора
//удаляем b, создаем новыйНе будет удаление старого b. У a осталась ссылка на него.
b = new B; //старый экземпляр (который был в b) удаляется сборщиком мусора
Не будет удаление старого b. У a осталась ссылка на него.Не обманывай :
class Program
{
static void Main(string[] args)
{
var program = new Program;
program.a.OnE1;
program.b = new B(2);
GC.Collect;
Thread.Sleep(1000);
Console.WriteLine("GC.Collect");
program.a.OnE1;
}
public Program
{
a.E1 += => b.M1;
}
public A a = new A;
public B b = new B(1);
}
class A
{
public event Action E1;
public void OnE1
{
if (E1 != null) E1;
}
}
class B
{
private readonly int i;
public B(int i)
{
this.i = i;
}
public void M1
{
Console.WriteLine("M1" + i);
}
~B
{
Console.WriteLine("~B" + i);
}
}
Результат:
M11
~B1
GC.Collect
M12
~B2
ps
Сомневаюсь, что возможно выстроить реальный интерфейс без отписывания от событий.
Сомневаюсь, что возможно выстроить реальный интерфейс без отписывания от событий.Отписывание от событий используется либо для обхода косяков в используемых компонентах, либо это говнокод.
Это частный случай использование mutable состояния. Можно писать экономя mutable состояние. Код получается проще.
Отписывание от событий используется либо для обхода косяков в используемых компонентах, либо это говнокод.В сложном интерфейсе появляется инкапсуляция, динамика и временность. Как предлагаешь это реализовывать без отписывания от событий?
Инкапсуляция - UI разделен на части, которые не знают напрямую ничего друг о друге
Динамика - интерфейс меняется - перестраивась в ответ на действия пользователя
Временность - часть элементов проявляется в UI только временно: анимация, отображение текущей операции и т.д.
В твоём примере все эти три пункта нарушаются:
- инкапсуляция: Program знает о поведении всех частей UI
- динамика: интерфейс не меняется
- временность: реагирование на a.E1 всегда одно и тоже в течении всего отображения UI
Всё из перечисленного можно показать на маленьких примерах. Код в студию.
Контролы A добавляются/удаляются пользователем
Контролы A для своей работы постоянно выполняют "тяжелую" функцию.
Контролы A отключают выполнение "тяжелой" функции, когда форма свернута, подписываясь для этого на события формы
подписываясь для этого на события формы
подписаться на событие формы один раз
form.Minimized += => foreach(var aControl in AControls) aControl.ОтключитьТяжелуюФункцию;
подписаться на событие формы один разКто это делает? Если сама форма, то нарушается инкапсуляция. Форма тогда знает о том, что бывают контролы с тяжелой функцией.
Что такое AControls? Все контролы A регистрируются в static-коллекции?
Куда они добавляются?
Код бы всё разъяснил сразу.
Куда они добавляются?куда-то в дерево контролов. (до этого тобой декларировался принцип, что не стоит состояние хранить где-то еще, если оно и так уже хранится в самих контролах)
Кто это делает? Если сама форма, то нарушается инкапсуляция. Форма тогда знает о том, что бывают контролы с тяжелой функцией.
Ты, действительно, не знаешь как сделать эту инкапсуляцию без изменяемого состояния?
У тебя в контрол A передается форма. Пусть это происходит в конструкторе (если не в конструкторе, а в сетере, то всё аналогично):
public A(Form form) {
form.Minimized += => { ... }
};
Инкапсуляция делается следующим образом:
public static Func<A> ACreator(Form form) {
form.Minimized += => foreach(var aControl in AControls) aControl.ОтключитьТяжелуюФункцию;
return => ...;
};
private A {
}
куда-то в дерево контролов. (до этого тобой декларировался принцип, что не стоит состояние хранить где-то еще, если оно и так уже хранится в самих контролах)
Тогда для AControls используются селекторы, например, по css классу.
тестами код
Тесты и мутабельность это не связанные вещи. На imutable коде тесты хорошо пишутся.
Nuclide. В качестве фронтенда она использует Atom - редактор на основе HTML5, а в качестве бэкенда - движок для статического анализа кода Infer.
Это еще один шаг на пути к созданию Facebook Stack (так, думаю, это назовут в будущем). Еще немного, и можно будет начинать разрабатывать кроссплатформенное приложение целиком на технологиях Facebook. Пишем код на FlowType в среде Nuclide. В качестве UI фреймворка используем React, сразу в разы облегчая себе разработку Android и IOS приложений благодаря React Native. Общаемся с сервером по протоколу GraphQL.
В принципе, фейсбуку осталось только заопенсорсить серверную технологию. Я мечтаю о СУБД, из коробки поддерживающей GraphQL по HTTP и вебсокетам, с хранимыми процедурами на javascript.
Можно сказать, что фейсбук более чем достойно отвечает на вызов, обозначенный Шуриком в этом разделе. Действительно, простые разработчики устали от постоянной фрустрации, вызванной фрагментированностью и отсутствием целостного видения при разработке под такие платформы, как, например, Java и .Net.
Тем временем, Facebook заопенсорсил многоязычную интегрированную среду разработки Это еще один шаг на пути к созданию Facebook Stack (так, думаю, это назовут в будущем). Еще немного, и можно будет начинать разрабатывать кроссплатформенное приложение целиком на технологиях Facebook. Пишем код на FlowType в среде Nuclide. В качестве UI фреймворка используем React, сразу в разы облегчая себе разработку Android и IOS приложений благодаря React Native. Общаемся с сервером по протоколу GraphQL.
В принципе, фейсбуку осталось только заопенсорсить серверную технологию. Я мечтаю о СУБД, из коробки поддерживающей GraphQL по HTTP и вебсокетам, с хранимыми процедурами на javascript.
Можно сказать, что фейсбук более чем достойно отвечает на вызов, обозначенный Шуриком в этом разделе. Действительно, простые разработчики устали от постоянной фрустрации, вызванной фрагментированностью и отсутствием целостного видения при разработке под такие платформы, как, например, Java и .Net.
Действительно, простые разработчики устали от постоянной фрустрации, вызванной фрагментированностью и отсутствием целостного видения при разработке под такие платформы, как, например, Java и .Net.Простые разработчики устали получать 1 звезду от пользователей, у которых тормозят не native приложения. Это к истории с Facebook, да.
Простые разработчики устали получать 1 звезду от пользователей, у которых тормозят не native приложения. Это к истории с Facebook, да.Не понял, о чем ты. У тебя какие-то претензии к продуктам или библиотекам фейсбука?
Не понял, о чем ты. У тебя какие-то претензии к продуктам или библиотекам фейсбука?Рейтинг приложения Facebook, написанного Facebook на HTML5, был очень низким из-за того, что оно дико тормозило. Пока они не переписали всё на native.
Рейтинг приложения Facebook, написанного Facebook на HTML5, был очень низким из-за того, что оно дико тормозило.Да, HTML5 приложения проигрывают нативным. Именно поэтому фейсбук создал React Native, который не использует HTML5
Я мечтаю о СУБД, из коробки поддерживающей GraphQL по HTTP и вебсокетам, с хранимыми процедурами на javascript.Какая модель данных у этой СУБД?
Именно поэтому фейсбук создал React Native, который не использует HTML5Да, но это тоже не Native. Я более чем уверен, что все дифирамбы утихнут на пару-тройку лет, когда выйдут реальные приложения, которые будут тормозить или плохо работать. Не верится, что Facebook в чём-то принципиально отличается от разработчиков Native компонентов в Google и Apple.
Да, но это тоже не Native. Я более чем уверен, что все дифирамбы утихнут на пару-тройку лет, когда выйдут реальные приложения, которые будут тормозить или плохо работать. Не верится, что Facebook в чём-то принципиально отличается от разработчиков Native компонентов в Google и Apple.А в чем принципиальное отличие от Native приложений? Виджеты используются нативные, можно писать виджеты на Java / ObjectiveС и использовать их в React.Native приложении. Весь код бизнес-логики, взаимодействия с сервером и часть вью-логики типа "при нажатии на кнопку перейти на другой экран" оказывается кроссплатформенной.
Кстати, ты всё время сливаешься в спорах про языки с динамической типизацией, т.к. скудоумие не даёт тебе понять, что для некоторых групп разработчиков читаемость более приоритетна, чем все плюсы статической типизации вместе взятые.Статический анализ кода компилятором/инструментами, не использование лишнего mutable состояния — всё это является средствами улучшения читаемости кода.
Довольно странно рассматривать отдельно читаемость и статический анализ. Закрадывается подозрение, что ты не понимаешь и поэтому не используешь все преимущества статического анализа.
Статический анализ кода компилятором/инструментами, не использование лишнего mutable состояния — всё это является средствами улучшения читаемости кода.Куколка моя, я смотрю, ты перечитал весь тредик, чтобы рассказать всем о том, что я не понимаю? Видишь ли, вот этому кодику статический анализ с читаемостью как бы не помогает:
public static Func<object, QWriter, TemplateBase> BuildQFragment(Type type, string text,
[CallerLineNumber] int lineNumber = 0, [CallerFilePath] string filePath = "")
{
//TODO: теперь имя класса может быть постоянным, поскольку эмитем в отдельную сборку. Сделать это.
var template = "T" + Guid.NewGuid.ToString("N");
var host = new RazorEngineHost(new CSharpRazorCodeLanguage {
GeneratedClassContext = new GeneratedClassContext(
executeMethodName: GeneratedClassContext.Default.ExecuteMethodName,
writeMethodName: GeneratedClassContext.Default.WriteMethodName,
writeLiteralMethodName: GeneratedClassContext.Default.WriteLiteralMethodName,
writeToMethodName: WriteToMethodName,
writeLiteralToMethodName: WriteLiteralToMethodName,
templateTypeName: typeof (QFragment).FullName,
defineSectionMethodName: GeneratedClassContext.Default.DefineSectionMethodName
DefaultBaseClass = typeof (TemplateBase).FullName,
DefaultClassName = template
};
var razorConfiguration = RazorConfigurationHelper.Get(filePath, lineNumber);
foreach (var namespaceImport in razorConfiguration.Item1)
host.NamespaceImports.Add(namespaceImport);
var engine = new RazorTemplateEngine(host);
GeneratorResults razorResult;
using (var reader = new StringReader(text
razorResult = engine.GenerateCode(reader, className: null, rootNamespace: null,
sourceFileName: GetQueryPath(filePath, lineNumber;
if (!razorResult.Success)
{
var error = razorResult.ParserErrors[0];
throw new ApplicationException(
String.Format("Razor parsing error: '{3}' (Caller line number {4}, Ln {0}, Col {1}) {2}",
error.Location.LineIndex + 1, error.Location.CharacterIndex + 1, error.Message,
filePath, lineNumber;
}
StringWriter stringWriter;
using (stringWriter = new StringWriter
using (var codeProvider = new CSharpCodeProvider
codeProvider.GenerateCodeFromCompileUnit(
razorResult.GeneratedCode, stringWriter, new CodeGeneratorOptions;
var propertyInfos = type.GetProperties.Select(_ => new {
_.Name,
_.PropertyType,
SetterName = "." + _.Name
}).ToList;
if (propertyInfos.All(_ => _.Name != "p"
propertyInfos.Add(new {
Name = "p",
PropertyType = type,
SetterName = ""
});
var types = new Dictionary<Assembly, string>
var syntax = GetTypeSyntax(type, types);
var typeSyntaxs = propertyInfos.Select(_ => GetTypeSyntax(_.PropertyType, types.ToList;
var compilationUnitSyntax = SyntaxTree.ParseText(stringWriter + @"
namespace Razor
{
" + String.Join(@"
", types.Select(_ => " extern alias " + _.Value + ";" + @"
public partial class " + template + @"
{
internal static " + typeof (TemplateBase).FullName + @" M58a3d6d024dc47c6a692e20e29210b89(object arg, " +
typeof (QWriter).FullName + @" writer) {
var arg2 = (System.Object)arg;
var result = new " + template + @"(writer);
" + String.Join(@"
", propertyInfos.Select(_ => " result." + _.Name + " = arg2" + _.SetterName + ";" + @"
return result;
}
public " + template + @"(" + typeof (QWriter).FullName + @" builder): base(builder) {}
" + String.Join(@"
", propertyInfos.Select(_ => " internal System.Object " + _.Name + ";" + @"
}
}
").GetRoot;
var descendantNodes = compilationUnitSyntax.ChildNodes.Skip(1).First.DescendantNodes;
var syntaxTree = SyntaxTree.CreateCompilationUnitSyntax) new Rewriter(
descendantNodes.OfType<MethodDeclarationSyntax>.First.DescendantNodes
.OfType<CastExpressionSyntax>.First
syntax,
descendantNodes.OfType<FieldDeclarationSyntax>.Select(
_ => _.DescendantNodes.OfType<VariableDeclarationSyntax>.Single
.Take(typeSyntaxs.Count).SelectdeclarationSyntax, i) => new {
declarationSyntax,
typeSyntax = typeSyntaxs[i]
}).ToDictionary(_ => _.declarationSyntax, _ => _.typeSyntax)
).Visit(compilationUnitSyntax;
var compilation = Compilation.Create(DynamicMaterializer.AssemblyName.Name,
new CompilationOptions(OutputKind.DynamicallyLinkedLibrary
new[] {syntaxTree},
types.Select(_ => new MetadataFileReference(_.Key.Location,
new MetadataReferenceProperties(alias: _.Value
.Concat(razorConfiguration.Item2
.Select(_ => new MetadataFileReference(_.Location;
var className = "Razor." + template;
CommonEmitResult emitResult;
Func<Type> typeFunc;
if (!Debugger.IsAttached)
{
emitResult = compilation.Emit(DynamicMaterializer.ModuleBuilder);
typeFunc = => DynamicMaterializer.ModuleBuilder.GetType(className);
}
else
{
MemoryStream stream;
MemoryStream pdbStream;
using (stream = new MemoryStream
using (pdbStream = new MemoryStream
emitResult = compilation.Emit(stream, pdbStream: pdbStream);
typeFunc = => Assembly.Load(stream.GetBuffer pdbStream.GetBuffer.GetType(className);
}
foreach (var diagnostic in emitResult.Diagnostics)
{
var lineSpan = diagnostic.Location.GetLineSpan(usePreprocessorDirectives: true);
throw new ApplicationException(
string.Format("Build error: '{3}' (Caller line number {4}, Ln {0}, Col {1}) {2}",
lineSpan.StartLinePosition.Line + 1,
lineSpan.StartLinePosition.Character + 1,
diagnostic.Info.GetMessage
filePath, lineNumber;
}
return (Func<object, QWriter, TemplateBase>) typeFunc
.GetMethod("M58a3d6d024dc47c6a692e20e29210b89", BindingFlags.Static | BindingFlags.NonPublic)
.CreateDelegate(typeof (Func<object, QWriter, TemplateBase>
}
Куколка моя, я смотрю, ты перечитал весь тредик, чтобы рассказать всем о том, что я не понимаю? Видишь ли, вот этому кодику статический анализ с читаемостью как бы не помогает:О, я вижу, что ты даже версию 2.0 моего проекта посмотрел. Продолжаешь фапать?
Твоя агрессия это такое проявление любви что ли?
Видишь ли, вот этому кодику статический анализ с читаемостью как бы не помогает:У тебя низкие навыки чтения чудого кода. Учись читать.
У тебя низкие навыки чтения чудого кода. Учись читать.Шурика кода не читателя, Шурика кода писателя. Но вообще, да, проблемы в твоём коде нет, продолжай так думать, а то ещё узнаешь правду и сбросишься с ГЗ. Вот уже опечатки по Фрейду делаешь.
Назовешь проблемы, будет повод думать иначе. А пока в твоих постах только фапанье на мой проект.
Назовешь проблемы, будет повод думать иначе.Деточка, что ты, никаких проблем в твоём коде нет. Не волнуйся так, а то очки запотеют и стукнешься головой об стену. Страшно подумать, какой красоты код ты после этого будешь писать. Все, кто его увидит, сгорят заживо, не выдержав его божественной чистоты.
Эти задачи решает и твой работодатель - Microsoft. Entity Framework предоставляет статически валидируемый доступ к БД.
Интересно обсудить, какие бы ты использовал подходы, и какой бы ты рекомендовал стиль код для решения задач:
- генерация dbml из БД
- генерация кода на основе dbml
- исполнение EF-запросов
- проверки того, что в этих генерациях минимизированы ошибки
Интересно обсудить, какие бы ты использовал подходы, и какой бы ты рекомендовал стиль код для решения задач:- генерация dbml из БД- генерация кода на основе dbml- исполнение EF-запросов- проверки того, что в этих генерациях минимизированы ошибкиТы спрашиваешь так как будто не заешь ответа. ТЕСТЫ!
Для
Мне тоже многое не нравится в стиле оформления кода Shurick-а, но это не отменяет необходимость в задачах генерации кода, статической валидации кода и написания такого кода, который удобен для статической валидации.Только бы не , только бы не ... тьфу. Речь не о том, что есть какая-то необходимость, а о том, что все вокруг не понимают гения! Без статического анализа и ReSharper-а код не может быть читаемый в принципе, и только один разработчик на форуме это понимает!
Возникли проблем? Значит написали мало тестов. Пишите, пишите, обезьянки, а тимлид один в белом.Вам требуется проверять свой код? Обезьянки! Только один гений пишет идеальный код, статически анализирует его и, не тестируя, сразу же выкладывает в production. За десять лет ни единого разрыва!11
ТЕСТЫ!Интересно, как найти баланс между тестами и статическим анализом для C#-проектов?
Представив этот баланс в виде линии, где слева - много тестов и мало стат. анализа, а справа - много стат. анализа и мало тестов. Подходы, аналогичные твоему, получаются радикально справа - 100% стат. анализа и 0% тестов, подходы скриптовщиков, радикально слева - 0% стат. анализа и 100% тестов.
Вторая шкала: насколько вообще измеряется или гарантируется для проекта, что в нём нет ошибок. При подходах, аналогичных твоему - 20% (условно на php (сайты-визитки) - 0.2%. 100% - это использование и остальных инструментов статической валидации: доказательство валидности, зависимые типы, code analytics и т.д.
Третья шкала: кто делает валидацию ошибок - люди или автоматика? При подходах, аналогичных твоему - 5% люди, 95% автоматика; на php(сайты-визитки) - 100% люди (чаще всего пользователи 0% - автоматика.
Подходы, аналогичные твоему, получаются радикально справа - 100% стат. анализа и 0% тестовТолько что переключился из окна Visual Studio, где писал тесты. О каком "моем подходе" ты говоришь?
Без статического анализа и ReSharper-а код не может быть читаемый в принципе,Имхо, без стат. анализа и Resharper-а код неудобен для микро-изменений.
Сейчас по работе много сталкиваюсь с sql-кодом, и вижу что программисты оставляют в нем много соплей по сравнению с C#-кодом. Неудачные названия, неоптимальность конструкций, плохая разбивка на блоки, много copy-paste, раздутые тела и т.д.
В том числе и за собой отмечаю такой подход при написании sql-кода. Связываю это с тем, что уборка этих соплей занимает очень и очень много времени и нервов пока пишешь на sql, без всех этих стат. анализов и решарперов.
Только что переключился из окна Visual Studio, где писал тесты. О каком "моем подходе" ты говоришь?Ок. Был не прав.
По твоему какой у тебя баланс между стат. анализом и тестами?
Представив этот баланс в виде линии, где слева - много тестов и мало стат. анализа, а справа - много стат. анализа и мало тестов. Подходы, аналогичные твоему, получаются радикально справа - 100% стат. анализа и 0% тестов, подходы скриптовщиков, радикально слева - 0% стат. анализа и 100% тестов.Это ложная дихотомия, потому что проверка типов дается забесплатно.
Это ложная дихотомия, потому что проверка типов дается забесплатно.Эти дихотомии полезны для измерения разницы между подходами.
Возьмем задачу проверки, что код соответсвует структуре БД и три подхода для обращения к БД (условные Ado.Net, Linq и EF):
DataTable itemTable = new Command("select X, Y from Item where Item.X = {0}", x).Execute;
if (<condition>) y = (int)itemTable.Rows[i]["Y"];
var items = context.Query<Item>("select X, Y, From Item where Item.X = {0}", x);
if (<condition>) y = items[i].Y;
//коллекция для доступа к БД, сгенерированная на основе мета-описания
IQueriable<Item> Items;
var items = context.Items.Where(item => item.X == x).ToArray;
if (<condition>) y = items[i].Y;
В первом случае, стат. анализ используется минимально. Для проверки соответствия кода и БД необходимо много тестов, которые покроют каждый IF.
Соотношение тестов к стат. анализу: 99% к 1%
Во втором случае, появляется использование стат. анализа. Для проверки соответствия кода и БД необходимы тесты по кол-ву Query в коде. Какждый if проверять не требуется.
Соотношение тестов к стат. анализу: 90% к 10%
В третьем случае, стат. анализ используется по максимуму. Необходимо лишь пара тестов для проверки соответствия структуры БД и мета-описания, из которого генерится код для доступа к БД. Соотношение тестов к стат. анализу: 1% к 99%
Для проверки соответствия кода и БД необходимо много тестов, которые покроют каждый IF.Во всех трёх подходах каждый if уже покрывается за счёт тестов на бизнес логику.
Во всех трёх подходах каждый if уже покрывается за счёт тестов на бизнес логику.Во-первых, останутся If-ы не покрытые бизнес-тестами. Это обычно if-ы, которые срабатывают при ошибке или при нештатной ситуации.
Соответственно, при первом подходе не будет покрыто около 10% if-ов, во втором - 1%, в третьем - 0%.
Во-вторых, бизнес-тесты выполняются от десятков минут до нескольких часов, проверка соответствия кода и БД делается моментально.
Во-первых, останутся If-ы не покрытые бизнес-тестами. Это обычно if-ы, которые срабатывают при ошибке или при нештатной ситуации.Эти if-ы никак не зависят от проверок типизации.
Во-вторых, бизнес-тесты выполняются от десятков минут до нескольких часов, проверка соответствия кода и БД делается моментально.Щито?
Эти if-ы никак не зависят от проверок типизации.Это утверждение не соответствует примерам выше.
>> Щито?
Сколько у тебя было на проекте бизнес-тестов? И сколько времени они выполнялись? Сколько в проекте было if-ов?
Во всех трёх подходах каждый if уже покрывается за счёт тестов на бизнес логику.Чем это гарантируется?
Отцу основателю Kent Beck требуется еще 10-20 лет на создание теории: какие тесты надо писать, а какие нет. А Майк уже пользуется следствиями этой теории. Мол, будут обязательно написаны все тесты, которые покрывают sql magic string. Забавно апеллировать к следствиям из несуществующей теории.
Это утверждение не соответствует примерам выше.Это утверждение соответствует.
Сколько у тебя было на проекте бизнес-тестов? И сколько времени они выполнялись? Сколько в проекте было if-ов?Более 10.000 тестов, и выполнялись они за 15-20 минут, но это никак не влияет на мою сомневаку.
Отцу основателю Kent Beck требуется еще 10-20 лет на создание теории: какие тесты надо писать, а какие нет. А Майк уже пользуется следствиями этой теории. Мол, будут обязательно написаны все тесты, которые покрывают sql magic string. Забавно апеллировать к следствиям из несуществующей теории.Детонька, ты продолжай, продолжай думать, что важно только совпадение типов. Остальное таким гениям, как ты, не нужно. А все, кто смеётся над твоими гениальными идеями, кому нужны тесты, кому нужно ООП - они плохие глупые обезьянки.
Возьмем задачу проверки, что код соответсвует структуре БД и три подхода для обращения к БДТы задаешь эти вопросы человеку, который использует RDBMS как key-value storage. И употребляет словосочетание "реляционные отношения". Только одно это словосочетание говорит о том, что человек не знаком с основами реляционной теории. Человек не умеет и не хочет работать с реляционкой, а ты ему про высокие материи: валидацию кода и т.п.
Получается, что реляционные отношения практически отсутствуют. Даже join иногда выполняется двумя запросами в базу.
Ты задаешь эти вопросы человеку, который использует RDBMS как key-value storage. И употребляет словосочетание "реляционные отношения". Только одно это словосочетание говорит о том, что человек не знаком с основами реляционной теории. Человек не умеет и не хочет работать с реляционкой, а ты ему про высокие материи: валидацию кода и т.п.Мы разговариваем с человеком, у которого не всё в порядке с головой. Он всё время жалуется на тех, кто умнее его. Только одно это говорит о том, что человек не готов к признанию реальности и вопреки всему считает, что он адекватен. На деле же его ранимая детская психика не готова к серьёзным разговорам, а мы ему про высокие материи: тестирование, читаемость кода, и т.п.
Пользователь форумлокал сказал мне сделать суицид
Мы разговариваем с человеком, у которого не всё в порядке с головой. Он всё время жалуется на тех, кто умнее его. Только одно это говорит о том, что человек не готов к признанию реальности и вопреки всему считает, что он адекватен. На деле же его ранимая детская психика не готова к серьёзным разговорам, а мы ему про высокие материи: тестирование, читаемость кода, и т.п.Серьёзные разговоры с умными людьми, которые предлагают собеседнику суицид. Абсурд же!
Серьёзные разговоры с умными людьми, которые предлагают собеседнику суицид. Абсурд же!Да-да, серьёзные разговоры с человеком, который проиграл на Форексе, и испытал такой баттхёрт, что написал о применении ядерного оружия. Первый и уникальный случай ядерного баттхёрта на форумлокал.
И употребляет словосочетание "реляционные отношения".И что не так с этим словосочетанием?
Умный человек, ты не понял моего поста про ядерное оружие.
И что не так с этим словосочетанием?Попробуй перевести его на английский.
Умный человек, ты не понял моего поста про ядерное оружие.Петушочек, ты пойди мне в той теме возрази.
Попробуй перевести его на английский.Только "фрайворк" не переводи. Хотя бы не перед сном.
Только "фрайворк" не переводи. Хотя бы не перед сном."фрайворк" по английски framework. Что тебя смущает?
Петушочек, ты пойди мне в той теме возрази.Ты предлагаешь Петушку возражать Умному Человеку?
"фрайворк" по английски framework. Что тебя смущает?framework [ˈfreɪmwɜːk] - фрэймвок
А фрайворк это скорее frywork (жарительная работа?)
По твоему какой у тебя баланс между стат. анализом и тестами?If I don't typically make a kind of mistake ..., I don't test for it. (с) Kent Beck
За десять лет ни единого разрыва!11На твоих проектах (с тестами) ни единого разрыва?
Это ложная дихотомия, потому что проверка типов дается забесплатно.Давай погорим о твоем любимом GraphQL.
Тут говорится, что "A GraphQL query is a string interpreted by a server". Т.е. это аналогично sql magic string внутри кода на языке общего назначения (C#/Java и т.д.)? Тогда возникает вопрос, как статическим анализом кода проверять запросы, которые собираются посредством конкатенации строк?
Ты предлагаешь Петушку возражать Умному Человеку?Я предлагаю тебе захлопнуть свой клювик.
"фрайворк" по английски framework. Что тебя смущает?Меня ничего не смущает. Но только одно это заявление говорит о том, что ты вообще ничего не знаешь об основах разработки программного обеспечения.
ты пойди мне в той теме возрази.
Я предлагаю тебе захлопнуть свой клювик.Душевные метания
Душевные метания гопника Умного Человека.OK, продолжим разговор, когда тебе хватит духу возразить в тобою же созданной теме.
Тебе пришлось вынести редактируемое состояние контрола в state доменной модели, потому что типы данных доменной модели и модели контрола различаются.На днях писал формы на реакте, вспомнил этот тред. Загрязнять доменную модель строками не годится, тут согласен. Допилил свой пример с калькулятором. Там теперь есть возможность делать из текстового инпута и пары функций парсер/форматтер новый инпут, который сразу работает с отпарсенными данными (у меня там intInput, который принимает и возвращает Maybe Int).
Затем, я придумал прикольную функцию combineObject. Она проходится по всем свойствам объекта (в нашем случае {first: Maybe Int, second: Maybe Int} ищет там Nothing, и если все ок то возвращает Just {first: Int, second: Int}, иначе Nothing.
Таким образом, если у нас форма о 20 полях, то чтобы проверить что она заполнена, не надо писать багоопасный код сложения 20 флагов. У нас просто каждый виджет будет смотреть в определенное поле объекта и читать/писать туда Maybe<T>. Потом можно сделать combineObject и посмотреть, что получилось - Just или Nothing. На статическом языке написать такую функцию не получится, ну или потребуется очень сильное метапрограммирование.
Затем, мы можем просто писать функции, которые работают с объектом формы, но уже без Maybe, и применять эти функции через fmapObject. Таким образом, удается избежать заражения кода монадой. Джаваскрипт позволяет написать сразу целый модуль таких функций, и сразу все одной строчкой обернуть во fmapObject.
На статическом языке написать такую функцию не получится, ну или потребуется очень сильное метапрограммирование.Потребуется одна функция - которая отображает поля класса на dictionary. В JS - эта функция из коробки (в нём каждый object изначально является dictionary а в static языках она возьмется из custom library.
Затем, я придумал прикольную функцию combineObject. Она проходится по всем свойствам объекта (в нашем случае {first: Maybe Int, second: Maybe Int} ищет там Nothing, и если все ок то возвращает Just {first: Int, second: Int}, иначе Nothing.Забавно, что для объяснения условия ты используешь статически типизированную нотацию. А говорил, что читаемость это конек динамических языков.
Таким образом, если у нас форма о 20 полях, то чтобы проверить что она заполнена, не надо писать багоопасный код сложения 20 флагов. У нас просто каждый виджет будет смотреть в определенное поле объекта и читать/писать туда Maybe<T>. Потом можно сделать combineObject и посмотреть, что получилось - Just или Nothing. На статическом языке написать такую функцию не получится, ну или потребуется очень сильное метапрограммирование.
Потребуется одна функция - которая отображает поля класса на dictionary. В JS - эта функция из коробки (в нём каждый object изначально является dictionary а в static языках она возьмется из custom library.говорит про то, что в C#/Java можно написать вот такую функцию для Tuple
Option<Tuple<T1, T2>> CombineTuple<T1>(Tuple<Option<T1>, Option<T2>> tuple)
{
...
}
Но нельзя аналогичную функцию написать для произвольного объекта.
Да, было бы здорово иметь named tuple (как в Python) в C#.
Возвращать dictionary не круто, поскольку теряется статическая типизация.
Потом можно сделать combineObject и посмотреть, что получилось - Just или Nothing. На статическом языке написать такую функцию не получится, ну или потребуется очень сильное метапрограммирование.А еще есть кодогенерация.
На днях писал формы на реакте, вспомнил этот тред. Загрязнять доменную модель строками не годится, тут согласен. Допилил свой пример с калькулятором. Там теперь есть возможность делать из текстового инпута и пары функций парсер/форматтер новый инпут, который сразу работает с отпарсенными данными (у меня там intInput, который принимает и возвращает Maybe Int).Давайте посмотрим на subject треда, там идет речь про поля (не про объекты а ты опять вводишь функцию с названием combineObject.
Затем, я придумал прикольную функцию combineObject. Она проходится по всем свойствам объекта (в нашем случае {first: Maybe Int, second: Maybe Int} ищет там Nothing, и если все ок то возвращает Just {first: Int, second: Int}, иначе Nothing.
Таким образом, если у нас форма о 20 полях, то чтобы проверить что она заполнена, не надо писать багоопасный код сложения 20 флагов. У нас просто каждый виджет будет смотреть в определенное поле объекта и читать/писать туда Maybe<T>. Потом можно сделать combineObject и посмотреть, что получилось - Just или Nothing. На статическом языке написать такую функцию не получится, ну или потребуется очень сильное метапрограммирование.
Затем, мы можем просто писать функции, которые работают с объектом формы, но уже без Maybe, и применять эти функции через fmapObject. Таким образом, удается избежать заражения кода монадой. Джаваскрипт позволяет написать сразу целый модуль таких функций, и сразу все одной строчкой обернуть во fmapObject.
Если у тебя есть механизм из первого поста:
легко добавлять, удалять, менять обработку поля? Поле должно легко протаскиваться и искаться по всем слоям от БД до пользовательского интерфейса.
то формулировать задачи в терминах объектов тебе не нужно, просто оперируешь терминами на уровне полей. Терминология на уровне полей естественна, и заказчики именно этой терминологией пользуются. Поэтому когда код наиболее приближен к терминологии заказчиков жизнь становится проще и все становятся довольными. Делайте мир лучше. А не закапывайтесь в програмерские объекты.
На статическом языке написать такую функцию не получится, ну или потребуется очень сильное метапрограммирование.В этом разделе ты повторил эту фразу уже несколько десятков раз. И каждый раз она не соответствовала действительности. Зачем ты всё время пишешь о том, чего не знаешь? Сильное метапрограммирование потребуется только на C++, на Java или C# мы легко напишем фреймворк, который обработает атрибуты вида [Field(true)] int second; и нам не придётся писать ничего, кроме одного атрибута на поле.
Чуть дальше в реальность. Функция combineObject на самом деле только первый шаг к чему-то приличному. В реальном проекте нам потребуется выводить сообщения о том, какое именно поле не заполнено или заполнено неправильно. Устанавливать фокус ввода на первое неправильное поле. Закрашивать его другим цветом. И всё это на C# делается одним атрибутом на поле и одной строчкой для привязки к представлению поля. Правда, если подумать ещё немного, то окажется, что обязательность и валидация одних полей зависят от значений в других полях, и код сложной валидации придётся-таки написать руками. А ещё сообщения от валидации могут зависеть от назначения поля и должны поддерживать интернационализацию.
Собственно, со всякими модными новыми языками и технологиями, которых наплодили уже over 9000, всегда так. Кто-то под эмоциями от какой-то платформы садится, рисует простой туториал, не имеющий никакого отношения к реальности, выдаёт это за вселенское достижение и делает на этом стартап. А потом приходят реальные требования, и этот красивый маленький кусочек кода вырастает в монстра, которого потом выкинут на помойку, если стартап раньше не развалится.
Кто-то под эмоциями от какой-то платформы садится, рисует простой туториал, не имеющий никакого отношения к реальностиА к каким технологиям или патернам есть примеры, которые имею отношения к реальности? Вот Фаулер в своих книгах честно признается:
I like to use examples that are no less realistic as you usually find in books like this.
http://martinfowler.com/eaaDev/uiArchs.html
вики, msdn, FowlerПеречитываю тут Фаулера. У него изначально вводится неверный постулат про три копии данных:
С точки зрения пользователя есть всего две копии данных. Первая копия — это общее долговременное хранилище (там постоянно хранятся данные от всех пользователей). И кратковременная копия, с которой работает пользователь. Всё! Только две копии. Дублирование данных в "session state" и "screen state" — это не является свойством изначальной задачи, не является essential complexity. Такое дублирование — это чистой воды accidental complexity.
Undo/Redo данные где лежат?
Auth-cookie где находится? в Screen-state? или всё-таки в session-state?Проблема в самих названиях "session state" и "screen state". Главное не должно быть дублирования. Должен быть один экземпляр mutable данных. А название можно придумать, например, user state.
Undo/Redo данные где лежат?
В этом разделе ты повторил эту фразу уже несколько десятков раз. И каждый раз она не соответствовала действительности. Зачем ты всё время пишешь о том, чего не знаешь? Сильное метапрограммирование потребуется только на C++, на Java или C# мы легко напишем фреймворк, который обработает атрибуты вида [Field(true)] int second; и нам не придётся писать ничего, кроме одного атрибута на поле.В java/c# нельзя выразить тип функции combineObject. Использование такой функции будет динамическим, так же как в js. Кроме того, тебе потребуется руками написать оба типа - первый который приходит на вход функции combineObject, второй который она возвращает. Вроде бы в языке D можно в compile time сгенерить второй тип из первого, так чтобы абсолютно все было типизировано, но я не уверен.
Что касается атрибутов, то это типичное джава-убожество. Например, если у меня есть атрибуты Foo и Bar, то я не могу сделать атрибут FooBar, который работал бы как их комбинация. Кроме того, я не могу написать функцию, которая возвращает атрибут. В общем, если сущности не композируются и не взаимодействуют с другими сущностями, то это не программирование.
И всё это на C# делается одним атрибутом на поле и одной строчкой для привязки к представлению поля.А как называется этот C# фреймворк? Вот тут Шурик показывал какой-то - без слез не взглянешь.
В java/c# нельзя выразить тип функции combineObject.В решении с reflection это не нужно.
Что касается атрибутов, то это типичное джава-убожество. Например, если у меня есть атрибуты Foo и Bar, то я не могу сделать атрибут FooBar, который работал бы как их комбинация. Кроме того, я не могу написать функцию, которая возвращает атрибут. В общем, если сущности не композируются и не взаимодействуют с другими сущностями, то это не программирование.Куча воды, а по существу ничего не возразил. Если тебе не хочется, то можешь не называть упомянутые мною две строчки кода программированием, но задачу они решают полностью. Чтобы спорить о недостатках Java, тебе нужно сначала чему-то научиться. А так ты только ещё раз продемонстрировал, что у тебя от этого языка программирования какой-то нереальный батхёрт.
А как называется этот C# фреймворк? Вот тут Шурик показывал какой-то - без слез не взглянешь.Никак не называется, пишется за день или два с поддержкой всех требований, которые я перечислил. Выглядеть будет так:
[Field(IsRequired = true)] int first;
view.Add(model => model.first);
Если тебе не хочется, то можешь не называть упомянутые мною две строчки кода программированием, но задачу они решают полностью.Ты полностью решил задачу двумя воображаемыми строчками кода на несуществующем фреймворке?
В решении с reflection это не нужно.Да, но это динамическая типизация, так как у твоей функции combineObject будет тип Object => Object. Я как раз и написал, что сделать такую функцию типизированной можно на очень ограниченном количестве языков.
Ты полностью решил задачу двумя воображаемыми строчками кода на несуществующем фреймворке?Я полностью решил эту задачу в реальном проекте, написав такой фреймворк. Ты ведь понятия не имеешь, как это можно сделать, да? Поэтому и выглядишь глупо.
Да, но это динамическая типизация, так как у твоей функции combineObject будет тип Object => Object. Я как раз и написал, что сделать такую функцию типизированной можно на очень ограниченном количестве языков.Во-первых, мне как-то наплевать на такую функцию, она не решает задачи, которые возникнут в реальном проекте. Во-вторых, я не молюсь богам статической типизации, ты меня с кем-то спутал.
Я полностью решил эту задачу в реальном проекте, написав такой фреймворк.А делает нереальные проекты?
не надо писать багоопасный код сложения 20 флагов.Просто пишешь тесты и багов нет.
А делает нереальные проекты?Я без понятия, что он делает. А вот твои проекты и правда далеко от реальности.
А вот твои проекты и правда далеко от реальности.Я вчера покверил продакшен базу своего последнего проекта, на предмет статистики. Остался доволен. Таким образом, ты врешь. Может ты и про свои "реальные" проекты врешь?
Таким образом, ты врешь. Может ты и про свои "реальные" проекты врешь?Может и вру. Но настоящее, кристальное чистое враньё в Development - это твои воображаемые коллеги, которым нравится ко-ку. Ты выдумал их, потому что очень сильно переживаешь из-за того, что происходит на самом деле - этот проект говно и никому не нужен.
Остальным (которых просветление еще не посетило) скармливают Entity Framwork, который в седьмой версии полностью переписывают.
А в настоящей реальности работает 1C.
Действительно, не нужен для тех, на кого снизошло просветление, что RDBMS проще использовать как key-value storage.Ты не совсем понял смысл моего поста. Ко-ку не просто кому-то не нужен - он полное говно.
Во-первых, мне как-то наплевать на такую функцию, она не решает задачи, которые возникнут в реальном проекте.Как ты моделируешь отсутствующие данные?
Как ты моделируешь отсутствующие данные?Какие? Я вроде бы писал выше. Если представление с состоянием и поле по требованиям обязательное, то мне не нужно ничего моделировать. Если поле по требованиям необязательное, то простой nullable тип.
то простой nullable тип.Nullable типы в C# это maybe монада с ограничением только на value-типы.
Nullable типы в C# это maybe монада с ограничением только на value-типы.Какой ужас, как я теперь буду жить?
Это я к тому что спрашивает, как ты моделируешь отсутствие состояния. Ты моделируешь его так же как он, через Maybe монаду.
Если поле по требованиям необязательное, то простой nullable тип.Ты не пытаешься абстрагировать проверки на null? Чтобы функция, которая что-то считает, была избавлена от необходимости проверять на null, а проверки были отдельно?
Ты не пытаешься абстрагировать проверки на null? Чтобы функция, которая что-то считает, была избавлена от необходимости проверять на null, а проверки были отдельно?В интерфейсе это не нужно. Там всё это покрывается тем самым загадочным фреймворком, про который я то ли наврал, то ли нет. Функции, которые что-то считают, обычно не получают null. Ничего хорошего при натягивании арифметики на null обычно не получается.
В интерфейсе это не нужно.Вот как раз в калькуляторе не надо считать сумму если данные невалидные.
Ты не совсем понял смысл моего поста. Ко-ку не просто кому-то не нужен - он полное говно.Ты о моем проекте думаешь больше, чем я сам. Я готов согласиться, что Controllable Query полное говно. Но что мне использовать в следующем проекте? Entity Framework? Какой версии? Седьмая еще не вышла, шестая уже устарела. Или использовать RDBMS как key-value storage? Или каждый запрос оформлять в отдельный метод и писать на него тест, в котором наполнять базу данных и проверять правильно ли прочиталось из базы каждое поле?
Вот как раз в калькуляторе не надо считать сумму если данные невалидные.Но для этого не нужно никаких nullable полей.
Ты о моем проекте думаешь больше, чем я сам. Я готов согласиться, что Controllable Query полное говно. Но что мне использовать в следующем проекте? Entity Framework? Какой версии? Седьмая еще не вышла, шестая уже устарела. Или использовать RDBMS как key-value storage? Или каждый запрос оформлять в отдельный метод и писать на него тест, в котором наполнять базу данных и проверять правильно ли прочиталось из базы каждое поле?Организуй доступ к данным через слой вьюх, вьюхи типизированные.
http://samsaffron.com/archive/2011/09/05/Digging+ourselves+o...
Как такое делать на вьюхах? Приведи код, как бы ты это сделал?
Вот пример из реального проекта Как такое делать на вьюхах? Приведи код, как бы ты это сделал?
Как такое делать на вьюхах? Приведи код, как бы ты это сделал?Там много переделать можно. Например, надо сделать вьюху can_vote_for_tag(tag_id, can_vote(boolean. Надо приджойнивать эту вьюху всегда, независимо от условий. Дальше если столбцы из нее не используются, то оптимизатор удалит ненужные джоины. В общем, ты все параметры по которым собираешься фильтровать или сортировать выводишь во вьюху, а потом в самом конце генеришь sql типа "select col1,col2,... from superview where some_filter_col = ? order by some_sort_col". Динамическая генерация минимальна.
Тесты тут обязательны конечно. Нужна стандартная фикстура с несколькими пользователями, вопросами, тегами etc. Относительно этой фикстуры пишутся все тесты. Тут тестировать немного, при том что это мажорная фича stackoverflow. Люди вон тестируют UI виджеты в клауде под 20 разных браузеров, скриптами эмулируя движения мышкой. А тут чистая функция от базы и параметров, детский сад.
Или каждый запрос оформлять в отдельный метод и писать на него тест, в котором наполнять базу данных и проверять правильно ли прочиталось из базы каждое поле?Мне даже интересно стало, а как ты по-другому собрался делать? Ты будешь руками накликивать, создавая пользователей, тэги, накручивать лайки и звездочки, а потом пристальным взглядом смотреть, что все правильно выводится? А что если UI делает отдельный человек уже после того как ты предоставил API?
Дальше если столбцы из нее не используются, то оптимизатор удалит ненужные джоины. В общем, ты все параметры по которым собираешься фильтровать или сортировать выводишь во вьюху, а потом в самом конце генеришь sql типа "select col1,col2,... from superview where some_filter_col = ? order by some_sort_col".Т.е. по человечески SQL запросы писать нельзя.
Спасибо, но в продакшен я с таким не пойду.
Тесты тут обязательны конечно. Нужна стандартная фикстура с несколькими пользователями, вопросами, тегами etc. Относительно этой фикстуры пишутся все тесты.
Никто не спорит, что тесты будут. Вопрос сколько и какие тесты писать?
С одной стороны, со слов Kent Beck на сегодняшний день не существует универсальной теории какие тесты писать, а какие не писать.
С другой стороны, у нас есть конкретный пример. Покажи, как ты бы оформил код, чтобы его можно было тестировать? И какие бы тесты ты бы написал?
С другой стороны, у нас есть конкретный пример. Покажи, как ты бы оформил код, чтобы его можно было тестировать? И какие бы тесты ты бы написал?Загоняешь данные в пустую базу, дергаешь функцию, сравниваешь результат с эталоном, откатываешь транзакцию.
Так даже быстрее будет чем руками тыкать.
Сколько и какие тестовые данные подготовишь?
С какими параметрами будешь вызывать запрос?
Сколько строчек/символов кода у тебя это займет?
Как долго ты это будешь отлаживать?
Какое количество ошибок сделаешь в тестах?
Сколько времени потратишь на всё это?
Интересно обсудить, какие бы ты использовал подходы, и какой бы ты рекомендовал стиль код для решения задач:Тимлиды бывают двух видов. У одних есть хороший, проверенный временем стиль, который они рекомендуют другим разработчиками, приводя обоснования. Другие умение работать с баранами.
- генерация dbml из БД
- генерация кода на основе dbml
- исполнение EF-запросов
- проверки того, что в этих генерациях минимизированы ошибки
Проблему взаимодействия C#/Java/JS с базой осознают оба ведущих поставщика RDBMS: Oracle и Microsoft.
Слайд с сайта Oracle:
Oracle видит решение в том, чтобы запихнуть весь SQL в объекты базы.
Microsoft встроила в C# язык запросов.
Я готов сравнивать эти подходы с Controllable Query, но не готов сделать суицид, как рекомендует прокаченный в работе с баранами тимлид.
Я готов сравнивать эти подходы с Controllable Query, но не готов сделать суицид, как рекомендует прокаченный в работе с баранами тимлид.Потратить два часа на пост, смысл которого в том, чтобы просто послать меня на хуй? Интересно, сколько стульев сгорело, пока ты его писал.
Что поделать, программистишко из тебя так себе. Может быть, тебе стоит поискать смысл жизни в чём-то другом? Например, пойти работать в макдак, уровня твоего интеллекта как раз хватит. Всего лишь нужно научиться вместо "Controllable Query" всё время кричать "Свободная касса".
Ты используешь RDBMS как key value storage, пишешь говно фрейворки на атрибутах. Честно говоря, я упорно до последнего думал, что ты что-то из себя представляешь как программист. Оказывает всё гораздо хуже, ты реализовываешь наихудшие практики.
Ты используешь RDBMS как key value storage, пишешь говно фрейворки на атрибутах. Честно говоря, я упорно до последнего думал, что ты что-то из себя представляешь как программист. Оказывает всё гораздо хуже, ты реализовываешь наихудшие практики.Напиши, какой я плохой, в СпортЛото. Там тебя поймут, я гарантирую это.
Ты уже писал?
Ты уже писал?Нет, мне просто нравится творчество Высоцкого.
Видимо, до тебя и в его творчестве не всё доходит. Иначе сложно объяснить твою гаденькую душонку.
Видимо, до тебя и в его творчестве не всё доходит. Иначе сложно объяснить твою гаденькую душонку.Я понимаю, что ты долго и упорно писал ко-ку, а в итоге получилось никому не нужное говно. Но нельзя же судить людей только за то, что они сказали тебе правду. Хотя вряд ли эти слова до тебя дойдут, ведь в твоём понимании "форум плохой", а "Шурик хороший".
Как только мне скажут как работать из C# с SQL Server, я тут же откажусь от Controllable Query.
Насколько я понял, ты где-то пишешь чистые функции (boolean, boolean, ...) => SQLQuery, как-то аннотируешь их, и во время билда генерятся все пермутации аргументов и выпоняются запросы?
Из C#/Java работа с RDMS идет через ADO.NET/JDBC. Формируется строка SQL запроса и коллекция параметров — в ADO.NEТ для этих целей используется SqlCommand, в JDBC — PreparedStatement. Запрос отправляется в СУБД, и мы получаем DataReader (в JDBC это ResultSet). Поля DataReader обычно мепят на свойства некоторого типа T001 — названия и типы свойств T001 совпадают с названиями и типами полей в DataReader. В коде это выглядит так:
static IEnumerable<T001> XxxQuery(DateTime? startDate, DateTime? endDate)
{
var command = new SqlCommand;
var builder = new StringBuilder(@"
SELECT Id, CreateDate
FROM Post
WHERE 1 = 1");
if (startDate.HasValue)
{
builder.Append(@"
AND CreateDate >= @startDate");
command.Parameters.AddWithValue("@startDate", startDate.Value);
}
if (endDate.HasValue)
{
builder.Append(@"
AND CreateDate <= @endDate");
command.Parameters.AddWithValue("@endDate", endDate.Value);
}
command.CommandText = builder.ToString;
return command.GetEnumerable<T001>
}
interface T001
{
int Id {get;}
DateTime? CreateDate {get;}
}
Controllable Query делает следующее. Находит все методы, где вызывается сетер свойства command.CommandText. Тестирует найденные методы. Смотрит на типы параметров метода. Для типов можно задать наборы тестовых значений. Например, для "DateTime?" — null и new DateTime(2001, 1, 1); для string — null и "test string". Если встретился тип, для которого не задан набор тестовых значений, то ругаемся. CQ вызывает тестируемый метод, получает sql-строку, набор sql-параметров и тип результата T001. Затем он оправляет sql-строку и sql-параметры в СУБД в режиме schema only. В этом режиме СУБД проверяет валидность запроса и возвращает названия и типы полей, которые есть в DataReader. CQ проверяет, совпадают ли названия и типы полей в DataReader с названиями и типами свойств T001.
Еще CQ может отдать sql-строку и sql-параметры в СУБД и спросить у СУБД, используется ли в этом запросе заданная таблица, колонка, вьюшка и т.д. CQ показывает имя файла и номер строки, где начинается c#-метод, который сформировал данную sql-строку.
Мимо тебя совершенно точно проходит, что ещё больше говорят о повышении читаемости кода, автоматическом тестировании и сокращении boilerplate. Кстати, ты всё время сливаешься в спорах про языки с динамической типизацией, т.к. скудоумие не даёт тебе понять, что для некоторых групп разработчиков читаемость более приоритетна, чем все плюсы статической типизации вместе взятые.Вот определение читаемости кода из Википедии:
In computer programming, readability refers to the ease with which a human reader can comprehend the purpose, control flow, and operation of source code. It affects the aspects of quality above, including portability, usability and most importantly maintainability.
Readability is important because programmers spend the majority of their time reading, trying to understand and modifying existing source code, rather than writing new source code.
http://en.wikipedia.org/wiki/Computer_programming#Readabili...
Именно maintainability посвящено большинство моих постов в этом разделе.
В больших коммерческих проектах очень важную роль начинают играть средства поиска, навигации по коду, возможность безболезненно поменять код и т.д. Уменьшение mutable состояния очень сильно способствует всему перечисленному.
Ты, видимо, под читаемостью подразумеваешь что-то своё личное, какие-то особенности твоего вкуса.
Ты, видимо, под читаемостью подразумеваешь что-то своё личное, какие-то особенности твоего вкуса.Запомни эту фразу, подойди к зеркалу и произнеси её 1024 раза.
Запомни эту фразу, подойди к зеркалу и произнеси её 1024 раза.Мой вкус относительно языков совпадает с командой Хельсберга, которая делает C# и TypeScript.
Вкус относительно работы с СУБД совпадет с теми, которые не любят ORM, не любят expression trees. Это отражено в так называемых микро-орм.
Мой вкус относительно языков совпадает с командой Хельсберга, которая делает C# и TypeScript.Да что ты говоришь! И какие нотки ты различаешь в языках? Корицу и фрукты?
Мой вкус относительно языков совпадает с командой Хельсберга, которая делает ... TypeScript.Боюсь, это вкус говнеца - геттеры, сеттеры, аннотации.
Боюсь, это вкус говнеца - геттеры, сеттеры, аннотации.Расскажи про свой вкус. Например, GraphQL — читаем Why invent something new? — описывается проблемы, которых при удаленном взаимодействии C#<->C# просто нет.
... grow into a maintenance nightmare ..
Необразованные дети. Статическая типизация является отличным средством для maintenance.
Например, GraphQL — читаем Why invent something new? — описывается проблемы, которых при удаленном взаимодействии C#<->C# просто нет.Ну тупи, GraphQL - это кроссплатформенный формат, разработан чтобы сервер на каком-нибудь PHP обменивался данными с js, java, objective-c. Обмениваться данными C#<->C# никому не нужно.
кроссплатформенный формат, разработан чтобы сервер на каком-нибудь PHP обменивался данными с js, java, objective-c.Архитекторы астронавты разрабатывают "кросплатформенные" форматы каждые два года. Мне уже скучно.
Заказчику нужно работающее приложение. А кросплатформенность это забота программиста, выбирающего зоопарк технологий.
И, да, моему работодателю нужен C#<->C#.
Уменьшение mutable состояния очень сильно способствует всему перечисленному.Имхо, сейчас есть единственный простой язык с немутабельными структурами данных - clojure, и он динамический. Там есть удобные функции, например assoc-in.
На статических языках ничего похожего по простоте и удобству нет.
Имхо, сейчас есть единственный простой язык с немутабельными структурами данных - clojure, и он динамический. Там есть удобные функции, например assoc-in.На статических языках ничего похожего по простоте и удобству нет.Зачем впадать в академические крайности? Если для мутабельности есть причины, то мутабельность надо использовать. Для сложения двух чисел, взятых из текстбоксов, такой причины нет. Для энтерпрайза статический C# и мутабельность вполне себе сочетаются. Кстати, Roslyn запилили тоже во много имутабельным.
Если для мутабельности есть причины, то мутабельность надо использовать.На C# тебе придется использовать мутабельность, даже если причин для этого нет. Ты банально не сможешь без бойлерплейта сделать такое:
(def users [{:name "James" :age 26} {:name "John" :age 43}])
;; update the age of the second (index 1) user
(assoc-in users [1 :age] 44)
;;=> [{:name "James", :age 26} {:name "John", :age 44}]
Зачем впадать в академические крайности?Когда-то структурное программирование считалось академической крайностью, потом сборка мусора, потом первоклассные функции. То, что с точки зрения более низкоуровнего языка является крайностью, для более высокоуровневого - естественный идиоматичный код.
Ты банально не сможешь без бойлерплейта сделать такое:без статической проверки:
users = users.AssocIn(1, "age", 44);
со статическими проверками
users = users.AssocIn(1, _=>_.age, 44);
Какой тип у функции AssocIn?
IEnumerable<T> AssocIn<T, TValue>(this IEnumerable<T> items, int index, Expression<Func<T, TValue>> f, TValue v);
assoc-in работает на структуры данных любой глубины, там может быть любая последовательность массивов и ассоциативных массивов.
users = users.Update(_=>_.By(1).Cars.By(0).Wheels.By(3).Disk.Color, Color.Red);
IEnumerable<T> Update<T, TValue>(this IEnumerable<T> items, Expression<Func<T, TValue>> f, TValue v);
Ты пишешь на псевдокоде или на C#? На C# ты такого не сделаешь без зашкаливающего количества магии, которая сделает идею непрактичной. Если на псевдокоде, то я согласен, что можно реализовать такой статически типизированный язык, но пока никто не сделал.
Ты уверен, что понимаешь, как работает assoc-in?Да, понимаю.
Ты пишешь на псевдокоде или на C#?на C#.
Update с expression-ом - это сахар для:
users = users.Update(1, user => user.With(cars:user.Cars
.Update(0, car => car.With(wheels:car.Wheels.Update(3, wheel => wheel.With(disk:wheel.Disk.With(color:Color.Red;
... причин для этого нет. ...Как это нет причин, слово update является прямой причиной, указанной в условии задачи.
...
update ...
Учитесь читать то, что вас просят сделать. И не занимайтесь отсебятиной.
Update с expression-ом - это сахар дляОткуда берутся функции users.Update и user.With?
Откуда берутся функции users.Update и user.With?Update - это функция immutableCollection, With - это функция, которая генерится для каждого immutable-класса.
которая генерится для каждого immutable-класса.Кем генерится?
Кем генерится?codegenerator-ом. Который перед build-ом пробегает исходники, парсит их с помощью Roslyn-а, выдергивает immutable-классы и для них генерит сахар-ные функции
codegenerator-ом. Который перед build-ом пробегает исходники, парсит их с помощью Roslyn-а, выдергивает immutable-классы и для них генерит сахар-ные функцииДа, если постараться, то кодогенерацией можно навелосипедить, но на практике это неюзабельно.
но на практике это неюзабельно.почему?
на практике это неюзабельно.Ты ищешь практическую юзабельность в академических изысканиях?
почему?Знаешь какие-нибудь примеры, чтобы такого рода кодогенерация была популярна? На практике люди или пишут на том что есть, либо переходят на более высокоуровневый язык.
Знаешь какие-нибудь примеры, чтобы такого рода кодогенерация была популярна?WindowsForms, EntityFramework и Asp.net построены на кодогенерации
WindowsFormsИ где там похожая кодогенерация?
Или ты имеешь ввиду дизайнер? Тк дизайнер это же как раз слабая сторона винформз, о которой лучше умалчивать.
EntityFramework
Говно же.
Asp.net
Ты про какую кодогенерацию? Про ту что в WebForms? WebForms это говнищееееее!
Razor это няшная кодогенерация, которая по хорошему должна стать частью языка C#.
Сейчас ASP MVC 6 сделали еще что-то, опять тащат какое то говно в разметку, вместо чистого Razor.
Согласен, что кодогенерация не самый лучший подход, но вполне юзабельный при аккуратном подходе.
Ты ищешь практическую юзабельность в академических изысканиях?Персистентные структуры данных - это академические изыскания? Лол.
Персистентные структуры данных - это академические изыскания? Лол.В рамках темы этого треда, да.
Боюсь, это вкус говнеца - геттеры, сеттеры, аннотации.Внезапно, Angular2 написали на TypeScript:
Внезапно, Angular2 написали на TypeScript:Да, подобное тянется к подобному:
@Component({
selector: 'talk-list',
viewInjector: [TalksAppBackend]
})
class TalkList {
constructor(backend:TalksAppBackend) {
this.talks = backend.fetchTalks;
}
}
Обычная ситуация в кафкианском мире джава-программирования - я не могу сам передать аргумент в функцию, мне надо просить об этом какой-то Фреймворк™ с помощью Аннотаций™. Сам Фреймворк™ передаст аргумент не просто так, а с помощью Инджектора™, причем не просто Инджектора™, а ВьюИнджектора™ (думаю, там есть иерархия Инджекторов™, унаследованных от какого-нибудь БазовогоИнджектора™).
К счастью, сейчас не 2005 год, и авторы и пользователи этого творчества вызывают даже уже не смех, а откровенную жалость.
Внезапно, Angular2 написали на TypeScript:Кстати, это не совсем так. Вроде бы код ангуляра одновременно является кодом на typescript и на Dart, вроде бы в ci он компилится сразу двумя компиляторами.
и на DartДарт это гугл, а гугл языки писать не умеет.
Обычная ситуация в кафкианском мире джава-программирования - я не могу сам передать аргумент в функцию, мне надо просить об этом какой-то Фреймворк™ с помощью Аннотаций™. Сам Фреймворк™ передаст аргумент не просто так, а с помощью Инджектора™, причем не просто Инджектора™, а ВьюИнджектора™ (думаю, там есть иерархия Инджекторов™, унаследованных от какого-нибудь БазовогоИнджектора™).Я тут нашел классный фрейворк:
К счастью, сейчас не 2005 год, и авторы и пользователи этого творчества вызывают даже уже не смех, а откровенную жалость.
Vanilla JS is a fast, lightweight, cross-platform framework
for building incredible, powerful JavaScript applications.
http://vanilla-js.com/
Идеально сочетается с TS.
vanilla-C# еще зацени, поймешь, что твой CQ нафиг не нужен
vanilla-C# еще зацени, поймешь, что твой CQ нафиг не нуженОтлично! Ты захватил наживку. Я хочу торгануть CQ как ванильный ADO.NET. Поэтому и начал гуглить слово vanilla.
Найди отличие от ванильного C#:
static IEnumerable<T001> XxxQuery(DateTime? startDate, DateTime? endDate)
{
var command = new SqlCommand;
var builder = new StringBuilder(@"
SELECT Id, CreateDate
FROM Post
WHERE 1 = 1");
if (startDate.HasValue)
{
builder.Append(@"
AND CreateDate >= @startDate");
command.Parameters.AddWithValue("@startDate", startDate.Value);
}
if (endDate.HasValue)
{
builder.Append(@"
AND CreateDate <= @endDate");
command.Parameters.AddWithValue("@endDate", endDate.Value);
}
command.CommandText = builder.ToString;
return command.GetEnumerable<T001>
}
interface T001
{
int Id {get;}
DateTime? CreateDate {get;}
}
Отлично! Ты захватил наживку.А ты опасный парень, как я погляжу! Ждём, когда ответы на твои посты будут ставить на комп трояны.
Найди отличие от ванильного C#:Блядь, а это что?
interface T001
Блядь, а это что?Это тип, который возвращает метод XxxQuery.
code:
interface T001
Это тип, который возвращает метод XxxQuery.А 153-ий метод будет возвращать T0153?
А 153-ий метод будет возвращать T0153?Название типа на усмотрение автора кода.
Название типа на усмотрение автора кода.А зачем ты тогда выбираешь такое говнецо?
А зачем ты тогда выбираешь такое говнецо?Это известная проблема:
here we face our first problem, in that an object-oriented system like C# or Java cannot return just "parts" of an object--an object is an object, and if the Person object consists of 12 fields, then all 12 fields will be present in every Person returned. This means that the system faces one of three uncomfortable choices: one, require that Person objects must be able to accomodate "nullable" fields, regardless of the domain restrictions against that; two, return the Person completely filled out with all the data comprising a Person object; or three, provide some kind of on-demand load that will obtain those fields if and when the developer accesses those fields, even indirectly, perhaps through a method call.
(Note that some object-based languages, such as ECMAScript, view objects differently than class-based languages, such as Java or C# or C++, and as a result, it is entirely possible to return objects which contain varying numbers of fields. That said, however, few languages possess such an approach, not even everybody's favorite dynamic-language poster child, Ruby, and until such languages become widespread, such discussion remains outside the realm of this essay.)
http://blogs.tedneward.com/2006/06/26/The+Vietnam+Of+Compute...
Я склоняюсь к четвертому варианту: делать на каждую выборку свой тип. Выборки по разным множествам полей. Не похоже, что есть какие-то хорошие имена для таких типов. Поэтому в качестве имени использую числовой индекс.
Поэтому в качестве имени использую числовой индекс.Имхо, следующий вариант нагляднее: Post_Sub0001. Означает, что данный структурный тип является подмножеством структурного типа Post
Это известная проблема:Проблема-то может и известная, но решение выбрано такое, что меркнут все вместе взятые недостатки предыдущих.
Я склоняюсь к четвертому варианту:Чёрт, всё время забываю поставить галочку, что ты у нас особенный и уникальный.
Проблема-то может и известная, но решение выбрано такое, что меркнут все вместе взятые недостатки предыдущих.Какие недостатки у четвертого варианта?
Чёрт, всё время забываю поставить галочку, что ты у нас особенный и уникальный.Всё ты мне пытаешься приписать уникальность, а я опять скопировал у других . Мотивация для введения в язык анонимных типов:
That sure seems like a lot of boilerplate code to project out a subset of the fields. It’s also often unclear what to name such a type. Is CustomerTuple really a good name? What if we had projected out Name and Age instead? That could also be a CustomerTuple. So, the problems are that we have boilerplate code and it doesn’t seem that there are any good names for the types that we create. Plus, there could also be many different types required, and managing them could quickly become a headache.
MSDN Magazine 2007 June
Проблемы boilerplate кода у меня нет, поскольку T001 генерируется при первом запуске тестов.
А имя типа близко к отсутствию имени.
Какие недостатки у четвертого варианта?А ты сам не понимаешь? В любом случае это бесполезно тебе объяснять, потому что ты упорот.
А ты сам не понимаешь? В любом случае это бесполезно тебе объяснять, потому что ты упорот.Распиши, попытаюсь понять.
Проблемы boilerplate кода у меня нет, поскольку T001 генерируется при первом запуске тестов.Щито? У тебя там больше половины кода - boilerplate. Но этого ты тоже не понимаешь.
Распиши, попытаюсь понять.А ты попытайся понять без расписываний. На тебя тут серьёзно время тратит только . Этого ты тоже не замечаешь, ведь это "форум такой", а ты у нас умница.
А ты попытайся понять без расписываний. На тебя тут серьёзно время тратит только . Этого ты тоже не замечаешь, ведь это "форум такой", а ты у нас умница.Давай ты не будешь указывать что мне делать? Оставь свои привычки пастуха для своих покорных баранов на работе.
Давай ты не будешь указывать что мне делать? Оставь свои привычки пастуха для своих баранов на работе.У тебя что, только что прогорел стул? Или пламя прожгло пол и добалось до соседей?
Я тебе подкину такую идею - пойди напиши на меня ещё одну докладную в разделе Forum. Тему можно назвать так: "Пользователь форумлокал сказал мне подумать". Но пукан перед этим остуди, а то устроишь пожар почище 1812 года.
Имхо, следующий вариант нагляднее: Post_Sub0001. Означает, что данный структурный тип является подмножеством структурного типа PostПроекция может быть из нескольких таблиц. Это чистой воды анонимный тип: просто множество полей. Не требуется заключать в имя типа какую-либо информацию.
На твой пост можно сослаться как #Post12445174. Аналогично, используя T001, мы просто ссылаемся на тип.
Я склоняюсь к четвертому варианту: делать на каждую выборку свой тип.А что делать, если на руках есть выборка T1234 и есть функция, которая принимает в качестве аргумента T4321, являющийся сужением типа T1234? Вешаться?
А что делать, если на руках есть выборка T1234 и есть функция, которая принимает в качестве аргумента T4321, являющийся сужением типа T1234? Вешаться?Зачастила суицидальная риторика в девелопменте. Нелегкая жизнь программиста?
Аргумент передается так:
T123 x = ...
Method1(x.As(default(T321;
Method1(T321 y) {...}
Method1(x.As(default(T321;Отличная ваниль, правда уже отдаёт говном. Осталось ещё посмотреть на примеры кода, когда чукча писатель. В смысле когда тип T4321 состоит из данных, которые в двух других типах, плюс ещё какие-то новые значения, которые нужно вычислить.
Отличная ваниль, правда уже отдаёт говном.
Покажи код, который лучше.
T4321 состоит из данных, которые в двух других типах,Вызываешь метод As с двумя аргументами.
В смысле когда тип T4321 состоит из данных, которые в двух других типах, плюс ещё какие-то новые значения, которые нужно вычислить.
Типы T001 это типы результатов выборки из БД. Вычислениями занимаются другие типы.
Покажи код, который лучше.У тебя даже пример, который специально натянут на ко-ку, и который не решает ни одной задачи полностью, выглядит хуже обычного ADO.NET
Вызываешь метод As с двумя аргументами.
С какими? Названия свойств не пересекаются.
Типы T001 это типы результатов выборки из БД. Вычислениями занимаются другие типы.Ага, после ванили со вскусом говна начинаются искусственные ограничения. То есть ко-ку за все эти годы так и не научилась решать банальные задачи? Выгрузить объект из БД, поменять поле, записать обратно? Выгрузить два объекта, сделать из этих данных третий, записать? То есть это не просто убогое говно с вырвиглазным стилем кода, но ещё и за все эти годы оставшееся абсолютно бесполезным?
С какими? Названия свойств не пересекаются.Method1(x.As(y, default(T321;
Реальным неудобством метода As является то, что нужно запускать дополнительный find usages. Стандартный плюс дополнительный. Это не удобно, да.
У тебя даже пример, который специально натянут на ко-ку, и который не решает ни одной задачи полностью, выглядит хуже обычного ADO.NETВызываешь метод As с двумя аргументами. С какими? Названия свойств не пересекаются.Типы T001 это типы результатов выборки из БД. Вычислениями занимаются другие типы.Ага, после ванили со вскусом говна начинаются искусственные ограничения. То есть ко-ку за все эти годы так и не научилась решать банальные задачи? Выгрузить объект из БД, поменять поле, записать обратно? Выгрузить два объекта, сделать из этих данных третий, записать? То есть это не просто убогое говно с вырвиглазным стилем кода, но ещё и за все эти годы оставшееся абсолютно бесполезным?Пока ты не напишешь код (и будет с чем сравнить твои слова банальная ложь.
Method1(x.As(y, default(T321;И как это поменяет тип T312 { string Name; int Age; } на T647 { int AgeInHours; string ShortName; string LongName; }?
И как это поменяет тип T312 { string Name; int Age; } на T647 { int AgeInHours; string ShortName; string LongName; }?Я думал, что идет речь о вот таком условии задачи:
T001 x = ...
T001 {string Name; int Age;}
T002 y = ...
T002 {int AgeInHours; string ShortName; string LongName;}
Выходной тип:
T312 { string Name; string ShortName; string LongName; }
Я думал, что идет речь о вот таком условии задачи:И что предлагается для решения задачи с этим условием?
T001 x = ...T001 {string Name; int Age;}
T002 y = ...T002 {int AgeInHours; string ShortName; string LongName;}
Выходной тип:T312 { string Name; string ShortName; string LongName; }
И что предлагается для решения задачи с этим условием?Типы Txxx это интерфейсы. В рантайме генерируется реализация T312, которая перенаправляет вызовы к x и y.
В тестах в сборке находятся все вызовы метода As, смотрим какие generic параметры и проверяем совместимость. Аналогичным образом реализуем find usages.
Типы Txxx это интерфейсы. В рантайме генерируется реализация T312, которая перенаправляет вызовы к x и y.Как она генерируется, где ты указал, что у неё всего три конкретных свойства?
Как она генерируется, где ты указал, что у неё всего три конкретных свойства?В методе As генерируется реализация интерфейса TResult.
class Program
{
void Method0(T001 x, T002 y)
{
Method1(x.As(y, default(T312;
}
void Method1(T312 z)
{
throw new NotImplementedException;
}
}
public interface T001
{
string Name { get; }
int Age { get; }
}
public interface T002
{
int AgeInHours { get; }
string ShortName { get; }
string LongName { get; }
}
public interface T312
{
string Name { get; }
string ShortName { get; }
string LongName { get; }
}
public static class Util
{
public static TResult As<T1, T2, TResult>(this T1 arg1, T2 arg2, TResult resultPrototype)
{
...
}
}
В методе As генерируется реализация интерфейса TResult.И как это работает с условиями, которые озвучивались выше? Например, "Названия свойств не пересекаются." То есть всё сломается, если у меня AgeInYears и Age?
И как это работает с условиями, которые озвучивались выше? Например, "Названия свойств не пересекаются." То есть всё сломается, если у меня AgeInYears и Age?Кто тебя занет, о каком пересечении ты говоришь. Может ты меешь ввиду, что нет пересечений между T001 и T002.
То есть всё сломается, если у меня AgeInYears и Age?
Статическая проверка не пройдет.
Статическая проверка не пройдет.То есть мы пришли к ещё одному ограничению твоей говнотехнологии.
твои слова банальная ложь.Вот это тебе и нужно доказать, написав код, который решает хотя бы одну реальную задачу. Пока нет кода, который выгружает два объекта, делает из этих данных третий с другими названиями свойств, и записывает его в БД, говорить вообще не о чем.
То есть мы пришли к ещё одному ограничению твоей говнотехнологии.Еще одного ограничения нет. Опять обманываешь.
Вот это тебе и нужно доказать, написав код, который решает хотя бы одну реальную задачу. Пока нет кода, который выгружает два объекта, делает из этих данных третий с другими названиями свойств, и записывает его в БД, говорить вообще не о чем.Не о чем говорить, потому что ты не формулируешь задачу. Напиши нормальное условие задачи.
Не о чем говорить, потому что ты не формулируешь задачу. Напиши нормальное условие задачи.Выгружаются два списка, каждый из которых состоит из объектов с разными названиями полей. В твоём стиле пусть это будет: T1000 { int Id; string Name; int Age; int Version; } и T0047 { string Name; string BlobText; int Val; } Для этой задачи будем считать, что Id - это primary key, а пересечение по Name не выгодно для выгрузки с помощью JOIN даже при условии последующей фильтрации.
Нужно создать список из объектов T3000 { int Id; string BlobPreview; int AdjustedVal; }, который получается объединением T1000 и T0047 по Name, с фильтром когда BlobText содержит некоторую подстроку, BlobPreview является подстрокой из первых 100 символов, а AdjustedVal равен Age + Val.
Типы T1000 и T3000 передаются в стороннюю библиотеку. Названия их свойств не совпадают с названием столбцов в БД. В частности в нашем примере T1000 хранится в NameAgeVersionTable (Id, ShortName, VersionRenamed а T3000 хранится в PreviewTable (Id, BlobPreviewRenamed, AdjustedValRenamed)
После создания списка T3000 происходит запрос, который делает MERGE INTO PreviewTable (которая для T3000) USING (VALUES...) ON ...Id = ...Id WHEN NOT MATCHED BY TARGET INSERT, иначе UPDATE полей BlobPreviewRenamed и AdjustedValRenamed
После этого запроса сразу же идёт запрос, который делает UPDATE NameAgeVersionTable (которая для T1000) SET VersionRenamed = VersionRenamed + 1 для всех Id из списка обновления по T3000
Все запросы выполняются в одной транзакции с уровнем изоляции RepeatableRead. Запросы должны выполняться батчами, то есть для обычного SQL Server 2012 + ADO.NET внутри USING VALUES содержит как минимум по 300 значений. То же касается, например, условия Id IN (...) для T1000.
хотя бы одну реальную задачу.
Честно говоря, я мало что понял из твоего потока сознания, увидел лишь признаки реально задачи:
1. Типы T1000 и T3000 передаются в стороннюю библиотеку.
2. VersionRenamed, BlobPreviewRenamed, AdjustedValRenamed.
Поскольку подача материала и при первом прочтении сам материал меня не привлек, то займусь им, если будет свободное время. Надо продираться через твой поток сознания.
P.S. И да, если ссылаешься на мой стиль, то, пожалуйста, не перевирай его. У меня используется три цифры, больше тысячи типов я не использую. Поправь, пожалуйста, либо убери ссылку на мой стиль. Иначе я этим заниматься не буду.
Поскольку подача материала и при первом прочтении сам материал меня не привлек, то займусь им, если будет свободное время. Надо продираться через твой поток сознания.Слив засчитан.
Слив засчитан.Буду надеяться, что ты больше не будешь проявлять интереса к Controllable Query.
Буду надеяться, что ты больше не будешь проявлять интереса к Controllable Query.Не льсти себе, к этому говну никто не проявляет никакого интереса. Тебя просто лениво и зачастую весьма жЫрно потролливают. Правда в последнее время ты генерируешь всё меньше лулзов и всё больше клиники...
Ты думаешь от вот этого весело кому то?
Выгружаются два списка, каждый из которых состоит из объектов с разными названиями полей. В твоём стиле пусть это будет: T1000 { int Id; string Name; int Age; int Version; } и T0047 { string Name; string BlobText; int Val; } Для этой задачи будем считать, что Id - это primary key, а пересечение по Name не выгодно для выгрузки с помощью JOIN даже при условии последующей фильтрации.
Нужно создать список из объектов T3000 { int Id; string BlobPreview; int AdjustedVal; }, который получается объединением T1000 и T0047 по Name, с фильтром когда BlobText содержит некоторую подстроку, BlobPreview является подстрокой из первых 100 символов, а AdjustedVal равен Age + Val.
Типы T1000 и T3000 передаются в стороннюю библиотеку. Названия их свойств не совпадают с названием столбцов в БД. В частности в нашем примере T1000 хранится в NameAgeVersionTable (Id, ShortName, VersionRenamed а T3000 хранится в PreviewTable (Id, BlobPreviewRenamed, AdjustedValRenamed)
После создания списка T3000 происходит запрос, который делает MERGE INTO PreviewTable (которая для T3000) USING (VALUES...) ON ...Id = ...Id WHEN NOT MATCHED BY TARGET INSERT, иначе UPDATE полей BlobPreviewRenamed и AdjustedValRenamed
После этого запроса сразу же идёт запрос, который делает UPDATE NameAgeVersionTable (которая для T1000) SET VersionRenamed = VersionRenamed + 1 для всех Id из списка обновления по T3000
Все запросы выполняются в одной транзакции с уровнем изоляции RepeatableRead. Запросы должны выполняться батчами, то есть для обычного SQL Server 2012 + ADO.NET внутри USING VALUES содержит как минимум по 300 значений. То же касается, например, условия Id IN (...) для T1000.
И потом код всё равно нужен для сравнения.
Тролинг это когда весело.Ты думаешь от вот этого весело кому то?Деточка, троллинг - это когда весело троллю.
Кстати, напиши свой поток сознания на C#, так будет понятнее. И потом код всё равно нужен для сравнения.Ты уже слился, зачем мне теперь что-то писать?
Может и не весело, но в общих чертах вполне понятно.
Деточка, троллинг - это когда весело троллю.То есть ты веселишься, выливая поток сознания на форум, а изложить это в понятной форме это уже не так весело?
Подожди, а зачем мне вестись на тролинг?
Может и не весело, но в общих чертах вполне понятно.В общих чертах там проблем нет, решение очевидно в общих чертах. Распишите детали на C#.
Вау, нас еще кто-то читает.
Распишите детали на C#.Если я и буду этим заниматься, то сначала сделаю код на ADO.NET без CQ. Для того чтобы согласовать с Майком, что он именно это имел в виду. А потом добавлю тесты посредством CQ.
До тех пор пока никто не написал вариант на ванильном ADO.NET говорить о том, что CQ плох или хорош смысла нет.
Подожди, а зачем мне вестись на тролинг?Я не знаю, зачем ты на него ведёшься. Я думаю, что ты клоун по жизни, и призван радовать читателей Society и Development.
Для того чтобы согласовать с Майком, что он именно это имел в виду.Да не оправдывайся, и так понятно, что ты слился. Задачка-то тривиальная, на семействах ORM light и Dapper решается за 15 минут, если не проверять корректность. А её и не планировалось проверять, мы же оцениваем ограничения ко-ку.
нас еще кто-то читает.Я еще иногда почитываю. Через страницу (у меня на страницу по 40 постов).
Неконструктивная в теме беседа. Диалог нептуниста и плутониста.
ORM light и Dapper решается за 15 минутПриведи код.
CQ отработает как положено.
Неконструктивная в теме беседа.Когда у меня с Майком была конструктивная беседа? Майк пытается за счет меня само утверждаться. И у него это все время не выходит.
CQ отработает как положено.Ага, ага, но код написать ты не осилил... Конечно, конечно...
Ага, ага, но код написать ты не осилил... Конечно, конечно...Именно код не осилил написать ты. А для меня основное препятствие не код написать, а детально разобраться в твоем потоке сознания.
Именно код не осилил написать ты.На нормальной технологии там писать-то нечего. Будет 3 DTO класса и 4 метода.
А для меня основное препятствие не код написать, а детально разобраться в твоем потоке сознания.Заметь, Бобровников всё понял, а наш супер-герой, у которого типа скиллы по работе с кодом, уже час ноет. В моей задаче каждый пункт делается в 2-3 строки, кроме основного MERGE. Наверное, ты просто не знаешь SQL для SQL Server.
Заметь, Бобровников всё понял, а наш супер-герой, у которого типа скиллы по работе с кодом, уже час ноет. В моей задаче каждый пункт делается в 2-3 строки, кроме основного MERGE. Наверное, ты просто не знаешь SQL для SQL Server.
Бобровников понял ВСЁ, а я ничего не знаю. Раз он понял всё пусть напишет код.
Напиши код. Или сам не понимаешь что написал?Да ты не волнуйся, я уже написал. И в контексте полного слива ко-ку я запощу свой код.
Ну так пости.
Ну так пости.Нееет. Сначала ты напишешь, что ты слился.
Нееет. Сначала ты напишешь, что ты слился.Я ж не ты, ложь писать не привык.
Давайте так, вы оба пришлете мне код. Я подтверждаю появление кода от каждой стороны, но не показываю его. Как только оба кода будут у меня, я их выкладываю в одном посте. Обязуюсь код не читать!
Я ж не ты, ложь писать не привык.Так ты напиши правду. Она же в том, что ты слился.
Майк хочет показать, что CQ не отработает проверки. Какая разница кто первый запостит код?
Кое какие проверки может и не отработают автоматически, но CQ это никогда и не заявлял. CQ всегда позиционировался как помощник в написании тестов.
Так ты напиши правду. Она же в том, что ты слился.Прикинь, я не слился.
Майк хочет показать, что CQ не отработает проверки. Какая разница кто первый запостит код?Какие проверки? До проверок дело не дошло. У тебя три типа с разными названиями свойств не работают. О чём ты сам сказал выше. И вдруг "Еще одного ограничения нет. Опять обманываешь." Вот я и дал тебе задачку, где имена столбцов отличаются от имён свойств, а типы надо собрать из других типов и не только читать, но и записывать в базу.
Прикинь, я не слился.Нет, нет, ты именно что слился. Это уже понятно. Может быть, если бы ты попытался написать хоть какой-то код, это бы немного сгладило твой слив. Но ты не можешь этого сделать. Хотя на семействе orm light / dapper там 100 строк кода брутто, включая boilerplate.
orm light / dapper там 100 строк кода брутто, включая boilerplate.которые ты не осилил запостить
которые ты не осилил запоститьПо рыцарски предлагаю тебе смягчить твой слив, ожидая, что ты хотя бы попытаешься решить задачку.
По рыцарски предлагаю тебе смягчить твой слив, ожидая, что ты бы попытаешься решить задачку.Так там задачку еще надо решать? Я думал, мы про проверки говорим. Или это у тебя привычка дает о себе знать, поскольку всё на чем ты набил руку это решать задачки с собеседований. О реальных задачах, которые встают на работающем проекте, ты не знаешь. Поэтому и не догоняешь зачем CQ, и как без него плохо.
Так что пока ты сливаешься.
Вы два ушлепка. Но для постановки задачи ты мог бы написать нормальный код, а шурик - переделать его с CQ. Потому как по твоей постановке неясно что ты имел в виду.Так что пока ты сливаешься.Опа, явление великого и ужасного мистера "знаю всё лучше всех". Понятное дело, что тебе не ясно, ты же не умеешь программировать.
Так там задачку еще надо решать? Я думал, мы про проверки говорим. Или это у тебя привычка дает о себе знать, поскольку всё на чем ты набил руку это решать задачки с собеседований. О реальных задачах, которые встают на работающем проекте, ты не знаешь. Поэтому и не догоняешь зачем CQ, и как без него плохо.Детка, ты ответил на все посты, но продолжаешь активно игнорировать суть. Напомню, мы говорили, что есть три типа данных с разным набором свойств, имена которых не совпадают. Надо взять два и сделать третий.
Всем читателям этого треда, давайте попросим Майка запостить код. Разве вам не интересно как сольется CQ?
Бля, сегодня никакой работу. Пойду поработаю.
Опа, явление великого и ужасного мистера "знаю всё лучше всех". Понятное дело, что тебе не ясно, ты же не умеешь программировать.На вашем ентерпрайзном говне не умею, конечно. Мы же не раз это с тобой обсуждали.
Код от Майка получен. Жду Шурика.
Я должен продемонстрировать, как CQ проверяет запросы, поэтому сначала надо получить код от Майка. Пусть будет код, а то Майк опять начнет словоблудить с условием задачи.
Считаю, что ты должен написать свой код без его.
Код пришел давно. Просто я был не у компа - ездил забирать авто из ремонта.А зачем ты вообще нужен?
Майк мог выложить zip архив с паролем, а пароль выложить позже.
Не вижу смысла писать мне код, пока я не увижу, что хочет Майк.
Считаю, что ты должен написать свой код без его.Я и так сегодня задержался на работе из-за этого, больше тратить время на неконструктив не буду.
Считаю, что ты должен написать свой код без его.Какова причина, почему Майк не выкладывает код? Что он хочет показать?
Все. Более дискутировать не буду. Получу код от тебя, выложу оба.
При этом лично мне не важно, что конкретно хочет показать Майк.
Не вижу смысла писать мне код, пока я не увижу, что хочет Майк.Пиши код, потом посмотришь на код майка и перепишешь. По пути обзовем его идиотом.
Мы же не раз это с тобой обсуждали.Обсуждали? Вряд ли с тобой можно что-нибудь обсуждать. Я бы сказал, что мы определённо перекидывались постами, но этот процесс вряд ли можно назвать обсуждением.
Конечно, могут быть некоторые разночтения, но явно не сильно существенные.Нет, разночтения могут быть вполне существенными. К постановке задачи придраться намного легче, чем к решению. Но постановку всегда можно уточнить.
Например, "Типы T1000 и T3000 передаются в стороннюю библиотеку". Можно уточнить, что из-за этого нельзя менять названия свойств, типы должны быть ровно такими, как описаны. Можно уточнить, что тип T0047 хранится в таблице на усмотрение решающего, потому что в условии про это не написано.
Можно сразу сказать, что объединение T1000 и T0047 по Name выполняется через LINQ Join. Вообще всё условие для построения списка из T3000 - это LINQ Where и Join. Можно написать формально и с ошибками, это не главная часть задачи. Здесь важно посмотреть, как тип T3000 создаётся в рантайме.
Условие про MERGE можно переформулировать попроще. В конце концов это стандартная операция, можно было бы написать так: для полученного списка из T3000 нужно выполнить MERGE по Id, который либо заканчивается INSERT, либо UPDATE всех полей.
Можно уточнить, что в транзакцию достаточно поместить только две последние операции записи. Это действительно серьёзная неточность в моём условии. Впрочем, переделать код одинаково не составит труда.
Последнее условие про батчинг означает, что операции MERGE и UPDATE может не получиться выполнить одним запросом, поэтому список из T3000 нужно разбить на куски или использовать другой подход. (Кстати, как оказалось, в моём фреймворке это не встроено, поэтому моё решение содержит ошибку, и к нему нужно будет добавить пару строк.)
В общем, уточнить можно всё и всегда. Вместо этого в ответ написан пост, за который просто засчитывается слив.
Хочу уточнить две вещи: ждать ли мне код от Шурика, и выкладывать ли код Майка, если от Шурика ничего не будет?
Хочу уточнить две вещи: ждать ли мне код от Шурика, и выкладывать ли код Майка, если от Шурика ничего не будет?Я могу и сам выложить, там стесняться нечего. Но тогда давай я это сделаю в новой теме.
Обсуждали? Вряд ли с тобой можно что-нибудь обсуждать. Я бы сказал, что мы определённо перекидывались постами, но этот процесс вряд ли можно назвать обсуждением.Да, я тоже помню что ты тогда очень тупил. Я на тебе совсем уж было крест поставил, а вишь, оказывается, это могло быть досадное недоразумение. Пости код, реабилитируйся.
Да, я тоже помню что ты тогда очень тупил. Я на тебе совсем уж было крест поставил, а вишь, оказывается, это могло быть досадное недоразумение. Пости код, реабилитируйся.Ты поставил на мне крест? Это приятный комплимент от человека, который изображает из себя гиганта мысли и знания, а на деле обладает интеллектом обычного деревенского Ваньки, который постоянно ищет куда бы ему сунуться, чтобы нос не прищемили.
Ты поставил на мне крест? Это приятный комплимент от человека, который изображает из себя гиганта мысли и знания, а на деле обладает интеллектом обычного деревенского Ваньки, который постоянно ищет куда бы ему сунуться, чтобы нос не прищемили.Как же так, тебя оказывается можно загнобить даже с деревенским интеллектом
Как же так, тебя оказывается можно загнобить даже с деревенским интеллектомС деревенским интеллектом можно загнобить только себя, но вряд ли потом получится это понять.
С деревенским интеллектом можно загнобить только себя, но вряд ли потом получится это понять.Давай ссылку на ту историю, помогу, объясню
Давай ссылку на ту историю, помогу, объяснюМне не нужны объяснения деревенского дурачка, и тем более его помощь.
Мне не нужны объяснения деревенского дурачка, и тем более его помощь.Ок, передам . Мое предложение в силе.
Вернемся к нашим двум баранам: Так что там с кодом? Ты слился или как?
Вернемся к нашим двум баранам: Так что там с кодом? Ты слился или как?Ты хочешь поучаствовать? Тогда решай задачу. Если нет, то предлагаю тебе проследовать на хуй.
Вынужден отклонить ваше предложение. Можете сбежать с шуриком в приват, поджав хвост. Иначе страдайте.
Вынужден отклонить ваше предложение. Можете сбежать с шуриком в приват, поджав хвост. Иначе страдайте.Какой грозный вояка! Послан на хуй, но не сдаётся и потрясает кулачками.
Неплохое начало.
Но пукан перед этим остуди, а то устроишь пожар почище 1812 года.
новый инпут, который сразу работает с отпарсенными данными (у меня там intInput, который принимает и возвращает Maybe Int).
Я тут вдруг вспомнил, что твой новый инпут это фактически мой метод расширения для текстбокса Option<int> GetInt32(this TextBox it)
Результат совпал. Только у тебя обоснование не убедительное "загрязнять доменную модель". А у меня четкое обоснование — кешируем результат функции int.Parse. В реальности такое кеширование вряд ли понадобится.
Я правильно понимаю что в дотнете текстовый инпут одновременно является хэштаблицей, в которую кто угодно может пихать что угодно?
Я правильно понимаю что в дотнете текстовый инпут одновременно является хэштаблицей, в которую кто угодно может пихать что угодно?У input-а есть одно поле, в которое кто угодно может пихать что угодно. Если договориться, что в этом поле будет хэштаблица, то тогда кто угодно сможет хранить что угодно под своим ключем.
поддержка синтаксиса jsx.
Хотя я считаю, что jsx - ненужный синтаксический наворот, но оцениваю новость положительно - она приближает окончательное доминирование реакта.
Тем временем, в typescript 1.6 заявлена Хотя я считаю, что jsx - ненужный синтаксический наворот, но оцениваю новость положительно - она приближает окончательное доминирование реакта.
Оставить комментарий
6yrop
На какой платформе и в каком фрейворке можно легко добавлять, удалять, менять обработку поля? Поле должно легко протаскиваться и искаться по всем слоям от БД до пользовательского интерфейса. При этом никаких ограничений от фрайворка, должна быть возможность подкрутить что-нибудь на любом слое. И должно быть прозрачно как это сделать.