티스토리 뷰

Mobile/Flutter

[Flutter] Dart의 Coding Standard

풍요로운 해구름 2021. 3. 12. 19:09

Flutter에서는 코딩에 대한 구체적인 표준이나 규범을 정의하지 않고 있습니다. 하지만 Flutter는 Dart 언어를 사용하기에 Dart의 Coding Standard를 준수하여 작성하게 됩니다. 여기서는 Dart의 Coding Standard를 정리해 보았습니다.

식별자 (Identifiers)

Dart에서는 UppderCamelCase, lowerCamelCase, lowercase_with_underscores 네이밍 규칙을 사용합니다.

  • UppderCamelCase : 대문자로 시작하며, 각 단어의 시작 문자를 대문자로 합니다. (예: UpperCamelCase)
  • lowerCamelCase : 소문자로 시작하며, 각 단어의 시작 문자를 대문자로 합니다. (예: lowerCamelCase)
  • lowercase_with_underscores : 소문자만 사용하며 단어의 구분은 언더스코어(_)를 사용합니다. 대문자나 다른 구분자는 사용하지 않습니다.
  • SCREAMING_CAPS : 항상 대문자로 표현합니다. Dart 초기에 사용했으나 이제는 더 이상 사용하지 않습니다.

Type의 경우 UpperCamelCase를 사용합니다.

Class, Enum, Typedef, Type 매개변수는 UpperCamelCase를 사용합니다.

class SliderMenu { ... }
class HttpRequest { ... }
typedef Predicate<T> = bool Function(T value);

Metadata Annotation으로 사용되는 Class의 경우에도 동일합니다.

class Foo {
  const Foo([arg]);
}

@Foo(anArg)
class A { ... }

@Foo()
class B { ... }

만약 Annotation Class의 생성자가 매개변수를 받지 않는다면, 별도의 상수를 lowerCamelCase 형태로 정의하여 사용할 수 있습니다.

const foo = Foo();

@foo
class C { ... }

extensions의 경우 UpperCamelCase를 사용합니다.

참고: Extension은 Dart 2.7 버전에 추가된 기능입니다. 자세한 정보는 Extension design 문서를 참고하세요.

extension MyFancyList<T> on List<T> { ... }

extension SmartIterable<T> on Iterable<T> { ... }

소스파일, 디렉터리, 라이브러리, 패키지는 lowercase_with_underscores를 사용합니다.

일부 운영체제의 파일시스템은 파일명의 대소문자를 구분하지 못합니다. 따라서 파일시스템이 달라져도 문제가 발생하지 않게 파일명은 모두 소문자를 사용합니다. 파일명이 길어질 경우 가독성을 높이기 위해 언더스코어(_)를 구분자로 사용할 수 있습니다. 언더스코어(_)는 Dart 언어 내에서 Identifier로서 사용가능한 문자이기 때문에 추후 Dart에서 Symbolic Import를 지원할 때에도 파일명을 그대로 식별자로 사용 할 수 있습니다.

//Good
library peg_parser.source_scanner;

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

//Bad
library pegparser.SourceScanner;

import 'file-system.dart';
import 'SliderMenu.dart';

참고: 여기에서는 라이브러리의 이름을 정하는 가이드를 보여주고 있습니다. 괜찮다면 파일에서 library 지시문은 생략하는 것이 좋습니다.

library prefix(import prefix)는 lowercase_with_underscores를 사용합니다.

두 라이브러리의 이름이 충돌할 경우 library prefix(import prefix)를 통해 구분할 수 있습니다. 이 경우 lowercase_with_underscores를 사용합니다.

//Good
import 'dart:test/lib.dart' as test_lib;
import 'package:example/lib.dart' as example_lib;

var element1 = test_lib.Element();
var element2 = example_lib.Element();


//Bad
import 'dart:test/lib.dart' as TestLib;
import 'package:example/lib.dart' as exampleLib;

var element1 = TestLib.Element();
var element2 = exampleLib.Element();

기타 식별자(Identifier)의 경우에는 lowerCamelCase를 사용합니다.

Class의 멤버, Top-level definition, 변수, 매개변수, 명명된 매개변수(Named parameters) 등의 경우에는 소문자로 시작하며 각 단어의 첫글자는 대문자를 사용합니다. 그리고 Undercore 등 다른 구분자는 사용하지 않습니다.

//Good
var count = 3;
HttpRequest httpRequest;

void align(bool clearItems) {
  // ...
}


//Bad
var COUNT = 3;
HttpRequest http_Request;

void Align(bool clearItems) {
  // ...
}

상수에는 lowerCamelCase 사용하는 것이 좋습니다.

enum 값을 포함하여 상수에는 lowerCamelCase를 사용해주세요.

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

class Dice {
  static final numberGenerator = Random();
}

//Bad
const PI = 3.14;
const DefaultTimeout = 1000;
final URL_SCHEME = RegExp('^([a-z]+):');

class Dice {
  static final NUMBER_GENERATOR = Random();
}

이미 존재하는 코드와의 일관성을 위해 SCREAMING_CAPS(항상 대문자로 쓰는 것)을 사용할 수 있습니다. 예를들면:

  • 이미 SCREAMING_CAPS 규칙을 사용하여 작성된 코드나 라이브러리에 코드를 추가하는 경우
  • Java 코드와 병렬로 작성하는 경우 (예를들어 Java로 작성된 시스템과 Dart로 작성된 시스템이 서로 데이터를 주고 받는 경우 동일한 직렬화 규약을 사용해야합니다. 이를 위해 Protobufs(Protocol Buffers)를 사용하는 경우 SCREAMING_CAPS를 사용할 수 있습니다)

참고: Dart의 초창기에는 Java와 동일하게 상수에 SCREAMING_CAPS를 사용했습니다. 하지만 몇가지 이유로 사용하지 않게 되었습니다.

  • SCREAMING_CAPS로 작성된 경우 가독성이 떨어지는 경우가 많았습니다. 특히 CSS 색상을 표현하기 위한 enum 값을 식별하기 어려웠습니다. DEEP_BLUE보다는 deepBlue가 가독성이 더 높습니다.
  • 상수로 선언된 변수는 종종 final을 사용하는 비상수 변수로 변경되기도 합니다. 이 때 변수명을 바꾸어야 하는 수고를 덜 수 있습니다.
  • enum 타입의 값에 자동적으로 추가되는 value Property는 상수이면서 소문자입니다.

축약된 단어나 두문자어(Acronym)가 2글자를 초과하는 경우 대문자로 표현합니다.

FTP는 File Transfer Protocol를 축약한 대표적인 두문자어(Acronym)입니다. 영어권에서 이러한 축약된 단어는 대문자로 표기합니다. 하지만 대문자로 표현된 코드는 가독성을 떨어뜨립니다. 예를들어 여러 두문자어가 조합되는 경우가 있습니다. NFC + COM을 조합하는 경우 NFCCOM가 되며 HTTPS와 URL을 결합하면 HTTPSURL 형태가 되어 식별하기 어려워집니다.

이러한 문제를 피하기 위해 축약어나 두문자(Acronym)가 2글자를 초과하면 SCREAMING_CAPS가 아닌 UppderCamelCase나 lowerCamelCase를 사용합니다.

예외사항: 축약어가 2글자 이하인 경우에는 전체를 대문자로 표현합니다. 예를들어 IO(Input/Output)는 IO로 표현합니다. 반면에 ID(Identification)와 같은 축약어는 Id와 같이 좀 더 일반적인 표현을 사용할 수도 있습니다.

//Good
class HttpConnection {}
class DBIOPort {}
class TVVcr {}
class MrRogers {}

var httpRequest = ...
var uiHandler = ...
Id id;


//Bad
class HTTPConnection {}
class DbIoPort {}
class TvVcr {}
class MRRogers {}

var hTTPRequest = ...
var uIHandler = ...
ID iD;

콜백함수의 매개변수가 사용되지 않으면, 매개변수 이름을 _나 __ 등을 사용하는 것이 좋습니다.

콜백함수가 매개변수를 전달 받는 경우가 있습니다. 하지만 해당 매개변수가 사용되지 않으면 관용적으로 _로 표현합니다. 만약 사용되지 않는 매개변수가 1개 이상이라면 __, ___ 와 같이 여러 개의 Underscore를 사용합니다.

futureOfVoid.then((_, __, ___) {
  print('작업 완료');
});

주의: 이 지침은 지역함수 혹은 익명함수에만 해당하며 전역 함수나 Class의 메서드에는 해당되지 않습니다. 지역함수나 익명함수는 보통 해당 컨텍스트에서 즉시 사용되며 재사용되지 않기 때문에, 쓰지 않는 매개변수를 _로 표현해도 문제가 없습니다. 반대로 전역 함수나 클래스의 메서드의 경우에는 사용되지 않는다 해도 매개변수 이름을 분명히 해야합니다. 그렇게 해야 다른 사람이나 다른 곳에서 해당 함수를 참조할 때 각 매개변수가 무엇인지 식별할 수 있게 됩니다.

private 변수가 아니면 이름을 _로 시작하지 않습니다.

Dart에서 private 변수는 변수명이 _로 시작합니다. 따라서 개발자들은 Dart에서 _로 시작하는 이름을 만날 경우 private로 생각합니다.

Dart의 지역변수, 매개변수, 지역함수, Library prefix의 경우에는 private 개념이 없습니다. 그렇다고 _로 시작하는 이름을 사용하게 되면, 코드를 읽는 다른 사람으로 하여금 private 지역변수로 오해하게 만들 수 있습니다. 따라서 _로 시작하는 이름은 private 변수에만 사용해야합니다.

접미사 문자를 사용하지 않습니다

개발도구가 친절하지 않았던 과거 BCPL(Basic Combined Programming Language) 시절에는 헝가리안 표기법(Hungarian notation)이나 다른 접미사 규칙을 사용했었습니다. 하지만 Dart는 타입, 접근범위, 불변성 여부 등 다양한 속성을 검증하고 친절하게 알려줍니다. 따라서 가독성을 떨어뜨리는 불필요한 접미사를 더 이상 사용할 필요가 없습니다.

//Good
defaultTimeout
count
isValid

//Bad
kDefaultTimeout
intCount
blnIsValid

정렬

파일의 시작 부분을 깔끔하게 유지하기 위해서 지시자의 정렬 순서가 규정되어 있습니다. 각 "섹션"은 공백 라인으로 구분되어야 합니다.

단일 린터 규칙(Single Linter Rule)은 이러한 정렬 가이드라인을 준수하도록 합니다: directives_ordering

"dart:" Import를 다른 Import 앞에 두세요.

//Good
import 'dart:async'; //Dart Libraries
import 'dart:html';

import 'package:bar/bar.dart'; //Package Import
import 'package:foo/foo.dart';

"pakcage:" Import를 Relative Import 앞에 두세요.

//Good
import 'package:bar/bar.dart'; //Package Import
import 'package:foo/foo.dart';

import 'util.dart'; //Relative Import

참고: Dart의 Import 구문

  • "dart:"로 시작되는 import는 Dart Core Package에서 제공하는 라이브러리를 Import하는 것입니다.
  • Package Import란 lib 폴더에 포함된 파일을 Import 할 때 사용하며 경로는 lib 폴더에서 절대경로로 기술합니다. 예를들어 'package:foo/bar.dart'는 lib/foo/bar.dart를 import 합니다.
  • Relative Import는 Import 할 파일을 상대경로로 기술하는 방법을 말합니다. 예를들어 foo/foo.dart에서 bar/bar.dart를 import 하면 foo/bar/bar.dart가 import 됩니다.

export 구문은 모든 import 구문이 끝난 뒤에 분리된 섹션으로 두세요.

//Good
import 'src/error.dart';
import 'src/foo_bar.dart';

export 'src/error.dart';

//Bad
import 'src/error.dart';
export 'src/error.dart';
import 'src/foo_bar.dart';

Import 섹션들은 알파벳 순으로 정렬하세요.

//Good
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

import 'foo.dart';
import 'foo/foo.dart';

//Bad
import 'package:foo/foo.dart';
import 'package:bar/bar.dart';

import 'foo/foo.dart';
import 'foo.dart';

Formatting

다른 프로그래밍 언어들과 마찬가지로 Dart 언어도 White-space를 무시합니다. 하지만 컴퓨터가 아닌 사람은 White-space에 영향을 받습니다. White-space를 일관된 규칙으로 유지하는 것은 다른 사람들이 컴파일러와 동일한 방법으로 코드를 분석하게 하는데 도움을 줍니다.

dartfmt를 사용하여 코드를 Format 하세요.

Formatting은 리팩토링 작업 중에서 지루하면서 시간을 많이 잡아먹는 작업입니다. 다행이도 Dart에서는 섬세하면서 자동화된 Code Formatter인 dartfmt 도구를 제공합니다. dartfmt가 적용하는 규칙에 대한 문서가 존재하며 dartfmt가 생성하는 모든 것은 Dart의 공식 Whitespace 처리 규칙과 같습니다.

아래의 Formatting 가이드라인은 dartmft가 처리하지 못하는 몇가지 사항에 대한 것입니다.

코드를 좀 더 Formatter 친화적으로 변경하는 것을 고려하세요.

어떤 코드에 대해서도 Formatter은 가능한 최선을 다하겠지만, 모든 것을 완벽하게 처리해 줄 수는 없습니다. 특히 길이가 긴 식별자, 단계가 깊은 표현식 중첩, 다른 종류의 연산자 혼합 등의 경우에 Formatter가 생성하는 코드는 여전히 읽기가 힘들 수 있습니다.

Formatter가 생성한 코드의 가독성이 떨어지는 경우 코드를 단순화하거나 재구성해보세요. 지역변수 이름을 짧게 변경하거나 중첩 표현식을 새로운 지역변수로 끌어올려보세요. 가독성을 높이기 위해 진행했었던 코드정렬 작업을 직접 진행하세요. dartfmt는 아름다운 코드를 생성하기 위해 협력하는 파트너라고 생각하세요.

한 라인에 80자 이상은 피하세요.

가독성에 대한 연구는 길이가 긴 텍스트가 가독성을 떨어뜨린다고 밝히고 있습니다. 한 라인의 끝에서 다음 라인의 시작점으로 눈이 이동하는 거리가 멀어지기 때문입니다. 이러한 이유로 신문사나 잡지사에서는 지면을 여러 열(Column)로 쪼개어 가로 길이가 짧은 문장들을 배치하고 있습니다.

한 라인에 80자 이상의 코드는 대개 장황한 코드이거나 단순하게 정리할 수 있는 코드일 가능성이 높습니다. 코드 길이를 늘어뜨리는 주요 원인 중 하나는 매우 긴 길이의 Class 이름입니다. 이 경우 "Class 이름을 구성하는 각 단어가 정말 중요한 정보를 담고있는가? 혹은 이름 충돌을 방지하는데 필수적일까?"라는 질문을 던져보세요. 그렇지 않다면 생략하세요.

dartfmt는 99%의 Formatting 작업을 대신해주지만, 마지막 1%는 당신이 처리해야 합니다. dartfmt는 80자 이상의 코드를 짧게 줄여주지 않습니다. 이 작업은 직접 진행해야 합니다.

예외사항: 주석이나 문자열에 포함된 URI나 파일 경로(보통 import나 export 구문)의 경우, 80자를 초과하더라도 쪼개지 않고 길이가 긴 상태로 작성할 수 있습니다. 이렇게 하는 것이 파일 경로나 URI를 검색할 때 편리합니다.

예외사항: Dart의 Multi-line string 표현을 사용하는 경우 80자 이상의 라인을 포함할 수 있습니다. Dart 컴파일러는 White-space를 무시하지만, Multi-line string에 포함된 줄바꿈은 개행문자로서 처리합니다. 따라서 가독성을 위해서 Multi-line string에 줄바꿈을 추가하는 것은 프로그램이 기대와 다르게 동작하게 할 수 있습니다.

모든 제어처리 구문에는 중괄호를 사용하세요.

중괄호를 사용하는 것은 dangle else 문제를 막아줍니다.

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

예외사항: else 구문이 존재하지 않고 if 구문이 1줄로도 충분한 경우, 원한다면 중괄호를 생략할 수 있습니다.

//Good
if (arg == null) return defaultValue;

if 구문의 내용이 다음 줄에 위치 한다면 중괄호를 추가해야합니다.

//Good
if (overflowChars != other.overflowChars) {
  return overflowChars < other.overflowChars;
}

//Bad
if (overflowChars != other.overflowChars)
  return overflowChars < other.overflowChars;
댓글
댓글쓰기 폼