Flutter 語(yǔ)法規(guī)范

命名

  • 1.1 庫(kù)名、文件名使用小寫(xiě)加下劃線來(lái)命名
    ? library prometheumTools
    ? prometheum-tools.dart
    ? library prometheum_tools
    ? prometheum_tools.dart
  • 1.2 類(lèi)、枚舉、類(lèi)型使用大駝峰規(guī)則
    ? class trademodel{...}
    ? typedef voidCallback = void Function();
    ? enum app_lifecycle_state { ... }
    ? class Trademodel{...}
    ? typedef VoidCallback = void Function();
    ? enum AppLifecycleState { ... }
  • 1.3 使用小寫(xiě)或小寫(xiě)加下劃線來(lái)命名導(dǎo)入前綴
    ? import 'dart:math' as MATH;
    ? import 'dart:math' as Math;
    ? import 'dart:math' as math;
    ? import 'dart:math' as dart_math;
  • 1.4 使用小駝峰命名變量名、函數(shù)名、枚舉case、參數(shù)名
    ? String DogName = 'dudu';
    ? String dog_name = 'dudu';
    ? int FindMax() {....};
    ? String dogName = 'dudu';
    ? int findMax() {....};
    ? enum ActionType { touchUp, touchDown, ... };
  • 1.5 變量名、函數(shù)名、類(lèi)型名、類(lèi)名等應(yīng)語(yǔ)義化,做到見(jiàn)名知意
    ? String a = "zhangsan";
    ? class Abc{ ... }
    ? void doAbc(){...}
    ? String name = "zhangsan";
    ? class TradePage{ ... }
    ? Widget chartLine(){...}
  • 1.6 變量名不要使用前綴字母
    ? double kDefaultTimeout = 5.0;
    ? double defaultTimeout = 5.0;

導(dǎo)入

  • 2.1 導(dǎo)入順序:先導(dǎo)入dart庫(kù),然后導(dǎo)入flutter庫(kù),其次導(dǎo)入三方庫(kù),再次導(dǎo)入項(xiàng)目工具庫(kù),最后導(dǎo)入自寫(xiě)的功能庫(kù)
import 'dart:async';
import 'dart:html';

import 'package:flutter/cupertino.dart'
import 'package:flutter/material.dart';

import 'package:oktoast/oktoast.dart';
import 'package:provider/provider.dart';

import 'extensions/color_extension.dart';
import 'extensions/double_extension.dart';

import './business/main/config/app_locales.dart';
import './business/main/config/app_router.dart';
  • 2.2 導(dǎo)入方式:
    dart庫(kù):import 'dart:async';
    flutter庫(kù)以package協(xié)議導(dǎo)入:import 'package:flutter/material.dart';
    三方庫(kù)以package或http協(xié)議導(dǎo)入:import 'package:provider/provider.dart';
    自寫(xiě)庫(kù)使用./或../以相對(duì)路徑的方式導(dǎo)入:import './business/main/config/app_locales.dart';

變量

  • 3.1 不需要外部訪問(wèn)的變量請(qǐng)使用下劃線開(kāi)頭
    ? double _treasure = 100000000.0;
  • 3.2 編譯時(shí)需要確定的常量請(qǐng)使用const修飾
    ? const String url = "http://www.baidu.com";
  • 3.3 運(yùn)行時(shí)才確定的常量請(qǐng)使用final修飾
    ? final DateTime now = DateTime.now();
  • 3.4 只需要外部讀取或賦值的請(qǐng)?zhí)峁┫鄳?yīng)的get 或 set方法
class Person{
  int _age;
  
  set age(int value){
    _age = value;
  }

  int get age {
    return _age;
  }
}
  • 3.5 不要顯示的給一個(gè)變量賦值為null,因?yàn)閐art會(huì)幫我們將未賦初始值的變量設(shè)置為bull
    ? String name = null;
    ? String name;
  • 3.6 使用確定的類(lèi)型聲明變量
    ? var name;
    ? String name";

函數(shù)

  • 4.1 構(gòu)造函數(shù)
    不推薦如下寫(xiě)法:
  class Point {
    num x, y;
    Point(num x, num y) {
      this.x = x;
      this.y = y;
    }
  }

推薦如下寫(xiě)法:

class Point {
  num x, y;
  Point(this.x, this.y);
}
  • 4.2 可選參數(shù),盡量使用命名可選參數(shù),少使用位置可選參數(shù)

不推薦:

Widget _sortBtn(context,[String title,Image image]){
    return Container();
}

推薦:

Widget _sortBtn(context,{String title,Image image}){
    return Container();
}
  • 4.3 單個(gè)必選參數(shù)可以省略參數(shù)名,多個(gè)必傳參數(shù)的時(shí)候也需要參數(shù)名

不推薦:

  Widget sortBtn(BuildContext context,String title){
    return Container();
  }

推薦:

  Widget sortBtn({@required BuildContext context,@required String title}){
    return Container();
  }
  • 4.4 不需要被外部調(diào)用的函數(shù),其名稱(chēng)請(qǐng)以下劃線開(kāi)始

不推薦:

  Widget sortBtn(BuildContext context,String title){
    return Container();
  }

推薦:

  Widget _sortBtn({@required BuildContext context,@required String title}){
    return Container();
  }

集合

  • 5.1 聲明集合變量的時(shí)候盡可能的指定類(lèi)型

不推薦:

List studentNames = [];
Map studentIdAndName = {};

推薦:

List<String> studentNames = [];
Map<String,String> studentIdAndName = {};
  • 5.2 不要使用集合的length屬性去做判空,而是使用isEmpty

不推薦:

List<String> studentNames = [];
if (studentNames.length == 0) {
    ...
}

Map<String,String> studentIdAndName = {};
if (studentIdAndName.length == 0) {
    ...
}

推薦:

List<String> studentNames = [];
if (studentNames.isEmpty) {
    ...
}

Map<String,String> studentIdAndName = {};
if (studentIdAndName.isEmpty) {
    ...
}
  • 5.3 使用字面量創(chuàng)建集合

不推薦:

List list = List();
Map map = Map();

推薦:

List<String> list = <String>[];
Map<String, dynamic> map = <String, dynamic>{};

代碼格式

6.1 if后的大括號(hào)與else在同一行

不推薦:

if (條件) {

} 
else if(條件) {

}
else{

}

推薦:

if (條件) {

} else if(條件) {

} else {

}
  • 6.2 類(lèi)、枚舉、函數(shù)名后緊跟大括號(hào)

不推薦:

class Person
{
    ...
}

推薦:

class Person {
    ...
}
  • 6.3 所有流控制結(jié)構(gòu)請(qǐng)使用大括號(hào)括起來(lái),包括一個(gè)if語(yǔ)句沒(méi)有else子句的情況,也包括if代碼塊里只有一條語(yǔ)句的情況

推薦:

if (條件) {

} else {

}

if (條件) {
}

不推薦

if(條件) return value;
或者 
if (條件)
    return value;

6.4 單行代碼字符不易超過(guò)80

6.5 鏈?zhǔn)秸{(diào)用過(guò)多和級(jí)聯(lián)調(diào)用請(qǐng)分多行寫(xiě)

不推薦:

Paint paint = Paint()..color = Colors.black12..isAntiAlias = false..strokeCap = StrokeCap.round..strokeWidth = 2..style = PaintingStyle.stroke;

var abd = Provider.of<MarketProvider>(context,listen: true).coinList[0].candle.first.price.toString().toDouble;

推薦:

Paint paint = Paint()
      ..color = Colors.black12
      ..isAntiAlias = false
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke;
      
var abd = Provider.of<MarketProvider>(context,listen: true)
    .coinList[0]
    .candle
    .first
    .price
    .toString()
    .toDouble;

注釋

  • 7.1 函數(shù)外的單行注釋請(qǐng)使用///

不推薦:

/* Assume we have a valid name. */
 greet(name) {
    print('Hi, $name!');
  }

推薦:

/// Assume we have a valid name.
greet(name) {
    print('Hi, $name!');
}
  • 7.2 函數(shù)內(nèi)的單行注釋?xiě)?yīng)在代碼上面另起一行

不推薦:

 greet(name) {
    print('Hi, $name!');//Assume we have a valid name.
  }

推薦:

greet(name) {
// Assume we have a valid name.
    print('Hi, $name!');
}
  • 7.3 使用/**/臨時(shí)注釋掉一段代碼,除此之外所有的注釋都應(yīng)該使用///

不推薦:

//greet(name) {
//    print('Hi, $name!');
//}

推薦:

/*
greet(name) {
    print('Hi, $name!');
}
*/
  • 7.4 Doc 注釋?zhuān)?br> 使用///文檔注釋來(lái)記錄成員和類(lèi)型。使用doc注釋而不是常規(guī)注釋?zhuān)梢宰宒artdoc找到并生成文檔。用一句話總結(jié)開(kāi)始doc注釋?zhuān)院?jiǎn)短的、以用戶為中心的描述開(kāi)始你的文檔注釋?zhuān)跃涮?hào)結(jié)尾。“doc注釋”的第一句話分隔成自己的段落。

不推薦:

  /// Depending on the state of the file system and the user's permissions,
  /// certain operations may or may not be possible. If there is no file at
  /// [path] or it can't be accessed, this function throws either [IOError]
  /// or [PermissionError], respectively. Otherwise, this deletes the file.
  void delete(String path) {
    ...
  }

推薦:

  /// Deletes the file at [path].
  ///
  /// Throws an [IOError] if the file could not be found. Throws a
  /// [PermissionError] if the file is present but could not be deleted.
  void delete(String path) {
    ...
  }

其他

8.1 dart 2.0中創(chuàng)建新對(duì)象已經(jīng)省略 new 關(guān)鍵字了
? Object obj = new Object();
?Object obj = Object();
8.2 定義const常量時(shí),不要=兩邊都使用const,推薦=左邊使用const
? const Object obj = const Object();
?const Object obj = Object();
8.3 組件嵌套不要超過(guò)3層,大于3層是請(qǐng)拆分

不推薦:

  Widget _marketItem() {
    return InkWell(
      onTap: (){},
      child: Container(
          child:Column(
            children: <Widget>[
              OutlineButton(
                child:Row(
                  children: <Widget>[
                    Text('title'),
                    Container(
                      margin: EdgeInsets.only(left:12),
                      child:Image.asset('assets/images/arrowUp.png'),
                    ),
                  ],
                ),
              ),
            ],
          )
      ),
    );
  }

推薦:

 Widget _marketItem() {
    return InkWell(
      onTap: (){},
      child: Container(
          child:Column(
            children: <Widget>[
              _outLineBtn(),
            ],
          )
      ),
    );
  }
  
  Widget _outLineBtn() {
    return OutlineButton(
      child:Row(
        children: <Widget>[
          Text('title'),
          Container(
            margin: EdgeInsets.only(left:12),
            child:Image.asset('assets/images/arrowUp.png'),
          ),
        ],
      ),
    );
  }

8.4 所有條件語(yǔ)句的結(jié)果值一定要是bool類(lèi)型的

錯(cuò)誤:

  if(model?.isSelected) {
    
  }

正確:

  bool isSelected = model?.isSelected ?? false;
  if(isSelected) {

  }

Flutter代碼開(kāi)發(fā)規(guī)范0.1.0

Style

-定義了布局和組織代碼的規(guī)則

標(biāo)識(shí)符

在 Dart 中標(biāo)識(shí)符有三種類(lèi)型。

  • UpperCamelCase 每個(gè)單詞的首字母都大寫(xiě),包含第一個(gè)單詞。
  • lowerCamelCase 每個(gè)單詞的首字母都大寫(xiě),除了第一個(gè)單詞, 第一個(gè)單詞首字母小寫(xiě),即使是縮略詞。
  • lowercase_with_underscores 只是用小寫(xiě)字母單詞,即使是縮略詞, 并且單詞之間使用 _ 連接。
使用 UpperCamelCase 風(fēng)格命名類(lèi)型

Classes(類(lèi)名)、 enums(枚舉類(lèi)型)、 typedefs(類(lèi)型定義)、 以及 type parameters(類(lèi)型參數(shù))應(yīng)該把每個(gè)單詞的首字母都大寫(xiě)(包含第一個(gè)單詞), 不使用分隔符。

?正確示范

class SliderMenu { ... }

class HttpRequest { ... }

typedef Predicate = bool Function<T>(T value);

這里包括使用元數(shù)據(jù)注解的類(lèi)。

class Foo {
  const Foo([arg]);
}

@Foo(anArg)
class A { ... }

@Foo()
class B { ... }
使用lowercase_with_underscores風(fēng)格命名庫(kù)、包 、文件夾、源文件名

?正確示范

library peg_parser.source_scanner;

import 'file_system.dart';
import 'slider_menu.dart';

?錯(cuò)誤 示范

library pegparser.SourceScanner;

import 'file-system.dart';
import 'SliderMenu.dart';
使用 lowercase_with_underscores 風(fēng)格命名導(dǎo)入的前綴

?正確示范

import 'dart:math' as math;
import 'package:angular_components/angular_components'
    as angular_components;
import 'package:js/js.dart' as js;

?錯(cuò)誤 示范

import 'dart:math' as Math;
import 'package:angular_components/angular_components'
    as angularComponents;
import 'package:js/js.dart' as JS;
使用 lowerCamelCase 風(fēng)格來(lái)命名其他的標(biāo)識(shí)符

類(lèi)成員、頂級(jí)定義、變量、參數(shù)以及命名參數(shù)等 除了第一個(gè)單詞,每個(gè)單詞首字母都應(yīng)大寫(xiě),并且不使用分隔符。

var item;

HttpRequest httpRequest;

void align(bool clearItems) {
  // ...
}
優(yōu)先使用 lowerCamelCase 來(lái)命名常量

在新的代碼中,使用 lowerCamelCase 來(lái)命名常量,包括枚舉的值。 已有的代碼使用了 SCREAMING_CAPS 風(fēng)格, 你可以繼續(xù)全部使用該風(fēng)格來(lái)保持代碼的一致性。

?正確示范

const pi = 3.14;
const defaultTimeout = 1000;
final urlScheme = RegExp('^([a-z]+):');

class Dice {
  static final numberGenerator = Random();
}

?錯(cuò)誤示范

const PI = 3.14;
const DefaultTimeout = 1000;
final URL_SCHEME = RegExp('^([a-z]+):');

class Dice {
  static final NUMBER_GENERATOR = Random();
}
把超過(guò)兩個(gè)字母的首字母大寫(xiě)縮略詞和縮寫(xiě)詞當(dāng)做一般單詞來(lái)對(duì)待

首字母大寫(xiě)縮略詞比較難閱讀, 特別是多個(gè)縮略詞連載一起的時(shí)候會(huì)引起歧義。 例如,一個(gè)以 HTTPSFTP 開(kāi)頭的名字, 沒(méi)有辦法判斷它是指 HTTPS FTP 還是 HTTP SFTP 。

為了避免上面的情況,縮略詞和縮寫(xiě)詞要像普通單詞一樣首字母大寫(xiě), 兩個(gè)字母的單詞除外。 (像 ID 和 Mr. 這樣的雙字母縮寫(xiě)詞仍然像一般單詞一樣首字母大寫(xiě)。)

?正確示范

HttpConnectionInfo
uiHandler
IOStream
HttpRequest
Id
DB

?錯(cuò)誤 示范

HTTPConnection
UiHandler
IoStream
HTTPRequest
ID
Db
不要 使用前綴字母

?正確示范

defaultTimeout

?錯(cuò)誤 示范

kDefaultTimeout

順序

為了使文件前面部分保持整潔,規(guī)定關(guān)鍵字出現(xiàn)順序。 每個(gè)“部分”應(yīng)該使用空行分割。

把 “dart:” 導(dǎo)入語(yǔ)句放到其他導(dǎo)入語(yǔ)句之前
import 'dart:async';
import 'dart:html';

import 'package:bar/bar.dart';
import 'package:foo/foo.dart';
把 “package:” 導(dǎo)入語(yǔ)句放到項(xiàng)目相關(guān)導(dǎo)入語(yǔ)句之前
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

import 'util.dart';
優(yōu)先把外部擴(kuò)展 “package:” 導(dǎo)入語(yǔ)句放到其他語(yǔ)句之前

如果使用了多個(gè) “package:” 導(dǎo)入語(yǔ)句來(lái)導(dǎo)入自己的包以及其他外部擴(kuò)展包, 推薦將自己的包分開(kāi)放到一個(gè)額外的部分。

import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

import 'package:my_package/util.dart';
把導(dǎo)出(export)語(yǔ)句作為一個(gè)單獨(dú)的部分放到所有導(dǎo)入語(yǔ)句之后

?正確示范

import 'src/error.dart';
import 'src/foo_bar.dart';

export 'src/error.dart';

?錯(cuò)誤示范

import 'src/error.dart';
export 'src/error.dart';
import 'src/foo_bar.dart';

格式化

使用 dartfmt 格式化你的代碼

代碼編輯完成后,鼠標(biāo)右鍵編輯器,在彈出窗中找到reformat code with dartfmt或者快捷鍵 cmd+option+L

盡量避免單行超過(guò)80個(gè)字符
對(duì)所有流控制結(jié)構(gòu)使用花括號(hào)
if (isWeekDay) {
  print('Bike to work!');
} else {
  print('Go dancing or read a book!');
}

這里有一個(gè)例外:一個(gè)沒(méi)有 elseif 語(yǔ)句, 并且這個(gè) if 語(yǔ)句以及它的執(zhí)行體適合在一行中實(shí)現(xiàn)。 在這種情況下,如果您愿意,可以不用括號(hào):

if (arg == null) return defaultValue;

但是,如果執(zhí)行體包含下一行,請(qǐng)使用大括號(hào):

if (overflowChars != other.overflowChars) {
  return overflowChars < other.overflowChars;
}

?錯(cuò)誤示范

if (overflowChars != other.overflowChars)
  return overflowChars < other.overflowChars;

Documentation

盡可能為類(lèi)、成員變量、方法、關(guān)鍵邏輯判斷寫(xiě)注釋

// 單行注釋

/// 文檔注釋

/** 塊狀注釋 */

注釋

像句子一樣來(lái)格式化注釋
// Not if there is nothing before it.
if (_chunks.isEmpty) return false;

如果第一個(gè)單詞不是大小寫(xiě)相關(guān)的標(biāo)識(shí)符,則首字母要大寫(xiě)。 使用句號(hào),嘆號(hào)或者問(wèn)號(hào)結(jié)尾。所有的注釋都應(yīng)該這樣: 文檔注釋?zhuān)瑔涡凶⑨專(zhuān)踔?TODO。即使它是一個(gè)句子的片段。

不要使用塊注釋作用作解釋說(shuō)明

可以使用塊注釋 (/* ... */) 來(lái)臨時(shí)的注釋掉一段代碼, 但是其他的所有注釋都應(yīng)該使用 //

?錯(cuò)誤示范

greet(name) {
  /* Assume we have a valid name. */
  print('Hi, $name!');
}

?正確示范

greet(name) {
  // Assume we have a valid name.
  print('Hi, $name!');
}

文檔注釋

使用 /// 文檔注釋來(lái)注釋成員和類(lèi)型

?正確示范

/// The number of characters in this chunk when unsplit.
int get length => ...

?錯(cuò)誤示范

// The number of characters in this chunk when unsplit.
int get length => ...
要在文檔注釋開(kāi)頭有一個(gè)單句總結(jié)

注釋文檔要以一個(gè)以用戶為中心,簡(jiǎn)要的描述作為開(kāi)始。 通常句子片段就足夠了。為讀者提供足夠的上下文來(lái)定位自己, 并決定是否應(yīng)該繼續(xù)閱讀,或?qū)ふ移渌鉀Q問(wèn)題的方法。

?正確示范

/// Deletes the file at [path] from the file system.
void delete(String path) {
  ...
}

?錯(cuò)誤示范

/// Depending on the state of the file system and the user's permissions,
/// certain operations may or may not be possible. If there is no file at
/// [path] or it can't be accessed, this function throws either [IOError]
/// or [PermissionError], respectively. Otherwise, this deletes the file.
void delete(String path) {
  ...
}
讓文檔注釋的第一句從段落中分開(kāi)

用一句話總結(jié)文檔,在其后添加一個(gè)空行,將剩余的部分放在后面的段落中

?正確示范

/// Deletes the file at [path].
///
/// Throws an [IOError] if the file could not be found. Throws a
/// [PermissionError] if the file is present but could not be deleted.
void delete(String path) {
  ...
}

?錯(cuò)誤示范

/// Deletes the file at [path]. Throws an [IOError] if the file could not
/// be found. Throws a [PermissionError] if the file is present but could
/// not be deleted.
void delete(String path) {
  ...
}
使用方括號(hào)在文檔注釋中引用作用域內(nèi)的標(biāo)識(shí)符

給變量,方法,或類(lèi)型等名稱(chēng)加上方括號(hào),讓注釋更清晰

/// Throws a [StateError] if ...
/// similar to [anotherMethod()], but ...

要鏈接到特定類(lèi)的成員,請(qǐng)使用以點(diǎn)號(hào)分割的類(lèi)名和成員名:

/// Similar to [Duration.inDays], but handles fractional days.

點(diǎn)語(yǔ)法也可用于引用命名構(gòu)造函數(shù)。 對(duì)于未命名的構(gòu)造函數(shù),在類(lèi)名后面加上括號(hào)

/// To create a point, call [Point()] or use [Point.polar()] to ...
使用散文的方式來(lái)描述參數(shù)、返回值以及異常信息
/// Defines a flag.
///
/// Throws an [ArgumentError] if there is already an option named [name] or
/// there is already an option using abbreviation [abbr]. Returns the new flag.
Flag addFlag(String name, String abbr) => ...

把文檔注釋放到注解之前

?正確示范

/// A button that can be flipped on and off.
@Component(selector: 'toggle')
class ToggleComponent {}

?錯(cuò)誤示范

@Component(selector: 'toggle')
/// A button that can be flipped on and off.
class ToggleComponent {}

Usage

庫(kù)

part of 中使用字符串

很多 Dart 開(kāi)發(fā)者會(huì)避免直接使用 part 。他們發(fā)現(xiàn)當(dāng)庫(kù)僅有一個(gè)文件的時(shí)候很容易讀懂代碼。 如果你確實(shí)要使用 part 將庫(kù)的一部分拆分為另一個(gè)文件,則 Dart 要求另一個(gè)文件指示它所屬庫(kù)的路徑。 由于遺留原因, Dart 允許 part of 指令使用它所屬的庫(kù)的名稱(chēng)。 這使得工具很難直接查找到這個(gè)文件對(duì)應(yīng)主庫(kù)文件,使得庫(kù)和文件之間的關(guān)系模糊不清。

推薦的現(xiàn)代語(yǔ)法是使用 URI 字符串直接指向庫(kù)文件。 首選的現(xiàn)代語(yǔ)法是使用直接指向庫(kù)文件的URI字符串,URI 的使用和其他指令中一樣。 如果你有一些庫(kù),my_library.dart,其中包含:

library my_library;

part "some/other/file.dart";

從庫(kù)中拆分的文件應(yīng)該如下所示:

part of "../../my_library.dart";

?錯(cuò)誤示范

part of my_library;
使用相對(duì)路徑在導(dǎo)入你自己 package 中的 lib 目錄

在同一個(gè) package 下其中一個(gè)庫(kù)引用另一個(gè) lib 目錄下的庫(kù)時(shí), 應(yīng)該使用相對(duì)的 URI 或者直接使用 package:。

比如,下面是你的 package 目錄結(jié)構(gòu):

my_package
└─ lib
   ├─ src
   │  └─ utils.dart
   └─ api.dart

如果 api.dart 想導(dǎo)入 utils.dart ,應(yīng)該這樣使用:

import 'src/utils.dart';

而不是:

import 'package:my_package/src/utils.dart';

字符串

使用相鄰字符串連接字符串文字

如果你有兩個(gè)字面量字符串(不是變量,是放在引號(hào)中的字符串),你不需要使用 + 來(lái)連接它們。 應(yīng)該想 C 和 C++ 一樣,只需要將它們挨著在一起就可以了。 這種方式非常適合不能放到一行的長(zhǎng)字符串的創(chuàng)建。

?正確示范

raiseAlarm(
    'ERROR: Parts of the spaceship are on fire. Other '
    'parts are overrun by martians. Unclear which are which.');

?錯(cuò)誤示范

raiseAlarm('ERROR: Parts of the spaceship are on fire. Other ' +
    'parts are overrun by martians. Unclear which are which.');
使用插值的形式來(lái)組合字符串和值

?正確示范

'Hello, $name! You are ${year - birth} years old.';

?錯(cuò)誤示范

'Hello, ' + name + '! You are ' + (year - birth).toString() + ' y...';
在不需要的時(shí)候,避免使用花括號(hào)

如果要插入是一個(gè)簡(jiǎn)單的標(biāo)識(shí)符,并且后面沒(méi)有緊跟隨在其他字母文本,則應(yīng)省略 {} 。

?正確示范

'Hi, $name!'
"Wear your wildest $decade's outfit."
'Wear your wildest ${decade}s outfit.'

?錯(cuò)誤示范

'Hi, ${name}!'
"Wear your wildest ${decade}'s outfit."

集合

使用集合字面量

有兩種方式來(lái)構(gòu)造一個(gè)空的可變 list : []List() 。有三總方式來(lái)構(gòu)造一個(gè)空的鏈表哈希 map:{}, Map(), 和 LinkedHashMap() 。

如果想創(chuàng)建一個(gè)固定不變的 list 或者其他自定義集合類(lèi)型,這種情況下你需要使用構(gòu)造函數(shù)。 否則,使用字面量語(yǔ)法更加優(yōu)雅

?正確示范

var points = [];
var addresses = {};

?錯(cuò)誤示范

var points = List();
var addresses = Map();
不要 使用 .length 來(lái)判斷一個(gè)集合是否為空

調(diào)用 .length 來(lái)判斷集合是否包含內(nèi)容是非常低效的,Dart 提供了更加高效率和易用的 getter 函數(shù):.isEmpty.isNotEmpty

?正確示范

if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');

?錯(cuò)誤示范

if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');
盡量使用高階函數(shù)來(lái)轉(zhuǎn)換集合數(shù)據(jù)

如果你有一個(gè)集合并且想要修改里面的內(nèi)容轉(zhuǎn)換為另外一個(gè)集合, 使用 .map()、 .where() 以及 Iterable 提供的其他函數(shù)會(huì)讓代碼更加簡(jiǎn)潔

var aquaticNames = animals
    .where((animal) => animal.isAquatic)
    .map((animal) => animal.name);
避免使用帶有函數(shù)字面量的Iterable.forEach()

在Dart中,如果你想遍歷一個(gè)序列,慣用的方法是使用循環(huán)。

?正確示范

for (var person in people) {
  ...
}

?錯(cuò)誤示范

people.forEach((person) {
  ...
});

例外:如果要執(zhí)行的操作是調(diào)用一些已存在的并且將每個(gè)元素作為參數(shù)的函數(shù), 在這種情況下,forEach() 是很方便的。

people.forEach(print);
不要 使用 List.from() 除非想修改結(jié)果的類(lèi)型

給定一個(gè)可迭代的對(duì)象,有兩種常見(jiàn)方式來(lái)生成一個(gè)包含相同元素的 list:

var copy1 = iterable.toList();
var copy2 = List.from(iterable);

明顯的區(qū)別是前一個(gè)更短。 更重要的區(qū)別在于第一個(gè)保留了原始對(duì)象的類(lèi)型參數(shù):

// Creates a List<int>:
var iterable = [1, 2, 3];

// Prints "List<int>":
print(iterable.toList().runtimeType);
// Creates a List<int>:
var iterable = [1, 2, 3];

// Prints "List<dynamic>":
print(List.from(iterable).runtimeType);

如果你想要改變類(lèi)型,那么可以調(diào)用 List.from()

var numbers = [1, 2.3, 4]; // List<num>.
numbers.removeAt(1); // Now it only contains integers.
var ints = List<int>.from(numbers);
使用 whereType() 按類(lèi)型過(guò)濾集合
var objects = [1, "a", 2, "b", 3];
var ints = objects.whereType<int>();

函數(shù)

在 Dart 中,就連函數(shù)也是對(duì)象

使用函數(shù)聲明的方式為函數(shù)綁定名稱(chēng)

如果你確實(shí)需要給方法一個(gè)名字,請(qǐng)使用方法定義而不是把 lambda 賦值給一個(gè)變量

?正確示范

void main() {
  localFunction() {
    ...
  }
}

?錯(cuò)誤示范

void main() {
  var localFunction = () {
    ...
  };
}
不要 使用 lambda 表達(dá)式來(lái)替代 tear-off

如果你在一個(gè)對(duì)象上調(diào)用函數(shù)并省略了括號(hào), Dart 稱(chēng)之為”tear-off”—一個(gè)和函數(shù)使用同樣參數(shù)的閉包, 當(dāng)你調(diào)用閉包的時(shí)候執(zhí)行其中的函數(shù)。

如果你有一個(gè)方法,這個(gè)方法調(diào)用了參數(shù)相同的另一個(gè)方法。 那么,你不需要人為將這個(gè)方法包裝到一個(gè) lambda 表達(dá)式中。

?正確示范

names.forEach(print);

?錯(cuò)誤示范

names.forEach((name) {
  print(name);
});

參數(shù)

使用 = 來(lái)分隔參數(shù)名和參數(shù)默認(rèn)值

Dart 同時(shí)支持 := 作為參數(shù)名和默認(rèn)值的分隔符。 為了與可選的位置參數(shù)保持一致,請(qǐng)使用 =

?正確示范

void insert(Object item, {int at = 0}) { ... }

?錯(cuò)誤示范

void insert(Object item, {int at: 0}) { ... }
不要 顯式的為參數(shù)設(shè)置 null

?正確示范

void error([String message]) {
  stderr.write(message ?? '\n');
}

?錯(cuò)誤示范

void error([String message = null]) {
  stderr.write(message ?? '\n');
}

變量

不要 顯示的為參數(shù)初始化 null

在Dart中,未自動(dòng)顯式初始化的變量或字段將初始化為 null ,不要多余賦值null

?正確示范

int _nextId;

class LazyId {
  int _id;

  int get id {
    if (_nextId == null) _nextId = 0;
    if (_id == null) _id = _nextId++;

    return _id;
  }
}

?錯(cuò)誤示范

int _nextId = null;

class LazyId {
  int _id = null;

  int get id {
    if (_nextId == null) _nextId = 0;
    if (_id == null) _id = _nextId++;

    return _id;
  }
}
避免儲(chǔ)存你能計(jì)算的東西

在設(shè)計(jì)類(lèi)時(shí),您通常希望將多個(gè)視圖公開(kāi)到相同的底層狀態(tài)。通常你會(huì)看到在構(gòu)造函數(shù)中計(jì)算所有視圖的代碼,然后存儲(chǔ)它們:

?錯(cuò)誤示范

class Circle {
    num radius;
    num area;
    num circumference;
  
    Circle(num radius)
        : radius = radius,
          area = pi * radius * radius,
          circumference = pi * 2.0 * radius;
  }

如上代碼問(wèn)題:

  • 浪費(fèi)內(nèi)存
  • 緩存的問(wèn)題是無(wú)效——如何知道何時(shí)緩存過(guò)期需要重新計(jì)算?

?正確示范

class Circle {
    num radius;
  
    Circle(this.radius);
  
    num get area => pi * radius * radius;
    num get circumference => pi * 2.0 * radius;
  }

成員

不要 為字段創(chuàng)建不必要的 getter 和 setter 方法

?正確示范

class Box {
  var contents;
}

?錯(cuò)誤示范

class Box {
  var _contents;
  get contents => _contents;
  set contents(value) {
    _contents = value;
  }
}
使用 final 關(guān)鍵字來(lái)創(chuàng)建只讀屬性

如果你有一個(gè)變量,對(duì)于外部代買(mǎi)來(lái)說(shuō)只能讀取不能修改, 最簡(jiǎn)單的做法就是使用 final 關(guān)鍵字來(lái)標(biāo)記這個(gè)變量

?正確示范

class Box {
  final contents = [];
}

?錯(cuò)誤示范

class Box {
  var _contents;
  get contents => _contents;
}
對(duì)簡(jiǎn)單成員使用 =>
double get area => (right - left) * (bottom - top);

bool isReady(num time) => minTime == null || minTime <= time;

String capitalize(String name) =>
    '${name[0].toUpperCase()}${name.substring(1)}';

如果你有很多行聲明或包含深層的嵌套表達(dá)式,應(yīng)該換做使用代碼塊和一些語(yǔ)句來(lái)實(shí)現(xiàn)

?錯(cuò)誤示范

Treasure openChest(Chest chest, Point where) =>
    _opened.containsKey(chest) ? null : _opened[chest] = Treasure(where)
      ..addAll(chest.contents);

?正確示范

Treasure openChest(Chest chest, Point where) {
  if (_opened.containsKey(chest)) return null;

  var treasure = Treasure(where);
  treasure.addAll(chest.contents);
  _opened[chest] = treasure;
  return treasure;
}
在不需要的時(shí)候不要用this

只有兩種情況需要使用 this.

其中一種情況是要訪問(wèn)的局部變量和成員變量命名一樣的時(shí)候

?錯(cuò)誤示范

class Box {
  var value;

  void clear() {
    this.update(null);
  }

  void update(value) {
    this.value = value;
  }
}

?正確示范

class Box {
  var value;

  void clear() {
    update(null);
  }

  void update(value) {
    this.value = value;
  }
}

另一種使用 this. 的情況是在重定向到一個(gè)命名函數(shù)的時(shí)候:

?錯(cuò)誤示范

class ShadeOfGray {
  final int brightness;

  ShadeOfGray(int val) : brightness = val;

  ShadeOfGray.black() : this(0);

  // 這樣是無(wú)法解析和編譯的!
  // ShadeOfGray.alsoBlack() : black();
}

?正確示范

class ShadeOfGray {
  final int brightness;

  ShadeOfGray(int val) : brightness = val;

  ShadeOfGray.black() : this(0);

  // 現(xiàn)在就可以了!
  ShadeOfGray.alsoBlack() : this.black();
}
盡可能的在定義變量的時(shí)候初始化變量值

?錯(cuò)誤示范

class Folder {
  final String name;
  final List<Document> contents;

  Folder(this.name) : contents = [];
  Folder.temp() : name = 'temporary'; // Oops! Forgot contents.
}

?正確示范

class Folder {
  final String name;
  final List<Document> contents = [];

  Folder(this.name);
  Folder.temp() : name = 'temporary';
}

構(gòu)造函數(shù)

盡可能的使用初始化形式

?正確示范

class Point {
  num x, y;
  Point(this.x, this.y);
}

?錯(cuò)誤示范

class Point {
  num x, y;
  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
}
; 來(lái)替代空的構(gòu)造函數(shù)體 {}

?正確示范

class Point {
  int x, y;
  Point(this.x, this.y);
}

?錯(cuò)誤示范

class Point {
  int x, y;
  Point(this.x, this.y) {}
}
不要 使用 new

?正確示范

Widget build(BuildContext context) {
  return Row(
    children: [
      RaisedButton(
        child: Text('Increment'),
      ),
      Text('Click!'),
    ],
  );
}

?錯(cuò)誤示范

Widget build(BuildContext context) {
  return new Row(
    children: [
      new RaisedButton(
        child: new Text('Increment'),
      ),
      new Text('Click!'),
    ],
  );
}

錯(cuò)誤處理

避免 使用沒(méi)有 on 語(yǔ)句的 catch
不要 丟棄沒(méi)有使用 on 語(yǔ)句捕獲的異常
只在代表編程錯(cuò)誤的情況下才拋出實(shí)現(xiàn)了 Error 的異常

Error 類(lèi)是所有 編碼 錯(cuò)誤的基類(lèi)。當(dāng)一個(gè)該類(lèi)型或者其子類(lèi)型, 例如 ArgumentError 對(duì)象被拋出了,這意味著是你代碼中的一個(gè) bug。 當(dāng)你的 API 想要告訴調(diào)用者使用錯(cuò)誤的時(shí)候可以拋出一個(gè) Error 來(lái)表明你的意圖。

同樣的,如果一個(gè)異常表示為運(yùn)行時(shí)異常而不是代碼 bug, 則拋出 Error 則會(huì)誤導(dǎo)調(diào)用者。 應(yīng)該拋出核心定義的 Exception 類(lèi)或者其他類(lèi)型。

不要 顯示的捕獲 Error 或者其子類(lèi)

既然 Error 表示代碼中的 bug, 應(yīng)該展開(kāi)整個(gè)調(diào)用堆棧,暫停程序并打印堆棧跟蹤,以便找到錯(cuò)誤并修復(fù)。

捕獲這類(lèi)錯(cuò)誤打破了處理流程并且代碼中有 bug。 不要在這里使用錯(cuò)誤處理代碼,而是需要到導(dǎo)致該錯(cuò)誤出現(xiàn)的地方修復(fù)你的代碼

使用 rethrow 來(lái)重新拋出捕獲的異常

如果你想重新拋出一個(gè)異常,推薦使用 rethrow 語(yǔ)句。 rethrow 保留了原來(lái)的異常堆棧信息。 而 throw 會(huì)把異常堆棧信息重置為最后拋出的位置

?錯(cuò)誤示范

try {
  somethingRisky();
} catch (e) {
  if (!canHandle(e)) throw e;
  handle(e);
}

?正確示范

try {
  somethingRisky();
} catch (e) {
  if (!canHandle(e)) rethrow;
  handle(e);
}

異步

使用 async/await 而不是直接使用底層的特性

?正確示范

Future<int> countActivePlayers(String teamName) async {
  try {
    var team = await downloadTeam(teamName);
    if (team == null) return 0;

    var players = await team.roster;
    return players.where((player) => player.isActive).length;
  } catch (e) {
    log.error(e);
    return 0;
  }
}

?錯(cuò)誤示范

Future<int> countActivePlayers(String teamName) {
  return downloadTeam(teamName).then((team) {
    if (team == null) return Future.value(0);

    return team.roster.then((players) {
      return players.where((player) => player.isActive).length;
    });
  }).catchError((e) {
    log.error(e);
    return 0;
  });
}
不要 在沒(méi)有有用效果的情況下使用 async

當(dāng)成為習(xí)慣之后,你可能會(huì)在所有和異步相關(guān)的函數(shù)使用 async。但是在有些情況下, 如果可以忽略 async 而不改變方法的行為,則應(yīng)該這么做:

?正確示范

Future afterTwoThings(Future first, Future second) {
  return Future.wait([first, second]);
}

?錯(cuò)誤示范

Future afterTwoThings(Future first, Future second) async {
  return Future.wait([first, second]);
}

async有用情況:

  • 使用了 await
  • 異步的拋出一個(gè)異常。 async 然后 throwreturn new Future.error(...) 要簡(jiǎn)短很多
  • 返回一個(gè)值,但是你希望他顯式的使用 Future。asyncnew Future.value(...) 要簡(jiǎn)短很多
  • 不希望在事件循環(huán)發(fā)生事件之前執(zhí)行任何代碼
Future usesAwait(Future later) async {
  print(await later);
}

Future asyncError() async {
  throw 'Error!';
}

Future asyncValue() async => 'value';

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容