1# 示例代码风格 2本规范适用于文档中ArkTS、JavaScript和C/C++等编程语言的示例代码片段,旨在提高OpenHarmony文档示例代码的可读性、可维护性,以及风格一致性。 3 4## 代码规范 5 6### 【规则】遵守基本编程规范 7 8【描述】 9 10文档的示例代码需要遵循[OpenHarmony应用ArkTS编程规范](../../contribute/OpenHarmony-ArkTS-coding-style-guide.md)、[JavaScript语言编程规范](../OpenHarmony-JavaScript-coding-style-guide.md)、[C语言编程规范](../OpenHarmony-c-coding-style-guide.md)和[C++语言编程规范](../OpenHarmony-cpp-coding-style-guide.md)基本的编码规范,包括命名规范、代码格式和代码规范等。 11 12### 【规则】每个接口提供示例代码 13 14【描述】 15 16API参考中,每个接口(包括方法和组件)均需要提供示例代码。如果多个API存在关联关系,则需要在一个场景化的代码示例中体现。 17 18### 【规则】API参考示例代码不包含异常处理 19 20【描述】 21 22API参考文档的主要目的是展示如何调用和使用API。为了保持示例代码的简洁性和易读性,API参考示例中无需添加异常处理逻辑。这使得开发者可以快速理解API的基本用法。 23 24【正例】 25 26```ts 27declare function doSthAsync(): Promise<void>; 28 29declare function doSthSync(): void; 30 31// 正例1在API示例代码中,异步场景,无需增加`.catch()`分支 32doSthAsync() 33 .then(() => { 34 }) 35 36// 正例2:在API示例代码中,同步场景,无需使用`try...catch...`包裹 37doSthSync(); 38``` 39 40【反例】 41 42```ts 43import { BusinessError } from '@kit.BasicServicesKit'; 44 45declare function doSthAsync(): Promise<void>; 46 47declare function doSthSync(): void; 48 49// 反例1:在API示例代码中,异步场景,无须增加`try...catch...`,也无须使用 `.catch()` 方法处理错误。 50try { 51 doSthAsync() 52 .then(() => { 53 }) 54 .catch((err: BusinessError) => { 55 console.error(`Failed to do sth. Code is ${err.code}, message is ${err.message}`); 56 }); 57} catch (error) { 58 const err: BusinessError = error as BusinessError; 59 // 处理入参错误异常 60 console.error(`Failed to do sth. Code is ${err.code}, message is ${err.message}`); 61} 62 63// 反例2:在API示例代码中,同步场景,无须增加`try...catch...` 64try { 65 doSthSync(); 66} catch (error) { 67 const err: BusinessError = error as BusinessError; 68 // 处理入参错误异常 69 console.error(`Failed to copy file. Code is ${err.code}, message is ${err.message}`); 70} 71``` 72 73### 【规则】开发指南示例代码包含异常处理 74 75【描述】 76 77开发指南旨在提供全面的代码示例,帮助开发者在实际应用中正确使用API。为了确保代码的健壮性和可靠性,示例代码中需要包含异常处理逻辑。这有助于开发者理解如何在不同情况下处理错误,提高代码质量。 78 79通过在开发指南的示例代码中添加异常处理,开发者可以更好地理解如何编写健壮的代码,并在实际开发中应用这些实践。 80 81【正例】 82 83```ts 84import { BusinessError } from '@kit.BasicServicesKit'; 85 86declare function doSthAsync1(): Promise<void>; 87 88declare function doSthAsync2(): Promise<void>; 89 90declare function doSthAsync3(): Promise<void>; 91 92declare function doSthSync1(): void; 93 94declare function doSthSync2(): void; 95 96// 正例1:异步接口,Promise场景,加上`.catch()`相关的分支判断 97doSthAsync1() 98 .then(() => { 99 }) 100 .catch((err: BusinessError) => { 101 console.error(`Failed to do sth. Code is ${err.code}, message is ${err.message}`); 102 }); 103 104// 正例2:异步接口,结合async/await使用,增加`try...catch...`包裹 105async () => { 106 try { 107 await doSthAsync1(); 108 await doSthAsync2(); 109 await doSthAsync3(); 110 } catch (error) { 111 const err: BusinessError = error as BusinessError; 112 // 处理入参错误异常 113 console.error(`Failed to do sth. Code is ${err.code}, message is ${err.message}`); 114 } 115} 116 117// 正例3:同步接口,增加`try...catch...`包裹 118try { 119 doSthSync1(); 120 doSthSync2(); 121} catch (error) { 122 const err: BusinessError = error as BusinessError; 123 // 处理入参错误异常 124 console.error(`Failed to do sth. Code is ${err.code}, message is ${err.message}`); 125} 126 127// 正例4:对于同步接口增加`try...catch...`包裹,按需增加对应的异常错误码判断 128try { 129 doSthSync1(); 130 doSthSync2(); 131} catch (err) { 132 switch (err.code) { 133 case 401: 134 // Handle parameters exceptions 135 break; 136 case 12300001: 137 // Handle 12300001 exceptions 138 break; 139 case 12300002: 140 case 12300003: 141 case 12300004: 142 console.error(`Failed to do sth. Code is ${err.code}, message is ${err.message}`); 143 break; 144 } 145} 146``` 147 148【反例】 149 150```ts 151declare function doSthAsync1(): Promise<void>; 152 153declare function doSthAsync2(): Promise<void>; 154 155declare function doSthAsync3(): Promise<void>; 156 157declare function doSthSync1(): void; 158 159declare function doSthSync2(): void; 160 161// 反例1:异步接口,缺少`.catch()`异常处理 162doSthAsync1() 163 .then(() => { 164 }) 165 166// 反例2:异步接口,结合async/await使用,缺少`try...catch...`异常处理 167async () => { 168 await doSthAsync1(); 169 await doSthAsync2(); 170 await doSthAsync3(); 171} 172 173// 反例3:同步接口,缺少`try...catch...`异常处理 174doSthSync1(); 175doSthSync2(); 176``` 177 178### 【规则】明确变量定义与用途 179 180【描述】 181 182示例代码中的变量需要包含定义、使用方法或者来源链接参考或者说明,以确保开发者能够理解如何使用。例如,如果涉及到应用开发路径,需要提供获取应用开发路径的链接参考或方法。 183 184【正例】 185 186示例中的`context`的获取方式请参见[获取UIAbility的上下文信息](../../application-dev/application-models/uiability-usage.md#获取uiability的上下文信息)。 187 188```ts 189import { common, Want } from '@kit.AbilityKit'; 190 191const context: common.UIAbilityContext = this.context; // UIAbilityContext 192let want: Want = { 193 deviceId: '', // deviceId为空表示本设备 194 bundleName: 'com.example.myapplication', 195 abilityName: 'FuncAbility', 196 moduleName: 'func', // moduleName非必选 197 parameters: { 198 // 自定义信息 199 info: '来自EntryAbility Index页面', 200 }, 201} 202// context为调用方UIAbility的UIAbilityContext 203context.startAbilityForResult(want) 204 .then((data) => { 205 // ... 206 }) 207``` 208 209【反例】 210 211```ts 212// 反例:使用到的context和want变量未进行定义 213// context为调用方UIAbility的UIAbilityContext 214context.startAbilityForResult(want) 215 .then((data) => { 216 // ... 217 }) 218``` 219 220### 【建议】统一依赖包命名风格 221 222【描述】 223 224导入的依赖包的命名与其依赖包的命名空间保持一致,以便于维护和理解代码。 225 226采用一致的依赖包命名风格还可以方便IDE进行提示导入,提高编码效率。 227 228【正例】 229 230```ts 231import { promptAction } from '@kit.ArkUI'; 232``` 233 234【反例】 235 236```ts 237// 包名和其命名空间不一致,不利于维护和理解代码 238import { promptAction as prompt } from '@kit.ArkUI'; 239``` 240 241### 【规则】UI组件宽高等属性不加单位 242 243【描述】 244 245为了保持代码的一致性和简洁性,在设置组件的宽度、高度等属性时,应该尽量避免添加单位(例如`vp`/`fp`/`px`),因为组件的宽度、高度等属性默认以像素为单位。同时,避免添加单位也可以提高代码的可读性和便于维护。 246 247【正例】 248 249```ts 250Text('Hello World') 251 .width(100) 252 .height(100) 253 254Text('Hello World') 255 .fontSize(50) 256``` 257 258【反例】 259 260```ts 261Text('Hello World') 262 .width('100vp') 263 .height('100vp') 264 265Text('Hello World') 266 .width('300px') 267 .height('400px') 268 269Text('Hello World') 270 .fontSize('50fp') 271``` 272 273## 代码展示 274 275### 【规则】行内代码使用反引号显示 276 277【描述】 278 279正文描述中涉及代码的内容,比如实际代码中的方法名、参数名或代码文件名等,使用`包裹显示。 280 281【正例】 282 283在`Index.ets`文件中实现页面跳转。 284 285【反例】 286 287在Index.ets文件中实现页面跳转。 288 289### 【规则】代码示例使用代码块进行代码染色 290 291【描述】 292 293对代码示例、命令行使用代码样式。在Markdown中,使用```呈现代码样式,同时指定语言类型。 294 295代码染色是指在编辑器中对代码进行不同颜色的标记,以区分不同语法元素的功能。例如在编辑器中对不同的关键字、变量名、注释等使用不同的颜色进行标记,可以让代码更加易读易懂。 296 297 298 299### 【规则】代码格式化 300 301【描述】 302 303在将代码示例放入指南之前,使用DevEco Studio中的代码格式化功能对代码进行格式化,以确保代码的一致性和可读性。 304 305格式化代码的方法包括缩进、空格、换行等,这些方法可以使代码更易于阅读和理解,提高代码的可维护性和扩展性。 306 307【正例】 308 309```ts 310import { window } from '@kit.ArkUI'; 311import { UIAbility } from '@kit.AbilityKit'; 312import { BusinessError } from '@kit.BasicServicesKit'; 313 314export default class EntryAbility extends UIAbility { 315 onWindowStageCreate(windowStage: window.WindowStage) { 316 windowStage.loadContent('pages/Index', (err: BusinessError, data) => { 317 }); 318 } 319} 320``` 321 322【反例】 323 324```ts 325import { window } from '@kit.ArkUI'; 326import { UIAbility } from '@kit.AbilityKit'; 327import { BusinessError } from '@kit.BasicServicesKit'; 328 329export default class EntryAbility extends UIAbility { 330 onWindowStageCreate(windowStage: window.WindowStage) { 331 // 代码未格式化,没有缩进 332 windowStage.loadContent('pages/Index', (err: BusinessError, data) => { 333 }); 334 } 335} 336``` 337 338### 【规则】使用省略符展示代码省略部分 339 340【描述】 341 342当需要在文档或代码中展示省略的部分时,使用统一的省略代码格式。省略代码应该简洁明了,避免冗长或混乱的格式。 343 344【正例】 345 346```ts 347// 正例1 348// ... 349 350// 正例2 351// To do sth. 352``` 353 354【反例】 355 356```ts 357// 反例1 358... 359 360// 反例2 361.... 362 363// 反例3 364...... 365``` 366 367### 【规则】添加清晰的代码注释 368 369【描述】 370 371示例代码中的关键内容和逻辑需要添加注释来说明,以确保开发者理解代码的作用。 372 373适时为代码块添加注释,特别是有解释说明、开发建议或注意事项的位置。恰当的注释可有效提升代码块可读性,帮助开发者快速掌握开发过程。 374 375注释符与代码块语法保持一致,禁止自创注释符。注释符与注释内容间统一添加一个空格。例如:对于ArkTS代码块,注释写法为“// 注释内容”。 376 377当一行注释内容过长时,注意断句切分到下一行呈现。 378 379代码注释应该清晰、简洁、有用,能够方便别人理解代码的含义和作用。注释应该写在代码上方或右方。 380 381【正例】 382 383```ts 384// 正例1 385// 定义生命周期回调对象 386let abilityLifecycleCallback = {}; 387 388// 正例2 389let abilityLifecycleCallback = {}; // 定义生命周期回调对象 390``` 391 392【反例】 393 394```ts 395// 反例1:注释符与注释内容之间没有添加空格 396//定义生命周期回调对象 397let abilityLifecycleCallback = {}; 398 399// 反例2:注释符与注释内容之间没有添加空格 400let abilityLifecycleCallback = {}; //定义生命周期回调对象 401 402// 反例3:注释符与代码行之间添加了多个空格 403let abilityLifecycleCallback = {}; // 定义生命周期回调对象 404``` 405 406### 【规则】同一个代码块中只放置单个文件的代码内容 407 408【描述】 409 410在技术文档中展示代码示例时,应遵循单一文件代码块原则。这意味着每个代码块应只包含来自单个文件的代码内容。这种做法有助于提高代码的可读性、可理解性,并且更贴近实际开发环境。混合多个文件的代码可能会导致读者混淆,难以理解代码的结构和上下文关系。通过遵循这一原则,可以确保代码示例清晰、有组织,并且易于复制和实施。 411 412【正例】 413 414将不同文件的代码分别放在独立的代码块中。这种方式清晰地展示了每个文件的内容,使读者能够轻松理解文件结构和组件之间的关系。例如: 415 4161. 开发`MyComponent`自定义组件。 417 418 ```ts 419 // MyComponent.ets 420 @Component 421 export struct MyComponent { 422 @State message: string = 'Hello, World!'; 423 424 build() { 425 Row() { 426 Text(this.message) 427 .fontSize(50) 428 .fontWeight(FontWeight.Bold) 429 } 430 .height('100%') 431 } 432 } 433 ``` 434 4352. 在`Index.ets`文件中引用该自定义组件。 436 437 ```ts 438 // pages/Index.ets 439 import { MyComponent } from '../common/components/MyComponent'; 440 441 @Entry 442 @Component 443 struct Index { 444 build() { 445 Column() { 446 MyComponent() 447 } 448 .width('100%') 449 .height('100%') 450 } 451 } 452 ``` 453 454【反例】 455 456将多个文件的代码混合在一个代码块中,这可能会导致读者难以区分不同文件的边界,增加理解难度,同时也不利于代码的复制和实际应用。例如: 457 458开发`MyComponent`自定义组件。在`Index.ets`文件中引用该自定义组件。 459 460```ts 461// MyComponent.ets 462@Component 463export struct MyComponent { 464 @State message: string = 'Hello, World!'; 465 466 build() { 467 Row() { 468 Text(this.message) 469 .fontSize(50) 470 .fontWeight(FontWeight.Bold) 471 } 472 .height('100%') 473 } 474} 475 476 477// pages/Index.ets 478import { MyComponent } from '../common/components/MyComponent'; 479@Entry 480@Component 481struct Index { 482 build() { 483 Column() { 484 MyComponent() 485 } 486 .width('100%') 487 .height('100%') 488 } 489} 490``` 491 492## 异常处理 493 494### 【规则】在可能的异常处添加异常捕获 495 496【描述】 497 498在可能出现异常的代码段加上异常捕获,并按不同的异常类型进行分支判断,针对不同的异常类型需要使用统一的异常分支判断方式。 499 500【正例】 501 502```ts 503// 正例1:情形一、err为undefined的场景 504if (err) { 505 // ... 506} 507 508// 正例2:情形二、err.code为非0的场景 509if (err.code) { 510 // ... 511} 512``` 513 514【反例】 515 516```ts 517// 反例1 518if (err == null) { 519 // ... 520} 521 522// 反例2 523if (err != null) { 524 // ... 525} 526 527// 反例3 528if (err == undefined) { 529 // ... 530} 531 532// 反例4 533if (err === undefined) { 534 // ... 535} 536 537// 反例5 538if (err !== undefined) { 539 // ... 540} 541``` 542 543### 【规则】使用`console.error`输出详细异常信息 544 545【描述】 546 547当存在异常情况时,统一使用`console.error(...)`方法将异常信息打印到控制台,以便在调试时能够快速发现问题。 548 549在异常处理中,应该打印出异常的详细信息以便调试。 550 551在打印异常信息时,应该使用模板字符串,并标明异常信息的`code`和`message`参数。 552 553【正例】 554 555```ts 556// 模板 557console.error(`Failed to do sth. Code: ${err.code}, message: ${err.message}`); 558 559// 正例1 560notificationManager.publish(notificationRequest, (err: BusinessError) => { 561 if (err) { 562 // 异常分支打印 563 console.error(`Failed to publish notification. Code: ${err.code}, message: ${err.message}`); 564 return; 565 } 566 // ... 567}); 568 569// 正例2 570notificationManager.publish(notificationRequest) 571 .then(() => { 572 // ... 573 }) 574 .catch((err: BusinessError) => { 575 // 异常分支打印 576 console.error(`Failed to publish notification. Code: ${err.code}, message: ${err.message}`); 577 }) 578 579// 正例3 580let pathDir: string = '<pathDir>'; // 应用文件路径 581let filePath: string = pathDir + '/test.txt'; 582try { 583 let str: string = fs.readTextSync(filePath, { offset: 1, length: 3 }); 584 console.info(`Succeeded in reading text, str is ${str}`); 585} catch (error) { 586 const err: BusinessError = error as BusinessError; 587 console.error(`Failed to read text. Code is ${err.code}, message is ${err.message}`); 588} 589``` 590 591【反例】 592 593```ts 594// 反例1:错误日志使用console.log输出,不足以让开发者在调试时快速找到问题 595notificationManager.publish(notificationRequest, (err: BusinessError) => { 596 if (err) { 597 // 异常分支打印 598 console.log(`Failed to publish notification. Code: ${err.code}, message: ${err.message}`); 599 return; 600 } 601 // ... 602}); 603 604// 反例2:错误日志使用console.info输出,而非console.error,不利于开发者区分日志级别,快速找到问题 605notificationManager.publish(notificationRequest, (err: BusinessError) => { 606 if (err) { 607 // 异常分支打印 608 console.info(`Failed to publish notification. Code: ${err.code}, message: ${err.message}`); 609 return; 610 } 611 // ... 612}); 613 614// 反例3:异常信息缺乏具体的code和message参数,不利于开发者定位和解决问题 615console.error(`Failed to publish notification, err: ${err}`); 616 617// 反例4:使用单引号而非模板字符串的双引号,导致变量无法解析,输出的日志信息不正确 618console.error('Failed to publish notification, err: ${err}'); 619 620// 反例5:使用字符串拼接和JSON序列化输出错误信息,不够直观和简洁,会增加日志产生量,不利于快速定位问题 621console.error('Failed to publish notification, err: ' + JSON.stringify(err)); 622``` 623 624## 日志打印 625 626### 【规则】使用`console.info`进行正常日志打印 627 628【描述】 629 630在代码编写过程中,为了更好地记录程序运行过程中的信息,开发者需要使用日志打印,以便于在程序出现异常时进行定位和排查。 631 632在正常情况下,使用`console.info(...)`来打印正常日志信息。 633 634【正例】 635 636```ts 637// 模板 638console.info('Succeeded in doing sth.'); 639 640// 正例 641declare function doSthAsync1(): Promise<void>; 642 643doSthAsync1() 644 .then(() => { 645 console.info('Succeeded in publishing notification.'); 646 }) 647``` 648 649【反例】 650 651```ts 652declare function doSthAsync1(): Promise<void>; 653 654// 反例1:使用console.log(...)可能会让程序员产生困惑,无法明确该日志信息是正常日志还是错误日志 655doSthAsync1() 656 .then(() => { 657 console.log('Succeeded in publishing notification.'); 658 }) 659 660// 反例2:使用了console.error(...)而不是console.info(...)来打印正常日志信息。console.error通常用于打印错误信息,而不是正常的日志信息 661doSthAsync1() 662 .then(() => { 663 console.error('Succeeded in publishing notification.'); 664 }) 665``` 666 667### 【规则】使用`Succeeded`表示成功的日志信息 668 669【描述】 670 671在日志打印中使用一致的语言,例如成功日志可以使用`Succeeded`来表达。 672 673【正例】 674 675```ts 676// 模板 677console.info('Succeeded in doing sth.'); 678 679// 正例 680declare function doSthAsync1(): Promise<void>; 681 682doSthAsync1() 683 .then(() => { 684 console.info('Succeeded in doing sth.'); 685 }) 686``` 687 688【反例】 689 690```ts 691declare function doSthAsync1(): Promise<void>; 692 693// 反例1 694doSthAsync1() 695 .then(() => { 696 console.info('Invoke do sth success.'); 697 }) 698 699// 反例2 700doSthAsync1() 701 .then(() => { 702 console.info('Invoke do sth successful.'); 703 }) 704 705// 反例3 706doSthAsync1() 707 .then(() => { 708 console.info('Invoke do sth successfully.'); 709 }) 710``` 711 712## 代码逻辑 713 714### 【规则】回调方法中使用箭头函数 715 716【描述】 717 718在使用回调方法时,应该尽可能使用箭头方法代替常规方法,箭头方法的一个主要优点是可以避免 `this` 指向问题。在箭头方法中,`this` 绑定的是它所处的上下文的 `this` 值,而不是方法被调用时的 `this` 值。这样可以避免在使用常规方法时需要使用 `bind()` 或 `call()` 来绑定 `this` 的问题,从而简化代码并提高可读性。 719 720【正例】 721 722```ts 723notificationManager.publish(notificationRequest, (err: BusinessError) => { 724 if (err) { 725 // ... 726 return; 727 } 728 console.info('Succeeded in publishing notification.'); 729}); 730``` 731 732【反例】 733 734```ts 735notificationManager.publish(notificationRequest, function (err: BusinessError) { 736 if (err) { 737 // ... 738 return; 739 } 740 console.info('Succeeded in publishing notification.'); 741}); 742``` 743 744### 【规则】禁止使用废弃接口 745 746【描述】 747 748禁止使用废弃接口,因为他们可能会在未来的版本中被删除或更改。使用最新的接口可以确保代码的稳定性和可维护性。 749 750使用废弃接口可能导致代码不稳定,也可能会因为该接口在后续版本的更新中被废弃而引发编译错误、运行错误等问题。 751 752如果在后续的版本中废除了某些接口,所有依赖这些接口的示例代码都需要同步适配修改。 753 754【正例】 755 756```ts 757import { common } from '@kit.AbilityKit'; 758import { fileIo } from '@kit.CoreFileKit'; 759 760/** 761 * 获取应用文件路径 762 **/ 763const context: common.UIAbilityContext = this.context; // UIAbilityContext 764let filesDir: string = context.filesDir; 765 766// 新建并打开文件 767let file: fileIo.File = fileIo.openSync(filesDir + '/test.txt', fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); 768// 写入一段内容至文件 769let writeLen: number = fileIo.writeSync(file.fd, 'Try to write str.'); 770// 从文件读取一段内容 771let buf: ArrayBuffer = new ArrayBuffer(1024); 772let readLen: number = fileIo.readSync(file.fd, buf, { 773 offset: 0 774}); 775// 关闭文件 776fileIo.closeSync(file); 777``` 778 779【反例】 780 781```ts 782// 使用了废弃的fileio接口,而不是推荐的fileIo接口 783import fileio from '@ohos.fileio'; 784import common from '@ohos.app.ability.common'; 785 786// 获取应用文件路径 787const context: common.UIAbilityContext = this.context; // UIAbilityContext 788let filesDir: string = context.filesDir; 789 790// 新建并打开文件 791let fileFD: number = fileio.openSync(filesDir + '/test.txt', 0o102, 0o640); 792// 写入一段内容至文件 793let writeLen: number = fileio.writeSync(fileFD, 'Try to write str.'); 794// 从文件读取一段内容 795let buf: ArrayBuffer = new ArrayBuffer(1024); 796let readLen: number = fileio.readSync(fileFD, buf, { offset: 0 }); 797// 关闭文件 798fileio.closeSync(fileFD); 799```