After three days of refactoring the code of my team - I decide to send this epistle to my "Flex software engineer":
Небольшой flex-дайджест (для флекс-разработчиков к прочтению ОБЯЗАТЕЛЬНО и крайне желательно все это применять в работе)
0)
a) у нес нет PMD, у нас нет CheckStyle - поэтмоу иметь флексовые варнинги - НЕДОПУСТИМО
обязательно исправляйте ситуацию которая вызвала варнинг!
b) мы НЕ пользуемся табуляция в исходниках (в as и mxml)!
c) отступы делаются 4 пробелами (в as и mxml)!
для этого в настройках эклипса нужно везде в настройках редакторов установать испльзование пробелов вместо табуляций и размер отступа в 4 пробела
для облегчения жизни и автоматического устраниния пробелов нужно использовать (пока не найдена достойная альтернатива)
http://andrei.gmxhome.de/anyedit/index.html
1)НАПОМИНАНИЕ по конвеншенам
a) в классах (даже если это mxml - приватные переменные (филды) объявляем в конце класса и название всегда с _ (подчеркиванием в начале) - это всегда позволяет сходу определить приватные филды класса
если работаете с кодом в котором это соглашение нарушено (анпример испольшуются _ для локальных переменных метода - исправляйте это)
быстро преименовывать переменные класса помогает ctrl-alt-R (это единственный рефакторинг флекс билдера - и он вроде нормально работает в пределах скомпиленного класса :-))
b) в mxmxl классах
- сначала идет целиком описание комопнента с помощью mxml тегов (можно первый, после корневого тега, начинать с 0 отступа - для экономии места на экране)
описание неймспейсов в корне
<?xml version="1.0" encoding="utf-8"?>
<sp:SPBox xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="com.os.sp.view.contactGroups.*"
xmlns:controls="com.os.core.controls.*"
xmlns:validators="com.os.core.validators.*"
xmlns:sp="com.os.sp.controls.sp.*"
xmlns:spTabBar="com.os.sp.controls.sp.spTabBar.*"
width="100%"
height="100%"
backgroundColor="#ffffff"
bundle="contacts"
styleName="vboxpanel"
verticalGap="0">
<sp:Первый_Тег id="aaa" ...
- потому идут валидаторы - которые как правило заключены в валидаторсГроуп (тег ValidatorsGroup тоже можно начинать с 0 отступа)
- дальше (в конце) идет весь код, заключенный в тег Script (тег Script тоже можно начинать с 0 отступа для экономии места)
<mx:Script>
<![CDATA[
import org.puremvc.as3.utilities.flexpage.events.EditingStateEvent;
import com.os.sp.constants.Pages;
...
с) для сложных компонентов (сложных страниц) которые требуют для своего создания своих кастомных событий, рендереров, классов-дескрипторов, классов-ошибок или своих кастомных субкомпонентов - мы должны создавать отдельную папку со следующей структурой
в самой папке лежат mxml компонента (либо нескольких его наиболее крупных частей) и медиатор, если он необходим
- controls - кастомыне контролы (субкомпоненты) именно для этого компонента (общие контролы которые используются в нескольких местах системы лежат в shadow-planner-flex-ui\src\com\os\sp\controls )
- events - кастомыне события именно этого комопнента (общие события которые используются в нескольких часятх системы должны быть в shadow-planner-flex-ui\src\com\os\sp\view\events )
- validators - кастомные валидаторы именно этого комопнента (общие валидаторы которые используются в нескольких часятх системы должны быть в shadow-planner-flex-ui\src\com\os\sp\view\validators )
- common - папка для всевозможных классов, необходимых для этого компонента (хелперов, утилит, классов-дескрипторов и т.д.) - структуры общие для всей системы находятся в shadow-planner-flex-ui\src\com\os\sp\common )
- errors - то же самое для классов ошибок (наследников Error) - для отдельных компонентов определяются редко
- constants - классы описатели - обычно редко используются для компонента, а общие константы лежат в shadow-planner-flex-ui\src\com\os\sp\constants
!!! отдельное упоминание о неймспейсе (пакеджах) из src\com\os\core - сюда складываются компоненты, которые достаточно независимы чтобы быть (возможно позднее) использованы в других проектах кроме офис шедова (независимы от этого проекта)
!!! все классы и компоненты из com\os\core не должны иметь зависимостей от com\os\sp (его внутренних классов и структур)
пустые папки создавать не нужно - папки создаем по мере необходимости
примеры PerspectivesPage или com\os\sp\view\administration\customFields
d) для операторов с фигурными скобками {} - в соответствии с конвеншенами - скобки должны быть на одно уровне отступа, а для операторa if - если фигурные скобки использовались в одной ветви, то должны быть использованы и в другом
if(блабла == блаблабла)
{
траляля;
if(хухуху)
{
делай-хохохо;
}
else
{
трулюлю;
}
e) тег [Bindable] пишем над филдом (переменной) или проперти (геттром или сеттером - тем что идет первым в классе) - не в одной строке
[Bindable]
protected var editable:Boolean;
f) если дефолтное значение булевской переменной false - то его можно не писать (это опционально)
g) в самом классе описания идут почти по конвеншенам flex sdk, а именно
- константы (пишутся большими буквами через подчеркивание) (static const) в следующем порядке порядке public - protected - private
- все статические переменный класса (static var) (public - protected - private)
- потом филды (var) - сначала public - потом protected (примечание - pivate var описываются в самом конце класса и начинаются с подчерка - только они начинаются с подчерка)
- конструктор
- public getters and setters
- public functions
- protected getters and setters
- protected functions
- private functions
- private vars (pivate var описываются в самом конце класса и начинаются с подчерка - только они начинаются с подчерка)
2) новое в системных Messadge
a) появился новый тип месседжа STRICT_CONFIRM - который прсит чтото подтвердить и делает кнопку Yes (Ok) доступной только если ты введешь правильно сгенерированную случайно строку
b) в классе SPMessage есть ряд статических функций которые позволяют получить SPMessage нужного типа (вместо использования конструктора) - лучше использовать их чем констркутор
public static function getLocalizedOkMessage(textKey:String, callback:Function=null, params:Array=null):SPMessage<br />public static function getLocalizedConfirmMessage(textKey:String, callback1:Function=null, callback2:Function=null, params:Array=null):SPMessage<br />public static function getLocalizedStrictConfirmMessage(textKey:String, callback1:Function=null, callback2:Function=null, params:Array=null):SPMessage<br />public static function getLocalizedErrorMessage(textKey:String, callback:Function=null, params:Array=null):SPMessage<br />public static function getErrorMessage(text:String, callback:Function=null):SPMessage <br /></pre></blockquote> с) SPMessage и класс его отображения теперь при переводе строк из локалей используют params - для подстановки значений в по шаблонам {0} {1} и так далее<br /> <br /> d) в базовом медиаторе SPMediator появлись<br /><br /> d.1) bundle и messageBundle - позволяют установить префиксы локализации для функций показывающих сообщения (позволяют сформипровать полный ключ локализыции)<br /> <br /> d.2) функции которые позволяют отправлять нужного типа нотификейшен для того чтобы показать нужного типа мессидж (используют вышеописанные bundle)<br /> ими можно и нужно пользоваться во всех медиаторах наследниках (практически во всех пейдж и слот медиаторах)<br /> Пример использования в PerspectivesPageMediator<br /><br /><pre>protected function showConfirmationMessage(key:String, callback1:Function=null,callback2:Function=null, params:Array=null):void<br />protected function showStrictConfirmationMessage(key:String,callback1:Function=null, callback2:Function=null,params:Array=null):void<br />protected function showSuccessfulMessage(key:String, callback1:Function=null, params:Array=null):void<br />protected function showErrorMessage(key:String, callback1:Function=null, params:Array=null):void<br />public function showMessageBox(message:SPMessage):void<br /></pre> e) написание комментариев в стиле Java-doc (as-doc) поощряется (теги @param @default @return @see @private)<br /> для пар public геттеров-сеттеров - мы пишем комментарий для первого геттера а сеттер помечаем тегом @private<br /><br /><blockquote><pre>/**<br />* Current selected Record Type<br />*/<br />public function get currentRecordType():RecordType<br />{<br /> return _currentRecordType;<br />}<br /><br />/**<br />* @private<br />*/<br />public function set currentRecordType(value:RecordType):void<br />{<br /> if(_currentRecordType != value)<br /> {<br /> _currentRecordType = value;<br /> loadCustomFieldsList();<br /> }<br />}<br />
если нужен хотя бы какой-то генератор геттеров сеттеров (пока адоби не разродятся флекс билдером с нормальным функционалом) можно пользоваться плагином Monkey
http://download.eclipse.org/technology/dash/update
http://panellabs.net/eclipse-monkey-asctionsript-generate-scripts/
http://wiki.eclipse.org/Eclipse_Monkey_Overview#Eclipse_Monkey_Script_Exchange
http://www.mandrew182.org.ua/node/1
3) некоторые изменения в Spxxx компонентах
a) SPText - теперь позволяет задать setKeyParams() - для задания массива подстановоак при локализации текста по ключу key (по шаблонам {0} {1} и так далее)
примечание - эту функциоанльность легко включить для всех компонентов использующих SPHelper
b) SPFormItemCheckBox и SPFormItemTextInput теперь позволяют подписаться на событие "change" (которое редиспатчится от обернутого ими компонента)
4) теоретически правильный способ использования Notification когда в теле (body) переается несколько разнотипных параметров
создаем класс для объектов котоыре будут преедаватсья в body (этот класс лучше всего создавать в папке common рядом с классом медатора или классом команды, который получает этот нотификейшен)
Если есть желание кроме постоянных полей body использовать некие дополнительные (не добавляя их в описание класса - то класс можно сделать динамическим) - но имейте в виду что для полей не описанных в классе мы теряем контроль ошибок во время компиляции и требуются дополнительыне преобразования типа во время выполнения.
пример CustomFieldsForRecordTypeBody
ну и соответсветнно - источник нотификейшена создает именно этот body-класс вместо обычного нетипизированного объекта, заполняет его и отсылает. А приемник сразу достает body и пользуется им для своих целей
case SPNotification.CUSTOM_FIELDS_FOR_RECORD_TYPE_WITHIN_RECORD_RULESETS_LOADED:
var body:CustomFieldsForRecordTypeBody = CustomFieldsForRecordTypeBody(notification.getBody());
if(body.recordTypeId == RecordTypeBase.RT_CONTACT_PERSONAL_INFO ....
Преимущество такого подхода - копилятор выяdляет ошибки на ранних стадиях
Недостаток - необходимость создания нового допольнительго класса
Рекомендуется - если необходимо передавать в теле Notification более одного параметра
5) касательно pureMVC
a) медиаторы и прокси как правило СОЗДАЮТСЯ и регистрируются в системе один раз -
поэтому блоки кода типа facade.registerProxy(new XXX()) в местах которые повтоно исполняются несколко раз неприемлемы - в этом случае раньше создавались множественные копии проксей или медиаторов и доступ мы имели только к последней
теперь я поставил эксепшены в ядре PureMVC и каждый такой случай будет сразу замечен!
b) может кто и не заметил, хотя работает это давно - все медиаторы описанный в дескрпторе страницы (PagesHelper) регистрируются в системе (и становятся доступными через фасад) в момент активации страеницы и удаляются из PureMVC в момент перед переходом на другую страницу
то есть эти медиаторы становятся недоступны через фасад, но при этом сами по себе они не уничтожаются (а хранятся во внутреннем кэше приложения - и при последующем показе этой страницы не создаются заново а просто достаются из кеша и регистрируются)
то же самое касается и компонентов, которые эти медиаторы обслуживают - создаются (находятся и регистрируются в медиаторе) они только в первый раз - когда создается медиатор.
c) в случае если вы в прокси делаете запрос на сервер и возвращаете полученные данные в нотификейшене не сохраняя в самом прокси и не делая никаких дополнительных операций можно пользоваться методами, описанными в базовом прокси
getSendNotificationResultHandler и getSendNotificationFaultHandler - которые принимают имя нотификейшена который должны послать по получении ответа от сервера и создают соответствующие callback функции
Например:
public function getCompanyCurrency():void
{
new PerspectifiedDelegate(
getSendNotificationResultHandler(SPNotification.COMPANY_CURRENCY_LOADED),
getSendNotificationFaultHandler(SPNotification.COMPANY_CURRENCY_FAULT)
,true, false).retrieveSingletonRecord(RecordTypeBase.RT_COMPANY_CURRENCY);
}
Но создание отдельных коллбэк функций как приватных методов класса является предпочтительным с точки зрения оптимизации по памяти и для простоты дальнейшего расширения функционала
6) ближайшие планы
a) как вы наверное знаете упраление включением-выключением лоадера сейчас просходит следующим образом
- включает лоадер обынчо команда ViewPаge
- выключает - любой ответ от сервер
- дополнительно к этому - выключением занимался метод update PageBaseMediator-а и всех его наследников в случае если устанавливался флаг resetLoadingStateWhenUpdate
override public function update():void // use viewParams & stateParams
{
super.update();
if(resetLoadingStateWhenUpdate) setLoadingState(false);
....
эта схема была хороша для быстрого введения и повсеместного использования
но на многих страницах это приводит к преждевременному выключению лоадера (особенно в методе update)
в ближайшем будущем от этой практики необходимо отходить - будет так
- включает лоадер команда ViewPаge
- а выключать нужно обудет ручками в том месте где это необходимо данной конкретной странице...
для того чтобы сделать переход на новую систему более плавным - можно просто последовательно отключать дефолтные хендлеры в конструкторе делегатов при соответствующих вызовах
Например
если было так (по умолчанию оба дифолтных хендлера вызывались и отключали loadingState
new CustomFieldDelegate(
loadCustomFieldsForRecordTypeResult,
loadCustomFieldsForRecordTypeFault
).findCustomFieldsByRecordTypeId(recordTypeId);
то теперь можно сделать так (в этом случае мы отключаем только default resutHandler - и в случае Fault - будет выводиться ошибки и отключаться лоадер)
new CustomFieldDelegate(
loadCustomFieldsForRecordTypeResult,
loadCustomFieldsForRecordTypeFault
,false, true).findCustomFieldsByRecordTypeId(recordTypeId);
или так (в этом случае мы отключаем все - и в случае Fault - не будет ни отключения лоадера ни сообщения об ошибке)
new CustomFieldDelegate(
loadCustomFieldsForRecordTypeResult,
loadCustomFieldsForRecordTypeFault
,false, false).findCustomFieldsByRecordTypeId(recordTypeId);