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