인공지능 시대의 인간과 컴퓨터를 위한 언어: Crystal
요약
본 글은 AI 에이전트가 코드를 작성하는 시대에, 개발자가 여전히 코드의 내부 구조를 이해하고 통제할 필요성을 강조합니다. Crystal 언어는 Ruby의 친숙한 문법과 정적 타입 체크 기능을 결합하여 이러한 요구사항을 충족시키며, 컴파일러 피드백이 에이전트가 만든 코드를 검토하는 데 중요한 역할을 함을 설명합니다.
핵심 포인트
- AI 시대에도 개발자는 코드 내부 구조 이해가 필수적입니다.
- Crystal은 Ruby의 친숙함과 정적 타입 체크를 결합한 언어입니다.
- 컴파일러 피드백은 에이전트 생성 코드를 검증하는 핵심 도구입니다.
- 에이전트의 선택을 비즈니스/도메인 지식으로 수정해야 할 때가 있습니다.
Rails World 2026에서 DHH는 제가 주목한 노동 분업을 설명했습니다. 바로 에이전트(agent)가 Rust 코드를 작성하게 하고, 우리는 그 결과를 코드 자체를 볼 필요 없이 즐기는 것입니다.
opening keynote의 약 30분 15초경에 그는 이를 간단히 요약합니다:
에이전트는 Rust를 좋아한다.
저는 그 매력을 이해할 수 있고, 제 직장에서도 같은 정서를 느낍니다. 에이전트가 코드를 작성하면, 우리가 손으로 직접 코드를 작성하는 노력은 덜 중요해집니다. 컴파일러(compiler)가 에이전트가 작업하는 동안 실수를 잡아낼 수 있기 때문입니다.
하지만 저는 여전히 제가 책임지는 소프트웨어를 이해하고 싶고 원합니다. 저에게는 그것이 Crystal을 가리킵니다.
저는 Ruby와 Crystal로 작업하며 Marten web framework에 기여하고 있습니다.
친숙한 문법과 정적 검사(static checks)
Crystal은 Ruby에서 영감을 받은 문법, 정적 타입 체크(static type checking) 및 타입 추론(type inference)을 결합했으며 네이티브 코드로 컴파일됩니다. 만약 당신이 Ruby 출신이라면, 많은 부분이 친숙하게 느껴질 것입니다.
이러한 친숙함은 제가 더 이상 모든 줄을 직접 타이핑하지 않을 때에도 중요합니다. 저는 여전히 구현체를 읽고, 그 선택에 의문을 제기하며, 직접 변경 사항을 만듭니다. 타입 추론은 많은 주석(annotations)들을 간결하게 유지하는 동시에 컴파일러가 값들이 어떻게 사용되는지 검사해 줍니다.
널(null) 값을 처리하는 정적 체크는 다른 언어에서도 가능합니다. 하지만 제가 Crystal에 흥미를 느끼는 부분은, 그 체크들이 읽기 쉽다고 생각하는 코드 내에서 가능하다는 점입니다. 에이전트는 컴파일러로부터 피드백을 받고, 저는 작업할 수 있는 구현체를 얻습니다.
컴파일러가 올바른 도구를 선택할 수는 없다
에이전트가 적절한 라이브러리와 통합 접근 방식을 선택했는지 어떻게 알 수 있을까요?
강연 후반부, 약 32분 15초경에서 DHH는 작업을 비동기적으로 할당하고 그 결과를 검토하는 과정을 설명합니다. 제 프로젝트에서는 그 검토에 구현체 내부의 선택 사항들이 포함됩니다.
저는 최근 단 하나의 프롬프트만으로 작은 Marten 애플리케이션을 설정해 보았습니다. 구현 과정에서 저는 에이전트를 두 번이나 수정해야 했습니다:
- 저는 Marten Turbo와 Marten Stimulus가 필요했습니다. 에이전트는 제가 원했던 통합 방식인 Marten-importmap의 CLI 대신 npm을 사용하여 JavaScript 패키지를 설치했습니다.
- 또한, Marten-throttle를 사용하는 대신 자체 스로틀링 미들웨어를 개발하기 시작했습니다.
저는 이 Marten 샤드들을 유지 관리하고 있기 때문에 이러한 우회 경로(detours)들을 인식할 수 있었습니다. npm은 제가 원했던 설정이 아니었고, 커스텀 미들웨어는 제가 더 많은 코드를 유지 관리해야 한다는 것을 의미했을 것입니다. 어느 쪽 선택도 타입 오류는 아니었습니다. 이를 수정하는 것은 프로젝트와 그 라이브러리에 대한 지식을 필요로 했습니다.
컴파일러 피드백이 저에게 도움이 된 경우
몇 달 전, 제가 사용하던 모델들이 덜 유능했을 때는 생성된 Crystal 코드에서 반복적으로 컴파일 오류를 보았습니다. 일부는 String? 값의 잘못된 처리와 관련되어 있었습니다. 어떤 경우에는 컴파일러 피드백이 제가 직접 타입 문제를 설명하지 않아도 에이전트를 수정 방향으로 이끌었습니다.
작은 예시는 컴파일러가 무엇을 잡아낼 수 있는지 보여줍니다:
name = ARGV[0]?
puts name.upcase
첫 번째 인수가 누락될 수 있습니다. 그 타입은 String | Nil이며, String?로도 작성됩니다. Nil이 해당 메서드를 제공하지 않기 때문에 upcase를 직접 호출하는 것은 거부됩니다.
일반적인 조건문은 누락된 값을 처리합니다:
name = ARGV[0]?
if name
...
if 블록 내부에서 컴파일러는 지역 변수가 nil일 수 없다는 것을 알고 있습니다. Crystal 문서는 타입 좁히기(type narrowing)와 그 한계를 포함하여, 반복적인 게터 호출이 항상 같은 방식으로 좁혀질 수 없는 이유도 설명합니다.
또 다른 if/else를 작성하는 것은 지루하게 느껴질 수 있습니다. 에이전트가 코드를 작성해 줄 때 더 감사하게 생각합니다. 그러면 인수가 누락되었을 때 그것이 무엇을 하도록 결정했는지 볼 수 있기 때문입니다.
컴파일해도 여전히 결정할 부분이 남아있음
오류를 피할 또 다른 방법이 있습니다:
name = ARGV[0]?
puts name.not_nil!.upcase
이것은 컴파일되지만, 인자가 누락되면 예외를 발생시킵니다. standard-library documentation에 따르면 해당 동작을 설명하며 가능한 경우 not_nil! 사용을 피할 것을 권장합니다.
Ameba의 Lint/NotNil 규칙은 이 예시의 단언(assertion)을 플래그합니다. 해당 규칙이 활성화되면, 린터는 컴파일러가 허용하는 단축키에 대해 에이전트에게 피드백을 제공합니다.
요구사항은 여전히 미결 상태입니다. 이름 누락 시 게스트 레이블(guest label)을 생성해야 할까요? 아니면 작업을 중단시켜야 할까요? 부재는 불가능하다고 가정되어야 할까요? 컴파일러는 게스트 폴백(guest fallback)과 단언 둘 다를 허용합니다. 저는 여전히 어떤 것이 요구사항에 맞는지 결정해야 합니다.
저는 Marten 애플리케이션과 배포 도구인 Meridian에 대한 엔드투엔드(end-to-end) 검사가 필요하며, 수정 사항이나 기능 추가가 필요합니다. 그 작업의 일부는 이미 자동화될 수 있지만, 제가 사용하는 에이전트들이 모든 요구사항을 안정적으로 커버하지 못합니다.
같은 오해에서 생성된 코드와 테스트는 서로 일치할 수 있습니다.
라이브러리 및 컴파일러 피드백
Marten 실험은 제가 원하는 라이브러리를 에이전트가 찾아줄 것이라고 가정할 수 없다는 것을 보여주었습니다. 더 작은 생태계에서는 제가 그 라이브러리 이름을 명시하고 최신 문서를 제공해야 합니다.
저자원 프로그래밍 언어용 코드 생성에 대한 연구는 도구와 피드백이 모델이 덜 익숙한 언어로 작업하는 데 어떻게 도움이 될 수 있는지 탐구합니다. 저는 제 실험에서 실수가 발생한 원인을 알지 못합니다. 어느 쪽이든 에이전트의 라이브러리 선택과 언어 사용을 확인합니다.
컴파일러를 기다리는 것도 시간이 많이 걸립니다. 긴 개발 빌드는 모든 수정 라운드를 늦춥니다.
The Generative Compilation preprint는 부분적인 Rust 프로그램 생성 중 컴파일러 피드백을 탐구하며, 완전한 생성 후의 피드백보다 개선된 결과를 보고합니다. 그 결과는 컴파일러 피드백의 시점 자체가 검사 내용만큼 중요하다는 것을 시사합니다.
Rust가 제공하는 것들
Rust의 소유권 모델 (ownership model)과 동시성 검사 (concurrency checks)는 Crystal의 가비지 컬렉터가 대체할 수 없는 보장을 제공합니다. Rust는 또한 피드백 도구로 cargo check와 Clippy를 제공합니다.
저는 저의 Crystal 워크플로우를 Rust와 비교하여 벤치마킹한 적이 없습니다. 만약 매일 Rust로 작업한다면, 그 코드가 검토하기 더 쉽다고 느낄 수 있습니다.
Crystal 역시 읽기 어려울 수 있습니다. 복잡한 매크로는 코드의 동작을 숨길 수 있으며, 안전하지 않은 연산 (unsafe operations)은 신중한 검토가 필요합니다. 에이전트가 구현을 처리하더라도 저는 그 노력을 고려해야 합니다.
제가 여전히 이해하고 싶은 코드
저는 에이전트들이 더 많은 구현 작업을 수행할 것이라고 예상합니다. 제 프로젝트에서는 여전히 결과를 판단하고, 요구사항을 확인하며, 변경 사항을 만들 만큼 충분히 이해하는 것이 필요하고 원합니다.
Crystal은 정적 피드백을 통해 에이전트가 특정 실수를 수정하도록 돕고, 그 구문(syntax)은 제가 남아있는 결정들을 검사할 수 있도록 도와줍니다.
Crystal은 스스로를 인간과 컴퓨터를 위한 언어라고 부릅니다. 컴퓨터가 글쓰기 작업을 더 많이 맡게 되면서, 인간적인 측면이 그것을 선택하는 이유가 됩니다. 저는 구현을 위임하고 결과를 이해하고 변경할 수 있는 능력을 유지하고 싶습니다. Crystal은 저에게 그것을 실용적으로 만듭니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기