메뉴
HN
Hacker News 8일 전

LLM 시대, '지루한' 프로그래밍 언어를 써야 하는 이유

IMP
7/10
핵심 요약

이 글은 코딩 에이전트가 더 안정적인 결과를 내기 위해서는 파편화가 덜하고 일관된 '지루한' 기술 스택을 선택해야 한다고 강조합니다. 자바스크립트나 파이썬처럼 생태계가 복잡한 언어는 LLM이 학습한 데이터마저 다양하게 파편화되어, 모델이 구식 패턴이나 엉뚱한 코드를 생성할 확률(추론의 도박)을 높입니다. 반면 Go나 Ruby on Rails처럼 강력한 컨벤션(규칙)을 가진 언어는 일관된 학습 데이터를 바탕으로 에이전트가 훨씬 더 예측 가능하고 신뢰도 높은 코드를 작성하게 만듭니다.

번역된 본문

LLM에는 '지루한' 언어를 사용하세요 (2026년 4월 20일)

안녕하세요, 저는 소프트웨어 컨설팅 그룹인 Sancho Studio를 운영하고 있는 Jacob입니다. 우리는 기업들의 기술 리더십, 전략 및 보안을 지원합니다. 저는 '일관성은 복리로 쌓인다'는 생각에 계속해서 고통하고 있습니다. 지난 2년 동안 여러 다양한 프로젝트에 참여한 컨설턴트로서 이 사실을 뼈저리게 느꼈습니다.

대형 언어 모델(LLM)은 불일치하는 기술의 단점을 증폭시키고, 일관된 기술의 장점을 조용히 강화합니다. 파편화가 가장 심한 언어와 생태계는 에이전트(Agentic)의 결과물이 가장 좋지 않으며, 가장 강력한 규칙(컨벤션)을 가진 생태계가 최고의 결과를 냅니다. 저는 이러한 효과가 방대한 말뭉치(Corpus)로 학습된 거대 모델의 패러다임에서 어떤 도구가 살아남을지를 점점 더 결정짓게 될 것이라고 생각합니다.

코드 생성이 저렴해졌다고 해도, 추론(Inference)을 실행하는 것은 일종의 도박입니다. 모델이 언제 패키지를 설치하기로 결정하거나 2019년에 쓰이던 기이한 코딩 패턴을 생성할지 전혀 알 수 없습니다. 우리가 토큰(Tokens)을 가지고 도박을 하고 있다고 가정한다면, 우리는 평균적인 결과물을 내기 위해 강력하게 일관되고 강화된 모델 가중치(Weights)를 대표하는 임베딩(Embeddings) 세트에 베팅해야 합니다. 소프트웨어 개발에서 이는 실제로 이상적입니다. 왜냐하면 평균적인 프로그램은 일반적으로 기본적인 작업, 즉 정보 처리, 파일 읽기/쓰기, 네트워크 요청 응답 등을 수행하기 때문입니다.

AI 시대 이전에 엔지니어들은 매년 자신을 재발명하는 듯한 언어들에 대해 불평했습니다. 이러한 불만은 사실이었지만 대부분 미적인 문제였으며, 인간이 불필요하게 변하는 생태계를 유지 관리하거나 따라잡아야 한다는 좌절감의 증상이었습니다. 돌이켜 보면, 2024년 'State of JS' 설문조사는 상대적으로 파편화된 생태계를 보여줍니다. 인간에게 파편화는 짜증나는 일입니다. 모든 것의 공개 말뭉치로 학습된 모델에게 파편화는 강화 학습이나 에이전트 하네스(Harness)에서 해결해야 하는 문제에 더 가깝습니다 (예를 들어, Claude Code가 유출되었을 때 Anthropic이 JavaScript 프레임워크에 대해 일부 편향(Bias)을 하드코딩한 것을 볼 수 있었습니다).

Python도 마찬가지지만 약간 다른 결의 문제입니다. "어떤 패키지 관리자를 사용하고 계신가요?"와 같은 단순한 질문은 언어 버전, 패키지 관리자 버전, OS 호환성의 매트릭스를 만들어내며, 이는 기술 리드로서 완전히 머리가 아픈 일입니다. pip, poetry, 또는 uv 중 하나를 사용해야 할까요? 당신의 툴체인이 중요한가요, 아니면 크로스 컴파일을 하고 있나요? 어떻게 하면 Python 패키지가 조용히 C 종속성(Dependency)에 링크되어 있는지 알 수 있을까요? async를 사용하고 있나요, 아니면 대신 작업 큐(Task Queue)를 사용하고 있나요? Django인가요, FastAPI인가요? 모델의 관점에서 볼 때, 이 모든 것을 작성하는 방법은 너무 많으며, 학습 시 최신 편향(Recency bias)이 도입되지 않는 한 말뭉치는 이 모든 방법을 대략적으로 동일한 가중치로 반영합니다.

이것이 의미하는 바는 저에게 매우 명확하며, 여러분에게도 그래야 합니다... 학습 말뭉치에서 분산(Variance)이 낮은 언어와 생태계는 코딩 에이전트에 의해 더 잘 표현되고 더 안정적으로 실행됩니다. 고차원 벡터 공간(Higher dimension vector spaces)에서 학습 데이터의 코사인 유사도(Cosine similarity)는 모델의 어텐션(Attention) 및 MLP 레이어가 다음 토큰을 예측하도록 학습하는 기반입니다. 일관된 말뭉치는 일관된 추론 토큰을 생성합니다.

이러한 패턴은 다른 언어에서도 유지됩니다. 에이전트 작업에서 일반적인 JavaScript 백엔드와 비교하여 Rails 프로젝트에 대한 추론은 더 일관된 출력을 생성합니다. 이는 Ruby가 어떤 철학적(Platonic) 의미에서 더 나은 언어라서가 아니라, 본질적으로 '하나의 Rails'만 존재하기 때문입니다. 동일한 기본 기능 위에 서로 다른 벤 다이어그램을 제공하는 프로덕션급 JavaScript 프레임워크는 최소 12개는 있습니다.

'설정보다 컨벤션(규칙)'은 인간 프로그래머에게 승리였습니다. 왜냐하면 유연성은 과대평가되어 있고 제약 조건은 우리를 자유롭게 해주기 때문입니다. 20년이 지난 지금, 기계에게도 마찬가지이며 어쩌면 더 그렇습니다. 모델은 단지 어떤 결과가 가장 가능성이 높은지를 풀어내고 있을 뿐입니다...

Go 언어는 우연이든 의도든 이 원칙을 가장 잘 구현하고 있습니다. 수년 동안 Golang은 프로그래머들이 계속 요구하던 편의성과 고차원적인 표현(특히 제네릭, Generics)을 거부해 왔으며, 이러한 저항은 대중들에게 매우 인기가 없었습니다.

원문 보기
원문 보기 (영어)
Use boring languages with LLMs Apr 20, 2026 I'm Jacob - I run Sancho Studio a software consulting group, we help companies with technical leadership, strategy, and security. I keep coming back to this idea that consistency compounds. I’ve noticed it acutely as a consultant working on multiple different projects in the last two years. Large language models amplify inconsistent technology and quietly reinforce consistent ones . The languages and ecosystems that suffer from the most fragmentation produce the worst agentic output, and the ones with the strongest conventions produce the best. I think this effect will increasingly determine which tool survives in the paradigm of massive models trained on large corpuses. Even if code is cheap, running inference is a gamble. It’s impossible to know if at any moment the model will make a decision to install a package or produce a bizarre coding pattern from 2019. If we consider that we’re gambling with tokens, we should bet on the set of embeddings which represent strongly consistent and reinforced model weights to produce median output. For software development this is actually ideal as the median program is typically doing the basics: processing information, reading/writing files, responding to network requests, etc. Before AI, engineers complained about languages which reinvented themselves on what felt like an annual basis. These complaints were real but mostly aesthetic and symptomatic of a frustration that humans needed to maintain or keep up with needlessly changing ecosystems. If we look back, the 2024 State of JS survey describes a relatively fragmented ecosystem. For a human, fragmentation is annoying. For a model trained on the public corpus of all of it; fragmentation is something closer to a problem which needs to be solved in reinforcement learning or agent harnesses (e.g. Claude Code leaked and showed us that Anthropic hard-coded some bias for JavaScript frameworks). Python is the same story but sung in a different key. Asking a simple question like “which package manager are you using?” produces a matrix of language versions, package manager version, and OS compatibility which I find to be completely mind-numbing as a technical lead. Should one use pip, poetry, or uv? Does your toolchain matter, or are you cross-compiling? How do you know if a Python package silently has a link to a C dependency? Are you using async, or have you reached for a task queue instead? Django or FastAPI? From a model’s standpoint, there are simply too many ways to write any of this, and the corpus reflects every one of them in roughly equal weight unless recency bias is introduced at training time. What this means is straightforward to me and should be to you as well… Languages and ecosystems with low variance in their training corpus are represented better and executed more reliably by coding agents. In higher dimension vector spaces, cosine similarity in the training data is the substrate on which the model’s attention and MLP layers learn to predict the next token. A consistent corpus produces consistent inference tokens. This pattern holds in other languages. Inference on Rails projects produces more consistent output compared to generic JavaScript backends in agentic work, not because Ruby is a better language in some Platonic sense, but because there is essentially one Rails. There are at least a dozen production-grade JavaScript frameworks offering different Venn diagrams over the same default features. Convention over configuration was a win for human programmers because flexibility is overrated and constraints are liberating . Twenty years later the same is true for machines, and arguably more so. The model just is solving for which outcome is most likely… Go embodies this principle best and almost by accident. For years Golang resisted the conveniences and higher-level expressions that programmers kept demanding (generics in particular) and the resistance was deeply unpopular among working developers. I was one of those developers; I have written hundreds of thousands of lines of Go, and as a programmer I found the language often infuriating. I know some of the people on the Golang team and admire their headstrong commitment to the future of programs . I have come to think Google produced, mostly inadvertently, the best language in the world for this moment. Out of the box, Go gives an agent a set of advantages no other mainstream language provides in the same combination. The concurrency model is the first of these. Goroutines are a far more tractable primitive for coding agents than threads, callbacks, async/await, or any of the colored-function regimes that dominate elsewhere. They are simple, type-safe, and ubiquitously used in the corpus the model was trained on. There is no question of what color your function is, because the question does not exist. results := make ( chan string , len ( urls )) for _ , u := range urls { go func ( u string ) { resp , err := http . Get ( u ) if err != nil { results <- err . Error () return } defer resp . Body . Close () results <- resp . Status }( u ) } for range urls { fmt . Println ( <- results ) } The standard library is the second. net/http alone runs a non-trivial fraction of the internet’s microservices, and the cryptography packages (funded and maintained by Google) are world-class. I have shipped production systems at Zoom and Keybase that depended on these defaults and never once needed to reach outside them. package main import ( "fmt" "net/http" ) func main () { http . HandleFunc ( "/healthz" , func ( w http . ResponseWriter , r * http . Request ) { fmt . Fprintln ( w , "ok" ) }) http . ListenAndServe ( ":8080" , nil ) } The toolchain is the third, and possibly the most underrated. Go has, by design, one right way to do most things: gofmt , go vet , and now go fix enforce a single canonical style with no negotiation. The best possible combination for a language model is a consistent corpus to train on, plus one-right-way tooling for testing and feedback at runtime. gopls is a fantastic guardrail for agents because it provides real-time semantic feedback, and golangci-lint lets you statically enforce coding styles and primitives without having to prompt the agent into compliance. $ go vet ./... ./user.go:22:2: result of fmt.Errorf call not used ./user.go:38:9: declaration of "err" shadows declaration at line 34 $ golangci-lint run user.go:51:6: exported func LoadUser should have comment (revive) user.go:63:3: if block ends with return, drop this else (golint) The fourth advantage is performance with garbage collection. Language models are inconsistent in managing memory; this is a well-documented limitation, and not one that is going away soon. Rust enforces memory safety at the type and borrow-checking level, which is excellent for humans and a constant fight for agents. Writing C or C++ with a coding agent is harder still, because the training data is full of memory bugs, use-after-free errors, and decades of hard-won mistakes that the model is just as capable of reproducing as it is of avoiding. Go gives you native-like performance without asking the agent to manage memory directly. func parseLines ( r io . Reader ) [] string { var out [] string s := bufio . NewScanner ( r ) for s . Scan () { out = append ( out , s . Text ()) } return out } The fifth is the small, known set of footguns. nil pointers can be difficult to trace down in production stack traces for human engineers, given the right tools agents are surprisingly good at it. The set of things that can go wrong in idiomatic Go is bounded in a way that the set of things that can go wrong in, say, Python with arbitrary metaclasses simply is not. data , err := os . ReadFile ( path ) if err != nil { return fmt . Errorf ( "read config %q: %w" , path , err ) } Reading this, the right reaction is not “Go is the best language” but something more specific. This language and toolchain can write the majorit