В изначальном примере таймаут передавался прямо в вызываемую функцию, для клиента это в общем-то то же самое, что и токен. Можно было не накручивать снаружи Task.WhenAny
Вы использовали выражение await, для того, что бы явно сделать синхронный вызов функции.
Синхронный вызов — это когда исполнение приостанавливается в точке вызова.
Именно это вы и делаете, когда предлагаете использовать await.
Синхронный — это если бы я написал waitForBytesAsync.Wait().
Здесь же, если говорить грубо, await приводит не к синхронному ожиданию, а просто код после него будет вызван, когда асинхронная операция завершится. В остальное же время поток будет свободен и ничего ждать не будет.
Субъективно — производительность отличная. В отличие от студии+решарпера, работа с крупным проектом в rider не раздражает. Тормоза изредка случаются, но по сравнению с постоянно зависающей и вылетающей 3 раза за день студией — прекрасно. Это личный опыт, YMMV.
В языках, которые умеют async/await (тот же C#), в вашем примере все сведется к такой замене:
serial->waitForBytes( 18, 5000)
на
await serial->waitForBytesAsync(18, 5000)
В результате код ничуть не усложнился, а взаимодействие стало асинхронным. Так что асинхронные интерфейсы при наличии соответствующих механизмов ничуть не сложнее синхронных.
Количество багов зависит от логической продуманности приложения, а не архитектуры и способа написания.
"Логическая продуманность приложения" вообще-то примерно то же самое, что и архитектура, не находите?
А скорость выявления багов вообще только замедляется с увеличением качества кода. Ведь, чем кода меньше, тем он сложней! И находить ошибки в коде, который сложнее написан — «сложнее».
Ну если оценивать качество по количеству логики на число строк, то да. Но только качество оценивают не по этому критерию. Увеличение качества — это когда код становится понятнее. Это не обязательно ведет к уменьшению размера, кстати.
но люди выздоравливают… и технология работает…
Пока не убъет кого-нибудь из-за бага, закравшегося в адовую простыню кода.
А что вы вообще понимаете под технологиями? Использование самого модного сегодня фреймворка? Как это влияет на результат? Продукт должен соответствовать требованиям заказчика. Какое качественное влияние оказывают на результат технологии? С кодом как-то понятнее, коряво напишешь — будут неочевидные баги и каждую мелкую проблему исправлять придется по 2 дня. А что изменится от того, что вместо современной технологии X я использую прошлогоднюю технологию Y?
В остальных случаях приоритет должен быть в технологиях, а не качестве кода.
Приоритет должен быть на соответствии требованиям. Накой все эти технологии, если код неподдерживаемый и любое изменение ломает пол-системы? Мы не технологии создаем, а продукты для бизнеса. Создание технологий — узкая ниша, в которой сидят компании вроде MS, Google, JetBrains и т.п. И то над созданием технологий там скорее всего трудятся небольшие группы экспертов, а подавляющее большинство программистов делает продукты.
Так пусть они свои задачи решают, не проблема. Пусть занимаются исследованиями, руководят аспирантами и преподают фундаментальные предметы вроде матана или алгоритмов.
Но очень грустно смотреть, как такие вот уважаемые "д.т.н., профессор", которые всю жизнь занимаются теоретическими исследованиями и ни строчки кода для бизнеса не написали, преподают программирование на всяких современных языках, ООП и прочее. В итоге их студенты кое-как могут запилить простые лабораторки, но код пишут как получится — нарушая все возможные рекомендации, которые можно найти в книгах Макконелла, Фаулера, Мартина и т.п. По рукам им дать некому. Что такое git, юнит-тесты, паттерны проектирования, SOLID и т.п. — они тоже не знают.
Я ровно о том, что все эти доктора наук должны заниматься своим делом, а в обучении будущих программистов/админов должны быть задействованы те, кто занимается этим на практике и знает, какие навыки нужны сейчас, а не 20 лет назад. Но таких обычно 2-3 человека на 20 преподавателей на кафедре, и они обычно зайдействованы не в обучении, а в выдаче и проверке лабораторных.
Где доктора наук будут рассказывать, что обязательно нужно комментировать каждую строчку кода на русском языке, чтобы другой программист понимал, что int c = a*b — это // с равно произведению a и b.
Имхо, девочек профессоров надо менять на программистов (админов/инженеров/...) с опытом практической работы, а не курсы вводить. Практика показала, что в образовании толку гораздо больше от аспирантов и молодых преподавателей, которые параллельно с универом работают и могут полезными знаниями и навыками поделиться. Но, к сожалению, таких мало и обычно они только лабы проверяют, а учат эти лабы выполнять такие вот профессора, которые примерно в начале 2000-х окончательно потеряли связь с тем, что происходит в индустрии.
Так-то я совершенно с вами согласен, но одно только это не поможет.
абстрактная фэктори для сервиса с единственным публичным методом, который вычисляет факториал используя частичную LINQ-выборку из бесконечного генератора чисел (который на yield return построен)
Это не совсем верно. Система образования у нас в основном такая, что из этой фразы выпускники большинства ВУЗов, обучающих по разным направлениям IT, знают только факториал. Ну и кто-то может помнить, что "методы — это то же самое, что функции".
И это неудивительно, если учитывать, что курс по веб-программированию в одном из довольно известных профильных ВУЗов последние лет 15 или даже больше читает 90+-летний профессор, рассказывающий про то, что сайты надо делать на CGI или PHP3 в IE6, а все более современное — фигня для домохозяек, которая никому не нужна. И это не один особый случай, так дела обстоят во многих учебных заведениях. Есть, конечно, и отличные преподаватели с отличным курсами, но это скорее исключение.
С другой стороны, многим студентам тоже нафиг не надо напрягаться, мотивации никакой. Все думают, что их учат как надо и они потом после выпуска сразу будут работать программистами, хотя их уровня знаний в IT хватит только для того, чтобы в excel графики строить и по инструкции hello world написать. А еще они методички понятного качества для будущих поколений пишут вместо преподавателей за автомат.
Так что я более чем уверен, что озвученная вами проблема — это не от переизбытка образования, а от его отсутствия. Я как раз сейчас магистратуру заканчиваю (осталось только диплом на руки получить) и это все прямо на себе ощущал 6 лет. Если бы не занимался самообразованием, то сейчас бы, видимо, думал, в какую организацию идти гамбургеры продавать на кассе. Зато отчеты писать умеем по ГОСТам и на 20 страниц размазывать то, для чего хватило бы 3-4.
Ковариантность и контравариантность — это о производных типах, а не об отношении квадрата и прямоугольника.
С точки зрения геометрии да, Square является Rectangle. В ООП это не обязательно так. Например, у прямоугольника при изменении Width не должно меняться значение Height. У квадрата же Height тоже изменится, что нарушает LSP. Поэтому такое наследование недопустимо. Допустимо оно только тогда, когда Width/Height неизменяемы.
Да в общем-то в этом нет ничего такого. В жизни да, это не двигатель. Но если в предметной области системы он выполняет только функцию двигателя и в другой роли не представлен, то как бы и нет особого смысла с интерфейсами заморачиваться. Принципиально это ничего не изменит.
Понимаете, даже первый пример не является стратегией. Но даже если допустить, что в первом примере двигатель приходит снаружи, то окажется, что дальнейшие упрощения уже неприменимы. Поэтому получается, что статья о том, как из стратегии сделать не-стратегию.
Кстати, можно было бы generic-аргументом показывать тип клиента, а не требуемой стратегии, и в зависимости от типа создавать/получать нужный экземпляр стратегии. Тогда предлагаемые улучшения в принципе возможны. Хотя тут встает вопрос, насколько адекватно делать аргументом двигателя тип транспорта.
Абстрагируйтесь, пожалуйста, от автомобилей. Если говорить про код, а не про автомобили, то никто и ничто в этом мире не мешает нам использовать один и тот же экземпляр дважды.
Да в общем-то даже один двигатель можно прицепить к двум машинам сразу, было бы желание и инструмент. Пример с буксиром в этом плане замечателен. Тут сразу видно, что мы один буксир прицепили к 3 плавсредствам. Если нам так надо было, не вижу проблем.
Проблема вашего решения с общим двигателем ровно в том, что это все происходит неявно и непредсказуемо.
Создавая инстанс автомобиля, я не могу узнать о том, что внутри там один двигатель, это крайне неочевидный контракт.
Если я это уже знаю, то этого может не знать мой коллега, который в другой части системы создаст инстанс автомобиля и угробит все приложение.
Нет средств контроля того, у кого двигатель, его нельзя отнять у владельца
Перестав использовать один инстанс автомобиля, мы просто не можем создать еще один, потому что старый может быть все еще не собран GC и возможны всякие интересные эффекты.
Ничто не мешает сделать двигатель параметром конструктора.
Да, безусловно. Тогда можно будет вести речь о том, что это стратегия. Но если это сделать, то все упрощения и generic-и из статьи уже неприменимы. Или надо делать Car, Plane и т.п. тоже generic и параметризовать типом двигателя.
Так в этом принципиальная разница, которая не дает текущему решению называться стратегией.
Паттерн Стратегия не определяет, каким образом контекст получает экземпляр стратегии. Контекст может получать ее в аргументах конструктора, через метод или свойство или получать ее у третьей стороны.
Получает. А не создает сам. В ваших примерах именно самостоятельное создание контекстом (автомобилем, самолетом, рикшей) конкретного двигателя. Следовательно, это не стратегия.
Если есть требование менять мотор прямо в полёте
Обычно подмена стратегии в уже созданном экземпляре контекста не нужна, и даже в рамках всего приложения во время его работы может использоваться стратегия только одного типа. Речь не о том. Речь о том, что в рантайме вы можете решить, использовать ли, например, двс или газовый двигатель. Сейчас для этого нужна перекомпиляция.
По определению, применение стратегии обусловлено двумя причинами: (1) инкапсуляция поведения или алгоритма и (2) возможность замены поведения или алгоритма во время исполнения. Любой нормально спроектированный класс уже инкапсулирует в себе поведение или алгоритм, но не любой класс с некоторым поведением является или должен быть стратегией. Стратегия нужна тогда, когда нужно не просто спрятать алгоритм, а когда нам важно иметь возможность заменить его во время исполнения!
Другими словами, стратегия обеспечивает точку расширения системы в определенной плоскости: класс-потребитель стратегии не знает, как выполняется некоторое действие и кто именно его выполняет; об этом знают классы более высокого уровня.
Суть в том, что клиент получает стратегию откуда-то, не зная о конкретном типе. То есть, если к автомобилю или рикше можно прикрутить реактивный двигатель, двс, лошадей, ездовых собак или педали от велосипеда — это стратегия. Но если класс "автомобиль" прибит к двс, а "рикша" — к слуге (как в вашем примере), то это не стратегия, а обычная композиция.
Само по себе вынесение двигателя из транспортного средства — это не стратегия. Не каждое вынесенное куда-то поведение есть стратегия.
Стратегию характеризует возможность замены этого поведения во время работы. Т.е. надо либо просунуть стратегию (двигатель) через конструктор конкретного клиента (машины, самолета), либо запросить его из клиента без указания конкретного типа стратегии.
Здесь этого нет, потому что каждый клиент (машина, рикша, самолет) сам говорит, с каким именно типом двигателя он работает (через Vehicle, через new FooEngine(), через Engine.GetEngine(). По сути это все разные способы создать двигатель определенного типа.
Я бы поспорил насчет стратегии, кстати. Это паттерн подразумевает, что клиенту можно подсунуть любую стратегию. Здесь же клиент создает ее сам (или получает ее откуда-то, но тип заранее известен), и предметная область подразумевает, что к каждому транспорту применим только один тип двигателя (нельзя вкрутить в самолет рикшу, например). Тут нет стратегии, это обычное наследование.
Вы говорите верно с точки зрения логики реального мира, в котором один двигатель действительно нельзя засунуть в две кредитопомойки.
Почему нельзя? Можно. Только не так, как в примере, а, упрощенно, так:
var car = new Car();
car.SetEngine(engine);
...
car.RemoveEngine();
Автомобиль не отвечает за то, чтобы найти и прикрутить себе двигатель, это делается как раз снаружи. Так есть хоть какие-то гарантии, что двигатель не прицепят два автомобиля сразу (это надо как-то проверять).
Предположим, на складе есть один рабочий двигатель, который подходит к нескольким транспортным средствам.
…
Это может быть накладно по ресурсам.
Почему бы не сделать пул двигателей?
var car = new Car();
car.SetEngine(enginePool.Acquire());
...
var engine = car.RemoveEngine();
enginePool.Add(engine);
А причем здесь вообще потоки? Здесь должна быть не потокобезопасность, а гарантия, что всегда существует не более одного инстанса любого типа, использующего данный двигатель. А раз речь идет о C#, где объекты чистятся GC, а не руками, то таких гарантий вообще нельзя дать.
И да. У вас в двигателе написано: // burn fuel // spin wheel. Видимо, топливо из общего глобального бензобака, и крутим глобальные колеса, которые прилеплены ко всем автомобилям мира?
Зачем такая сложность? В чем проблема создавать двигатель заново для каждого конкретного автомобиля?
В изначальном примере таймаут передавался прямо в вызываемую функцию, для клиента это в общем-то то же самое, что и токен. Можно было не накручивать снаружи Task.WhenAny
Синхронный — это если бы я написал waitForBytesAsync.Wait().
Здесь же, если говорить грубо, await приводит не к синхронному ожиданию, а просто код после него будет вызван, когда асинхронная операция завершится. В остальное же время поток будет свободен и ничего ждать не будет.
Субъективно — производительность отличная. В отличие от студии+решарпера, работа с крупным проектом в rider не раздражает. Тормоза изредка случаются, но по сравнению с постоянно зависающей и вылетающей 3 раза за день студией — прекрасно. Это личный опыт, YMMV.
В языках, которые умеют async/await (тот же C#), в вашем примере все сведется к такой замене:
на
В результате код ничуть не усложнился, а взаимодействие стало асинхронным. Так что асинхронные интерфейсы при наличии соответствующих механизмов ничуть не сложнее синхронных.
"Логическая продуманность приложения" вообще-то примерно то же самое, что и архитектура, не находите?
Ну если оценивать качество по количеству логики на число строк, то да. Но только качество оценивают не по этому критерию. Увеличение качества — это когда код становится понятнее. Это не обязательно ведет к уменьшению размера, кстати.
Пока не убъет кого-нибудь из-за бага, закравшегося в адовую простыню кода.
А что вы вообще понимаете под технологиями? Использование самого модного сегодня фреймворка? Как это влияет на результат? Продукт должен соответствовать требованиям заказчика. Какое качественное влияние оказывают на результат технологии? С кодом как-то понятнее, коряво напишешь — будут неочевидные баги и каждую мелкую проблему исправлять придется по 2 дня. А что изменится от того, что вместо современной технологии X я использую прошлогоднюю технологию Y?
Приоритет должен быть на соответствии требованиям. Накой все эти технологии, если код неподдерживаемый и любое изменение ломает пол-системы? Мы не технологии создаем, а продукты для бизнеса. Создание технологий — узкая ниша, в которой сидят компании вроде MS, Google, JetBrains и т.п. И то над созданием технологий там скорее всего трудятся небольшие группы экспертов, а подавляющее большинство программистов делает продукты.
Так пусть они свои задачи решают, не проблема. Пусть занимаются исследованиями, руководят аспирантами и преподают фундаментальные предметы вроде матана или алгоритмов.
Но очень грустно смотреть, как такие вот уважаемые "д.т.н., профессор", которые всю жизнь занимаются теоретическими исследованиями и ни строчки кода для бизнеса не написали, преподают программирование на всяких современных языках, ООП и прочее. В итоге их студенты кое-как могут запилить простые лабораторки, но код пишут как получится — нарушая все возможные рекомендации, которые можно найти в книгах Макконелла, Фаулера, Мартина и т.п. По рукам им дать некому. Что такое git, юнит-тесты, паттерны проектирования, SOLID и т.п. — они тоже не знают.
Я ровно о том, что все эти доктора наук должны заниматься своим делом, а в обучении будущих программистов/админов должны быть задействованы те, кто занимается этим на практике и знает, какие навыки нужны сейчас, а не 20 лет назад. Но таких обычно 2-3 человека на 20 преподавателей на кафедре, и они обычно зайдействованы не в обучении, а в выдаче и проверке лабораторных.
Где доктора наук будут рассказывать, что обязательно нужно комментировать каждую строчку кода на русском языке, чтобы другой программист понимал, что int c = a*b — это // с равно произведению a и b.
Имхо,
девочекпрофессоров надо менять на программистов (админов/инженеров/...) с опытом практической работы, а не курсы вводить. Практика показала, что в образовании толку гораздо больше от аспирантов и молодых преподавателей, которые параллельно с универом работают и могут полезными знаниями и навыками поделиться. Но, к сожалению, таких мало и обычно они только лабы проверяют, а учат эти лабы выполнять такие вот профессора, которые примерно в начале 2000-х окончательно потеряли связь с тем, что происходит в индустрии.Так-то я совершенно с вами согласен, но одно только это не поможет.
Это не совсем верно. Система образования у нас в основном такая, что из этой фразы выпускники большинства ВУЗов, обучающих по разным направлениям IT, знают только факториал. Ну и кто-то может помнить, что "методы — это то же самое, что функции".
И это неудивительно, если учитывать, что курс по веб-программированию в одном из довольно известных профильных ВУЗов последние лет 15 или даже больше читает 90+-летний профессор, рассказывающий про то, что сайты надо делать на CGI или PHP3 в IE6, а все более современное — фигня для домохозяек, которая никому не нужна. И это не один особый случай, так дела обстоят во многих учебных заведениях. Есть, конечно, и отличные преподаватели с отличным курсами, но это скорее исключение.
С другой стороны, многим студентам тоже нафиг не надо напрягаться, мотивации никакой. Все думают, что их учат как надо и они потом после выпуска сразу будут работать программистами, хотя их уровня знаний в IT хватит только для того, чтобы в excel графики строить и по инструкции hello world написать. А еще они методички понятного качества для будущих поколений пишут вместо преподавателей за автомат.
Так что я более чем уверен, что озвученная вами проблема — это не от переизбытка образования, а от его отсутствия. Я как раз сейчас магистратуру заканчиваю (осталось только диплом на руки получить) и это все прямо на себе ощущал 6 лет. Если бы не занимался самообразованием, то сейчас бы, видимо, думал, в какую организацию идти гамбургеры продавать на кассе. Зато отчеты писать умеем по ГОСТам и на 20 страниц размазывать то, для чего хватило бы 3-4.
Ковариантность и контравариантность — это о производных типах, а не об отношении квадрата и прямоугольника.
С точки зрения геометрии да, Square является Rectangle. В ООП это не обязательно так. Например, у прямоугольника при изменении Width не должно меняться значение Height. У квадрата же Height тоже изменится, что нарушает LSP. Поэтому такое наследование недопустимо. Допустимо оно только тогда, когда Width/Height неизменяемы.
Да в общем-то в этом нет ничего такого. В жизни да, это не двигатель. Но если в предметной области системы он выполняет только функцию двигателя и в другой роли не представлен, то как бы и нет особого смысла с интерфейсами заморачиваться. Принципиально это ничего не изменит.
Понимаете, даже первый пример не является стратегией. Но даже если допустить, что в первом примере двигатель приходит снаружи, то окажется, что дальнейшие упрощения уже неприменимы. Поэтому получается, что статья о том, как из стратегии сделать не-стратегию.
Кстати, можно было бы generic-аргументом показывать тип клиента, а не требуемой стратегии, и в зависимости от типа создавать/получать нужный экземпляр стратегии. Тогда предлагаемые улучшения в принципе возможны. Хотя тут встает вопрос, насколько адекватно делать аргументом двигателя тип транспорта.
Да в общем-то даже один двигатель можно прицепить к двум машинам сразу, было бы желание и инструмент. Пример с буксиром в этом плане замечателен. Тут сразу видно, что мы один буксир прицепили к 3 плавсредствам. Если нам так надо было, не вижу проблем.
Проблема вашего решения с общим двигателем ровно в том, что это все происходит неявно и непредсказуемо.
Да, безусловно. Тогда можно будет вести речь о том, что это стратегия. Но если это сделать, то все упрощения и generic-и из статьи уже неприменимы. Или надо делать Car, Plane и т.п. тоже generic и параметризовать типом двигателя.
Так в этом принципиальная разница, которая не дает текущему решению называться стратегией.
Получает. А не создает сам. В ваших примерах именно самостоятельное создание контекстом (автомобилем, самолетом, рикшей) конкретного двигателя. Следовательно, это не стратегия.
Обычно подмена стратегии в уже созданном экземпляре контекста не нужна, и даже в рамках всего приложения во время его работы может использоваться стратегия только одного типа. Речь не о том. Речь о том, что в рантайме вы можете решить, использовать ли, например, двс или газовый двигатель. Сейчас для этого нужна перекомпиляция.
Как раз наоборот. Например, смотрим пост SergeyT тут: http://sergeyteplyakov.blogspot.ru/2014/02/singleton-pattern.html:
Суть в том, что клиент получает стратегию откуда-то, не зная о конкретном типе. То есть, если к автомобилю или рикше можно прикрутить реактивный двигатель, двс, лошадей, ездовых собак или педали от велосипеда — это стратегия. Но если класс "автомобиль" прибит к двс, а "рикша" — к слуге (как в вашем примере), то это не стратегия, а обычная композиция.
Само по себе вынесение двигателя из транспортного средства — это не стратегия. Не каждое вынесенное куда-то поведение есть стратегия.
Стратегию характеризует возможность замены этого поведения во время работы. Т.е. надо либо просунуть стратегию (двигатель) через конструктор конкретного клиента (машины, самолета), либо запросить его из клиента без указания конкретного типа стратегии.
Здесь этого нет, потому что каждый клиент (машина, рикша, самолет) сам говорит, с каким именно типом двигателя он работает (через Vehicle, через new FooEngine(), через Engine.GetEngine(). По сути это все разные способы создать двигатель определенного типа.
Я бы поспорил насчет стратегии, кстати. Это паттерн подразумевает, что клиенту можно подсунуть любую стратегию. Здесь же клиент создает ее сам (или получает ее откуда-то, но тип заранее известен), и предметная область подразумевает, что к каждому транспорту применим только один тип двигателя (нельзя вкрутить в самолет рикшу, например). Тут нет стратегии, это обычное наследование.
Почему нельзя? Можно. Только не так, как в примере, а, упрощенно, так:
Автомобиль не отвечает за то, чтобы найти и прикрутить себе двигатель, это делается как раз снаружи. Так есть хоть какие-то гарантии, что двигатель не прицепят два автомобиля сразу (это надо как-то проверять).
Почему бы не сделать пул двигателей?
А причем здесь вообще потоки? Здесь должна быть не потокобезопасность, а гарантия, что всегда существует не более одного инстанса любого типа, использующего данный двигатель. А раз речь идет о C#, где объекты чистятся GC, а не руками, то таких гарантий вообще нельзя дать.
И да. У вас в двигателе написано: // burn fuel // spin wheel. Видимо, топливо из общего глобального бензобака, и крутим глобальные колеса, которые прилеплены ко всем автомобилям мира?
Зачем такая сложность? В чем проблема создавать двигатель заново для каждого конкретного автомобиля?