Подарки от программис'тов
Допотопное и самописное. Появилось примерно в одно время с хибернейтом, может года с 2002-2003, потом проект свернули. В проекте, в котором я это видел, заменять ни на что не стали, а купили права, и потом 10 лет допиливали его сами. Уже и как называлось не вспомню.
Кто-нибудь встречал такой кейс, что базовые типы, типа строк, чисел, булевых и прочих (но не все) переписаны, в них добавлена встроенная валидация, способность определять, можно ли их отрисовывать (на основе встроенной авторизации и аутентификации), присутствуют состояния и сообщения об ошибках (т.е. в "базовых" типах заведена куча полей, логики, они сами из себя представляют лютую иерархию наследования)? И все остальные модели строятся на этих кастомным "базовых" типах.
Щас ещё глянул - как я писал, эти базовые типы наследуются от другого базового, у которого есть уникальный идентификатор GUID. И от этого базового наследуются вообще все типы - и строки, числа, булевы и т.п. данные, и всякие более высокоуровневые, типа меню, таблиц, отдельных строк таблиц, форм. При этом форма может состоять из строк и чисел, но все они наследуются от одного базового типа с полем уникального идентификатора. Т.е. получается, что все элементы и данные в приложении имеют зачем-то свой GUID.
Но этого мало. В этот самый базовый тип встроена логика по аутентификации и правам (нужно ли быть залогиненным, чтобы видеть этот объект, и имеет ли юзер достаточно для этого прав), по валидации и сообщениям об ошибках валидации (да, на каждое данное и каждый объект вообще), по фокусировке (объект может получить фокус в UI), знает своего родителя в визуальном дереве (т.е. например поле "имя пользователя" знает, что его родитель - форма залогинивания), и ещё много чего. Т.е., повторю, эту функциональность имеет абсолютно каждый объект в программе - от простого числа, до целой формы.
По-моему,
в проекте нарушены потоки управления. Вместо привычного, скажем, "форма залогинивания или объект пользователя управляет видимостью своих полей, проводит их валидацию и показывает ошибки", существует какой-то дикий треш, в котором каждый сам собой управляет, валидируется и предоставляет сообщения об ошибках. Это вообще ООП или что? Где инкапсуляция, где границы ответственности данных и объектов? Такое ощущение, что все данные всех объектов отделены от них и представляют из себя огромную кучу из атомарных данных со своими идентификаторами и встроенной независимой функциональностью. При этом они всё же объединены в формы и объекты, но типа для вида - всё равно эти формы и объекты ими не управляют.
Делали явно не от мира сего. Мне трудно представить себе какой-то уникальный процесс на фабрике в реальном мире, который бы моделировался подобной конструкцией. Нахрена иметь абсолютно уникальные айдишники для абсолютно всех данных, которые существуют, скажем, лишь на протяжении одной сессии? И всё это дерьмо ещё и логируется через каждые 3-5 строчек кода. Ну и что даст чтение логов через месяц, где будет отображено, что данное с айди таким-то при обработке в форме с айди таким-то получило ошибочные данные с айди таким-то? Я же не смогу воссоздать эту ситуацию и продебажить её - айди же каждый раз разные, даже для одинаковых данных.
Ну и вишенка - этот GUID существует в базовом классе в виде... строки! При инициализации создаётся новый идентификатор через Guid.NewGuid(), что возвращает структуру типа Guid, и тут же переводится в строку через ToString. Т.е. не по месту где надо конвертиться в строку, а изначально гуляет по приложению в виде строки. Ну и потом ещё с ним манипуляции проводят, типа дополняют эту строку разными символами. Т.е. это уже не Guid, а хрень собачья - назад в Guid не сконвертишь. Но есть финт ушами - в одном месте есть метод GetUID, который проверяет строку на магическое число 36 (всё без комментариев, напомню). И если строка больше 36, то метод берёт подстроку в 36 символов.
Правда, нашёл ещё метод, который конкатинирует эти GUIDы в одну строку. Там создаётся локальная переменная szId и ей присваивается результат конкатенации. В принципе, пофиг, т.к. переменная существует лишь в этом методе, и можно назвать её хоть "абракадабра", и за пределами этого метода можно об этой бессмысленной переменной забыть. Но мне просто интересно, что может означать название "szId"? Комментов нигде ни строчки нет. Там человек ещё местами вкорячивает вперемешку немецкий с английским в названиях переменных. Но вот просто любопытно - если сконкатинировать два гуида, то почему получается szId?
Они там постоянно говорят о каких-то Unikat. Но это явно какие-то отдельные вещи, типа поступлений на склад или движения объектов по фабрике. Но не отдельный уникат на каждое свойство каждого объекта в каждой сессии. Надо расспросить получше, но последний вариант с уникатами на всё и вся - явно чушь.
Во, ещё нашёл. В каждом данном - любом, типа "имя пользователя" или "масса изделия" хранятся данные, кто у этого данного непосредственный (родительский) контейнер и кто - корневой контейнер. Вот нахрена это знать каждому числу, строке и вообще любой величине? Не объекту в целом, а даже отдельным свойствам этого объекта. Но и сам объект тоже знает, т.к. он тоже унаследован от базового типа, где эти данные хранятся. Просто дикий оверхед по памяти и обработке информации. Про то, что в исходниках этой каши ещё и сложно разобраться, вообще молчу. Как будто всё приложение в каждой своей части сделано против всех правил - зато "оригинальная архитектура". Явно делали, чтобы никто кроме создателей поддерживать не мог. То, что создатели могут продать своей проект с потрохами, без поддержки, их команда может развалиться, они могут свалить куда-то или просто умереть - да пофиг. Ну а тот, кто купил эту байду без экспертизы и своего рода аудита, думая, что его программеры сами разберутся - лох.
Ещё такой прикол. Есть много форм, где часто встречается поле, в которое нужно ввести название одной и той же сущности (но разные экземпляры). Т.е. скажем поле "расположение" - много где надо его ввести. Обработка ввода и валидация этого поля везде одна и та же, проверка на одни и те же исключения, показ одних и тех же сообщений об ошибках. Но... В каждой, ска, форме, делается это немного по-другому! И для каждой формы своё сообщение об ошибке! Т.е. логика остаётся той же, а оформление и код - немного другой. И сообщения об обшибках ввода этого поля выглядят в разных формах примерно так:
"введённое расположение не найдено"
"расположение не найдено"
"введённое вами расположение отсутствует в базе данных"
"такого расположения не существует"
Но все эти сообщения находятся под разными ключами, т.к. формат ключа "имяФормы_ещёМногоРазныхУсловныхОбозначений_ключСообщения". То, что в разных формах одна и та же обработка и одни и те же сообщения об ошибках должны быть - создатели такой архитектуры, похоже, не предусмотрели.
В этом проекте "гениальность" архитектуры соперничает с абсолютной идиотичностью реализации её мелких частей - вот типа такого способа хранения сообщений в одном словаре с гигантскими составными ключами (и сотнями строк парсинга этих ключей, плюс тесты на всё это), или способа валидации одной и той же сущности в разных местах по смыслу одинаково, но по коду - немного по-разному.
Ну а вообще - бывает, что одна и та же сущность в разных случаях должна валидироваться по-разному? Как тогда?
Вот есть модель БД в виде ORM. Там валидация в атрибутах прописана. Но если я хочу в разных формах по-разному отвалидировать? Тогда надо ввести допольнительный слой - скажем, модели (или модели представления) и валидацию прописать уже в них такую, какая нужна для каждой формы. А потом при отправке в БД согласовать их валидацию с валидацией в ORM. Правильно?
Entity Location (location validation)
ViewModel LocationInLocationForm (location in location form validation)
ViewModel LocationInOrderForm (location in order form validation)
and so on...
... А потом при отправке в БД согласовать их валидацию с валидацией в ORM. Правильно?
А есть море примеров где это нужно? Хотя придумать можно.
То есть однозначно сказать нельзя. Думаю, в большинстве случаев это не нужно. Например, возраст/год рождения.
В одном месте встречаю проверку строки на целое число в методе IsInteger через Double.TryParse.
Через 10 строчек кода, в этом же методе эта же строка конвертится через Int.Parse.
В методе IsInteger после вызова Double.TryParse распарсенный результат проверяется на <=long.MaxValue и >=long.MinValue. Зачем? Вероятность ввода строки "NaN" явно исключена.
В одном методе при конвертации строки в число используется параметр культуры.
В перегрузке этого метода, не использующей параметр культуры, в комменте написано, что эта перегрузка использует актуальную культуру. А в коде тут же вызывается первая перегрузка с захардкоденным параметров "en-US".
Окончательно убедился в "гениальности" данного проекта.
Я уже рассказывал, что лучше в принципе исключить ошибки на этапе ввода, чем с упорством барана их обрабатывать. Нужно лишь подобрать правильный контрол.
Оцените подход. В проекте используются везде контролы, принимающие лишь строки. Хотя вводить в некоторые поля нужно и числа. Что делают авторы этого проекта? - В каждой форме пишут однотипный код по проверке введённой строки и показывании статуса - типа "поле не должно быть пустым", "введённое значение - не число" и т.п. Хорошо хоть методы парсинга строк в числа вынесли в переиспользуемый код. Но вот эти обработки со статусами - в каждой форме. На эти статусные сообщения - словарик ресурсов с локализацией.
И всё это вместо того, чтобы методы парсинга строки и сообщения об ошибках ввода затолкать в какой-нибудь контрол, пусть и самописный.
И? Я логику всё равно не понимаю. Ну парси тогда лонг сразу?
Дальше-то оно всё равно через обычный int парсится. Программист в десяти строчках кода не может решить, с каким типом он работает - три разных использует.
С другой стороны, я когда на эту всю вакханалию смотрю, то тоже не охота ничего особо править. У меня задача - перевести на новый интерфейс. Тупо беру и копирую код. Там и так править много, чтобы под новую версию фреймворка подогнать, так что исправлять ещё и косяки и странности остального кода, до кучи вникая, зачем это и почему было сделано, и не порушит ли что другое - нет ни времени, ни желания особого. Но вот такие штуки просто в глаза бросаются. Думаешь про себя - да хер с ним, раньше же как-то у них много лет работало, значит и сейчас как-то будет работать, если я просто скопирую. Мне постоянно напоминают, мол "клиент этого не заказывал, того не заказывал" - т.е. времени править все грехи всех предыдущих погромистов нет, и за это не заплатят. Но из-за этого иногда приходится вкорячивать костыли, чтобы обойти костыли предыдущих бедолаг. Вот так код и приходит в совершенную негодность и "проще выкинуть" через несколько таких итераций "да нам по-быстрому, лишь бы на новых версиях железа/ПО заработало". У заказчика только одна претензия к этому ону мамонта - не работает нормально в новых браузерах, а только в ИЕ. Работало бы в новых - они до сих пор сидели бы на попе ровно с этим кодом 15-20-летней давности, который никуда не масштабируется и к которому прилагаются старинные PDA с Вин Мобайл на борту и специальным гуём, вместо единообразной адаптивной вёрстки десктоп/мобилы.
Я логику всё равно не понимаю. Ну парси тогда лонг сразу?
Если вводятся только строки, то скорее всего ориентация идет на double, как базовый тип.
Совершенно не следует понимать, почему так сделано, в каком то старом проекте - это не всегда может объяснить даже тот, кто это сделал.
Совершенно не следует понимать, почему так сделано
Я-я, натюрлихь! Тафай-тафай, прокраммируй, не задумывайся, блин. Самый отвратительный совет, который только можно дать.
Прям 1 в 1 мои коболисты. А чо, работает же? А то что ты уже за эти полгода пятый раз ошибки правишь в этом куске кода, ничерта не понимая, добавляя очередной if-else ни на какие мысли не наводит, нет. Главное быстро тикет сделать.
Тафай-тафай, прокраммируй, не задумывайся, блин
Похоже мы смотрим в разные стороны. Имелось в виду совершенно другое.
Понять как "правильно" делать - это вроде как само собой разумеется. То, что у всех может быть разное мнение по этому поводу - вопрос дополнительной дискуссии.
Встретил такое
свойство
public bool HasFocus
get
internal set
метод
public void SetFocus
Нафига, чтобы прочитать свойство, нужно обратить себя к свойству, а чтобы установить его - к специальному методу?
Разная логика при установке свойства? Типа есть логика внешняя для пользователя класса, и внутренняя - для своего внутреннего фреймворка? Ну так сделай, чтобы пользователю было удобно и он с твоими свойствами работал привычно, а не через задницу. А сам внутри своего фреймворка можешь через задницу устанавливать свои свойства.