การเขียนภาษา Dart ที่มีประสิทธิภาพ

สวัสดีครับ ทุกท่าน วันสองวันนี้เป็นช่วงเวลาว่างๆ เลยหาอะไรมาอ่านเล่น ผมไปเจอกับบทความใน guides ของเว็บไซต์ dart.dev ที่มีเนื้อหาเกี่ยวกับแนวทางการเขียน Dart อ่านแล้วก็น่าสนใจดี เพราะช่วงแรกๆของผมนั้น ผมชอบเขียนปะปนกับ Java style ใน dart เสมอๆ เลยนำมาสรุปไว้ในบล็อกนี้ โดยผมเลือกมาเฉพาะส่วนหลักๆที่ผมคิดว่าสำคัญกับการทำงานร่วมกันกับคนอื่นๆ
โดยขอย้ำว่า นี่เป็นเพียงแนวทางเท่านั้นนะ ไม่จำเป็นต้องทำตามทั้งหมดก็ได้ สุดท้ายแล้วก็อยู่ที่การตกลงกันภายในทีม
สามารถอ่านฉบับเต็มได้ที่นี่เลย
https://dart.dev/guides/language/effective-dart
ไม่ใช้ “new” keyword
ตั้งแต่ dart2 คำว่า “new” ที่ใช้เวลาเราสร้าง instance นั้น ได้กำหนดเป็น optional สิ่งนี้ช่วยลด code ที่ซ้ำๆไปได้เยอะเลย ดังนั้นเลิกใช้มันได้เลย
1 2 3 |
/*new*/ Text('Increment'); |
บรรทัดละ 80 ตัวอักษร
Dart บอกว่า อ้างอิงจากผลการศึกษาที่ว่าสายตาคนเราจะกวาดตาอ่านได้ดี คือราวๆ 80 ตัวอักษรในหนึ่งบรรทัด นั่นเป็นเหตุผลว่าทำไมหนังสือพิมพ์ถึงมีหลายๆคอลัมน์
ตัวอย่าง code ที่มี 80 ตัวอักษร
1 2 3 |
Navigator.push(context, MaterialPageRoute(builder: (_) => CreditCardScreen())); |
สำหรับใน Android Studio สามารถปรับค่าของการขึ้นบรรทัดใหม่อัตโนมัติ หรือเรียกว่า Hard Wrap ได้ที่ Setting และค้นหาคำว่า “hard wrap” ปรับได้ตามใจเลย

Classes , Enum , Type , Extension
ชื่อ Class จะใช้การตั้งชื่อแบบ UpperCamelCase รวมถึง enum , typedef ด้วย
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// CLass class Employee { ... } // Enum enum DisplayMode {GRID , LIST} // Typedef typedef Predicate<T> = bool Function(T value); //Extension extension MyCustomList<T> on List<T> { ... } |
ชื่อไฟล์ Dart และ Package
ชื่อไฟล์ dart , directory, library, ชื่อ package ต่างๆ จะใช้การตั้งชื่อแบบ lowercase_with_underscores
1 2 3 |
import 'splash_screen.dart'; |
Import prefixes
การตั้งชื่อให้กับ package ที่ import เข้ามา จะใช้ keyword “as” โดย prefix ที่ว่านี้ แนะนำให้ตั้งชื่อแบบ lowercase_with_underscores
1 2 3 |
import 'package:date_picker/date_components' as date_components; |
ลำดับของ Import
Dart แนะนำให้จัดลำดับการ import จะแบ่งเป็น 4 กลุ่ม เรียงตามลำดับคือ
1. กลุ่ม dart หรือ Flutter ที่เป็น core package
2. กลุ่ม package แบบชื่อเต็ม ที่เป็น library
3. กลุ่ม package แบบ relative path ในโปรเจคของเรา
4. กลุ่ม export สำหรับทำเป็น external package
จากนั้นก็เรียงตามลำดับตัวอักษรในแต่ละกลุ่ม
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Dart or Flutter import 'dart:async'; import 'dart:html'; // Packages import 'package:bar/bar.dart'; import 'package:foo/foo.dart'; // Relative imports import 'util.dart'; // Export section export 'src/error.dart'; |
สำหรับไฟล์ dart ภายในโปรเจคของเรา จะสามารถ import แบบ package: หรือ relative path ก็ได้ ซึ่ง Dart แนะนำให้ใช้แบบ relative path
เช่น
my_package
└─ lib
├─ src
│ └─ utils.dart
└─ api.dart
1 2 3 |
import 'src/utils.dart'; |
แต่ใน Android Studio หลายๆครั้ง มักจะแนะนำให้ใช้แบบ package: ซะอย่างนั้น แม้จะเป็น dart ในโปรเจคของเราเองก็ตาม

ตัวแปรต่างๆ (Variable)
ตัวแปรไม่ว่าจะเป็น members ของ Class หรือที่อยู่ top-level รวมทั้งเป็น parameters หรือ named parameters จะใช้การตั้งชื่อแบบ lowerCamelCase
1 2 3 4 5 6 7 8 9 |
var data; CoffeeProvider coffeProvider; void start(bool force){ ... } |
การตั้งชื่อตัวแปรที่ไม่ใช่ประเภท boolean แนะนำให้ใช้คำนาม และหลีกเลี่ยงการใช้คำที่มาจากตัวย่อ
1 2 3 4 5 6 |
pageCount list.length context.lineWidth // numPages "num" เป็นตัวย่อของคำว่า number(of) |
ค่าคงที่ (Constant)
ค่าคงที่ต่างๆจะใช้ keyword ว่า “const” หรือ “final” และจะใช้การตั้งชื่อแบบ lowerCamelCase
1 2 3 4 5 6 7 8 9 10 |
// Constant variable const pi = 3.14; const defaultTimeout = 1000; // Constant class Dice { static final numberGenerator = Random(); } |
คำย่อ (acronyms)
การตั้งชื่อ Class ที่มาจากคำเต็มๆแล้วต่อย่อให้สั้น จะแบ่งเป็น 2 กรณี คือ กรณีที่ย่อแล้วมี 2 ตัวอักษร และกรณีที่ยาวกว่า 2 ตัวอักษร
2 ตัวอักษร
เช่น
input/output = IO
User Interface = UI
การตั้งชื่อ Class จะใช้ตัวใหญ่ทั้งหมด (Fully Capitalized) และใช้ lowerCamelCase สำหรับชื่อตัวแปร
1 2 3 4 5 6 |
class IO {} class UIHandler{} var uiHandler = UIHandler(); |
มากกว่า 2 ตัวอักษร
เช่น
HTTP = Hypertext Transfer Protocol
ใช้การตั้งชื่อขึ้นต้นตัวใหญ่ตามด้วยตัวเล็ก (Capitalized)
1 2 3 |
class HttpConnection {} |
ตัวย่อ (abbreviations)
ตัวย่อคือการตัดเอาตัวอักษรบางส่วนของคำมาใช้เรียกสั้นๆ
เช่น
id = identifier
ชื่อ Class จะใช้ Capitailzed เช่น Id
ส่วนชื่อตัวแปรจะใช้ lowerCamelCase
1 2 3 4 5 |
class UserId { ... } UserId userId = UserId(); |
Method
method โดยทั่วไปแนะนำให้ตั้งชื่อที่เมื่ออ่านแล้วมีลักษณะเป็นประโยค จะทำให้สามารถเข้าใจได้ง่าย
1 2 3 4 |
// "If errors is empty..." if (errors.isEmpty) |
หากมี return type เป็น boolean ให้ใช้ชื่อเป็นคำคุณศัพท์หรือคำกริยา
1 2 3 4 |
if (window.closeable) ... // Adjective. if (window.canClose) ... // Verb. |
แนะนำว่าให้ใช้ชื่อที่เป็นไปในทางบวก
เช่น isConnected แทน isNotConnected
1 2 3 4 5 |
if (socket.isConnected && database.hasData) { ... } |
หาก method นั้น ทำงานลักษณะ copy ไปเป็น state ใหม่ให้ใช้คำว่า to ขึ้นต้น
1 2 3 |
DaTeim local = dateTime.toLocal(); |
หาก method นั้น ทำงานลักษณะเปลี่ยน object เป็น type ที่ต่างออกไป ให้ใช้คำว่า as ขึ้นต้น
1 2 3 |
var map = table.asMap(); |
แล้วก็ในชื่อ method ไม่จำเป็นต้องอธิบาย argument ว่าทำอะไร เพราะยังไง ก็สามารถดูจาก definetion ได้ และอาจจะยิ่งทำให้สับสน
1 2 3 4 5 6 7 |
list.add(element); map.remove(key); // list.addElement(element); // map.removeKey(key); |
Callback parameters ที่ไม่ใช้
หลายครั้งจะมี callback ที่มี parameter แต่เราไม่ได้ใช้ ให้ตั้งชื่อมันด้วย _ (underscore) หรือ ถ้ามีหลาย parameter จะใช้ __ , ___ ไปเรื่อยๆ
1 2 3 4 5 6 7 8 9 |
futureMethod.then((_) { print('Done.'); }); futureMethod.then((_,__) { print('Done.'); }); |
Prefix letters
การตั้งชื่อตัวแปรโดยใช้ตัวอักษรนำหน้าเพื่อระบุประเภทอะไรบางอย่าง ใน dart จะไม่แนะนำให้ใช้
แต่ว่าใน Flutter Repository นั้นจะมีใช้ prefix แบบนี้บ้าง เช่น จะใช้ “k” เป็น prefix สำหรับ ค่าคงที่ที่เป็น global
1 2 3 4 5 6 7 |
// Dart const double paragraphSpacing = 1.5; // Flutter repo const double kParagraphSpacing = 1.5; |
เรื่องของ space
เว้น space ระหว่าง operator เพื่อให้อ่านง่าย
1 2 3 4 |
average = (a + b) / 2; largest = a > b ? a : b; |
เว้น space หลัง , และ : เสมอ
1 2 3 4 5 |
function(a, b, named: c) [some, list, literal] {map: literal} |
การเขียน for loop ต้องเว้น space แบบนี้
1 2 3 4 5 |
for (var i = 0; i < 100; i++) { ... } for (final item in collection) { ... } |
และให้เว้น space ก่อน { ของ method body
1 2 3 4 5 |
getEmptyFn(a) { ... } |
การเขียน if
อย่างที่รู้กันว่า if-else มี style เขียนหลายแบบ เคยเป็นที่ถกเถียงกันในกลุ่มโปรแกรมเมอร์กันด้วย ว่า style ไหนคือดีที่สุด ส่วนใน dart แนะนำว่าให้ใช้แบบนี้
1 2 3 4 5 6 7 8 9 10 11 |
class Foo { method() { if (someCondition) { // ... } else { // ... } } } |
กรณีไม่มี else จะเขียนแบบบรรทัดเดียว แบบที่ไม่มี { } ก็ได้ แต่ถ้าขึ้นบรรทัดใหม่ให้ใช้ { } เสมอ
1 2 3 4 5 6 7 |
if (arg == null) return defaultValue; if (overflowChars != other.overflowChars) { return overflowChars < other.overflowChars; } |
Switch case
การเขียน switch case จะมี indent และเว้นบรรทัดระหว่าง case ทุกครั้ง
1 2 3 4 5 6 7 8 9 10 11 |
switch (fruit) { case 'apple': print('delish'); break; case 'durian': print('stinky'); break; } |
Constructor
ส่วนกำหนดค่าเริ่มต้นใน constructor ให้เขียนแยกบรรทัดในแต่ละ field
1 2 3 4 5 6 7 8 |
MyClass() : firstField = 'some value', secondField = 'another', thirdField = 'last' { // ... } |
Comments
comment ที่เราเห็นกันบ่อยๆจะมี 3 แบบ คือ
//
/* */
///
// คือ comment โดยทั่วไปจะใช้เพื่ออธิบายเพิ่มเติมว่ากำลังทำอะไร
1 2 3 4 5 6 |
greet(name) { // Assume we have a valid name. print('Hi, $name!'); } |
ส่วน /* */ หรือเรียกว่า Block comment จะใช้สำหรับ comment code ส่วนที่ไม่ใช้
1 2 3 4 5 6 7 8 9 10 11 12 |
greet(name) { // Assume we have a valid name. print('Hi, $name!'); } /* hello() { print('Hello'); } */ |
และ comment /// จะใช้สำหรับเป็นคำอธิบายแบบทางการ สามารถนำไป generate เป็น document ได้
1 2 3 4 |
/// The number of characters in this chunk when unsplit. int get length => ... |
อย่างไรก็ตาม การ comment แบบ /// นั้นมาจาก C# แต่ว่าใน dart ยังรองรับ /** */ ของ Java โดยทั้งสองแบบเป็น comment doc สามารถใช้ได้ทั้งคู่ แต่ dart แนะนำแบบ C# มากกว่า เพราะอ่านง่ายกว่า
forEach
แนะนำให้ใช้ for (var .. in ..) สำหรับคำสั่งหลาย statement ส่วน .foreach() จะใช้สำหรับกำหนดชื่อ method ลงไปเป็น parameter
1 2 3 4 5 6 7 8 9 10 11 |
for (var person in people) { ... } // people.forEach((person) { // ... // }); people.forEach(print); |
ไม่ต้องเขียน type ถ้าไม่จำเป็น
.map(T) เป็น method ที่น่าจะใช้กันบ่อยๆ โดย Dart แนะนำว่าไม่ต้องเขียนชื่อ type ใน parameter ของ map()
1 2 3 4 |
var names = people.map((person) => person.name); // var names = people.map((Person person) => person.name); |
type arguments ของ Generic ก็แนะนำว่าเขียน type เพียงจุดเดียวก็พอ
1 2 3 4 5 6 7 8 |
Set<String> things = Set(); var things = Set<String>(); //Set<String> things = Set<String>(); List<String> list = List(); //List<String> list = List<String>(); |
ใช้ => สำหรับ method ง่ายๆ
method ไหนที่ทำงานง่ายๆ หรือเป็น getter ถ้าเป็นไปได้ให้ใช้ => expression
1 2 3 4 5 6 7 |
double get area => (right - left) * (bottom - top); // double get(){ // return (right - left) * (bottom - top); // } |
Dart Analysis !
ทั้งหมดที่ว่าไปนี้ มีบางส่วนอยู่ใน Android studio เรียบร้อยแล้ว เช่น naming ตัวแปร โดยมันติดมากับ dart plugin นั่นเอง ทำให้เวลาเราเขียน Flutter จะมีหน้าต่าง Dart Analysis คอยเช็คให้ พร้อมกับแสดง warning

เราสามารถไป ignore ได้
1 2 3 4 |
// ignore: non_constant_identifier_names String KEY_LANGUAGE = "language"; |
แต่ถ้าไม่สนใจจะเขียนตาม Guide นี้ ก็ยังสามารถ compile ได้ตามปกติ อย่างผมเองเขียนไปเขียนมา ใน dart analysis ก็มี warning 400 กว่าจุดเลย (แฮะๆ)

หวังว่าบทความจะมีประโยชน์กับผู้อ่านครับ (: