flutter에서 제공하는 코드 컨벤션을 정리해보자!
장문이지만 여러번 반복적으로 읽어서 꼭 내 것으로 체화하자!
공식 문서에서 제공하는 컨벤션
flutter에서는 세가지의 네이밍규칙을 사용한다.
린터 규칙: camel_case_types,camel_case_extensions
Classes, enum types, typedefs, type parameters는 UpperCamelCase를 사용하여 표시한다.
또한, extension도 UpperCamelCase를 사용한다.
GOOD:
class SliderMenu { ... }
class HttpRequest { ... }
typedef Predicate<T> = bool Function(T value);
메타데이터 주석을 위한 클래스도 해당한다.
class Foo {
const Foo([Object? arg]);
}
(anArg)
class A { ... }
()
class B { ... }
생성자가 매개변수를 사용하지 않는 경우, lowerCamelCase를 사용할 수 있다.
근데 솔직히.. 그렇게 따지고 쓰지는 않을듯ㅋㅋ 소스가 어케 수정될지도 모르는데...
걍 UpperCamelCase 써라. 맘편하게..
const foo = Foo();
class C { ... }
type처럼 extension도 UpperCamelCase를 사용한다.
extension MyFancyList<T> on List<T> { ... }
extension SmartIterable<T> on Iterable<T> { ... }
린터 규칙: non_constant_identifier_names, Constant_identifier_names
클래스 멤버, 최상위 정의, 변수, 매개변수 및 명명된 매개변수(positional, named), 상수(const)를 lowerCamelCase로 표현한다.
positional : 고정된 위치로 전달
named : 파라메터 이름으로 값 전달void align(bool clearItems,{int count}){ //여기서 clearItems는 positional, count 는 named이다. }```
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();
}
린터 규칙: file_names , package_names, library_prefixes
packages, directories, source files는 lowercase_with_underscores를 사용한다.
GOOD:
my_package
└─ lib
└─ file_system.dart
└─ slider_menu.dart
BAD:
mypackage
└─ lib
└─ file-system.dart
└─ SliderMenu.dart
또한, import prefixes 도 lowercase_with_underscores를 사용한다.
GOOD:
import 'dart:math' as math;
import 'package:angular_components/angular_components.dart' as angular_components;
import 'package:js/js.dart' as js;
BAD:
import 'dart:math' as Math;
import 'package:angular_components/angular_components.dart' as angularComponents;
import 'package:js/js.dart' as JS;
GOOD:
class HttpConnection {}
class DBIOPort {}
class TVVcr {}
class MrRogers {}
var httpRequest = ...
var uiHandler = ...
var userId = ...
Id id;
BAD:
class HTTPConnection {}
class DbIoPort {}
class TvVcr {}
class MRRogers {}
var hTTPRequest = ...
var uIHandler = ...
var userID = ...
ID iD;
DONT! private가 아니면, _를 사용하지 말 것!
선언에서 확인할 수 있고, 생각보다 직관성에 크게 도움이 안된다.
GOOD:
defaultTimeout
BAD:
kDefaultTimeout
library 명시 가능하지만 레거시임.
GOOD:
/// A really great test library.
('browser')
library;
BAD:
library my_library;
린터 규칙: directives_ordering
파일의 시작 부분을 깔끔하게 유지하기 위해서 import/export 정렬 순서와 섹션이 규정되어 있다.
dart: 로 시작하는 패키지 제일 앞에 배치
import 'dart:async';
import 'dart:html';
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';
pakcage: 로 시작하는 패키지 상대 경로 패키지 앞에 배치
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';
import 'util.dart';
import는 import 끼리, export는 export 끼리
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';
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';
dart는 공백을 무시한다. 하지만 인간에게 공백 한 줄로 직관성이 증가한다.
dart에서 제공하는 자동 구조잡기 기능을 적극적으로 사용하여 formatting한다. 인간따리의 formatting을 사용하며 나대지 말고 dart가 제공하는 dartfmt 사용해서 협업해라.
긴 식별자, 깊이 중첩된 표현식, 다양한 종류의 연산자 혼합 등이 있는 경우,여전히 읽기 어려울 수 있다.
아래의 작업으로 Formmater가 더 강력하게 동작할 수 있도록 도와보자.
린터 규칙: line_longer_than_80_chars
가독성 연구에 따르면 긴 텍스트 줄은 다음 줄의 시작 부분으로 이동할 때 눈이 더 멀리 이동해야 하기 때문에 읽기가 더 어렵다.
한 줄 코드는 정말 하지 말자! 가독성이 너무 떨어지고 솔직히 꼴갑이다😅
예외
1. URI/파일 경로가 주석이나 문자열(일반적으로 가져오기 또는 내보내기에서)에 나타나면 줄이 80자를 초과하더라도 전체로 유지하는 것이 좋다. 이렇게 하면 소스 파일에서 경로를 더 쉽게 검색할 수 있다.
2. 여러 줄 문자열(Muti-line string)에는 80자보다 긴 줄이 포함될 수 있다. 줄 바꿈은 문자열 내에서 중요하고 줄을 더 짧은 줄로 나누면 프로그램이 변경될 수 있기 때문.
구문이 모호해지는 것을 피할 수 있다.
GOOD:
if (overflowChars != other.overflowChars) {
return overflowChars < other.overflowChars;
}
BAD:
if (overflowChars != other.overflowChars)
return overflowChars < other.overflowChars;
막 소스코드를 짰을 때에는 명확하고 깰꼼하다고 착각하기 쉽다.
미래의 자신과 협업하는 동료를 위해 코멘트를 생활화하자.
처음 보는 사람이 이해하기 쉬울까? 를 생각하며 작성하는 것이 중요하다.
물론 주석은 outdate 되기 쉽다. 그래도 코드 10줄 분석하는 것보다 잘 쓴 주석 한 줄이 도움이 되는 것도 사실이다.
멋진 코드는 남들이 이해하기 쉬운 코드다! 가독성을 높히기 위해 고민해보자!
문서에 포함되지 않는 주석
대문자 시작. 마침표사용.
근데 난 한국인인걸? 알아볼 수 있게만 쓰자고~
GOOD:
// Not if anything comes before it.
if (_chunks.isNotEmpty) return false;
블록 주석은 임시 주석에 사용하도록 한다.
GOOD:
void greet(String name) {
// Assume we have a valid name.
print('Hi, $name!');
}
BAD:
void greet(String name) {
/* Assume we have a valid name. */
print('Hi, $name!');
}
/// 로 시작되는 주석은 doc comments 라고 한다. dart doc 으로 문서 페이지를 만들 수 있다는 장점이 있다.
근데 안되는데?
또한, 마우스 호버로 주석을 확인 할 수 있다.
린터 규칙: slash_for_doc_comments
GOOD:
/// The number of characters in this chunk when unsplit.
int get length => ...
BAD:
// The number of characters in this chunk when unsplit.
int get length => ...
dart doc 은 C# 스타일의 문서주석(///) 과 JavaScript 스타일의 문서주석(/** ... */)를 지원한다. C# 스타일의 문서주석(///)이 더 읽기 쉬우므로 JavaScript 스타일의 문서주석(/** ... */)은.. 정리하자.
린터 규칙: package_api_docs , public_member_api_docs
라이브러리에는 문서 주석만 사용
GOOD:
/// A Foo.
abstract class Foo {
/// Start foo-ing.
void start() => _start();
_start();
}
BAD:
class Bar {
void bar();
}
override 된 메소드는 재 주석할 필요없다.
/// Base of all things.
abstract class Base {
/// Initialize the base.
void init();
}
/// A sub base.
class Sub extends Base {
void init() { ... }
}
Class 가 유일한 프로그램 구성인 Java와 다르게 library 는 유저가 직접적으로 import하고 작업할 수 있는 entity 이다. library의 주요 컨셉과 제공되는 기능을 사용자(개발자)에게 소개하기 위해 다음을 고려해 보자:
공개 API 에만 문서 주석쓰지 말고~ 비공개 API에도 문서 주석을 제공하자.
사용자(개발자) 중심의 설명을 한 줄 요약으로 제공하자.
마침표로 끝나면 더욱 좋다.
GOOD:
/// Deletes the file at [path] from the file system.
void delete(String path) {
...
}
BAD:
/// Depending on the state of the file system and the user's permissions,
/// certain operations may or may not be possible. If there is no file at
/// [path] or it can't be accessed, this function throws either [IOError]
/// or [PermissionError], respectively. Otherwise, this deletes the file.
void delete(String path) {
...
}
GOOD:
/// Deletes the file at [path].
///
/// Throws an [IOError] if the file could not be found. Throws a
/// [PermissionError] if the file is present but could not be deleted.
void delete(String path) {
...
}
BAD:
/// Deletes the file at [path]. Throws an [IOError] if the file could not
/// be found. Throws a [PermissionError] if the file is present but could
/// not be deleted.
void delete(String path) {
...
}
가장 가깝게 래핑된 함수/변수의 의미에 집중하여 작성한다.
GOOD:
class RadioButtonWidget extends Widget {
/// [lines] 에 현재 글꼴을 적용합니다.
void tooltip(List<String> lines) {
...
}
}
BAD:
class RadioButtonWidget extends Widget {
/// 라디오 버튼 웨젯의 설명을 [lines]에 적용된 문자열로 적용합니다.
void tooltip(List<String> lines) {
...
}
}
근데 우리는 한국인이니까... 뭐.. 알아서 하세용
이거는 한국인라도 지켜줘~
한국인이면.. '...여부'나 '...한지?' 로 끝나면 좋을듯?
/// 유저에게 모달이 보여지고 있는지 or 유저에게 모달 표출 여부
bool isVisible;
둘 중 하나만 주석 작성하세요~
GOOD:
/// The pH level of the water in the pool.
///
/// Ranges from 0-14, representing acidic to basic, with 7 being neutral.
int get phLevel => ...
set phLevel(int level) => ...
BAD:
/// The depth of the water in the pool, in meters.
int get waterDepth => ...
/// Updates the water depth to a total of [meters] in height.
set waterDepth(int meters) => ...
클래스에 대한 주석은 정말 정말 중요함!
/// A chunk of non-breaking output text terminated by a hard or soft newline.
///
/// ...
class Chunk { ... }
인간은 예제를 통해 일반화하는 데 능숙하므로 단일 코드 샘플이라도 API를 더 쉽게 배울 수 있다.
/// Returns the lesser of two numbers.
///
/// ```dart
/// min(5, 3) == 3
/// ```
num min(num a, num b) => ...
[] 로 묶으면 이름과 링크를 찾아 표시한다. 파라미터에 적용가능하다.
/// 정산서 다운로드(zip)
///
/// [dateList] : 월별 리스트 (ex. ['2023-08', '2023-09', '2023-12'])
/// [downloadPath] : os에 따라 저장경로 다름
/// [fileName] : 거래명세서(yyyymmdd_hh24mi).zip
Future<bool> downloadAccountsSettlement(
List<String> dateList, String downloadPath, fileName) async {
....
}
특정 클래스의 멤버에 연결하려면 클래스 이름과 멤버 이름을 점으로 구분하여 사용하세요.
/// Similar to [Duration.inDays], but handles fractional days.
점 구문은 명명된 생성자를 참조하는 데에도 사용할 수 있습니다. 명명되지 않은 생성자의 경우 .new클래스 이름 뒤에 사용하십시오.
/// To create a point, call [Point.new] or use [Point.polar] to ...
메소드 설명에 통합하고 대괄호를 사용하여 매개변수를 강조하라.
GOOD:
/// Defines a flag.
///
/// Throws an [ArgumentError] if there is already an option named [name] or
/// there is already an option using abbreviation [abbr]. Returns the new flag.
Flag addFlag(String name, String abbr) => ...
BAD:
/// Defines a flag with the given name and abbreviation.
///
/// @param name The name of the flag.
/// @param abbr The abbreviation for the flag.
/// @returns The new flag.
/// @throws ArgumentError If there is already an option with
/// the given name or abbreviation.
Flag addFlag(String name, String abbr) => ...
GOOD:
/// A button that can be flipped on and off.
(selector: 'toggle')
class ToggleComponent {}
BAD:
(selector: 'toggle')
/// A button that can be flipped on and off.
class ToggleComponent {}
문서 주석에는 마크다운 형식을 사용할 수 있다.
테이블 같은 경우에는 괜찮을 수도..?
Markdown 에서 ` 를 세번 쓰고 뒤에 언어를 쓰면 코드블록 작성이 가능하다
₩ 를 한영 변경하면 사용가능함~
GOOD:
/// You can use [CodeBlockExample] like this:
///
/// ```dart
/// var example = CodeBlockExample();
/// print(example.isItGreat); // "Yes."
/// ```
BAD:
/// You can use [CodeBlockExample] like this:
///
/// var example = CodeBlockExample();
/// print(example.isItGreat); // "Yes."
동료와 미래의 나를 위해 잘 정리된 글을 쓰도록 노력하자
컴퓨터 언어 인 이유가 있다! 소스 코드도 문맥이고, 이 문맥 파악을 돕기위해 주석을 생활화하자!
간결하고 명확한 주석. 아름다운 주석.
대중적이지 않은 약어는 사용을 지양하자. '대중적'이라는 것도 사실 꽤나 주관적이다. 이런 부분은 동료들과 함께 규칙을 정하는 것도 좋겠다.
이거는... 한국인이면 그다지 상관없음. ㅋㅋ