Не так давно, мне посчастливилось взять интервью у Бертрана Мейера, того самогоJ, автора самого фундаментального труда в области ООП и разработке ПО (это я все о том же талмуде "Объектно-ориентированное конструирование программных систем"), у человека, который внес неоценимый вклад в развитие инженерных аспектов разработки ПО, в формализацию объектной методологии, а также процесс разработки в целом.
Возможность эта появилась у меня благодаря Учебному Центру Люксофт, который организует мастер класс Бертрана по теме "Design by Contract" (на который, кстати, могут записаться все желающие за умеренную плату;)). И мы решили, что было бы неплохо пообщаться с Бертраном до этого и обсудить некоторые интересные моменты разработки ПО.
Перед интервью я подготовил список вопросов на 3 страницы (!), к сожалению, из-за ограничения по времени я так и не узнал, чья же версия принципа открыт-закрыт является более разумной – Бертрана или "дядюшки" Боба. Но несмотря на это, мы успели обсудить много интересного, начиная от контрактов, заканчивая идеями из новой книги Бертрана под названием "Agile!: The Good, the Hype and the Ugly".
Кстати, интервью было на русском языке, что было весьма приятно, хотя и сказалось на количестве тем, которые мы успели обсудить.
Дизайн и проектирование по контракту
Здравствуйте, Бертран. Я очень рад, что у меня появилась возможность пообщаться с человеком, который внес неоценимый класс в методологию разработки ПО вообще, и в объектно-ориентированную разработку в частности.
Одним из наиболее важных ваших вкладов является идея контрактного программирования (Design by Contract), поэтому именно с нее хотелось бы начать наш диалог. Итак, в чем заключается важность проектирования по контракту?
Контракты представляют собой один из лучших способов, который мы знаем сегодня, для достижения корректности в ПО, важность которой должна быть очевидной. Конечно, контракты не являются панацеей в этом деле, но они являются очень важным этапом на этом пути. Еще важно подчеркнуть, что контракты – это не только теоретическая идея, но и весьма практический инструмент, которым может пользоваться практически каждый программист.
Ну и нужно сказать, что контракты – это не исключительно мое изобретение. Корни этих идей лежат в трудах таких ученых, как Тони Хоар, Эдсгер Дейкстра и других.
Идеи контрактного программирования возникли в середине 80-х годов, но лишь сейчас они начали появляться в mainstream языках программирования и платформах разработки. Как по вашем, в чем причина столь долгой адаптации этих идей? Почему они поддерживаются столь малым числом языков программирования?
Конечно можно сказать, что идеи контрактного программирования все же распространились и многие программисты о них знают и используют Но вы правы, пользуются ими далеко не все программисты. Причина такого положения очень проста: для нормального использования контраков нужно использовать язык с их полноценной поддержкой. Конечно, можно добавить техники DbC (Design by Contract) в любой язык, но они будут не столь удобными, как в случае использования языка с их полноценной поддержкой.
В таком случае остается вторая часть вопроса: почему тогда столь мало языков поддерживают контракты? Конечно же есть Eiffel, из новых языков можно отметить язык D, а для платформы .NET есть библиотека Code Contracts (хотя это поддержка на уровне платформы, а не на уровне языка). Но это все же единицы...
Предусловия и постусловия довольно легко добавить в любой язык с помощью макросов или сторонней библиотеки. Например, есть библиотека Nana для поддержки контрактов в С++. О ней довольно много говорили несколько лет назад, но я не знаю, используется ли она сейчас или нет.
Добавить предусловия и постусловия даже в существующий язык довольно просто, но это далеко не все, что может понадобиться. Значительно более интересные аспекты контрактного программирования – это инварианты классов и циклов, связь контрактов с документацией, статический анализатор кода (прим.: речь идет о статическом анализаторе корректности на основе утверждений контрактов) и поддержка контрактов инструментами разработки. Вот эти аспекты довольно сложно реализовать, если контракты не являются изначально частью языка программирования.
Именно поэтому многие люди, начинают пользоваться контрактами, но со временем осознают, что пользоваться ими ежедневно в таком виде слишком трудно или неудобно. И со временем они либо переходят на язык с полноценной поддержкой контрактов или прекращают их использование.
Если же разработчики используют такой язык, как Eiffel, в котором пользоваться контрактами легко и удобно, то такие проблемы не возникают.
Насколько большое комьюнити языка Eiffel и в каких областях он применяется?
Сообщество относительно небольшое и не идет ни в какое сравнение с сообществами Java или C#. Тем не менее это значительное сообщество, которое растет довольно быстро.
Что касается области использования, то Eiffel обычно используется в критических приложениях и в областях с очень сложной доменной логикой: это финансы, сетевое программирование, здравоохранение, военные приложения и приложения для авиционной отрасли.
А что по вашему может стать следующим большим прорывов в области проектирования приложений вообще и проектирования надежных и качественных приложений в частности?
Сейчас активно продолжаются работы в области верифицируемого ПО, что позволит гарантировать отсутствие ошибок. Это всегда было целью языка Eiffel и некоторых других языков. Эта технология медленно, но все таки развивается. Уже сейчас можно доказать корректность программ в очень специфических условиях для простых (обычно старых) языков программирования.
Впоне возможно, что через несколько лет будет возможность доказать корректность приложений в рамках современного языка программирования, среды и инструментов разработки. Технологии, на которых это строится довольно старые и существуют уже несколько лет, так что скоро уже их можно будет использовать в реальных приложениях.
Мне кажется, что это очень важная область, поскольку наше общество все сильнее начинает зависеть от корректности окружающих нас приложений, и пользователи хотят не просто функциональные приложения, но и приложения, чью корректность можно гарантировать.
В последнее время наметилась тенденция в популяризации функциональных языков и функциональной парадигмы программирования. Что вы скажите, является ли объектная технология конкурентом функциональному программированию?
Нет, эти две парадигмы не являются конкурентами, они успешно могут дополнять друг друга. Тем не менее, тенденция к функциональному программированию является важной и интересной.
На мой взгляд, когда речь идет о высокоуровневой структуре приложения (особенно больших программ), то в мире нет ничего лучше объектного подхода. Я просто не вижу, как можно писать действительно большую программу исключительно на функциональном языке.
С другой стороны, если общая структура приложения построена на основе объектов, то очень даже полезно, если некоторые ее части будут написаны на функциональном языке, для обеспечения простоты и возможности доказательства корректности, о которых я говорил ранее.
Несколько лет назад я опубликовал статью на эту тему, где сравнивал ОО и ФП подходы. В ней я постарался показать, что ОО метод включает функциональное программирование, а не наоборот.
Да, я кажется читал эту статью, которая затем вошла в качестве одной из глав в книгу “Beautiful Architecture”.
Вы знаете об этом? Я очень впечатлен.
(Смеюсь...) Да, и насколько я помню, это был ваш ответ на статью Саймона Пейтона Джонса, в которой автор старался показать, что ФП подход является более предпочтительным.
Да, совершенно верно.
ПРИМЕЧАНИЕ
Речь идет о статье Бертрана "Software Architecture: Functional vs. Object-Oriented Design in Beautiful Architecture", опубликованной в книге "Идеальная архитектура. Ведущие специалисты о красоте программных архитектур.". Эта статья Мейера была ответом на статью Саймона "Composing contracts: an adventure in financial engineering."
Давайте все же немного вернемся к вопросу OOP vs FP. Какие именно преимущества у функционального подхода на "низком уровне"?
В Eiffel существует очень важный принцип, под названием Command-Query Separation Principle, который можно рассматривать, в некотором роде, как сближение ОО и ФП миров. Я не считаю, что наличие состояния – это однозначно плохо. Но очень важно, чтобы мы могли ясно различать операции, которые это состояние изменяют (т.е. командами), и операции, которые лишь возвращают информацию о состоянии, его не изменяя (т.е. запросами). В других языках эта разница отсутствует. Так, например, в С/С++ часто пишут функции, которые возвращают результат и изменяют состояние.
Следование этому принципу позволяет безопасно использовать выражения с запросами зная, что они не изменяют состояние. В некоторых случаях можно пойти еще дальше и работать в чисто функциональном мире с полным отсутствием побочных эффектов.
Означает ли это, что метод в таких языках как C++/C#/Java, который лишь вычисляет результат и не производит побочных эффектов, также отвечает принципу CQSP?
Да, конечно.
Т.е. отличительной чертой запроса является не отсутствие каких-либо вычислений, а именно отсутствие побочных эффектов?
Совершенно верно!
Процессы, принципы и методологии разработки
Я недавно начал читать вашу книгу "Agile!: The Good, the Hype and the Ugly" и обратил внимание, на ваше прагматичное отношение ко многим "модным" сегодня тенденциям. А как вы относитесь к принципам программирования вообще, и SOLID принципам в частности?
Очень важно выделять хорошие и полезные принципы; это характерно для всех инжерерных дисциплин. Но тут важно, чтобы эти идеи служили не только ради популяризации идей конкретного человека, а чтобы за ними стояли точные и убедительные аргументы (rationale). Именно это я старался делаться с принципами проектирования, которые сам продвигал. Я очень положительно отношусь к трудам, которые пробуют определить хорошие принципы и методологии.
Интересно, что в каждое время есть свои "проповедники". Так, в 70-х годах были очень сильные "методологисты", такие как Дейкстра, Хоар, Парнас и другие. Это были люди, которые совмещали в себе "евангелизм" и глубину понимания обсуждаемой проблемы, особенно в области разработки корректных и надежных программ. В течении последних 20 лет было довольно мало интересных работ в этой области.
Работы по методологии программирования хоть и существовали в этот период в большом количестве, не были столь научными и серьезными, а скорее отражали мнение группы людей. Они считали, что они открыли истину, хотя сделать это без глубокого изучения и базовых знаний, которыми обладал тот же Дейкстра, невозможно. Но радует, что в последнее время начались появляться более серьезные работы на ту тему.
В последнее время стали очень популярными гибкие методологии и юнит-тестирование в частности. В своей последней книге Вы упоминаете, что положительной стороной гибких методологий является популяризация автоматического (регрессионного) тестирования.
Что вы думаете о юнит-тестировании?
Причина моего интереса к гибким методологиям в том, что когда я начал изучать эту теу более подробно, то обратил внимание, что это дикая смесь очень хороших и очень плохих идей. И тестирование – это типичный пример.
Тестирование является очень важным аспектом разработки ПО и очень здорово, что гибкие методы сделали его столь популярным. Но, с другой стороны, есть и негативная сторона. Она заключается в том, что очень часто тестами пытаются заменить спецификацию системы. Это откровенная ерунда.
Здесь отлично подходит аналогия с яйцами и омлетом. Из яиц можно сделать омлет, но обратного пути нет. Отношение между спецификацией и тестами аналогично: при наличии спецификации вы сможете сделать хороший набор тестов, но обратное преобразование невозможно. Ни один, ни даже тысяча тестов не могут заменить спецификацию системы, поскольку тест – это лишь один пример, а спецификация – это абстрактное описание целей программы.
В плане тестов гибкие методологии сыграли двоякую роль. С одной стороны, для многих тесты стали неотъемлемой частью процесса разработки, а с другой стороны, многие начали думать, будто при наличии тестов делают спецификацию не нужной. Именно это я стараюсь делать в своей книге: показать, что является полезным в гибких методологиях, а о чем лучше просто забыть.
Мы обсудили связь тестов со спецификацией, а как насчет влияния тестов на дизайн. Есть ли связь юнит-тестов и дизайна приложения?
Я обратил внимание, что многие разработчики при использовании юнит-тестов начинают пользоваться контрактами, но делают это слишком поздно. Очень важно, чтобы контракты внедрялись не на этапе тестирования, а на этапе дизайна. Даже во время разработки стоит думать о дизайне и спецификации: нужно думать не только о том, как работает программа, но и о том, что она делает на более абстрактном уровне. Если думать о спецификации, контрактах и кодировании одновременно, то тогда, когда придет время для тестирования, ты будешь точно знать, какие тесты нужны.
Вы сказали, что важно думать о дизайне класса на более ранних этапах, а не во время тестирования. Но ведь есть же TDD (Test-Driven Development), в котором предполагается использовать тесты на этапе дизайна и обдумать обязанности класса через тесты, и лишь затем переходить к реализации. Что вы думаете на этот счет?
Мне кажется, что здесь снова есть удачная и неудачная мысли. Хорошая мысль в том, что появилось правило, воспетое ХР (eXtreme Programming), что для каждой строки кода должен быть соответствующий тест. Поэтому каждый раз, когда вы пишите строку кода, метод или модуль вы должны написать один или несколько тестов.
Плохая идея в том, что тестами пытаются заменить спецификацию и появились мысли, что требования (requirements) не нужны, а вместо них достаточно использовать сценарии использования (use cases). Это глупая мысль, поскольку сценарий использования по отношению к требованиям играют ту же роль, что и тест по отношению к спецификации: сценарий использования является лишь частным случаем, из которого невозможно вывести полноценные требования.
Я неоднократно подчеркивал в своей последней книге, что система – это не просто объединение частных случаев. Поэтому ничего хорошего не выйдет, если строить систему так, как рекомендуют авторы гибких методологий, путем объединения последовательных этапов.
Все подходы "гибких авторов" направлены против идеи "big upfront something" и с этой мыслью я крайне не согласен. Да, они правильно критикуют проекты, в которых на сбор и изучение требований, на обсуждение архитектуры и дизайн, тратится очень много времени без написания единой строчки кода. Но не стоит впадать в другую крайность. Мысль, что можно начинать писать систему без каких-либо начальных усилий по сбору требований и совершенно без затрат на дизайн, также утопична.
Я правильно понимаю, что вы критикуете обе крайности: паралич анализа плох, но и бежать "с шашкой на голо" и браться необдуманно за разработку тоже утопично?
Совершенно верно.
Команда в Eiffel Software является распределенной. Сейчас, в эпоху аутсорса это типичная картина. Есть ли секреты, как упростить процесс разработки в таких условиях, и как распределенная команда влияет на процесс разработки?
Никакого секрета нет, мы просто следуем ряду полезных принципов.
Во-первых, мы уделяем внимание коммуникациям. В нашей команде мы проводим 2 встречи в неделю по одному часу каждая, что сделать достаточно сложно, учитывая разные часовые пояса, в которых находятся ее члены, но мы с этим справляемся.
Эти две встречи носят разный характер. Первая встреча проходит в понедельник и является своего рода недельным стенд-апом. На ней каждый участник старается ответить на три классических скрам-вопроса: что я делал на прошлой неделе, что я буду делать на этой неделе и с какими сложностями (impediments) я столкнулся. Единственная цель этой встречи – это оценить, насколько эффективно мы двигаемся к достижению целей текущего спринта (который длится 4 недели).
Вторая встреча проходит в четверг и тоже длится один час. Но для нее мы заранее готовим четкий план (agenda), который публикуем в Google Docs. В понедельник мы никогда не обсуждаем детали и если на встрече возникает технический вопрос, не решаемый за минуту, то мы переносим его на четверг.
Эта техника двух встреч, мне кажется, работает очень хорошо. У нас есть возможность для каждого члена команды пообщаться друг с другом физически через Skype, WebEx, Citrix или подобный инструмент, проверить состояние релиза, а также обсудить трудные технические вопросы.
Второй наш принцип – это использование инспекций кода (code review). Я также написал статью на эту тему (прим., речь идет о статье "Design and Code Reviews in the Age of the Internet". Кстати, статья очень классная!). Эта практика очень хорошо работает, если подходить к ней с умом. Инспекция кода состоит из двух этапов: офлайнового и онлайнового. Вначале каждый инспектирует код самостоятельно, а затем мы проводим встречу, к которой тщательно готовимся. Мы письменно излагаем вопросы, которые собираемся обсуждать, и которые вызвали разногласия в команде. Мы стараемся использовать время встречи максимально эффективно, обсуждая интересные вещи и проблемы, по которым не было единого мнения.
Коммуникация очень важна; важно, чтобы люди лично знали друг друга. Мне было бы очень трудно работать в распределенной команде с людьми, с которыми я никогда не встречался лично. Даже если вы недолго проработали под одной крышей, этого уже достаточно. После этого гораздо проще общаться на расстоянии, по сравнению с тем, когда вы никогда не видели своего коллегу лично.
О языках программирования
Давайте вернемся немного к языкам программирования. Я обратил внимание, что новая версия языка Eiffel выходит дважды в год и следующий релиз должен состояться очень скоро – в этом мае. Я посмотрел на план этого релиза и увидел, что одно из нововведений звучит так: “functional programming mechanisms and other language facilities”. Интересно, о каких таких "функциональных механизмах" идет речь?
Нужно сказать, что базовые механизмы функционального программирования – агенты (прим. это локальные или анонимные методы в Eiffel) появились 10 или 12 лет назад. Данные изменения касаются прежде всего синтаксиса, чтобы писать в функциональном стиле стало проще, а синтаксис стал короче. Это значит, что на языке Eiffel можно будет писать программу или ее часть таким образом, что она будет очень похожей на программы на Haskell или ML.
Не все знают, что хотя Eiffel является объектно-ориентированным языком, на нем вполне можно писать программы в функциональном стиле. Это было возможным давно, но синтаксис был несколько тяжелым, так что мы тщательно продумали и добавили новые техники и выражения, которые позволят писать более короткие и изящные программы не жертвуя ОО-принципами вообще и принципа языка Eiffel в частности.
В последнее время во многих языках появилась поддержка асинхронного программирования на уровне языка (например, конструкции async/await в языке C#). Eiffel обладает поддержкой параллельного программирования в виде расширения SCOOP, а есть ли мысли расширения языка для поддержки асинхронного программирования?
Я очень положительно отношусь к новым возможностям async/await в C#, но мне кажется, что для их полноценного использования все же нужно понимать достаточно сложные детали. С первого взгляда кажется, что пользоваться этими конструкциями просто, но в некоторых случаях они могут оказаться такими же сложными, как и ручное управление многопоточностью и асинхронностью.
В SCOOP мы пробуем добавиться того, чтобы параллельное программирование было таким же простым, как и последовательное, однопоточное. По поводу же вашего вопроса: SCOOP является надмножеством и "из коробки" поддерживает вещи, аналогичные async/await, причем делает это просто и надежно.
Таким образом, особого смысла в добавлении такой возможности в Eiffel нет, поскольку уже есть механизмы, которые прекрасно справляется с решением таких задач?
Да, именно так.
Насколько я помню из вашей книги "Объектно-ориентированное конструирование", одним из главных лозунгов языка Eiffel является следующий: в языке должна быть одна и только одна возможность выполнить некоторую задачу. Именно поэтому до последнего времени был лишь один способ организации цикла в Eiffel. Не изменилась ли эта точка зрения со временем?
Во-первых, лозунг звучит так: в любом языке есть множество способов достижения цели, но должна быть лишь одна ХОРОШАЯ возможность сделать это.
Да, принцип все еще существует, хотя появились критики, что мы уже не следуем ему так строго, как раньше. Когда вы упомянули по поводу циклов, то видимо имели ввиду появление в языке Eiffel нового вида циклов – across (прим. это конструкция аналогична циклу foreach языка C#). Очевидно, что если речь идет о простом переборе элементов списка или другой структуры данных, то across является наиболее подходящей формой цикла. Если же нужна более сложная форма цикла, то традиционный вариант всегда остается доступным.
Мне кажется, мы сознательно следуем этому принципу. Очень важно, чтобы язык не стал набором возможностей, собранных из разных языков программирования. Если идея хорошая, то это не значит, что ее нужно добавлять на уровне языка. Любой дизайн – это компромисс. Это очевидно при разработке приложений, и еще более справедливо для разработки языков программирования.
Так, я очень строго борюсь против появления новых ключевых слов. Обычно довольно легко найти вескую причину для добавления нового ключевого слова, и если бы я не был "врединой", то за последние 20 лет количество ключевых слов в Eiffel уже удвоилось бы. Это не только моя заслуга; эту точку зрения разделяют многие мои коллеги и мы вместе боремся с естественной тенденцией расширения языка и его "фичеризма". Тем не менее, Eiffel развивается и это уже не тот язык, каким он был 20 или даже 5 лет назад.
Спасибо Бертран. Нам пора заканчивать наш диалог и в конце я хотел бы задать вопрос общего характера. Что по-вашему является наиболее полезной чертой разработчика? Что нужно развивать в себе, чтобы стать успешным программистом или архитектором?
Ох... это сложный вопрос. Я бы сказал, что самая важная черта – это уметь доводить дело до конца. Достаточно просто придумать хорошую идею и начать ее реализовывать, а вот довести его до ума, продумав все детали, чтобы программа была функциональной и изящной, вот это самое сложное.
Сложность заключается в том, что существует масса критериев. Программа должна быть корректной, надежной, безопасной, удобной в использовании и т.п. Достичь одного-двух критериев не так и сложно, а вот достичь компромиссной реализации всех критериев – очень сложно.
----------------------
У меня осталась масса вопросов, но к сожалению время Бертрана было ограниченным и мы так и не смогли поговорить более глубоко о гибких методологиях, современных принципах проектирования и многих других вопросах. Тем не менее, мне было очень интересно пообщаться с Бертраном и я надеюсь, что вам было приятно читать его интервью;)
UPDATE
Поступило предложение добавить пруф, хотя бы в виде скриншота нашего интервью:
Спасибо за интересное интервью! Как раз читаю "Object-Oriented Software Construction". И как раз главу про контракты :)
ОтветитьУдалитьПока есть ощущение, что сильно заморачиваться с контрактами стоит при написании повторноиспользуемых библиотек/фреймворков, либо сложных приложений. В довольно простых приложениях, мне кажется, вполне хватит простейших предусловий (проверка на null, неотрицательность и прочее; здесь вполне можно обойтись обычными Guard Clause) для public и internal членов, а также проверяемых на этапе компиляции инвариантов уровня класса с использованием ключевого слова readonly (привет, constructor injection). Во всем остальном можно обойтись тестами.
С другой стороны, не всегда угадаешь, когда простое приложение эволюционирует в сложное.
Владимир, я сейчас пилю небольшой аддон к решарперу, как раз для поддержки контрактов (удобная генерация предусловий/постусловий/инвариантов) - https://github.com/SergeyTeplyakov/ReSharperContractExtensions.
УдалитьТак вот, я там очень интенсивно использую контракты. Особенно полезны инварианты, которые позволяют лучше понять, что справедливо, а что нет на протяжении всего времени жизни объекта. Обычно это очень сильно упрощает понимание системы.
Eiffel уникальный язык. Когда на нем начинаешь программировать, то контракты начинают писаться сами собой и для всего. Уж не знаю, что именно этому способствует. Например, мне было тяжело писать циклы в Eiffel, но если делать это одновременно с декларацией инвариантов цикла, то работа с циклами упрощается.
УдалитьИ такая хрень с контрактами в Eiffel-е везде. Даже документация по штатным библиотекам воспринимается иначе, т.к. контракты становятся ее частью и, зачастую, взгляда на прототип метода и его контракты становится достаточно для понимания того, как метод следует использовать, даже описание читать не приходится.
Однако, в других языках, в том же D, где есть часть Design By Contract, ситуация уже не такая. Даже если пытаешься себя заставлять писать контракты, то все равно очень быстро на это забиваешь. И не понятно, то ли в других языках действительно без контрактов можно обойтись, то ли ты делаешь что-то не так.
Евгений: хорошее наблюдение. Видимо это именно то, о чем говорил Бертран (хотя я стараюсь таки с собой бороться и их использовать относительно полноценно в .NET через Code Contracts, хотя поддержка IDE - весьма печальная).
УдалитьСергей, не было проблем на сборочном сервере при использовании CodeContracts? Мы на проекте отказались от них из-за того, что сборка время от времени подвисала во время emit-а кода этой библиотекой
УдалитьВлад, у нас не настолько огромное покрытие контрактами, возможно поэтому проблем не было. А возможно, мы используем чуть более позднюю версию Code Contracts.
УдалитьУ вас кстати они включаются в релизную сборку? Или только в дебажную?
УдалитьПоскольку мы мигрируем на контракты, то мы в дебажной используем все, а в релизной вырубаем, поскольку используем "старые" контракты: простую проверку, после которой идет EndContractBlock();
Удалитьпроверка на null
ОтветитьУдалитьиспользуйте монадки, и не нужно парится с null. Накладывайте рестрикшен через тип.
если интрестинг то https://github.com/AntyaDev/FunctionalWeapon
Мне кажется, что это какой-то сверхупрощенный взгляд на мир.
УдалитьНесколько моментов: да, выражение чего-то через систему типов значительно лучше, чем через контракты, но и приведенная библиотека далеко не всегда поможет.
Во-первых, нам нужны не монадки, а not-nullable reference типы; Ведь в том же F# (откуда, собственно, и растут корни библиотеки) все переменные ссылочного типа являются not-null. В твоем же случае, никто не запретит вместо твоего Maybe получить null и потом долго долго разбираться, почему же летит NullReferenceException, из-за того, что ты пытаешься достучаться к Value, когда IsValue == false, или весь твой Maybe равен null.
Именно поэомудоступе к Value в твоем Maybe, когда IsSome возвращает false НЕЛЬЗЯ БРОСАТЬ NullReferenceException!!! Нужно бросать InvalidOperationException.
В том же Eiffel пошли дальше и сделали все типы по умолчанию not-nullable, и добавили спец тип string!, который говорит, что тип nullable.
Но даже в этом случае твои монады вряд ли помогут. Во-первых, все еще нужны будут контракты, чтобы выразить, что в определеленном месте твоя монадка содержит или не содержит значения (сама монада об этом знать не может, это ТВОЕ предположение по поводу поведения системы). Во-вторых, контракты тебе помогут при работе с внешним API, а твои монады - нет.
Так что идея интересная, но она точно не сможет заменить контракты в борьбе за отсутствие NRE.
1) не понял почему корни из F# "Ведь в том же F# (откуда, собственно, и растут корни библиотеки)"
Удалить2) все переменные ссылочного типа являются not-null
- там есть возможность выхватить null, даже в коде чекать можно на null(AllowNullLiteralAttribute).
3) Во-вторых, контракты тебе помогут при работе с внешним API, а твои монады - нет.
Почему нет? у меня весь API на них строится? я получаю значение из "вне" и заворачиваю в Maybe or Exceptional. (монады это ж не только Maybe). Это довольно выразительно выглядит.
4) Контракт можно провтыкать поставить? просто если ты увязался например с Maybe то это уже тебя защитит от забывания проверить значение перед прямым доступом. Ты банально на компайл тайме получишь ошибку, что типы не одинаковы. (Maybe тоже можно провтыкать проверить написав someInstance.Value)
Я понимаю твой поинт, мне кажется что если (как зачастую) юзать контракты только для чека на null и примитивных проверок... то это оверхед(для .NET), а смысла не много(в сравнении с использованием Maybe).
Ссори я вверху лишний знак дописал. Нужно так: "у меня весь API на них строится."
УдалитьДобавлю, на проекте не все быстро подхватили идею "монад" в целом, но со временем все на них перешли... весь код выглядит в апликативном стиле, очень выразительно(без кучи basic controlflow операторов типа if else, try catch).
1) Я думал ты почерпнул идеи из F#. Даже если это не так, то это идеи ФП-языков, в которых все ссылочные типы not-null. Ты просто не можешь в F# присвоить переменной ссылочного типа null-литерал (или передать null в метод, которые требует string). Именно поэтому использование Maybe в F# проще, чем в C#.
Удалить2) да, в F# можно передать null, но это все же исключительный случай, а не базовый. В языке же C# - это базовый, причем запретить передать null в твой Maybe или любой другой "приемник" ссылочного типа невозможно.
>> 3) Во-вторых, контракты тебе помогут при работе с внешним API, а твои монады - нет.
> Почему нет? у меня весь API на них строится?
В том-то и вопрос: что это у тебя он на них строится, я же говорю за *внешний API*. Плюс не забывай, что твой Maybe - это класс, а значит ты удваиваешь число аллокаций. В большинстве случаев - все равно, но это все равно достаточно дорого для использования абсолютно повсеместно.
> 4) Контракт можно провтыкать поставить?
Разве это аргумент? Ты же сам привел контраргумент:)
Вопрос в другом: сам Maybe не знает, в каких случаях он должен содержать значения, а в каких нет. Собственно именно для этого и нужны контракты: эта форма спецификации прямо в коде, которая показывает твои предположения относительно системы во время исполнения.
Я-то не особо спорю, что контракты исключительно для обеспечения "Void Safety" - это перебор. Но даже в этом случае Maybe не всегда тебе поможет.
> юзать контракты только для чека на null и примитивных проверок... то это оверхед(для .NET)
Только не "оверхед", а оверкил. Оверхеда в контрактах не больше, чем в простых проверках на null.
Еще раз: Maybe - это ок вариант, но он слишком ограничен. Если ты работаешь лишь со своим кодом, то он подходит, но как только ты начинаешь работать с внешним API, то ты теряешь эту чудную возможность. Каждый раз оборачивать все обращения к внешним (включая BCL) типам - не вариант, в результате ты получаешь неуниверсальный инструмент, что не удобно.
По поводу второго коммента: я прекрасно понимаю пользу Maybe, но не в C#, а в F#. Причины смотри в предыдущем комментарии.
УдалитьА в чем разница? Мнимое отсутствие деструктивного присваивания? Причем здесь вообще ФП языки? Мне кажется, что монада это вычислительная конструкция, тайп класс с определенными laws. Сама семантика использования монад подразумевает принципильно иной вектор мышления - контроль за сайд-эффектами, монадик копрехеншенс, композиция вычислительных элементов.
УдалитьА чем можно заменить ST или ContT?
> 4) Контракт можно провтыкать поставить?
УдалитьРазве это аргумент? Ты же сам привел контраргумент:)
В том то и поинт, что это еще легче провтыкать чем явно кинуть return null. Конкретно с Maybe и для C#: когда ты с ним увязываешся ты про null должен забыть вообще.
>> 3) Во-вторых, контракты тебе помогут при работе с внешним API, а твои монады - нет.
Я привиду реальный кусок кода, с внешним API
var info = WinApiService.GetForegroundWindow(processName)
.Map(AutomationElement.FromHandle)
.Map(element => element.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "My App")))
.Map(element => TreeWalker.RawViewWalker.GetLastChild(element))
.Map(element => element.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar)))
.Map(toolbar => toolbar.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom)))
>>4) Плюс не забывай, что твой Maybe - это класс, а значит ты удваиваешь число аллокаций.
- как по мне вообще не аргумент. что тогда говорить за F#? (за оптимизацию(реализацию) с immutable колекциями я знаю)
чисто из спортивного интереса, я сделал примитивные замеры:
static void Main(string[] args)
{
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000000; i++)
{
var dd = new StringBuilder();
//var dd = Maybe.Some(new StringBuilder());
}
Console.WriteLine(sw.ElapsedMilliseconds);
Console.ReadLine();
}
обычное инстанцирование милиона StringBuilder занимает у меня 31 - 32 мс, с Maybe время увиличилось: 41 - 43 мс
- Это маленький хак для оптимизации, есле действительно надо: ты можешь писать обычные private методы которые могут ретурнуть тебе null. а уже в public Maybe ты делаешь композицию с private методов.
>>Каждый раз оборачивать все обращения к внешним (включая BCL) типам - не вариант, в результате ты получаешь неуниверсальный инструмент, что не удобно.
- А Code Contracts для .NET универсальный?
>> В твоем же случае, никто не запретит вместо твоего Maybe получить null и потом долго долго разбираться, почему же летит NullReferenceException
- за почти 1 год активного юзания в продакшене не было ни одного случая.
>> Именно поэомудоступе к Value в твоем Maybe, когда IsSome возвращает false НЕЛЬЗЯ БРОСАТЬ NullReferenceException!!! Нужно бросать InvalidOperationException.
- 100% согласен. Поправлю, спс.
>>Так что идея интересная, но она точно не сможет заменить контракты в борьбе за отсутствие NRE.
она делает больше чем просто проверки, ты строишь проджекшены, ты уходишь от if, else, try catch, ты выносишь все сайд эффекты в одно место.
@Antya: меня не нужно убеждать в пользе Maybe там, где мне нужны not-nullable типы. От повторения, насколько он хорош аргумент не станет более убедительным;) А фишка в том, что мне обычно нужен именно такой: NotNullable, который бы не позволял создавать экземпляр со значением T равным null.
УдалитьЯ понимаю пользу твоего решения, но не верю, что оно толковое. Посему у меня предложение: с тебя статья с описанием "хорошести" твоего подхода, а с меня последующая статья-критика. Вот это будет более конструктивно, чем препирательство в комментах.
Идет?
@Antya: готов продолжить наше обсуждение в комментариях к новой статье;)
Удалитья ответил в коментах к новой статье.
УдалитьСпасибо за интересное интервью!
ОтветитьУдалитьИнтересное интервью, спасибо. Да и плагин для решарпера заинтересовал, буду следить. Слабая поддержка в IDE - один из очень важных факторов, мешающих использовать контракты. Уж что-что, а собственную лень не стоит недооценивать.
ОтветитьУдалитьПриятно когда такой человек высказывает мысли, котрые полностью совпадают с вашими :) (это я про дизайн и юнит тесты)
ОтветитьУдалитьНе совсем понял - как это интервью на русском? Мейер знает русский?
Да, Бертран знает русский. Вот небольшой пруф: https://www.youtube.com/watch?v=bPc-HyFdP-g
УдалитьИ да, и правда приятно, когда мнения совпадают:)