Effective Dart 읽어보기

2021. 5. 21. 15:15

Dart 공식 문서의 Overview ~ Usage를 내맘대로 요약해봄.
Design 파트는 Dart에 국한된 내용이 아니라 '클린 코드'로 다루어지는 범용적인 원칙과 겹치는 내용이 많음.

왜 Flutter는 Dart를 사용하는가? - Flutter의 특징과 장점이 정리된 글

Dart의 두가지 핵심 컨셉

  1. Be consistent (일관성 있게) : 코드 컨벤션에 대한 논쟁은 주관적이고 정답이 없지만, 일관성을 유지하는건 객관적으로 도움이 된다.
  2. Be brief (짧게) : Dart는 C, Java, JavaScript 등의 언어와 비슷한 구문이 많다. 저 언어들을 이미 알고있다면 친숙한 형태로 보이지만, 더 쉽고 간결하게 사용할 수 있도록 설계되었다.

주요 용어

  • library member : top-level field, getter, setter, or function
  • class member : constructor, field, getter, setter, function, or 클래스 내부에 선언된 연산자
  • member : library member or a class member.
  • variable : top-level variables, parameters, and local variables. static or instance fields를 포함하지 않는다
  • type : 타입을 정의할때 사용한다 (class, typedef, enum)
  • property : top-level variable, getter (inside a class or at the top level, instance or static), setter (same), or field (instance or static).

Code Style

클래스, enum, typedef, type 파라미터는 UpperCamelCase 사용

class SliderMenu { ... }

class HttpRequest { ... }

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

libraries, packages, directories, source file은 스네이크_케이스 사용

library peg_parser.source_scanner;

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

그 외 변수, 상수명엔 lowerCaseCamel 사용 (처음엔 자바처럼 상수명을 대문자로만 작성했지만, 가독성 문제로 컨벤션을 변경)

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

dangling else를 사용하지 말것 (body가 한줄이어도 괄호를 붙여주기)

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

예외적으로, else문이 없고 if문이 간결할 경우 괄호를 생략할 수 있다.

if (arg == null) return defaultValue;

그 외에도...

  • private이 아닌 변수명의 앞에 _를 사용하지 말것
  • prefix letter를 사용하지 말것
  • 한 라인이 80글자를 넘어가는 것을 피할 것

Usage

null을 명시적으로 할당하지 말 것. nullable 타입은 따로 null로 초기화하지 않아도 null이 암시적 할당이 되기 때문에 코드로 직접 작성하지 않아도 된다.

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

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

nullable 타입을 boolean으로 변환할 때 == 대신 ??를 사용할 것

// O
if (optionalThing?.isEnabled ?? true) {
  print('Have enabled thing or nothing.');
}

// X
if (optionalThing?.isEnabled != true) {
  print('Have enabled thing or nothing.');
}
  • 둘다 실행해보면 동일한 결과가 나오지만, ??를 권장하는 이유
    • ?? 연산자는 연산식이 null과 관련되었음을 명확하게 나타낸다
    • == true 같은 구문은 불필요한 연산자로 여겨져서(redundant) 지울 수 있는 실수를 유발한다.

Dart는 List, Set, Map 컬렉션을 생성할 때 이름을 생략할 수 있도록 지원한다. 가급적 Collection literals를 사용할 것

// good
var points = <Point>[];
var addresses = <String, Address>{};
var counts = <int>{};

// avoid
var address = Map<String, Address>();

함수에 이름을 선언해야 할 경우 람다식 변수를 만드는 대신 함수를 직접 선언할 것

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

// avoid
void main() {
  var localFunction = () {
    ...
  };
}

Dart는 (레거시 때문에) 파라미터에 기본값을 부여할 수 있도록 :, = 연산자를 둘 다 지원하지만 일관성을 위해 가급적 =를 사용하는 것을 권장한다.

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

변수를 선언할 때 var, final을 사용하는 규칙의 일관성을 지킬 것. 널리 사용되는 두가지 규칙은 다음과 같다.

두가지 규칙 중 하나를 취사 선택했다면 프로젝트 전체에 일관성을 유지하자.

  1. final은 불변하는 지역 변수 선언 시 사용, var은 재할당 가능한 지역변수 선언 시 사용
  2. var을 모든 지역 변수에 붙이고, 재할당은 절대 불가능하다. final은 field랑 top-level 변수 선언에만 사용 가능

그 외에도...

  • 초기화 여부를 체크해야하는 변수면 late(지연 초기화를 위한 키워드)를 사용하지 말 것
  • nullable field를 local 변수로 사용해야 할 경우 ! 연산자를 붙이는 대신 지역 변수로 값을 복사해서 사용하는 것을 고려할 것
  • 긴 문자열을 사용해 줄바꿈이 필요할 경우 + 연산자를 사용하지 말고 나란히 문자열을 나열할 것
  • 문자열에서 ${ }를 사용할 때 괄호를 생략할 수 있는 상황이면 생략할 것
  • 컬렉션이 비었는지 확인할 때 .length 대신 .isEmpty를 사용할 것
  • 컬렉션의 forEach, from, cast 함수는 가급적 사용하지 말 것
  • 컬렉션에 타입을 기준으로한 필터링이 필요할 경우 whereType 함수를 사용할 것
  • final은 가급적 읽기 전용 property를 만드는데 사용하자
  • member를 단순하게 표현하기 위해 =>를 사용하자. (=> 뒤로 읽기 어려운 코드가 오지 않게할 것)
  • this는 생성자를 초기화할 때 필드와 파라미터의 이름이 동일할 경우 구분하는 용도로만(shadowing) 사용하고, 그 외엔 가급적 사용하지 말자
  • 가급적 선언과 동시에 필드를 초기화하자
  • 생성자 바디가 비어있을 경우 {} 대신 ;를 사용하자.
  • 불필요한 new, const 키워드를 사용하지 말 것
  • 불필요하게 async 키워드를 사용하지 말 것