Effective Dart
Effective Dart
일관되고 유지보수하기 쉬우며 효율적인 Dart 라이브러리를 만들기 위한 모범 사례를 소개해 드릴게요.
출처: 원문
본문
지난 몇 년 동안 우리는 수많은 Dart 코드를 작성하면서 무엇이 잘 작동하고 무엇이 그렇지 않은지에 대해 많이 배웠어요. 여러분도 일관되고, 견고하며, 빠른 코드를 작성할 수 있도록 이 경험을 공유해 드릴게요. 여기에는 두 가지 핵심 주제가 있어요.
- 일관되게 작성해요. 형식이나 대소문자 같은 것에 대해 어떤 것이 더 나은지는 주관적이라 결론을 내기 어려워요. 다만 우리가 확실히 아는 것은 일관성이 객관적으로 도움이 된다는 점이에요. 두 코드 조각이 다르게 보인다면, 그건 어떤 의미 있는 부분에서 실제로 다르기 때문이어야 해요. 눈에 띄는 코드 한 조각이 있다면, 그건 유용한 이유가 있어서 눈에 띄는 것이어야 해요.
- 간결하게 작성해요. Dart는 익숙함을 목표로 설계되어 C, Java, JavaScript 등 다른 언어들의 많은 문장과 표현을 물려받았어요. 그러나 우리가 Dart를 만든 이유는 그 언어들이 제공하는 것보다 개선할 여지가 많기 때문이에요. 문자열 보간(interpolation)부터 초기화 형식 인자(initializing formals)까지, 여러분의 의도를 더 간단하고 쉽게 표현할 수 있도록 많은 기능을 추가했어요. 어떤 것을 표현하는 방법이 여러 가지라면, 보통 가장 간결한 방법을 고르는 게 좋아요. 이것은 프로그램 전체를 한 줄에 몰아넣는 코드 골프를 하라는 뜻이 아니에요. 목표는 밀도 있는 코드가 아니라 경제적인 코드예요.
가이드 소개
지침을 소화하기 쉽게 몇 개의 별도 페이지로 나누어 두었어요.
- 스타일 가이드 – 코드를 배치하고 구성하는 규칙, 적어도
dart format이 처리해 주지 못하는 부분을 정의해요. 스타일 가이드는 식별자를 어떻게 형식화하는지(camelCase,using_underscores등)도 지정해요. - 문서화 가이드 – 주석 안에 무엇을 넣어야 하는지에 대해 알아야 할 모든 것을 알려줘요. 문서 주석(doc comment)과 평범한 일반 코드 주석 둘 다요.
- 사용 가이드 – 언어 기능을 가장 잘 활용해 동작을 구현하는 방법을 가르쳐줘요. 문장이나 표현에 들어가는 내용이라면 여기서 다뤄요.
- 디자인 가이드 – 가장 부드러운 가이드지만 범위가 가장 넓어요. 라이브러리를 위해 일관되고 사용하기 좋은 API를 설계하는 데 배운 것들을 다뤄요. 타입 시그니처나 선언에 들어가는 내용이라면 이 가이드에서 다뤄요.
모든 지침의 링크는 요약(summary)에서 볼 수 있어요.
가이드 읽는 방법
각 가이드는 몇 개의 섹션으로 나뉘어요. 섹션은 지침 목록을 담고 있어요. 각 지침은 다음 단어 중 하나로 시작해요.
- DO(하세요) 지침은 항상 따라야 하는 관행을 설명해요. 이 지침에서 벗어날 타당한 이유는 거의 없어요.
- DON'T(하지 마세요) 지침은 그 반대, 즉 거의 좋은 생각이 아닌 것들이에요. 우리는 역사적 짐이 적기 때문에 다른 언어들만큼 이런 지침이 많지 않기를 바래요.
- PREFER(선호하세요) 지침은 따라야 하는 관행이에요. 다만 그렇게 하지 않는 것이 타당한 상황도 있을 수 있어요. 그럴 때는 지침을 무시했을 때의 모든 영향까지 이해하고 행동하도록 해요.
- AVOID(피하세요) 지침은 "선호"의 반대, 즉 하지 말아야 하지만 드물게 타당한 이유가 있을 수 있는 것들이에요.
- CONSIDER(고려하세요) 지침은 상황, 선례, 그리고 자신의 선호에 따라 따를 수도 있고 안 따를 수도 있는 관행이에요.
일부 지침은 규칙이 적용되지 않는 예외를 설명해요. 예외가 나열되어 있어도 완전하지 않을 수 있으니, 다른 경우에는 여전히 자신의 판단을 써야 할 수도 있어요.
이렇게 들으면 신발끈을 제대로 묶지 않으면 경찰이 문을 두드릴 것처럼 느껴질 수 있지만, 그렇게 심각한 건 아니에요. 여기 있는 지침 대부분은 상식이고 우리 모두 합리적인 사람들이에요. 목표는 언제나 읽기 좋고, 명확하며, 유지보수하기 쉬운 코드예요.
Dart 분석기(analyzer)는 이런 지침과 그 외 다른 지침을 따르는 좋고 일관된 코드를 작성하도록 돕는 린터(linter)를 제공해요. 지침을 따르는 데 도움이 되는 린터 규칙이 하나 이상 있으면, 그 지침은 해당 규칙에 연결돼요. 링크는 다음과 같은 형식을 사용해요.
린터 규칙:
unnecessary_getters_setters
린터 사용법을 배우려면 linter 규칙 활성화 방법과 linter 규칙 목록을 참고해요.
용어집
지침을 간결하게 유지하기 위해, 서로 다른 Dart 구조를 가리키는 몇 가지 약칭 용어를 사용해요.
- 라이브러리 멤버(library member) 는 최상위 필드, 게터, 세터, 또는 함수예요. 기본적으로 타입이 아닌 최상위에 있는 모든 것이에요.
- 클래스 멤버(class member) 는 클래스 안에 선언된 생성자, 필드, 게터, 세터, 함수, 또는 연산자예요. 클래스 멤버는 인스턴스 멤버일 수도 있고 정적(static) 멤버일 수도 있으며, 추상적이거나 구체적일 수 있어요.
- 멤버(member) 는 라이브러리 멤버 또는 클래스 멤버 중 하나예요.
- 변수(variable) 는 일반적으로 사용될 때 최상위 변수, 매개변수, 지역 변수를 가리켜요. 정적 필드나 인스턴스 필드는 포함하지 않아요.
- 타입(type) 은 이름 붙은 타입 선언, 즉 클래스, typedef, 또는 enum이에요.
- 프로퍼티(property) 는 최상위 변수, 게터(클래스 안 또는 최상위, 인스턴스 또는 정적), 세터(동일), 또는 필드(인스턴스 또는 정적)예요. 대략 "필드처럼 생긴" 이름 붙은 구조를 가리켜요.
전체 규칙 요약
스타일(Style)
식별자(Identifiers)
- DO: 타입 이름은
UpperCamelCase로 지어요. - DO: 확장(extension) 이름은
UpperCamelCase로 지어요. - DO: 패키지, 디렉터리, 소스 파일 이름은
lowercase_with_underscores로 지어요. - DO: import 접두사 이름은
lowercase_with_underscores로 지어요. - DO: 그 외 다른 식별자는
lowerCamelCase로 지어요. - PREFER: 상수 이름에는
lowerCamelCase를 사용해요. - DO: 두 글자보다 긴 약어와 준말은 단어처럼 첫 글자를 대문자로 써요.
- PREFER: 사용하지 않는 콜백 매개변수에는 와일드카드를 사용해요.
- DON'T: 비공개가 아닌 식별자에 밑줄 접두사를 쓰지 마세요.
- DON'T: 접두 글자를 쓰지 마세요.
- DON'T: 라이브러리에 명시적으로 이름을 붙이지 마세요.
정렬(Ordering)
- DO:
dart:import를 다른 import보다 먼저 배치해요. - DO:
package:import를 상대(relative) import보다 먼저 배치해요. - DO: 모든 import 뒤의 별도 섹션에서 export를 지정해요.
- DO: 섹션을 알파벳순으로 정렬해요.
형식(Formatting)
- DO:
dart format을 사용해 코드를 형식화해요. - CONSIDER: 코드를 formatter에 친화적으로 바꾸는 것을 고려해요.
- PREFER: 80자 이하의 줄을 선호해요.
- DO: 모든 흐름 제어 문장에 중괄호를 사용해요.
문서화(Documentation)
주석(Comments)
- DO: 주석을 문장처럼 형식화해요.
- DON'T: 문서화에 블록 주석을 사용하지 마세요.
문서 주석(Doc comments)
- DO: 멤버와 타입을 문서화할 때
///문서 주석을 사용해요. - PREFER: 공개 API에 문서 주석을 작성해요.
- CONSIDER: 라이브러리 수준 문서 주석 작성도 고려해요.
- CONSIDER: 비공개 API에 문서 주석 작성도 고려해요.
- DO: 문서 주석을 한 문장 요약으로 시작해요.
- DO: 문서 주석의 첫 문장을 별도의 문단으로 분리해요.
- AVOID: 주변 맥락과 중복을 피해요.
- PREFER: 함수나 메서드의 주요 목적이 부수 효과(side effect)라면 3인칭 동사로 주석을 시작해요.
- PREFER: 불리언이 아닌 변수나 프로퍼티 주석은 명사구로 시작해요.
- PREFER: 불리언 변수나 프로퍼티 주석은 "Whether" 뒤에 명사구나 동명사구로 시작해요.
- PREFER: 함수나 메서드의 주요 목적이 값 반환이라면 명사구나 비명령형 동사구를 사용해요.
- DON'T: 프로퍼티의 게터와 세터 둘 다에 문서를 작성하지 마세요.
- PREFER: 라이브러리나 타입 주석은 명사구로 시작해요.
- CONSIDER: 문서 주석에 코드 샘플을 포함하는 것을 고려해요.
- DO: 문서 주석에서 대괄호를 사용해 범위 안의 식별자를 가리켜요.
- DO: 매개변수, 반환 값, 예외를 산문(prose)으로 설명해요.
- DO: 메타데이터 애너테이션 앞에 문서 주석을 배치해요.
마크다운(Markdown)
- AVOID: 마크다운을 과하게 사용하지 마세요.
- AVOID: 형식화에 HTML을 사용하지 마세요.
- PREFER: 코드 블록에는 백틱 fence를 사용해요.
작문(Writing)
- PREFER: 간결함을 선호해요.
- AVOID: 명백하지 않은 한 약어와 준말을 사용하지 마세요.
- PREFER: 멤버의 인스턴스를 가리킬 때 "the" 대신 "this"를 사용해요.
사용(Usage)
라이브러리(Libraries)
- DO:
part of지시문에 문자열을 사용해요. - DON'T: 다른 패키지의
src디렉터리 안에 있는 라이브러리를 import하지 마세요. - DON'T: import 경로가
lib안팎으로 침범하지 않게 해요. - PREFER: 상대 import 경로를 선호해요.
Null
- DON'T: 변수를 명시적으로
null로 초기화하지 마세요. - DON'T: 명시적 기본값
null을 사용하지 마세요. - DON'T: 동등 비교 연산에
true나false를 사용하지 마세요. - AVOID: 초기화 여부를 확인해야 한다면
late변수를 사용하지 마세요. - CONSIDER: nullable 타입을 사용할 때 타입 승격이나 null 검사 패턴을 고려해요.
문자열(Strings)
- DO: 인접한 문자열을 사용해 문자열 리터럴을 이어 붙여요.
- PREFER: 문자열과 값을 조합할 때 보간(interpolation)을 사용해요.
- AVOID: 필요하지 않을 때 보간에서 중괄호를 사용하지 마세요.
컬렉션(Collections)
- DO: 가능하면 컬렉션 리터럴을 사용해요.
- DON'T: 컬렉션이 비어 있는지 확인할 때
.length를 사용하지 마세요. - AVOID: 함수 리터럴과 함께
Iterable.forEach()를 사용하지 마세요. - DON'T: 결과의 타입을 바꾸려는 게 아니라면
List.from()을 사용하지 마세요. - DO: 컬렉션을 타입별로 필터링할 때
whereType()을 사용해요. - DON'T: 가까운 연산으로 처리될 수 있을 때
cast()를 사용하지 마세요. - AVOID:
cast()를 사용하지 마세요.
함수(Functions)
- DO: 함수에 이름을 바인딩할 때 함수 선언을 사용해요.
- DON'T: tear-off로 충분할 때 람다를 만들지 마세요.
변수(Variables)
- DO: 지역 변수의
var와final에 일관된 규칙을 따르세요. - AVOID: 계산할 수 있는 것을 저장하지 마세요.
멤버(Members)
- DON'T: 필드를 불필요하게 게터와 세터로 감싸지 마세요.
- PREFER: 읽기 전용 프로퍼티를 만들 때
final필드를 사용해요. - CONSIDER: 단순한 멤버에
=>를 사용하는 것을 고려해요. - DON'T: 이름 붙은 생성자로 리다이렉트하거나 그림자(shadowing)를 피할 때를 제외하고
this.를 사용하지 마세요. - DO: 가능하면 선언 시점에 필드를 초기화해요.
생성자(Constructors)
- DO: 가능하면 초기화 형식 인자(initializing formals)를 사용해요.
- DON'T: 생성자 초기화 리스트로 충분할 때
late를 사용하지 마세요. - DO: 빈 생성자 본문에는
{}대신;을 사용해요. - PREFER: 간결한 생성자 구문을 사용해요.
- DON'T:
new를 사용하지 마세요. - DON'T:
const를 중복해서 사용하지 마세요.
오류 처리(Error handling)
- AVOID:
on절 없는 catch를 피하세요. - DON'T:
on절 없는 catch의 오류를 버리지 마세요. - DO: 프로그램 오류에만
Error를 구현하는 객체를 throw해요. - DON'T:
Error나 그것을 구현하는 타입을 명시적으로 catch하지 마세요. - DO: 잡은 예외를 다시 던질 때
rethrow를 사용해요.
비동기(Asynchrony)
- PREFER: raw future보다
async/await을 선호해요. - DON'T: 유용한 효과가 없을 때
async를 사용하지 마세요. - CONSIDER: 스트림을 변환할 때 고차 메서드를 사용하는 것을 고려해요.
- AVOID:
Completer를 직접 사용하지 마세요. - DO: 타입 인자가
Object가 될 수 있는FutureOr<T>를 구분할 때Future<T>를 검사해요.
디자인(Design)
이름(Names)
- DO: 용어를 일관되게 사용해요.
- AVOID: 약어를 사용하지 마세요.
- PREFER: 가장 설명적인 명사를 마지막에 배치해요.
- CONSIDER: 코드가 문장처럼 읽히도록 하는 것을 고려해요.
- PREFER: 불리언이 아닌 프로퍼티나 변수에는 명사구를 사용해요.
- PREFER: 불리언 프로퍼티나 변수에는 비명령형 동사구를 사용해요.
- CONSIDER: 이름 붙은 불리언 매개변수에서 동사를 생략하는 것을 고려해요.
- PREFER: 불리언 프로퍼티나 변수에는 "긍정적" 이름을 사용해요.
- PREFER: 주요 목적이 부수 효과인 함수나 메서드에는 명령형 동사구를 사용해요.
- PREFER: 주요 목적이 값 반환이라면 함수나 메서드에 명사구나 비명령형 동사구를 사용해요.
- CONSIDER: 수행하는 작업에 주의를 끌고 싶다면 함수나 메서드에 명령형 동사구를 고려해요.
- AVOID: 함수나 메서드 이름을
get으로 시작하지 마세요. - PREFER: 객체의 상태를 새 객체로 복사하는 메서드 이름은
to___()로 지어요. - PREFER: 원본 객체에 기반한 다른 표현을 반환하는 메서드 이름은
as___()로 지어요. - AVOID: 함수나 메서드 이름에 매개변수를 설명하지 마세요.
- DO: 타입 매개변수 이름을 지을 때 기존의 연상 기호 규약을 따르세요.
라이브러리(Libraries)
- PREFER: 선언을 비공개로 만드는 것을 선호해요.
- CONSIDER: 같은 라이브러리에 여러 클래스를 선언하는 것을 고려해요.
클래스와 믹스인(Classes and mixins)
- AVOID: 단순한 함수로 충분할 때 멤버가 하나뿐인 추상 클래스를 정의하지 마세요.
- AVOID: 정적 멤버만 포함하는 클래스를 정의하지 마세요.
- AVOID: 서브클래싱을 의도하지 않은 클래스를 확장하지 마세요.
- DO: 클래스가 확장 가능한지 제어하기 위해 클래스 한정자를 사용해요.
- AVOID: 인터페이스로 의도되지 않은 클래스를 구현하지 마세요.
- DO: 클래스가 인터페이스가 될 수 있는지 제어하기 위해 클래스 한정자를 사용해요.
- PREFER: mixin 클래스보다 순수 mixin이나 순수 클래스를 정의해요.
생성자(Constructors)
- CONSIDER: 클래스가 지원한다면 생성자를
const로 만드는 것을 고려해요.
멤버(Members)
- PREFER: 필드와 최상위 변수를
final로 만드는 것을 선호해요. - DO: 개념적으로 프로퍼티에 접근하는 연산에는 게터를 사용해요.
- DO: 개념적으로 프로퍼티를 변경하는 연산에는 세터를 사용해요.
- DON'T: 대응하는 게터 없이 세터를 정의하지 마세요.
- AVOID: 오버로딩을 흉내내기 위해 런타임 타입 검사를 사용하지 마세요.
- AVOID: 초기화가 없는 공개
late final필드를 피하세요. - AVOID: nullable
Future,Stream, 컬렉션 타입을 반환하지 마세요. - AVOID: 플루언트 인터페이스를 위해 메서드에서
this를 반환하지 마세요.
타입(Types)
- DO: 초기화가 없는 변수에는 타입 애너테이션을 붙여요.
- DO: 타입이 명확하지 않으면 필드와 최상위 변수에 타입 애너테이션을 붙여요.
- DON'T: 초기화된 지역 변수에 타입 애너테이션을 중복해서 붙이지 마세요.
- DO: 함수 선언의 반환 타입에 애너테이션을 붙여요.
- DO: 함수 선언의 매개변수 타입에 애너테이션을 붙여요.
- DON'T: 함수 표현식의 추론된 매개변수 타입에 애너테이션을 붙이지 마세요.
- DON'T: 초기화 형식 인자에 타입 애너테이션을 붙이지 마세요.
- DO: 추론되지 않는 제네릭 호출에는 타입 인자를 작성해요.
- DON'T: 추론되는 제네릭 호출에는 타입 인자를 작성하지 마세요.
- AVOID: 불완전한 제네릭 타입을 작성하지 마세요.
- DO: 추론이 실패하도록 두는 대신
dynamic으로 애너테이션을 붙여요. - PREFER: 함수 타입 애너테이션에 시그니처를 사용해요.
- DON'T: 세터에 반환 타입을 지정하지 마세요.
- DON'T: 레거시 typedef 구문을 사용하지 마세요.
- PREFER: typedef보다 인라인 함수 타입을 선호해요.
- PREFER: 매개변수에 함수 타입 구문을 사용해요.
- AVOID: 정적 검사를 끄고 싶지 않다면
dynamic을 사용하지 마세요. - DO: 값을 생성하지 않는 비동기 멤버의 반환 타입으로
Future<void>를 사용해요. - AVOID: 반환 타입으로
FutureOr<T>를 사용하지 마세요.
매개변수(Parameters)
- AVOID: 위치 기반 불리언 매개변수를 피하세요.
- AVOID: 사용자가 앞선 매개변수를 생략하고 싶을 수 있다면 선택적 위치 매개변수를 피하세요.
- AVOID: 특수한 "인자 없음" 값을 받는 필수 매개변수를 피하세요.
- DO: 범위를 받을 때 시작 포함(inclusive)과 끝 제외(exclusive) 매개변수를 사용해요.
동등(Equality)
- DO:
==를 오버라이드하면hashCode도 오버라이드해요. - DO:
==연산자가 수학적 동등 규칙을 따르게 해요. - AVOID: 가변 클래스에 사용자 지정 동등을 정의하지 마세요.
- DON'T:
==의 매개변수를 nullable로 만들지 마세요.