Move 모범 사례
이 가이드는 Sui에서 Move 스마트 컨트랙트를 작성하기 위한 권장되는 규칙과 모범 사례를 설명한다. 이러한 지침을 따르면 생태계 표준에 부합하며 유지보수 가능하고, 안전하고, 조합 가능한 코드를 작성하는 데 도움이 된다.
이러한 규칙은 엄격한 규칙이라기보다는 권장 사항이지만, 많은 Sui 프로젝트에서 효과가 입증된 패턴을 나타낸다. 이는 생태계 전반의 일관성을 생성하고 코드를 더 쉽게 이해하고 유지보수할 수 있도록 돕는다.
Organization principles
Package
Sui 패키지는 다음으로 구성된다:
- 블록체인에 업로드될 Move 코드를 포함하는
sources디렉토리 - 패키지의 의존성과 기타 정보를 선언하는
Move.toml매니페스트 파일 - Sui Move 툴체인이 자동으로 생성하는
Move.lock파일로, 의존성 버전을 고정하고 서로 다른 네트워크에 존재하는 패키지의 게시 및 업그레이드 버전을 추적한다.
이러한 이유로 Move.lock 파일은 항상 패키지에 포함되어야 한다 (.gitignore 파일에 추가하지 말아야 한다). 매니페스트 파일의 이전 published-at 필드 대신 자동 주소 관리를 사용한다.
선택적으로, 패키지 테스트를 포함하는 tests 디렉터리와 패키지 사용 사례를 제공하는 examples 디렉터리를 추가할 수 있다. 두 디렉터리 모두 패키지를 게시할 때 온체인에 업로드되지 않는다.
sources/
my_module.move
another_module.move
...
tests/
my_module_tests.move
...
examples/
using_my_module.move
Move.lock
Move.toml
패키지 매니페스트에서 패키지 이름은 PascalCase여야 한다: name = "MyPackage". 이상적으로, 패키지를 나타내는 명명된 address는 패키지 이름과 동일하되, snake_case를 사용해야 한다: my_package = 0x0.
Modules
모듈은 Move 코드의 주요 구성 요소이다. 모듈은 관련된 기능을 조직화하고 캡슐화하는 데 사용된다. 하나의 object 또는 데이터 구조체를 중심으로 모듈을 설계한다. 변형 구조체는 복잡성과 버그를 피하기 위해 자체 모듈을 가져야 한다.
모듈 선언은 더 이상 중괄호가 필요하지 않으며 컴파일러가 널리 사용되는 모듈에 대한 기본 use 문을 제공하므로 모든 모듈을 선언할 필요가 없다.
module conventions::wallet;
public struct Wallet has key, store {
id: UID,
amount: u64
}
Body
주석을 사용하여 Move 코드 파일의 섹션을 만 들어 코드를 구조화한다. 제목 양쪽에 ===를 사용하여 제목을 구조화한다.
module conventions::comments;
// === Imports ===
// === Errors ===
// === Constants ===
// === Structs ===
// === Events ===
// === Method Aliases ===
// === Public Functions ===
// === View Functions ===
// === Admin Functions ===
// === Package Functions ===
// === Private Functions ===
// === Test Functions ===
여기서 public 함수는 상태를 수정하는 함수이고 view 함수는 종종 온체인 게터 또는 오프체인 헬퍼이다. 후자는 object를 쿼리하여 데이터를 읽을 수 있으므로 필요하지 않다. init 함수는 모듈에 존재하는 경우 모듈의 첫 번째 함수여야 한다.
가독성을 높이기 위해 함수는 목적과 사용자 흐름에 따라 정렬하는 것이 좋다. 또한 admin_set_fees와 같이 명시적인 함수 이름을 사용하여 함수가 수행하는 작업을 명확하게 할 수 있다.
이상적으로, 테스트 함수는 tests 디렉터리에 있는 실제 테스트를 위한 [test_only] 헬퍼로만 구성되어야 한다.
종속성별로 import를 그룹화한다, 예를 들면:
use std::string::String;
use sui::{
coin::Coin,
balance,
table::Table
};
use my_dep::battle::{Battle, Score};
Naming conventions
코드에서 명명 규칙을 준수하면 가독성이 높아지고 궁극적으로 코드베이스를 더 쉽게 유지보수할 수 있다. 다음 섹션에서는 Move 코드를 작성할 때 따라야 할 주요 명명 규칙을 간략하게 설명한다.
Constants
상수는 대문자여야 하며 snake case 형식을 사용해야 한다. 오류는 PascalCase를 사용하고 E로 시작하는 특정 상수이다. 의미가 명확하게 드러나도록 작성한다.
module conventions::constants;
// 올바른 오류 아닌 상수
const MAX_NAME_LENGTH: u64 = 64;
// 올바른 오류 상수
const EInvalidName: u64 = 0;
// 잘못된 오류 상수
const E_INVALID_NAME: u64 = 0;
Structs
항상 다음 순서로 구조체 abilities를 선언한다: key, copy, drop, store.
구조체 이름에 'potato'를 사용하지 않는다. Abilities가 없는 것은 potato 패턴으로 정의된다.
구조체는 간단한 wrapper, dynamic field key 또는 튜플로 사용할 수 있는 위치 필드를 지원한다.
이벤트를 발생시키는 구조체 이름에는 Event 접미사를 사용한다.
module conventions::request;
// dynamic field keys
public struct ReceiptKey(ID) has copy, drop, store;
// dynamic field
public struct Receipt<Data> has key, store {
id: UID,
data: Data
}
// 올바른 이름 지정
public struct Request();
// 잘못된 이름 지정
public struct RequestPotato {}
CRUD function names
다음 함수는 표준 CRUD (생성, 읽기, 업데이트, 삭제) 명명 규칙을 따른다:
new: 빈 object를 생성한다.empty: 빈 구조체를 생성한다.create: 초기화된 object 또는 구조체를 생성한다.add: 값을 추가한다.remove: 값을 제거한다.exists: 키가 존재하는지 확인한다.contains: 컬렉션에 값이 포함되어 있는지 확인한다.borrow: 구조체 또는 object의 불변 참조를 반환한다.borrow_mut: 구조체 또는 object의 가변 참조를 반환한다.property_name: 필드의 불변 참조 또는 복사본을 반환한다.property_name_mut: 필드의 가변 참조를 반환한다.drop: 구조체를 드롭한다.destroy: drop ability가 있는 값을 가진 object 또는 데이터 구조체를 파괴한다.destroy_empty: drop ability가 있는 값을 가 진 빈 object 또는 데이터 구조체를 파괴한다to_name: 타입 X를 타입 Y로 변환한다.from_name: 타입 Y를 타입 X로 변환한다.
Generics
단일 문자 이름 또는 전체 이름을 사용하여 제네릭을 선언한다. 관례적으로 개발자는 제네릭 타입에 T와 U를 사용하지만, 다른 타입과 혼동되지 않는다면 더 서술적인 이름을 사용할 수 있다. 항상 가독성을 우선시한다.
module conventions::generics;
// 단일 문자 이름
public struct Receipt<T> has store { ... }
// 전체 이름
public struct Receipt<Data> has store { ... }