flutter 코드 컨벤션 한방 정리

Clean Code Big Poo·2023년 9월 18일
3

Flutter

목록 보기
35/38
post-thumbnail

Overview

flutter에서 제공하는 코드 컨벤션을 정리해보자!
장문이지만 여러번 반복적으로 읽어서 꼭 내 것으로 체화하자!

공식 문서에서 제공하는 컨벤션

코드 컨벤션

  • 읽고 관리하기 쉬운 코드를 작성하기 위한 일종의 코딩 스타일 규약이다.
  • 유연한 문법 구조를 가진 언어일수록 통일된 규약이 없다면 코드의 의도를 파악하거나 오류를 찾기 어렵다. 이는 유지보수 비용 상승으로 직결된다.
  • 고로, 코드의 가독성을 높이고 작성한 코드를 효율적으로 유지보수하기 위해서는 공통의 규칙 을 꼭 작성하자!

왜? Dart는 컨벤션을 제공할까?

  • 일관성을 유지할수 있다.
    -> 어느 것이 더 나은 것인가? 이것은 주관적이다.
  • 간략하게 코딩이 가능
    -> C, Java, JavaScript의 장점을 흡수하여 간략한 코드 작성이 가능하다.

Style

Identifiers(식별자)

flutter에서는 세가지의 네이밍규칙을 사용한다.

  • UpperCamelCase : 첫글자가 대문자인 카멜체.
  • lowerCamelCase : 첫글자가 소문자인 카멜체, 단어가 약어여도 첫글자는 소문자여야 함.
  • lowercase_with_underscores : 소문자와 언더스코프로 구성됨.

UpperCamelCase

린터 규칙: 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> { ... }

lowerCamelCase

린터 규칙: 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();
}

lowercase_with_underscores

린터 규칙: 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;
  • IO는 두 글자이지만, 대문자 사용

매개변수 _

  • 사용하지 않는 콜백 매개변수에는 _, __ 등을사용하는 것이 좋다.

private _

  • private 변수에만 prefix 로 _를 사용

    DONT! private가 아니면, _를 사용하지 말 것!

속성은 이름으로 사용하지마

선언에서 확인할 수 있고, 생각보다 직관성에 크게 도움이 안된다.

GOOD:
defaultTimeout

BAD:
kDefaultTimeout

library 명시 금지

library 명시 가능하지만 레거시임.

GOOD:
/// A really great test library.
('browser')
library;

BAD:
library my_library;

Ordering(정렬)

린터 규칙: directives_ordering
파일의 시작 부분을 깔끔하게 유지하기 위해서 import/export 정렬 순서와 섹션이 규정되어 있다.

"dart:" 은 제일 앞

dart: 로 시작하는 패키지 제일 앞에 배치

import 'dart:async';
import 'dart:html';

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

"pakcage:"는 Relative의 앞

pakcage: 로 시작하는 패키지 상대 경로 패키지 앞에 배치

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

import 'util.dart';

import 후 export

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';

Formatting(코드 구조)

dart는 공백을 무시한다. 하지만 인간에게 공백 한 줄로 직관성이 증가한다.

dartfmt

dart에서 제공하는 자동 구조잡기 기능을 적극적으로 사용하여 formatting한다. 인간따리의 formatting을 사용하며 나대지 말고 dart가 제공하는 dartfmt 사용해서 협업해라.

Formatter 친화적인 코드작성하기

긴 식별자, 깊이 중첩된 표현식, 다양한 종류의 연산자 혼합 등이 있는 경우,여전히 읽기 어려울 수 있다.
아래의 작업으로 Formmater가 더 강력하게 동작할 수 있도록 도와보자.

  • 코드 재구성
  • 코드 단순화
  • 변수이름 줄이기

80자 이상? 넥슬라이스

린터 규칙: line_longer_than_80_chars

가독성 연구에 따르면 긴 텍스트 줄은 다음 줄의 시작 부분으로 이동할 때 눈이 더 멀리 이동해야 하기 때문에 읽기가 더 어렵다.
한 줄 코드는 정말 하지 말자! 가독성이 너무 떨어지고 솔직히 꼴갑이다😅

예외
1. URI/파일 경로가 주석이나 문자열(일반적으로 가져오기 또는 내보내기에서)에 나타나면 줄이 80자를 초과하더라도 전체로 유지하는 것이 좋다. 이렇게 하면 소스 파일에서 경로를 더 쉽게 검색할 수 있다.
2. 여러 줄 문자열(Muti-line string)에는 80자보다 긴 줄이 포함될 수 있다. 줄 바꿈은 문자열 내에서 중요하고 줄을 더 짧은 줄로 나누면 프로그램이 변경될 수 있기 때문.

모든 if 문에 중괄호를 사용

구문이 모호해지는 것을 피할 수 있다.

GOOD:
if (overflowChars != other.overflowChars) {
  return overflowChars < other.overflowChars;
}

BAD:
if (overflowChars != other.overflowChars)
  return overflowChars < other.overflowChars;

Documentation

막 소스코드를 짰을 때에는 명확하고 깰꼼하다고 착각하기 쉽다.
미래의 자신과 협업하는 동료를 위해 코멘트를 생활화하자.
처음 보는 사람이 이해하기 쉬울까? 를 생각하며 작성하는 것이 중요하다.
물론 주석은 outdate 되기 쉽다. 그래도 코드 10줄 분석하는 것보다 잘 쓴 주석 한 줄이 도움이 되는 것도 사실이다.
멋진 코드는 남들이 이해하기 쉬운 코드다! 가독성을 높히기 위해 고민해보자!

Comments(주석)

문서에 포함되지 않는 주석
대문자 시작. 마침표사용.
근데 난 한국인인걸? 알아볼 수 있게만 쓰자고~

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(문서 주석)

/// 로 시작되는 주석은 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 스타일의 문서주석(/** ... */)은.. 정리하자.

공개 API 에 문서 주석을 사용하자

린터 규칙: 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의 주요 컨셉과 제공되는 기능을 사용자(개발자)에게 소개하기 위해 다음을 고려해 보자:

  • library의 주요 사용처를 한 문장으로 소개하자.
  • 주요 용어를 설명하자.
  • API 사용을 안내하는 샘플 코드를 제공하자.
  • 가장 중요하거나 가장 일반적으로 사용되는 함수/클래스에 대한 연결고리를 제공하자.
  • library의 외부 참조의 연결고리를 제공하자.

비공개 API에 문서 주석을 사용하자

공개 API 에만 문서 주석쓰지 말고~ 비공개 API에도 문서 주석을 제공하자.

문서 주석 첫줄은 summary 한 문장으로

사용자(개발자) 중심의 설명을 한 줄 요약으로 제공하자.
마침표로 끝나면 더욱 좋다.

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) {
    ...
  }
}

함수/메서드 주석은 3인칭 동사로 시작하자

근데 우리는 한국인이니까... 뭐.. 알아서 하세용

변수 주석은 명사로 시작하자(except bool)

이거는 한국인라도 지켜줘~

bool 변수 주석은 Whether로 시작하자

한국인이면.. '...여부'나 '...한지?' 로 끝나면 좋을듯?

/// 유저에게 모달이 보여지고 있는지 or 유저에게 모달 표출 여부
bool isVisible;

getter, setter 둘 다 주석 작성하지마시오(택 1)

둘 중 하나만 주석 작성하세요~

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

문서 주석에는 마크다운 형식을 사용할 수 있다.

과도하게 사용하지 말기

Formatting(코드 구조)에 HTML사용 금지

테이블 같은 경우에는 괜찮을 수도..?

` (백틱) 을 사용해 주석에 코드블록 작성가능

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."

Writing

주석도 글쓰기 능력이 필요함

동료와 미래의 나를 위해 잘 정리된 글을 쓰도록 노력하자
컴퓨터 언어 인 이유가 있다! 소스 코드도 문맥이고, 이 문맥 파악을 돕기위해 주석을 생활화하자!

간결! 명확!

간결하고 명확한 주석. 아름다운 주석.

명확 하지 않은 약어는 사용을 지양

대중적이지 않은 약어는 사용을 지양하자. '대중적'이라는 것도 사실 꽤나 주관적이다. 이런 부분은 동료들과 함께 규칙을 정하는 것도 좋겠다.

the 보다 this

이거는... 한국인이면 그다지 상관없음. ㅋㅋ

Usage

Libraries

Null

Strings

Collections

Functions

Variables

Members

Constructors

Error handling

Asynchrony

Design

Names

Libraries

Classes and mixins

Constructors

Members

Types

Parameters

Equality

참고

0개의 댓글