學習Flutter,你須要瞭解的Dart 編碼規範

本文來自dart官方文檔中的 【Effective Dart】git

規範主要分爲四個部分:github

  • 樣式規範
  • 文檔規範
  • 使用規範
  • 設計規範

每一個部分都有許多的例子說明,每一個例子都會如下面五個詞中的某些做爲開頭:api

  • DO :表示你須要遵照的作法
  • DONT :表示這樣的作法是很是很差的
  • PREFER :在多數狀況下,都推薦的作法
  • AVOID : 在多數狀況下,都應該避免的作法
  • CONSIDER : 須要你本身去斟酌的作法

在我看來,編碼習慣都是因人而異的,並無所謂的最佳方案。bash

若是你是一我的開發,固然不須要在乎這些問題,可是若是你的代碼須要展示給別人,或者你須要與別人協同開發,編碼規範就很是有必要了。app

下面,將會從官方文檔中選取最基本,最典型,發生率較高的一些狀況,做爲規範說明。dom

✅表示正面作法,❌表示反面作法async

樣式規範

命名

DO: 類, 枚舉, 類型定義, 以及泛型,都須要使用大寫開頭的駝峯命名法ide

✅
class SliderMenu { ... }

class HttpRequest { ... }

typedef Predicate<T> = bool Function(T value);
複製代碼

在使用註解時候,也應該這樣函數

✅

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

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

@Foo()
class B { ... }
複製代碼

不過爲一個類的構造函數添加註解時,你可能須要建立一個小寫開頭的註解變量學習

✅

const foo = Foo();

@foo
class C { ... }
複製代碼

DO: 命名庫、包、目錄、dart文件都應該是小寫加上下劃線

✅

library peg_parser.source_scanner;

import 'file_system.dart';
import 'slider_menu.dart';
複製代碼
❌

library pegparser.SourceScanner;

import 'file-system.dart';
import 'SliderMenu.dart';
複製代碼

DO: 將引用使用as轉換的名字也應該是小寫下劃線

✅

import 'dart:math' as math;
import 'package:angular_components/angular_components'
    as angular_components;
import 'package:js/js.dart' as js;
複製代碼
❌

import 'dart:math' as Math;
import 'package:angular_components/angular_components'
    as angularComponents;
import 'package:js/js.dart' as JS;
複製代碼

DO: 變量名、方法、參數名都應該是小寫開頭的駝峯命名法

✅

var item;

HttpRequest httpRequest;

void align(bool clearItems) {
  // ...
}
複製代碼
✅

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

class Dice {
  static final numberGenerator = Random();
}
複製代碼
❌

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

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

複製代碼

花括號

DO: 只有一個if語句且沒有else的時候,而且在一行內可以很好的展現,就能夠不用花括號

if (arg == null) return defaultValue;
複製代碼

可是若是一行內展現比較勉強的話,就須要用花括號了:

if (overflowChars != other.overflowChars) {
  return overflowChars < other.overflowChars;
}
複製代碼
if (overflowChars != other.overflowChars)
  return overflowChars < other.overflowChars;
複製代碼

文檔規範

DO: 在dart的註釋中,更加推薦使用///而非//

✅

/// The number of characters in this chunk when unsplit.
int get length => ...
複製代碼
❌

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

至於爲何要這樣作,官方表示是因爲歷史緣由以及他們以爲這個在某些狀況下看起來更方便閱讀。

DO: 文檔註釋應該以一句簡明的話開頭

✅

/// Deletes the file at [path] from the file system.
void delete(String path) {
  ...
}
複製代碼
❌

/// 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) {
  ...
}
複製代碼

DO: 將註釋的第一句與其餘內容分隔開來

✅

/// 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) {
  ...
}
複製代碼
❌

/// 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) {
  ...
}
複製代碼

DO: 使用方括號去聲明參數、返回值以及拋出的異常

❌

/// Defines a flag with the given name and abbreviation.
///
/// @param name The name of the flag.
/// @param abbr The abbreviation for the flag.
/// @returns The new flag.
/// @throws ArgumentError If there is already an option with
///     the given name or abbreviation.
Flag addFlag(String name, String abbr) => ...
複製代碼
✅

/// 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) => ...
複製代碼

使用規範

依賴

PREFER: 推薦使用相對路徑導入依賴

若是項目結構以下:

my_package
└─ lib
   ├─ src
   │  └─ utils.dart
   └─ api.dart
複製代碼

想要在 api.dart 中導入 utils.dart

✅

import 'src/utils.dart';
複製代碼
❌

import 'package:my_package/src/utils.dart';
複製代碼

賦值

DO: 使用??將null值作一個轉換

在dart中 ?? 操做符表示當一個值爲空時會給它賦值 ?? 後面的數據

if (optionalThing?.isEnabled) {
  print("Have enabled thing.");
}
複製代碼

optionalThing 爲空的時候,上面就會有空指針異常了。

這裏說明一下。 ?. 操做符至關於作了一次判空操做,只有當 optionalThing 不爲空的時候纔會調用 isEnabled 參數,當 optionalThing 爲空的話默認返回null,用在if判斷句中天然就不行了

下面是正確作法

✅

// 若是爲空的時候你想返回false的話:
optionalThing?.isEnabled ?? false;

// 若是爲空的時候你想返回ture的話:
optionalThing?.isEnabled ?? true;
複製代碼
❌

optionalThing?.isEnabled == true;

optionalThing?.isEnabled == false;
複製代碼

字符串

在dart中,不推薦使用 + 去鏈接兩個字符串

DO: 使用回車鍵直接分隔字符串

✅

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

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

PREFER: 使用${}來鏈接字符串與變量值

'Hello, $name! You are ${year - birth} years old.';
複製代碼
'Hello, ' + name + '! You are ' + (year - birth).toString() + ' y...';
複製代碼

集合

dart中建立空的可擴展 List 有兩種方法: []List();建立空的 HashMap 有三種方法: {}, Map(),和 LinkedHashMap()

若是要建立不可擴展的列表或其餘一些自定義集合類型,那麼務必使用構造函數。

DO: 儘量使用簡單的字面量建立集合

✅

var points = [];
var addresses = {};
複製代碼
❌

var points = List();
var addresses = Map();
複製代碼

當你想要指定類型的時候

✅

var points = <Point>[];
var addresses = <String, Address>{};
複製代碼
❌

var points = List<Point>();
var addresses = Map<String, Address>();
複製代碼

DON’T: 不要使用.lenght的方法去表示一個集合是空的

if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');
複製代碼
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');
複製代碼

CONSIDER: 考慮使用高階方法轉換序列

var aquaticNames = animals
    .where((animal) => animal.isAquatic)
    .map((animal) => animal.name);
複製代碼

AVOID: 避免使用帶有函數字面量的Iterable.forEach()

forEach()函數在JavaScript中被普遍使用,由於內置的for-in循環不能達到你一般想要的效果。在Dart中,若是要迭代序列,那麼慣用的方法就是使用循環。

for (var person in people) {
  ...
}
複製代碼
❌

people.forEach((person) {
  ...
});
複製代碼

DON’T: 不要使用 List.from() 除非你打算更改結果的類型

有兩種方法去獲取 Iterable,分別是List.from()Iterable.toList()

✅

// 建立一個List<int>:
var iterable = [1, 2, 3];

// 輸出"List<int>":
print(iterable.toList().runtimeType);
複製代碼
❌

// 建立一個List<int>:
var iterable = [1, 2, 3];

// 輸出"List<dynamic>":
print(List.from(iterable).runtimeType);
複製代碼

DO: 使用 whereType()去用類型過濾一個集合

❌

var objects = [1, "a", 2, "b", 3];
var ints = objects.where((e) => e is int);
複製代碼
❌

var objects = [1, "a", 2, "b", 3];
var ints = objects.where((e) => e is int).cast<int>();

複製代碼
✅

var objects = [1, "a", 2, "b", 3];
var ints = objects.whereType<int>();
複製代碼

參數

DO: 使用 = 給參數設置默認值

✅

void insert(Object item, {int at = 0}) { ... }
複製代碼
❌

void insert(Object item, {int at: 0}) { ... }
複製代碼

DON’T: 不要將參數的默認值設置爲 null

✅

void error([String message]) {
  stderr.write(message ?? '\n');
}
複製代碼
❌

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

變量

AVOID: 避免存儲能夠計算的值

❌

class Circle {
  num _radius;
  num get radius => _radius;
  set radius(num value) {
    _radius = value;
    _recalculate();
  }

  num _area;
  num get area => _area;

  num _circumference;
  num get circumference => _circumference;

  Circle(this._radius) {
    _recalculate();
  }

  void _recalculate() {
    _area = pi * _radius * _radius;
    _circumference = pi * 2.0 * _radius;
  }
}
複製代碼
✅

class Circle {
  num radius;

  Circle(this.radius);

  num get area => pi * radius * radius;
  num get circumference => pi * 2.0 * radius;
}
複製代碼

成員

DON’T: 不要寫不必的getter 和 setter

✅

class Box {
  var contents;
}
複製代碼
❌

class Box {
  var _contents;
  get contents => _contents;
  set contents(value) {
    _contents = value;
  }
}
複製代碼

構造函數

DO: 儘量使用簡單的初始化形式

❌

class Point {
  num x, y;
  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
}
複製代碼
✅

class Point {
  num x, y;
  Point(this.x, this.y);
}
複製代碼

DON’T: 不要使用 new 來建立對象

dart中不須要new

✅

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

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

DON’T: 不要使用多餘的 const 修飾對象

✅

const primaryColors = [
  Color("red", [255, 0, 0]),
  Color("green", [0, 255, 0]),
  Color("blue", [0, 0, 255]),
];
複製代碼
❌

const primaryColors = const [
  const Color("red", const [255, 0, 0]),
  const Color("green", const [0, 255, 0]),
  const Color("blue", const [0, 0, 255]),
];
複製代碼

異常處理

DO: 使用 rethrow 從新拋出異常

❌

try {
  somethingRisky();
} catch (e) {
  if (!canHandle(e)) throw e;
  handle(e);
}
複製代碼
✅

try {
  somethingRisky();
} catch (e) {
  if (!canHandle(e)) rethrow;
  handle(e);
}
複製代碼

設計規範

AVOID: 避免爲了實現流式調用而讓方法返回this

✅

var buffer = StringBuffer()
  ..write('one')
  ..write('two')
  ..write('three');
複製代碼
❌

var buffer = StringBuffer()
    .write('one')
    .write('two')
    .write('three');
複製代碼

AVOID: 避免使用 FutureOr<T> 做爲返回類型

✅

Future<int> triple(FutureOr<int> value) async => (await value) * 3;
複製代碼
❌

FutureOr<int> triple(FutureOr<int> value) {
  if (value is int) return value * 3;
  return (value as Future<int>).then((v) => v * 3);
}
複製代碼

AVOID: 避免將bool值直接做爲輸入參數

❌

new Task(true);
new Task(false);
new ListBox(false, true, true);
new Button(false);
複製代碼
✅

Task.oneShot();
Task.repeating();
ListBox(scroll: true, showScrollbars: true);
Button(ButtonState.enabled);
複製代碼

DON’T: 不要在自定義的 == operator 方法中進行判空

✅

class Person {
  final String name;
  // ···
  bool operator ==(other) => other is Person && name == other.name;

  int get hashCode => name.hashCode;
}
複製代碼
❌

class Person {
  final String name;
  // ···
  bool operator ==(other) => other != null && ...
}
複製代碼

附錄

最後。

學習Flutter,你還須要瞭解這個精美的app😄

Todo-List

相關文章
相關標籤/搜索