Posts for: #Book

Go 로 읽는 소프트웨어 설계의 철학 Part 8: 유행, 성능, 그리고 무엇이 중요한가

«A Philosophy of Software Design» 19~21장으로 8부작을 맺습니다. struct embedding 으로 흉내 낸 구현 상속에서 ‘덮어쓴’ 메서드가 부모의 호출에는 적용되지 않는 함정을 재현하고 인터페이스 합성으로 바꾸며, 저자의 TDD 비판을 공정하게 옮기고 반론을 덧붙입니다. 20장의 RAMCloud Buffer 예제를 Go 로 옮겨 ‘측정 먼저’ 를 실제로 해 본 결과는 책의 2배가 아니라 10% 였고, 그 숫자를 그대로 싣습니다. 마지막으로 시리즈 전체를 위험 신호 표 하나로 정리합니다.

[Read more]

Go 로 읽는 소프트웨어 설계의 철학 Part 7: 기존 코드 수정, 일관성, 명백함

«A Philosophy of Software Design» 16~18장을 Go 코드로 읽습니다. 경로 탈출 버그를 Save 한 곳에만 땜질해서 Load 와 Delete 에 구멍을 남기는 수정과, ID 타입을 도입해 세 함수를 한 번에 막는 수정을 비교하고, ‘없음’ 을 네 가지 방식으로 표현하는 저장소를 한 가지로 통일하며, 제네릭 Pair 의 First·Second 를 이름 있는 struct 로 바꾸고, 이벤트 버스 뒤에 숨은 호출 흐름을 직선으로 펴는 과정을 Before → After 로 보여 줍니다. gofmt 가 이 책의 17장을 언어 차원에서 구현한 것이라는 이야기도 합니다.

[Read more]

Go 로 읽는 소프트웨어 설계의 철학 Part 6: 주석과 이름

«A Philosophy of Software Design» 12~15장을 Go 코드로 읽습니다. 이름을 그대로 풀어 쓴 주석을 단위와 경계 조건을 채운 주석으로 바꾸고, 저자가 6개월을 쏟은 Sprite 의 block 버그를 Go 의 named type 으로 컴파일 시점에 잡아내며, 14.5절에서 저자가 Go 스타일 가이드와 갈라서는 지점을 양쪽 논거 그대로 싣습니다. 여섯 줄짜리 주석 없이는 설명할 수 없던 Search 함수를 주석부터 다시 써서 설계를 고치는 과정도 보여 줍니다.

[Read more]

Go 로 읽는 소프트웨어 설계의 철학 Part 5: 에러의 존재를 없애라

«A Philosophy of Software Design» 10장을 Go 코드로 읽습니다. 예외가 없는 Go 에서 이 장은 ‘if err != nil 을 어떻게 줄이는가’ 라는 질문이 됩니다. 없는 노트를 지우면 에러를 내던 Delete 를 ‘없는 상태로 만든다’ 로 재정의해 시그니처에서 error 를 지우고, 범위 밖이면 패닉하던 Snippet 을 항상 정의되는 함수로 바꾸고, 짧은 읽기를 io.ReadFull 로 감추고, 핸들러마다 반복되던 http.Error 를 한 곳으로 모으고, nil 검사를 부르던 ‘선택 없음’ 을 빈 선택으로 없앱니다. 저자가 Tcl 의 unset 을 설계 실수로 꼽는 이유와, 이 원칙을 지나치게 밀면 어떻게 되는지도 함께 봅니다.

[Read more]

Go 로 읽는 소프트웨어 설계의 철학 Part 4: 합칠 것인가 나눌 것인가, 그리고 두 번 설계하기

«A Philosophy of Software Design» 9장과 11장을 Go 코드로 읽습니다. ‘20줄 넘으면 나눠라’ 를 따르느라 공유 상태 구조체를 주고받는 함수 다섯 개가 된 임포트 코드를 하나로 되돌리고, 텍스트 타입 안에 커서 undo 까지 끌어안은 설계를 범용 History 와 특수 목적 Action 으로 갈라내며, 줄 단위·범위 단위 두 인터페이스를 둘 다 Go 로 써 보고 상위 코드의 길이로 판정합니다. 책이 Clean Code 와 갈라서는 지점도 이 장에 있습니다.

[Read more]

Go 로 읽는 소프트웨어 설계의 철학 Part 3: 범용성, 계층, 그리고 복잡성 끌어내리기

«A Philosophy of Software Design» 6~8장을 Go 코드로 읽습니다. 백스페이스 키마다 메서드를 하나씩 둔 텍스트 타입을 Insert 와 Delete 두 개로 줄이면서 여러 줄 선택 삭제가 공짜로 생기는 과정, 다섯 메서드 중 넷이 저장소를 그대로 부르는 ‘서비스 계층’ 을 걷어내는 과정, 함수 네 단계를 관통하는 pass-through 인자를 App 구조체로 거두는 과정, 그리고 재시도 간격을 설정값으로 떠넘기는 대신 응답 시간을 재서 스스로 정하게 만드는 과정을 Before → After 로 보여 줍니다. Go 의 context.Context 가 책의 context 와 다른 이유도 짚습니다.

[Read more]

Go 로 읽는 소프트웨어 설계의 철학 Part 2: 깊은 모듈과 정보 은닉

«A Philosophy of Software Design» 4~5장을 Go 코드로 읽습니다. 객체 세 개를 조립해야 노트 하나를 읽을 수 있는 얕은 모듈을 함수 하나짜리 깊은 모듈로 바꾸고, 내부 map 을 그대로 돌려주는 인터페이스가 어떻게 정보를 누출하는지, ‘읽고 나서 파싱한다’ 는 시간적 분해가 왜 같은 지식을 두 곳에 복사하게 만드는지를 실제로 컴파일되는 Before → After 로 보여 줍니다. go doc 출력 줄 수로 인터페이스 크기를 세는 방법도 소개합니다.

[Read more]

Go 로 읽는 소프트웨어 설계의 철학 Part 1: 복잡성은 어디서 오는가

존 오스터하우트의 «A Philosophy of Software Design» 을 Go 코드로 다시 읽는 8부작의 첫 편입니다. 책이 말하는 복잡성의 세 가지 증상(변경 증폭·인지 부하·모름)과 두 가지 원인(의존성·모호성)을 각각 Before → After Go 코드로 보여 주고, 전술적 땜질이 어떻게 쌓이는지와 전략적 수정이 무엇을 다르게 하는지를 같은 버그 하나로 비교합니다. Go 가 이 책과 어디서 부딪히는지도 미리 밝혀 둡니다.

[Read more]

Go 로 읽는 SICP Part 14: 한 줄이 만드는 상수 공간, 그리고 4.2배 빠른 컴파일러

시리즈 마지막입니다. 명시적 제어 평가기를 만들어 꼬리 재귀의 스택 깊이를 n 과 무관하게 10 으로 고정했고, 그 한 줄을 빠뜨렸다가 다시 찾은 과정을 실측과 함께 실었습니다. 컴파일러는 어휘 주소 지정으로 해석 대비 4.2배 빨라졌습니다. 마지막에 네 가지 실행 방식을 속도와 공간 두 축에 올리고, Go 로 SICP 를 읽은 것에 대한 최종 판정을 정리했습니다.

[Read more]

Go 로 읽는 SICP Part 13: 기계 수준에서 갚는 꼬리 호출의 빚

SICP 5장은 레지스터와 명령어만 있는 기계를 만들고 그 위에서 재귀를 구현합니다. 시리즈 내내 세 번 미룬 질문 — 꼬리 재귀는 왜 상수 공간인가 — 이 여기서 push 횟수 0 으로 자명해집니다. stop-and-copy 가비지 컬렉터도 만들었고, 만드는 도중 루트 집합을 빠뜨려 리스트가 깨진 사고를 그대로 실었습니다.

[Read more]

Go 로 읽는 SICP Part 12: call/cc 없이 시간 되감기 — amb 평가기

SICP 4.3 은 백트래킹을 언어 기능으로 만듭니다. amb 로 아무 값이나 고르고, 모순이 나오면 시간을 되감습니다. Part 1 에서 예고한 세 번째 마찰인 일급 연속의 부재는 실제로는 문제가 되지 않았고, 대신 다른 곳에서 값을 치렀습니다. 다세대 주택 퍼즐을 원서와 같은 답으로 풀었고 호스트 스택 132 MB 를 썼습니다.

[Read more]

Go 로 읽는 SICP Part 11: 1.74배 빨라지고 꼬리 호출을 잃다

SICP 4.1.7 은 구문 분석을 실행에서 분리해 평가기를 빠르게 만듭니다. 실제로 재 보니 1.74배 빨라지고 할당이 절반으로 줄었습니다. 그런데 같이 재 보니 꼬리 호출 최적화가 사라졌습니다. 스택이 0.62 MB 에서 512 MB 로 늘었습니다. 후반부는 게으른 평가기입니다. 세 군데만 고치면 스트림이 언어 기능으로 공짜로 딸려 옵니다.

[Read more]

Go 로 읽는 SICP Part 10: Go 로 Scheme 인터프리터 만들기 — 파서라는 청구서

SICP 4장은 Scheme 으로 Scheme 인터프리터를 씁니다. eval 과 apply 가 서로를 부르는 30줄 남짓으로 언어 하나가 정의됩니다. Go 로 옮기면 렉서와 파서를 손으로 만들어야 하고, 그것이 Part 1 에서 예고한 마찰의 청구서입니다. 실제로 돌아가는 인터프리터를 만들어 factorial(20)과 3장의 계좌 객체까지 우리 언어 위에서 실행했습니다.

[Read more]

Go 로 읽는 SICP Part 9: 채널은 SICP 스트림이 아니다

SICP 3.4 는 동시성을 사고 실험으로 다루는데, Go 에서는 실제로 돌릴 수 있습니다. 1000번의 인출 중 900건 가까이가 사라지는 것을 재현했고 go test -race 로 잡았습니다. 3.5 스트림은 반대입니다. 고루틴과 채널이 SICP 스트림처럼 보이지만 결정적으로 다릅니다. 스트림·채널·iter.Seq 를 같은 실험에 올려 세 가지가 어떻게 갈리는지 확인했습니다.

[Read more]

Go 로 읽는 SICP Part 8: 같은 pair 세 개를 세는 네 가지 답, 그리고 이벤트 루프

가변 데이터를 도입하면 공유가 생기고, 공유가 생기면 pair 세 개를 세는 답이 3·4·7·무한대로 갈립니다. SICP 연습문제 3.16 을 Go 로 재현하고, 같은 함정이 Go 슬라이스 append 에서 어떻게 나타나는지 실측했습니다. 후반부는 디지털 회로 시뮬레이터입니다. 원서와 같은 시각(8·11·16)을 재현했고 전가산기 진리표를 전수 검증했습니다.

[Read more]

Go 로 읽는 SICP Part 7: 시간이 들어오면 치환 모델이 무너진다

SICP 3장은 대입을 도입하고 그 대가를 절 하나를 써서 계산합니다. 치환 모델이 폐기되고 환경 모델이 들어옵니다. Go 클로저가 실제로 힙에 프레임을 만드는 것을 탈출 분석으로 확인했고, Go 1.22 의 루프 변수 변경이 정확히 이 프레임 이야기라는 것을 두 시맨틱을 나란히 실행해 보였습니다. 몬테카를로 예제에서는 난수 생성기 결함으로 π 가 2.72 로 나온 사고를 그대로 실었습니다.

[Read more]

Go 로 읽는 SICP Part 6: 1985년에 쓰인 Go 인터페이스 설계 지침

SICP 2.4~2.5 의 데이터 지향 프로그래밍은 Go 인터페이스 설계 이야기와 거의 같은 내용입니다. 명시적 디스패치·디스패치 테이블·메시지 패싱 세 가지를 Go 로 모두 구현하고, Go 인터페이스가 그중 어느 것인지 판정했습니다. 표현 문제(expression problem)는 컴파일 에러로 직접 확인했고, 제네릭 산술 타워에서 i×i 가 정수 -1 까지 내려오는 것도 재현했습니다.

[Read more]

Go 로 읽는 SICP Part 5: 파이프라인으로서의 프로그램, 그리고 quote 가 없는 언어

map, filter, accumulate 만으로 프로그램을 신호 처리 파이프라인처럼 조립하는 SICP 2.2.3 을 Go 로 옮기고, Go 1.23 의 iter.Seq 판과 나란히 놓았습니다. 여덟 퀸은 92개 해를 재현했습니다. 후반부는 기호 데이터입니다. Go 에 quote 가 없다는 사실이 여기서 처음으로 아프게 다가옵니다.

[Read more]

Go 로 읽는 SICP Part 4: 데이터란 무엇인가 — 저장 공간 없는 pair

SICP 2장은 데이터가 무엇인지 다시 묻습니다. 답은 “약속을 지키는 것이면 무엇이든 데이터"이고, 증거로 저장 공간이 전혀 없는 pair 를 클로저만으로 만들어 보입니다. Go 로 그대로 옮겨 봤고, 제네릭으로 옮기려다 Go 타입 시스템의 천장에 정확히 부딪혔습니다. 컴파일 에러를 그대로 실었습니다.

[Read more]

Go 로 읽는 SICP Part 3: 함수를 돌려주는 함수, 그리고 제네릭이 필요해지는 순간

SICP 1.3 은 프로시저를 인자로 넘기고 반환값으로 돌려주는 방법을 다룹니다. Go 로 옮기면 원서에 없던 것이 하나 생깁니다. 타입 시그니처입니다. 추상화의 계약이 눈에 보이고, 대신 코드가 길어집니다. 뉴턴법을 고정점 탐색으로 분해하고, 연습문제 1.45 의 평균 감쇠 횟수를 n=2부터 32까지 전부 측정했습니다.

[Read more]