ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 린트라는 이름은 건조기 필터에서 왔다 — 1978년 C 코드 검사기가 디자인 토큰까지 온 길
    IT 2026. 8. 10. 21:00
    린트라는 이름은 건조기 필터에서 왔다 — 1978년 C 코드 검사기가 디자인 토큰까지 온 길

    파이썬 파일을 고치면 ruff가 돌고, 셸 스크립트를 고치면 shellcheck가 붙는다. 통과하지 못하면 커밋 자체가 막히도록 걸어 뒀으니 하루에도 몇 번씩 마주치는 이름들이다. 그런데 얼마 전 문득 멈칫했다. "린트(lint)"가 대체 무슨 뜻이지? 몇 년을 쓰면서 한 번도 궁금해한 적이 없었다. 그냥 "코드 검사기를 부르는 말"로 통째로 외워 쓰고 있었던 것이다.

    찾아보니 출처가 아주 분명했다. 1978년 7월 26일, 벨 연구소의 Stephen C. Johnson이 만든 프로그램 이름이 그대로 `lint`였다. 같은 해 10월 25일에는 「Lint, a C Program Checker」라는 기술 보고서(Bell Labs CSTR 78–1273)로 정리됐고, 이듬해 유닉스 V7에 실려 연구소 밖으로 나왔다. 48년 전 도구 하나의 이름이 지금 내 터미널까지 살아남은 셈이다. 더 재미있는 건 그 이름이 건조기 먼지 필터에서 왔다는 것, 그리고 그 비유 안에 "린터가 무엇을 하는 도구인가"에 대한 정의가 이미 다 들어 있다는 점이었다.

    건조기 필터에서 온 이름

    영어 단어 lint는 원래 옷감에서 떨어져 나오는 보풀이다. 어원을 더 거슬러 가면 라틴어 līnum(아마), 고대 프랑스어 linette를 거쳐 온 말로, 1400년경에는 상처를 덮는 데 쓰던 아마 부스러기를 뜻했고 1610년대부터 목화 보풀을 가리키게 됐다.

    Johnson이 이 단어를 고른 이유가 기록에 남아 있다. 그가 만든 명령어가 건조기의 보풀 필터(lint trap)처럼 작동하기를 바랐다는 것이다. 건조기를 돌리고 나면 필터에 먼지 뭉치가 잔뜩 붙어 있다. 그런데 정작 옷은 멀쩡하다. 필터는 옷감을 건드리지 않고 떨어져 나온 부스러기만 걷어낸다.

    이게 린터의 정의 그대로다. 프로그램은 멀쩡히 돌아간다. 다만 표면에 미심쩍은 것들이 붙어 있고, 그것만 골라 보여준다. 컴파일러처럼 "이건 틀렸으니 못 만든다"고 막아서는 게 아니라, 완성된 결과물은 그대로 둔 채 부스러기를 모아서 내민다. 이름 한 단어가 도구의 역할을 이렇게 정확히 요약하는 경우는 흔치 않다.

    왜 컴파일러가 아니라 별도 도구였나

    Johnson이 이 도구를 만든 계기도 구체적으로 남아 있다. 그는 당시 C 언어용 yacc(문법 정의를 주면 파서 코드를 자동 생성해 주는 도구) 문법을 디버깅하고 있었고, 동시에 유닉스를 32비트 기계로 이식하면서 생기는 호환성 문제와 씨름하고 있었다. 어떤 기계에서는 잘 돌던 코드가 다른 기계에서 깨지는데, 컴파일러는 아무 말도 해주지 않았다.

    당시 C가 그럴 수밖에 없었던 구조적 이유가 있다. 그 시절 C에는 함수 프로토타입(함수가 어떤 타입의 인자를 몇 개 받는지 미리 선언해 두는 문법)이 없었다. 게다가 파일을 따로따로 컴파일하니, 컴파일러는 다른 파일에 있는 함수가 무엇을 받는지 알 방법이 아예 없었다.

    /* a.c — 정수 두 개를 받는 함수 */
    int add(x, y)
    int x, y;
    {
        return x + y;
    }
    
    /* b.c — 다른 파일에서 호출한다 */
    int main(void)
    {
        /* 인자 개수도 틀렸고(2개 아닌 1개), 타입도 틀렸다(int 아닌 문자열).
           그런데 컴파일러는 a.c를 안 보고 b.c만 컴파일하므로
           add가 무엇을 받는 함수인지 모른다 → 조용히 통과시킨다.
           실행하면 그때서야 엉뚱한 값이 나오거나 죽는다. */
        add("hello");
        return 0;
    }
    

    코드 설명. 이 예시가 보여주는 건 "컴파일이 된다"와 "올바르다"가 전혀 다른 말이라는 사실이다. 두 파일을 각각 컴파일할 때 컴파일러의 시야는 그 파일 하나뿐이라, 다른 파일의 함수를 잘못 부르는 것을 잡아낼 방법이 원리적으로 없다. 여기서 핵심 설계 판단이 나온다 — 여러 파일을 한꺼번에 훑어보는 일은 컴파일러가 아니라 별도 도구가 맡는다. 컴파일러는 "이 파일 하나를 기계어로 바꾸는" 자기 일에 집중하고, 파일 경계를 넘는 정합성 검사는 lint가 가져간 것이다. 이 분업이 린터라는 도구 범주가 태어난 순간이다. lint는 이 외에도 안 쓰는 변수, 도달할 수 없는 코드, 반환값을 무시하는 호출, 기계마다 결과가 달라지는 코드 같은 것들을 함께 봤다.

    컴파일러가 따라잡았는데 린터는 왜 안 사라졌나

    여기서 이야기가 흥미로워진다. 시간이 지나며 컴파일러가 똑똑해졌다. ANSI C가 함수 프로토타입을 도입하면서 위의 예시 같은 건 컴파일러가 직접 잡게 됐고, gcc -Wall 같은 경고 옵션이 lint가 하던 일을 상당 부분 흡수했다.

    상식적으로는 lint의 존재 이유가 사라져야 했다. 그런데 정반대의 일이 벌어졌다. 린터는 사라지기는커녕 언어마다 하나씩 생겼고, 검사하는 대상은 점점 넓어졌다. 왜 그랬을까.

    답은 lint가 처음부터 진짜로 나눠 가진 것이 검사 항목이 아니라 기준을 정하는 권한이었기 때문이다. 컴파일러가 쓰는 기준은 언어 명세가 정한다. 내가 바꿀 수 없고 바꿔서도 안 된다. 반면 린터가 쓰는 기준은 사람이 정한다. 우리 팀이, 우리 프로젝트가 "이건 하지 말자"고 합의한 내용이 그대로 규칙이 된다.

    diagram

    다이어그램 설명. 코드 한 줄이 두 개의 관문을 차례로 지난다는 그림이다. 첫 관문인 "언어 문법에 맞는가"에서 걸리면 컴파일러가 막고, 통과하면 두 번째 관문인 "우리가 정한 규칙에 맞는가"로 넘어가 여기서 걸리면 린터가 막는다. 두 관문의 결정적 차이는 기준을 누가 소유하느냐다. 파란색으로 칠한 컴파일러 쪽 기준은 언어 명세가 정하므로 개발자가 손댈 수 없다. 노란색으로 칠한 린터 쪽 기준은 사람이 정하므로 프로젝트마다 다르고, 언제든 바뀌고, 무엇이든 담을 수 있다. 바로 이 유연성이 린터가 컴파일러에 흡수되지 않고 따로 살아남은 이유다. 놓치기 쉬운 함정은 두 관문의 위상을 같게 보는 것이다 — 린터 경고는 "틀렸다"가 아니라 "우리 약속과 다르다"는 뜻이라, 약속을 바꾸면 경고도 사라진다. 컴파일 오류에는 없는 성질이다.

    기준이 사람 것이라, 검사 대상이 계속 넓어졌다

    기준을 사람이 쥐고 있으니 "무엇을 검사할 것인가"에 원리적 한계가 없다. 컴파일러가 못 잡고 실행해도 안 터지는데 우리 기준에는 어긋나는 것 — 이 조건만 만족하면 무엇이든 린트 대상이 될 수 있다. 실제로 지난 48년 동안 대상이 이렇게 넓어졌다.

    diagram

    다이어그램 설명. 가운데 있는 "컴파일은 되지만 우리는 허용하지 않는다"라는 한 문장이 네 갈래로 뻗어 나가는 그림이다. 네 갈래는 서로 다른 검사 항목처럼 보이지만 전부 같은 조건을 만족한다 — 기계는 문제 삼지 않는데 사람이 문제 삼는다는 조건이다. 1978년의 이식성 검사와 오늘날의 디자인 토큰 검사 사이에 기술적 연속성은 거의 없지만, 정의상으로는 완전히 같은 도구다. 여기서 읽을 결론은 린트가 "코드 검사 기술"이 아니라 "사람이 정한 약속을 기계에 위임하는 방식"이라는 것이다. 그래서 약속을 적을 수 있는 영역이 새로 생길 때마다 린터가 하나씩 따라 생긴다.

    그래서 '디자인 린트'라는 말이 성립한다

    최근 프론트엔드 쪽에서 "디자인 린트"라는 말을 쓴다. 처음 들으면 어색하다. 디자인은 주관의 영역인데 그걸 어떻게 기계가 검사한다는 걸까. 그런데 위의 정의를 대입하면 딱 맞아떨어진다.

    // 디자인 시스템 규칙을 검사하는 정적 분석기 규칙 (핵심만 발췌)
    module.exports = {
      create(context) {
        return {
          Literal(node) {
            if (typeof node.value !== 'string') return;
    
            // #0052CC 같은 색상값을 코드에 직접 박았는가?
            // 문법 오류가 아니다. 실행해도 안 터진다. 화면도 잘 나온다.
            // 다만 "승인된 디자인 토큰만 쓴다"는 우리 약속에 어긋날 뿐이다.
            if (/^#[0-9a-f]{3,8}$/i.test(node.value)) {
              context.report({
                node,
                message: '하드코딩 색상입니다. 디자인 토큰을 사용하세요.',
              });
            }
          },
        };
      },
    };
    

    코드 설명. 이 규칙이 잡아내는 #0052CC어느 모로 보나 정상 코드다. 문법에 맞고, 컴파일되고, 실행하면 파란색이 제대로 칠해진다. 그런데도 경고를 띄우는 이유는 단 하나 — 팀이 "색은 승인된 토큰 목록에서만 고른다"고 합의했기 때문이다. 1978년의 lint가 "이 코드는 다른 기계에서 깨질 수 있다"고 알려준 것과 구조가 정확히 같다. 둘 다 컴파일러 소관이 아니고, 둘 다 사람이 정한 기준이며, 둘 다 결과물을 막지 않고 부스러기만 걷어낸다. 다만 하나 덧붙일 점이 있다. 이 규칙은 "목록 안에서 골랐는가"만 볼 뿐 "이 자리에 맞는 색을 골랐는가"는 보지 않는다. 브랜드 파랑이 들어갈 자리에 경고 빨강 토큰을 써도 조용히 통과한다. 린트가 검사하는 것은 언제나 형식이지 의미가 아니라는 점은, 48년 전이나 지금이나 변하지 않았다.

    정리 — 린터는 기술이 아니라 위임의 형식이다

    정리하면 이렇다. 린트는 1978년 벨 연구소에서 유닉스를 32비트로 옮기다 생긴 호환성 문제를 잡으려고 태어났고, 건조기 필터라는 비유에서 이름을 얻었다. 옷은 그대로 두고 보풀만 걷어낸다는 그 비유가 도구의 성격을 정확히 규정한다.

    컴파일러가 발전하며 lint의 원래 일감은 대부분 흡수됐는데도 린터라는 범주가 살아남은 이유는, 그것이 특정 검사 기술이 아니라 "기준을 사람이 쥔다"는 구조였기 때문이다. 컴파일러의 기준은 언어 명세가 정해 바꿀 수 없지만, 린터의 기준은 우리가 정하고 우리가 바꾼다. 그래서 검사 대상이 이식성에서 버그 패턴으로, 팀 관례로, 마침내 디자인 토큰까지 넓어질 수 있었다.

    이 관점이 실무에서 주는 이득이 하나 있다. 새로 자동화하고 싶은 규칙이 생겼을 때 "이게 린트로 잡힐까?"를 판단하는 기준이 명확해진다는 것이다. 물어볼 것은 딱 두 가지다 — 기계가 확인할 수 있을 만큼 규칙을 명시적으로 적을 수 있는가, 그리고 그 규칙이 언어가 아니라 우리가 정한 것인가. 둘 다 예라면 린터로 만들 수 있다. 반대로 "이 색이 이 자리에 어울리는가"처럼 규칙으로 적을 수 없는 것은, 도구를 아무리 정교하게 만들어도 린트로는 잡히지 않는다. 48년 된 이름이 알려주는 경계선은 여기까지다 — 보풀은 걷어내지만, 옷이 예쁜지는 봐주지 않는다.


    참고한 공개 자료:

    • Lint (software) — 최초 릴리스 1978-07-26, 이름 유래와 개발 배경 — https://en.wikipedia.org/wiki/Lint_(software)
    • S. C. Johnson, 「Lint, a C Program Checker」 (Bell Labs Computer Science Technical Report 78–1273, 1978-10-25) — https://www.semanticscholar.org/paper/Lint,-a-C-Program-Checker-Johnson-Hill/74617cffa3c6438d04aa99bef1cca415de47d0d3
    • Stephen C. Johnson — yacc 등 유닉스 도구 개발 이력 — https://en.wikipedia.org/wiki/Stephen_C._Johnson
    • lint 어원 (Online Etymology Dictionary) — 라틴어 līnum에서 온 경로와 시기별 의미 변화 — https://www.etymonline.com/word/lint
    • Lint (material) — 섬유 보풀로서의 lint — https://en.wikipedia.org/wiki/Lint_(material)

    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.