코딩 표준
이 컨벤션들은 코드 리뷰·CI lint·(가능한 경우) 자동 체크로 강제됩니다. 첫 PR 전에 읽어 두세요 — 나중에 수정하면 사이클을 낭비합 니다.
대상 독자
모든 컨트리뷰터. 코드 블록을 건드리는 chore·docs PR을 포함해 모든 PR에 적용됩니다.
TypeScript — strict 모드, any 금지
apps/frontend/tsconfig.json은 "strict": true와 "noImplicitAny": true로 동작합니다. 구체적으로:
any캐스팅 금지.any에 손이 간다면 보통 type guard 또는 generic이 빠진 것입니다.unknown을 사용하고 의도적으로 좁히세요.- non-null assertion(
!) 금지 — 해당 시점에 값이 증명적으로 non-null이고 그 이유를 주석으로 설명하지 않는 한. - enum보다 discriminated union. Enum은 transitive import를 누설합니다. 리터럴 union(
type Status = "pending" | "running" | "succeeded")은 tree-shake됩니다.
좁히기의 정당한 예:
// `data`는 서버에서 JSON으로 오므로 사용 전에 런타임 검증.
function isProject(value: unknown): value is Project {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
typeof (value as { id: unknown }).id === "string"
);
}
any가 정말 불가피한 경우(예: 타입이 없는 third-party 콜백과의 interop), 타입 경계 함수로 감싸고 한 줄 정당화와 함께 // eslint-disable-next-line @typescript-eslint/no-explicit-any를 붙이세요.
Pydantic v2 — BaseModel + Field(...)
백엔드는 Pydantic v2입니다. 스키마는 apps/backend/schemas/에 둡니다.
- 항상 타입 선언. 필수 필드는
Field(...), 기본값은Field(default=...)또는Field(default_factory=...). - 교차 필드 불변식은
model_validator.__init__에서 raise하지 마세요. validator가 적절한 시점에 동작하며 구조화된 오류를 만듭니다. arbitrary_types_allowed회피. 검증을 우회합니다. 대신 third-party 타입을 커스텀 validator로 감싸세요.
from pydantic import BaseModel, Field, model_validator
class ProjectCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
repository_url: str = Field(pattern=r"^(https?|ssh)://")
visibility: Literal["team-only", "org-wide"] = "team-only"
@model_validator(mode="after")
def visibility_requires_team_admin(self) -> "ProjectCreate":
# 교차 필드 체크. 위반 시 ValueError raise.
return self
Alembic — forward-only
마이그레이션 정책은 forward-only