Skip to content

Split View: 문서 속 코드 스크린샷은 언제부터 거짓말이 되는가 — 이미지를 빌드 산출물로 만들기

✨ Learn with Quiz
|

문서 속 코드 스크린샷은 언제부터 거짓말이 되는가 — 이미지를 빌드 산출물로 만들기

반년 지난 문서의 스크린샷이 하는 일

가이드 문서 중간에 예쁜 코드 스크린샷이 하나 박혀 있습니다. 어두운 배경에 둥근 모서리, 왼쪽 위에 빨강 노랑 초록 점 세 개. 여섯 달 전에 누군가 자기 편집기에서 캡처해 붙인 것입니다.

그 사이에 함수 이름이 바뀌었습니다. 옵션 하나가 사라졌습니다. 문서의 본문은 갱신됐습니다. 이미지는 그대로입니다. 이제 그 페이지는 위쪽 문단에서 새 이름을 말하고 아래쪽 이미지에서 옛 이름을 보여 줍니다. 그리고 처음 온 사람은 대개 이미지를 믿습니다. 글보다 그림이 구체적으로 보이기 때문입니다.

문제는 이미지가 틀렸다는 것이 아닙니다. 틀렸다는 사실을 아무도 알 수 없다는 것입니다. 코드 블록이었다면 검색에 걸렸을 텐데 이미지는 걸리지 않습니다.

스크린샷은 산출물인데 소스가 없습니다

이 문제를 다르게 말하면 이렇습니다. 문서 저장소에 들어 있는 그 PNG 파일은 산출물입니다. 그런데 그것을 다시 만들 방법이 저장소에 없습니다. 어떤 편집기에서, 어떤 테마로, 어떤 폰트 크기로, 어느 부분을 잘라서 찍었는지가 기록되어 있지 않습니다.

우리는 이런 상황을 다른 영역에서는 곧바로 문제로 봅니다. 빌드 결과물을 소스 없이 저장소에 커밋하면 리뷰에서 지적이 나옵니다. 그런데 이미지는 이상하게 예외가 됩니다. 이미지는 문서의 장식이지 코드가 아니라고 생각하기 때문입니다.

기준을 하나로 맞추면 해야 할 일이 분명해집니다. 명령 한 줄로 다시 만들 수 없는 것은 저장소에 두지 않는다. 이미지도 예외가 아닙니다.

그 전에 한 단계 먼저 물어야 할 것이 있습니다. 이 코드가 굳이 이미지여야 하는가입니다. 문서 안의 코드는 대부분 코드 블록이 낫습니다. 복사할 수 있고, 검색에 걸리고, 화면 낭독기가 읽고, 화면 폭에 맞춰 접힙니다. 이미지가 정당한 경우는 좁습니다. 강조 색이나 편집기 표시처럼 텍스트로 옮길 수 없는 정보가 함께 있어야 할 때, 발표 슬라이드나 소셜 카드처럼 텍스트를 넣을 수 없는 매체에 실을 때, 그리고 터미널의 색과 정렬이 설명의 일부일 때 정도입니다. 이 셋에 해당하지 않는데 이미지를 쓰고 있었다면, 재생성 문제를 푸는 것보다 코드 블록으로 되돌리는 쪽이 먼저입니다.

goshot이 하는 일

goshot은 코드와 터미널 출력을 이미지로 만드는 Go 라이브러리이자 CLI입니다. README는 스스로를 Carbon이나 Silicon과 비슷한 도구라고 소개합니다. 차이는 웹 페이지가 아니라 명령줄에서 돈다는 점이고, 이 글의 논지에서는 그 차이가 전부입니다.

기능 목록에서 확인한 것만 적으면, 구문 강조는 chroma를 써서 수백 가지 테마를 지원하고, ANSI 색이 들어간 터미널 출력을 그대로 렌더링하며, 창 장식은 macOS와 Windows 11과 GNOME과 KDE Breeze 모양을 고를 수 있습니다. 배경은 단색과 일곱 가지 그라디언트와 이미지를 지원하고, 출력은 PNG와 JPEG와 BMP이며 클립보드와 표준 출력으로도 내보냅니다. 그리고 API 키와 토큰과 비밀번호를 자동으로 가리는 기능이 목록에 들어 있습니다.

기본 사용은 이렇습니다.

# 파일 하나를 이미지로
goshot main.go -o main.png

# 표준 입력을 받아 클립보드로
cat main.go | goshot -c

# 테마와 창 모양과 배경을 지정
goshot main.go -t catppuccin-mocha -C gnome -b '#1e1e2e'

# 특정 줄만 강조
goshot main.go --highlight-lines 10..14

설치는 Go가 있으면 go install github.com/watzon/goshot/cmd/goshot@latest이고, Arch의 AUR과 우분투 PPA 패키지도 안내되어 있습니다.

라이브러리로 쓸 때의 구조도 봐 둘 만합니다. README는 이것을 작은 파이프라인으로 설명합니다. 내용을 이미지로 그리고, 그것을 창 장식으로 감싸고, 배경 위에 올립니다. 세 단계가 각각 독립된 개념이라 하나만 바꾸기 쉽습니다. 문서 도구를 직접 만든다면 이 분리 자체가 참고할 만한 설계입니다. 테마를 바꾸는 일과 창 모양을 바꾸는 일과 여백을 바꾸는 일이 서로를 건드리지 않으면, 나중에 스타일을 일괄 변경할 때 고칠 자리가 한 곳으로 모입니다.

명령이 되는 순간 달라지는 것

goshot 자체가 특별한 도구라서가 아닙니다. 이미지 생성이 명령이 되면 세 가지가 따라옵니다.

첫째, 문서 갱신과 이미지 갱신이 같은 작업이 됩니다. 코드를 고치고 문서를 고칠 때 이미지 생성 명령을 한 번 더 돌리면 됩니다. 사람이 캡처 도구를 열고 창 크기를 맞추고 잘라내는 과정이 사라집니다.

둘째, 차이가 눈에 보입니다. 이미지가 소스에서 결정론적으로 만들어지면, 코드가 안 바뀌었을 때 이미지도 안 바뀝니다. 그래서 변경 사항에 이미지 파일이 올라왔다는 것 자체가 신호가 됩니다.

셋째, 일관성이 규칙이 됩니다. 테마와 폰트와 여백이 명령의 인자로 적히므로, 팀원마다 다른 편집기 테마로 찍은 스크린샷이 섞이는 일이 없어집니다.

터미널 출력도 같은 문제를 갖고 있습니다

문서에서 더 자주 낡는 것은 코드보다 터미널 출력입니다. 명령의 출력 형식은 도구 버전이 오르면 조용히 바뀌기 때문입니다. goshot은 명령을 실행하고 그 출력을 그대로 이미지로 만드는 하위 명령을 제공합니다.

# 명령을 실행하고 출력을 이미지로
goshot exec -A -p -- go test ./...

# 제목 표시줄 색을 내용에 맞춰 자연스럽게
goshot exec -A -p --title-bar-color auto -- ls -la

여기서 중요한 것은 이미지가 예쁘다는 점이 아니라, 그 출력이 문서를 만들 때 실제로 실행된 결과라는 점입니다. 손으로 옮겨 적은 출력은 옮기는 사람이 줄을 지우거나 다듬으면서 원본과 달라지고, 그 차이를 나중에 검증할 방법이 없습니다.

비밀 가리기는 부가 기능이 아닙니다

기능 목록 중 실무에서 가장 값이 나가는 것은 자동 가리기라고 생각합니다. 스크린샷을 통한 자격 증명 유출이 위험한 이유는 유출 자체보다 탐지가 어렵다는 데 있습니다. 저장소에 커밋된 텍스트 키는 비밀 스캐너가 잡아냅니다. 이미지 안에 들어간 키는 잡히지 않습니다. 사람이 눈으로 발견할 때까지 그대로 있습니다.

goshot은 API 키와 토큰과 비밀번호를 자동으로 가리는 기능을 내세우고, 가리는 방식도 고를 수 있게 되어 있습니다.

goshot config.go --redact --redact-style blur

다만 여기서 한 가지를 분명히 해야 합니다. 자동 가리기는 알려진 패턴에 반응하는 장치이고, 사내 시스템의 고유한 형식은 패턴에 없을 수 있습니다. 그래서 이 기능은 마지막 방어선이 아니라 첫 번째 그물로 두는 것이 맞습니다. 진짜 방어선은 문서를 만들 때 실제 자격 증명이 들어간 환경을 쓰지 않는 것입니다.

스타일 설정을 저장소에 두기

goshot은 자주 쓰는 플래그의 기본값을 설정 파일에 둘 수 있습니다. README에 따르면 ~/.config/goshot/config.yaml에 플래그 이름과 값을 평평한 형태로 적고, 명령줄에서 준 플래그가 항상 이깁니다.

theme: catppuccin-mocha
chrome: mac
background: '#1e1e2e'
corner-radius: 12

이 파일이 홈 디렉터리에 있으면 사람마다 다른 결과가 나옵니다. 그래서 문서를 만드는 저장소라면 이 값들을 홈이 아니라 프로젝트 안에 두고 스크립트에서 명시적으로 넘기는 편이 낫습니다. 그러면 스타일 변경이 코드 리뷰에 올라오고, 어느 날 갑자기 문서 전체의 배경색이 바뀌는 일이 생기지 않습니다.

이번 주에 붙일 수 있는 최소 구성

가장 작은 형태는 이렇습니다. 이미지의 소스가 될 코드 조각을 따로 파일로 두고, 그 파일에서 이미지를 만드는 규칙을 하나 적습니다.

# docs/snippets/*.go 에서 docs/images/*.png 를 만든다
SNIPPETS := $(wildcard docs/snippets/*.go)
IMAGES   := $(patsubst docs/snippets/%.go,docs/images/%.png,$(SNIPPETS))

images: $(IMAGES)

docs/images/%.png: docs/snippets/%.go
	goshot $< -o $@ -t catppuccin-mocha -C mac --redact

이렇게 두면 코드 조각이 실제 컴파일 대상이 되므로, 스니펫이 낡으면 빌드나 린트가 먼저 알려 줍니다. 이미지는 그 뒤에 따라옵니다. 이미지가 낡는 문제를 이미지로 푸는 대신 소스가 낡는 문제로 되돌린 것이 이 구성의 요점입니다.

여기서 솔직하게 적어 둘 것이 있습니다. 이 Makefile은 제가 goshot을 설치해 실제로 돌려 본 결과가 아닙니다. goshot의 명령과 플래그는 저장소 README에서 확인한 것이고, Makefile의 패턴 규칙 부분은 일반적인 make 문법입니다. 도입할 때는 작은 스니펫 하나로 먼저 돌려 보시기 바랍니다.

정리와 출처

핵심은 도구 이름이 아닙니다. 문서에 들어가는 이미지에도 다른 산출물과 같은 기준을 적용하자는 것입니다. 명령으로 다시 만들 수 있는가. 만들 수 없다면 그 이미지는 언젠가 아무도 모르게 틀린 것을 말하기 시작합니다.

  • watzon/goshot 저장소 — 기능 목록, CLI 예시, 설정 파일 형식, 라이선스. 본문의 goshot 관련 서술은 모두 이 저장소의 README에서 확인했습니다.
  • 저장소 메타데이터로 확인한 사항: 라이선스는 MIT, 주 언어는 Go, 저장소 생성은 2024년 11월입니다. 1인 개발자가 유지하는 프로젝트이며 대기업이 뒤를 받치는 도구가 아니라는 점을 감안해 도입 여부를 판단하시기 바랍니다.
  • 본문의 CLI 예시는 README에 실린 것을 옮긴 것이고, Makefile 예시는 실행해 확인하지 않았습니다.

When Does a Code Screenshot in Your Docs Start Lying — Turning Images into Build Artifacts

What a screenshot in a six-month-old document is doing

There is a pretty code screenshot planted in the middle of a guide. Dark background, rounded corners, three red-yellow-green dots at the top left. Someone captured it from their editor six months ago and pasted it in.

Since then a function has been renamed. An option has disappeared. The body text of the document was updated. The image was not. Now that page says the new name in the paragraph above and shows the old name in the image below. And a newcomer generally believes the image, because a picture looks more concrete than prose.

The problem is not that the image is wrong. It is that nobody can find out that it is wrong. A code block would have turned up in a search. An image does not.

A screenshot is an artifact with no source

Put the problem another way. That PNG file sitting in the documentation repository is an artifact. And there is no way in the repository to build it again. Which editor, which theme, which font size, which region was cropped — none of that is recorded.

We treat this situation as an obvious problem in other areas. Commit a build output to a repository without its source and review will call it out. Yet images somehow get an exemption, because we think of an image as decoration for the document rather than as code.

Apply a single standard and the work to be done becomes clear. Anything that cannot be rebuilt with one command does not belong in the repository. Images are no exception.

There is a prior question to ask first, though: does this code have to be an image at all? Most code inside documentation is better as a code block. It can be copied, it turns up in searches, screen readers read it, and it wraps to the width of the screen. The cases where an image is justified are narrow. When information that cannot be carried in text has to come along, such as highlight colors or editor markers. When it goes into a medium that cannot hold text, such as a presentation slide or a social card. And when terminal color and alignment are part of the explanation. If none of those three apply and you were using an image, going back to a code block comes before solving the regeneration problem.

What goshot does

goshot is a Go library and CLI that turns code and terminal output into images. The README introduces it as a tool similar to Carbon or Silicon. The difference is that it runs on the command line rather than as a web page, and for the argument of this post that difference is everything.

Writing down only what I confirmed in the feature list: syntax highlighting uses chroma and supports hundreds of themes, terminal output containing ANSI colors is rendered as-is, and for window chrome you can choose the macOS, Windows 11, GNOME, or KDE Breeze look. Backgrounds support solid colors, seven gradients, and images; output is PNG, JPEG, and BMP, and it can also emit to the clipboard and to standard output. And a feature that automatically redacts API keys, tokens, and passwords is on the list.

Basic usage looks like this.

# one file to an image
goshot main.go -o main.png

# take standard input, send to the clipboard
cat main.go | goshot -c

# specify theme, window shape, and background
goshot main.go -t catppuccin-mocha -C gnome -b '#1e1e2e'

# highlight only certain lines
goshot main.go --highlight-lines 10..14

Installation is go install github.com/watzon/goshot/cmd/goshot@latest if you have Go, and an AUR package for Arch and an Ubuntu PPA are documented too.

The structure when used as a library is worth a look as well. The README describes it as a small pipeline: render the content into an image, wrap that in window chrome, place it on a background. The three stages are independent concepts, so changing just one is easy. If you are building a documentation tool yourself, this separation alone is a design worth borrowing. When changing the theme, changing the window shape, and changing the padding do not touch one another, the place you have to edit for a later bulk style change collapses into one spot.

What changes the moment it becomes a command

It is not that goshot is a special tool. Once image generation becomes a command, three things follow.

First, updating the document and updating the image become the same job. When you fix the code and fix the docs, you run the image generation command one more time. The process of a human opening a capture tool, sizing a window, and cropping disappears.

Second, differences become visible. When an image is produced deterministically from a source, the image does not change when the code does not change. So the mere fact that an image file appeared in a changeset is itself a signal.

Third, consistency becomes a rule. Theme, font, and padding are written as arguments to a command, so you no longer get a mixture of screenshots taken with each team member's own editor theme.

Terminal output has the same problem

What goes stale more often in documentation is terminal output rather than code, because the output format of a command changes quietly when the tool version goes up. goshot provides a subcommand that runs a command and turns its output straight into an image.

# run a command and turn the output into an image
goshot exec -A -p -- go test ./...

# make the title bar color follow the content naturally
goshot exec -A -p --title-bar-color auto -- ls -la

What matters here is not that the image is pretty but that the output is what actually ran when the document was built. Output transcribed by hand diverges from the original as the transcriber deletes or tidies lines, and there is no way to verify that divergence later.

Redacting secrets is not a bonus feature

Of everything on the feature list, I think automatic redaction is worth the most in practice. What makes credential leakage through screenshots dangerous is not the leak itself but how hard it is to detect. A text key committed to a repository gets caught by a secret scanner. A key inside an image does not. It stays there until a human notices it with their eyes.

goshot advertises automatic redaction of API keys, tokens, and passwords, and lets you choose the redaction style as well.

goshot config.go --redact --redact-style blur

One thing has to be made clear, though. Automatic redaction is a device that reacts to known patterns, and the formats specific to an internal system may not be among those patterns. So this feature belongs as the first net, not the last line of defense. The real line of defense is not using an environment that contains genuine credentials when you build documentation.

Keeping the style configuration in the repository

goshot lets you put default values for frequently used flags in a configuration file. According to the README, you write flag names and values in a flat form in ~/.config/goshot/config.yaml, and a flag given on the command line always wins.

theme: catppuccin-mocha
chrome: mac
background: '#1e1e2e'
corner-radius: 12

If this file lives in a home directory, each person gets a different result. So for a repository that builds documentation, it is better to keep these values inside the project rather than in the home directory and pass them explicitly from a script. Then a style change comes up in code review, and you never wake up one day to find the background color of every document has changed.

The smallest thing you could add this week

The smallest form looks like this. Keep the code fragments that will become the sources of the images in their own files, and write one rule that builds images from those files.

# build docs/images/*.png from docs/snippets/*.go
SNIPPETS := $(wildcard docs/snippets/*.go)
IMAGES   := $(patsubst docs/snippets/%.go,docs/images/%.png,$(SNIPPETS))

images: $(IMAGES)

docs/images/%.png: docs/snippets/%.go
	goshot $< -o $@ -t catppuccin-mocha -C mac --redact

Set up this way, the code fragments become actual compilation targets, so when a snippet goes stale the build or the linter tells you first. The image follows afterwards. The point of this arrangement is that instead of solving the problem of stale images with images, it turns the problem back into one of stale source.

There is something to record honestly here. This Makefile is not the result of me installing goshot and actually running it. The goshot commands and flags were confirmed in the repository README, and the pattern rule part of the Makefile is ordinary make syntax. When you adopt it, please run it against one small snippet first.

Summary and sources

The point is not the name of a tool. It is that images going into documentation should be held to the same standard as any other artifact. Can it be rebuilt with a command? If it cannot, then someday that image will start saying something false without anyone knowing.

  • The watzon/goshot repository — feature list, CLI examples, configuration file format, license. Everything written about goshot in this post was confirmed in this repository's README.
  • Confirmed from repository metadata: the license is MIT, the primary language is Go, and the repository was created in November 2024. Please weigh adoption with the understanding that this is a project maintained by a single developer and not a tool backed by a large company.
  • The CLI examples in this post are carried over from the README, and the Makefile example was not verified by running it.