• Занимательный C#. Пять примеров для кофе-брейка
    +1
    Проблема в том, что нет никакого смысла в том, чтобы «взять развернутый код и получить из него IL». Есть IL-код, генерируемый компилятором в котором упаоквки нет, а есть 'constrained' вызов метода Dispose.

    «Развернутый код» же — это лишь пример (псевдокод, по сути) того, что делает компилятор. На самом же деле, поведение несколько иное — более эффективное.

  • Занимательный C#. Пять примеров для кофе-брейка
    +3
    Комментарий Эрика датирован 2011-м, и после этого, скорее всего, спека была изменена, чтобы убрать эту неоднозначность. Я же привел кусок спеки:

    An implementation is permitted to implement a given using-statement differently, e.g. for performance reasons, as long as the behavior is consistent with the above expansion.


    И это черным по английскому говорит о том, что детали реализации могут быть любыми, если это не нарушает логической семантики.

    Так что *сегодня* никакого нарушения спеки нет. Не стоит вводить себя и читателей в заблуждение;)
  • Занимательный C#. Пять примеров для кофе-брейка
    +4
    Вы правы. Действительно, компилятор разворачивает блок using для значимых типов в другую конструкцию, нарушая спецификацию языка C# ради оптимизации


    И снова я с этим не совсем согласен. Эрик, ведь, — жук, да он пишет вот что: «You’d be perfectly justified in thinking that there is a boxing performed in the finally because that’s what the spec says.». Но ведь он пишет лишь то, что «да, в спеке есть пример, который говорит, что using блок выворачивается в каст», но спека не говорит, что каст там должен быть.

    Спека (вот здесь, например) вообще хитро написана, там есть вот что:

    An implementation is permitted to implement a given using-statement differently, e.g. for performance reasons, as long as the behavior is consistent with the above expansion.


    Так что никто никого не нарушает;), не стоит спешить с выводами.
  • Занимательный C#. Пять примеров для кофе-брейка
    +3
    Нет, все сложнее. Полное описание можно найти здесь: The ‘in’-modifier and the readonly structs in C#.

    Если коротко, то in-модификатор очень похож на readonly поля: такие параметры нельзя переприсвоить и для структур компилятор генерирует защитные копии для обращений к свофствам и методам, чтобы обеспечить неизменяемость. А вот эти защитные копии и будут ломать нам ноги, поскольку они не явные.
  • Занимательный C#. Пять примеров для кофе-брейка
    +15
    Да, и хочется добавить.

    Вообще, меня очень смущает первый вопрос. Он показывает, что автор этого вопроса сам не совсем понимает, что происходит у дот-нетика под капотом.

    Упаковка и защитные копии структур — это две очень сильно разные вещи. Упоковки приводят к засорению кучи и влияют на производительность засчет притормаживания сборки мусора (добиться которого в простом Ынтырпрайз приложении, кстати, очень сложно).

    Защитные же копии — это другой зверь. Их и было много, а с появлением 'in' модфикаторов и ref-return-ов стало еще больше. И я бы рассчитывал, что в голове у автора подобных вопросов эти два кейса лежат на разных полках, поскольку они приводят к разным проблемам, по разному проявляются, по разному ищутся и по разному решаются.
  • Занимательный C#. Пять примеров для кофе-брейка
    +27

    Объяснение к первому примеру — неверное. Дело не в том, что в блоке finally происходит упаковка, а в том, что компилятор создает "защитную" копию переменной, поскольку переменные в блоке 'using' очень похожи на readonly-поля (ее нельзя переприсвоить) и компилятор пытается гарантировать "неизменяемость" состояния.


    Посмотрите во что компилятор разворачивает код:


    SDummy sDummy = default(SDummy);
            SDummy sDummy2 = sDummy;
            try
            {
                Console.WriteLine(sDummy.GetDispose());
            }
            finally
            {
                ((IDisposable)sDummy2).Dispose();
            }
            Console.WriteLine(sDummy.GetDispose());

    И упаковки — не просиходит в этом коде вообще! И это очень, повторюсь, очень важно. Если бы она была, то енумераторы в коллекциях не были бы структурами (какой смысл делать их структурами, если блок 'using', который генерирует компилятор для foreach все равно приведет к упаковке?


    Ну и главное, эти примеры нужно всегда давать в контексте: структуры — это оптимизация и к их примненеию нужно подходить соответственно — использовать тогда, когда профилирование показало их необхоидмость.


    Потом нужно объяснять семантическую разницу между классами и структурами и говорить о том, что структуры должны быть неизменяемыми (должны быть readonly struct). Если структура изменяемая, то компилятор генерирует огромную кучу защитных компий, которые совершенно не очевидны никому в этом мире, кроме пары десятков человек. А эти копии могут а) негативно повлиять на производительность (тут идет напоминание, что структуры — это оптимизация) и б) вы можете очень легко отстрелить себе ногу, поскольку изменение состояние произойдет на компии.


    Ну и использовать подобные вещи в качестве загадок — это очень, имхо, плохая практика. Число профиссиональных разработчиков (а не задротов) знают подобные ньюансы единицы. В буквальном смысле.


    Это очень advanced знания, которые нужны либо а) энтузиастам-задротам или б) людям, которые разрабатываеют очень высоконагруженные приложения на .net.

  • To box or not to Box? That is the question
    0
    1. Эта возможность есть и сейчас и для этого IEquatable не нужен.
    2. Потомка не существует. Для этого нужно, чтобы каждый enum был унаследован от System.Enum.

    Это все я к тому, что для того, чтобы избежать виртуального вызова и упаковке совсем не обязательно, чтобы перечисления реализовывали интерфейс. Для этого необходимо и достаточно, чтобы JIT-компилятор знал о них, аналогично тому, как он сейчас уже знает о Enum.HasFlags.
  • To box or not to Box? That is the question
    +1
    Простите, но я не понимаю:).

    1. Наличие IEquatable никак не влияет на то, будет ли вызов Equals/GetHashCode виртуальным или нет.
    2. Вызов метода, определенного в System.ValueType или System.Enum приводит к упаковке:
    int n = 42; var t = n.GetType(); — невиртуальный, но приводит к упаковке.

    Теперь вопрос: как реализация IEquatable поможет избавиться от виртуального вызова и при этом не будет упаковки (котооорая дороже виртуального вызова).
  • To box or not to Box? That is the question
    +3
    Enum — это не просто int. Это кастомный тайп, отнаследованный от System.Enum, который отнаследован от System.ValueType, который содержит примитив внутри. Это может быть int, а может быть short или byte. Именно за счет того, что поля могут быть разного типа mscorlib содержит семейство «сравнителей»: SByteEnumEqualityComparer, и простой EnumEqualityComparer.

    Некоторая работа по оптимизации кишочков идет. Например, новый JIT знает о существовании Enum.HasFlags. Плюс идет работа по девиртуализации вызовов.

    Да, и я не совсем понял, как реализация IEquatable поможет избавиться от виртуального вызова. Можете пояснить этот момент?
  • To box or not to Box? That is the question
    +1
    Я подозреваю, что сложность в предложенном подходе заключается в том, что JIT-у пришлось бы много работать, чтобы это сделать. Например, реализация IList массивом является достаточно нетривиальной штукой.
  • Где лучше всего жить и работать разработчику
    +1
    Собственно, предудущий комментарий NIKOSV полностью соответствует выводам статьи и совпадает с моим текущим пониманием рынка.

    Тут (район Сиэттла) и правда зп сопоставимы с Нью Йорком и долиной, а стоимость жилья — ниже. Да и с комьютом тоже дела несколько лучше. Сейчас здесь все еще можно на программерскую зп купить нормальный дом в пределах 40 минут езды и не переходить на доширак при этом.

    И да, район активно развивается, что сказывается на ценах, как на съем, так и на покупку. Например, вменяемая аренда — ~2K, что, насколько я знаю, соизмеримо со многими районами Калифорнии и Нью Йорка.
  • Изучаем ActionBlock: или короткая история о противном дедлоке
    0
    Ну, можно почитать, что такое ActionBlock и понять, что подобная задача является выражденным случаем датафлоу.

    Я как-то несколько запутался и не понял, что мы обсуждаем? Что было очевидно с самого начала, что тул не подходит? Возможно, но если вы не поленитесь и погуглите на гитхабе или в других общедоступных репах сценарии использования ActionBlock-ов, то найдете, что они используются в большом числе сценариев.

    Или вы хотите сказать, что никогда не выибрали инструмент, который потом оказывался неподходящим? Возможно:), я не такой. И после своей ошибки я решил поделиться своим опытом. Если у вас и с этим какое-то несогласие, то я не совсем понимаю, чего вы хотите добиться своими комментариями;)
  • Изучаем ActionBlock: или короткая история о противном дедлоке
    0
    Нисколько с этим не спорю. Проблема лишь в том, что проблема ActionBlock-а не очевидна.

    Давайте, например, посмотрим, как может приниматься решение об использовании того или иного инструмента длярешения определенной проблемы.

    Дана проблема: проанализировать набор файлов с возможными зависимостями.
    Возможные решения: ActionBlock, ConcurrentCollection + TPL, что-то свое.

    Даже при довольно серьезном анализе, ActionBlock кажется одним из лучших решений, поскольку он контролирует конкурентность, позволяет задавать границу, удобен в использовании, поддерживается одной небольшой компанией и используется сотнями тысяч программистов.

    В результате, он является вполне адекватным выбором и лишь люди, знающие хорошо его внутреннее устройство смогут сказать сразу же, что у него будут проблемы при обработке древовидных структур данных.
  • Изучаем ActionBlock: или короткая история о противном дедлоке
    +1
    Ну, это просто не важно:). Это не настоящий парсинг, а фейковый, любой вариант будет ок;)
  • Изучаем ActionBlock: или короткая история о противном дедлоке
    0
    Кстати, лучший способ — использовать SpinWait.Spin(), он реально будет нагружать процессор, в отличие от Sleep-а, который будет блокировать таску. Но в данном случае блокировка, а не yield управления из таски имеет смысл.
  • Изучаем ActionBlock: или короткая история о противном дедлоке
    0
    Ну, код не совсем продакшн качества, так что в этом случае — это не сильно важно.
  • Изучаем ActionBlock: или короткая история о противном дедлоке
    0
    Я могу описать проблему еще проще: причина дедлока в наличии join-а потоков. Но ведь это малоинформативно и не очень-то дает понять, почему ожидаемый поток все еще заблокирован.

    И нет, тут все сложнее, чем «своя задача блокирует свое исполнение». SendAsync реализован несколько сложнее: на самом деле, это source dataflow block с буфером в один элемент и TaskCompletionSource-ом, который переводит нежележащую таску в завершенное состояние, когда целевой блок (в данном случае — ActionBlock) решается на обработку элемента от него.

    И нет, вычислять рекусривно задачи по месту — это не вариант, поскольку в этом случае Degree of parallelism пойдет лесом. Представьте себе, что зависимости между файлами — это дерево. Так вот, дерево может быть (и, как вы сказали по поводу «плохого») или наверняка будет не сбаллансированным. Это значит, что использование этого подхода приведет к тому, что мы останемся с одной задачей, которая будет долго и упорно парсить огромную ветку дерева. Нехорошо это:)
  • TDD все еще сравнивают с TLD — мнения экспертов
    +1
    А можно я подкину пару ссылок на уже готовые размышления по этой теме: Размышления о TDD, TDD: Test-Driven vs. Type-Driven development, Is TDD Dead, Часть 5.

    А вообще, я очень рекомендую ознакомиться с оригинальным трудом Кента и эпичной баталией под названием Is TDD Dead, чтобы увидеть, насколько автор этой практики является здравомыслящим и прагматичным человеком. Который, кстати, неоднократно писал и говорил о том, что этот подход с короткими итерациями тест-код подходит именно к нему и что у других разработчиком может и должно быть другое мнение по этому поводу.
  • Проверяем Microsoft Code Contracts
    +10
    Ну, как вам сказать:), что можно ожидать от кода, entry point которого называется MikesArchitecture? Комьюнити в последнее время пытается причесать этого монстра, но поддается он с трудом.

    Так что у меня есть фичареквест для любого анализатора: если он встречает метод с именем _Some_Authors_Name_Architecture, то прекращает анализ с сообщением, что он сдается и дальше не готов анализировать код дабы не повредить свою психику:)
  • Как я добавлял поддержку Code Contracts для VS2015
    0
    Понял. Да, вполне возможно, что так и есть.
  • Как я добавлял поддержку Code Contracts для VS2015
    0
    Я не совсем понял мысль. О какой «неподдержке» идет речь? Тул этот асболютно standalone, тут нет разницы между MS и non-MS assemblies.
  • Как я добавлял поддержку Code Contracts для VS2015
    0
    Сейчас это — by design. Ну и с альтернативами ведь довольно плохо все. Ведь основная фишка контрактов (ладно, одна из основных) — это возможность включать/отключать некоторые утверждения по требованию. Вот одна сборка, там нужно все — включая инварианты, а вот эта — тут оставляем только предусловия. А вот здесь мы вообще хотим положить контракты рядом, чтобы они никак не влияли на эффективность нашего кода, но использовались нашими клиантами, если они захотят. В общем, вменяемая альтернатива — это впиливать это на уровне компилятора. И недавно даже proposal появился, хотя ХЗ, что из него выйдет и выйдет ли вообще что-то.
  • Как я добавлял поддержку Code Contracts для VS2015
    0
    Там нужно вначале выяснить, что именно тормозит. Есть все шансы, что проблемы не в самом rewriter-е, а в CCI — туле, который используется для анализа и генерации IL-а. Если это так, то решать глобальную проблему с производительностью нужно будет глобальным образом — переходом на CCI2 (есть и такое) или на Розлиновские потроха для чтения/генерации IL-а. Но такие радикальные шаги будет довольно сложно реализовать.
  • Как я добавлял поддержку Code Contracts для VS2015
    0
    Да, rewriting имеет сложность O(N) от количества зависимостей переписываемой сборки (нужно получить полное транзитивное замыкание всех зависимостей прежде чем можно будет вычислить контракты класса). Это одна из причин существенного замедления ccrewrite при увеличении числа проектов (поскольку обычно при том растет и число зависимостей каждого из них).
  • Как я добавлял поддержку Code Contracts для VS2015
    +2
    Да, исходники открыты с начала года. Но там довольно высокий порог вхождения, ИМХО. В статическом анализаторе вообще без бутылки и PhD не раобраться. В rewriter-е разобраться можно, но тоже довольно трудоемко.

    По теме: контракты вообще по своей природе не очень хорошо дружат с многопоточностью. В Eiffel-е есть SCOOP, но даже там дружба контрактов и многопоточности далека от идеала. Я бы посоветовал вот что (чем пользуюсь активно): постараться избегать мутабельного состояния, переходя к чистым функциям и таскам. В этом случае число инвариантов сведется к минимуму (формально, у неизменяемого объекта они не нужны: предусловий конструктора достаточно), что уменьшит проблемы от использования в многопоточном окружении.

    З.Ы. У rewriter-а есть очень нехорошее поведение. Когда есть анонимный метод, проверяющий постусловие и захватывающий только Contract.Result, то в этом случае ccrewrite генерирует некорректный с точки зрения многопоточности код, протаскивая результат через статическое свойство.
  • Как я добавлял поддержку Code Contracts для VS2015
    +1
    У меня в текущем проекте (200К строк где-то) контракты используются сверх активно. Да, билд тормозится сильно. Но теперь есть планы и возможности по устранению этих проблем.

    Так что я все еще советую;)
  • Захват пакетов в Linux на скорости десятки миллионов пакетов в секунду без использования сторонних библиотек
    +4
    Название статьи показалось до боли знакомым.
    Не вот эта ли статья была источником вдохновения: How to receive a million packets per second?
  • Обзор книги «Паттерны проектирования на платформе .NET»
    0
    Небольшой опыт написания книг показал, что этот процесс достаточно сложен и очень сильно напоминает разработку софта. Нужно найти определенный балланс между тем, что давать, о чем упоминать и на что забить. Молодые авторы (книг, блогов, тренингов) обычно стараются впихнуть максимальный объем материала по принципу — чем больше, тем лучше. Подход хорош для автора, но не всегда подходит читателю, которому теперь придется перескакивать с темы на тему (получаем low cohesion и нарушение SRP). С другой стороны, забить совсем — тоже не хорошо, поскольку связь у двух тем все же присутствует. В результате и появляется большое число перекрестных ссылок:)
  • Никто не умеет обрабатывать ошибки
    +2
    Несколько замечаний:

    1. StackoverflowExcpetion невозможно обработать. Пруф:

    Starting with the .NET Framework version 2.0, a StackOverflowException object cannot be caught by a try-catch block and the corresponding process is terminated by default. Consequently, users are advised to write their code to detect and prevent a stack overflow. For example, if your application depends on recursion, use a counter or a state condition to terminate the recursive loop. Note that an application that hosts the common language runtime (CLR) can specify that the CLR unload the application domain where the stack overflow exception occurs and let the corresponding process continue.


    2. OutOfMemoryException — тоже обработать сложно. Точнее, существует две версии OOM — синхронная, которая бросается при выделении большого куска, который GC выделить не может, и асинхронная — которое может сгенерировать CLR, например, при попытке выделить новый сегмент для GC0.

    3. Метод FullMessage является адским велосипедом, который не понятно зачем нужен. Вы же выводите в builder целиком объект исключения на каждой итерации. A ex.ToString будет выводить каждый раз полное исключение, включая все вложенные. Просто создайте исключение с пятком вложенных и вызовите этот метод.

    4. Существует два стандартных способа фильтрации исключений: по типу в блоке catch, с помощью фильтров исключений (начиная с C# 6.0), зачем делать еще один обобщенный обработчик на основе Func-ов и как он помогает решать проблему обработки исключений, мне не ясно.
  • Анализатор исключений на базе Roslyn-а
    +2
    > Можете прокомментировать этот комментарий на схожу тему? Это второй вопрос. Тема крайне интересная и до конца не понятая, как мне кажется.

    Очень толковый комментарий. Возразить нечего.
    Действительно, проблема с исключениями в том, что одна и та же концепция (исключения) несет разную информацию: это могут быть баги (ArgumentException и наследники, NullReferenceException, InvalidOperationException), критические невостановливаемые системные ошибки (Critical System Faults) (OutOfMemoryException, StackOverflowException, ExecutionEngineExcpetion, ThreadAbortException и, возможно, одно-два других), ошибки окружения (environmental faulures или recoverable system failrues: IOException, DBException etc), и логические ошибки (DatabaseRecordNotFound, EmployeeAlreadyExistsException etc).

    Все эти вещи выражаются в виде исключений, при этом баги не должны отлавливаться, поскольку они требуют изменения кода для их исправления. Критические системыне не должны отлавливаться, поскольку восстановитсья после них невозможно. А два последних вида исключения могут перехватываться. Но и в этом случае перехватывать их нужно на нужно уровне.

    Вот пример. Насколько логично, что контроллер или модель, которые используют DAL будут бросать DbException? не логично, поскольку для пользователя модели или контроллера наличие базы данных вообще спрятано. Это деталь реализации модели или контроллера. Поэтому, когда происходит ошибка в DAL-е, у вызывающего кода есть два варианта: обработать исключение самостоятельно (подавить, вернуть значение по умолнчанию, попробовать трижды и закрыть принудительно приложение), или сообщить вызывающему коду о неудаче, при этом использовать термины, понятные для него (это значит, что нужно бросать PersistentStoreUnavaliable или InfrastructureFailure, завернув во внутрь нового исключения исходное).

    В этом случае, модель или контроллер перекладывает ответственность за восстановление на вызывающий код.

    При этом что делать моделе/контроллеру, если при вызове DAL-а вылетает что-то отличное от DbExcpetion? Можно ли при этом сказать, что PersistentStoreUnavailable? не совсем! Ведь смысл PersistentStoreUnavailable в том, что персистент стор недоступен сейчас, но будет доступен в будущем. Но если мы хватаем System.Exception, то причина может быть не во временной недоступности хранилища, а в баге в реализации слоя доступа к данным, который никогда не будет исправлен. Поэтому оборачивать что-то, что мы не ожидали в PesistentStoreUnavailable опасно, поскольку мы можем сокрыть ошибки!

    С другой стороны, генерируемые исключения редко описываются достаточно четко, чтобы модель или контроллер понимали, какие исключения ожидаемые, а какие нет. Вот именно поэтому мы обычно приходим к стратегии catch all — хватаем все, показываем MessageBox и все. Но последнее замечание не означает, что хватать все стоит. Гораздо разумнее следовать такому подходу: хватаем то, что знаем, оборачиваем в более высокоуровневое исключение или пытаемся восстановиться самостоятельно, а все остальные исключения пробрасываем дальше. Так доходит до самого верха стека вызовов, где мы уже решаем, что делать с неизвестными исключениями — показывать сообщение и закрываться, просить пользователя попробовать позже или что-то еще.

    Поэтому исходный посыл статьи по ссылки верен — никто не умеет нормально обрабатывать ошибки.
  • Анализатор исключений на базе Roslyn-а
    +2
    Продолжение:
    > Иначе однажды исключение пролетит до самого корня и там нам придётся выводить «Спасите-помогите, критическая ошибка», вместо всё того же сообщения «Ошибка открытия файла».

    Для этого команда должна перехватывать понятные ей исключения оборачивать их в более высокоуровневые исключение (throw ФайлоОченьНужноеДляЭтогоПриложенияНеНайденоИлиИспорчено), которое затем будет уже перехватываться на верхнем уронве. Но показывать сообщения об ошибках в команде, если они не являются рутами — не хорошо.
  • Анализатор исключений на базе Roslyn-а
    +1
    > согласны ли вы с тем, что в таких случаях можно поймать все исключения, профильтровать на АДСКИЕ и просто вывести сообщение об ошибке?

    Не совсем:)

    Более подходящим способом обработки исключений будет такой: команда вью-модели ловит только ожидаемые исключения (IOException), а все остальные просто пробрасывает дальше (возможно логгирует). При этом в приложении добавляется глабальный хендлер, который ловит необработанные исключения, показывает глобальное сообщение об ошибке и там уже решает, крашить приложение или продолжать работать.

    Плюс этого подхода в том, что обработка ожидаемых и неожидаемых исключений четко разнесена. В этом случае ваша вью-модель не подавит баги (например, ContractException не должны перехватываться никогда, это баги, аналогично не должны подавляться ArgumentExceptions). При этом решение о поведении при встрече необработанных исключений будет в одном месте (глобальном обработчике, которые есть для Windows Forms и WPF), а не размазаны по всем командам.
  • Анализатор исключений на базе Roslyn-а
    0
    ну так я ж и пишу, что в общем случае на уровне статического анализа эта задача не решается.
  • Анализатор исключений на базе Roslyn-а
    0
    Этот пример относительно простой, поскольку можно посмотреть секцию exceptions документации. Но это не будет работать со сторонним, но не стандартным кодом. Можно попробовать прикрутить анализатор, аналогичный приведенному в предыдущем комментарии по ссылке Вывод NotNull-аннотаций по байткоду Java. Но это будет весьма затратная операция.

    Именно по этой причине, данная проблема на 100% не решаема.
  • Анализатор исключений на базе Roslyn-а
    0
    Да, и мысль дельная. Тот же R# именно этот подход используется для анализа nullable/not-nullable типов.
  • Анализатор исключений на базе Roslyn-а
    +1
    По идее, эти все вещи можно даже на лету выводить. Вот, народ делает такое для вывода not-nullable аннотаций в IntelliJ IDEA — Вывод NotNull-аннотаций по байткоду Java. Теперь дело за малым, скрестить это дело с Розлином и впихнуть в анализатор!:)
  • Анализатор исключений на базе Roslyn-а
    +2
    Дельный совет! Но для реализации этого дела нужно решить более общую проблему: нужно выяснить, какие исключения может генерировать метод. А вот это задача весьма непростая. Точнее в общем случае она вообще не решается, поскольку является разновидностью проблемы под названием Halting Problem.

    Я думаю, как эту штуку побороть для реальных случаев. Если вразумительное решение будет найдено, то тут можно выдавать массу предупреждений. Например, метод потока (или пула потоков) вызывает метод, который может сгенерить исключение (разновидность предложенного ворнинга); или генерация документации для открытых методов, которая добавит потенциальные исключения в секцию и т.п.

    Но, пока что, я не знаю, как подойти к этой задаче в рамках розлина…
  • Анализ одного рефакторинга
    +1
    Никто не говорит, что рефакторинг длинных методов не нужен, но если уж показывать пример, то как минимум нужно сказать о потенциальных негативыных последствиях.

    Ведь никто не отменял правила из Framework Design Guidelines, которые говорят, что все статические методы должны быть потокобезопасными. Причем сделать это довольно просто, достаточно перенести статическое состояние во вновь выделенный объект, а старый метод сделать фасадным методом. Так бы автор показал, что он знает и уважает принципы той платформы, на которой он пишет примеры, да еще бы и один дополнительный паттерн показал бы в придачу.
  • Еще одна книга о паттернах? Дайте две!
    0
    Книга Head First Design Patterns действительно хороша, но это скорее учебник для новичка. Как уже сказали, там примеры с пиццами, которые сложно встретить в реальных приложениях.
  • Еще одна книга о паттернах? Дайте две!
    0
    Я думаю, что сегодня выпускать книгу без электронной версии смысла нет. Я сам когда-то большой любитель бумажных книг, уже давно пересел на электронные версии. Так что в том или ином виде, но электронная версия точно будет.