Flutter 编码规范

前言:标准的开发规范可以让我们事半功倍,也可以让别人更好的理解和使用你的代码、算法。flutter 的编码规范大致可以分为:、命名规范、缩进格式规范、注释规范、代码规范、其他规范等。

一、命名规范 :
大部分编程语言或多或少都有自己的命名特点,不过大同小异。这里给大家介绍下Flutter的相关命名规范。良好的编码规范习惯,一致的命名规则有助于我们进行开发。Flutter的命名规范其实也就是Dart语言的规范,后面都以Flutter规范代替。
先看下Flutter的三种命名方式:

1、UpperCamelCase:单词首字母大写的驼峰命名方式,例如StudentName;
2、lowerCamelCase:第一个单词的首字母小写的驼峰命名方式,如studentName;
3、lowercase_with_underscores:单词全部是小写字母,中间用_连接,如student_name。
接下来看下这三种命名方式一般都在哪种情况下使用:
先看下UpperCamelCase命名方式:
UpperCamelCase命名方式一般用在类名、注解、枚举、typedef和参数的类型上,一般都使用UpperCamelCase命名方式。例如:

 //类名命名
class ItemMenu { ... }

class HttpApi { ... }

//注解
@Foo()
class A { ... }

//枚举
enum Color {
  LightRed, 
  LightBlue
}

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

//方法参数类型
@override
Widget build(BuildContext context) {...}

再看下lowerCamelCase命名方式:
lowerCamelCase命名方式一般用在类成员、变量、方法名、参数命名等命名上。如:

//变量命名
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();
}

最后看下lowercase_with_underscores命名方式:
lowercase_with_underscores命名方式一般用在命名库(libraries)、包(packages)、目录(directories)和源文件(source files)上,类似这样的格式:libray_names, file_names。因此Flutter里的库名,包名,目录和源代码文件的命名都建议需要采用小写单词加_下划线分隔方式命名。如:

library json_parser.string_scanner;

import 'file_system.dart';
import 'item_menu.dart';

//目录文件夹命名可以类似:http_utils这种形式
//源代码文件命名可以类似:screen_utils.dart这种形式

同时,在Flutter导入类库时候的as关键字后面的命名也要遵循lowercase_with_underscores命名方式。如:

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

二、代码格式化
很多语言都有自己的格式要求,这样有利于排版和阅读使用。其实很多IDE也自带了一些格式化工具和插件,如Visual Studio Code可以使用Alt+Shift+F进行格式化代码。那么接下来就讲解下Flutter代码格式化的相关建议规范:
官方建议可以使用dartfmt进行格式化代码。dartfmt插件地址:https://github.com/dart-lang/dart_style
这个dartfmt可以帮我们自动按照规范格式化代码,非常方便。
如果遇到格式化工具都无法格式化的代码,建议重新简化组织代码,如缩短局部变量名称或更改层级等等。
官方建议,每行代码不超过80个字符。太长的单行显示不利于阅读,所以建议不要每行超过80个字符。

建议流程控制相关语句都要加花括号{…},防止出现其他错误,也更有利于排版和阅读。如:

if (isWeekDay) {
  print('Bike to work!');
} else {
  print('Go dancing or read a book!');
}

但是如果一个控制语句只有if,没有else的话,可以不使用{}:

if (arg == null) return defaultValue;

但是,如果if里的判断语句和return的返回的语句内容都很长,可能会产生换行,这种建议要加花括号{…}:

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

其他格式化控制需要注意的就是,Flutter采用的是React方式进行开发,所有类都是Widget。如果遇到一些层级嵌套太深的情况下,你也可以将某个层级定义为另一个方法进行调用引入即可。

class _MyMainPageState extends State<MyMainApp> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('标题'),
        ),
        //通过方法引入
        body: getBody(),
      ),
    );
  }

  Widget getBody() {
    return Center(child: Text("我是内容"));
  }
}

三、注释
Flutter注释分为几种。首先看下//形式单行注释,这种注释不会出现、生成在文档里,只是代码里的注释:

// 这个注释不会出现生成到文档里
if (_chunks.isEmpty) return false;

greet(name) {
  // 单行注释,这个注释不会出现生成到文档里
  print('Hi, $name!');
}

接下来是块注释(多行注释),这个可以用来注释代码,或者需要多行注释说明的情况下。

/*
class _MyMainPageState extends State<MyMainApp> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //页面
      home: Scaffold(
        appBar: AppBar(
          title: Text('标题'),
        ),
        body: getBody(),
      ),
    );
  }

  Widget getBody() {
    return Center(child: Text("我是内容"));
  }
}
*/


greet(name) {
  /* 用多行注释(块注释),来写单行注释是不建议的*/
  print('Hi, $name!');
}

最后看下文档注释,这个注释使用///来表示,并且注释会出现生成到文档里。一般我们可以使用文档注释来注释类成员和类型、方法、参数、类、变量常量等:

/// 这个是获取字符长度
int get length => ...

此时就不建议使用单行注释了,而是使用文档注释,来说明这个成员变量和类型是干什么的。当然还有一种多行注释也是支持的,只不过Flutter不建议使用.

/**
 * 多行注释,不建议这种方式,但是也是支持的
 */

编写注释时候,建议注释要精炼简短;适当的时候可以用空行来分隔注释内容;不要把注释内容和周围的上下文代码混合在一起,不容易阅读。
我们也可以在文档注释里加入一些dart代码例子:

 /// A widget to display before the [title].
  ///
  /// If this is null and [automaticallyImplyLeading] is set to true, the
  /// [AppBar] will imply an appropriate widget. For example, if the [AppBar] is
  /// in a [Scaffold] that also has a [Drawer], the [Scaffold] will fill this
  /// widget with an [IconButton] that opens the drawer (using [Icons.menu]). If
  /// there's no [Drawer] and the parent [Navigator] can go back, the [AppBar]
  /// will use a [BackButton] that calls [Navigator.maybePop].
  ///
  /// {@tool sample}
  ///
  /// The following code shows how the drawer button could be manually specified
  /// instead of relying on [automaticallyImplyLeading]:
  ///
  /// ```dart
  /// AppBar(
  ///   leading: Builder(
  ///     builder: (BuildContext context) {
  ///       return IconButton(
  ///         icon: const Icon(Icons.menu),
  ///         onPressed: () { Scaffold.of(context).openDrawer(); },
  ///         tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
  ///       );
  ///     },
  ///   ),
  /// )
  /// ```
  /// {@end-tool}
  ///
  /// The [Builder] is used in this example to ensure that the `context` refers
  /// to that part of the subtree. That way this code snippet can be used even
  /// inside the very code that is creating the [Scaffold] (in which case,
  /// without the [Builder], the `context` wouldn't be able to see the
  /// [Scaffold], since it would refer to an ancestor of that widget).
  ///
  /// See also:
  ///
  ///  * [Scaffold.appBar], in which an [AppBar] is usually placed.
  ///  * [Scaffold.drawer], in which the [Drawer] is usually placed.
  final Widget leading;

四、代码使用规范

Flutter代码使用规范内容比较多,这里就说几个典型的例子。
导包相关:
假如我们包结构如下:

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

在api.dart中想引入scr下的utils.dart类,建议这样引入:

//相对路径引入即可
import 'src/utils.dart';

//而不是这样引入
import 'package:my_package/src/utils.dart';
//不需要加入package,因为如果后续package名字变了,修改起来非常麻烦

字符串相关:
字符串连接不需要用+号连接,直接挨着写即可:

raiseAlarm(
    'ERROR: Parts of the spaceship are on fire. Other '
    'parts are overrun by martians. Unclear which are which.');
    ...
'Hello, $name! You are ${year - birth} years old.';

//使用+号连接是错误的,不支持的
raiseAlarm('ERROR: Parts of the spaceship are on fire. Other ' +
    'parts are overrun by martians. Unclear which are which.');
    ...
'Hello, ' + name + '! You are ' + (year - birth).toString() + ' y...';

集合相关:
Flutter的集合类型有这几种:lists, maps, queues, sets。

//建议用这种方式创建空集合
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>();

//使用isEmpty和isNotEmpty来判断集合是否为空
if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');

//不要使用.length方法来判断是否是空
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');

//对于集合转换,我们可以使用它的链式高级方法来转换
var aquaticNames = animals
    .where((animal) => animal.isAquatic)
    .map((animal) => animal.name);

//集合的循环遍历建议使用for
for (var person in people) {
  ...
}

//这种forEach写法不推荐
people.forEach((person) {
  ...
});

//List.from一般用于类型转换,这两种方式都可以实现,但是推荐第一种写法
var copy1 = iterable.toList();
var copy2 = List.from(iterable);

//建议这种写法
// Creates a List<int>:
var iterable = [1, 2, 3];

// Prints "List<int>":
print(iterable.toList().runtimeType);

//不建议使用List.from这种写法
// Creates a List<int>:
var iterable = [1, 2, 3];

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


//但是如果改变集合类型,这是可以使用List.from方法
var numbers = [1, 2.3, 4]; // List<num>.
numbers.removeAt(1); // Now it only contains integers.
var ints = List<int>.from(numbers);

var stuff = <dynamic>[1, 2];
var ints = List<int>.from(stuff);

//关于集合过滤
//不建议
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>();

函数方法相关:

//建议写法
void main() {
  localFunction() {
    ...
  }
}
//不建议写法
void main() {
  var localFunction = () {
    ...
  };
}

//建议写法
names.forEach(print);
//不建议写法
names.forEach((name) {
  print(name);
});

//用等号将默认值和参数分隔
//建议写法
void insert(Object item, {int at = 0}) { ... }
//不建议写法
void insert(Object item, {int at: 0}) { ... }

//可以使用??两个问号来判断是否是null
void error([String message]) {
  stderr.write(message ?? '\n');
}

//不要将变量初始化为null
//建议写法
int _nextId;

class LazyId {
  int _id;

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

    return _id;
  }
}
//不建议写法
int _nextId = null;

class LazyId {
  int _id = null;

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

    return _id;
  }
}

//不用写类成员变量的getter和setter方法,默认是隐藏自带的
//建议写法
class Box {
  var contents;
}

//不建议,没必要的,不用写类成员变量的getter和setter方法
class Box {
  var _contents;
  get contents => _contents;
  set contents(value) {
    _contents = value;
  }
}

//可以使用final来创建只读常量,也支持=>简写
class Box {
  final contents = [];
}

double get area => (right - left) * (bottom - top);
//=>也就是省略了{...}和return

//不建议重复多次使用this关键字
//建议写法
class Box {
  var value;

  void clear() {
    update(null);
  }

  void update(value) {
    this.value = value;
  }
}
//不建议写法
class Box {
  var value;

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

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

//尽量在声明中初始化常量
//建议
class Folder {
  final String name;
  final List<Document> contents = [];

  Folder(this.name);
  Folder.temp() : name = 'temporary';
}
//不建议
class Folder {
  final String name;
  final List<Document> contents;

  Folder(this.name) : contents = [];
  Folder.temp() : name = 'temporary'; // Oops! Forgot contents.
}
//缩减构造方法初始化写法
//建议
class Point {
  num x, y;
  Point(this.x, this.y);
}
//不建议
class Point {
  num x, y;
  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
}

//构造方法里无需重复声明参数类型
//建议
class Point {
  int x, y;
  Point(this.x, this.y);
}
//不建议
class Point {
  int x, y;
  Point(int this.x, int this.y);
}

//对于空方法体的构造方法直接写;结尾
//建议
class Point {
  int x, y;
  Point(this.x, this.y);
}
//不建议
class Point {
  int x, y;
  Point(this.x, this.y) {}
}

//new关键字可以不写,dar2已经支持不写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!'),
    ],
  );
}

//无需重复定义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]),
];


异常处理相关:

//可以使用rethrow重新处理后抛出异常,以提供给其他后续逻辑处理
//建议
try {
  somethingRisky();
} catch (e) {
  if (!canHandle(e)) rethrow;
  handle(e);
}
//不建议
try {
  somethingRisky();
} catch (e) {
  if (!canHandle(e)) throw e;
  handle(e);
}

异步任务编程相关:

//我们可以使用Future和async、await来进行处理异步编程,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;
  }
}
//不建议写法
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;
  });
}

//如果有些方法功能没有用到异步任务,不要加async关键字
//建议写法
Future afterTwoThings(Future first, Future second) {
  return Future.wait([first, second]);
}
//不建议写法
Future afterTwoThings(Future first, Future second) async {
  return Future.wait([first, second]);
}

//关于数据转换我们可以用Future里高级用法来简化操作
//建议写法
Future<bool> fileContainsBear(String path) {
  return File(path).readAsString().then((contents) {
    return contents.contains('bear');
  });
}
//建议写法
Future<bool> fileContainsBear(String path) async {
  var contents = await File(path).readAsString();
  return contents.contains('bear');
}
//不建议写法
Future<bool> fileContainsBear(String path) {
  var completer = Completer<bool>();

  File(path).readAsString().then((contents) {
    completer.complete(contents.contains('bear'));
  });

  return completer.future;
}

//可以适当的使用T泛型
//建议写法
Future<T> logValue<T>(FutureOr<T> value) async {
  if (value is Future<T>) {
    var result = await value;
    print(result);
    return result;
  } else {
    print(value);
    return value as T;
  }
}
//不建议写法
Future<T> logValue<T>(FutureOr<T> value) async {
  if (value is T) {
    print(value);
    return value;
  } else {
    var result = await value;
    print(result);
    return result;
  }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容