пятница, мая 23, 2008

Хороший стиль Flex-программирования. Использование языка программирования

Продолжаем обозревать статью Flex SDK coding conventions and best practices.

Следующий подраздел: Language Usage.

Использование языка программирования

Здесь будут рассмотрены вопросы использования языковых конструкций ActionScript 3. Особое внимание мы уделим ситуацям, когда для выражения одной идеи существует несколько способов.

Параметры компиляции

Компилируйте проекты с параметрами -strict и -show-actionscript-warnings (они заданы по умолчанию в файле flex-config.xml).

API, базирующиеся на свойствах

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

Объявление типа

Указывайте типы для каждой константы, каждой переменной, каждого аргумента функции и ее результата. Даже если они не имеют типа, указывайте ":*":

Например:
можно: var value:*;
нельзя: var value;

Используйте наиболее подходящий для конкретной ситуации тип. Например, переменную цикла "for" лучше объявлять типом int а не Number, и, конечно, не Object или *. Другой пример: в mouseDownHandler необходимо объявить аргумент в виде event:MouseEvent, но не event:Event.

Используйте тип int для работы с целыми числами, даже если предполагается, что они будут только положительными. Тип uint используйте только для значений цветов RGB, битовых масок и в других значений, не предназначенных для вычислений.

Используйте тип * только в случае, если значение переменной может быть undefined. Но лучше всегда использовать вместо * тип Object, значение которого равно null, в случае, если объект не существует.

Если вы объявляете переменную типа Array, добавляйте комментарий /* of Тип */ непосредственно после слова Array, указывая тип элементов массива. Это важно, так как в будущих версиях ActionScript появятся типизированные массивы. (Интересно, такой синтаксис будет учитываться непосредственно компилятором, или это совет с расчетом на последующий рефакторинг кода?)

Например:
можно: var a:Array /* of String */ = [];
нельзя: var a:Array = [];
можно: function f(a:Array /* of Number */):Array /* of Object */ {...}
нельзя: function f(a:Array):Array


Литералы

undefined
Избегайте по возможности использования undefined. Единственный случай, когда это необходимо - использование переменных типа *.

int и uint
Никогда не используйте точку дроби в целочисленных значениях:
можно: 2
нельзя: 2.

В шестнадцатиричных числах всегда используйте "x" в нижнем регистре:
можно: 0xFEDCBA
нельзя: 0XFEDCBA

Всегда указывайте RGB-цвета в виде шестизначного шестнадцатиричного числа:
можно: private const BLACK:uint = 0x000000;
нельзя: private const BLACK:uint = 0;

При работе с индексами (например указатель на элемент в массиве), всегда используйте "-1" для обозначения отсутствия индекса.


Number
Если предполагается, что значение переменной будет дробным (обычный случай), инициировать его нужно с использованием точки дроби, после которой ставится дробная часть, либо, при ее отсутствии, нуль:
можно: alphaFrom = 0.0; alphaTo = 1.0;
нельзя: alphaFrom = 0; alphaTo = 1;

Но исключение для этого правила составляют пиксельные координаты, которые условно целые, хотя, в принципе, могут быть и дробными:
можно: var xOffset:Number = 3;
нельзя: var xOffset:Number = 3.0;

При использовании знака экспоненты, применяйте "e" в нижнем регистре:
можно: 1.0e12
нельзя: 1.0E12

Для Number используйте значение по умолчанию NaN - "не установлено".


String
Для обозначения строк всегда используйте двойные кавычки, даже если внутри строки присутствует символ кавычки:
можно: "What's up, \"Big Boy\"?"
нельзя: 'What\'s up, "Big Boy"?'

При использовании escape-последовательностей, спецсимвол "\u" указывайте в нижнем регистре:
можно: \u
нельзя: \U


Array
Используйте вместо new Array() литералы массива:
можно: [], [ 1, 2, 3 ]
нельзя: new Array(), new Array(1, 2, 3)

Используйте конструктор массива Array() только для определения массива заданного размера. Это важно, т.к. new Array(3) создает не [ 3 ], а [ undefined, undefined, undefined ].


Object
Используйте вместо new Object() литералы объекта:
можно: {}
нельзя: new Object(),

можно: {}, o = { a: 1, b: 2, c: 3 };
нельзя:
o = new Object();
o.a = 1;
o.b = 2;
o.c = 3;
или
o = {};
o.a = 1;
o.b = 2;
o.c = 3;



Function
Избегайте использования литералов функции для определения анонимных функций. Вместо этого, используйте методы класса или функции пакета.

При использовании литерала функции, обязательно указывайте возвращаемый тип, а последний оператор в теле функции заканчивайте символом точки с запятой ";":
можно: function(i:int):void { doIt(i - 1); doIt(i + 1); }
нельзя: function(i:int) { doIt(i - 1); doIt(i + 1) }


RegExp

Для определения регулярного выражения используйте представление в виде последовательности литералов, а не инстанцирование объекта RegExp с параметром String:
можно: var pattern:RegExp = /\d+/g;
нельзя: var pattern:RegExp = new RegExp("\\d+", "g");


XML и XMLList
Используйте представление в виде последовательности литералов, а не инстанцирование объекта XML с параметром String:
можно: var node:XML = <name first="Jane" last="Doe"/>;
нельзя: var node:XML = new XML("<name first=\"Jane\" last=\"Doe\"/>");

Используйте только двойные кавычки для определения значений аттрибутов тегов XML:
можно: var node:XML = <name first="Jane" last="Doe"/>;
нельзя: var node:XML = <name first='Jane' last='Doe'/>;


Class
Используйте полное имя класса (включающее имена пакетов) только в том случае, когда импортируется два класса с одним и тем же именем (но из разных пакетов), и необходимо разрешить конфликт имен:
можно:
import mx.controls.Button;
...
var b:Button = new Button();

нельзя:
import mx.controls.Button;
...
var b:Button = new mx.controls.Button();

Но в этом случае можно:
import mx.controls.Button;
import my.controls.Button;
...
var b:Button = new mx.controls.Button();



Выражения

Круглые скобки
При работе с операторами +, -, *, /, &&, , <, <=, >, >=, ==, и !=, не используйте круглые скобки там, где в этом нет необходимости:
можно: var e:Number = a * b / (c + d); var e:Boolean = a && b c == d;
нельзя: var e:Number = (a * b) / (c + d); var e:Boolean = ((a && b) (c == d));

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


Приведение типов
Не сравнивайте переменную типа Boolean с "true" или "false". Она уже является первым или вторым.
можно: if (flag)
нельзя: if (flag == true)
можно: var flag:Boolean = a && b;
нельзя: var flag:Boolean = (a && b) != false;

Явно приводите к типу Boolean переменные типа int, uint, Number или String:
можно: if (n != 0)
нельзя: if (n)
можно: if (s != null && s != "")
нельзя: if (s)

Переменные типа Object можно приводить к типу Boolean неявно:
можно: if (child)
нельзя: if (child != null)
можно: if (!child)
нельзя: if (child == null)

Лучше использовать приведение к типу (кастинг), чем оператор "as". Оператор "as" используйте только в том случае, если при ошибке приведения к типу, вы хотите получить в результате значение "null", а не генерацию исключения:
можно: IUIComponent(child).document
нельзя: (child as IUIComponent).document


Сравнение
Старайтесь расставлять сравниваемые значения в естественном для прочтения порядке (более удобном для прочтения порядке):
можно: if (n == 3) // "Если n равно 3"
нельзя: if (3 == n) // "Если 3 равно n"


Операторы ++ и --
Если в какой-либо ситуации префиксная и постфиксная формы (оператор ставится до или после переменной) логически равнозначны, используйте постфиксную форму. Используйте префиксную форму только в том случае, когда требуется получить значение переменной до ее изменения.
можно: for (var i:int = 0; i < n; i++)
нельзя: for (var i:int = 0; i < n; ++i)


Тернарный оператор

Ипользуйте вместо if/else тернарный оператор в случае простых операций проверки, особенно, для проверок значений на null:
можно: return item ? item.label : null;
нельзя:
if (!item) return null;
return item.label;

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

if (a < b)
return -1;
else
if (a > b)
return 1;
return 0;
нельзя:
return a < b ? -1 : (a > b ? 1 : 0);


Оператор new
Всегда используйте круглые скобки после имени класса, даже если конструктор не требует аргументов:
можно: var b:Button = new Button();
нельзя: var b:Button = new Button;

Операторы

Всегда ставьте после операторов точку с запятой ";". ActionScript 3 не считает за ошибку отсутствие точки с запятой в конце оператора, но лучше не пользоваться этой особенностью:
можно:
a = 1;
b = 2;
c = 3;
нельзя:
a = 1
b = 2
c = 3


Директива include
Используйте include вместо #include. В конце ставьте точку с запятой, как и для других операторов:
можно: include "../core/ComponentVersion.as";
нельзя: #include "../core/ComponentVersion.as"

Всегда используйте относительные пути вместо абсолютных.


Директива import
Указывайте только те классы, интерфейсы и пакетные функции, которые используйте. Не используйте спецсимвол "*":
можно:
import mx.controls.Button;
import flash.utils.getTimer;
нельзя: import mx.core.*;


Директива use namespace
Избегайте ее использования. Вместо этого, используйте синтаксис "::" для каждого определения вне основного пространства имен:
можно:
import mx.core.mx_internal;
// Позже, в каком-либо методе...
mx_internal::doSomething();
нельзя:
import mx.core.mx_internal;
use namespace mx_internal;
// Позже, в каком-либо методе...
doSomething();


Оператор if
Если ветви оператора if/else не содержат более одного оператора, не заключайте эти операторы в блок фигурными скобками.
можно: if (flag) doThing1();
нельзя: if (flag) { doThing1(); }
можно:

if (flag)
doThing1();
else
doThing2();
нельзя:
if (flag) {
doThing1();
}
else {
doThing2();
}

Однако, если одна из ветвей содержит более одного оператора, заключайте в блоки все ветви:
можно:

if (flag) {
doThing1();
}
else {
doThing2();
doThing3();
}
нельзя:
if (flag)
doThing1();
else {
doThing2();
doThing3();
}

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

if (!condition1)
return false;
...
if (!condition2)
return false;
...
if (!condition2)
return false;
...
return true;
нельзя:
if (condition1) {
...
if (condition2) {
...
if (condition3) {
...
return true;
}
}
}
return false;


Оператор for
Всегда заключайте тело цикла в болк фигурными скобками, даже если оно состоит только из одного оператора.
можно:

for (var i:int = 0; i < 3; i++) {
doSomething(i);
}
нельзя:
for (var i:int = 0; i < 3; i++)
doSomething(i);

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

var n:int = a.length;
for (var i:int = 0; i < n; i++) {
...
}
нельзя:
for (var i:int = 0; i < a.length; i++) {
...
}

Определяйте переменную цикла внутри круглых скобок оператора цикла, если она не определяется где-либо повторно:
можно: for (var i:int = 0; i < 3; i++)
нельзя:

var i:int;
for (i = 0; i < 3; i++) {
...
}


Оператор while

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

while (i < n) {
doSomething(i);
}
нельзя:
while (i < n)
doSomething(i);


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

do {
doSomething(i);
} while (i < n);
нельзя:
do
doSomething(i);
while (i < n);


Оператор switch

Заключайте тело каждого выражения case и default в болк фигурными скобками. Операторы break или return ставьте внутрь блока, а не после него. Если вы используете в блоке return, не ставьте после него break. Вариант default обрабатывайте так же как и case, вызов break из него так же обязателен:
можно:

switch (n) {
case 0: {
foo();
break;
}
case 1: {
bar();
return;
}
case 2: {
baz();
return;
}
default: {
blech();
break;
}
}
нельзя:
switch (n) {
case 0:
foo();
break;
case 1: {
bar();
}
break;
case 2:
baz();
return;
break;
default:
blech();
}


Оператор return

Не заключайте возвращаемое значение в круглые скобки:
можно: return n + 1;
нельзя: return (n + 1);

Вызов оператора return разрешается из любого места тела метода.

Объявления

Никогда не объединяйте несколько констант/переменных в одно объявление:
можно: var a:int = 1; var b:int = 2;
нельзя: var a:int = 1, b:int = 2;


Ключевое слово override
Там, где override необходимо, указывайте его до объявления спецификатора доступа:
можно: override protected method measure():void
нельзя: protected override method measure():void


Спецификаторы доступа

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

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


Ключевое слово static
Там, где static необходимо, указывайте его после объявления спецификатора доступа:
можно: public static const MOVE:String = "move";
нельзя: static public const MOVE:String = "move";


Ключевое слово final

Там, где final необходимо, указывайте его после объявления спецификатора доступа:
можно: public final class BoxDirection
нельзя: final public class BoxDirection

Объявляйте все классы-каталоги ("enum classes") как final.
Также, объявляйте "базовые" свойства и методы (те, что начинаются с $) как final.


Константы

Все константы должны объявляться как static. Нет необходимости объявлять в классе динамические константы, поскольку всё равно все объекты этого класса будут хранить одни и те же значения:
можно: public static const ALL:String = "all";
нельзя: public const ALL:String = "all";


Переменные

Если переменную необходимо инициализировать каким-либо значением, делайте это сразу при объявлении переменной, а не в конструкторе:
можно: private var counter:int = 1;
нельзя:

private var counter:int;
...
public function MyClass() {
super();
...
counter = 1;
}


Локальные переменные

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

private function f(i:int, j:int):int {
var a:int = g(i - 1) + g(i + 1);
var b:int = g(a - 1) + g(a + 1);
var c:int = g(b - 1) + g(b + 1);
return (a * b * c) / (a + b + c);
}
нельзя:
private function f(i:int, j:int):int {
var a:int;
var b:int;
var c:int;
a = g(i - 1) + g(i + 1);
b = g(a - 1) + g(a + 1);
c = g(b - 1) + g(b + 1);
return (a * b * c) / (a + b + c);
}

Объявляйте локальные переменные в функции только один раз. ActionScript 3 не поддерживает объявление локальных переменных с областью видимости в одном блоке:
можно:

var a:int;
if (flag) {
a = 1;
...
}
else {
a = 2;
...
}
нельзя:
if (flag) {
var a:int = 1;
...
}
else {
var a:int = 2;
...
}

можно:
var i:int;
for (i = 0; i < n; i++) {
...
}
for (i = 0; i < n; i++) {
...
}
нельзя:
for (var i:int = 0; i < n; i++) {
...
}
for (var i:int = 0; i < n; i++) {
...
}


Классы
Если класс наследуется от Object, не нужно указывать extends Object.

The only “bare statements” in a class should be calls to static class initialization methods, such as loadResources(). (Совсем ничего не понял в этой фразе).


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

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

public function MyClass(foo:int, bar:int) {
this.foo = foo;
this.bar = bar;
}
нельзя:
public function MyClass(fooVal:int, barVal:int) {
foo = fooVal;
bar = barVal;
}

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

Послесловие

Далее перечислены темы, которые еще не в разработке:

  • Interfaces
  • Namespaces
  • Implementing properties
  • Metadata
  • Packages
  • Helper classes
  • Bare statements

Будем иногда заглядывать СЮДА и следить за продолжением.

Правила, конечно, порой спорные.
Например, инициализация переменных при их объявлении допустима только при работе с простыми значениями. [Например, инициализировав какую-либо переменную объектом {prop:1}, мы получим во всех объектах этого класса ссылку на один и тот же объект, но не клоны отого объекта.]

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

Остальное, в принципе, вполне законно и приемлемо.
В следующий раз поговорим об организации файлов: File Organization.

10 комментариев:

k0zer комментирует...

Мне тоже очень многое спорным показалось.
Я поправлю немного: "Например, инициализировав какую-либо переменную объектом {prop:1}, мы получим во всех объектах этого класса ссылку на один и тот же объект, но не клоны того объекта." - это не так. И массивы [] о объекты {}, в АС3 ведут себя правильно.

Racer комментирует...

Ага, спасибо за поправку - я сталкивался с этой проблемой, но действительно в AS2.
И все же указывать сложные объекты в "шапке" класса я рисковать не буду. Кстати, может быть такая практика полезна для ASDoc, который автоматом берет эти значения как по умолчанию? Я не проверял, но есть такая уверенность.

mike комментирует...

The only “bare statements” in a class should be calls to static class initialization methods, such as loadResources().

"Открытыми операторами" в классе могут являться только вызовы статических методов инициализации классов, такие как loadResources().

Анонимный комментирует...

return item ? item.label : null;
item не есть выражение типа Boolean. Следует писать:
return (item == null) ? null : item.label

Racer комментирует...

Это вполне логично, но вот Адоби и многие другие считают, что такая конструкция очень удобна:
return item ? item.label : null;
Честно говоря, никогда раньше не придерживался этого принципа, и вообще не пользую тренарный оператор.
Но стоит немного себя пересилить и принять новые правила - сразу появляется какая-то свежесть в коде и мыслях. Просто попробуй в следующем проекте делать так.

Анонимный комментирует...

Тернарный оператор очень помогает, когда надо написать выражение, которое умещается в строку, но при этом содержит условие, например:
var turnedOn:Boolean = true;
...
return "Подсказки "+(turnedOn ? "включены" : "выключены");

Анонимный комментирует...

Так всё-таки, где лучше инициализировать переменные? В конструкторе или при их объявлении? Какие есть соображения по этому поводу? Я пока придерживаюсь первого метода (в конструкторе).

Racer комментирует...

При объявлении переменных типа String, Number, Boolean, int очень удобно устанавливать их значения.
Ибо повторять этот многочисленный список мелочевки в конструкторе просто трата времени.

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

Евгений комментирует...

адобы сами себе противоречат, например взять массивы:
тут они пишут, что при объявлении массива, нужно писать a:Array = [];
а если заглянуть в хелп по as3 в класс Array и посмотреть примеры, там они объявляют массивы через a:Array = new Array(); так как всетаки правильно?:)
и как лучше их называть, с добавлением типа или без?
buttons или buttonsArray или buttonsCollection ?

Racer комментирует...

Не думаю, что это очень принципиально.
Мне больше нравится [] и {}.

Аналогично с названиями - надо по контексту ориентироваться. Если в одном месте (классе, методе, и т.п.) объявляется несколько разнотиповых списков с кнопками, то в принципе, уточнение нужно. Если нет - пойдет множественное число - buttons. Только я бы делал упор не на тип а на назначение - для чего нужен тот или иной массив.