[Coding Standard] Figma 리뷰 댓글: 에이전트는 어디까지 하는가
블로그를 리뉴얼하면서 화면을 Figma에 그리고 있다. 내가 자는 동안 에이전트가 화면을 만들고, 아침에 내가 Figma를 열어 댓글을 단다. 그러면 에이전트가 그걸 읽고 고친다.
블로그를 리뉴얼하면서 화면을 Figma에 그리고 있다. 내가 자는 동안 에이전트가 화면을 만들고, 아침에 내가 Figma를 열어 댓글을 단다. 그러면 에이전트가 그걸 읽고 고친다.
디자인 도구에서 코드로 넘어올 때 같은 것에 이름이 두 번 지어진다.
실패 화면 표준이 예상 못 한 예외를 error.js로 잡기로 했다. 화면은 지켜진다. 그런데 터졌다는 사실은 아무 데도 안 간다.
후기 목록이 이렇게 생겼다.
화면 크기마다 다르게 보여야 할 때 방법이 셋 있다.
Next.js 앱이 둘 돌고 있다. 공개 사이트(client)와 관리자(admin)다. 둘이 무엇을 나눠 갖고 무엇을 따로 가질 것인가.
프론트엔드가 서버를 부르는 자리는 셋으로 갈릴 수 있다.
폼이 21개인데 폼 규칙이 하나도 없다. 그래서 이렇게 되어 있다.
실패에는 두 종류가 있고, 지금 한쪽만 다루고 있다.
컴포넌트를 새 자리로 옮기기로 정했다. 그런데 그 CSS 는 따라오지 않는다.
Header는 components/ 루트에 있고 Footer는 components/sections/ 안에 있다. 같은 레이아웃 조각인데 사는 곳이 다르다.
홈은 조각 여덟 개로 쪼개져 있고, about 은 91줄을 한 파일에 다 쓴다.
디자인 시스템에 Dialog 가 있는데 CallConfirmationDialog 를 직접 만들었다. Badge 가 있는데 InquiryStatusBadge 를 직접 만들었다.
에이전트를 여럿 굴릴 때, 사용자는 그중 누구와 말하는가.
지금까지 표준은 전부 무엇을 만드는가에 대한 것이었다. 패키지를 어떻게 나누고, DTO를 어떻게 만들고, 계약을 어떻게 적는지. 그런데 그 표준들을 어떤 태도로 실행하는가는 어디에도 없었다.
테스트가 왜 힘든지를 진단하면서 통증을 넷으로 적었다.
패키지 구조 표준은 도메인 안을 api·application·domain·infra 네 겹으로 나눴다. 컨트롤러 패키지 분리 표준이 거기에 web을 형제로 붙였다. 그 겹 안을 더 나눌지는 어느 쪽도 정하지 않았다.
계약 커버리지 게이트 표준을 만들면서 유예 여덟 건을 남겼다. 계약은 적어뒀는데 테스트를 못 짜는 칸들이었고, 이유는 이랬다.
API 테스트 계약표 표준이 계약을 저장소의 enum이 소유하게 만들었다. @Contract 애노테이션 덕에 없는 계약을 가리키는 테스트는 컴파일이 막고, TestExecutionListener가 계약과 테스트 실행 결과를 맞춰 색을 칠한다.
API 테스트 계약표 표준 원칙 7이 색을 넷으로 늘리면서 이렇게 적었다.
AI가 테스트를 빨리 짜준다. 그런데 통과를 봐도 안심이 안 된다.
패키지 구조 표준과 컨트롤러 패키지 분리 표준이 컨트롤러가 사는 자리(api/web)를 정했다. 그런데 그 안의 메서드가 어떤 URL을 받을지는 한 번도 정한 적이 없다.
예외의 종류 원칙 9가 ErrorCode를 업무 도메인까지 세분화하기로 하면서, 마지막 문단에 대가를 이렇게 적어뒀다.
바로 앞 글인 URL 라우팅 네이밍 표준에 architectures: [ddd, layered]를 달았다. 경로 규칙이니 계층 구조와 무관하다고 봤고, 그건 맞다.
표준 동기화 표준에서 배포 스크립트에 검사 넷을 붙였다. 밀어내기 직전에 서서, 어긋난 것이 나가지 못하게 막는 문이었다.
페이징 방식 표준이 나가는 쪽을 정했다. PagingResult와 CursorResult, 커서의 모양, hasNext 판별까지.
에이전트 여럿에게 일을 시킬 때, 기능 하나에 이슈를 몇 개 만드는가.
이 저장소는 규칙을 여러 벌로 복사해서 쓴다. AI 도구마다 읽는 파일 이름이 정해져 있어서다.
에이전트에게 일을 시키면 어디까지 하고 멈춰야 하는가.
지금까지 정한 표준은 전부 근거를 적을 수 있는 것이었다. 애그리거트 경계는 트랜잭션으로 갈랐고, 페이징은 임의 페이지 점프로 갈랐다. 버린 안마다 “언제 무엇이 깨지는가”를 적었다.
Entity에 필드를 하나 추가한다고 하자.
목록 조회를 어떻게 반환할지는 이미 정해져 있었다. 계층별 DTO 네이밍 표준이 PagingResult<T>를 만들었고, Repository부터 api까지 그 타입 하나로 올라간다.
패키지 구조 표준은 도메인 안을 네 겹으로 나누고 들어오는 칸을 api라고 불렀다. 그때는 컨트롤러가 한 종류라고 전제하고 있었다.
예외 처리 표준의 HttpApiResponse에는 success(int status, T data)가 있었다. 하지만 언제 200, 201, 202를 넘길지는 정하지 않았다. 더 큰 문제는 본문의 status만 바뀌고 실제 HTTP 응답은 여전히 200일 수 있다는 점이었다.
애플리케이션 시간 표준에서 실제 사건의 시점은 UTC Instant로 표현한다고 정했다. 하지만 Java의 Instant를 그대로 필드에 썼다고 DB 왕복까지 같은 시점이 되는 것은 아니다. MariaDB 컬럼 타입, 연결 세션 시간대, 저장 정밀도 중 하나만 달라도 저장 전과 조...
계층별 검증 표준은 Request의 선택 필드에는 @Nullable, Command·Query·Domain의 nullable 필드에는 orNull을 쓰기로 했다. 그런데 요청이 반대 방향으로 돌아올 때 만나는 Snapshot과 Response는 비어 있었다.
마감 시각 같은 정책은 Domain이 판단해야 한다. 그런데 Domain 안에서 LocalDateTime.now()를 부르면 테스트가 실행 시각에 흔들리고, 같은 요청 안에서도 판단 시각과 기록 시각이 달라진다. 그렇다고 생일과 영업일까지 전부 UTC 시점으로 바꾸면 원래 없던 시...
관리자 주문 목록 화면을 만든다고 하자. 한 행에 이렇게 나온다.
애그리거트 경계 표준은 조회 쪽을 정면으로 다뤘다. “Domain은 POJO라 지연 로딩이 없다. 그래서 애그리거트 크기가 곧 매 조회의 고정 비용이다.”
Repository 설계 표준에서 대량 연산을 Repository 밖으로 빼면서 이렇게 정했다.
주문을 만드는 Controller는 이렇게 생겼다.
지금까지 XxxRepository에 정의한 것은 둘뿐이다.
패키지 구조 표준은 infra를 전부 package-private으로 막아서 “Entity는 패키지 밖으로 나가지 않는다”를 약속이 아니라 사실로 만들었다. domain에 인터페이스를 두고 구현체를 infra에 감춘 덕분이다.
지금까지 정한 표준들은 Repository의 자리를 정했다. 애그리거트 경계 표준은 “Repository는 애그리거트 루트당 하나”라고 했고, 패키지 구조 표준은 인터페이스는 domain에 public으로, 구현체는 infra에 package-private으로 두라고 했다. DTO...
API 문서가 코드와 어긋나는 순간, 문서는 없느니만 못한 것이 된다. 클라이언트 개발자는 문서를 믿고 구현했는데 실제 서버는 다르게 동작하기 때문이다. 그리고 이 어긋남은 대개 같은 정보를 두 군데에 적어놓고 한 쪽만 고쳤을 때 생긴다.
Service 메서드를 처음 여는 사람은 두 가지를 알고 싶어 한다. 이 메서드가 무엇을 전제하고 동작하는지, 그리고 어디까지 왔을 때 무엇이 보장되는지. 보통은 주석이 그 역할을 한다.
@Valid 하나 붙이면 검증이 끝난다고 생각하기 쉽다. 그런데 실제로 코드를 열어보면 검증이 여러 군데에 흩어져 있다. CreateOrderRequest의 @NotBlank, OrderDomain 생성자의 Objects.requireNonNull, OrderEntity의 @Col...
지금까지 세운 표준들을 다시 읽어보면 같은 문장이 반복해서 나온다.
DTO 하나를 Controller부터 JPA Entity까지 그대로 끌고 다니는 구조는 편하다. 필드도 한 번만 정의하면 되고, 변환 코드도 안 짜도 된다. 문제는 편한 만큼 계층 사이의 의존성이 전부 한 클래스에 뭉친다는 것이다. Entity에 컬럼 하나를 추가하면 API 응답이...
예외 처리 표준에서 ErrorCode와 ErrorCodeException으로 실패를 다루는 방법을 정했다. 그런데 그 글이 다룬 것은 우리가 직접 던지는 예외뿐이다.
지금까지 정리한 표준들은 요청이 들어오는 길을 다뤘다. 이 글은 반대 방향, 실패가 나가는 길을 다룬다.
객체 하나가 어떤 상태로 만들어질 수 있는지 알아내려면 코드를 몇 군데나 봐야 할까. setter가 있는 DTO라면 답은 “전부”다. 어디서든 필드 하나를 바꿀 수 있으니, 이 객체의 가능한 상태를 알려면 이 타입을 쓰는 모든 코드를 확인해야 한다.
패키지 구조 표준에서 “패키지가 애그리거트 경계 역할을 한다”고 적었다. 그런데 그 애그리거트를 어디서 끊을지는 정하지 않았다.
“상속은 나쁘다”, “합성이 항상 우월하다”는 말을 자주 들어보셨나요? 하지만 실무에서는 이런 이분법적 사고가 오히려 비효율적일 수 있습니다. 이번 글에서는 상속(Inheritance)과 합성(Composition)을 실무에서 어떻게 선택하고 활용하는지, 실제 경험을 바탕으로 정리...
Java의 assert는 개발자들 사이에서 “있지만 거의 쓰지 않는” 기능으로 취급받습니다. 왜일까요?
이번 글은 “애플리케이션 내부에는 null이 없다” 라는 전제에서 출발한다. 문자열/객체형은 null을 허용하지 않고, null이 가능한 경우에만 변수명에 orNull을 명시한다. 이 전제를 지키면 팀 내부 규칙이 아주 단순해진다.
비지니스 로직이 복잡해 지면서 Service 레이어의 코드가 복잡해지기 시작했습니다. 특히 DTO를 VO로 변환할 때 문제가 두드러졌습니다. 해당 문제는 로직의 가독성을 크게 떨어뜨렸습니다.