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
287Index.ets文件中实现页面跳转。
288
289### 【规则】代码示例使用代码块进行代码染色
290
291【描述】
292
293对代码示例、命令行使用代码样式。在Markdown中,使用```呈现代码样式,同时指定语言类型。
294
295代码染色是指在编辑器中对代码进行不同颜色的标记,以区分不同语法元素的功能。例如在编辑器中对不同的关键字、变量名、注释等使用不同的颜色进行标记,可以让代码更加易读易懂。
296
297![代码块示例](figures/code-block-example.png)
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```