Thursday, July 29, 2010

Don’t talk to the strangers

Читая про принципы проектирования модулей, я наткнулся на еще один интересный принцип «Don’t talk to the strangers» (или “Only talk to your friends ” или «Law of Demeter», LOD).

Каждый юнит должен как можно меньше знать о других юнитах: только о непосредственно с ним связанных. Или: Юнит может общаться только со своими непосредственными друзьями и не может общаться с незнакомцами.

Основная цель этого принципа – минимизировать связность между юнитами. Применительно к ООП под юнитом понимают метод класса.

Следуя этому принципу, метод может вызывать:

  • другие методы своего класса
  • методы своих полей
  • методы своих параметров
  • методы создаваемых им локальных объектов

Распространенным примером нарушения этого принципа является следующий код:

alpha = beta.GetAlpha()
gamma = alpha.GetGamma()

Если beta – параметр метода, то вызов его метода является допустимым. Но alpha – уже не параметр, и значит, используя его в нашем методе, мы добавляем себе лишнюю зависимость. Вариантом решения может быть добавление к типу beta метода GetGamma(). Это позволит убрать лишнюю зависимость.

gamma = beta.GetGamma()

Еще один классический пример использования LOD можно посмотреть здесь.

Применяя LOD, мы в итоге получим менее связанные классы. И это хорошо т.к. изменения в одних классах будут меньше или вообще не будут затрагивать другие классы. Это согласуется с принципом OCP. Однако во всем надо знать меру. Если для того чтобы не нарушать LOD вы начинаете раздувать интерфейсы своих классов обертками типа GetGamma() из примера, то тем самым вы наверняка нарушаете ISP. Задумайтесь насколько хорош ваш дизайн, если вам понадобилась куча оберток.

Ссылки:

Wednesday, July 28, 2010

Гранулярность. Принципы проектирования модулей

В предыдущем посте я рассмотрел пять основных принципов проектирования классов, сформулированных Робертом Мартином.

Сейчас я хочу подробно разобрать следующие три принципа из списка Мартина, касающиеся проектирования модулей. Эти принципы позволяют правильно разделять функциональность между модулями, управляя тем самым сфокусированностью (cohesion) функционала в каждом модуле.

REP. Reuse / Release equivalence principle

Гранулярность повторного использования эквивалентна гранулярности пакетирования. Пакет должен пройти полный цикл разработки (от проектирования до тестирования).

Повторное использование кода – одна из основных целей ООП. Давайте определимся, что означает «повторное использование». Тут все достаточно просто. Если вы добавляете себе в проект исходный код, написанный кем-то другим, то это не есть «повторное использование». Неважно как вы добавите себе чужие исходники:

  • просто скопируете интересующий вас метод или класс
  • скачаете из интернета и добавите себе в солюшн отдельным проектом
  • стянете из системы контроля версий и опять же добавите себе отдельным проектом

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

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

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

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

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

Итак, давайте вернемся к нашему первоначальному вопросу. Что же означает «повторное использование»? Повторное использование по определению Мартина – это когда у себя в проекте вы используете функционал, написанный кем-то другим и при этом у вас нет необходимости видеть чужие исходники. Все что вам нужно, это подключить себе скомпилированную сборку (пакет) и знать ее публичный API.

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

Итак, согласно REP, единицей повторно используемого функционала следует считать только бинарную сборку (пакет), которая прошла все стадии разработки от проектирования до тестирования, имеет определенную версию и самое главное гарантирует вам изолированность от ее исходников.

CRP: Common Reuse Principle

Классы в пакете используются совместно. Даже если вы используете всего один класс из пакета, вы используете все его классы.

Этот принцип помогает определить какие классы следует поместить в пакет. Ссылаясь на класс, мы неявно ссылаемся на весь пакет, содержащий этот класс, имеем доступ ко всем классам в пакете и можем неявно от них зависеть. Поэтому классы, которые не используются вместе не должны попадать в один пакет. Обратное не верно - классы, которые вместе используются отнюдь необязательно должны располагаться в одном пакете.

Этот принцип, как ISP для классов, требует включать в модули только минимально необходимый объем функционала, избавляясь от классов с которыми нет явных связей.

CCP: Common Closure Principle

Классы, которые изменяются вместе, должны располагаться вместе, то есть в одном пакете.

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

Ссылки:

Thursday, July 22, 2010

SOLID. Принципы проектирования классов

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

SOLID – это акроним названий пяти основных принципов проектирования классов сформулированных Робертом Мартином: Single Responsibility, Open Closed, Liskov Substitution, Interface Segregation и Dependency Inversion.


SRP: Single Responsibility Principle (принцип единственной ответственности).

Класс должен иметь одну и только одну причину для модификации.

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

Этот класс нарушает принцип единственной ответственности, т.к. он решает сразу три задачи:

  1. управляет элементами заказа
  2. умеет работать с базой для сохранения и загрузки заказа
  3. умеет распечатывать заказ на принтере

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

Это очень простой пример и тут достаточно очевидно, сколько и каких именно обязанностей было у исходного класса Order. В реальной ситуации выделение у класса нескольких ответственностей может быть гораздо менее очевидным и однозначным. Для таких случаев существует правило: Старайтесь выделять ответственности, которые изменяются независимо друг от друга. Вот как в нашем примере. Если вдруг изменится вывод заказа на печать, то достаточно будет поправить только PrintManager, а два других класса останутся без изменений.


OCP: Open/Closed Principle (принцип открытия/закрытия)


Объекты проектирования (классы, функции, модули и т.д.) должны быть открыты для расширения, но закрыты для модификации.

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

У него есть всего один метод Print, который в каком-то виде печатает заказ. Допустим, в один прекрасный день нам понадобится печатать заказ в трех разных формах. Самый простой вариант модифицировать PrintManager следующим образом.

Для расширения поведения класса PrintManager мы полезли изменять его код. И каждый раз при появлении новой формы для печати мы опять будем изменять PrintManager. Это явное нарушение принципа OCP.

Решить проблему добавления форм для печати при этом, не нарушая OCP можно, например вот так. Определим интерфейс IOrderFormatter. Классы реализующие этот интерфес будут отвечать за форматирование отчета в определенный вид.

Теперь PrintManager нужно изменить следующим образом:

После этого мы сможем добавлять новые формы для отчета, не изменяя PrintManager, а просто добавляя в программу новые имплементации интерфейса IOrderFormatter. Т.е. мы сможем расширять функционал класса PrintManager не изменяя его код.


LSP: Liskov Substitution Principle (принцип замещения Лисков)


Функции, которые используют ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом.

Давайте посмотрим на метод Print класса PrintManager, который мы создали в предыдущем разделе. Предполагается, что он использует свой второй параметр через ссылку на интерфейс, не зная ничего о его конкретном типе.

Как только в методе Print появится код для приведения параметра formatter к какому-то конкретному типу, это будет нарушением принципа LSP.

Еще одним видом нарушения принципа LSP является ошибочное наследование. Подробные примеры можно посмотреть у Роберта Мартина здесь и у Александра Бындю здесь.


ISP: Interface Segregation Principle (принцип изоляции интерфейса)


Клиент не должен вынужденно зависеть от элементов интерфейса, которые он не использует.

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

Когда мы реализуем этот интерфейс в классе Воробей, то все никаких проблем при этом не возникнет. Дальше мы реализуем интерфейс для Утки. И столкнемся с тем, что с чириканьем у нее как-то не сложилось. Для Пингвина все еще печальнее. Он не умеет ни чирикать, ни летать. Получается что, только потому, что Пингвин реализует IBird, ему надо реализовать у себя два метода, которые ему на самом деле абсолютно не нужны. Зато он умеет плавать, но наш интерфейс такого метода не содержит.

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

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

На практике проблема неадекватно широких интерфейсов может быть еще хуже чем в примере с птицами. Одним из самых ярких примеров является MembershipProvider, который содержит почти три десятка абстрактных методов, а в большинстве ситуаций нужно реализовать от силы парочку.


DIP: Dependency Inversion Principle (принцип обращения зависимости)

  1. Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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

DIP1

Manager содержит жесткую ссылку на Worker, что нарушает принципы DIP и OCP. Например, если у конструктора Worker изменится список параметров, то придется вносить соответствующие изменения в Manager, хотя изменения в Worker его не должны касаться. Кроме того, если вдруг для выполнения работы нам потребуется использовать какою-то иную имплементацию Worker’a, например, SeasonedWorker, то опять придется изменять класс Manager.

Для того чтобы отвязать класс Manager от конкретной реализации Worker’a давайте создадим интерфейс IWorker и изменим классы Worker и SeasonedWorker так чтобы они реализовывали IWorker.

Teперь давайте модифицируем Manager, так чтобы он хранил ссылку на IWorker и получал ее через конструктор.

Теперь зависимости между классами выглядят так:

DIP2

В классе Manager больше нет жесткой ссылки на конкретный класс Worker, вместо этого он содержит ссылку на абстракцию IWorker. Теперь Manager соответствует требованиям и DIP и OCP.

 

Ссылки:

Sunday, July 18, 2010

Windows Phone 7

Вчера на встрече киевской .Net User Group новый DPE Lead Microsoft Ukraine Андрей Терехов рассказал много интересного про Windows Phone 7. И не только рассказал, но и показал единственный в Украине, прототип телефона, привезенный им на несколько дней, специально для презентаций партнерам и разработчикам.

DSCN0039

Все присутствовавшие смогли в перерыве поиграться с телефоном. Я тоже не упустил такой возможности и остался очень доволен. На практике интерфейс Metro оказался таким же классным, как и на тех презентациях, что я видел ранее. С большим мультитач экраном, удобным оригинальным UI, кучей разных сенсоров и функций – я думаю, эти телефоны понравятся потребителям. Более детально обзор того телефона, что попал в Киев можно почитать здесь.

Однако лично меня платформа Windows Phone 7 больше интересует не как пользователя, а как разработчика. Мне очень нравится то, что программы для нее можно писать в привычной мне Visual Studio, используя Silverlight + C#. При этом все инструменты разработки абсолютно бесплатны. Уже доступны для загрузки Visual Studio 2010 Express for Windows Phone Beta и Expression Blend® 4 for Windows Phone Beta. Эмулятор, встроенный в Visual Studio позволяет уже сейчас тестировать и отлаживать приложения.

На данный момент единственным источником приложений для телефонов Windows Phone 7 является Marketplace. Т.е. официально не поддерживается установка программ по USB или Wi-Fi c локального компьютера или других источников. Для того чтобы размещать свои приложения в Marketplace, нужно зарегистрироваться. Регистрация стоит $99 в год. После регистрации вы получите возможность размещать там до пяти бесплатных приложений и неограниченное количество платных. Доходы от продажи программы распределяются так: 70% вам, 30% Майкрософту.

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

На данный момент с доставкой приложений на телефоны есть еще много нерешенных вопросов. Например, если я пишу какое-то бизнес-приложение для заказчика и не хочу светить его в публичном Marketplace. Где размещать установочный пакет так чтобы только сотрудники моего заказчика имели к нему доступ?

Еще одна странность: новые телефоны будут официально поставляться в Украину, однако Marketplace почему-то доступен не будет. Т.е. если вы разработчик и у вас в профиле Live ID указана Украина, то извините, вы не зарегистрируетесь в Marketplace. А вот для России почему-то все наоборот. Телефоны официально поставляться не будут, зато будет доступен Marketplace.

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

 

Полезные ссылки:

  1. Windows Phone Developer Portal
  2. Visual Studio 2010 Express for Windows Phone Beta
  3. Expression Blend® 4 for Windows Phone Beta
  4. Windows Phone 7 Developer Training Kit
  5. Windows Phone Developer Tools Team Blog
  6. Getting Started Guide for Developing for Windows Phone
  7. Code Samples for Windows Phone

Monday, July 12, 2010

HTTP REST Overview

Ранее я упоминал, что доступ к Windows Azure Storage осуществляется через HTTP REST интерфейс. Давайте разберемся что это означает.

REST (Representational State Transfer) – это архитектурный стиль организации доступа к данным. В его основе лежат три понятия: ресурсы, их идентификаторы, и набор операций для работы с ресурсами.

REST

Сам по себе REST не привязан ни к какой конкретной платформе, технологии или протоколу передачи данных. Однако на сегодняшний день наиболее распространена реализация REST поверх HTTP для доступа к данным через Web.

Вообще говоря, REST очень сильно напоминает организацию доступа к данным в реляционных БД. Там, зная сервер, базу, таблицу и идентификатор интересующего вас объекта, вы легко сможете манипулировать любыми данными используя универсальные операции insert, select, update и delete.

В REST аналогом записи в таблице БД выступает ресурс. Ресурсом может быть что угодно. Например, если у вас есть база данных с информацией о работе магазина, то каждый поставщик, каждый товар, каждый заказ – все это ресурсы.

К любому ресурсу можно получить доступ, зная его уникальный идентификатор. В REST в роли идентификаторов выступают URL. Вот, например, URL, которые однозначно идентифицируют заказчика с ID=1701 и продукт с ID=11921.

http://example.com/customers/1701
http://example.com/items/11921

С помощью URL можно адресовать не только одиночные ресурсы, но и коллекции ресурсов. Вот, например, URL, которые адресуют все заказы, сделанные в мае 2010 года, а также все продукты зеленого цвета.

http://example.com/orders/2010/05
http://example.com/items?color=green

И, наконец, операции. В HTTP REST для доступа к данным используются стандартные методы протокола HTTP:

GET Запрашивает преставление ресурса с сервера (select).
POST Добавляет новый ресурс (insert).
PUT Добавляет новый ресурс либо обновляет уже существующий (insert / update).
DELETE Удаляет ресурс (delete).

Обратите внимание, что также как и методы для доступа к данным в БД, операции REST могут работать с абсолютно любыми ресурсами. Например, для организации доступа к заказам и продуктам вам не нужно реализовывать в API два отдельных метода GetOrder и GetItem. Доступ к обоим типам ресурсов можно будет получить с помощью метода HTTP GET.

GET http://example.com/customers/1701 
GET http://example.com/items/11921

Это, на мой взгляд, ключевая идея REST: вместо построения широкого API, содержащего методы для манипуляции каждым типом ресурсов вашего приложения, вы определяете уникальные URL для адресации всех ваших ресурсов и затем работаете с ресурсами любого типа используя небольшой набор стандартных методов.

Ссылки:

  1. How I Explained REST to My Wife
  2. A Brief Introduction to REST
  3. REST Web Services
  4. Roots of the REST/SOAP Debate

IL Merge

ILMerge – это утилита от Microsoft Research, которая умеет объединять несколько .Net сборок в одну. Зачем это надо? Например, для того чтобы нельзя было вывести из строя приложение удалив одну из его библиотек. Или для того чтобы упростить жизнь пользователю, предоставив ему всего один исполняемый файл вместо папки с кучей библиотек.

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

imageimage Давайте посмотрим как с помощью ILMerge можно свернуть эти три сборки в одну. Вообще ILMerge – это утилита командной строки. Поэтому достаточно вызвать ее с нужными параметрами и дело будет сделано. Однако лично мне такой вариант использования не нравится. Я предпочитаю написать custom action для MSBuild и встроить его в процесс сборки моего приложения.

Для того чтобы использовать API ILMerge в своем приложении достаточно добавить в нем ссылку на ILMerge.exe.

image

Я не буду вдаваться в детали создания custom action для MSBuild, а приведу только код, необходимый для объединения сборок:

После объединения я получил один исполняемый файл, который включает всю информацию из все трех исходных сборок.

image image

Как видите все достаточно просто. Дополнительную информацию можно найти в .doc файле поставляемом вместе с ILMerge.

Ссылки:

  1. Download ILMerge
  2. ILMerge-GUI at Codeplex
  3. How to merge your referenced assemblies into the output assembly for improved usability
  4. Leveraging ILMerge to simplify deployment and your users experience