Go fix를 사용해 Go 코드를 현대화하기
(go.dev)- Go 1.26 버전에서 완전히 새로 작성된
go fix명령어가 도입되어, 최신 언어 및 라이브러리 기능을 활용해 코드를 자동으로 개선할 수 있음 - 이 도구는 수십 개의 분석기(analyzer) 를 통해 코드 패턴을 탐지하고,
minmax,rangeint,stringscut등 다양한 모더나이저(modernizer) 를 적용해 반복적이거나 구식 코드를 최신 형태로 변환함 - 새 기능인
new(expr)지원을 위해newexpr분석기가 추가되어,newInt같은 헬퍼 함수를 자동으로 단순화할 수 있음 -
go fix는 여러 번 실행 시 시너지 효과를 내며, 서로 다른 분석기가 연속적으로 개선을 제안할 수 있고, 충돌 시 자동 병합 및 불필요한 import 제거 기능을 포함함 - Go 팀은 향후 ‘셀프서비스(Self-service)’ 분석 패러다임을 통해 개발자들이 자체 API용 모더나이저를 정의하고 배포할 수 있도록 확장할 계획임
go fix 명령어 개요
- Go 1.26에서
go fix가 완전히 새로 구현되어, 코드베이스를 최신 Go 스타일로 자동 변환하는 기능 제공-
go fix ./...명령으로 현재 디렉터리 이하 모든 패키지를 수정 -
-diff옵션으로 변경 사항 미리보기 가능
-
- 등록된 분석기 목록은
go tool fix help로 확인 가능하며,any,forvar,mapsloop,minmax등 다양한 변환 규칙 포함 - 특정 분석기만 실행하려면
-any같은 플래그 사용, 제외하려면-any=false지정 - 플랫폼별 코드 차이를 고려해
GOOS,GOARCH조합별로 여러 번 실행 가능
Modernizers — 코드 현대화 도구
- Go 1.18 이후 제네릭 도입으로 코드 단순화 가능성이 커짐
- 예:
maps.Keys로 map 키 수집,strings.Cut으로 문자열 분리
- 예:
- LLM 기반 코드 생성 도구들이 구식 패턴을 유지하는 문제를 해결하기 위해, 최신 Go 관용구를 반영한 오픈소스 코드 갱신 필요성 강조
-
go fix와gopls에 포함된 모더나이저는 코드 가독성과 학습 효과를 높임 - 예시 모더나이저:
-
minmax:
if문을min/max함수로 대체 -
rangeint: 3절
for문을range-over-int로 변환 -
stringscut:
strings.Index기반 코드를strings.Cut으로 단순화
-
minmax:
Go 1.26의 new(expr) 기능
-
new함수가 값 인자를 허용하도록 확장되어,new("go1.26")형태로 초기화 가능 -
newexpr분석기가newInt같은 헬퍼 함수를 찾아return new(x)로 단순화하고, 호출부를new(expr)로 교체 - 최소 Go 버전(예:
go 1.26지시문) 충족 시에만 적용 -
$ go fix -newexpr ./...명령으로 전체 코드베이스에 적용 가능 - 사용 후 불필요한 헬퍼 함수는
deadcode도구로 식별 가능
시너지 및 충돌 처리
- 한 번의 수정이 다른 수정 기회를 만드는 시너지 효과 존재
- 예:
minmax적용 후 추가 변환 제안 -
stringsbuilder→fmt.Fprintf로 이어지는 연속 최적화 가능
- 예:
-
go fix는 3-way merge 알고리듬으로 수정 충돌을 자동 병합- 구문 충돌 시 수정 건을 건너뛰고 경고 표시
- 의미적 충돌(예: 변수 제거, import 미사용)은 수동 조정 필요
- 불필요한 import는 자동 제거
Go 분석 프레임워크 통합
-
go vet과go fix가 공통 분석 프레임워크를 공유하도록 통합-
vet은 오류 탐지 중심,fix는 안전한 자동 수정 중심
-
- 분석기는
unitchecker,multichecker,gopls,staticcheck,Tricorder등 다양한 드라이버에서 실행 가능 -
팩트(fact) 시스템을 통해 패키지 간 정보 공유 가능
- 예:
log.Printf가fmt.Printf의 래퍼임을 추론
- 예:
-
gopls는 실시간 진단 및 자동 수정 제안 기능 제공
분석 인프라 개선
-
inspector 패키지 확장으로 AST 탐색 효율 향상,
Cursor타입으로 상하좌우 탐색 지원 - typeindex를 통한 함수 호출 인덱싱으로 분석 속도 최대 1000배 향상
- 추가 개선 사항:
- 표준 라이브러리 의존성 그래프 제공
- 파일별 Go 버전 쿼리 지원
- 리팩터링 프리미티브 확충으로 주석 처리 등 안전한 코드 수정 가능
- 일부 모더나이저는 미묘한 동작 변화로 제외됨 (
append([]string{}, slice...)→slices.Clone(slice)사례) - 향후 패턴 매칭 엔진, 자동 테스트 하네스, 정확한 수정 연산자 라이브러리 개발 예정
Self-service 패러다임
- Go 1.26부터 셀프서비스 분석 모델 도입 예고
- 개발자가 자체 API용 모더나이저를 정의하고 배포 가능
- 중앙화된 승인 절차 없이 프로젝트 수준에서 실행 가능
- 첫 단계로 주석 기반 인라이너(Annotation-driven inliner) 기능이 미리보기 형태로 포함
- 향후 계획:
-
동적 로딩을 통한 사용자 정의 분석기 실행 (
go fix또는gopls내) - 제어 흐름 기반 체크 일반화, 예: “open 후 close”, “lock 후 unlock” 등 불변 조건 검증
-
동적 로딩을 통한 사용자 정의 분석기 실행 (
- 목표는 유지보수 효율 향상과 최신 Go 기능의 빠른 도입 지원
Hacker News 의견들
-
2024년 말 LLM 코드 어시스턴트가 급속히 확산될 때, 이런 도구들이 학습 데이터의 기존 Go 코드 스타일을 그대로 재현하는 경향이 있다는 점이 흥미로웠음
최신 문법을 쓰라고 지시해도 무시하거나, 심지어 존재하지 않는다고 부정하는 경우도 있었음
앞으로의 모델이 최신 Go 1.25 관용구를 반영하려면, 오픈소스 코드 전체가 그 스타일로 업데이트되어야 함- PHP도 예전에 Stack Overflow의 낡은 조언(예: magic_quotes)을 정리하려는 시도가 있었음
하지만 LLM은 한 번 잘못된 데이터가 들어가면 수정이 거의 불가능함
모델이 어떤 근거로 결론을 내렸는지 추적하기 어렵고, 차기 모델에서 수정되길 바라는 수밖에 없음 - LLM이 생성하는 Go 동시성 코드는 특히 위험함
단순해 보여서 리뷰를 통과하지만, 실제로는 에러 처리와 엣지 케이스가 빠져 있음
리뷰 후 LLM에 다시 넣으면 겉보기엔 수정된 코드가 나오지만, 그 안에 데이터 레이스나 데드락이 생김
거의 모든 모델에서 반복되는 문제임 - 나도 이런 문제를 자주 겪었음
Go는 하위 호환성이 좋아서 컴파일은 되지만, 코드 스타일이 너무 달라짐
Python은 API 변경으로 실제 호환성 깨짐이 발생함
그래도 Go는 언어 안정성과 표준 라이브러리 덕분에 코드 생성용 언어로는 매우 훌륭함 - LLM 사용은 결국 획일적이고 평범한 코드를 양산하게 될 것임
- LLM으로 코드를 작성하는 발상 자체를 버려야 한다고 생각함
Rob Pike의 경고처럼, 이런 기술은 소프트웨어 생태계의 오염임
많은 사람들이 ‘편리함’이라는 슬롭(slop) 을 원하지만, 그게 문제의 본질임
- PHP도 예전에 Stack Overflow의 낡은 조언(예: magic_quotes)을 정리하려는 시도가 있었음
-
소스 코드를 자동으로 최신 스타일로 변환해주는 도구는 정말 멋짐
Java의 OpenRewrite가 대표적이지만, 다른 언어에는 비슷한 게 잘 떠오르지 않음
Go처럼 이런 기능이 언어에 내장되어 있다면 언어 성숙도가 크게 높아짐
앞으로 새 언어들이 Go의 이런 통합적 접근을 참고할 것 같음- C 언어에는 Coccinelle이 있고, 2009년 LWN 기사에서도 소개됨
JetBrains IDE들은 수백만 줄의 코드를 한 번에 리팩터링하거나 새 문법으로 자동 변환할 수 있음
ConvertToPrimaryConstructor 같은 기능도 있음
또 Structural Search and Replace는 단순 텍스트가 아닌 언어 구문 수준에서 작동함
.NET의 Roslyn 분석기도 IDE에서 코드 수정 제안을 제공함
튜토리얼 링크 - Rust의 clippy는 최신 문법을 권장하고 일부는 자동 수정도 가능함
덕분에 코드가 훨씬 깔끔해졌음 - Haskell의 hlint도 오래전부터 존재했음
concat과map을concatMap으로 바꾸거나, 불필요한if표현을 단순화해줌 - TypeScript 코드베이스를 이런 식으로 변환해본 사람 있는지 궁금함
LSP 서버는 기능이 부족하고, 인자 제거 같은 기본 리팩터링도 지원 안 함
jscodeshift나 Claude를 조합하면 가능할지 고민 중임
- C 언어에는 Coccinelle이 있고, 2009년 LWN 기사에서도 소개됨
-
이런 자동 수정 도구(go fix) 덕분에 Go는 정말 뛰어난 언어임
새 기능인 rangeint도 go fix로 자동 반영될 예정이라 기대됨
Go 팀에게 찬사를 보냄- 다른 언어를 쓰고 싶을 때도 많지만, Go의 빌드·테스트·린트 도구가 너무 좋아서 결국 Go를 쓰게 됨
컴파일 속도도 믿을 수 없을 만큼 빠름 - 예전엔 직접 정규식으로
for루프를 찾아 수정했는데, 이제는 이 도구가 훨씬 세련된 방식으로 처리해줌
- 다른 언어를 쓰고 싶을 때도 많지만, Go의 빌드·테스트·린트 도구가 너무 좋아서 결국 Go를 쓰게 됨
-
기사에는 언급되지 않았지만, 내가 가장 좋아하는 기능은
//go:fix inline지시어임
한 줄짜리 함수를 호출자에 인라인으로 삽입해줌
라이브러리 작성자가 구버전 함수를 새 버전으로 자연스럽게 자동 마이그레이션할 수 있게 해줌
semver가 달라져도go fix로 자동 업그레이드가 가능함 -
최근 본 Wes McKinney 팟캐스트에서
Go가 빠른 컴파일-실행 주기, 강한 타입 시스템, 멀티스레드 안정성 덕분에 코딩 에이전트에 이상적이라고 함
그 말 듣고 Go에 다시 흥미가 생김 -
Go의 확립된 도구 체계와 관습은 에이전트 기반 개발에 큰 도움이 됨
go run main.go로 개발 환경을 바로 띄우고, 여러 워크트리와 중앙 설정, 마이그레이션된 DB까지 지원함
housecat-inc/cheetah에서 이런 툴을 공유함
go generate,go build,go test,go vet까지 포함된 빠른 루프에go fix를 추가할 생각임- 초고속 컴파일 속도는 LLM 반복 실험에도 큰 이점임
-
Python을 배우다 보니 같은 일을 하는 방법이 너무 많고 일관성이 없음
C처럼 한 가지 방식만 있는 게 오히려 그립더라
Go는 이런 단계에 도달했는지 궁금함
LLM 도움 없이도 베스트 프랙티스를 따를 수 있는 언어인지 알고 싶음- Go는 꽤 의견이 분명한 언어라 대부분 한 가지 방식만 있음
복잡하지 않고 단순함을 유지하려는 노력이 인상적임
Python의 혼란스러움에 지친 사람에게 추천함
- Go는 꽤 의견이 분명한 언어라 대부분 한 가지 방식만 있음
-
Self-service analyzer 개념이 정말 흥미로움
대형 라이브러리나 인프라 팀에서 적극 활용될 것 같음 -
이런 도구가 있다면 하위 호환성 없는 언어도 가능하지 않을까 하는 생각이 듦
-
TypeScript 세계에서는 biome이 이런 역할을 함
예를 들어forEach대신for...of를 권장하고, ultracite와 함께 쓰면 워크플로우가 훨씬 매끄러워짐
agents.md파일에 “수정 후 biome fix 실행”을 명시해두었고, 덕분에 코드 품질이 자동으로 유지됨
eslint보다 훨씬 가볍고 효율적인 경험임