• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# ArkTS编程规范
2
3<!--Kit: ArkTS-->
4<!--Subsystem: ArkCompiler-->
5<!--Owner: @yyytiancai-->
6<!--Designer: @qyhuo32-->
7<!--Tester: @kirl75; @zsw_zhushiwei-->
8<!--Adviser: @zhang_yixin13-->
9
10## 目标和适用范围
11
12本文参考业界标准和实践,结合ArkTS语言特点,提供编码指南,以提高代码的规范性、安全性和性能。
13
14本文适用于使用ArkTS编写的开发场景。
15
16## 规则来源
17
18ArkTS在保持TypeScript基本语法风格的基础上,进一步强化静态检查和分析。本文部分规则筛选自《[OpenHarmony应用TS&JS编程指南](https://gitcode.com/openharmony/docs/blob/master/zh-cn/contribute/OpenHarmony-Application-Typescript-JavaScript-coding-guide.md)》,为ArkTS语言新增的语法添加了规则,旨在提高代码可读性、执行性能。
19
20## 章节概览
21
22### 代码风格
23
24包含命名和格式。
25
26### 编程实践
27
28包含声明与初始化、数据类型、运算与表达式、异常等。
29
30参考了《OpenHarmony应用TS&JS编程指南》中的规则,去除了ArkTS语言不涉及的部分,并为新增的语法添加了规则。
31
32## 术语和定义
33
34|  术语   | 缩略语  | 中文解释 |
35|  ----  | ----  |  ----|
36| ArkTS  | 无 | ArkTS编程语言 |
37| TypeScript  | TS | TypeScript编程语言 |
38| JavaScript  | JS | JavaScript编程语言 |
39| ESObject  | 无 | 在ArkTS跨语言调用的场景中,用以标注JS/TS对象的类型 |
40
41## 总体原则
42
43规则分为两个级别:要求和建议。
44
45**要求**:表示原则上应该遵从。本文所有内容目前均为针对ArkTS的要求。
46
47**建议**:表示该条款属于最佳实践,可结合实际情况考虑是否纳入。
48
49## 命名
50
51### 为标识符取一个好名字,提高代码可读性
52
53**【描述】**
54
55好的标识符命名应遵循以下原则:
56 - 清晰表达意图,避免使用单个字母或非标准缩写命名。
57 - 使用正确的英文单词并符合英文语法,不要使用中文拼音。
58 - 确保语句清晰,避免歧义。
59
60### 类名、枚举名、命名空间名采用UpperCamelCase风格
61
62**【级别】建议**
63
64**【描述】**
65
66类采用首字母大写的驼峰命名法。
67类名通常是名词或名词短语,例如Person、Student、Worker。不应使用动词,也应该避免类似Data、Info这样的模糊词。
68
69**【正例】**
70```
71// 类名
72class User {
73  username: string
74
75  constructor(username: string) {
76    this.username = username;
77  }
78
79  sayHi() {
80    console.log('hi' + this.username);
81  }
82}
83
84// 枚举名
85enum UserType {
86  TEACHER = 0,
87  STUDENT = 1
88};
89
90// 命名空间
91namespace Base64Utils {
92  function encrypt() {
93    // todo encrypt
94  }
95
96  function decrypt() {
97    // todo decrypt
98  }
99};
100```
101
102### 变量名、方法名、参数名采用lowerCamelCase风格
103
104**【级别】建议**
105
106**【描述】**
107
108函数的命名通常是动词或动词短语,采用小驼峰命名。示例如下:
1091.   load + 属性名()
1102.   put + 属性名()
1113.   is + 布尔属性名()
1124.   has + 名词/形容词()
1135.   动词()
1146.   动词 + 宾语()
115变量名通常是名词或名词短语,采用小驼峰命名,便于理解。
116
117**【正例】**
118```
119let msg = 'Hello world';
120
121function sendMsg(msg: string) {
122  // todo send message
123}
124
125let userName = 'Zhangsan';
126
127function findUser(userName: string) {
128  // todo find user by user name
129}
130```
131
132### 常量名、枚举值名采用全部大写,单词间使用下划线隔开
133
134**【级别】建议**
135
136**【描述】**
137
138常量命名,应该由全大写单词与下划线组成,单词间用下划线分割。常量命名要尽量表达完整的语义。
139
140**【正例】**
141
142```
143const MAX_USER_SIZE = 10000;
144
145enum UserType {
146  TEACHER = 0,
147  STUDENT = 1
148};
149```
150
151### 避免使用否定的布尔变量名,布尔型的局部变量或方法需加上表达是非意义的前缀
152
153**【级别】建议**
154
155**【描述】**
156
157布尔型的局部变量建议加上表达是非意义的前缀,比如is,也可以是has、can、should等。但是,当使用逻辑非运算符,并出现双重否定时,会出现理解问题,比如!isNotError,难以理解。因此,应避免定义否定的布尔变量名。
158
159**【反例】**
160
161```
162let isNoError = true;
163let isNotFound = false;
164
165function empty() {}
166function next() {}
167```
168
169**【正例】**
170
171```
172let isError = false;
173let isFound = true;
174
175function isEmpty() {}
176function hasNext() {}
177```
178
179## 格式
180
181### 使用空格缩进,禁止使用tab字符
182
183**【级别】建议**
184
185**【描述】**
186
187只允许使用空格(space)进行缩进。
188
189建议大部分场景优先使用2个空格,换行导致的缩进优先使用4个空格。
190不允许插入制表符Tab。当前几乎所有的集成开发环境(IDE)和代码编辑器都支持配置将Tab键自动扩展为2个空格输入,应在代码编辑器中配置使用空格进行缩进。
191
192**【正例】**
193
194```
195class DataSource {
196  id: number = 0
197  title: string = ''
198  content: string = ''
199}
200
201const dataSource: DataSource[] = [
202  {
203    id: 1,
204    title: 'Title 1',
205    content: 'Content 1'
206  },
207  {
208    id: 2,
209    title: 'Title 2',
210    content: 'Content 2'
211  }
212
213];
214
215function test(dataSource: DataSource[]) {
216  if (!dataSource.length) {
217    return;
218  }
219
220  for (let data of dataSource) {
221    if (!data || !data.id || !data.title || !data.content) {
222      continue;
223    }
224    // some code
225  }
226
227  // some code
228}
229```
230
231### 行宽不超过120个字符
232
233**【级别】建议**
234
235**【描述】**
236
237代码行宽不宜过长,否则不利于阅读。
238
239控制行宽可以间接引导程序员缩短函数和变量的命名,减少嵌套层数,精炼注释,从而提升代码可读性。
240建议每行字符数不超过120个,除非需要显著增加可读性(超过120个),且不会隐藏信息。
241例外:如果一行注释包含了超过120个字符的命令或URL,则可以保持一行,以方便复制、粘贴和通过grep查找;预处理的error信息在一行便于阅读和理解,即使超过120个字符。
242
243### 条件语句和循环语句的实现建议使用大括号
244
245**【级别】建议**
246
247**【描述】**
248
249在`if`、`for`、`do`、`while`等语句的执行体加大括号`{}`是一种最佳实践,因为省略大括号可能导致错误,并且降低代码的清晰度。
250
251**【反例】**
252
253```
254let condition = true;
255if (condition)
256  console.info('success');
257for (let idx = 0; idx < 5; ++idx)
258  console.info('', idx);
259```
260
261**【正例】**
262
263```
264let condition = true;
265if (condition) {
266  console.info('success');
267}
268for (let idx = 0; idx < 5; ++idx) {
269  console.info('', idx);
270}
271```
272
273### `switch`语句的`case`和`default`需缩进一层
274
275**【级别】建议**
276
277**【描述】**
278
279`switch`的`case`和`default`要缩进一层(2个空格)。开关标签之后换行的语句,需再缩进一层(2个空格)。
280
281**【正例】**
282
283```
284switch (condition) {
285  case 0: {
286    doSomething();
287    break;
288  }
289  case 1: {
290    doOtherthing();
291    break;
292  }
293  default:
294    break;
295}
296```
297
298### 表达式换行需保持一致性,运算符放行末
299
300**【级别】建议**
301
302**【描述】**
303
304当语句过长或可读性不佳时,需要在合适的地方进行换行。
305换行时将操作符放在行末,表示“未结束,后续还有”,保持与常用的格式化工具的默认配置一致。
306
307**【正例】**
308
309```
310// 假设条件语句超出行宽
311if (userCount > MAX_USER_COUNT ||
312  userCount < MIN_USER_COUNT) {
313  doSomething();
314}
315```
316
317### 多个变量定义和赋值语句不允许写在一行
318
319**【级别】要求**
320
321**【描述】**
322
323每个语句的变量声明都应只声明一个变量。
324这种方式更便于添加变量声明,无需考虑将分号改为逗号,以免引入错误。此外,每个语句只声明一个变量,使用调试器逐个调试也很方便,而不是一次跳过所有变量。
325
326**【反例】**
327
328```
329let maxCount = 10, isCompleted = false;
330let pointX, pointY;
331pointX = 10; pointY = 0;
332```
333
334**【正例】**
335
336```
337let maxCount = 10;
338let isCompleted = false;
339let pointX = 0;
340let pointY = 0;
341```
342
343### 空格应该突出关键字和重要信息,避免不必要的空格
344
345**【级别】建议**
346
347**【描述】**
348
349空格应该突出关键字和重要信息。总体建议如下:
3501.   `if`, `for`, `while`, `switch`等关键字与左括号`(`之间加空格。
3512.   在函数定义和调用时,函数名称与参数列表的左括号`(`之间不加空格。
3523.   关键字`else`或`catch`与其之前的大括号`}`之间加空格。
3534.   任何打开大括号(`{`)之前加空格,有两个例外:
354a)   在作为函数的第一个参数或数组中的第一个元素时,对象之前不用加空格,例如:`foo({ name: 'abc' })`。
355b)   在模板中,不用加空格,例如:`abc${name}`。
3565.   二元操作符(`+` `-` `*` `=` `<` `>` `<=` `>=` `===` `!==` `&&` `||`)前后加空格;三元操作符(`? :`)符号两侧均加空格。
3576.   数组初始化中的逗号和函数中多个参数之间的逗号后加空格。
3587.   在逗号(`,`)或分号(`;`)之前不加空格。
3598.   数组的中括号(`[]`)内侧不要加空格。
3609.   不要出现多个连续空格。在某行中,多个空格若不是用来作缩进的,通常是个错误。
361
362**【反例】**
363
364```
365// if 和左括号 ( 之间没有加空格
366if(isJedi) {
367  fight();
368}
369
370// 函数名fight和左括号 ( 之间加了空格
371function fight (): void {
372  console.log('Swooosh!');
373}
374```
375
376**【正例】**
377
378```
379// if 和左括号之间加一个空格
380if (isJedi) {
381  fight();
382}
383
384// 函数名fight和左括号 ( 之间不加空格
385function fight(): void {
386  console.log('Swooosh!');
387}
388```
389
390**【反例】**
391
392```
393if (flag) {
394  // ...
395}else {  // else 与其前面的大括号 } 之间没有加空格
396  // ...
397}
398```
399
400**【正例】**
401
402```
403if (flag) {
404  // ...
405} else {  // else 与其前面的大括号 } 之间增加空格
406  // ...
407}
408```
409
410**【正例】**
411
412```
413function foo() {  // 函数声明时,左大括号 { 之前加个空格
414  // ...
415}
416
417bar('attr', {  // 左大括号前加个空格
418  age: '1 year',
419  sbreed: 'Bernese Mountain Dog',
420});
421```
422
423**【正例】**
424
425```
426const arr = [1, 2, 3];  // 数组初始化中的逗号后面加个空格,逗号前面不加空格
427myFunc(bar, foo, baz);  // 函数的多个参数之间的逗号后加个空格,逗号前面不加空格
428```
429
430### 建议字符串使用单引号
431
432**【级别】建议**
433
434**【描述】**
435
436为了保持代码一致性和可读性,建议使用单引号。
437
438**【反例】**
439
440```
441let message = "world";
442console.log(message);
443```
444
445**【正例】**
446
447```
448let message = 'world';
449console.log(message);
450```
451
452### 对象字面量属性超过4个,需要都换行
453
454**【级别】建议**
455
456**【描述】**
457
458对象字面量的属性应保持一致的格式:要么每个属性都换行,要么所有属性都在同一行。当对象字面量的属性超过4个时,建议统一换行。
459
460**【反例】**
461
462```
463interface I {
464  name: string
465  age: number
466  value: number
467  sum: number
468  foo: boolean
469  bar: boolean
470}
471
472let obj: I = { name: 'tom', age: 16, value: 1, sum: 2, foo: true, bar: false }
473```
474
475**【正例】**
476
477```
478interface I {
479  name: string
480  age: number
481  value: number
482  sum: number
483  foo: boolean
484  bar: boolean
485}
486
487let obj: I = {
488  name: 'tom',
489  age: 16,
490  value: 1,
491  sum: 2,
492  foo: true,
493  bar: false
494}
495```
496
497### 把`else`/`catch`放在`if`/`try`代码块关闭括号的同一行
498
499**【级别】建议**
500
501**【描述】**
502
503编写条件语句时,建议将`else`放在`if`代码块关闭括号的同一行。同样,编写异常处理语句时,建议将`catch`放在`try`代码块关闭括号的同一行。
504
505**【反例】**
506
507```
508if (isOk) {
509  doThing1();
510  doThing2();
511}
512else {
513  doThing3();
514}
515```
516
517**【正例】**
518
519```
520if (isOk) {
521  doThing1();
522  doThing2();
523} else {
524  doThing3();
525}
526```
527
528**【反例】**
529
530```
531try {
532  doSomething();
533}
534catch (err) {
535  // 处理错误
536}
537```
538
539**【正例】**
540
541```
542try {
543  doSomething();
544} catch (err) {
545  // 处理错误
546}
547```
548
549### 大括号`{`和语句在同一行
550
551**【级别】建议**
552
553**【描述】**
554
555应保持一致的大括号风格。建议将大括号置于控制语句或声明语句的同一行。
556
557**【反例】**
558
559```
560function foo()
561{
562  // ...
563}
564```
565
566**【正例】**
567
568```
569function foo() {
570  // ...
571}
572```
573
574## 编程实践
575
576### 建议添加类属性的可访问修饰符
577
578**【级别】建议**
579
580**【描述】**
581
582ArkTS提供了`private`, `protected`和`public`可访问修饰符。默认情况下,属性的可访问修饰符为`public`。选取适当的可访问修饰符可以提升代码的安全性和可读性。注意:如果类中包含`private`属性,无法通过对象字面量初始化该类。
583
584**【反例】**
585
586```
587class C {
588  count: number = 0
589
590  getCount(): number {
591    return this.count
592  }
593}
594```
595
596**【正例】**
597
598```
599class C {
600  private count: number = 0
601
602  public getCount(): number {
603    return this.count
604  }
605}
606```
607
608### 不建议省略浮点数小数点前后的0
609
610**【级别】建议**
611
612**【描述】**
613
614ArkTS中,浮点值包含一个小数点,不要求小数点之前或之后必须有一个数字。在小数点前面和后面都添加数字可以提高代码的可读性。
615
616**【反例】**
617
618```
619const num = .5;
620const num = 2.;
621const num = -.7;
622```
623
624**【正例】**
625
626```
627const num = 0.5;
628const num = 2.0;
629const num = -0.7;
630```
631
632### 判断变量是否为`Number.NaN`时必须使用`Number.isNaN()`方法
633
634**【级别】要求**
635
636**【描述】**
637
638在ArkTS中,`Number.NaN`是`Number`类型的一个特殊值。它被用来表示非数值,这里的数值是指在IEEE浮点数算术标准中定义的双精度64位格式的值。
639在ArkTS中,`Number.NaN`的独特之处在于它不等于任何值,包括其本身。与`Number.NaN`进行比较时,结果是令人困惑的:`Number.NaN !== Number.NaN` 和 `Number.NaN != Number.NaN` 的值都是 `true`。
640因此,必须使用`Number.isNaN()`函数来测试一个值是否是`Number.NaN`。
641
642**【反例】**
643
644```
645if (foo == Number.NaN) {
646  // ...
647}
648
649if (foo != Number.NaN) {
650  // ...
651}
652```
653
654**【正例】**
655
656```
657if (Number.isNaN(foo)) {
658  // ...
659}
660
661if (!Number.isNaN(foo)) {
662  // ...
663}
664```
665
666### 数组遍历优先使用`Array`对象方法
667
668**【级别】要求**
669
670**【描述】**
671
672对于数组遍历,应该优先使用Array对象方法,如:`forEach(), map(), every(), filter(), find(), findIndex(), reduce(), some()`。
673
674**【反例】**
675
676```
677const numbers = [1, 2, 3, 4, 5];
678// 依赖已有数组来创建新的数组时,通过for遍历,生成一个新数组
679const increasedByOne: number[] = [];
680for (let i = 0; i < numbers.length; i++) {
681  increasedByOne.push(numbers[i] + 1);
682}
683```
684
685**【正例】**
686
687```
688const numbers = [1, 2, 3, 4, 5];
689// better: 使用map方法是更好的方式
690const increasedByOne: number[] = numbers.map(num => num + 1);
691```
692
693### 不要在控制性条件表达式中执行赋值操作
694
695**【级别】要求**
696
697**【描述】**
698
699控制性条件表达式用于 `if`、`while`、`for` 以及 `?:` 等条件判断语句中。
700在控制性条件表达式中执行赋值容易导致意外行为,且降低代码的可读性。
701
702**【反例】**
703
704```
705// 在控制性判断中赋值不易理解
706if (isFoo = false) {
707  // ...
708}
709```
710
711**【正例】**
712
713```
714const isFoo = false; // 在上面赋值,if条件判断中直接使用
715if (isFoo) {
716  // ...
717}
718```
719
720### 在`finally`代码块中,不要使用`return`、`break`、`continue`或抛出异常,避免`finally`块非正常结束
721
722**【级别】要求**
723
724**【描述】**
725
726在`finally`代码块中,直接使用`return`、`break`、`continue`、`throw`语句或调用方法时未处理异常,会导致`finally`代码块无法正常结束。`finally`代码块异常结束会影响`try`或`catch`代码块中异常的抛出,也可能影响方法的返回值。因此,必须确保`finally`代码块正常结束。
727
728**【反例】**
729
730```
731function foo() {
732  try {
733    // ...
734    return 1;
735  } catch (err) {
736    // ...
737    return 2;
738  } finally {
739    return 3;
740 }
741}
742```
743
744**【正例】**
745
746```
747function foo() {
748  try {
749    // ...
750    return 1;
751  } catch (err) {
752    // ...
753    return 2;
754  } finally {
755    console.log('XXX!');
756  }
757}
758```
759
760### 避免使用`ESObject`
761
762**【级别】建议**
763
764**【描述】**
765
766`ESObject`主要用于ArkTS和TS/JS的跨语言调用场景中的类型标注。在非跨语言调用场景中使用`ESObject`标注类型,会引入不必要的跨语言调用,导致额外的性能开销。
767
768**【反例】**
769
770```
771// lib.ets
772export interface I {
773  sum: number
774}
775
776export function getObject(value: number): I {
777  let obj: I = { sum: value };
778  return obj
779}
780
781// app.ets
782import { getObject } from 'lib'
783let obj: ESObject = getObject(123);
784```
785
786**【正例】**
787
788```
789// lib.ets
790export interface I {
791  sum: number
792}
793
794export function getObject(value: number): I {
795  let obj: I = { sum: value };
796  return obj
797}
798
799// app.ets
800import { getObject, I } from 'lib'
801let obj: I = getObject(123);
802```
803
804### 使用`T[]`表示数组类型
805
806**【级别】建议**
807
808**【描述】**
809
810ArkTS提供了两种数组类型的表示方式:`T[]`和`Array<T>`。建议所有数组类型均使用`T[]`表示,以提高代码可读性。
811
812**【反例】**
813
814```
815let x: Array<number> = [1, 2, 3];
816let y: Array<string> = ['a', 'b', 'c'];
817```
818
819**【正例】**
820
821```
822// 统一使用T[]语法
823let x: number[] = [1, 2, 3];
824let y: string[] = ['a', 'b', 'c'];
825```
826