Здравствуйте, Mike Chaliy, Вы писали:
MC>Здравствуйте, kisel, Вы писали:
K>>У меня бы мозг закипел и подумал про себя — "снова какой-то индусский код подсунули "
MC>А у меня четкие ассоциации с индусами, когда вижу код свойств с логикой...
Зачем в таком случаи тебе вообще свойства, псевдо- инкапсуляцию реализуешь, тобишь, "простые" свойства делаешь?
Всё таки свойство с set-ом Age и метод SetAge(..) — плохой дизайн, если не сказать более.
На уровне IL (Intermediate Language) — нет понятия свойств, это так замануха C#, для удобства сделано.
Откуда вообще такое веяние пошло? Сам придумал или где вычитал? (имею ввиду, одновременно использовать свойство Age{...} и метод SetAge(..))
Здравствуйте, Mike Chaliy, Вы писали:
MC>.Net Framework Design Guidelines MC>тут есть видио http://channel9.msdn.com/pdc2008/PC58/, помойму на 20 минуте про геттеры, чуть дальше про контсрукторы.
А текстом нет?
MC>Хотя я там слишком жестко написал, в гаидлайнах речь идет про "хотелось бы что бы не кидали". "не должны" лучше читать как "не хотлоесь бы". Но так как весь суть разговора идет про идеолгическую часть, которая по необходисомти может быть попрана. То ИМХО вполне допустимо использовать черное и белое.
Не, очень даже хотелось бы, чтобы любая попытка создать объект с некорректным состоянием завершалась исключением, так как исключение в конструкторе не только информирует клиентскую сторону о некорректном действии, но и отменяет создание такого объекта. Советую посмотреть исходники более опытных коллег, чтобы убедиться в том, что конструкторы очень даже злоупотребляют исключениями. Советую почитать статьи о защитном программировании, искать по словам: защитное, безопасное, контрактное, dbc, design by contract.
Здравствуйте, Mike Chaliy, Вы писали:
MC>Если я хочу потдерживать корректное состояние то у меня по умолчанию все свойства будут ридонли. У меня будут методы которые будут это состояние менять. Так например у меня есть аппонтмент. MC>class Appointemet MC>{ MC> DateTime startTime; MC> DateTime endTime; MC> int Duration; // у меня по бизнесу это дени. MC>} MC>Если мне надо аппоинтменту укзать его даты то будет метод SetDates(DateTime start, DateTime end) этот же метод завалидирует что end > start, посчитает дюрейшен.
Во-первых, вполне допустим вариант, когда доступны set-секции свойств StartTime и EndTime (а указанного вами метода или нет вовсе, или он является дополнительным и использует внутри себя эти свойства), каждая из которых производит соответствующую проверку. Во-вторых, длительность надо считать лениво, то есть по запросу, к примеру, в секции get свойства Duration, а сразу в момент установки даты (дат) это вовсе не нужно. Итого, ваш пример в отношении setter-ов не показателен.
MC>Если обратиться к примеру с Age то вобщемто это такой же пример. Если мне необходимое корректное состояние и если у меня по бизнесу не может быть Age меньше нуля, то при вызове SetAge я крешанусь. Все ок, я же ожидаю что мое действие может привести к исключениям. MC>Єто реально мое ИМХО что юзабилити АПИ значительно выше, если сеттеры не кидают исключения. Точно так же как мое ИМХО что сеттеры не должны изменять соседние поля....
Хороший пацанский аргумент, что-то посерьезнее будет?
MC>Обьясню. Возраст -1 это вполне нормальное значение. Тоесть существуют таки задачи в которых это необходимо. Точно также как и со складами. Какбудтобы физически не может быть на складе -20 мочалок. Ан нет, может, это более чем повседневная задача в складском учете. MC>Дальше больше предположим что -1 это не валидное состояние по бизнесу. MC>Тогда есть два варианта. MC>а) Наша реализация это потдерживает! Тоесть у обьекта появляеться два состояния валидное, которое предполагаеться что юзер исправит до сохранения и валидное, кторое мы можем спокойно сохранить. Паралели с реальным миром тут вообще неприемлемы, вон в медециноском програмном обеспечении часто персоны до какогото возраста (14 лет) вообще не персоны... MC>б) Не потдерживает. Тоесть просто крешиться на момент установки. MC>В любом случае момент установки это действие.
Рассмотрим пример бизнес-процесса розничной продажи алкогольной продукции. С точки зрения бизнес-логики допустимым (если вам проще на птичьем языке, то "валидным") покупателем является человек, которому 21 год или старше. Бизнес-логика работает над объектом или его взаимодействием с другими объектами. Но кроме того есть еще и спецификация класса, которая описывает его поведение, эти правила работают только на уровне класса. Спецификация должна описывать область допустимых (область определения в отношении функций в алгебре) входных данных, то есть таких данных, на которых алгоритм преобразования выходных данных в выходные будет соответствовать спецификации. Так вот с точки зрения спецификации класса Человек, человека с возрастом -1 существовать не может (и никакую бизнес-логику тут за уши притягивать не надо). Разница понятна?
MC>Основная мысля это то что для каждого БЛ метода это может быть свое. Тоесть для Дринк должно быть 18 (21, хз у меня не было случая проверить...), а для метода Слип вообще не существенно какой возраст....
Основная ошибка в том, что вы бизнес-логику, которая должна оперировать сущностью, запихиваете внутрь этой сущности. Это также логично, как и в метод Run класса Car засунуть проверку на горение зеленого светофора: эту проверку осуществляет не автомобиль, а человек, который и заставляет машину двигаться — некоторые, кстати, забывают ее осуществить (сам порой пролетаю на красный, при это мое авто глупое нифига мне не сигнализирует!).
MC>Я же не советов просил. Тем более меня слегка удивляет пренебрежительное повествование вас и rsn81, но мы люди не гордые... Инфа полезней, так что проглотим.
А что вы просили?
Здравствуйте, kisel, Вы писали:
K>Всё таки свойство с set-ом Age и метод SetAge(..) — плохой дизайн, если не сказать более.
Погоди я говорил про ридонли Age и метод SetAge... Насчет плохого дизайна, не горячись, неззя вешать ярлыки.
K>Откуда вообще такое веяние пошло? Сам придумал или где вычитал? (имею ввиду, одновременно использовать свойство Age{...} и метод SetAge(..))
, я не знаю откуда такое веяние пошло...
1) Доменные модели. Да Age не может быть меньше нуля, но до момента сохранения это тока ворнинг. При том что классы с потдержкой не валидного состояния тяжелее, они удобней. Я уже приводил пример со складом, там это более наглядно.
2) Тестирование. Не имеет смысл настраивать весь обьект, для того чтобы проверить часть функционала. Все что не нужно для теста — это шум. И например для операции Крикнуть, побарабану какое значение Возраст.
3) Use a property when the member is a logical data member (http://msdn.microsoft.com/en-us/library/bzwdh01d(VS.71).aspx). Ну почему -1 это не данные? Данные. Сегодня для бизнеса они не валидные. Завтра валидные.
4) Свойство это то что изменяет вполне конкретный кусочек состояния (Properties should be stateless with respect to other properties). Сегодня УстановитьВозраст это простая операция которая просто меняет поле Возраст. Завтра эта штука начнет высчитывать возможную дату смерти. И упс, у нас уже это совсем другая семантика. Это я к тому что есть бизнес операция УстановитьВозраст и имхо она точно также операцие должна быть в коде.
Про то что свойства у тебя только get-ы, пропустил .... Что то уже запутался, ты же писал выше: MC>Если обратиться к примеру с Age то вобщемто это такой же пример. Если мне необходимое корректное состояние и если у меня по бизнесу не может быть Age меньше нуля, то при вызове SetAge я крешанусь. Все ок, я же ожидаю что мое действие может привести к исключениям.
Тобишь, на set-ах ты всётаки валидируешь данные.Получается, что говорим об одном и том же.
Собственно вопрос, к чему сводится, использовать set у свойств или методы, что ли?
Здравствуйте, rsn81, Вы писали:
R>Здравствуйте, Mike Chaliy, Вы писали:
MC>>.Net Framework Design Guidelines MC>>тут есть видио http://channel9.msdn.com/pdc2008/PC58/, помойму на 20 минуте про геттеры, чуть дальше про контсрукторы. R>А текстом нет?
До видео, вы можете увидить название книги.
MC>>Хотя я там слишком жестко написал, в гаидлайнах речь идет про "хотелось бы что бы не кидали". "не должны" лучше читать как "не хотлоесь бы". Но так как весь суть разговора идет про идеолгическую часть, которая по необходисомти может быть попрана. То ИМХО вполне допустимо использовать черное и белое. R>Не, очень даже хотелось бы, чтобы любая попытка создать объект с некорректным состоянием завершалась исключением, так как исключение в конструкторе не только информирует клиентскую сторону о некорректном действии, но и отменяет создание такого объекта. Советую посмотреть исходники более опытных коллег, чтобы убедиться в том, что конструкторы очень даже злоупотребляют исключениями. Советую почитать статьи о защитном программировании, искать по словам: защитное, безопасное, контрактное, dbc, design by contract.
Даже не знаю что сьзвить по поводу опытных коллег... Наверное напомнить что в SDK кошерные исключения не находяться.. да и обычные тоже...
Например берем MVC Framework.. упс. Не бросают. Все проверяеться и кидаеться на момент действия. А мож его писали не опытные люди?
Теперь про design by contract, вы заметили слово контракт? Знаете что это обозначает? Ну и как это относиться к бизнес значению Age? Или как это относиться к конструктору который у себя должен сходить в базу данных чтобы перепроверить все ли ок? ИМХО вы пытаетесь задавить бузвордом.
Здравствуйте, rsn81, Вы писали:
R>Здравствуйте, Mike Chaliy, Вы писали:
MC>>Если я хочу потдерживать корректное состояние то у меня по умолчанию все свойства будут ридонли. У меня будут методы которые будут это состояние менять. Так например у меня есть аппонтмент. MC>>class Appointemet MC>>{ MC>> DateTime startTime; MC>> DateTime endTime; MC>> int Duration; // у меня по бизнесу это дени. MC>>} MC>>Если мне надо аппоинтменту укзать его даты то будет метод SetDates(DateTime start, DateTime end) этот же метод завалидирует что end > start, посчитает дюрейшен. R>Во-первых, вполне допустим вариант, когда доступны set-секции свойств StartTime и EndTime (а указанного вами метода или нет вовсе, или он является дополнительным и использует внутри себя эти свойства), каждая из которых производит соответствующую проверку.
Allow properties to be set in any order (http://msdn.microsoft.com/en-us/library/bzwdh01d(VS.71).aspx).
R>Во-вторых, длительность надо считать лениво, то есть по запросу, к примеру, в секции get свойства Duration, а сразу в момент установки даты (дат) это вовсе не нужно. Итого, ваш пример в отношении setter-ов не показателен.
Опять вы учите меня. То что надо будет бизнесу то и будет. В конкретно этом случае у меня подвязан воркфлоу, который работает на изменении дюрейшена.
MC>>Если обратиться к примеру с Age то вобщемто это такой же пример. Если мне необходимое корректное состояние и если у меня по бизнесу не может быть Age меньше нуля, то при вызове SetAge я крешанусь. Все ок, я же ожидаю что мое действие может привести к исключениям. MC>>Єто реально мое ИМХО что юзабилити АПИ значительно выше, если сеттеры не кидают исключения. Точно так же как мое ИМХО что сеттеры не должны изменять соседние поля.... R>Хороший пацанский аргумент, что-то посерьезнее будет?
Судя по всему я не понятно выразился. Меня интерисует лиш ваше мнение. Я не навязываю своего. Я ваше мнение уже понял. Ессно применять не буду.
R>Разница понятна?
Я скипанул, так как к теме это никак не относиться. Если вам хочеться обудсить эту проблему начните новый топик.
MC>>Основная мысля это то что для каждого БЛ метода это может быть свое. Тоесть для Дринк должно быть 18 (21, хз у меня не было случая проверить...), а для метода Слип вообще не существенно какой возраст.... R>Основная ошибка в том, что вы бизнес-логику, которая должна оперировать сущностью, запихиваете внутрь этой сущности.
Я думаю это такая же придирка, что и про АргументЕксепшен. Если не нравиться Дринк, можете заменить его на ПоловоеСозревание...
Вобщемто вы опять не там нашли ошибку. Помойму проблема размещения бизнес логики никак не относиться к теме.
MC>>Я же не советов просил. Тем более меня слегка удивляет пренебрежительное повествование вас и rsn81, но мы люди не гордые... Инфа полезней, так что проглотим. R>А что вы просили?
Вам процитировать? Или вы сами проглядите первый топик?
Здравствуйте, kisel, Вы писали:
K>Здравствуйте, Mike Chaliy, Вы писали:
K>Про то что свойства у тебя только get-ы, пропустил .... Что то уже запутался, ты же писал выше: MC>>Если обратиться к примеру с Age то вобщемто это такой же пример. Если мне необходимое корректное состояние и если у меня по бизнесу не может быть Age меньше нуля, то при вызове SetAge я крешанусь. Все ок, я же ожидаю что мое действие может привести к исключениям.
K>Тобишь, на set-ах ты всётаки валидируешь данные.Получается, что говорим об одном и том же. K>Собственно вопрос, к чему сводится, использовать set у свойств или методы, что ли?
всех запутал... Я не ожидал что настолько все воспримиться в штыки что нанеться валиться куча поучений. Когда валиться куча поучений, приходитья отгавкиваться. Когда отгавкиваешся, получаються курьезы. Еще большие курьезы выходят когда смешиваються ветки.
Про использование метода сет или собсно сеттера я изначально отвечал C...R...a...S...H про обьекты в которых необходимо потдерживать валидное состояние. Мое судя по всему очень субьективное мнение что состояние такого обьекта могут менять тока методы. Даже при том что сеттеры это теже самые методы. Мое опять же субьективное (пацанский, не серьезный аргумент (С) rsn81) что так получаеться значительно более юзабельное/читабельное АПИ.
Mike я хотел бы пару слов добавить ко всем гвоздям, которые мы пытались забить в вашу теорию.
1) чем раньше пользователь получит информацию о том что он делает что-то не правильное, тем лучше для пользователя, думаю вы согласитесь с этой истеной.
2) Следуя вашей теории вообще нет смысла для Свойст. Используйте Field и радуйтесь жизни.
В ответ на все ваши Guidelines Please read C# Programming Guide здесь
Если вам лень читать вот выдержки:
Properties are members that provide a flexible mechanism to read, write, or compute the values of private fields.
Properties enable a class to expose a public way of getting and setting values, while hiding implementation or verification code.
Здравствуйте, C...R...a...S...H, Вы писали:
CRA>Боюсь после этого, можно прекращать обсуждение, если все еще остаетесь при своем мнении, то смысла вас переубеждать нет.
После этого, после чего? Я знаю все эти гаидланы. Я не вижу как это все может влиять на мое мнение. Ну есть у Майкров гаидланы по использованию ДатаСетов, но не юзать же их изза этого? Таких примеров десятки.
Конешно мне было бы проще если у когото оказались бы похожие переживания по поводу оверюза сеттеров... Но судя по всему не оказалось. Ниче, никто изза этого не умрет. Поэтому действительно можно прекращать это обсуждение.
Здравствуйте, Mike Chaliy, Вы писали:
MC>Интерсно мнение, может ли идеологически сеттер кидать исключение?
Если говорить _идеологически_, то нет, исключение в сеттере — это некошерно. По той причине, что сеттер имитирует доступное поле, и какой-то дополнительной логики при установке значения не ожидается. Вы же не ожидаете, например, что здесь
SomeClass i = new SomeClass();
..
SomeClass j = i; (*)
во втором присваивании (*) будет исключение? Если нужно бросить эксепшн — это означает логику, т.е. действие — лучше воспользоваться методом, он в этом плане нагляден.
А вообще вопрос — сродни "использовать ли триггеры в БД" Очень похоже — так же может быть удобно, и так же неочевидно.
Здравствуйте, Mike Chaliy, Вы писали:
MC>Интерсно мнение, может ли идеологически сеттер кидать исключение?
MC>Мое ИМХО сеттер не должен кидать никаких исключений. Валидация данных должна проиходить на момент дейтсвия.
Сеттер сеттеру рознь.
В сеттере domain object я бы не стал. А в сеттере какого нибудь билдера вполне.
Потому как бог знает в каком контексте понадобится использовать доменный обект а с билдером обычно такого не происходит. Вопрос контракта на самом деле.
Здравствуйте, http://code-inside-out.blogspot.com/, Вы писали:
HCI>Здравствуйте, Mike Chaliy, Вы писали:
MC>>Интерсно мнение, может ли идеологически сеттер кидать исключение?
HCI>Если говорить _идеологически_, то нет, исключение в сеттере — это некошерно. По той причине, что сеттер имитирует доступное поле, и какой-то дополнительной логики при установке значения не ожидается.
Объясните зачем тогда вообще вводили свойства, если можно было обойтись полями?
Там было написано русским по белому...
Re[3]: Exceptions в сеттерах
От:
Аноним
Дата:
19.11.08 06:45
Оценка:
Здравствуйте, C...R...a...S...H, Вы писали:
CRA>Объясните зачем тогда вообще вводили свойства, если можно было обойтись полями?
Ничего себе. "Объясните". Вас на Гугле не забаннили?
Свойства ввели для того, чтобы предоставить ограниченный доступ к полям. Например, только на get и т.д.
По поводу исключений — единственный Exception, который стоит использовать внутри свойств, это ArgumentNullException. Но это — exception уровня проектирования; это значит, что-то запрограммировано не так, и он не несет смысла из предметной области; так что его можно заменить, например, на Debug.Assert() (ну если вы совсем пурист)
Здравствуйте, http://code-inside-out.blogspot.com/, Вы писали:
HCI>Ничего себе. "Объясните". Вас на Гугле не забаннили?
Мне интересно было именно ваше видение.
HCI>Свойства ввели для того, чтобы предоставить ограниченный доступ к полям. Например, только на get и т.д.
Так для этого можно было бы методы использовать.
Или вообще могли придумать какой-нить аттрибут для полей аля PublicReadOnly. А тут наворотили:
public int Count
{
get{ return count;}
}
А под конец, для самых простых случаев придумали автоматические свойства.
И очень интересно как вы относитесь к такому вот свойству:
public string Text
{
get
{
string str = (string) this.ViewState["Text"];
if (str != null)
{
return str;
}
return string.Empty;
}
set
{
this.ViewState["Text"] = value;
}
}
Скажу по секрету в StateBag есть такой оператор
throw new ArgumentException();
HCI>По поводу исключений — единственный Exception, который стоит использовать внутри свойств, это ArgumentNullException. Но это — exception уровня проектирования; это значит, что-то запрограммировано не так, и он не несет смысла из предметной области; так что его можно заменить, например, на Debug.Assert() (ну если вы совсем пурист)
В предыдущем вашем посте вы говорили, что выбрасывать исключения — некошерно
Теперь пишете, что мол ArgumentNullException можно, тогда наверное и ArgumentException (как я показал выше) тоже можно
Наверное если продолжить, то так все исключения можно перечислить
Там было написано русским по белому...
Re[5]: Exceptions в сеттерах
От:
Аноним
Дата:
19.11.08 12:11
Оценка:
Здравствуйте, C...R...a...S...H, Вы писали:
CRA>Теперь пишете, что мол ArgumentNullException можно, тогда наверное и ArgumentException (как я показал выше) тоже можно CRA>Наверное если продолжить, то так все исключения можно перечислить
Согласен, тут Вы безусловно правы — то, что я не упомянул про ArgumentException, есть полностью моё упущение.
Однако взгляните выше — туда, где я сказал про исключения, несущие и не несущие смысл предметной области. Я имею в виду, что особые пуристы могут заменить проверку "if(badArg) throw new Argument*Exception()" на Debug.Assert(!badArg), потому как эти исключения не свидетельствуют не о неожиданной ошибке в процессе работы, а о том, что программист съел калошу, когда "кодил"; и все это должно быть устранено ещё на этапе отладки ПО.
>> Автоматические свойства
В них вообще не вижу пользы (Ну кроме, может быть, одной — если впоследствии я решу заменить его на рукописное, клиенты не пострадают. Но такого на моей памяти ещё не было).
>> Интересно ваше видение
Ну вот, я и высказался его, передаю микрофон Вам
Я скажу по-простому. Если не проверять корректность данных, то можно потерять инвариант объекта. А если корректность проверять, то в странной парадигме сеттера ничего не остается, как бросить исключение, так как возвращаемого результата у него в отличии от нормаьного метода нет.
Да здравствует мыло душистое и веревка пушистая.
Re: Проверки в сеттерах приводят к дублированию кода
Здравствуйте, Mike Chaliy, Вы писали:
MC>Мое ИМХО сеттер не должен кидать никаких исключений. Валидация данных должна проиходить на момент дейтсвия.
Пока для проверки достаточно самого присваиваемого значения вроде бы все нормально:
double Age
{
set
{
if (value < 18)
throw new InvalidOperation();
}
}
Однако завтра оказывается, что в разных странах разрешается начинать потреблять спиртные напитки в разном возрасте, соответственно добавляем:
Country Country { get; set; }
double Age
{
set
{
if (value < Country.MinDrinkAge)
throw new InvalidOperation();
}
}
После чего понимаем, что этот код валидность состояния объекта нам не гарантирует, надо так:
Country Country
{
set
{
if (Age < value.MinDrinkAge)
throw new InvalidOperation();
}
}
double Age
{
set
{
if (value < Country.MinDrinkAge)
throw new InvalidOperation();
}
}
Соответственно получаем, что и в сеттере страны нужно проверять и страну, и возраст, и в сеттере возраста нужно проверять и возраст, и страну. Т.е. получили дублирование кода со всеми сопутствующими прелестями.
Есть и еще одна проблема, при проверках в сеттерах при более-менее сложной логике может быть совершенно непонятно в каком порядке надо изменять поля объекта, чтобы не вылетело исключение, т.е. при одном состоянии изменяемого объекта такой код работать будет:
person.Country = Country.Russia;
person.Age = 19;
а такой нет:
person.Age = 19;
person.Country = Country.Russia;
А при другом состоянии изменяемого объекта может быть наоборот, второй вариант будет работать, а первый нет.
Поэтому проверок в сеттерах быть не должно, проверки корректности состояния объекта должны производиться либо по месту действия, либо специальным методом вроде IsValidObject().
Хотя правильнее всего сеттеры вообще не писать, чем меньше в коде сеттеров, тем проще он читается.
Re[2]: Проверки в сеттерах приводят к дублированию кода
Здравствуйте, Undying, Вы писали:
U>Соответственно получаем, что и в сеттере страны нужно проверять и страну, и возраст, и в сеттере возраста нужно проверять и возраст, и страну. Т.е. получили дублирование кода со всеми сопутствующими прелестями.
U>Есть и еще одна проблема, при проверках в сеттерах при более-менее сложной логике может быть совершенно непонятно в каком порядке надо изменять поля объекта, чтобы не вылетело исключение, т.е. при одном состоянии изменяемого объекта такой код работать будет:
U>А при другом состоянии изменяемого объекта может быть наоборот, второй вариант будет работать, а первый нет.
Это, например, неплохо решено в DependencyObject…
U>Поэтому проверок в сеттерах быть не должно, проверки корректности состояния объекта должны производиться либо по месту действия, либо специальным методом вроде IsValidObject().
…и решение данной проблемы оказалось ортогональным валидации. То есть, получается, каждому уровню валидации — своё место и своё поведение, свой результат. И на частном примере "вот так вот плохо", вывод что "вообще нельзя" сомнителен.
U>Хотя правильнее всего сеттеры вообще не писать, чем меньше в коде сеттеров, тем проще он читается.
Уже не раз обсуждалось, что immutable-модель плохо совместима с дизайнерами (за все дизайнеры на свете не поручусь, не знаю, но за дотнетовские — да) компонентов.
Даже структуры (вопреки рекомендациям), даже с перегруженными Equals\GetHashCode (вопреки здравому смыслу) разработчики FCL, , сделали изменяемыми (System.Drawing.Size и System.Windows.Size).
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Vamp, Вы писали:
V>Я скажу по-простому. Если не проверять корректность данных, то можно потерять инвариант объекта. А если корректность проверять, то в странной парадигме сеттера ничего не остается, как бросить исключение, так как возвращаемого результата у него в отличии от нормаьного метода нет.
Простота она иногда хуже воровства с другой стороны KISS тоже не спроста
Проблема как раз в том можем ли мы сформулировать этот самый инвариант так, чтоб покрыть все возможные сценарии использования объекта и при этом с уважением отнестись к SRP...