Yuri Trukhin - Software developement best practices

Preview:

Citation preview

Лучшие практики разработки ПОПринципы гибкого проектирования

Юрий Трухинsenior developer, CNIPGIS LLCMicrosoft Student Partner Guru

Лепшыя практыкі распрацоўкі праграмнага забеспячэння

прынцыпы гнуткага праектаванняЮрий Трухинsenior developer, CNIPGIS LLCMicrosoft Student Partner Guru

беларуская версія

Признаки плохого дизайна ПО

Признаки плохого дизайна ПО

• Жесткость: дизайн трудно поддается изменению• Хрупкость: дизайн легко разрушается• Косность: дизайн трудно использовать повторно• Вязкость: трудно добиться желаемого• Ненужная сложность: избыточное проектирование• Ненужные повторения: чрезмерное использование

копирования и вставки• Непрозрачность: плохо выраженная цель

Часто эти признаки появляются в результате нарушения одного из принципов проектирования.

Пример загнивания программы

public partial class Copier { public static void Copy() { int c; while ((c = Keyboard.Read()) != -1) { Printer.Write(c); } } }

public class CopierWithPerfocards { /// <summary> /// не забудьте сбросить этот флаг /// </summary> private static bool ptFlag = false;

public static void Copy() { int c; while ((c = (ptFlag ? PaperTape.Read() : Keyboard.Read())) != -1) Printer.Write(c); } }

public class CopierWithPerfocardsReadAndWrite { //не забудьте сбросить этот флаг public static bool ptFlag = false; public static bool punchFlag = false; public static void Copy() { int c; while ((c = (ptFlag ? PaperTape.Read() : Keyboard.Read())) != -1) punchFlag ? PaperTape.punchFlag(c) : Printer.Write(c); } }

public interface Reader { int Read(); } public class KeyboardReader : Reader { public int Read() { return Keyboard.Read(); }; }

public partial class Copier { public static Reader reader = new KeyboardReader(); public static void Copy() { var c; while ((c = (reader.Read())) != -1 ) { Printer.Write(c); } } }

Принципы гибкого проектирования

Принципы гибкого проектирования

• Принцип единственной обязанности• Принцип открытости/закрытости• Принцип подстановки Лисков• Принцип инверсии зависимости• Принцип разделения интерфейсов• Принцип эквивалентности повторного

использования и выпуска• Принцип общей закрытости• Принцип совместного повторного использования• Принцип ацикличности зависимостей• Принцип устойчивых зависимостей• Принцип устойчивых абстракций

Принцип единственной обязанности (SRP)

«Никто кроме Будды не должен брать на себя ответственность за сокровенные знания»

Э.Кобхэм Брюэр, 1897

Принцип единственной обязанности (SRP)

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

распределения обязанностей между классами. Если класс берет на себя несколько обязанностей – у него появляется несколько причин для изменения.

• Если класс отвечает за несколько действий, его обязанности оказываются связанными.

• Это является причиной хрупкого дизайна приложений.

Принцип единственной обязанности (SRP)

Приложение «Вычислительная

геометрия»

Rectangle

+draw()+area() : double

Графическое приложение

GUI

Более одной обязанности

Принцип единственной обязанности (SRP)

Проблемы:

Принцип единственной обязанности (SRP)

Проблемы:• В приложение «Вычислительная геометрия» попадает не нужная

логика вместе с классом Rectangle.• Если изменение графического приложения потребует изменить класс

Rectangle, придется заново собирать, тестировать и развертывать приложение «Вычислительная геометрия».

… или приложение «Вычислительная геометрия» неожиданно может перестать работать

Принцип единственной обязанности (SRP)

Приложение «Вычислительная

геометрия»

Rectangle

+draw()

Графическое приложение

GUI

Обязанности разделены

GeometricRectangle

+area() : double

Принцип единственной обязанности (SRP)

Обязанность – причина измененияЕсли вы можете найти несколько причин для изменения – у класса несколько обязанностей.

Принцип единственной обязанности (SRP)

Пример:Public interface Modem{

public void Dial(string pno);public void Hangup();public void Send (char c);public char Recv();

}Сколько обязанностей у интерфейса Modem?

Принцип единственной обязанности (SRP)

Сколько обязанностей?:Public interface Modem{

public void Dial(string pno); //управление соединениемpublic void Hangup(); //управление соединениемpublic void Send (char c); //передача данныхpublic char Recv(); //передача данных

}

Нужно ли разделить интерфейс?

Принцип единственной обязанности (SRP)

Нужно ли разделять интерфейс?:ДА!Если может потребоваться изменение сигнатуры методов управления соединением, класс, вызывающий send и receive придется повторно компилировать и развертывать чаще, чем хотелось бы.

Это приводит к жесткости дизайна. Следует разделить интерфейс на DataChannel и Connection

Принцип единственной обязанности (SRP)

Нужно ли разделять интерфейс?:НЕТ!Если приложение не модифицируют таким образом, что обязанности изменяются порознь, то разделять нет необходимости.

Это приводит к излишней сложности. Изменения интерфейса не требуются.

Принцип единственной обязанности (SRP)

Нужно ли разделять интерфейс?:НЕТ!Если приложение не модифицируют таким образом, что обязанности изменяются порознь, то разделять нет необходимости.

Это приводит к излишней сложности. Изменения интерфейса не требуются.

Вывод: ось изменения становится таковой, если изменение имеет место. Если на то нет причин, неразумно применять ЛЮБОЙ принцип проектирования.

Принцип единственной обязанности (SRP)

Разделение связанных обязанностей. Обеспечение сохранности.

Employee

+CalculatePay+StorePersistense System

Связь бизнес-правил и подсистемы сохраниния -> неприятности!Обычно тесты заставляют их разделять, если нет – в случае если появляется жесткость и хрупкость – нужен рефакторинг!(Для данного случая применимы паттерны Фасад, Объект доступа к данным, Прокси для разделения обязанностей).

Принцип открытости-закрытости

«Голланская дверь: существительное. Дверь, разделенная на две части по горизонтали так, что каждая створка может открываться и закрываться независимо»

The American Heritage Dictionary of English language, 2000

Принцип открытости-закрытости

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

Принцип открытости-закрытости

Основные храктеристики модулей, разработанных в соответствии с принципом открытости-закрытости:• Они открыты для расширения (поведение модуля можно расширить,

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

сопряжено с изменением в исходном или двоичном коде модуля).

Как это сделать?

Принцип открытости-закрытости

Как это сделать?С ПОМОЩЬЮ АБСТРАКЦИИ!

Абстракция – абстрактный базовый класс, поведение – производный класс. Модуль, манипулирующий абстракцией можно сделать закрытым для модификации – он зависит от ФИКСИРОВАННОЙ абстракции.Модуль при этом остается расширяемым.

Принцип открытости-закрытости

Пример:

Client Server

Класс Client не является открытым и закрытым.

Принцип открытости-закрытости

Применив паттерн «Стратегия» получим:

Client“interface”

Client Interface

Класс Client одновременно является открытым и закрытым.

Server

Принцип открытости-закрытости

Альтернатива: Паттерн «Шаблонный метод»

Открытые методы в policy реализуют некую политику, они аналогичны методам класса Client (описывают определенные функции в терминах абстрактных интерфейсов, часть класса policy, в C# это абстрактные методы, реализуются в подтипах policy). Поведения, описанные внутри policy, можно расширять и модифицировать.

Policy

+PolicyFunction()#ServiceFunction()

Implementation

#ServiceFunctiom

Принцип открытости-закрытости

Пример принципа OCP

--shape.h--------------------------------------------------------------------enum ShareType (circle, square);struct Shape{ ShapeType itsType;}--circle.h-------------------------------------------------------------------struct Circle{ ShapeType itsType; double itsRadius; Point itsCenter;};void DrawCircle(struct Circle*);

--square.h-------------------------------------------------------------------struct Square{ ShapeType itsType; double itsSide; Point itsTopLeft;};

void DrawSquare(struct Square*);

Принцип открытости-закрытости

--drawAllShapes.cc-----------------------------------------------------------typedef struct Shape *ShapePointer;

//функция не может быть закрытой для добавления других фигур, //не удовлетворяет принципам OCPvoid DrawAllShapes(ShapePointer list[], int n){ int i; for (i=0; i<n; i++) { struct Shape* s = list[i]; switch (s->itsType) { case square: DrawSquare((struct Square*)s); break; case circle: DrawCircle((struct Circle*)s); break; } }}

Принцип открытости-закрытости

///Резюме:///это решение жесткое,///тк после добавление фигуры Triange(треугольник)///необходимо заново откомпилировать и развернуть файлы , содержащие опредлеление///Shape, Square,Circle и DrawAllShapes.//////это решение хрупкое,///т.к. есть много других switch/case и if/else, которые сложно отыскать и понять.//////это решение костное, потому что всякий, кто попытается использовать ///функцию DrawAllShapes в другой программе, вынужден будет тащить за собой ///определения Square и Circle, даже если в этой программе они не используются.

Принцип открытости-закрытостиpublic interface Shape { void Draw(); }

public class Square : Shape { public void Draw() { //нарисовать квадрат } }public class Circle : Shape { public void Draw() { //нарисовать круг } }

public void DrawAllShape(IList shapes) { foreach (Shape shape in shapes) shape.Draw(); }

Принцип открытости-закрытости

Внимание, изменились требования!

Принцип открытости-закрытости

Изменившиеся требования

Необходимо, чтобы все круги рисовались раньше всех квадратов.

Принцип открытости-закрытости

Вывод 1

Модель Shape не является естественной в системе, где упорядоченность связана с типом фигуры.

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

Принцип открытости-закрытости

Расстановка «точек подключения»

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

«Стимулирование изменений» • Сначала пишем тесты.• Разработку ведем очень короткими циклами, измеряемыми в днях,

а не в неделях• Разрабатываем содержательные функции до инфраструктуры и

часто демонстрируем их заинтересованным сторонам• Первую версию ПО выпускаем быстро, а последующие часто.

Принцип открытости-закрытости

Заключение

Следование этому принципу позволит получить от ООП максимум обещанного: гибкость, возможность повторного использования и удобство сопровождения.Отказ от преждевременного абстрагирования столь же важен, как и само абстрагирование.

Принцип подстановки лисков

«Должна быть возможность вместо базового типа подставить любой его подтип»

Принцип инверсии зависимостей

1. Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций.

2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Принцип разделения интерфейсов

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

Принцип эквивалентности повторного использования и выпуска

Единица повторного использования равна единице выпуска.(либо все классы, включенные в компонент можно повторно использовать, либо ни один!)

Принцип общей закрытости

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

Принцип совместного повторного использования

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

Принцип ацикличности зависимостей

В графе зависимостей между компонентами не должно быть циклов

Принцип устойчивых зависимостей

Зависимости должны быть направлены в сторону устойчивости (изменяемые компоненты должны быть сверху графа, и из них собирается неизменяемый)

Принцип устойчивых абстракций

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

Что дальше?

• DI/IOC• Prism• Вред реинжиниринга• Паттерны• Постоянное развитие• Смотрите исходный код (codeplex и др)• Помогайте другим (форумы msdn и др)

Спасибо за внимание!

Юрий ТрухинSenior developer (CNIPGIS LLC)Microsoft Student Partner GURU

trukhin.yuri@hotmail.comtrukhinyuri.blogspot.com

twitter.com/trukhinyuri

Удачных проектов!

© 2008 Microsoft Corporation. All rights reserved. Microsoft, Windows, Windows Vista and other product names are or may be registered trademarks and/or trademarks in the U.S. and/or other countries.

The information herein is for informational purposes only and represents the current view of Microsoft Corporation as of the date of this presentation. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information provided after

the date of this presentation. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.