Dart 編碼規(guī)范:合理使用變量和類成員

前言

我們寫 Dart 代碼的時候苦银,變量和類成員天天用莹妒,但是用的方式一定對嗎?恐怕未必舞吭,本篇我們來介紹變量和類成員該如何合理使用县耽。

規(guī)則1:局部變量使用 var和 final 的方式要保持一致

對于大部分局部變量,無需指定類型镣典,而應該是使用 varfinal兔毙,應該從下面的兩條規(guī)則中選擇一條:

  • 對于不會重復賦值的使用 final,其他的使用 var兄春。這個其實看似很容易遵循澎剥,但是編寫的時候很容易忽略。一個經(jīng)驗就是赶舆,優(yōu)先使用 final哑姚,如果發(fā)現(xiàn)后面需要重新賦值的時候再使用 var
  • 對于局部變量芜茵,只使用 var叙量,即便是那些不會重新賦值的局部變量,也就是對于局部變量不使用 final九串。注意绞佩,這里的局部變了指的是函數(shù)內(nèi)部的局部變量寺鸥,類的成員變量當然可以使用 final 修飾。這一條更容易遵循一些品山。

一旦你選擇了上面中的一條胆建,那么應該一直遵循下去,要不有強迫癥的碼農(nóng)看到你的代碼后會肯定會冒出一堆問題 —— “大佬肘交,這里為什么用 final笆载?這里為什么又不用 final?”估計最后尷尬的你只能“呃……”涯呻,然后找個理由搪塞過去了凉驻。

規(guī)則2:不要存儲那些計算變量

計算變量是指可以通過別的類成員計算出來的屬性。當你存儲的時候复罐,會導致很多問題沿侈,比如你可能需要在各個關聯(lián)屬性變更的地方埋點更新這個計算變量,一旦遺漏就會出現(xiàn) bug市栗。例如下面的例子就是一個典型的反面例子缀拭。

// 錯誤示例
class Circle {
  double radius;
  double area;
  double circumference;

  Circle(double radius)
      : radius = radius,
        area = pi * radius * radius,
        circumference = pi * 2.0 * radius;
}

面積和周長都是依賴于半徑的。上面這種方式有兩個缺陷:

  • 增加了兩個成員屬性來緩存面積和周長填帽,浪費了存儲空間蛛淋;
  • 存在不同步的隱患,一旦半徑更改了篡腌,如果不主動更新面積和周長褐荷,就會出現(xiàn)不一致的情況。要解決這個問題嘹悼,需要在半徑改變的時候更新面積和周長:
// 錯誤示例
class Circle {
  double _radius;
  double get radius => _radius;
  set radius(double value) {
    _radius = value;
    _recalculate();
  }

  double _area = 0.0;
  double get area => _area;

  double _circumference = 0.0;
  double get circumference => _circumference;

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

  void _recalculate() {
    _area = pi * _radius * _radius;
    _circumference = pi * 2.0 * _radius;
  }
}

代碼又臭又長叛甫,對吧?正確的做法是用 get 來獲取計算屬性就可以了杨伙,像下面的代碼是不是超級清爽其监?

// 正確示例
class Circle {
  double radius;

  Circle(this.radius);

  double get area => pi * radius * radius;
  double get circumference => pi * 2.0 * radius;
}

當然,這個規(guī)則也不是一成不變的限匣,假設你的計算過程非常復雜抖苦,而且變量改變?nèi)肟诓欢啵敲绰暶饕粋€成員屬性來緩存計算結(jié)果會節(jié)省大量 CPU 的開銷米死,也就是已空間換時間锌历。這個時候是可以這么做的,具體怎么選擇需要自己根據(jù)代價去判斷峦筒。

規(guī)則3:如無必要究西,不要為類成員提供 getter 和 setter

在 Java 或 C#這類語言中,通常推薦是將所有成員屬性隱藏物喷,然后對外提供 getter 和 setter 來訪問卤材,這是因為這兩門語言對 getter遮斥,setter 和直接訪問屬性的處理方式不同。而在 Dart 里面商膊,通過成員訪問和使用 getter 和 setter 是沒有區(qū)別的伏伐。因此宠进,如果一個成員對外完全可以訪問(包括 getter 和 setter)晕拆,那么就沒必要使用 getter 和 setter。當然材蹬,如果一個成員對外只能進行 setter 或 getter实幕,那么就需要單獨提供對應的方法,而屏蔽另一個堤器。

// 正確示例
class Box {
  Object? contents;
}

// 錯誤示例
class Box {
  Object? _contents;
  Object? get contents => _contents;
  set contents(Object? value) {
    _contents = value;
  }
}

規(guī)則4:對只讀成員使用 final 修飾

如果你的成員屬性對外部是只讀的昆庇,那么應該使用 final 修飾,而不是提供 getter 訪問闸溃。當然整吆,這個前提是這個成員屬性初始化之后不會再重新賦值。

// 正確示例
class Box {
  final contents = [];
}

// 錯誤示例
class Box {
  Object? _contents;
  Object? get contents => _contents;
}

規(guī)則5:對于簡單的計算返回值辉川,優(yōu)先使用 => 表達式

這個其實是為了簡化代碼表蝙,提高可讀性的一個指引。對于使用簡單的表達式返回一個計算屬性或調(diào)用方法時乓旗,使用=> 表達式更加簡潔易懂府蛇。

// 正確示例
double get area => (right - left) * (bottom - top);

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

當然,假設計算返回值的代碼有好幾行屿愚,那么還是使用正常的函數(shù)形式更好汇跨。

// 正確示例
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;
}

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

實際上,原則就是根據(jù)代碼的可讀性決定選擇哪種方式妆距,而不是生搬硬套穷遂。對于成員屬性來說也可以使用 => 形式,例如下面的情況會讓代碼更簡潔娱据。

num get x => center.x;
set x(num value) => center = Point(value, center.y);

除非要和同名參數(shù)做區(qū)分塞颁,否則不要使用 this.來訪問成員

使用 this.訪問類成員在很多語言中很常見,但在 Dart 中不推薦吸耿。 對于訪問成員只有兩種情況需要使用 this.

  • 構(gòu)造方法中使用 this. 表名構(gòu)造函數(shù)的參數(shù)是用于設置成員變量的祠锣;
  • 其他函數(shù)中的參數(shù)名和類成員屬性同名,需要使用 this.來區(qū)分咽安。
// 正確示例
class Box {
  Object? value;

  void clear() {
    update(null);
  }
  
  String toString() {
    return 'Box: $value';
  }

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

// 錯誤示例
class Box {
  Object? value;

  void clear() {
    this.update(null);
  }
  
  String toString() {
    return 'Box: ${this.value}';
  }

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

另外用到 this.的場合包括使用構(gòu)造方法來完成構(gòu)造命名構(gòu)造器:

class ShadeOfGray {
  final int brightness;

  ShadeOfGray(int val) : brightness = val;
    
  // 使用構(gòu)造方法
  ShadeOfGray.black() : this(0);

  // 使用命名構(gòu)造方法
  ShadeOfGray.alsoBlack() : this.black();
}

盡可能在成員聲明的時候初始化

如果成員屬性不依賴于構(gòu)造參數(shù)伴网,那么可以在聲明的時候進行初始化,這會使得代碼量更少妆棒,而且避免了因為類有多個構(gòu)造器導致重復代碼出現(xiàn)的情況澡腾。

// 正確示例
class ProfileMark {
  final String name;
  final DateTime start = DateTime.now();

  ProfileMark(this.name);
  ProfileMark.unnamed() : name = '';
}

// 錯誤示例
class ProfileMark {
  final String name;
  final DateTime start;

  ProfileMark(this.name) : start = DateTime.now();
  ProfileMark.unnamed()
      : name = '',
        start = DateTime.now();
}

有些成員沒法在一開始初始化沸伏,是因為這些成員依賴于其他成員或需要調(diào)用方法來初始化。這個時候动分,應該用 late 來修飾毅糟,這個時候可以訪問 this 來進行初始化。

class _AnimatedModelBarrierDemoState extends State<AnimatedModelBarrierDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller =
      AnimationController(duration: const Duration(seconds: 1), vsync: this);

  late Animation<Color?> _colorAnimation = ColorTween(
    begin: Colors.black.withAlpha(50),
    end: Colors.black.withAlpha(80),
  ).animate(_controller);
  
  // ...
}

總結(jié)

可以看到澜公,實際上代碼的寫法有很多種姆另,所謂“寫法千萬種,規(guī)范第一條坟乾;代碼不規(guī)范迹辐,同事兩行淚”∩趼拢“碼農(nóng)何苦為難碼農(nóng)”呢?有了規(guī)范指引明吩,才能夠?qū)懗龈哔|(zhì)量代碼。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末殷费,一起剝皮案震驚了整個濱河市印荔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌详羡,老刑警劉巖仍律,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異殷绍,居然都是意外死亡染苛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門主到,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茶行,“玉大人,你說我怎么就攤上這事登钥∨鲜Γ” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵牧牢,是天一觀的道長看锉。 經(jīng)常有香客問我,道長塔鳍,這世上最難降的妖魔是什么伯铣? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮轮纫,結(jié)果婚禮上腔寡,老公的妹妹穿的比我還像新娘。我一直安慰自己掌唾,他們只是感情好放前,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布忿磅。 她就那樣靜靜地躺著,像睡著了一般凭语。 火紅的嫁衣襯著肌膚如雪葱她。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天似扔,我揣著相機與錄音吨些,去河邊找鬼。 笑死虫几,一個胖子當著我的面吹牛锤灿,可吹牛的內(nèi)容都是我干的挽拔。 我是一名探鬼主播辆脸,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼螃诅!你這毒婦竟也來了啡氢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤术裸,失蹤者是張志新(化名)和其女友劉穎倘是,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體袭艺,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡搀崭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了猾编。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘤睹。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖答倡,靈堂內(nèi)的尸體忽然破棺而出轰传,到底是詐尸還是另有隱情,我是刑警寧澤瘪撇,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布获茬,位于F島的核電站,受9級特大地震影響倔既,放射性物質(zhì)發(fā)生泄漏恕曲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一渤涌、第九天 我趴在偏房一處隱蔽的房頂上張望佩谣。 院中可真熱鬧,春花似錦歼捏、人聲如沸稿存。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓣履。三九已至率翅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間袖迎,已是汗流浹背冕臭。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留燕锥,地道東北人辜贵。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像归形,于是被迫代替她去往敵國和親托慨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 因為工作需要暇榴,公司組里要求考阿里巴巴編程規(guī)范厚棵,于是我花了一天的時間看了一遍,然后刷了一些題蔼紧,終于在第三次的時候考過...
    添磚java的啾閱讀 5,846評論 0 2
  • 如何閱讀指南 DO 應始終遵循的準則 DON'T 不應該這么使用的準則 PREFER 應該遵循的準則婆硬,但是在某些情...
    _白羊閱讀 3,067評論 0 3
  • 這一秒不放棄,下一秒有奇跡 代碼規(guī)范整理 命名風格 代碼中的命名均不能以下劃線或美元符號開始奸例,也不能以下劃線或美元...
    來晚了各位閱讀 1,286評論 0 1
  • Java代碼規(guī)范整理 對于一個整體的軟件系統(tǒng)而言彬犯,既需要宏觀上的架構(gòu)決策,設計與指導原則查吊,也必須重視微觀上的代碼細...
    jeffrey_hjf閱讀 5,225評論 0 1
  • Java代碼規(guī)范整理 對于一個整體的軟件系統(tǒng)而言谐区,既需要宏觀上的架構(gòu)決策,設計與指導原則菩貌,也必須重視微觀上的代碼細...
    亞武de小文閱讀 2,746評論 8 47