Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@
## 2025-01-24 - 단일 readAttributes 호출로 파일 속성 조회 최적화
**학습:** `isDirectory`, `!it.isDirectory()`, `isSymbolicLink` 3개의 개별적인 파일 시스템 I/O 호출을 수행하면 성능 저하가 큽니다. 이를 단일 `Files.readAttributes` 호출로 변경하여 메타데이터를 한 번에 조회함으로써 I/O 오버헤드를 대폭 줄일 수 있음을 확인했습니다.
**조치:** 디렉토리 순회 시 파일의 여러 속성을 확인할 때는 개별적인 stat 호출보다 `Files.readAttributes`를 사용하여 필요한 모든 속성을 한 번에 가져오는 방식을 우선적으로 고려해야 합니다.
## 2025-01-24 - process_dir의 정적 에셋 객체(Object) 추출
**Learning:** `html4tree/main.kt`의 `process_dir`과 같은 디렉토리 처리 루프에서, 정적 CSS 콘텐츠를 생성하고 SHA-256 해시를 매 반복마다 수행하면 대규모 디렉토리를 순회할 때 불필요한 문자열 할당 및 CPU 오버헤드가 발생하여 성능이 저하됩니다. 이를 `private object`를 활용하여 캡슐화하면 JVM 수명 주기당 한 번만 초기화되어 효율적입니다. 코틀린에서 컴파일러가 암시적으로 생성하는 getter로 인한 JaCoCo 커버리지 누락을 방지하기 위해 동적 해시에는 `@JvmField`를, 상수로 처리할 수 있는 문자열에는 `const val`을 사용하는 것이 필수적입니다.
**Action:** 재귀적이거나 빈번하게 호출되는 디렉토리 처리 함수 내부에서 대규모 상수 문자열 및 결정론적 계산을 수행할 때는 항상 이를 `private object` 싱글톤으로 추출하십시오. 코틀린의 암시적 getter로 인한 커버리지 감소를 방지하기 위해 `const val`이 불가능한 프로퍼티에는 `@JvmField` 어노테이션을 반드시 사용하십시오.
24 changes: 15 additions & 9 deletions src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,12 @@ fun write_index_file(curr_dir: File, content: String) {
}
}

fun process_dir(curr_dir: File, excludeSet: Set<String>? = null, dirFiles: Array<File>? = null){

val exclude: Set<String> = excludeSet ?: process_ignore_file(curr_dir)

val cssContent = """
// ⚡ Bolt Performance Optimization: Extract static assets
// 1. Pre-computes SHA-256 hash at class-loading instead of per-directory
// 2. Avoids large string allocations inside recursive directory processing
// 3. Uses const val / @JvmField to prevent implicit getters and maintain 100% JaCoCo coverage
private object StaticAssets {
const val cssContent = """
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.5;
Expand Down Expand Up @@ -310,12 +311,17 @@ fun process_dir(curr_dir: File, excludeSet: Set<String>? = null, dirFiles: Array
}
"""

val styleHash = "sha256-" + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-256").digest(cssContent.toByteArray(Charsets.UTF_8)))
@JvmField val styleHash = "sha256-" + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-256").digest(cssContent.toByteArray(Charsets.UTF_8)))

val css = """
const val css = """
<style>
${cssContent} </style>
"""
Comment on lines +316 to 319
}

fun process_dir(curr_dir: File, excludeSet: Set<String>? = null, dirFiles: Array<File>? = null){

val exclude: Set<String> = excludeSet ?: process_ignore_file(curr_dir)

val index_top = """<!doctype html>
<html lang="ko">
Expand All @@ -324,11 +330,11 @@ ${cssContent} </style>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<!-- 보안 향상: 인라인 스크립트 실행 방지 -->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src '${styleHash}'; base-uri 'none'; form-action 'none';">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src '${StaticAssets.styleHash}'; base-uri 'none'; form-action 'none';">
<!-- 보안 향상: 리퍼러를 통한 디렉토리 경로 노출 방지 -->
<meta name="referrer" content="no-referrer">
<title>${curr_dir.getName().escapeHtml()}</title>
${css}
${StaticAssets.css}
</head>
<body>
<main>
Expand Down
Loading