.Net teza ru

103
Universitatea Tehnică a Moldovei REFACTORING ŞI PERFORMANŢA APLICATIILOR ÎN BAZA PLATFORMEI .NET COMPACT FRAMEWORK Masterand: Pavel Chiper Conducător: conf. univ., dr. Victor Ababii

Transcript of .Net teza ru

Page 1: .Net teza ru

Universitatea Tehnică a Moldovei

REFACTORING ŞI PERFORMANŢA

APLICATIILOR ÎN BAZA

PLATFORMEI .NET COMPACT

FRAMEWORK

Masterand:

Pavel Chiper

Conducător:

conf. univ., dr. Victor Ababii

Page 2: .Net teza ru

Chişinău – 2010

2

Page 3: .Net teza ru

Ministerul Educaţiei al Republicii Moldova

Universitatea Tehnică a Moldovei

Facultatea Calculatoare, Informatică şi Microelectronică

Catedra Calculatoare

Admis la susţinere

Şef de catedră: conf. univ., dr. Emilian Guţuleac

_______________________________

„__”_____________ 201_

REFACTORING ŞI PERFORMANŢA APLICATIILOR ÎN BAZA

PLATFORMEI .NET COMPACT FRAMEWORK

Teză de master în

___________________________________________

(programul de masterat )

Masterand:_______________________( P. Chiper )

Conducător:_______________________( V. Ababii )

Consultant:_______________________(__________)

Recenzent:_______________________(__________)

Chişinău – 2010

3

Page 4: .Net teza ru

ОГЛАВЛЕНИЕ

Список аббревиатур......................................................................................................................5

Список рисунков и таблиц............................................................................................................6

Аннотация......................................................................................................................................7

Rezumatul..................................................................................................................................7

Abstract......................................................................................................................................8

Аннотация.................................................................................................................................9

Список ключевых слов................................................................................................................10

Введение.......................................................................................................................................11

1 Анализ работ в области рефакторинга и оптимизации производительности мобильных

приложений..................................................................................................................................13

1.1 Оптимизация производительности..............................................................................13

1.2 Рефакторинг...................................................................................................................18

2 Особенности рефакторинга и оптимизации производительности на платформе .Net

Compact Framework.....................................................................................................................23

2.1 Рефакторинг как средство улучшения кода................................................................23

2.1.1 Когда следует производить рефакторинг...............................................................25

2.1.2 Признаки дурного кода............................................................................................27

2.1.3 Примеры рефакторингов..........................................................................................29

2.2 Производительность приложений на платформе .NET Compact Framework..........33

2.2.1 Понятие достаточной производительности...........................................................33

2.2.2 Влияние рефакторинга на производительность....................................................35

2.2.3 Рекомендации по оптимизации производительности...........................................38

3 Результаты исследования.......................................................................................................41

3.1 Описание приложения и поставленные цели.............................................................41

3.2 Описание инструментов разработки и оценки производительности.......................43

3.3 Рефакторинг на примере реального кода....................................................................46

4

Page 5: .Net teza ru

3.3.1 Разделение на слои...................................................................................................46

3.3.2 Кеширование сервисов и форм...............................................................................51

3.3.3 Оптимизация использования типов данных..........................................................54

3.3.4 Повышение отзывчивости приложения за счет многопоточности......................56

3.4 Сравнение характеристик приложения до и после рефакторинга............................59

3.4.1 Анализ показаний встроенных счетчиков производительности..........................59

3.4.2 Анализ результатов тестирования скорости загрузки форм................................62

Заключение...................................................................................................................................66

Список литературы......................................................................................................................68

Declaraţia de onestitate..................................................................................................................70

Приложение 1...............................................................................................................................71

Текст программы «Mobile Agent Speed Test».....................................................................71

Приложение 2...............................................................................................................................75

Код программы, результаты тестирования – CD-ROM.....................................................75

5

Page 6: .Net teza ru

СПИСОК АББРЕВИАТУР

БД – База Данных

ООП – Объектно-Ориентированное Программирование

API – Application Programming Interface

CD-ROM – Compact Disk Read-Only Memory

CE – Compact Edition

CF – Compact Framework

CLR – Common Language Runtime

COM – Component Object Model

CRUD – Create Read Update Delete (базовые функции хранилища данных)

DAO – Data Access Object

DLL – Dynamic-Link Library

GC – Garbage Collector

JIT – Just-In-Time Compiler

LINQ – Language Integrated Query

MDE – Model-Driven Engineering

SQL – Structured Query Language

XML – eXtensible Markup Language

XP – eXtreme Programming

6

Page 7: .Net teza ru

СПИСОК РИСУНКОВ И ТАБЛИЦ

Рисунок 3.1. Главное окно программы "Мобильный агент"..............................................41

Рисунок 3.2. Форма заказа.....................................................................................................42

Рисунок 3.3. Окно программы Remote performance monitor...............................................44

Рисунок 3.4. Окно программы CLR Profiler.........................................................................45

Рисунок 3.5. Окно программы Mobile Agent Speed Test.....................................................46

Рисунок 3.6. Упрощенная диаграмма классов до проекта до изменений.........................47

Рисунок 3.7. Упрощенная диаграмма классов после изменений.......................................50

Рисунок 3.8. Сравнение скорости загрузки. 1 цикл, без сохранения.................................62

Рисунок 3.9.Сравнение скорости загрузки. 1 цикл, с сохранением...................................63

Рисунок 3.10. Сравнение скорости загрузки. 10 циклов, без сохранения.........................63

Рисунок 3.11. Сравнение скорости загрузки. 10 циклов, с сохранением..........................64

Рисунок 3.12. Сравнение скорости загрузки. 100 циклов, без сохранения.......................64

Рисунок 3.13. Сравнение скорости загрузки. 100 циклов, с сохранением........................65

Таблица 2.1. Сравнение времени выполнения вызовов......................................................38

Таблица 3.1. Сводная таблица счетчиков производительности.........................................59

7

Page 8: .Net teza ru

АННОТАЦИЯ

REZUMATUL

Performanţa softului este una din cerinţele de bază în dezvoltarea aplicaţiilor mobile.

Necătînd la faptul creşterii puterii hardware, dispozitive mobile va rămâne mereu în urma

calculatoarelor desktop. Resurse limitate necesită abilităţi speciale pentru dezvoltator.

Productivitatea - o caracteristică pe care trebuie să fie furnizate de programator atunci când

planificarea proiectului, şi niciodată trecute cu vederea. În condiţia cerinţelor ce se schimbă rapid,

sau dezvoltat tehnici de programare “eXtreme Programing” pentru a omite cheltuieli suplimentare

legate de planificare. Reversul medaliei de accelerare a procesului de dezvoltare este lipsa de

planificare care întotdeauna afectează performanţa unui produs software. Un instrument pentru a

remedia această situaţie, precum şi întreţinerea periodică a calităţii de cod este refactoring.

Refactorizarea - metodologia şi procesul de a introduce modificări în cod fără a schimba

comportamentul extern.

Refactorizarea în ultimii 10-15 ani, a devenit răspîndită pe scară largă în diverse domenii de

dezvoltare a softului, şi continuă să fie obiectul unor cercetări. Dorinţa de a lega diverse cunoştinţe

despre optimizarea aplicaţiilor mobile, precum şi cercetarea în domeniul efectului refactoring

asupra performanţei sunt punctual de pornire al acestei teze. Scopul cercetării este de a obţine

rezultate practice obţinute prin teste de performanţă, în aplicarea unor recomandări teoretice şi

schimbări în codul de lucru folosind refactoring.

În această lucrare, vom analiza lucrările existente în domeniul refactoring-ului, şi dezvoltarea

softului pentru dispozitive mobile pe platforma NET Compact Framework, Se oferă baza teoretică a

refactoring-ului, se enumără recomandările pentru îmbunătăţirea performanţelor aplicaţiilor pe

platforma NET Compact Framework. Toate cunoştinţele teoretice în practică aplicate pentru a

îmbunătăţi aplicaţia comercială. Rezultatele obţinute confirmă eficienţa refactoring ca un mijloc de

modificare a codul programului, fără a schimba comportamentul, şi arată relevanţa acestor

recomandări.

8

Page 9: .Net teza ru

ABSTRACT

The issue of performance is one of the most actual in developing mobile applications. Despite

the hardware capacity growth mobile devices will always be behind the desktop PCs. Limited

number of resources requires special skills from developer. Performance is a characteristic on which

the whole project should be planned and never should be omitted. In a rapidly changing software

requirements there are developed extreme programming methodologies that guarantee that on

planning won't be spent extra resources. On the other hand speeding-up the development means the

lack of planning, which is always affects the implementation process. To fix such a situation and to

maintain the code quality exists a refactoring technique. Refactoring is a methodology and process

of the code modification without changing behavior.

Refactoring in the last 10-15 years was widely distributed in various areas of software

development, and still is the subject of investigations. The will to unite scattered knowledge about

the optimization of mobile applications, and investigation of the effect of refactoring on the

performance motivated me in writing of this thesis. The aim of research is to get practical

performance testing results using the theoretical recommendations and the working code

modifications using refactoring.

In this thesis there are analyzed existing projects in refactoring and software development for

mobile devices on the platform. NET Compact Framework presented the theoretical base of

refactoring and recommendations for improving the performance of applications on the

platform .NET Compact Framework. All the theoretical knowledge applied in practice was made to

improve the commercial applications. The obtained results confirm the effectiveness of refactoring

as a tool of modifying the code without changing the behavior, and show the actuality of these

recommendations.

9

Page 10: .Net teza ru

АННОТАЦИЯ

Вопрос производительности является одним из наиболее актуальных в разработке

мобильных приложений. Несмотря на рост аппаратных мощностей, мобильные устройства

всегда будут отставать от настольных компьютеров. Ограниченность ресурсов требует от

разработчика особых навыков. Производительность – характеристика, которая должна

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

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

методики экстремального программирования, гарантирующие, что на планирование не

будут затрачены лишние ресурсы. Обратной стороной ускорения процесса разработки

является недостаток планирования, который всегда сказывается на производительности

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

регулярного поддержания качества программного кода является рефакторинг. Рефакторинг –

методология и процесс внесения модификаций в программный код без изменения внешнего

поведения.

Рефакторинг за последние 10-15 лет получил широкое распространение в самых разных

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

исследований. Желание связать разрозненные знания об оптимизации мобильных

приложений, а также исследовать влияние рефакторинга на производительность побудило к

написанию данной работы. Целью исследований является получение практических

результатов тестирования производительности при применении теоретических

рекомендаций и внесении изменений в работающий код при помощи рефакторинга.

В данной работе проводится анализ имеющихся работ в области рефакторинга и

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

Compact Framework, приводится теоретическая база рефакторинга, перечисляются

рекомендации по повышению производительности приложений на платформе .NET Compact

Framework. Все теоретические знания на практике применяются к улучшению

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

рефакторинга как средства модификации программного кода без изменения поведения, а

также показывают актуальность приведенных рекомендаций.

10

Page 11: .Net teza ru

СПИСОК КЛЮЧЕВЫХ СЛОВ

Refactoring, performanţa, aplicaţiile mobile, .Net Compact Framework, calitatea softului

Refactoring, performance, mobile applications, .Net Compact Framework, software quality

Рефакторинг, производительность, мобильные приложения, .Net Compact Framework,

качество программного обеспечения

11

Page 12: .Net teza ru

ВВЕДЕНИЕ

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

содействия прогрессу создаются новые методологии, разрабатываются новые языки

программирования, совершенствуются средства разработки и тестирования программ. И все

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

взятого программиста и обеспечения конкурентоспособности предприятия-разработчика.

Классическим подходом к разработке является модель водопада, согласно которой

процесс разбивается на несколько последовательно сменяющих друг друга этапов:

определение требований, проектирование, конструирование, интеграция, верификация,

инсталляция, поддержка [3]. При этом наибольшее внимание уделяется этапам сборки

требований и проектированию. Этап планирования является ключевым и именно с ним

связаны главные риски проекта. С одной стороны, плохо спроектированная система может

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

функционала приводит к неоправданным затратам времени и денег. Именно поэтому в

последнее время приобрели популярность циклические модели разработки, согласно

которым в пределах одного цикла планируется, конструируется и тестируется какая-то

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

минимизировать риски, упростить процесс изменения требований, сделать разработку более

гибкой и быстрой.

Скорость разработки является ключевым фактором успешности и

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

функционал своих продуктов для соответствия требованиям рынка, при этом строго

ограничивая себя во времени. Зачастую функционал реализуется «на скорую руку», лишь бы

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

Существует множество так называемых «legacy» систем, которые когда-то были

спроектированы, разработаны, выполняют свои функции по сей день, но из-за

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

модификации и развития. Чтобы не приходить свой проект в состояние, когда его легче

переписать с нуля, чем разобраться, как работает написанный год назад класс или метод,

необходимо постоянно поддерживать чистоту, читаемость и логичность кода.

Для безопасного изменения кода с сохранением внешнего поведения был разработан

целый ряд несложных приемов, получивши название рефакторинг[4].

12

Page 13: .Net teza ru

Рефакторинг (Refactoring) (сущ.): изменение во внутренней структуре программного

обеспечения, имеющее целью облегчить понимание его работы и упростить модификацию,

не затрагивая наблюдаемого поведения[5].

Производить рефакторинг (Refactor) (глаг.): изменять структуру программного

обеспечения, применяя ряд рефакторингов, не затрагивая его поведения[5].

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

недостатка проектирования, для прозрачного и безопасного изменения внутренней

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

Повышение читаемости кода не всегда благоприятно сказывается на его

производительности. Но практика регулярных рефакторингов повышает настраиваемость

программной системы, уменьшая количество «узких» мест, и позволяет проще и надёжнее

тестировать внесенные изменения. А зачастую, в случае «запущенного» кода, без

рефакторинга не может идти и речи о каких-либо модификациях или повышения качества

продукта.

В данной работе рефакторинг рассматривается именно как средство повышения

производительности. И если рефакторинг настольных и серверных платформ более чем

подробно описан в различных изданиях, рефакторинг мобильных систем освещен

достаточно скупо. В качестве платформы для мобильной разработки выбран .NET Compact

Framework, как одна из наиболее динамично развивающихся и востребованных на

корпоративном рынке.

В первой главе проводится обзор работ в области исследования, во второй приводится

теоретическая база, в третьей – результаты практической реализации повышения

производительности программного продукта.

13

Page 14: .Net teza ru

1 АНАЛИЗ РАБОТ В ОБЛАСТИ РЕФАКТОРИНГА И ОПТИМИЗАЦИИ ПРОИЗВОДИТЕЛЬНОСТИ МОБИЛЬНЫХ ПРИЛОЖЕНИЙ

Общей чертой для рассматриваемых областей исследования является отсутствие

свежих работ, рассматривающих особенности .NET Compact Framework 3.5. И если

оптимизация производительности рассматривается для более ранних платформ, то

рефакторинг мобильных приложений вообще не упоминается. Во многом это связано с

общностью и глобальностью принципов рефакторинга и наличием капитальных трудов,

касающихся разработки настольных и клиент-серверных приложений.

В данной работе рефакторинг рассматривается как средство улучшения кода с целью

последующего повышения производительности. Поэтому вначале будут рассмотрены работы

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

второй части этой главы будут рассмотрены наиболее важные и наиболее свежие работы в

области рефакторинга.

1.1 ОПТИМИЗАЦИЯ ПРОИЗВОДИТЕЛЬНОСТИ

Программирование для Windows Mobile и в частности для .NET Compact Framework

хорошо освещено в целом ряде популярных технических изданий. Только некоторые из них

цитируются в данной работе. В книге Александра Климова[18] и ей подобных (а таких

большинство) рассматриваются лишь основы мобильного программирования, знание

которых необходимо для создания простых приложений с графическим интерфейсом.

Большинство важной для уважающего себя разработчика информации сосредоточено на

различных тематических интернет форумах и лишь некоторые авторы тратят время на

написание подробных учебников по .NET Compact Framework. Связанно это с тем, что

мобильные платформы в последние 5-7 лет стремительно эволюционируют, и то, что еще

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

использовано в качестве руководства.

Так или иначе, книга Иво Салмре[20] покрывает практически все аспекты мобильной

разработки, и не смотря на то, что в ней рассматривается программирование на устаревшей

версии платформы, является прекрасным пособием. Автор предлагает следующие этапы

разработки приложения:

1. Определение сферы применения приложения,

14

Page 15: .Net teza ru

2. Анализ проблем производительности,

3. Проектирование интерфейса,

4. Выбор подходящих моделей данных и памяти,

5. Выбор моделей коммуникации и ввода-вывода,

6. Создание дистрибутива приложения.

После определения сферы применения приложения автор предлагает проанализировать

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

определить критические точки приложения, влияющие на производительность, и как можно

чаще контролировать показатели, характеризующие работу приложения. Автор предлагает

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

реальных объемах данных. Важной часть данной работы является описание субъективных

оценок производительности и способы, при помощи которых можно визуально ускорить

приложение. Так приложение с быстрым интерфейсом, мгновенно реагирующим на действия

пользователя и постоянно информирующим его о процессе выполнения тех или иных

действий, будет восприниматься как более быстрое, несмотря на то, что реальная скорость

обработки данных может быть ниже чем у менее информативного приложения. В книге

рассматриваются все аспекты написания производительного кода: накладные расходы на

обработку исключений, эффективное управление памятью, многозадачность и

многопоточность в современных операционных системах, оптимизация работы с XML,

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

что момента выхода .NET Compact Framework 1.0 много поменялось как в базовых

библиотеках самого фреймворка, так и в средствах контроля производительности на данной

платформе. Вследствие этого практические примеры из книги следует адаптировать к

нынешним реалиям, что затрудняет чтение и быстрое обучение освещенным принципам.

Авторы статьи «Developing Well Performing .NET Compact Framework Applications»[6],

размещенной на сайте разработчиков Microsoft, авторы в подробностях описывают процесс

создания высокопроизводительных мобильных приложений, раскрывают некоторые

неочевидные особенности платформы, рассказывают о способах оценки

производительности. Данная статья является одним из наиболее полных руководств по

оптимизации приложений на платформе .NET Compact Framework, хотя и применятся к

версии 1.0 платформы.

15

Page 16: .Net teza ru

В книге «Microsoft Mobile и .Net Compact Framework. Руководство разработчика»[15]

так же освещаются все стадии разработки, и хотя вопросы производительности не являются

основными, им уделена отдельная глава, важной особенностью которой является конкретное

применение рекомендаций по повышению производительности к платформе .NET Compact

Framework 2.0. В главе 5 описаны Common Language Runtime, Just-In-Time компилятор,

сборщик мусора, их особенности в мобильной версии .NET, рассказывается об инструментах

сбора статистики счетчиков производительности, приводятся общие рекомендации по

повышению производительности. Как и Иво Салмре, авторы акцентируют внимание на том,

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

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

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

Погоня за микросекундами в большинстве случаев является лишней тратой сил и времени.

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

каждой операции и впоследствии придерживаться их при разработке. Важной особенностью

издания является его относительная свежесть: основные примеры приводятся для .NET

Compact Framework версии 2.0, при этом упоминается и версия 3.5 платформы.

В книге «Programming .NET Compact Framework 3.5»[14] на более глубоком уровне

рассматриваются вопросы управления памятью, потребления энергии, взаимодействия с

операционной системой. Автор рассказывает о различных способах реализации приложения

для Windows Mobile, таких как Win32 API, .NET Compact Framework, Web browser, Rich

Internet Application. Для каждой платформы приводятся положительные и отрицательные

стороны, аргументируется выбор автором .NET Compact Framework – автоматическое

управление памятью, сборка мусора, компиляция на лету, богатые и быстрые интерфейсы,

кроссплатформеность и легкость разработки. Автор в деталях рассказывает о работе

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

батареи, взаимодействии с Win32 API, отображении данных, работе с файловой системой и

реестром, взаимодействии с базами данных, веб-сервисами. Также в книге уделяется

внимание особенностям работы LINQ на мобильных устройствах, способах синхронизации

данных удаленном управлении, Windows Communication Foundation и разработке

интерфейсов.

Автор «.NET Compact Framework 3.5 Data-Driven Applications» сосредотачивает свое

внимание на разработке коммерческих приложений для мобильных продаж. В книге

подробно описываются особенности взаимодействия с SQL Server CE и Oracle Lite,

16

Page 17: .Net teza ru

механизмы синхронизации данных полнотекстовому поиску. Автор особое место в книге

уделяет вопросам безопасности, производительности, оптимизации баз данных, кода,

ускорения интерфейсов, инструментам тестирования и отладки, оценке производительности.

Книга Baijian Yang, Pei Zheng и Lionel M. Ni [13] несмотря на сравнительную старость

практического материала (.Net Compact Framework 2.0), является прекрасным источником

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

Авторы Подробно описываю процесс разработки на платформе .Net Compact Framework

начиная от основ Windows Mobile, Compact Framework и языка программирования C#. Книга

описывает инструменты для создания интерфейсов, работу с файловой системой и базами

данных, работу с сетью, XML и веб-сервисами, SMS и телефонными сервисами,

взаимодействие с Win32 API, обработку исключений и отладку. Отдельное место в книге

занимают главы о безопасности данных, глобализации и локализации, работе с графикой и

вопросах производительности. Авторы подробно, с примерами кода на C# описывают работу

CLR, Garbage Collector, накладные расходы при вызове методов, операциях с плавающей

запятой, использовании механизмов отражения (Reflection), преимущества типизированных

коллекций, ускорение работы с XML, оптимизацию загрузки форм. Авторам удалось создать

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

В предыдущих абзацах были рассмотрены наиболее интересные и полные переводные

издания о .NET Compact Framework. Авторы обеих книг глубоко погружаются в детали

разработки приложений для Windows Mobile и рассматривают вопросы производительности

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

является то, что они не поспевают за развитием технологий, и, с учетом времени на перевод,

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

информации для современного разработчика являются статьи в тематических блогах,

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

Одной из подобных уникальных публикаций является «An Empirical Study of the Code

Pitching Mechanism in the .NET Framework»[1]. В работе рассматривается механизм

«питчинга» кода – процесс, при котором прекомпилированный код выгружается из памяти в

случае острой ее нехватки, и, в последствие, компилируется заново при вызове. Авторы

исследуют особенности работы открытой версии CLR от Microsoft - Shared-Source Common

Language Infrastructure, и на примере приложений, написанных для данной платформы,

показывают зависимость вероятности выгрузки кода в зависимости от размера программного

кэша и различных сфер использования.

17

Page 18: .Net teza ru

В целом для области исследования характерна фрагментированость источников

информации, отсутствие полных изданий для последней версии платформы, значительный

перевес англоязычных авторов, оторванность рекомендаций от деталей реализации

платформы.

18

Page 19: .Net teza ru

1.2 РЕФАКТОРИНГ

Рефакторинг прекрасно описан во множестве популярных изданий, и уже успел

перейти из разряда научно-технических новинок в разряд повседневных инструментов

программиста. Практически все современные среды разработки (Microsoft Visual Studio,

NetBeans, Eclipse, IntelliJ IDEA и др.) включают в себя те или иные средства для

автоматического рефакторинга кода. Тем не менее, улучшение кода мобильных приложений

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

принципов рефакторинга. Может показаться, что разработка мобильных приложений ничем

существенно не отличается от разработки настольных или серверных систем. В следующей

главе будет более подробно описано, почему это не так. Здесь же будет проведен обзор

наиболее релевантных работ в области.

Уильям Апдайк может по праву считаться основателем рефакторинга. Именно ему

принадлежит первый печатный труд в области[10]. Во многом благодаря работе его команды

принципы рефакторинга получили такое широкое распространение в объектно-

ориентированном программировании. Главным мотивом для написания этой работы в 1992

году стало желание автора увеличить количество повторно используемого объектно-

ориентированного кода в своих проектах, реструктурировать имеющиеся программы с целью

их упрощения и снижения количества допускаемых ошибок. В работе описывается важность

и сложность процесса изменения кода без изменения поведения, приводятся примеры

наиболее важных рефакторингов, подводится теоретическая основа для автоматизации

процессов улучшения кода. Среди описанных рефакторингов в работе рассматривается

выделение абстрактного суперкласса, с целью изоляции повторяющегося кода, а также

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

изменениям на низком уровне, факторам, которые следует учитывать, чтобы сохранить

поведение объектов. Эта работа, написанная 18 лет назад, по сей день может быть

использована при формировании требований к средствам автоматической обработки кода. И

именно Уильям Апдайк подтолкнул Мартина Фаулера на написание книги «Рефакторинг:

улучшение существующего кода»[5], ставшей наиболее популярным руководством в

области.

В своей книге[5] Мартин Фаулер даёт определение рефакторингу, подробно описывает

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

дурного кода. Он говорит об автоматизированном тестировании кода как о неотъемлемой

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

19

Page 20: .Net teza ru

Несмотря на то, что с момента публикации первой редакции прошло более десяти лет, книга

по-прежнему остается актуальной и востребованной, потому как аккумулирует в себе знания

о рефакторинге, необходимые для любого объектно-ориентированного программиста. Автор

аргументирует необходимость проведения рефакторинга следующими утверждениями:

Рефакторинг улучшает композицию программного обеспечения,

Рефакторинг облегчает понимание программного обеспечения,

Рефакторинг помогает найти ошибки,

Рефакторинг позволяет быстрее писать программы.

Автор предлагает выполнять рефакторинг регулярно при добавлении функционала,

исправлении ошибок, или разборе программы. В книге перечисляются места, которым

следует уделить особое внимание при проведении очередной чистки. Это дублирование

кода, слишком длинные методы, перегруженные классы, методы с длинным списком

параметров, расходящиеся модификации, сильная связанность объектов, перегруженные

условные конструкции, параллельные иерархии наследования, неиспользуемый в проекте

код общего назначения, временные поля, наличие комментариев и другие. Автор подробно

перечисляет методы рефакторинга, для каждого метода дает описание, область

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

критерии для инструментов проведения рефакторинга, такие как индексирование и поиск по

проекту, качественный синтаксический разбор кода, скорость, возможность отмены

изменений, интеграция со средствами разработки. Данное издание стало своего рода библией

рефакторинга, и включает в себя все базовые знания о предмете. Хотя каталог

рефакторингов[16] пополняется с каждым годом, книга включает в себя наиболее важное

большинство из них. Автор рассказывает об истоках рефакторинга и упоминает докторскую

диссертацию Уильяма Апдайка, как «на сегодняшний день самую существенную работу по

рефакторингу».

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

рефакторинга. Другим интересным трудом в данной области является «Automated Application

of Design Patterns: A Refactoring Approach»[8], в котором автор описывает современные

методы разработки и формализирует требования к автоматизации применения шаблонов

проектирования. Рефакторинг рассматривается как инструмент внедрения шаблонов. Исходя

из требования сохранения поведения, автор описывает модели преобразований,

20

Page 21: .Net teza ru

необходимых для внедрения таких паттернов как Singleton, Abstract Factory, Builder,

Prototype, Bridge, Wrapper, Strategy, Delegation и других преобразований. Краткое изложение

предложенной модели может быть найдено в работе [9]. В данной работе формализуется

процесс рефакторинга Java программ с целью внедрения шаблонов проектирования,

рассматриваются пред- и постусловия примитивных изменений в коде, описывается

необходимая для сохранения поведения последовательность действий. Статья представляет

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

рефакторинга, и дает представление о серьезности затронутых в диссертации вопросов.

В статье «Patterns of Anti-Patterns?»[4] автор говорит о наихудших практиках

программирования, приводит в качестве примеров «антипаттернов»[3] инкрементальную

разработку (модель водопада) и злоупотребление повторно используемым кодом, а также

предлагает возможные варианты решения обозначенных проблем при помощи рефакторинга.

Также в статье приводятся устоявшиеся принципы качественной разработки. Это

программирование интерфейсов, использование композиции для расширения поведения,

минимизация зависимостей между составляющими элементами, итеративный и

инкрементальный подход к разработке.

Обзор современных техник программирования при помощи шаблонов проектирование

проводит в своей книге[17] Джошуа Кериевски. Автор рассказывает, как можно применять

шаблоны, избегая при этом избыточного программирования, как можно улучшить плохо

спроектированный проект. Автор говорит о шаблонах не только как об архитектурных

каркасах программных проектов, но и как о средстве упрощения и улучшения кода, как о

цели рефакторинга. Джошуа Кериевски рассматривает рефакторинг и шаблоны сточки

зрения эволюционного программирования и говорит о необходимости автоматизированного

тестирования кода, отзывается о Test Driven Development как об одной из лучших техник

экстремального программирования[2]. В книге автор описывает рефакторинг как

последовательность «малых шажков» трансформации кода с постоянным тестированием

поведения, рассказывает о различных способах реализации шаблонов, перечисляет признаки

плохого кода, приводит каталок рефакторингов «к шаблонам», «по направлению к

шаблонам» и «от шаблонов». Для каждого рефакторинга приводится область применения,

алгоритм реализации и пример на языке Java. Автор не затрагивает вопросов

производительности или разработки мобильных приложений. Тем не менее, в книге

описывается современный подход к использованию шаблонов проектирования, и принципы,

21

Page 22: .Net teza ru

изложенные в книге, могут быть с успехом использованы в любом объектно-

ориентированном проекте.

Роберт Мартин в своей книге[19] уделяет гораздо больше внимания чистоте кода. Хотя

в целом издание характеризует некоторая сумбурность изложения, книга является наиболее

полным справочником правил хорошего кодирования. Автор рассказывает о выборе имен

для объектов, об особенностях оформления функций, правилах составления комментариев к

коду, форматировании текста программы, выборе структур и объектов данных, обработке

ошибок, модульном тестировании, построении классов и систем, формировании архитектуры

проекта. Особое внимание в книге уделяется многопоточности. Автор рассказывает, зачем

нужны потоки, предупреждает о распространенных ошибках, рассказывает о популярных

потокобезопасных моделях обработки и синхронизации данных, об особенностях

тестирования многопоточных программ, приводит примеры кода и рассказывает о

повышении производительности.

Стефан Фаро в своей книге[22] адаптирует принципы рефакторинга для модификации

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

приводится подробный список рефакторингов, способов оценки производительности и

тестирования работоспособности.

Несмотря на свою зрелость, тема рефакторинга по-прежнему остается актуальной и

интересной для многих исследователей. Олег Степанов в своей диссертации[21]

рассматривает рефакторинг как неотъемлемую часть разработки. Автор приводит каталог

рефакторингов, на практике применяемых им и его студентами для модификации автоматов.

Для каждого рефакторинга автор приводит описание, мотивацию к применению, описание

техники и доказательство корректности. В работе описаны следующие виды рефакторингов:

Группировка состояний,

Удаление группы состояний,

Слияние состояний,

Выделение автомата,

Встраивание вызываемого автомата,

Переименование состояния,

Перемещение воздействия из состояния в переходы,

22

Page 23: .Net teza ru

Перемещение воздействия из переходов в состояние.

В статье «Classification of model refactoring approaches»[7] авторы проводят детальный

обзор имеющихся работ в области реструктуризации исходного кода и моделей с

формальной и практической точек зрения. Они предлагают классификацию имеющихся

рефакторингов для программирования, ориентированного на модели, приводят их сравнение.

В целом рефакторинг можно охарактеризовать как молодую, но зрелую и бурно

развивающуюся область разработки программного обеспечения. Благодаря развитию

объектно-ориентированного программирования и концепции повторно использующегося

кода, рефакторинг как методология получил широкое распространение как средство

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

исследования отражаются в росте количества различных автоматизированных систем

рефакторинга. Появляются и реализуются новые методологии применения принципов

рефакторинга в различных областях программирования – ООП, программировании

автоматов, разработке, управляемой моделями (MDE). Положительные и отрицательные

стороны различных рефакторингов в применении к разработке мобильных приложений

будут рассмотрены в следующих главах.

23

Page 24: .Net teza ru

2 ОСОБЕННОСТИ РЕФАКТОРИНГА И ОПТИМИЗАЦИИ ПРОИЗВОДИТЕЛЬНОСТИ НА ПЛАТФОРМЕ .NET COMPACT FRAMEWORK

2.1 РЕФАКТОРИНГ КАК СРЕДСТВО УЛУЧШЕНИЯ КОДА

Рефакторинг представляет собой процесс улучшения внутренней структуры

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

появления новых ошибок минимальны. Фактически, при проведении рефакторинга

программы композиция и дизайн улучшается уже после написания программы. Идея

улучшения программы после её написания может выглядеть странно. В классическом

понимании разработки программного обеспечения сначала создается дизайн, архитектура

системы, а потом генерируется код. Со временем программа модифицируется, и целостность

системы, соответствие ее поведения и структуры первоначальным требованиям постепенно

ухудшаются. Программирование медленно превращается в ломание программы.

Рефакторинг является противоположной методологией. С ее помощью можно взять плохой

проект, даже хаотический, и переделать его в хорошо спроектированный код. Каждый шаг

этого процесса элементарен до чрезвычайности. Перемещается поле или метод из одного

класса в другой, из одного метода часть кода помещается в отдельный метод, какая-то часть

код перемещается в иерархии вверх или вниз. Однако общий эффект столь малых изменений

может кардинально повысить качество проекта. Этот процесс прямо противоположен

явлению постепенного распада программы. При проведении таких изменений оказывается,

что соотношение между этапами разработки изменяется. Проектирование постоянно

выполняется во время разработки, а не осуществляется целиком заранее. При

конструировании системы становится ясно, как ее можно улучшить. Происходящее

взаимодействие ведет к созданию программы, качество которой остается высоким по мере

продолжения разработки[5].

Концепция рефакторинга быстро нашла себе дорогу в лагеря приверженцев различных

языков программирования. Поскольку рефакторинг является неотъемлемой частью

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

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

создать не сразу - он развиваться по мере накопления опыта. В настоящее время очень часто

приходится читать и модифицировать код, а не писать новый. В основе читаемости и

изменяемости кода лежит рефакторинг - как в частном случае библиотек классов, так и для

программного обеспечения в целом. Но с рефакторингом связан некоторый риск. Внесение

24

Page 25: .Net teza ru

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

в программе. Неправильно выполняя рефакторинг, можно потерять дни и недели отлаживая

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

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

из которой нельзя выбраться. Чтобы не утонуть в постоянно накапливающихся ошибках и

исправлениях , следует производить рефакторинг регулярно. Шаблоны проектирования

являются конечной целью рефакторинга. Но указать цель - лишь одна часть задачи.

Модифицировать код так, чтобы достичь этой цели - другая проблема.

Без регулярного осуществления рефакторинга внутренняя структура программы

приходит в негодность. По мере внесения в программу модификаций, связанных с

реализацией краткосрочных целей или производимых без полного понимания работы всего

проекта, последний утрачивает свою структурированность. Разобраться в нем, читая

исходный код, становится все труднее. Рефакторинг напоминает наведение порядка в

программе. Убираются части, оказавшиеся не на своем месте. Беспорядку свойственно

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

сохранить структуру и тем быстрее происходит её распад. Регулярное выполнение

рефакторинга помогает сохранять композицию.

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

он делает одно и то же в разных местах. Поэтому одной из важных частей улучшения

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

модификацией проекта в будущем. Сокращение количества строк кода не сделает систему

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

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

когда её приходится модифицировать. Чем больше кода, тем труднее в нем разобраться и

внести верные изменения. При модификации проекта в некотором месте система может

повести себя несоответственно расчетам, поскольку не изменен аналогичный участок,

выполняющий то же самое, но в другом контексте. Удаление дублирующегося кода

гарантирует, что все, что нужно в программе, находится только в одном месте, в чем и

состоит суть хорошего проектирования.

Программирование во многом представляет собой общение человека с компьютером.

Программа говорит компьютеру, что необходимо сделать, и тот в ответ делает в точности то,

что ему сказано. С накоплением опыта разрыв между тем, что требуется от машины, и тем,

что пишет программист, сокращается. При этом суть программирования состоит в том,

25

Page 26: .Net teza ru

чтобы точно сказать, что требуется от компьютера. Но ведь программа адресована не только

машине. Через какое-то время кому-нибудь понадобится прочесть код программы, чтобы

внести какие-то изменения. Стоит ли волноваться из-за того, что компьютеру для

компиляции потребуется несколько дополнительных циклов? Программист может потратить

неделю на изменение кода, которое заняло бы у него час, если бы он был в состоянии быстро

разобраться в программе. Рефакторинг помогает сделать код более легким для чтения. При

проведении рефакторинга код, который работает, но не отличается идеальной структурой,

приводится в состояние, в котором он лучше информирует о своей цели.

Лучшее понимание кода помогает быстро обнаруживать ошибки. Далеко не все

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

Однако при проведении рефакторинга программист глубоко вникает в суть проекта, пытаясь

понять, как работает программа, и достигнутое понимание возвращается обратно в код.

После прояснения структуры программы ошибки не могут остаться незамеченными.

2.1.1 КОГДА СЛЕДУЕТ ПРОИЗВОДИТЬ РЕФАКТОРИНГ

Рефакторинг как любую уборку следует проводить регулярно. То, что рефакторинг

часто применяется для улучшения проектов с недостатком проектирования, не означает, что

можно вначале написать программу, а потом доводить ее до ума. Мартин Фаулер в своей

книге[5] говорит о правиле трех ударов, которое подсказал ему Дон Роберте: «Делая что-то в

первый раз, вы просто это делаете. Делая что-то аналогичное во второй раз, вы морщитесь от

необходимости повторения, но все-таки повторяете то же самое. Делая что-то похожее в

третий раз, вы начинаете рефакторинг».

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

Наиболее распространенные из них следующие[17]:

Упрощение добавления нового кода,

Улучшение существующего проекта,

Достижение лучшего понимания кода,

Приведение кода к определенному стандарту.

При добавлении к системе новой функциональности существует определенный выбор:

её можно быстро запрограммировать, не обращая внимания на то, насколько хорошо она

26

Page 27: .Net teza ru

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

функция предоставлялась легко и естественно. Первый подход гарантирует лучшую

скорость разработки в настоящем, но в будущем придется расплачиваться по «долгам

проектирования»[17]. Следуя второму пути необходимо вначале проанализировать все

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

функциональность. Оба эти подхода равноценны. В условиях ограниченного времени часто

приходиться вначале добавлять код, а потом его реорганизовывать. Если же времени

достаточно, рефакторинг лучше провести заранее.

Постоянные улучшения в коде проекта делают работу с ним легче и дешевле. Для

поддержки продуманного проекта не нужны высококвалифицированные специалисты.

Непрерывная реорганизация кода включает в себя постоянный поиск несовершенств кода и

устранение их тотчас или вскоре после обнаружения. Регулярная чистка – необходимая и

важная привычка.

Иногда код программы приходит в состояние, в котором даже ее автор не в состоянии с

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

рефакторинг. Простые последовательные изменения вроде удаления дубликатов позволяют

лучше ознакомиться с принципами работы система, а также упростить ее понимание в

будущем.

По словам Джошуа Кериевски, немаловажной причиной для рефакторинга является то,

что код «раздражает». Это прекрасно коррелирует с тремя принципами Мартина Фаулера.

Если какая-то часть программы привлекает к себе слишком много внимания, и для внесения

новых функций необходимо постоянно вносить изменения в какой-то объект, растягивая

время разработки, это говорит о том, что необходимо провести рефакторинг.

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

необходимость переписать код с нуля. Иногда существующая программа настолько запутана,

что подвергнуть ее рефакторингу, конечно, можно, но легче начать все с самого начала.

Такое решение бывает нелегко принять, и достаточно сложно предложить какие-либо

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

неработоспособности. Это обнаруживается только при его тестировании, когда ошибок

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

состоит в разработке модулей с сильной инкапсуляцией через рефакторинг большей части

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

27

Page 28: .Net teza ru

решение относительно модификации его структуры при помощи рефакторинга или

воссоздания заново. Это многообещающий подход, но правила его реализации

индивидуальны и требуют наличия определенного опыта. Когда основная система является

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

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

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

поздно - после истечения срока. Незавершенный рефакторинг подобен влезанию в долги.

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

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

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

их часть посредством рефакторинга. Однако близость срока окончания работ - единственный

случай, когда следует отложить рефакторинг, ссылаясь на нехватку времени[5].

2.1.2 ПРИЗНАКИ ДУРНОГО КОДА

Рефакторинг, или улучшение проекта существующего кода, требует знания о том,

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

методов рефакторинга[16], хотя конкретная ситуация может и отличаться от описанного в

каталоге метода. Следовательно, необходимо знать о распространенных проблемах проектов,

чтобы их можно обнаружить и в своем собственном коде.

Наиболее частые проблемы проектов возникают в следующих случаях:

если код содержит повторы,

если код непонятен,

если код сложен.

Эти критерии помогают найти в программе места, нуждающиеся в улучшении. С

другой стороны многие программисты считают этот список слишком абстрактным[17]:

непонятно как распознать в коне повторения, если они не идентичны; невозможно с полной

уверенностью определять, ясно ли говорит код о своем назначении, не совсем понятно, как

отличить простой код от сложного.

В главе «Bad smells in the code» из книги «Refactoring - Improving the Design of Existing

Code»[5] Мартин Фаулер и Кент Бек представляют дополнительное руководство по

28

Page 29: .Net teza ru

определению проблем в проектах. Сравнивая проблемы проектов с неприятными запахами,

они объясняют, какие рефакторинги или их комбинации лучше сработают, чтобы освежить

проект.

Признаки плохого кода, описанные Фаулером и Беком, нацелены на проблемы,

которые возникают повсеместно: в методах, классах, иерархиях, пакетах (именованных

областях видимости, модулях) и целых системах. Далее будут кратко перечислены наиболее

важные из них.

Повторяющийся код – наиболее распространенная и острая проблема программного

обеспечения. Повторение может быть как явным, так и едва уловимым. Явное повторение

кода существует в виде идентичного кода, в тоже время повторение может проявляться в

структурах или шагах обработки, которые внешне различны, но, несмотря на это, одинаковы

по своей сути.

Длинные методы – следующая распространенная проблема. Мартин Фаулер приводит

несколько важных причин, по которым короткие методы лучше длинных. Основная причина

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

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

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

методы помогают в объяснении кода. Если функциональность некоторого фрагмента кода не

очевидна, его следует выделить в один небольшой, удачно названный метод, что упростит

понимание кода и исключит необходимость написания комментариев. Обычно легче

расширять и сопровождать системы, в которых преобладают небольшие методы, поскольку

такие системы проще для понимания и содержат меньше повторов. Если код метода не

помещается на экране, это повод задуматься о разделении его на более простые части при

помощи Compose Method или Extract Method[5,16].

Излишняя открытость – когда клиенту видны методы или классы, о которых ему

необязательно знать, усложняют понимание системы и засоряют интерфейсы. Не все классы

должны обладать открытым конструктором. Иногда для улучшения абстракции следует

сделать конструктор закрытым и использовать фабрику для создания экземпляров объектов.

Расплывшееся решение (solution sprawl) – когда код или данные, реализующие

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

29

Page 30: .Net teza ru

Альтернативные классы с разными интерфейсами – возникают в коде, когда

интерфейсы двух классов различны, а классы все еще достаточно похожи. Если между

классами обнаруживается сходство, следует провести рефакторинг для приведения их к

общему интерфейсу. Однако иногда нельзя непосредственно изменить интерфейс класса,

поскольку из-за отсутствия доступа к коду, при работе, например, со сторонней

библиотекой. В таком случае следует применить Unify Interfaces With Adapter[17].

Ленивые классы – классы, которые недостаточно нагружены функционалом, и не

делают достаточно для того чтобы себя окупить. Такие классы следует ликвидировать,

перенеся функционал в более подходящее место.

Большие классы, содержащие в себе слишком много полей, являются верным

признаком того, что классы пытаются делать слишком много, что они перегружены

ответственностью. Их следует разбивать на меньшие используя Extract Class или Extract

Subclass[5,16].

Наличие запутанных условных конструкций говорит о том, что следует задуматься о

полиморфизме.

2.1.3 ПРИМЕРЫ РЕФАКТОРИНГОВ

Наиболее полный список методов рефакторинга может быть найден в каталоге[16].

Здесь же будут приведены самые базовые методы.

«Выделение метода» (Extract Method) - один из наиболее частых типов рефакторинга.

Он применяется, чтобы упростить перегруженные методы. Если в большом методе какому-

то участку кода можно дать осмысленное название – его следует выделить. Для этого

необходимо создать новый метод и назвать его в соответствии с назначением метода (тому,

что он делает, а не как). Когда полагаемый к выделению код прост, например, если он

выводит некоторое сообщение или вызывает какую-либо функцию, следует выделять его,

если название порождаемого метода точнее раскрывает назначение кода. Если более

содержательное имя не находится, код не следует выделять в метод. Далее следует

скопировать код, подлежащий экстракции, из исходного метода в новый. После этого важно

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

исходном методе. Под ними подразумеваются локальные переменные и аргументы метода.

30

Page 31: .Net teza ru

Следует найти все временные переменные, которые используются только внутри этого

выделенного кода. Если такие переменные есть, следует объявить их в новом методе. Если

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

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

или таких переменных несколько, в существующем виде выделить метод нельзя. Сначала

следует выполнить «Расщепление временной переменной» (Split Temporary Variable)[16], а

потом выделить метод. Временные переменные можно удалить при помощи рефакторинга

«Замена временных переменных вызовом методов» (Replace Temp with Query). В

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

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

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

метода. Рефакторинг следует завершить компиляцией и тестированием.

Пример:

void printOwing() {

ICollection<Order> orders = _orders.elements();

decimal outstanding = 0.0;

// вывод баннера

Console.WriteLine (“**************************”);

Console.WriteLine (“***** Задолженность клиента ******”);

Console.WriteLine (“**************************”);

// расчет задолженности

foreach (Order order in orders) {

outstanding += orders.getAmount();

}

// вывод деталей

Console.WriteLine (“имя ” + _name);

Console.WriteLine (“сумма” + outstanding);

}

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

void printOwing() {

ICollection<Order> orders = _orders.elements();

decimal outstanding = 0.0;

// вывод баннера

printBanner() ;

// расчет задолженности

foreach (Order order in orders) {

outstanding += orders.getAmount();

31

Page 32: .Net teza ru

}

// вывод деталей

Console.WriteLine (“имя ” + _name);

Console.WriteLine (“сумма” + outstanding);

}

void printBanner() { // вывод баннера

Console.WriteLine (“**************************”);

Console.WriteLine (“***** Задолженность клиента ******”);

Console.WriteLine (“**************************”);

}

Встраивание метода (Inline Method) – другой распространенный рефакторинг,

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

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

метод. При этом необходимо убедиться, что метод не является полиморфным. Встраивание

не рекомендуется, если есть подклассы, перегружающие метод, так как они не смогут

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

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

Пример:

int getRating() {

return (moreThanFiveDeliveries()) ? 2 : 1;

}

bool moreThanFiveDeliveries() {

return _numberOfDeliveries > 5;

}

Встраиваем метод moreThanFiveLateDeliveries:

int getRating() {

return (_numberOfDeliveries > 5) ? 2 : 1;

}

Введение поясняющей переменной (Introduce Explaining Variable)[5,16] позволяет

значительно упростить понимание условных конструкций и последовательностей

вычеслений. Выражения бывают чрезвычайно сложными и трудными для чтения. В таком

случае полезно с помощью временных переменных превратить выражение в нечто, лучше

поддающееся управлению. Особую ценность «Введение поясняющей переменной» (Introduce

Explaining Variable) имеет в условной логике, когда для каждого пункта условия удобно

32

Page 33: .Net teza ru

объяснить, что он означает, с помощью временной переменной с логично подобранным

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

раскрыть с помощью временной переменной.

Пример:

if ( (platform.ToUpper().IndexOf(“MAC”) > -1 ) &&

(browser.ToUpperCase().IndexOf(“IE”) > -1 )&&

wasInitialized() && resize > 0 ) {

// do something

}

Заменяем на следующий код:

bool isMacOS = platform.ToUpper().IndexOf(“MAC”) > -1;

bool isIEBrowser = browser.ToUpper().IndexOf(“IE”) > -1;

bool isResized = resize > 0;

if(isMacOS && isIEBrowser && wasInitialized() && isResized) {

// do something

}

33

Page 34: .Net teza ru

2.2 ПРОИЗВОДИТЕЛЬНОСТЬ ПРИЛОЖЕНИЙ НА ПЛАТФОРМЕ .NET COMPACT FRAMEWORK

Несмотря на значительную положительную динамику на аппаратном уровне и на

уровне операционной системы, мобильные устройства по-прежнему ограничены по своим

базовым возможностям, и это еще более поднимает значимость вопросов

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

учесть, что мобильные устройства питаются от батарей, старая пословица «чем меньше —

тем лучше» становится жизненно важной из соображений не только производительности, но

и экономии электроэнергии.

2.2.1 ПОНЯТИЕ ДОСТАТОЧНОЙ ПРОИЗВОДИТЕЛЬНОСТИ

В некоторых коллективах разработчиков считается, что процессы надо стремиться

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

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

производительность, а все остальное учитывать не обязательно. Это неправильно. Просто

код должен выполняться «достаточно быстро». Наша задача — установить, что для

приложения означает «достаточно быстро».

Например, если приложению нужно три секунды для выполнения определенного

действия (например, для подключения к серверу и проверки полномочий на вход), и

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

означает неразумно расходовать ресурсы. В этой ситуации требуемый уровень

производительности определяется критерием приемлемости для пользователя. Соответствие

требуемого уровня производительности критерию приемлемости для пользователя должно

быть единственной движущей силой при определении того, как быстро должен выполняться

код. Если вам необходимы дополнительные факторы для определения быстродействия кода,

примите во внимание следующие вопросы: Насколько быстро работала предыдущая версия?

Насколько быстро работают аналогичные продукты конкурентов? Еще раз повторимся:

единственным критерием, который имеет смысл учитывать при принятии решения о

необходимости оптимизировать код, является удовлетворенность пользователей скоростью

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

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

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

34

Page 35: .Net teza ru

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

них конкретные минимальные и идеальные требования к производительности, избегая

характеристик типа «эта подсистема должна быть быстрой». К несчастью, многие

разработчики, написав огромное количество кода, только в последнюю минуту

обнаруживают, что их код недостаточно быстрый, и только после этого пытаются

оптимизировать его в надежде добиться прироста производительности. Требования к

производительности должны быть установлены уже на ранней стадии разработки, чтобы

впоследствии избежать неприятных сюрпризов. Частью цикла тестирования должен быть

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

спецификациям, и изменения кода не сказались на производительности отрицательно.

Высокопроизводительный код не создается «задним числом» — производительность

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

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

что загрузка 10000 позиций занимает недопустимо много времени. Следует ли после этого

для оптимизации кода переделывать отдельные методы или лучше изменить всю

конструкцию? Процесс загрузки 10000 позиций на ограниченном в ресурсах устройстве

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

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

чтобы он прокручивал тысячи позиций на устройстве, экран которого вмещает не более

десятка позиций? Очевидно, решение состоит в том, что данную подсистему изначально

следует конструировать с прицелом на высокую производительность: загружать по 100 или

200 позиций за раз, подгружать следующие, пока пользователь прокручивает список,

предложить кнопки для перемещения на предыдущую и следующую страницы, наконец,

разбить данные по алфавиту, по категориям или по какому-нибудь иному признаку.

Лучше в первую очередь сосредоточиться не на реальной (например, времени загрузки

формы), на воспринимаемой производительности (например, на времени, которое проходит

от нажатия кнопки до появления указателя в виде песочных часов, символизирующих

ожидание). Необходимо конструировать свои приложения таким образом, чтобы

пользователи постоянно получали отклик на свои действия. Удивительно, насколько

настойчиво пользователи утверждают, что одно приложение быстрее другого, если «более

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

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

Например, можно показать на экране индикатор процесса или указатель ожидания, можно

35

Page 36: .Net teza ru

также выводить в строке состояния некие промежуточные сообщения, чтобы информировать

пользователя о состоянии дел, а не заставлять его вглядываться в пустой экран в ожидании

завершения операции. Промежуточный отклик важен и для некоторых действий должен

быть заранее предусмотрен в проекте. Например, ситуация, когда результаты поиска

появляются на экране только через 10 секунд после нажатия кнопки поиска, является

совершенно неприемлемой. Однако если каждые 2 секунды пользователь будет видеть

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

восприниматься пользователем как быстрый.

Выше были перечислены базовые рекомендации, которые применимы к любому

проекту по разработке программного обеспечения. Что еще важно — необходимо всегда

понимать характеристики рабочей платформы. Это особенно верно в отношении

Microsoft .NET Compact Framework. Почти любой вариант оптимизации, который может

прийти в голову, имеет смысл использовать только при полном понятии того, как в .NET

Compact Framework функционирует общеязыковая исполнительная среда.

2.2.2 ВЛИЯНИЕ РЕФАКТОРИНГА НА ПРОИЗВОДИТЕЛЬНОСТЬ

С рефакторингом обычно связан вопрос о его влиянии на производительность

программы. С целью упростить понимание работы программы часто осуществляется

изменение, приводящее к замедлению работы программы. Это важный момент. Не стоит

пренебрегает производительностью в пользу чистоты проекта или в надежде на рост

мощности аппаратной части, особенно если речь идет об ограниченных ресурсах мобильных

устройств.

Программное обеспечение отвергается как слишком медленное, а более быстрые

машины устанавливают свои правила игры. Рефакторинг, несомненно, заставляет программу

работать медленнее, но при этом делает ее более удобной для настройки

производительности. Секрет разработки быстрых программ, если только они не

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

сначала написать программу, которую можно настраивать, а затем настроить ее так, чтобы

достичь необходимой скорости[5].

36

Page 37: .Net teza ru

Мартин Фаулер говорит о трех подходах к написанию быстрых программ. Наиболее

сложный из них связан с ресурсами времени и часто применяется в системах с жесткими

требованиями к выполнению в режиме реального времени. В этой ситуации при

декомпозиции проекта каждому модулю выделяется бюджет ресурсов - по времени и памяти.

Компонент не должен выйти за границы своего бюджета, хотя допустим механизм обмена

временными ресурсами. Такой механизм строго сосредоточен на контроле времени

выполнения. Это необходимо в таких системах, как, например, кардиостимуляторы, в

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

всего, будет избыточна в системах другого типа, как например в корпоративных

информационных порталах.

Второй подход предполагает постоянное улучшение. В этом случае каждый

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

производительность программы на высоком уровне. Это распространенный и интуитивно

заманчивый подход, однако он не так хорош на деле. Модификация, повышающая

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

программы. На это можно было бы пойти, если бы в результате получалось более быстрое

программное обеспечение, но обычно этого не происходит. Повышающие скорость

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

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

обстоятельство - анализ большинства программ показывает, что большая часть времени

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

что 90% оптимизации произведено впустую, потому что был оптимизирован код, который

выполняется сравнительно редко. Время, ушедшее на ускорение программы, и время,

потерянное из-за ее сложности - все это потрачено напрасно.

Третий подход к повышению производительности программы основан как раз на этой

статистике. Он предполагает создание программы с достаточным разложением ее на модули

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

производительности, который обычно наступает на довольно поздней стадии разработки и

на котором осуществляется особая процедура настройки программы. Начинается все с

запуска программы под профайлером, контролирующим программу и сообщающим, где

расходуются время и память. Благодаря этому можно обнаружить тот небольшой участок

программы, в котором находятся узкие места производительности. На этих узких местах

сосредоточиваются усилия, и выполняется та же самая оптимизация, которая была бы

37

Page 38: .Net teza ru

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

устремлено на выявленных узких местах, удается достичь больших результатов при

существенно меньших затратах времени и труда. Но даже в этой ситуации нужна

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

шажками, каждый раз компилируя, тестируя и запуская профайлер. Если

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

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

удовлетворяет пользователей. Хорошее разделение программы на модули содействует

оптимизации такого рода в двух отношениях. Во-первых, благодаря разделению появляется

время, которое можно потратить на оптимизацию. Имея хорошо структурированный код,

можно быстрее разрабатывать новые функции и выиграть время для того, чтобы уделить

внимание производительности. Профилирование гарантирует, что это время не будет

потрачено зря. Во-вторых, хорошо структурированный код обеспечивает более высокое

разрешение для анализа производительности. Профайлер указывает на более мелкие

фрагменты программы, которые легче настроить.

Благодаря большей понятности кода легче осуществить выбор потенциальных

вариантов и разобраться в том, какого рода настройка может оказаться эффективной. Мартин

Фаулер в своей книге[5] приходит к выводу, что рефакторинг позволяет ему писать

программы быстрее. На некоторое время он делает код более медленными, но облегчает

настройку программ на этапе оптимизации. В конечном итоге в производительности

достигается большой выигрыш.

Рефакторинг в любом случае следует проводить. Но если отладка показала, что

возникает много событий питчинга кода (Code Pitching)[1] или слишком много времени

уходит на вызов виртуальных методов, следует задуматься об упрощении иерархии классов

произведя такие рефакторинги как Inline Method и Inline Class.

38

Page 39: .Net teza ru

2.2.3 РЕКОМЕНДАЦИИ ПО ОПТИМИЗАЦИИ ПРОИЗВОДИТЕЛЬНОСТИ

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

структуры и особенностей работы .NET Compact Framework описаны в рассмотренных выше

источниках. В данном разделе будет перечислен краткий список рекомендаций,

представляющий собой экстракт из указанных источников и частного опыта автора.

Следует избегать применения Reflection. Механизмы отражения дают программисту

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

чрезвычайно высока.

Следует избегать по возможности использования виртуальных методов и свойств.

Виртуальные методы значительно медленнее (в 1.5-2 раз) чем обычные или статические

методы. Для исполнения виртуального метода платформе требуется определить тип объекта,

а следовательно использовать Reflection. Чем глубже виртуальный метод иерархии

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

избегать вызовов PInvoke и COM. Авторы [13] приводят следующую сравнительную таблицу

скорости выполнения различных типов вызовов.

Таблица 2.1. Сравнение времени выполнения вызовов

Тип вызова Описание

Коэффициент времени

выполнения

Системный вызов Вызов Windows API 1

Вызов экземпляра

или статический

Вызов метода из управляемого кода,

привязываемый на этапе компиляции2 - 3

Виртуальный

вызов

Вызов управляемого метода, требующего

привязки во время выполнения3 - 5

Вызов P/InvokeВызов импортированной из DLL функции

из управляемого кода10 -15

Вызов COMВызов метода через COM интерфейс из

управляемого кода10 -15

39

Page 40: .Net teza ru

Методы Equals() и GetHashCode() по-умолчанию используют Reflection, что

значительно влияет на производительность. Для своих объектов следует переопределять и

оптимизировать эти методы.

Следует оптимизировать код методов для встраивания (inline). Нельзя точно сказать,

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

способствуют следующие факторы: метод должен быть не более 16 байт IL кода, без

условий, локальных переменных, обработки исключений, без 32 битных параметров или

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

Начиная с CF 2.0 локальные переменные и аргументы длиной 32 бита хранятся в

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

преобразований.

Сборка мусора приводит к затратам. Следует по возможности создавать меньше

управляемых объектов, использовать StringBuilder при составлении больших строк,

типизированные коллекции при хранении value типов, во избежание операций упаковки и

распаковки.

Для асинхронных операций следует использовать ThreadPool. Использование пула

потоков благоприятно сказывается на производительности при использовании большого

количества коротких асинхронных операций.

Для парсинга объектов DateTime лучше использовать метод DateTime.ParseExact во

избежание перебора всех возможных культур.

Использование итераторов (foreach) приводит к вызову Reflection. Лучше использовать

индексы (for).

Для коллекций лучше заранее указывать длину. Иначе при превышении длины по-

умолчанию будет создана увеличенная вдвое коллекция, в которую будут скопированы все

элементы.

Следует избегать операций упаковки/запаковки (при вызове неуправляемого кода,

использовании нетипизированных коллекций).

Для работы с XML следует использовать классы XMLReader и XmlWriter. Не следует

проверять документ на соответствие схеме, если только это не обязательно. Следует

создавать как можно более короткие документы – убирать лишние пробелы, использовать

40

Page 41: .Net teza ru

атрибуты вместо тегов. Метод Skip() работает быстрее чем Read(). Следует создавать

оптимизированные экземпляры XMLReader/XMLWriter при помощи фабрики, также следует

использовать XmlReaderSettings и XmlWriterSettings.

Следует избегать использования DataSet. При необходимости – использовать

типизированные. Вместо колонки типа DateTime лучше использовать Ticks.

При использовании базы данных до 300 килобайт лучше использовать XML. Более -

Sql Server Ce.Чтение данных из Sql Server Ce следует проводить при помощи. Объекты

SqlCeConnection, SqlCeCommand и DataReader-ы следует использовать совместно с

конструкцией using. Это гарантирует, что объекты будут закрыты и выгружены из памяти.

Скорость выборки из базы сильно возрастает при использовании индексов. Однако при

чрезмерном их использовании падает скорость вставки данных.

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

методах Form.Show() и Form.Load() никаких тяжелых операций. Если необходимо загрузить

данные – это лучше сделать асинхронно. Формы лучше загружать заранее и кэшировать. При

отрисовке, добавлении, перемещении визуальных элементов следует вызывать методы

SuspendLayout и ResumeLayout. Если в классе формы привязываются какие-либо события, их

обязательно следует отвязать в методе Dispose, иначе при закрытии форма может не

выгружаться из памяти. При выполнении длительных операций следует информировать

пользователя о прогрессе.

41

Page 42: .Net teza ru

3 РЕЗУЛЬТАТЫ ИССЛЕДОВАНИЯ

Для подтверждения теоретических данных, а также эмпирических данных из

различных источников было принято решение провести рефакторинг действующего

мобильного приложения. В ходе исследования приложение было модифицировано в

соответствие с описанными выше рекомендациями. Были проведены различные тесты

производительности нового и старого приложения.

3.1 ОПИСАНИЕ ПРИЛОЖЕНИЯ И ПОСТАВЛЕННЫЕ ЦЕЛИ

В качестве практической части данной работы был выполнен рефакторинг приложения

«Мобильный Агент» компании ITproLab SRL.

Рисунок 3.1. Главное окно программы "Мобильный агент".

«Мобильный агент» представляет собой приложение для сбора заказов и

маркетинговой информации. Приложение включает серверную часть и мобильную. Сервер

включает в себя веб-сервис для синхронизации базы товаров и заказов с агентом, сервис

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

на .NET Compact Framework 3.5 и работает с базой данных Microsoft SQL Server Compact 3.5.

42

Page 43: .Net teza ru

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

Приложение включает 17 форм: форма заказа товаров и соответствующий журнал заказов,

форма и журнал заказов оборудования, форма и журнал заказов услуг, форма и журнал

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

справочник продукции, оборудования, клиентов, форма отчета по продажам. Наиболее

тяжеловесными являются формы заказов, так как на них отображаются наибольшее

количество данных – список заказчиков, адресов, товаров, предыдущих заказов и другое.

Рисунок 3.2. Форма заказа.

Изначально приложение написано без поддержки многопоточности. Все данные

подгружались непосредственно в методе Load. Каждая форма представляла самостоятельный

класс. Запросы к базе данных формировались в объектах бизнес-логики. Постольку

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

программистами, код был написан неоднородно, часто повторялся, был сложен для

понимания и модификации.

Для оптимизации скорости работы приложения было принято решение провести

полный рефакторинг, с изменением архитектуры, разделением на слои, включением

многопоточности. Была поставлена цель ускорить работу программы в полтора – два раза.

Для ускорения работы были запланированы следующие изменения:

создание промежуточного программного слоя для работы с данными(DAO),

43

Page 44: .Net teza ru

создание суперкласса для работы с базой данных, и вынесение в него всего

дублирующегося в CRUD объектах кода,

замена исторически используемых для отображения данных объектов DataTable

и DataSet на типизированные коллекции,

применение асинхронных операций для загрузки данных,

разгрузка методов Load для визуального ускорения загрузки форм,

кэширование форм заказов,

создание сервисов настроек, работающих с конфигурационным файлом и базой

данных,

создание сервиса обработки и журналирования ошибок.

3.2 ОПИСАНИЕ ИНСТРУМЕНТОВ РАЗРАБОТКИ И ОЦЕНКИ ПРОИЗВОДИТЕЛЬНОСТИ

Разработка приложения велась в Microsoft Visual Studio 2008 с подключенным

плагином Jetbrains Resharper 5. Среда разработки Visual Studio позволяет отлаживать

программу как на эмуляторе, так и на реальном устройстве. Jetbrains Resharper представляет

собой практически незаменимый инструмент поддержания чистоты кода и рефакторинга,

автоматизирующий рутинные операции и гарантирующий безопасность изменений.

Для оценки производительности были использованы как встроенные счетчики, так и

собственное приложение, замеряющее скорость загрузки форм.

Для отслеживания счетчиков производительности использовалась утилита из

пакета .Net Compact Framework Power Toys – Remote performance monitor (рис. 3.3).

Встроенные счетчики включают характеристики работы сборщика мусора, информацию о

количестве возникших исключительных ситуаций, информацию о количестве

скомпилированных классов и методов, количество обращений к внешним библиотекам,

информацию о потоках и блокировках, статистику использования памяти, информацию о

сетевой активности и данные о визуальных компонентах. Подробное описание этих

счетчиков имеется в описанных выше источниках[6,18,20]. Наибольший интерес

44

Page 45: .Net teza ru

представляет информация об использовании памяти и работе сборщика мусора. Как было

сказано выше, сборка мусора – дорогостоящая операция, поэтому, если в программе

создается большое количество объектов с коротким сроком жизни, например строк (String)

или кистей (Brush), это отрицательно сказывается на производительности. Об этих

проблемах говорят счетчики «Garbage Collections», «Managed Objects Allocated» и «Managed

Bytes In Use After GC». Параметр «Methods Pitched» говорит о том, что методов слишком

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

прекомпилированный код из кэша.

Рисунок 3.3. Окно программы Remote performance monitor

Счетчики производительности полезно отслеживать на продолжении всего процесса

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

работы программы и потребляемые ею ресурсы. Кроме указанной утилиты счетчики можно

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

ключ «HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETCompactFramework\

PerfMonitor», а в нем указать значение Counters типа DWORD, равное единице. В этом

45

Page 46: .Net teza ru

случае при запуске и завершении любого управляемого приложения в корне файловой

системы будет создаваться файл stat, содержащий значения всех счетчиков. Данные можно

просмотреть в простом текстовом редакторе или импортировать в редактор электронных

таблиц (Excel).

Другой крайне полезной утилитой из пакета .Net Compact Framework Power Toys

является CLR Profiler. Данное приложение позволяет отслеживать всю историю

программных вызовов, выделения памяти, запущенных потоков и другое. Отслеживание

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

отличное представление о том, как программа использует память, нет ли в ней утечек,

нерационально написанного кода. По полученным данным CLR Profiler позволяет строить

различные графики, такие как временная диаграмма вызовов, созданных объектов, сводная

диаграмма созданных объектов и занимаемой ими памяти, время жизни объектов.

Рисунок 3.4. Окно программы CLR Profiler

Но все это косвенные показатели. Для определения качественных улучшений в

скорости работы приложения было написано простое приложение, открывающее формы

старого и нового проекта, и засекающее скорость загрузки (рис 3.5). Приложению передается

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

файлы. Авторы статьи «Developing Well Performing .NET Compact Framework

Applications»[6] предлагают использовать функции QueryPerformanceCounter и

46

Page 47: .Net teza ru

QueryPerformanceFrequency для более точного определения времени. Однако для задачи

измерения загрузки формы вполне хватает точности встроенного в Compact Framework

метода Environment.TickCount, возвращающего количество тактов, прошедших с момента

запуска системы.

Рисунок 3.5. Окно программы Mobile Agent Speed Test

Описание выполненных модификаций и результатов тестирования будут описаны в

следующих параграфах.

3.3 РЕФАКТОРИНГ НА ПРИМЕРЕ РЕАЛЬНОГО КОДА

3.3.1 РАЗДЕЛЕНИЕ НА СЛОИ

Трехуровневая архитектура Interface – Business Layer – Data Layer хорошо

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

Агент» не планировалось делать тонким клиентом, была выбрана именно эта архитектура,

так как она ко всему прочему позволяет эффективно удалить дублирующийся код, применив

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

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

данными каждому классу бизнес логики был поставлен в соответствие класс DAO.

Так для класса Entity был создан GenericDAO, а для класса Product – ProductDAO.

47

Page 48: .Net teza ru

Рисунок 3.6. Упрощенная диаграмма классов до проекта до изменений

Все методы в объектах бизнес логики, отвечающие за работу с базой данных, были

перенесены в соответствующие объекты DAO при помощи рефакторинга Move Class[16].

Методы создания, удаления, сохранения, обновления объектов были переименованы

для соответствия общему стилю (CRUD) при помощи Rename Method. После этого сигнатура

методов была изменена с тем, чтобы принимать в качестве параметра объект бизнес логики и

возвращать идентификатор объекта в базе.

Для базовых операций были созданы соответствующие виртуальные методы в

GenericDAO, а все объекты данных были унаследованы от него (Extract Superclass, Pull Up

Method). Для заполнения объектов бизнес логики данными из DataReader при чтении из

базы, для каждого объекта бизнес логики был создан внутренний класс Processor в

результате чего объекты пришли к следующему виду:

using System.Data;using MobileAgent.API;using MobileAgent.DAO;

namespace MobileAgent.Domain {[TableName("ProductsCategories")][DefaultSort("Name")][SerializedFields("Name")][EntityProcessor(typeof (ProductsCategoryProcessor))]public class ProductsCategory : Entity {

public ProductsCategory(): base("ProductsCategories") {}

48

Page 49: .Net teza ru

public string Name { get; set; }

#region Nested type: ProductsCategoryProcessor

public sealed class ProductsCategoryProcessor : IEntityProcessor<ProductsCategory> {

public ProductsCategory Create() {return new ProductsCategory();

}

public void UpdateFromDataReader(ProductsCategory entity,IDataReader reader) {

if (reader.IsClosed) {return;

}entity.Id = DB.RSFieldInt(reader, "Id");entity.Name = DB.RSField(reader, "Name");

}

public object GetFieldValue(ProductsCategory entity, string fieldName) {

switch (fieldName) {case "Id":

return entity.Id;

case "Name":return entity.Name;

default:return null;

}}

}

#endregion}

}

Большинство объектов DAO стали имплементировать только специфические для

объекта методы. Основные операции стали выполняться родительским классом.

using MobileAgent.Domain;namespace MobileAgent.DAO {public class ProductsCategoryDAO : GenericDAO<ProductsCategory>, ProductsCategoryDAO {}}

Для обеспечения безопасности операций с данными и отслеживаемости ошибок

дублирующийся код, отвечающий за установление соединения с базой и проведения

транзакций был вынесен в отдельный метод (Extract Method) и перенесен в утилитарный

класс DB, содержащий необходимые для установления соединения данные. Таким образом,

метод GenericDAO.Delete и DB.ExecuteCommand приобрели следующий вид:

49

Page 50: .Net teza ru

public virtual int Delete(T entity) {var query = new StringBuilder();query.Append("delete from ");query.Append(TableName);query.Append(" where id = ");query.Append(entity.Id);return DB.ExecuteCommand(cmd =>{

cmd.CommandText = query.ToString();return cmd.ExecuteNonQuery();

});}

public delegate TE CommandExecutor<TE>(SqlCeCommand command);public static TE ExecuteCommand<TE>(CommandExecutor<TE> executor) {

TE result;using (var connection = new SqlCeConnection(ConnectionString)) {

connection.Open();using (var transaction =

connection.BeginTransaction(IsolationLevel.Unspecified)) {using (var command = connection.CreateCommand()) {

command.Transaction = transaction;try {

result = executor(command);transaction.Commit();

} catch (Exception ex) {if (transaction != null) {

try {transaction.Rollback();

} catch (Exception) {ErrorLogService.Log(ex);

}}throw new Exception("Ошибка выполнения команды

обращения к базе: " + ex.Message, ex);}

}}

}return result;

}

Все обращения к базе проходят в одном из двух параметризированных методов класса

DB: ExecuteCommand или ExecuteInConnection. Первый из них принимает в качестве

аргумента делегат с аргументом типа SqlCeCommand, а второй – SqlCeConnection. Логика

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

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

независимым слоем бизнес логики и слоем доступа к данным.

50

Page 51: .Net teza ru

Рисунок 3.7. Упрощенная диаграмма классов после изменений

Такая архитектура позволила минимизировать пути обращения к базе, и, как следствие,

перехватывать и журналировать все ошибки. Большую часть ответственности за работу с

базой данных взял на себя класс GenericDAO, вспомогательные статические методы для

работы с базой были вынесены в класс DB.

51

Page 52: .Net teza ru

3.3.2 КЕШИРОВАНИЕ СЕРВИСОВ И ФОРМ

В ходе тестирования и отладки было замечено, что большая часть времени при

открытии форм заказов тратиться на то, чтобы загрузить не меняющиеся в процессе работы

данные: список клиентов, список адресов, список категорий товаров и т.п. Поэтому было

принято решение ускорить процесс отображения форм путем их кэширования. Для этих

целей был выбран шаблон проектирования Singleton, а в качестве метода реализации –

рефакторинг Limit Instantiation with Singleton[17].

Для обобщения интерфейсов и упрощения последующего тестирования был написан

абстрактный родительский класс SingletonForm:

using System.Windows.Forms;

namespace MobileAgent.UI { public abstract class SingletonForm<T> : Form where T : SingletonForm<T> { private static T _instance;

public static T Instance { get { return _instance; } protected set { _instance = value; } } }}

После проведения рефакторинга вызовы метода ShowDialog были заменены на вызовы

соответствующего метода свойства Instance:

private void orderIconButton_JustClicked(object sender, EventArgs e) {try {

OrderForm.Instance.ShowDialog();} catch (Exception ex) {

Application.DoEvents();OrderForm.Instance = null;ErrorLogService.Handle(ex);

}}

При создании экземпляра форма подгружает все необходимые данные. При закрытии

данные, касающиеся отдельного заказа, очищаются, а данные общего назначения остаются в

памяти и при следующем открытии формы не показываются. Для открытия формы на

редактирование используется поле Order:

public partial class OrderForm : SingletonForm<OrderForm>{ ...

public Order Order {

52

Page 53: .Net teza ru

get {return _order ?? (_order = new Order());

}set {

_order = value;}

}private static OrderForm _instance;private static readonly Object Mutex = new Object();

public static OrderForm Instance {get {

lock (Mutex)return _instance ?? (_instance = new OrderForm());

}set {

_instance = value;}//used to kill form when updates are downloaded from server

}

private OrderForm() { InitializeComponent();

InitData();}

private void OrderForm_Closing(object sender, CancelEventArgs e) { if (DoNotSaveData) { return; }

if (formClosing) {return;

}formClosing = true;

... Clear();

}

private void OrderForm_Load(object sender, EventArgs e) {formClosing = false;ControlBox = true;LoadData();

}

private void InitData() {var mutex = CursorUtil.SetWaitCursor();...Order = new Order();CursorUtil.ResetWaitCursor(mutex);

}

public void Clear() {//clear order-related data...Order = null;

}

}

Результатом рефакторинга стала более значительно быстрая загрузка форм. А

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

кода. Шаблон Singleton гарантирует что в памяти всегда будет загружено не более одного

экземпляра формы, вне зависимости от того, из какого места кода производится доступ.

53

Page 54: .Net teza ru

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

сохраненный на рредактирование.

Кроме кэширования форм шаблон Singleton был применен для сервиса для работы с

конфигурационным файлом и сервиса для работы с серверными настройками программы.

Для большего абстрагирования от базы данных обращение ко всем DAO объектам

осуществляется по интерфейсу, а хранением и сопоставлением интерфейсов и реализаций

заведует статический класс DAOFactory:

using System;using System.Collections.Generic;

namespace MobileAgent.DAO {public static class DAOFactory {

private static readonly IDictionary<Type, object> Daos = new Dictionary<Type, object>();

static DAOFactory() {Load();

}

public static void Load() {Daos.Add(typeof(IProductDAO), new ProductDAO());Daos.Add(typeof(IAppConfigDAO), new AppConfigDAO());…Daos.Add(typeof(IErrorLogDAO), new ErrorLogDAO());

}

public static T GetDAO<T>() where T : class {return Daos[typeof (T)] as T;

}}

}

Экземпляр статического класса, как и экземпляр Singleton создать нельзя, он создается CLR

перед первым обращением к классу в программе. С помощью такой нехитрой, но

эффективной манипуляции осуществляется кэширование DAO объектов. Для

имплементации шаблона был использован рефакторинг Move Creation Knowledge to

Factory[17].

54

Page 55: .Net teza ru

3.3.3 ОПТИМИЗАЦИЯ ИСПОЛЬЗОВАНИЯ ТИПОВ ДАННЫХ

В приложении по историческим причинам повсеместно использовались для хранения

получаемых из базы данных объекты типа DataSet и DataTable.

public DataTable GetAll() { DataManager.Instance.OpenConnection(); SqlCeDataAdapter adapter = new SqlCeDataAdapter("SELECT Products.* FROM Products ORDER BY Products.Sequence, Products.Code", DataManager.Instance.Connection); DataTable result = GetDataTable(adapter); DataManager.Instance.CloseConnection(); return result;}

Из-за внутренней реализации, наличия огромного количества событий, срабатывающих

при изменении состояния объекта, данные типы слишком неповоротливы для простого

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

решено заменить на более легкие реализации ICollection<T>.

public virtual ICollection<T> GetAll() {var query = new StringBuilder();query.Append("select * from ");query.Append(TableName);if (defaultSortFields.Length > 0) {

query.Append(" order by ");query.Append(string.Join(",", defaultSortFields));

}

return DB.ExecuteCommand(cmd =>{

var result = new List<T>();cmd.CommandText = query.ToString();using (IDataReader reader = cmd.ExecuteReader()) {

while (reader.Read()) {T entity = entityProcessor.Create();entityProcessor.UpdateFromDataReader(entity, reader);result.Add(entity);

}}return result;

});}

Для отображения в сводных таблицах произвольных данных были использованы

анонимные объекты:

public ICollection<object> GetReport() {return DB.ExecuteCommand(cmd=> {

ICollection<object> result = new List<object>();const string sqlQuery =

@"SELECT PotentialPenetrations.*, PotentialDeliveryAddresses.Name AS Name, PotentialDeliveryAddresses.Address AS Address

55

Page 56: .Net teza ru

FROM PotentialPenetrations LEFT JOIN PotentialDeliveryAddresses ON PotentialPenetrations.PotentialDeliveryAddressesId =

PotentialDeliveryAddresses.Id ORDER BY PotentialPenetrations.Date";

cmd.CommandText = sqlQuery;using(IDataReader dataReader = cmd.ExecuteReader()) {

while (dataReader.Read()) {result.Add(new{

Id = DB.RSFieldInt(dataReader, "Id"),Date = DB.RSFieldDateTime(dataReader, "Date"),Time = DB.RSFieldDateTime(dataReader, "Time"),PotentialDeliveryAddressesId =

DB.RSFieldInt(dataReader, "PotentialDeliveryAddressesId"),Name = DB.RSField(dataReader, "Name"),Address = DB.RSField(dataReader, "Address")

});}

}

return result; });

}

Для повышения уровня абстракции и облегчения понимания кода данные о названии

таблицы в базе данных, столбца по-умолчанию для сортировки, типе EntityProcessor-а и

список сохраняемых в базу полей были вынесены в атрибуты.

[TableName("Products")][DefaultSort("Sequence", "Code")][SerializedFields("Code", "Name", "ProductCategoriesId", "LittersVolume",

"QuantityPerPackage", "StandartPrice", "ActionPrice", "BasePrice", "IsReturn", "Qty", "Sequence")]

[EntityProcessor(typeof(ProductProcessor))]public class Product : Entity {

}

public class GenericDAO<T> : IGenericDAO<T> where T : class, IEntity {private readonly string[] defaultSortFields;private readonly string[] serializedFields;private readonly IEntityProcessor<T> entityProcessor;protected readonly string TableName;

public GenericDAO() {Type entityType = typeof (T);

var tnattr = (TableNameAttribute[]) entityType.GetCustomAttributes(typeof (TableNameAttribute), false);

TableName = tnattr.Length > 0 ? tnattr[0].Name : entityType.Name;

var dsattr = (DefaultSortAttribute[]) entityType.GetCustomAttributes(typeof (DefaultSortAttribute), false);

56

Page 57: .Net teza ru

defaultSortFields = dsattr.Length > 0 ? dsattr[0].Fields : new string[] {};

var sfattr = (SerializedFieldsAttribute[])entityType.GetCustomAttributes(typeof(SerializedFieldsAttribute), false);

serializedFields = sfattr.Length > 0 ? sfattr[0].Fields : new string[] { };

var epattr = (EntityProcessorAttribute[]) entityType.GetCustomAttributes(typeof (EntityProcessorAttribute), false);

if (epattr.Length > 0) {entityProcessor =

Activator.CreateInstance(epattr[0].ProcessorType) as IEntityProcessor<T>;}else {

throw new ArgumentException("Сущность не имеет присоединенного обработчика.");

}}

}

Такая реализация не является лучшей с точки зрения производительности, так как для

чтения атрибутов используется Reflection. Однако это значительно упрощает разработку и

позволяет конструировать новые сущности за считанные минуты.

3.3.4 ПОВЫШЕНИЕ ОТЗЫВЧИВОСТИ ПРИЛОЖЕНИЯ ЗА СЧЕТ МНОГОПОТОЧНОСТИ

Главной проблемой скорости загрузки форм являются нагруженные методы Load и

Show. Кэширование форм значительно ускорило их загрузку, но асинхронная загрузка

данных вывела производительность на новый уровень.

public partial class OrdersForm : Form {private readonly IOrderDAO orderDAO = DAOFactory.GetDAO<IOrderDAO>();

private bool formClosing;

public OrdersForm() { InitializeComponent();

LoadOrders();…

}private void LoadOrders() {

ThreadPool.QueueUserWorkItem(LoadOrders);}private void LoadOrders(object stateInfo) {

var source = orderDAO.GetOrdersReport();if (ordersDataGrid != null && !formClosing) { ordersDataGrid.SafeInvoke(new

BindDataGridDelegate(BindingUtil.BindDataGrid), ordersDataGrid, source);

57

Page 58: .Net teza ru

}} … }

Как видно из примера, для запуска асинхронной операции используется пул потоков.

Асинхронная загрузка данных была имплементирована повсеместно, полностью заменив при

помощи рефакторинга Extract Method[16] первоначальные последовательные методы

загрузки данных.

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

AutoResetEvent.

private void LoadProductCategories() {ThreadPool.QueueUserWorkItem(LoadProductCategories);

}

private void LoadProductCategories(object stateInfo) { if (productsCategoryComboBox != null) { var result =productsCategoryComboBox.SafeInvoke( new BindComboSetDisplayMemberAndValueMemberAndWaitHandleDelegate(BindingUtil.BindCombo), productsCategoryComboBox, productsCategoryDAO.GetAll(), "Name", "Id", productCategoriesWaitHandle); }

}

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

BindingUtil, содержащий статические методы и соответствующие им делегаты.

public delegate void

BindComboSetDisplayMemberAndValueMemberAndWaitHandleDelegate(ComboBox comboBox,

object dataSource, string displayMember, string valueMember, AutoResetEvent

waitHandle);

public static void BindCombo(ComboBox comboBox, object dataSource, string displayMember, string valueMember, AutoResetEvent waitHandle) {

if (comboBox == null || comboBox.IsDisposed) {return;

}var mutex = CursorUtil.SetWaitCursor();comboBox.DisplayMember = displayMember;comboBox.ValueMember = valueMember;comboBox.DataSource = dataSource;if (comboBox.Items.Count > 0) {

comboBox.SelectedIndex = 0;comboBox.Enabled = true;

}if (waitHandle!= null && !waitHandle.Handle.Equals((IntPtr)(-1))) {

waitHandle.Set();}

58

Page 59: .Net teza ru

CursorUtil.ResetWaitCursor(mutex);}

Для безопасного обращения к визуальным компонентам из параллельного потока вызывается

метод-декоратор SafeInvoke:

private delegate object SafeInvokeCallback(Control control, Delegate method, params object[] parameters);public static object SafeInvoke(this Control control, Delegate method, params object[] parameters) { if (control == null) throw new ArgumentNullException("control"); if (control.InvokeRequired) { IAsyncResult result = null; try { result = control.BeginInvoke(new SafeInvokeCallback(SafeInvoke), control, method, parameters); } catch (InvalidOperationException) { } if (result != null) return control.EndInvoke(result); } else { if (!control.IsDisposed) return method.DynamicInvoke(parameters); } return null;}public static object DynamicInvoke(this Delegate dlg, params object[] args) { return dlg.Method.Invoke(dlg.Target, BindingFlags.Default, null, args, null);}

Этот код также отрицательно сказался на производительности из-за использования

Reflection для динамического вызова метода. Однако использование этой проверки без

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

Операции асинхронной загрузки данных используются довольно часто, например, для

загрузки товаров при смене категории:

private void LoadProductsAsync(object stateInfo) { if(!this.WaitAndClose(ref productCategoriesWaitHandle)) { return; } if ((int)stateInfo <= 0) { if(!formClosing) {

this.SafeInvoke(new LoadProductsDelegate(LoadProducts)); }

}else {int productCategoriesId = (int) stateInfo; …productsFilteredSource = productDAO.GetByCategory(productCategoriesId); …

if (productsDDDataGrid != null && !formClosing) { productsDDDataGrid.SafeInvoke(new BindDDDataGridDelegate(BindingUtil.BindDDDataGrid), productsDDDataGrid, productsFilteredSource); }

}}

59

Page 60: .Net teza ru

Как видно из примера, загрузка данных останавливается, если поток с зависимыми

данными был остановлен, и состояние объекта AutoResetEvent не было установлено в Set.

3.4 СРАВНЕНИЕ ХАРАКТЕРИСТИК ПРИЛОЖЕНИЯ ДО И ПОСЛЕ РЕФАКТОРИНГА

Для сравнения качественных характеристик производительности были использованы

утилиты CLR Profiler, Remote Performance Monitor и специально написанная программа для

тестирования скорости загрузки форм. Полученные результаты представлены ниже.

3.4.1 АНАЛИЗ ПОКАЗАНИЙ ВСТРОЕННЫХ СЧЕТЧИКОВ ПРОИЗВОДИТЕЛЬНОСТИ

Полученные в ходе исследования показания счетчиков производительности

отображены в следующей таблице.

Таблица 3.2. Сводная таблица счетчиков производительности

Название счетчика Старое значение

Новое значение

Total Program Run Time (ms) 238321 499915App Domains Created 1 1App Domains Unloaded 0 0Assemblies Loaded 10 11Classes Loaded 2267 2888Methods Loaded 9502 11115Closed Types Loaded 212 603Open Types Loaded 15 16Closed Methods Loaded 13 110Open Methods Loaded 0 0Threads in Thread Pool 2 2Pending Timers 0 0Scheduled Timers 523 819Timers Delayed by Thread Pool Limit 0 0Work Items Queued 51 101Uncontested Monitor.Enter Calls 13725 19108Contested Monitor.Enter Calls 75 73Peak Bytes Allocated (native + managed) 6544840 7737312Managed Objects Allocated 412718 404329Managed Bytes Allocated 56838212 53716224Managed String Objects Allocated 156977 154838Bytes of String Objects Allocated 6721532 6305634Garbage Collections (GC) 54 52Bytes Collected By GC 56570648 53181488

60

Page 61: .Net teza ru

Managed Bytes In Use After GC 624388 1363152Total Bytes In Use After GC 5585792 6854792GC Compactions 11 10Code Pitchings 0 0

Таблица 3-1. Продолжение

Calls to GC.Collect 0 0GC Latency Time (ms) 1507 2771Pinned Objects 185 177Objects Moved by Compactor 48920 42463Objects Not Moved by Compactor 31952 149173Objects Finalized 6493 5631Objects on Finalizer Queue 0 0Boxed Value Types 60271 37963Process Heap 161904 240624Short Term Heap 0 0JIT Heap 2416312 2648608App Domain Heap 1232904 1403816GC Heap 1986560 2560000Native Bytes Jitted 2346828 2571336Methods Jitted 5279 6164Bytes Pitched 0 0Methods Pitched 0 0Method Pitch Latency Time (ms) 0 0Exceptions Thrown 26 25Platform Invoke Calls 32590 27978COM Calls Using a vtable 0 0COM Calls Using IDispatch 0 0Complex Marshaling 477 357Runtime Callable Wrappers 0 0Socket Bytes Sent 40122 42079Socket Bytes Received 54447 53619Controls Created 493 472Brushes Created 140 113Pens Created 5 10Bitmaps Created 96 125Regions Created 399 363Fonts Created 19 7Graphics Created (FromImage) 0 0Graphics Created (CreateGraphics) 78 79

Как видно из приведенной таблицы в конечном проекте загружается на 27% больше

классов (значение Classes Loaded), на 17% больше методов (значение Methods Loaded). Это

связано с образованием нового слоя данных.

61

Page 62: .Net teza ru

В новом проекте гораздо более активно используются параметризированные методы и

типы (Generics), что отображается на показателях Closed Types Loaded, Open Types Loaded,

Closed Methods Loaded, Open Methods Loaded.

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

(Work Items Queued), на 40% больше неконкурентных вызовов Monitor.Enter (Uncontested

Monitor.Enter Calls) и при этом на 3% меньше блокируемых вызовоз, что говорит о

повышении потокобезопасности приложения. В целом блокируемые вызовы составляют

менее процента от блокируемых, что говорит о нормальной ситуации.

Значение параметра Peak Bytes Allocated (native + managed) на 20 процентов больше.

Загрузка native памяти увеличилась из-за того что формы заказов не выгружаются. При этом

потребление managed памяти уменьшилось благодаря оптимизации типов данных. Также

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

количество сборок уменьшилось на 10%.

Благодаря оптимизации типов данных в новом приложении происходит на 37% меньше

упаковок объектов (Boxed Value Types).

Несмотря на увеличение количества классов и методов кэш компилятора (JIT Heap)

увеличился всего на 9.6%. При этом ни одного метода не было выгружено из кэша (Methods

Pitched) .

Как показывают счетчики Platform Invoke Calls и Complex Marshaling, взаимодействие с

WinAPI заметно уменьшилось благодаря кэшированию форм и, соответственно,

уменьшению количества создаваемых окон.

В целом счетчики показали ожидаемые результаты. Хотя вследствие рефакторинга

архитектура приложения усложнилась, появились новые классы и методы, это не привело к

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

количество единовременно занимаемой нативной памяти, но при этом уменьшилось

взаимодействие с платформой, что благоприятно сказалось на производительности.

Благодаря оптимизации типов данных сократилось количество выделяемой управляемой

памяти, уменьшилось количество сборок мусора, количество упаковок объектов, что также

положительно отразилось на скорости работы программы. Более конкретные результаты

тестирования скорости интерфейса приведены в следующем параграфе.

62

Page 63: .Net teza ru

3.4.2 АНАЛИЗ РЕЗУЛЬТАТОВ ТЕСТИРОВАНИЯ СКОРОСТИ ЗАГРУЗКИ ФОРМ

Для определения скорости загрузки форм была написана программа, поочередно

загружающая формы из списка указанное количество раз. Для упрощения теста и приведения

форм к единому интерфейсу пришлось временно отказаться от шаблона Singleton и открыть

конструктор. Тестовая утилита способна засекать время, за которое форма откроется и

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

возможностью кэшировать форму в случае многоразового открытия.

Текст основного класса использованной утилиты приведен в приложении 1, а также на

прилагаемом диске. На диске также находятся полные данные тестирования в различных

режимах для случаев, когда форма загружается 1 раз, 10, 100 раз. Далее будут приведены

графики, отображающие полученные данные для старого и модифицированного

приложения, в режимах с сохранением данных и без сохранения.

На диаграммах отражается среднее время загрузки форм в секундах для указанного

режима и количества циклов.

Рисунок 3.8. Сравнение скорости загрузки. 1 цикл, без сохранения

63

Page 64: .Net teza ru

Как видно из первой диаграммы, в самом простом случае интерфейс

модифицированной программы более чем в два с половиной раза быстрее, причем лучше

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

Рисунок 3.9.Сравнение скорости загрузки. 1 цикл, с сохранением

В случае с сохранением ситуация не сильно меняется. Новое приложение быстрее в 2

раза. Разница сократилась, так как операция сохранения отнимает в обоих случаях примерно

одинаковое время.

Рисунок 3.10. Сравнение скорости загрузки. 10 циклов, без сохранения

64

Page 65: .Net teza ru

В случае 10 циклов виден результат применения кэширования. Новые кэшированные

формы по сравнению со старыми открываются практически мгновенно. Разница в средних

значениях более чем на порядок.

Рисунок 3.11. Сравнение скорости загрузки. 10 циклов, с сохранением

В случае с сохранением данных отслеживается та же тенденция. Средняя скорость

нового приложения больше почти на порядок. Разница сглаживается из-за одинаково

длительных операций сохранения.

Рисунок 3.12. Сравнение скорости загрузки. 100 циклов, без сохранения

65

Page 66: .Net teza ru

Как видно из последнего сравнения, эффект от произведенных изменений особенно

заметен при длительном использовании.

Рисунок 3.13. Сравнение скорости загрузки. 100 циклов, с сохранением

Случай с сохранением не меняет общей картины. Разница прямо пропорциональна

количеству циклов.

Проведенное тестирование показало, что внесенные изменения дали огромный рост

производительности. Увеличение числа классов вследствие проведенного рефакторинга

сделало приложение более понятным, настраиваемым и легким в отладке. При этом не было

нанесено вреда производительности. Применение на практике рассмотренных выше

рекомендаций дало желаемый эффект – увеличение скорости более чем в 2 раза для случая с

одним циклом. Применение же кэширования форм дает гораздо больший результат при

длительном использовании приложения.

66

Page 67: .Net teza ru

67

Page 68: .Net teza ru

ЗАКЛЮЧЕНИЕ

В данной работе были рассмотрены аспекты разработки и регулярного повышения

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

производительности. Рефакторинг рассматривается как инструмент улучшения понятности

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

В работе был произведен анализ теоретических и практических изысканий в области

рефакторинга и программирования приложений для платформы .NET Compact Framework.

Исследования показали, что рефакторинг как методология, несмотря на свою сравнительную

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

Рефакторинг завоевал умы объектно-ориентированных программистов и продолжает

распространяться в других областях, таких как разработка, ориентированная на модели,

программирование автоматов, программирование баз данных. Вопросы производительности

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

аппаратных мощностей. Хотя производительность указывается многими авторами как

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

всего цикла разработки, конкретные рекомендации по ее повышению разрознены и в

основном находятся на различных специализированных интернет ресурсах. Желание

объединить различные практики и неочевидные, недокументированные приемы по

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

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

Описанные в работе теоретические основы рефакторинга и рекомендации по

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

качества приложения для сбора заказов «Мобильный Агент», разрабатываемого

предприятием ITproLab S.R.L. Примененные модификации ускорили работу приложения

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

формы практически мгновенно. Улучшение производительности было доказано в

проведенных тестах при помощи специализированного программного обеспечения, а также

на практике было высоко оценено клиентами компании ITproLab S.R.L.

Проведенное исследование показало, что рефакторинг является эффективным

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

количество дублирующегося кода, и, как следствие, упростить отладку, поддержку,

настраиваемость приложения. Сфера применения рефакторинга не ограничивается

68

Page 69: .Net teza ru

разработкой объектно-ориентированных настольных и серверных решений, но охватывает

все области разработки программного обеспечения. При применении методов рефакторинга

в разработке приложений на платформе .NET Compact Framework следует учитывать

архитектуру платформы, особенности реализации мобильной версии CLR, GC и JIT.

Системы, в которых требования к скорости определены абсолютно, имеют узкую сферу

применения. В большинстве же случаев применимо понятие «достаточной

производительности» - состояния системы, в котором пользователь оценивает ее как

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

находиться при помощи регулярно проводимого тестирования, анализа счетчиков

производительности и опроса пользователей.

69

Page 70: .Net teza ru

СПИСОК ЛИТЕРАТУРЫ

1. ANTHONY D., WITAWAS SRISA-AN, LEUNG M., An Empirical Study of the Code

Pitching Mechanism in the .NET Framework, Journal of Object Technology, vol. 5, no. 3,

April 2006, Special issue: .NET Technologies 2005 Conference, стр. 107–127, доступно

онлайн:

http://www.jot.fm/issues/issue_2006_04/article5/

2. BECK K., Extreme Programming Explained: Embrace Change, Addison-Wesley

Professional, 1999, 224 с.

3. BROWN W.J. и др., AntiPatterns: Refactoring Software, Architectures, and Projects in

Crisis, Wiley, 1998, 336 с.

4. DODANI M., Patterns of Anti-Patterns?, Journal of Object Technology, vol. 5, no. 6, July -

August 2006, стр. 29-33, доступно онлайн:

http://www.jot.fm/issues/issue_2006_07/column4

5. FOWLER M., Refactoring - Improving the Design of Existing Code, Addison Wesley,

Boston, 1999, 464 с.

6. FOX D. и др., Developing Well Performing .NET Compact Framework Applications,

Microsoft Corporation, 2003, 29с., доступно онлайн:

http://msdn.microsoft.com/en-us/library/aa446542.aspx

7. MOHAMED M. и др., Classification of model refactoring approaches, Journal of Object

Technology, vol. 8, no. 6, September-October 2009, стр. 143 – 158, доступно онлайн:

http://www.jot.fm/issues/issue_2009_09/article3/

8. O'CINNEIDE M., Automated Application of Design Patterns: A Refactoring Approach, PhD

Thesis, University of Dublin, Trinity College, 2000, 240с.

9. O'CINNEIDE M., NIXON P., Composite Refactorings for Java Programs, technical report,

Dept. of Computer Science, Univ. College Dublin, 2000, 6с.

10. OPDYKE W.F., Refactoring object-oriented frameworks, PhD Thesis, University of Illinois

at Urbana-Champaign, 1992, 206с.

70

Page 71: .Net teza ru

11. ROYCE W., Managing the development of large software systems, Proceedings, IEEE

WESCON, August 1970, стр. 1-9

12. TAN E., .NET Compact Framework 3.5 Data-Driven Applications, Packt Publishing, 2010,

484 с.

13. YANG B. и др., Professional Microsoft® Smartphone Programming, Wiley Publishing,

2007, 523с.

14. YAO P., Programming .NET Compact Framework 3.5, 2nd ed., Addison-Wesley

Professional, 2009, 744 с.

15. ВИГЛИ Э., МОТ Д., ФУТ П., Microsoft Mobile и .Net Compact Framework. Руководство

разработчика, Питер, 2009, 672 с.

16. Каталог рефакторингов, доступно онлайн:

http://www.refactoring.com/catalog/index.html

17. КЕРИЕВСКИ ДЖ., Рефакторинг с использованием шаблонов, пер. с англ., М.: ООО

И.Д. Вильямс, 2006, 400с.

18. КЛИМОВ А.П., Программирование КПК и смартфонов на .NET Compact Framework,

СПб.: Питер, 2007 – 320с., ил.

19. МАРТИН Р., Чистый код: создание, анализ и рефакторинг. Библиотека

программиста, пер. с англ., СПб.: Питер, 2010, 464с.

20. САЛМРЕ И., Программирование мобильных устройств на платформе .NET Compact

Framework, пер. с англ., М.: Издательский дом Вильямс, 2006, 736 с.

21. СТЕПАНОВ О. Г., Методы реализации автоматных объектно-ориентированных

программ, диссертация на соискание ученой степени кандидата технических наук,

СПбГУ ИТМО, 2009, 153с.

22. ФАРО С., Рефакторинг SQL приложений, пер. с англ., СПб: Символ Плюс, 2009, 336с.

71

Page 72: .Net teza ru

DECLARAŢIA DE ONESTITATE

Subsemnatul (a)__________________________________________________________________

masterand la Facultatea Calculatoare, Informatică şi Microelectronică a Universităţii Tehnice a

Moldovei, programul de master _____________________________________________________

_______________________________________________________________________________

Declar pe proprie răspundere că la conceperea tezei de master cu titlul

_______________________________________________________________________________

_______________________________________________________________________________

Sub conducerea ştiinţifică__________________________________________________________

_______________________________________________________________________________

Nu am folosit alte surse decât cele menţionate în bibliografie, lucrarea îmi aparţine în întregime şi

nu conţine plagiat.

Data______________

Nume şi prenume

____________________________

Semnătura

____________________________

72

Page 73: .Net teza ru

ПРИЛОЖЕНИЕ 1

ТЕКСТ ПРОГРАММЫ «MOBILE AGENT SPEED TEST»

using System;using System.Collections.Generic;using System.Text;using System.Threading;using System.Windows.Forms;using oldUI = EfesMobileAgent.UI;using newUI = MobileAgent.UI;

namespace efes.MobileAgent.SpeedTest { public partial class TestSpeedForm : Form { private int cycleCount;

public TestSpeedForm() {InitializeComponent();

cycleCount = (int)ccNumericUpDown.Value; //Start(); }

private void Start() { Cursor.Current = Cursors.WaitCursor; resultsTextBox.Text = ""; TestFormsLoadSpeed(); Cursor.Current = Cursors.Default; }

private void TestFormsLoadSpeed() { #region form types declaration IList<Type> oldForms = new List<Type>(); oldForms.Add(typeof(oldUI.OrderForm)); oldForms.Add(typeof(oldUI.DownloadForm)); oldForms.Add(typeof(oldUI.PotentialForm)); oldForms.Add(typeof(oldUI.PenetrationForm)); oldForms.Add(typeof(oldUI.AssetsOrderForm)); oldForms.Add(typeof(oldUI.AssetsOrdersForm)); oldForms.Add(typeof(oldUI.PenetrationsForm)); oldForms.Add(typeof(oldUI.PotentialsForm)); oldForms.Add(typeof(oldUI.OrdersForm)); oldForms.Add(typeof(oldUI.ProductsForm)); oldForms.Add(typeof(oldUI.AssetsForm)); oldForms.Add(typeof(oldUI.DeliveryAddressesForm)); oldForms.Add(typeof(oldUI.UploadForm)); oldForms.Add(typeof(oldUI.SalesReportForm)); oldForms.Add(typeof(oldUI.ServicesOrderForm)); oldForms.Add(typeof(oldUI.ServicesOrdersForm));

IList<Type> newForms = new List<Type>(); newForms.Add(typeof(newUI.OrderForm)); newForms.Add(typeof(newUI.DownloadForm)); newForms.Add(typeof(newUI.PotentialForm)); newForms.Add(typeof(newUI.PenetrationForm)); newForms.Add(typeof(newUI.AssetsOrderForm)); newForms.Add(typeof(newUI.AssetsOrdersForm)); newForms.Add(typeof(newUI.PenetrationsForm)); newForms.Add(typeof(newUI.PotentialsForm)); newForms.Add(typeof(newUI.OrdersForm)); newForms.Add(typeof(newUI.ProductsForm)); newForms.Add(typeof(newUI.AssetsForm));

73

Page 74: .Net teza ru

newForms.Add(typeof(newUI.DeliveryAddressesForm)); newForms.Add(typeof(newUI.UploadForm)); newForms.Add(typeof(newUI.SalesReportForm)); newForms.Add(typeof(newUI.ServicesOrderForm)); newForms.Add(typeof(newUI.ServicesOrdersForm)); #endregion

int startTime = Environment.TickCount;

TestFormsLoadSpeed(new[] { typeof(oldUI.OrderForm) }, "MAOldWarmUp.csv", 1, false, true, false);//разогрев сервисов WaitThreads(); TestFormsLoadSpeed(oldForms, "MAOld.csv", false, false, false);//нет параллельных потоков,,формы не кешируются WaitThreads(); TestFormsLoadSpeed(oldForms, "MAOldSaving.csv", false, true, false);//то же с сохранением WaitThreads();

TestFormsLoadSpeed(new[] { typeof(newUI.OrderForm) }, "MANewWarmUp.csv", 1, true, true, true);//разогрев сервисов WaitThreads(); TestFormsLoadSpeed(newForms, "MANew.csv", false, false, true);//потоки можно и не ждать, формы кешируются WaitThreads(); TestFormsLoadSpeed(newForms, "MANewSaving.csv", true, true, true);//потоки нужно пожождать чтобы сохранить данные, формы кешируются WaitThreads();

var timeSpent = new TimeSpan(0,0,0,0,Environment.TickCount - startTime); resultsTextBox.Text += string.Format("Total time spent: {0}:{1}:{2}.{3}", timeSpent.Hours, timeSpent.Minutes, timeSpent.Seconds, timeSpent.Milliseconds ); resultsTextBox.Refresh(); }

private void TestFormsLoadSpeed(IEnumerable<Type> formTypes, string fileName) { TestFormsLoadSpeed(formTypes, fileName, cycleCount, false, false, false); } private void TestFormsLoadSpeed(IEnumerable<Type> formTypes, string fileName, bool waitForLoad) { TestFormsLoadSpeed(formTypes, fileName, cycleCount, waitForLoad, false, false); } private void TestFormsLoadSpeed(IEnumerable<Type> formTypes, string fileName, bool waitForLoad, bool saveData) { TestFormsLoadSpeed(formTypes, fileName, cycleCount, waitForLoad, saveData, false); } private void TestFormsLoadSpeed(IEnumerable<Type> formTypes, string fileName, bool waitForLoad, bool saveData, bool cacheForms) { TestFormsLoadSpeed(formTypes, fileName, cycleCount, waitForLoad, saveData, cacheForms); } private void TestFormsLoadSpeed(IEnumerable<Type> formTypes, string fileName, int count, bool waitForLoad, bool saveData, bool cacheForms) { var sb = new StringBuilder("form name\t ccl cnt\t ttl time, s\t avg time, s\r\n");

74

Page 75: .Net teza ru

foreach (var formType in formTypes) { statusLabel.Text = string.Format("{0}", formType.Name); statusLabel.Refresh(); TimeSpan ts = TestFormLoadSpeed(formType, count, waitForLoad,saveData,cacheForms); sb.Append(String.Format("{0}\t {1}\t {2:0.###}\t {3:0.###}\r\n", formType.Name, count, ts.TotalMilliseconds / 1000, ts.TotalMilliseconds / count / 1000)); } sb.Append("\r\n"); resultsTextBox.Text += fileName + ":\r\n"; resultsTextBox.Text += sb.ToString(); resultsTextBox.Refresh();

#if PocketPC || WindowsCE || NETCF#if NETCF_1_0 string myDocs = @"\My Documents";#else string myDocs = Environment.GetFolderPath(Environment.SpecialFolder.Personal);#endif string path = System.IO.Path.Combine(myDocs, fileName); using (System.IO.TextWriter writer = new System.IO.StreamWriter(path)) { writer.Write(sb.ToString()); writer.Close(); }#endif

statusLabel.Text = "Done"; statusLabel.Refresh(); }

private static TimeSpan TestFormLoadSpeed(Type formType, int count, bool waitForLoad, bool saveData, bool cacheForms) { int startTick = Environment.TickCount; Form form = null;

try { for (int i = 0; i < count; i++) { if (!cacheForms || i==0) { form = Activator.CreateInstance(formType) as Form; } if (form == null) { return new TimeSpan(0, 0, 0, 0, -1); } form.Show(); if(waitForLoad) { WaitThreads(); } var silentOld = form as oldUI.ISilentClosing; if (silentOld != null) { silentOld.SilentClose = true; silentOld.DoNotSaveData = !saveData; } var silentNew = form as newUI.ISilentClosing; if (silentNew != null) { silentNew.SilentClose = true; silentNew.DoNotSaveData = !saveData; } form.Close(); }

75

Page 76: .Net teza ru

} catch (Exception ex) { return new TimeSpan(0, 0, 0, 0, -1); }finally { if(!form.IsDisposed) {form.Close();} }

return new TimeSpan(0, 0, 0, 0, Environment.TickCount - startTick); }

private static void WaitThreads() { Thread.CurrentThread.Priority = ThreadPriority.BelowNormal; Thread.Sleep(1); Application.DoEvents(); Thread.CurrentThread.Priority = ThreadPriority.Normal; }

private void startButton_Click(object sender, EventArgs e) { Cursor.Current = Cursors.WaitCursor; resultsTextBox.Text = ""; TestFormsLoadSpeed(); Cursor.Current = Cursors.Default; }

private void ccNumericUpDown_ValueChanged(object sender, EventArgs e) { cycleCount = (int)ccNumericUpDown.Value; }

}}

76

Page 77: .Net teza ru

ПРИЛОЖЕНИЕ 2

КОД ПРОГРАММЫ, РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ – CD-ROM

77

Page 78: .Net teza ru

UNIVERSITATEA TEHNICĂ A MOLDOVEI

FACULTATEA Calculatoare, Informatică şi Microelectronică

Catedra ___________________________________________

AVIZ

la teza de master

Program de master_________________________________________________________________

Tema ___________________________________________________________________________

________________________________________________________________________________

Masterandul(a)______________________________ gr._____ _____________________________

1. Actualitatea temei _______________________________________________________________

________________________________________________________________________________

2. Caracteristica tezei de master ______________________________________________________

________________________________________________________________________________

3. Conţinutul ştiinţific/practic ________________________________________________________

________________________________________________________________________________

4. Estimarea şi valoarea rezultatelor obţinute ____________________________________________

________________________________________________________________________________

5. Corectitudinea materialului expus __________________________________________________

________________________________________________________________________________

6. Valoarea teoretică şi practică a tezei ________________________________________________

________________________________________________________________________________

7. Observaţii şi recomandări _________________________________________________________

________________________________________________________________________________

8. Caracteristica masterandului şi titlul conferit __________________________________________

________________________________________________________________________________

Conducătorul

tezei de master ___________________________________________________________________

________________________________________________________________________________

(funcţia, titlul ştiinţific), (semnătura, data), (numele, prenumele)

78