Pull to refresh
12
0
Artur Ampilogov @a-artur

User

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

Для начала, заказчик говорит «на моей форме должно быть 50 символов». Не, я серьезно — уговорить заказчика на униформность по всей системе еще надо постараться. Так что с этого момента у вас в системе есть Finances.CustomerName и CRM.Name, и они отличаются.

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

Но, уж простите за утрированный пример, если заказчик говорит вам, что в одном месте максимум 50 символов для имени, а в другом 51, а третьем 52, и все это должно использоваться в итоге в одном месте, то это уже проблема общения с заказчиком.

… и учите все инструменты с ним работать. Автоматический редактор поля в интерфейсе? Биндинги? Трансляция в БД?

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


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

Далее у вас новое требование от сторонней системы использовать данные как они есть, до исправления и пересохранения. Вот здесь вы нарушаете один из главных концептов стабильности системы. У вас в систему попадают потенциально некорректные данные. Вы разрешаете их где-то и когда-то исправить (возможно!) операторам.
Что если кто-нибудь прочитает эти некорректные данные до исправления и отправит в вашу систему неправильные данные, ну или в другие системы? Фонтан багов и некорректных данных в системе обеспечен. Иначе получается ужас какой-то, вы перед использованием данных каждый раз проверяете а не поправил ли их оператор, и так потенциально со всеми данными?

В общем случае картина часто выглядит следующим образом (на примере .NET, но актуально и для других):
/------------------------------------------
/ Внешние интерфейсы:
/ от пользователей на веб-морде до партнерских сервисов
/------------------------------------------
/ WebAPI
/ WCF
/ WS
/ WebHandler
/ External Library
/======================
||
||
||
/----------------------------------------
/ Core Logic
/----------------------------------------
/ Здесь лежат правила приложения
/ и требования заказчика.
/----------------------------------------

Из внешних систем приходят общие типы: String, Integer, etc.
В логике вашей системы или подсистемы вы работаете уже с конкретными типами.
Пока по вашим словам получается, что в вашей системы вы везде работаете с общим типом String, но где-то и как-то делаете валидацию IsName, CMS.IsLastname, IsEmailAddress, и отлично если вы не забыли эту проверку поставить в одной из многочисленных функций которые принимают множество общих типов String, ну или не забыли Unit test'ы накопипастить. Происходит посев потенциальных багов, которые на корню можно ликвидировать.

Расскажу про похожие случаи в двух крупных проектах в Австралии (Shell и Sanofi Pharmaceuticals).
Есть разные внешние системы, которые отправляют данные в систему компании. Поток сохраняют в очередь. Очередь обрабатывает автомат и конвертирует данные в систему компании. Если попадаются невалидные данные, то они отправляются на усмотрение оператора, который может их скорректировать, или вообще не впустить в систему (вы же не можете ручаться за все данные из внешних систем?).

Теперь об очень важном пункте в работе с заказчиком, который заказчики очень уважает. Как вы пришли к массовому невалидному потоку данных?
Вы отрабатывали импорт данных с заказчиком перед выпуском в продакшн и не учли какие-то факторы? 1) Если это ваша ошибка, то фиксите баг в ближайшую итерацию.
2) Если это ошибка заказчика
а) Заказчик с большими глазами прибежал и ему внезапно надо это в системе вчера — идет лесом, есть контракт работы. Ваша компания отвечает за программу, стабильность и потерю данных на данном этапе.
б) Заказчик понимает, что облажался, но фитча важная — делаете ее в высоком приоритете, также как и в 1 пункте.
Невалидные данные не теряются, они ждут в очереди вашего фикса или временной массовой обработки заказчика.

По поводу конвертации и валидации: не вижу в чем сложность написать один раз конвертер из String в EmailAddress, обратно даже ничего конвертировать не надо так как внутри String можно хранить. В случае веба сложно написать общий интерфейс для валидируемых объектов и один раз обработчик, который выкладывает ошибки в ModelState? Единственная проблема, это валидация на UI в JS, но это более общая проблема другого характера, которая тоже решаема.

Так что я еще могу понять использование именованных типов (на самом деле — банальных типов-синонимов) для того, чтобы разделить вещи, которые нельзя путать (вот, скажем, позицию и длину), но впихивать валидацию — которая рано или поздно окажется контекстно-зависимой — в систему типов мне кажется очень опасным решением. Именно из-за стоимости внесения изменений.

Иногда это все звучит так, что по не понятным мне причинам, в вашей системе все настолько универсальное, что даже Enum применить страшно (а вдруг программист большой Integer использует); валидации никакой нет и не будет (и с китайцами, и с японцами, и с арабами работаете), это просто хранилище данных в Unicode. Но тогда это к статье никакого отношения не имеет, да и вообще можно на ассемблере ваять.
Вижу что вы не поняли.

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

И такое часто в требованиях встречается.
Вместо глобального String вы создаете класс Name с требуемыми проверками.
И вместо размазни с атрибутами или проверками String на IsNameValid по всему приложению вы просто навсего используете этот класс. В частности для типа Person:
class Person{
Name Firstname {get;}
Name Lastnname {get;}
}


В другом месте на форме вы опять принимаете тип Name c теми же условиями, но только для Firstname.
И то же самое с EmailAddress, и другими маленьким типами и структурками.

Одно место для проверок, уверенность в валидности объекта в любое время и возможность повторного использования во всем приложении. Бинго!
Ну конечно.
Мне на практике не приходилось иметь дело с векторами, где нужны еще и разные типы для разных координат. Зачем?

Point3D все координаты float.
Vector3D все координаты типа double.
Vector3 для разработки игр в XNA имеет все три координаты float.
Vector3 в Unity — все float.
Безусловно в зависимости от поставленной задачи. Это просто пример.

Может быть SmallInt, Integer, Float, Decimal, или даже Fraction как в smalltalk.

Опережая другой вопрос, мне известны возможные проблемы из-за разницы в хранении и представлении чисел.
Очень хорошее замечание,
также ответил пользователю Alew.
Очень точно отметили, спасибо.
С точки зрения практики заметил, что даже зная про этот антипаттерн большинство программистов догадываются строить структуры или делать конкретную типизацию только для уж совсем наглядных сущностей, таких как Vector3D {int x, int y, int z}.
Безусловно забывают про более конкретную типизацию для простых типов, достаточно просмотреть OpenSource проекты, да и по работе приходится встречаться с огромным количеством как крупных, так и мелких проектов известных и крупных российских IT компаний. Уверен, что если у вас крупная компания, то и у вас такое встречается, так как невозможно за всеми уследить.

Отвечу в таком же стиле.

Так, стоп. Давайте не будем путать design by contract, который использует контракты как набор утверждений, и type-based design, который использует статическую систему типов для гарантии корректности.

Здесь нет путаницы, просто сделайте один шажок дальше.

В .net — нельзя. Code Contracts работает иначе. Ну и да, мы оставим за отдельными скобками тот вопрос, правильно ли это.

Я вас приятно удивлю прикрепленным скриншотом. Data Contracts используют статическую проверку, при отключении кнопки Compile in backrground в CodeContracts вы получите Failed Compilation на стадии компиляции проекта. Программисты Microsoft неоднократно заявляли о сложности такой проверки путем parsing'a. Для дополнительной оптимизации используется компиляция кода с пограничными условиями, подробнее в блогах MS команды CodeContracts.
image

Ну во-первых, это ровно тот самый design by contract, к которому вы только что призывали. Во-вторых, нет, это не «современный подход». Data Annotations применяются на другом уровне и для других целей. В частности, они применяются в UI, где применение строгой системы типов просто противопоказано.

Для меня лично есть огромная и существенная разница между использованием набора атрибутов над классом String и типом EmailAddress.
Удивлю приятно второй раз, data annotations еще и для создания баз данных используют.

Но, повторюсь, вы почему-то думаете, что про type-based design забыли — хотя и на Хабре по этому поводу есть статьи, и у Симана есть прекрасные примеры заведомо бизнес-корректных систем. Проблема type-based в том, что для них нужна существенно более мощная, нежели в том же C#, система типов, иначе это очень дорого. И начинать надо с — внезапно — устранения повсеместных null (о которых, кстати, в вашем тексте ни слова).

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

Если задуматься о чем статья и о чем пишет Хоар, то это не отдельно про subrange enum'ы, а про удобство использования более конкретных типов для конкретных нужд. И не важно, используете ли вы Enum или class с возможностью статического анализа проверок через контракты или что-то еще. Смотрите шире. Можно и без Design By Contracts обойтись, просто используя TryToConvert() и обрабатывая результат.

А еще проблема type-based в том, что обещанное вами переиспользование не работает. Нет общей библиотеки типов, где есть EmailAddress, поэтому две соседних библиотеки будут иметь две разных реализации, после чего нам понадобится писать конвертеры в две стороны.

Я специально для этого в статье упомянул про interoperability и конвертацию к простым типам (Integer, String).
Вы же не будете утверждать, что работая с типом String под видом Email'a в разных библиотеках и сторонних системах везде будут одни и те же проверки на IsEmail(). Тоже самое касается interoperability между разными платформами, например Java и .NET, конвертация к базовым типам безусловно необходима.

====
Я надеюсь, что многие читатели поняли идею и будут писать код с намного меньшими side-efect'ами.

И вам, и другим читателям приятного программирования!
Да пожалуйста, делайте проверку на localhost, если необходимо.
Никто не мешает использовать foreach c интерфейсами IEnumerable, что собственно сейчас и делают с типами Enum.

А то так и следующее перечисление с for(;;) не пройдет
enum Enum{
A = 1,
C = 3,
F = 20
}
Очевидно, если бы речь шла о простом хранении String, то и не было бы статьи.
Как раз вопрос в проверке бизнес логики вашей системы.
Если Microsoft решил ограничить длину Email в 256 символов, то это их право, это их система.
Помимо указанного на хабре поста, есть множество информации о неправильных предположениях о номерах телефона, индекса, дома, и т. д. Но если вы работаете только с почтой РФ, наверное стоит подумать об неком ограничении.
Вижу что не все поняли идею статьи.

Суть статьи напомнить об одной из важных идей программирования, которая заключается в использовании конкретных типов для конкретных нужд. Вместо всеобъемлющего String конкретный EmailAddress, о чем Хоар написал еще в 1972 году (это его раздел в книге).
Там где есть возможность использовать Enumeration c subrange соответсвенно использовать его, например, для месяцев вместо Integer использовать Enum(Jan… Dec), вместо широчайшего Integer для минут Enum(0… 59), и т. д.

В совокупности же с Design by Contracts проверки уже можно делать сегодня даже на стадии компиляции.
Например, можно настроить программу в Java и .NET так, что вызов упомянутого в статье метода со следующими аргументами
SendEmail(new EmailAddress("wrongEmail@com", "");
покажет ошибку на стадии компиляции.

Делайте код как можно более читаемым для других. Объявляя тип String для Email вы обманываете пользователей ваших методов, ведь «111», «222@ccc», «wrongEmail@com» являются типом String, но не валидным Email'ом. Также когда вы пишите софт для банкомата и декларируете аргумент в виде Interger/Decimal/Money для внесенных денег, явно показывая что вы принимаете -100 долларов, и -2 млрд. долларов, а неявно, в лучшем случае если не забыли, прокидываете где-то после ошибки. Другие возможные применения читайте в статье.

Очевидным бонусом использования конкретных типов является их переиспользование. Делая EmailAddress валидным всегда (например, через проверку и выброс ошибки в конструкторе), вы можете быть уверены, что это всегда правильный Email и не надо дополнительных проверок аля IsEmail() в других методах принимающих обширный String.

Это старые истины ООП, но мало кто это сегодня использует. Например, современный подход по поводу проверки Email в .NET через DataAnnotations выглядит так:
function void SendEmail([EmalAddress][Required] String email, [Required]String body){}


Если есть еще вопросы, с удовольствием отвечу, обращайтесь.
Не уверен, что Октопус именно то, что вам подойдет лучше всего. Вопрос: кто отвечает за сервер, где установлен Tentacle, клиент или вы? Кто будет делать Security обновления сервера, и кто будет обновлять Octopus Tentacle (не деплой, а security update)? Возможно, что обычный выпуск msi пакетов будет лучше, когда клиент решает надо им обновить сайт или нет. Как правило, используя Octopus вы фактически берете на себя ответственность за поддержку Tentacle. Иначе в будущем при расхождении версии Octopus Server'a и Octopus Tentacle обновление просто не пройдет.

По поводу прав для установки, скорее всего нужно иметь права администратора, как советуют lair, но это опять же зависит от настроек ОС вашими клиентами. Octopus Tentacle — это обычный Виндоус сервис. Можете узнать у клиентов, под каким пользователем разрешено устанавливать сервисы. Вот список прав, которые в конечном итоге нужны для успешной работы сервиса (не установки).

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

С радостью постараюсь ответить на ваши вопросы и помочь.

Information

Rating
Does not participate
Location
Yerevan, Yerevan, Армения
Registered
Activity