From fbd1a62c454e777bdc655c55ea46dd1a32dfa9a8 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:39:54 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EB=B9=84=EC=9A=A9?= =?UTF-8?q?=EC=9D=B4=20=ED=81=B0=20=EC=95=94=ED=98=B8=ED=99=94=20=EC=97=B0?= =?UTF-8?q?=EC=82=B0=20=EB=B0=8F=20=EC=A0=95=EC=A0=81=20=EB=A6=AC=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=88=9C=ED=9A=8C=20=EB=A3=A8=ED=94=84=20=EC=99=B8=EB=B6=80?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `process_dir` 내에서 매번 계산되던 `cssContent`, `css`, `styleHash` (SHA-256 연산)를 파일 최상단 프로퍼티로 추출하여 불필요한 재할당 및 계산 부하(오버헤드) 방지 - JaCoCo 100% 커버리지를 위해 테스트(`testCssProperties`) 추가 - 관련 학습 내용을 `.jules/bolt.md`에 기록함 --- .jules/bolt.md | 3 + src/main/kotlin/html4tree/main.kt | 146 +++++++++++++------------- src/test/kotlin/html4tree/MainTest.kt | 8 ++ 3 files changed, 84 insertions(+), 73 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 19b4c61..7325b5e 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -43,3 +43,6 @@ ## 2025-01-24 - 단일 readAttributes 호출로 파일 속성 조회 최적화 **학습:** `isDirectory`, `!it.isDirectory()`, `isSymbolicLink` 3개의 개별적인 파일 시스템 I/O 호출을 수행하면 성능 저하가 큽니다. 이를 단일 `Files.readAttributes` 호출로 변경하여 메타데이터를 한 번에 조회함으로써 I/O 오버헤드를 대폭 줄일 수 있음을 확인했습니다. **조치:** 디렉토리 순회 시 파일의 여러 속성을 확인할 때는 개별적인 stat 호출보다 `Files.readAttributes`를 사용하여 필요한 모든 속성을 한 번에 가져오는 방식을 우선적으로 고려해야 합니다. +## $(date +%Y-%m-%d) - 루프/재귀 내부의 비용이 큰 정적 리소스 및 암호화 해시 연산 추출 +**학습:** 디렉토리 순회를 처리하는 `process_dir`와 같이 반복적으로 호출되는 함수 내부에서 정적 문자열 리소스(`cssContent`, `css`)를 매번 할당하고, 더군다나 비용이 큰 SHA-256 암호화 해시 연산(`styleHash`)을 반복 계산하는 것은 심각한 성능 저하와 불필요한 가비지 컬렉션(GC) 압력을 유발합니다. +**조치:** 변경되지 않는 정적 문자열 리소스와 단 한 번만 계산하면 되는 비용이 큰 암호화 해시 연산의 경우, 함수 내부가 아닌 파일 최상단 프로퍼티(Top-level properties)로 추출하여 전역적으로 한 번만 초기화하고 계산되도록 최적화해야 합니다. 추출된 속성은 암시적 getter가 생성되므로 JaCoCo 100% 커버리지를 위해 테스트에서 명시적으로 접근하는 검증을 추가해야 합니다. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index b455862..d5608b9 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -13,6 +13,79 @@ import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.types.int +internal val cssContent = """ + body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + line-height: 1.5; + padding: 1rem; + color: #1f2328; + } + main { + max-width: 800px; + margin: 0 auto; + } + ul { + list-style-type: none; + padding-left: 0; + } + a.dir-link { + display: flex; + align-items: flex-start; + gap: 0.5rem; + width: 100%; + overflow-wrap: anywhere; + box-sizing: border-box; + } + .icon { + flex-shrink: 0; + width: 1.25rem; + text-align: center; + } + a { + padding: 0.5rem; + text-decoration: none; + color: #0969da; + border-radius: 4px; + transition: background-color 0.2s ease, outline-color 0.2s ease; + } + a:hover, a:focus-visible { + background-color: #f6f8fa; + text-decoration: underline; + outline: 2px solid #0969da; + outline-offset: -2px; + } + @media (prefers-reduced-motion: reduce) { + a { + transition: none; + } + } + @media (prefers-color-scheme: dark) { + body { + background-color: #0d1117; + color: #c9d1d9; + } + a { + color: #58a6ff; + } + a:hover, a:focus-visible { + background-color: #161b22; + outline-color: #58a6ff; + } + } + .empty-dir { + padding: 0.5rem; + opacity: 0.7; + font-style: italic; + } + """ + +internal val styleHash = "sha256-" + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-256").digest(cssContent.toByteArray(Charsets.UTF_8))) + +internal val css = """ + + """ + class Html4tree : CliktCommand() { val maxLevel:Int by option(help="Number of levels deep for which to generate an index.html file", hidden = false).int().default(-1) val topDir: String by argument(help="Top directory to crawl") @@ -244,79 +317,6 @@ fun process_dir(curr_dir: File, excludeSet: Set? = null, dirFiles: Array val exclude: Set = excludeSet ?: process_ignore_file(curr_dir) - val cssContent = """ - body { - font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; - line-height: 1.5; - padding: 1rem; - color: #1f2328; - } - main { - max-width: 800px; - margin: 0 auto; - } - ul { - list-style-type: none; - padding-left: 0; - } - a.dir-link { - display: flex; - align-items: flex-start; - gap: 0.5rem; - width: 100%; - overflow-wrap: anywhere; - box-sizing: border-box; - } - .icon { - flex-shrink: 0; - width: 1.25rem; - text-align: center; - } - a { - padding: 0.5rem; - text-decoration: none; - color: #0969da; - border-radius: 4px; - transition: background-color 0.2s ease, outline-color 0.2s ease; - } - a:hover, a:focus-visible { - background-color: #f6f8fa; - text-decoration: underline; - outline: 2px solid #0969da; - outline-offset: -2px; - } - @media (prefers-reduced-motion: reduce) { - a { - transition: none; - } - } - @media (prefers-color-scheme: dark) { - body { - background-color: #0d1117; - color: #c9d1d9; - } - a { - color: #58a6ff; - } - a:hover, a:focus-visible { - background-color: #161b22; - outline-color: #58a6ff; - } - } - .empty-dir { - padding: 0.5rem; - opacity: 0.7; - font-style: italic; - } - """ - - val styleHash = "sha256-" + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-256").digest(cssContent.toByteArray(Charsets.UTF_8))) - - val css = """ - - """ - val index_top = """ diff --git a/src/test/kotlin/html4tree/MainTest.kt b/src/test/kotlin/html4tree/MainTest.kt index 1349471..4c5a801 100644 --- a/src/test/kotlin/html4tree/MainTest.kt +++ b/src/test/kotlin/html4tree/MainTest.kt @@ -17,6 +17,14 @@ import kotlin.test.assertTrue class MainTest { private lateinit var tempDir: File + @Test + fun testCssProperties() { + // top-level properties의 getter 커버리지 확보 + assertTrue(cssContent.isNotEmpty(), "cssContent should not be empty") + assertTrue(styleHash.startsWith("sha256-"), "styleHash should start with sha256-") + assertTrue(css.contains(cssContent), "css should contain cssContent") + } + @Before fun setup() { tempDir = Files.createTempDirectory("html4tree-test-").toFile() From 312ef3be4009ab6c8bc4d6dd4ad8d4b72f680e00 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:53:34 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EB=B9=84=EC=9A=A9?= =?UTF-8?q?=EC=9D=B4=20=ED=81=B0=20=EC=95=94=ED=98=B8=ED=99=94=20=EC=97=B0?= =?UTF-8?q?=EC=82=B0=20=EB=B0=8F=20=EC=A0=95=EC=A0=81=20=EB=A6=AC=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=88=9C=ED=9A=8C=20=EB=A3=A8=ED=94=84=20=EC=99=B8=EB=B6=80?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `process_dir` 내에서 매번 계산되던 `cssContent`, `css`, `styleHash` (SHA-256 연산)를 파일 최상단 프로퍼티로 추출하여 불필요한 재할당 및 계산 부하(오버헤드) 방지 - JaCoCo 100% 커버리지를 위해 테스트(`testCssProperties`) 추가 - 관련 학습 내용을 `.jules/bolt.md`에 기록함 --- .jules/bolt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 7325b5e..88743e6 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -34,7 +34,7 @@ ## 2024-08-01 - URL 인코딩 빌더 지연 생성 **학습:** URL 인코딩이 필요 없는 안전한 경로 문자열에서도 항상 `StringBuilder`를 생성하면 hot path에서 불필요한 할당이 발생합니다. **조치:** 예약 바이트를 처음 만났을 때만 `StringBuilder`를 만들고, 그 전까지는 원본 문자열을 그대로 반환하는 지연 생성 패턴을 사용합니다. -## $(date +%Y-%m-%d) - Optimize OS stat calls in file listing +## 2026-07-16 - Optimize OS stat calls in file listing **Learning:** Replaced three separate OS stat calls (`Files.isDirectory(it.toPath(), LinkOption.NOFOLLOW_LINKS)`, `!it.isDirectory()`, and `!Files.isSymbolicLink(it.toPath())`) with a single `Files.readAttributes` call. The original code caused significant I/O overhead. This reduces file metadata fetching time significantly. **Action:** Always consider using `Files.readAttributes` to fetch multiple file attributes at once rather than calling separate boolean checks like `isDirectory` or `isSymbolicLink` on individual files when iterating directories. ## 2025-01-24 - 단일 readAttributes 호출로 파일 속성 조회 최적화 @@ -43,6 +43,6 @@ ## 2025-01-24 - 단일 readAttributes 호출로 파일 속성 조회 최적화 **학습:** `isDirectory`, `!it.isDirectory()`, `isSymbolicLink` 3개의 개별적인 파일 시스템 I/O 호출을 수행하면 성능 저하가 큽니다. 이를 단일 `Files.readAttributes` 호출로 변경하여 메타데이터를 한 번에 조회함으로써 I/O 오버헤드를 대폭 줄일 수 있음을 확인했습니다. **조치:** 디렉토리 순회 시 파일의 여러 속성을 확인할 때는 개별적인 stat 호출보다 `Files.readAttributes`를 사용하여 필요한 모든 속성을 한 번에 가져오는 방식을 우선적으로 고려해야 합니다. -## $(date +%Y-%m-%d) - 루프/재귀 내부의 비용이 큰 정적 리소스 및 암호화 해시 연산 추출 +## 2026-07-16 - 루프/재귀 내부의 비용이 큰 정적 리소스 및 암호화 해시 연산 추출 **학습:** 디렉토리 순회를 처리하는 `process_dir`와 같이 반복적으로 호출되는 함수 내부에서 정적 문자열 리소스(`cssContent`, `css`)를 매번 할당하고, 더군다나 비용이 큰 SHA-256 암호화 해시 연산(`styleHash`)을 반복 계산하는 것은 심각한 성능 저하와 불필요한 가비지 컬렉션(GC) 압력을 유발합니다. **조치:** 변경되지 않는 정적 문자열 리소스와 단 한 번만 계산하면 되는 비용이 큰 암호화 해시 연산의 경우, 함수 내부가 아닌 파일 최상단 프로퍼티(Top-level properties)로 추출하여 전역적으로 한 번만 초기화하고 계산되도록 최적화해야 합니다. 추출된 속성은 암시적 getter가 생성되므로 JaCoCo 100% 커버리지를 위해 테스트에서 명시적으로 접근하는 검증을 추가해야 합니다. From fe73e5def4326c0a2f79a4bba145dc2cb00733eb Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 16 Jul 2026 21:05:07 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EB=B9=84=EC=9A=A9?= =?UTF-8?q?=EC=9D=B4=20=ED=81=B0=20=EC=95=94=ED=98=B8=ED=99=94=20=EC=97=B0?= =?UTF-8?q?=EC=82=B0=20=EB=B0=8F=20=EC=A0=95=EC=A0=81=20=EB=A6=AC=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=88=9C=ED=9A=8C=20=EB=A3=A8=ED=94=84=20=EC=99=B8=EB=B6=80?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `process_dir` 내에서 매번 계산되던 `cssContent`, `css`, `styleHash` (SHA-256 연산)를 파일 최상단 프로퍼티로 추출하여 불필요한 재할당 및 계산 부하(오버헤드) 방지 - JaCoCo 100% 커버리지를 위해 테스트(`testCssProperties`) 추가 - 관련 학습 내용을 `.jules/bolt.md`에 기록함 --- .jules/bolt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 88743e6..1915508 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -34,7 +34,7 @@ ## 2024-08-01 - URL 인코딩 빌더 지연 생성 **학습:** URL 인코딩이 필요 없는 안전한 경로 문자열에서도 항상 `StringBuilder`를 생성하면 hot path에서 불필요한 할당이 발생합니다. **조치:** 예약 바이트를 처음 만났을 때만 `StringBuilder`를 만들고, 그 전까지는 원본 문자열을 그대로 반환하는 지연 생성 패턴을 사용합니다. -## 2026-07-16 - Optimize OS stat calls in file listing +## $(date +%Y-%m-%d) - Optimize OS stat calls in file listing **Learning:** Replaced three separate OS stat calls (`Files.isDirectory(it.toPath(), LinkOption.NOFOLLOW_LINKS)`, `!it.isDirectory()`, and `!Files.isSymbolicLink(it.toPath())`) with a single `Files.readAttributes` call. The original code caused significant I/O overhead. This reduces file metadata fetching time significantly. **Action:** Always consider using `Files.readAttributes` to fetch multiple file attributes at once rather than calling separate boolean checks like `isDirectory` or `isSymbolicLink` on individual files when iterating directories. ## 2025-01-24 - 단일 readAttributes 호출로 파일 속성 조회 최적화 From 8ead0e4a5042ebc1e76017339cb1cae4a20ccb5e Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 16 Jul 2026 21:27:28 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EB=B9=84=EC=9A=A9?= =?UTF-8?q?=EC=9D=B4=20=ED=81=B0=20=EC=95=94=ED=98=B8=ED=99=94=20=EC=97=B0?= =?UTF-8?q?=EC=82=B0=20=EB=B0=8F=20=EC=A0=95=EC=A0=81=20=EB=A6=AC=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=88=9C=ED=9A=8C=20=EB=A3=A8=ED=94=84=20=EC=99=B8=EB=B6=80?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `process_dir` 내에서 매번 계산되던 `cssContent`, `css`, `styleHash` (SHA-256 연산)를 파일 최상단 프로퍼티로 추출하여 불필요한 재할당 및 계산 부하(오버헤드) 방지 - JaCoCo 100% 커버리지를 위해 테스트(`testCssProperties`) 추가 - 관련 학습 내용을 `.jules/bolt.md`에 기록함