원클릭으로
frl-coding-standard
Coding standards for all Java code in fdb-record-layer. Apply when writing or reviewing any Java code.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Coding standards for all Java code in fdb-record-layer. Apply when writing or reviewing any Java code.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Write or update documentation in docs/sphinx/source/. Applies the appropriate style for user-facing vs. architecture docs.
Standards for writing tests in fdb-record-layer. Apply when writing or reviewing test code.
Code review the current branch for adherence to project standards, correctness, and best practices.
Simplifies and refines recently modified code for clarity, consistency, and maintainability while preserving all functionality.
Rebuild working context after a session restart. Reads branch state, recent commits, open PRs, and changed files to produce a "what was I doing" summary.
Specialized skill for working in the fdb-relational-core SQL processing layer — parser, plan generator, and Cascades planner. Use when debugging query plans, writing planner rules, or understanding the SQL execution path.
| name | frl-coding-standard |
| description | Coding standards for all Java code in fdb-record-layer. Apply when writing or reviewing any Java code. |
All Java, Gradle, and property files MUST end with a newline character.
Target Java 17 language level, compiled with JDK 21.
The two layers use different exception systems. Know which layer you are in.
Exceptions extend RecordCoreException, which extends LoggableException (unchecked).
.addLogInfo(LogMessageKeys.KEY, value) to attach context. Include as much as needed
to diagnose the error: subspace, primary key, record type, index name, etc.// Wrong
throw new RecordCoreException("Found corrupt record: " + record.getPrimaryKey());
// Correct
throw new RecordCoreException("Found corrupt record")
.addLogInfo(LogMessageKeys.PRIMARY_KEY, record.getPrimaryKey())
.addLogInfo(LogMessageKeys.RECORD_TYPE, record.getRecordType());
Exceptions are RelationalException (checked, extends Exception). Always pair with an
ErrorCode, which maps to a SQLSTATE-standard 5-digit code.
.addContext("key", value) — this travels with the exception for
logging and is preserved when converting to SQLException.// Minimal form
throw new RelationalException("Transaction rolled back", ErrorCode.SERIALIZATION_FAILURE);
// With context
throw new RelationalException("Unsupported type in column", ErrorCode.CANNOT_CONVERT_TYPE)
.addContext("columnName", colName)
.addContext("typeName", typeName);
At JDBC interface boundaries (where the signature throws SQLException), convert via
.toSqlException(), which produces a ContextualSQLException carrying the ErrorCode
as the SQLState and the context map for logging:
throw new RelationalException("Element is not of STRUCT type", ErrorCode.CANNOT_CONVERT_TYPE)
.toSqlException();
Specialized subclasses (InvalidColumnReferenceException, InvalidTypeException) exist for
common error cases — prefer them when they fit. InternalErrorException is deprecated; use
new RelationalException("...", ErrorCode.INTERNAL_ERROR) directly.
UncheckedRelationalException wraps a RelationalException as a RuntimeException for
contexts where checked exceptions cannot be thrown (e.g., inside lambdas). Unwrap with
.unwrap() to recover the original RelationalException.
Same principle as exceptions — structured, static messages:
KeyValueLogMessage.of("static message", key, value, ...).// Wrong
LOGGER.info("Unable to open file: " + filename);
// Correct
LOGGER.info(KeyValueLogMessage.of("Unable to open file", LogMessageKeys.FILENAME, filename));
The fdb-record-layer-* and lower-level parts of fdb-relational-core are fully asynchronous
via CompletableFuture. The JDBC-facing relational API presents a synchronous surface, but
async code is common in the implementation beneath it.
join() or get() in production code. Use context.asyncToSync() or
database.asyncToSync() instead — they enforce timeouts, record wait timers, and handle
error wrapping.CompletableFuture completion lambda. The
thread pool is finite; blocking inside a future can deadlock the entire system.thenApplyAsync() / thenComposeAsync() unless the lambda does significant CPU
work. These enqueue a new task on the executor for no benefit in the common case. When you
do need them, always pass an explicit Executor.Executor to any AsyncUtil or MoreAsyncUtil method that accepts one
(e.g. AsyncUtil.whileTrue()). Never use the overload without an executor.The authoritative list is in gradle/check.gradle (BANNED_IMPORTS).
Describe intent, not mechanics. Explain the why — a hidden constraint, a non-obvious invariant, a workaround for a specific FDB behavior. If removing the comment would not confuse a future reader, don't write it.
Add Javadoc for public APIs. It is not necessary for private methods unless the logic is genuinely non-obvious.