Effective Dart - Collections in usage

MJ·2023년 6월 11일
0

Dart Basic

목록 보기
8/12

가능하다면 컬렉션 리터럴을 사용한다

  • Dart는 세 개의 핵심 컬렉션 타입을 가지고 있음(List, Map, Set). Dart는 컬랙션 생성을 위한 더 좋은 내장 문법을 가지고 있음
// good case
var points = <Point>[];
var addresses = <String, Address>{};
var counts = <int>{};

// bad case
var addresses = Map<String, Address>();
var counts = Set<int>();

-> 해당 예제는 named 생성자에 적용하지 않음, List.from(), Map.fromIterable() 등 비슷한 생성자들은 저마다 사용법이 존재함 (List 클래스 또한 unnamed 생성자가 존재하지만, null safe Dart에서는 사용이 금지됨)

  • 컬렉션 리터럴은 다른 컬렉션의 요소들을 추가할 때 전개 연산자에 접근이 가능하고 요소를 빌드하는 동안 [if 그리고 for]를 사용하여 흐름 제어를 수행할 수 있다
// good case
var arguments = [
  ...options,
  command,
  ...?modeFlags,
  for (var path in filePaths)
    if (path.endsWith('.dart')) path.replaceAll('.dart', '.js')
];

// bad case
var arguments = <String>[];
arguments.addAll(options);
arguments.add(command);
if (modeFlags != null) arguments.addAll(modeFlags);
arguments.addAll(filePaths
    .where((path) => path.endsWith('.dart'))
    .map((path) => path.replaceAll('.dart', '.js')));

컬렉션이 비어있는지 확인할 때 .length를 사용하지 않는다

  • Iterable을 사용한 컬렉션의 구현은 자신의 길이를 알고 있거나 상수 시간 안에 제공하도록 되어있지 않음. 단지 컬렉션에 포함된 것이 있는지 확인하기 위해 .length를 호출하는건 매우 느릴 수 있다
  • 더 빠르고 가독성 높은 .isEmpty와 .isNotEmpty를 사용하는 것이 좋다
// good case
if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');

// bad case
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');

Iterable.forEach()를 함수 리터럴과 함께 사용하는 것을 피한다

  • Dart에서는 시퀀스를 반복할 때 for-in 루프를 사용하는 것이 관용적인 방법임
// good case
for (final person in people) {
  ...
}

// bad case
people.forEach((person) {
  ...
});

결과 값의 타입을 바꾸려는 것이 아니면, List.from()을 사용하지 않는다

  • 주어진 Iterable를 사용해서 동일한 요소를 가지는 새로운 List를 만드는 확실한 방법은 아래 두가지가 대표적

    var copy1 = iterable.toList();
    var copy2 = List.from(iterable);

  • 위 둘의 명백한 차이는 첫 번째가 더 간결하고 원래 객체의 타입 인자를 보전한다는 것

// Creates a List<int>:
var iterable = [1, 2, 3];

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

// bad case
// Prints "List<dynamic>":
print(List.from(iterable).runtimeType);
  • 타입을 바꾸고 싶다면 List.from()을 호출하는 것이 좋음
// good case
var numbers = [1, 2.3, 4]; // List<num>.
numbers.removeAt(1); // Now it only contains integers.
var ints = List<int>.from(numbers);

타입에 대해 신경스찌 않거나, 단지 iterable를 복사하고 원래 타입을 보존하는 것이 목적이라면, toList()를 사용한다

컬렉션을 타입으로 필터하고 싶다면, whereType()을 사용한다

  • 두 개의 간접 계층과 중복된 런타임 검사를 동반하여 두 개의 wrapper가 생성되도록함
  • 간결하면 불필요한 wrapper의 계층이 없이 원하는 타입의 Iterable을 생성할 수 있음
var objects = [1, 'a', 2, 'b', 3];
var ints = objects.whereType<int>();

코드의 주변에서 cast()와 같은 역할을 하는 연산이 있다면, cast()를 사용하지 마십시오.

  • Iterable 또는 stream을 다룰 때, 종종 cast를 수행, 특정 타입 인자를 사용하여 객체를 생성할 때, cast()를 무작정 호출하기 보다 이미 존재하는 변형이 해당 타입으로의 cast를 수행하는지 먼저 확인한다
  • toList()를 호출하면, 해당 호출을 원하는 객체의 타입 T를 지정하여 List.from()으로 대체함
// good case
var stuff = <dynamic>[1, 2];
var ints = List<int>.from(stuff);
  
// bad case
var stuff = <dynamic>[1, 2];
var ints = stuff.toList().cast<int>();
  • map()을 호출하면 원하는 타입을 명시적으로 타입 인자로 넘겨주어 해당 타입의 iterable을 생성, 타입 추론이 map()으로 넘겨준 함수를 기반으로 옳은 타입을 선택하지만, 종종 명시해야할 필요가 있음
// good case
var stuff = <dynamic>[1, 2];
var reciprocals = stuff.map<double>((n) => 1 / n);
  
// bad case
var stuff = <dynamic>[1, 2];
var reciprocals = stuff.map((n) => 1 / n).cast<double>();

cast() 사용을 피한다

  • 때때로 객체의 타입을 고정시킬만한 명령어가 없을 수 있지만 그런 경우라도 가능하다면 cast()를 사용하여 컬렌션 타입을 변경하는 것은 피해야 한다

올바른 타입으로 생성한다 -> 컬렉션이 처음 생성되는 코드를 수정하여 올바른 타입을 가지게 한다
요소에 접근할 때 캐스팅을 시도한다 -> 해당 컬렉션에 반복을 수행한다면, 반복 내부에서 각 요소를 캐스팅한다
List.from()을 사용하여 캐스팅하도록 한다 -> 컬렉션의 모든 요소에 접근을 끝냈고 원본 객체가 더 이상 필요하지 않다면, List.from()을 사용하여 타입을 변환한다

  • 타입 생성 예제
// good case
List<int> singletonList(int value) {
  var list = <int>[];
  list.add(value);
  return list;
}
  
// bad case
List<int> singletonList(int value) {
  var list = []; // List<dynamic>.
  list.add(value);
  return list.cast<int>();
}
  • 요소에 접근할 때 캐스팅하는 방법
// good case
void printEvens(List<Object> objects) {
  // List에 int만 존재한다는 것을 알고 있습니다.
  for (final n in objects) {
    if ((n as int).isEven) print(n);
  }
}
  
// bad case
void printEvens(List<Object> objects) {
  // List에 int만 존재한다는 것을 알고 있습니다.
  for (final n in objects.cast<int>()) {
    if (n.isEven) print(n);
  }
}
  • List.from()을 사용한 캐스팅
// good case
int median(List<Object> objects) {
  // List에 int만 존재한다는 것을 알고 있습니다.
  var ints = List<int>.from(objects);
  ints.sort();
  return ints[ints.length ~/ 2];
}
  
// bad case
int median(List<Object> objects) {
  // List에 int만 존재한다는 것을 알고 있습니다.
  var ints = objects.cast<int>();
  ints.sort();
  return ints[ints.length ~/ 2];
}
profile
느긋하게 살자!

0개의 댓글