美文网首页Flutter圈子Flutter 入门与实战
Dart 的函数参数怎么传更合理?

Dart 的函数参数怎么传更合理?

作者: 岛上码农 | 来源:发表于2022-05-14 19:48 被阅读0次

    前言

    函数是我们用得最多的了,通过函数可以将复杂业务拆分为简短精悍的函数,从而分解大业务,提高可维护性和代码复用性。在设计函数的时候,接收什么样的参数最为关键,参数定义了函数的输入。本篇来介绍 Dart 函数的合理使用。

    Dart 函数参数简介

    在 Dart 中,将参数传递给函数的方式有两种,包括占位方式和命名方式,再加上是否可为空组合起来有4种,如下所示。

    // 占位参数形式,第二个参数需要传
    void someFunction1(int a,  String? b) {
      // ...
    }
    
    // 占位参数形式,第二个参数可不传
    void someFunction2(int a,  [String? b]) {
      // ...
    }
    
    // 命名参数形式,p 为必传参数
    void someFunction4(int a,  {required Person p, Student? s} ) {
      // ...
    }
    
    
    // 命名参数形式,p 和 s 为可选参数
    void someFunction4(int a,  {Person? p, Student? s} ) {
      // ...
    }
    

    其中可选占位参数使用[...]形式,表示其中的参数可传可不传。命名参数采用{...}形式,如果参数必传则需要声明为 required。注意,可选占位参数和命名参数不可同时使用,例如下面的形式会报错。

    // 错误!可选占位参数和命名参数不可共用
    void someFunction4(int a, [String? b], {Person? p, Student? s} ) {
      // ...
    }
    

    由于 Dart 存在这两种形式,按说是用哪种形式都行,但是实际应用中还是需要遵循一定的规范。

    规范1:避免对布尔参数使用占位形式传递

    由于布尔值在字面上很难知晓具体的意思,因此,布尔参数若作为占位形式参数传递的话,会降低可读性。比如下面的例子,如果不去看函数定义、文档或内部代码,很难知道布尔值的意义。

    // 错误示例
    // 实际是创建单次任务
    Task(true);
    // 循环任务
    Task(false);
    // 呃,看不懂这是啥?
    ListBox(false, true, true);
    // 禁用状态的 button
    Button(false);
    

    这种情况,对于构造函数,可以使用命名构造函数或命名参数的形式,语义上会清晰很多。下面的代码,即便没有注释也能够看得懂。

    // 正确示例
    Task.oneShot();
    Task.repeating();
    ListBox(scroll: true, showScrollbars: true);
    Button(ButtonState.enabled);
    

    当然,对于 setter 而言,因为有上下文,使用布尔值是没问题的。

    // 正确示例
    listBox.canScroll = true;
    button.isEnabled = false;
    

    规范2:对于可选占位参数,按频次设置合理的次序

    可选占位参数意味着可以不传,如果不怎么用的参数放在前面的话,会导致如果需要传后面的参数就必须给该参数赋值,而如果没有值就得传 null,这样的代码会很难看。

    // 错误示例
    void someFunction(int a, [int? b, int? c, int d = 0]) {
      //...
    }
    
    // 调用示例
    someFunction(1, null, null, 12);
    

    上面的例子中,d 可能是经常要传的参数,结果放到了最后,导致调用者很郁闷,每次传 d 的时候都得给 b 和 c 传 null。正确的示例如下:

    // 正确示例
    void someFunction(int a, [int d = 0, int? b, int? c]) {
      //...
    }
    
    // 调用示例
    someFunction(1, 12);
    

    当然,这种情况,使用命名参数更合理。

    // 正确示例
    void someFunction(int a, {int d = 0, int? b, int? c}) {
      //...
    }
    
    // 调用示例
    someFunction(1, d: 12);
    

    规范3:避免要求传一些无意义的参数

    如果我们需要省略参数传递,那么应该就直接省略,而不是传没有意义的参数,例如 null、空字符串等等。省略参数看起来更简洁,也能够避免空值导致的错误 —— 这会让函数以为传入了一个有意义的参数。

    // 正确示例
    var rest = string.substring(start);
    
    // 错误示例
    var rest = string.substring(start, null);
    

    规范4:对于范围参数,遵循起始参数是闭区间,结束参数应该是开区间的约定

    我们在获取范围值的时候,会需要传入起止位置,通常是整数下标值,一个起始下标表示从第几个元素开始,一个结束下标表示到哪里为止。由于这类参数通常是占位参数而非命名参数,因此建议是和 Dart的核心库的用法保持一致 —— 起始位置包括在内,而结束位置不包括在内,用数学区间表示就是 [s, e)形式。下面是 Dart 核心库的示例:

    // 正确示例
    [0, 1, 2, 3].sublist(1, 3) // [1, 2]
    'abcd'.substring(1, 3) // 'bc
    

    规范5:对于可选参数,尽量设置默认值

    可选参数如果没有默认值,在不传递的时候,会默认为 null。如果不小心使用的话,可能会导致出现 bug。因此,假设编码的时候知道默认状态,那么请尽量设置默认值,例如下面的 DateTimeDuration 的例子:

    //正确示例
    DateTime(int year,
        [int month = 1,
        int day = 1,
        int hour = 0,
        int minute = 0,
        int second = 0,
        int millisecond = 0,
        int microsecond = 0]);
    
    Duration(
        {int days = 0,
        int hours = 0,
        int minutes = 0,
        int seconds = 0,
        int milliseconds = 0,
        int microseconds = 0});
    

    总结

    本篇介绍了 Dart 的函数参数传递形式以及5条规范。个人来说,更倾向于使用命名参数,因为在调用函数的时候会明确参数语义 —— 尤其是参数很多的时候。此外,建议明确参数是否是 required,从而约束调用者的参数传递行为。对于知道默认值的可选参数 ,推荐设置默认值。这样我们可以省略很多 null 判断。

    相关文章

      网友评论

        本文标题:Dart 的函数参数怎么传更合理?

        本文链接:https://www.haomeiwen.com/subject/blemurtx.html