블로그에 책에 대한 회고를 포스팅하는 것은 처음입니다.
내용이 부실하거나 구조가 이러면 어떨까? 하는 의견을 내주시면 감사하겠습니다.
(여기는 왜 이렇게 생각했어? 같은 질문도 좋아요)
소개할 책의 뒤를 보면 이렇게 적혀있습니다.
이 책의 목적은 여러분의 '왜'를 해결하는 것이다.
프로그래밍 언어가 가지고 있는 다양한 개념이 '왜' 존재하고 있는지를 설명합니다.
많은 프로그래밍 언어에서 공통적으로 사용하고 있는 개념도 있지만,
일부 언어만 채용하고 있는 개념도 많습니다.
그럼 이런 개념들은 '왜' 탄생한 것일까?
이 책의 목적은 그 이유를 알아내는 것입니다.
"책의 주제는 근본" 이구나 라고 느꼈습니다.
이 책은 "왜??" 라는것에 너무 많다보니 "왜?" 읽었는지 부터 말해야할것 같습니다.
왜 읽었는가? 누군가의 강력 추천...
우테코에 들어온지 얼마 안됐을때 누군가 독서에 대해서 주제를 꺼낸적이 있습니다.
지금은 데이터가 날아가서 누가 얘기를 꺼낸건지도 기억이 안나요.. 누구신지 모르지만 감사합니다.
그곳에서 누군가 '코딩을 지탱하는 기술' 을 강력하게 추천했습니다. 읽고 정리까지 하면 굉장한 도움이 된다는 말을 듣고 홀린듯이 결제했습니다.
주문한 책은 굉장히 빠르게 다음날 저녁에 받을 수 있었고 우테코를 진행하느라 "너무 바빠서" 3주간 읽지 못했습니다.
일단 이 글을 포스팅 하는 오늘 드디어 다 읽었었습니다!
근본을 학습한다.
책의 서두에는 학습의 중요 포인트에 대해 3가지가 있다고 말합니다.
- 비교를 통한 학습
- 다수의 언어를 비교하는 것
- 무엇이 그 언어만이 가진 특유의 개념인가?
- 무엇이 언어간에 공통으로 사용되는 개념인가
- 역사를 통한 학습
- 언어의 발달 과정을 따라가는 것
- 어떻게 탄생했는가
- 어떤식으로 변화했는가
- -> 위의 과정을 통해 '왜 이런 식으로 동작하고 있는지' 에 대한 의문을 풀 수 있다.
- 만드는 것을 통한 학습'
- 직접 언어를 만드는 것 -> '나라면 어떻게 만들까?'
- 이를 통해 언어 설계자의 의도를 쉽게 이해할 수 있게 된다.
- 실제로 만들어보면 자신이 이해하지 못한 것을 알 수 있게 된다.
'코딩을 지탱하는 기술'의 목적은 다양한 책을 읽으면서 가졌던 '왜?' 라는 의문을 해결하는 것입니다.
그리고 이를 위해 이 책에서는 '비교를 통한 학습'과 '역사를 통한 학습' 2가지 방법을 사용합니다.
아래 부터는 각 장의 주제를 간단하게 정리해보겠습니다.
1장 효율적으로 언어 배우기
많은 언어에서 공통적으로 사용되는 개념 -> "중요한 지식"
이 지식을 습득하면 다른 언어도 쉽게 배울 수 있다!
비교를 통한 배움
- 규칙은 언어마다 다르다.
- C언어 -> '0이 거짓이고, 그 이외의 값은 참이다'
- Ruby -> 'false와 nil이 거짓이고, 0을 초함한 그 이외의 값은 참이다'
- Java -> 'boolean 이라는 참거짓을 위한 타입을 가지고 true가 참 false가 거짓이다'
- '다른 언어에서는 참거짓이 어떻게 정의되는지 알고 싶다' 같은 명확한 목적이 생기면 학습 효율이 오른다.
역사를 통한 배움
- 언어 설계자의 의도를 파악하자
- 프로그래밍 언어도 사람이 만든 것입니다.
- 언어 설계자는 어떤 문제를 해결하기 위해 그 언어 또는 기능을 만든 것일까? 어떤 흐름을 따라 만들어 졌는가? 에 대해 알게되면 그 기능이 왜 필요한지 납득할 수 있게 됩니다.
- 어떤 언어를 배워야 한다는 아무도 모른다.
- 무슨 언어가 좋아서 배워야 한다는 없다.
- 언어에 의존하지 않고 언어가 바뀌어도 통용할 수 있는 이해력을 길러야한다.
2장 프로그래밍 언어를 조감하다
프로그래밍 언어 탄생의 목적
나태 - 프로그래머의 3대 미덕
Perl의 설계자 Larry Wall은 프로그래머가 가질 3가지 자질로서 '나태, 조바심, 자만심'을 제안했습니다.
나태 (Laziness)
전체 에너지 소비를 줄이기 위해 대부분의 능력을 집중하는 기질. 이렇게 노동력을 줄이기 위해 만든 프로그램은 다른 사람들도 사용하게 되며, 그 프로그램에 관한 질문에 일일이 답하는 수고를 덜기 위해 문서를 만들게 된다. 이는 프로그래머에게 있어 가장 중요한 자질이기도 하며, 이 책이 존해하는 이유이기도 하다. 조바심과 자만심에 관해서도 참조할 것.
프로그래밍 언어는 프로그램을 쉽게 짜기위해 만들어졌습니다.
그렇지 않으면 우리는 천공카드를 뚫고 있었을 거에요
어느 회사의 천공카드가 더 잘 뚫어지나, 컴퓨터가 잘 읽는가를 비교하고 있었겠네요.
언어에 따라 다른 '편리함'의 의미
- 프로그래밍 언어의 목적은 '편리함' 이라고 말했지만, 그럼 세상에는 왜 수많은 언어가 존재하는 것일까?
- -> 어떤 '편리함'을 목표로 했는지, 언어 설계자의 의도를 파악해야한다.
- C++은 빠른 실행속도를 중시
- Scheme은 언어 사양을 쉽게 파악할 수 있도록 중점을 두어 초기에는 사양서 전체가 50페이지 밖에 되지 않았다.
- Python은 다른 사람이 쓴 코드를 쉽게 해석하는데 중점
- PHP는 웹 서비스를 쉽게 만들 수 있도록 해준다.
- 위처럼 프로그래밍 언어는 사람을 편리하게 하기 위해 만들었지만 무엇이 편리한지는 언어마다 다릅니다.
누군가 좋다고 그 언어를 사용하는게 아닌, 좋은 언어를 현명하게 선택하여 적재적소에 사용하는게 중요합니다.
3장. 문법의 탄생
문법이란 무엇을 편하게 하기위해 만들어졌는가?
문법이란 언어 설계자가 만든 규칙
- '1 더하기 2 에 3을 곱한다' 라는 똑같은 처리도, 언어에 따라 표현방법이 틀립니다.
- 문법이란 '어떤 문자열을 쓰면 어떤 구문 트리가 생기는가' 라는 규칙입니다.
문법 설계와 구문 분석기 구현은 프로그래밍 언어의 외관을 결정하는 중요한 요소입니다.
- 언어 설계자는 문법을 설계할 때 무엇을 쉽게 쓸 수 있도록 할것인지
- 어떤 실수를 줄이도록 할 것인지 등
- 프로그래밍 언어가 사용자에게 어떤 가치를 줄 수 있을지
4장. 흐름의 제어
if 이나 while문은 왜 생겨난걸까?
아래는 c 언어로 작성된 코드로 goto문을 사용한 코드와 if문을 사용한 코드를 작성한것입니다.
goto 문을 사용한 코드
void not_use_if(int x) {
if(x <= 0) goto NOT_POSITIVE;
printf("플러스 숫자\n");
goto END;
NOT_POSITIVE:
if(x >= 0) goto NOT_NEGATIVE;
printf("마이너스 숫자\n");
goto END;
NOT_NEGATIVE:
printf("제로\n");
END;
if 문을 사용한 코드
void use_if(int x) {
if(x > 0) {
printf("플러스 숫자\n");
} else if(x < 0) {
printf("마이너스 숫자\n");
} else {
printf("제로\n");
}
}
if 문을 사용한 코드는 훨씬 이해하기 좋다는 장점이 있습니다.
while문의 장점
- 반복문도 goto를 이용해서 작성할 수 있지만 while문을 활용하면 훨씬 쉽게 반복문을 읽을 수 있습니다.
- while문이 가져온 편리함은 '새로운 것' 이 아니라 '읽기 쉽게, 쓰기 쉽게' 하기위함 입니다.
for문의 장점
- for문은 수치를 증가시키는 while을 쉽게 표현할 수 있습니다.
if문 for문 while문을 사용하지 않아도 프로그램을 짤 수는 있습니다.
그러나 사용하는 것이 보다 알기 쉬운 코드를 구성할 수 있습니다.
5장. 함수
함수는 왜 필요한가?
- 프로그램 규모가 커지면 전체적인 구조를 파악하기 어려워집니다.
- 이런 문제를 해결하기 위해 함수가 등장했습니다.
- 한그룹인 코드를 빼내어 이름을 부여하면 그 코드가 무엇을 하는지 파악하기 쉬워집니다.
- 또한 다른곳에서 쉽게 호출하여 재사용도 가능합니다.
- 함수를 사용함으로써 '재귀 호출'이라는 코딩 기술이 탄생했습니다.
6장. 에러 처리
에러처리 왜 필요한가?
가스가 새고있는데 가스 누출 검지기가 없다면 폭발등 큰 사고로 이어질 수 있습니다.
프로그램도 같습니다. 파일을 기록하려는데 실패했을 때 어떤 경고도 없다면 실패를 알아차리기 힘듭니다. 때문에 가스 누출 검지기처럼 '실패를 알리는 구조'가 필요합니다.
실패를 전달하는 방법은 아래와 같습니다.
- 반환값으로 실패를 알린다.
- 실패했을 때 반환값에 기록해둘테니 나중에 확인해~
- 반환값을 확인하는 것을 잊어버려서 실패를 놓칠 수 있는 문제가 있다.
- 실패하면 점프한다.
- 에러가 발생했을 때 점프할 장소를 사전에 등록해둔다.
- 여기서 발전한 것이 '예외 처리'
- 함수의 출구가 여러개로써 '짝을 이루는 처리'를 바르게 짝을 지어 처리하는 것이 어렵다.
- 실패할것같은 처리를 묶는 구문
- 미리 실패했을 때 처리를 등록해둔 후 실패할것 같은 코드를 쓰는 형식
- 자바의 try-catch문을 생각하면 됩니다.
7장. 이름과 스코프
지금은 대부분의 언어가 변수나 함수에 이름을 붙이는게 당연합니다.
당연하게 쓰고있는 이름은 왜 필요했는가??
- 만약 이름이 없다면 우리는 메모리 주소를 사용해서 데이터를 꺼내야합니다.
- 예를 들어 '흰색 셔츠 좀 줘'와 '옷걸이 13번째에 걸려있는 옷을 줘' 의 차이입니다.
- 이름을 붙이는것은 프로그래밍 언어도 이름으로 대상을 지정할 수 있도록 하기 위함입니다.
변수명끼리 중복되면, 즉 이름끼리 충돌하면 원하는 결과를 얻기 힘듭니다. 스코프는 위와 같은 문제를 해결하기 위해 탄생했습니다.
- 스코프란 이름의 유효 범위
- 이름의 유효범위를 좁게 한정해서 관리가 편해지도록 함
- 동적 스코프
- 함수 내부에서 같은 이름의 변수를 사용할 때 중복되는 이름의 변수를 복사해두고 함수에서 나갈 때 다시 같은 이름의 변수에 저장해준다. (원래의 값을 다른 곳에 피신시켜두고 나중에 되돌린다.)
- 위의 동작을 컴퓨터에게 맡기고 함수안에서 프로그래머는 원래 하던것처럼 변수를 선언하고 초기화하여 사용합니다.
- 스코프에 대한 로직은 컴퓨터에게 맡기는 것을 '동적 스코프' 라고합니다.
- '동적 스코프'의 문제로 함수 안에서 함수를 호출했을 때는 호출 했던 함수의 변수 스코프를 따라간다는 문제가 있습니다.
- 정적 스코프
- 동적 스코프는 가장 가까운 대응표를 보고 사용합니다.
- 정적 스코프는 함수별로 대응표를 나눈다.
- 각각의 함수별로 대응표를 만들기 때문에 함수(메서드) print 대응표에는 x가 없기 때문에 대응표 전역에서 가져옵니다.
위 처럼 스코프는 이름에 대한 충돌을 해결하여 쉬운 코드를 만들기 위한 핵심 개념으로 사용되고 있습니다.
8장. 형(type)이란?
0과 1의 집합인 비트열이 같다고 해도 '그것이 어떤 값인가?' 라는 정보를 추가한 것이 형(type)입니다.
수를 어떻게 표현하는가에 대한 발전
10이라는 수를 위해 10개의 표식을 사용한 가장 원시적인 방법에서 '가장 간단한 방법'이 무엇인지 고민했고 '자릿수'를 발명했습니다.
자릿수란? 1의 자리, 10의 자리, 100의 자리 각각 10개의 램프로 0~9의 숫자를 표현합니다. 1000의 자리를 추가로 표현하려면 10개의 램프를 추가하여 표현할 수 있습니다.
그리고 효율을 추구하는 과정에서 '10이 아닌 2로 자릿수를 표현하는 방법' 2진법을 발명했습니다.
2진법은 컴퓨터가 값을 표현하기 위해 중요한 역할을 하고 있습니다.
실수를 어떻게 표현하는가
- 고정 소수점 - 소수점을 어디에 붙일지 정한다.
- 이 정수는 소수점을 4자리 이동시켜 소수점 이하 4자리를 소수부로 한다. 라는 규칙을 정하는것
- 어디까지 소수부로 정했는지 사람이 기억하기 힘들다는 단점이 있습니다.
- 부동 소수점 - 어디부터 소수부인지의 정보 자체를 값에 포함시킵니다.
- IEEE 754가 정의하고 있는 부동 소수점 구조
- 최상위 비트(가장 좌측 비트) 1개는 '부호'를 나타냄 (0 이면 양수, 1이면 음수)
- 8개의 비트는 '지수부(소수점의 위치)'를 표현함
- 나머지 23개의 비트는 '가수부'
지수부 | 가수부 |
0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
위는 부동 소수점으로 1.75라는 실수를 나타냅니다.
위의 형(type)이 정수라면? 이 값은 엄청 큰 값이 될것입니다.
정수와 실수의 형을 구분하지 않으면 전혀 다른 값을 가지게 됩니다. 따라서 언어 차리계에 '이 변수는 정수다' 와 같이 알려서 사람이 아닌 컴퓨터가 기억하는 것입니다.
-> 변수형 선언이 탄생한 이유
9장. 컨테이너와 문자열
왜 다양한 컨테이너가 존재할까?
- 각각의 컨테이너에 장단점이 있기 때문
- ArrayList와 LinkedList를 생각해보면
- ArrayList
- 선형 구조, 데이터가 순서대로 저장됨.
- 검색에 유용합니다.
- 추가 삭제를 하려면 뒤의 요소값들의 자리를 이동해야하기 때문에 비용이 커질 수 있음
- LinkedList
- 다음 데이터가 어디 있는지 알려주는 연결형 구조.
- 각각의 요소는 자신의 다음 데이터가 어디에 저장됐는지를 알고있습니다.
- 따라서 추가나 삭제를 위해서는 자리 이동없이 알맞은 데이터가 있는 장소와 연결해주면 됩니다.
만능 컨테이너란 없다 -> 어떤 목적이든 최선의 컨테이너는 없다.
- 컨테이너를 사용하는 목적
- 어떤 사용법을 적용할 것인가
- 어떤 조작이 많은가
- 위의 필요조건에 따라 균형을 맞춰 최적의 컨테이너를 찾는것
문자열
문자들이 들어있는 것
- 컴퓨터 이전 시대의 부호화
- 모스 부호
- 보 코드 - 5개의 비트로 on/off 를 조합해 문자를 표현하는것
- EDSAC 문자 코드
- ASCII(아스키 코드) 와 EBCDIC
- UNICODE
언어마다 어떤 정보를 어떤 메모리에 저장하는지(문자열 구현)의 차이가 있습니다.
-> 많은 언어가 문자열을 지원하고 있지만, 서로 동일하다고 말할 수 없습니다.
10장. 병행 처리
옛날 컴퓨터인 EDSAC 등에서는 프로그램을 입력 후 계산을 실행하면 계산이 끝날때 까지 다른 프로그램을 실행할 수 없었습니다.
-> 요즘은 유튜브를 보면서 카톡을하고 파일도 전송할 수 있습니다.
요즘 이런게 가능한 이유는 편리한 병행 처리를 실형하기 위해 프로세스나 스레드(thread) 등의 개념이 만들어졌기 때문입니다.
또한 병행 처리 문제가 발생했을 때(메모리 공유에 의한 문제) 락(Lock)이나 파이버(fiber) 등의 개념이 발명되었습니다.
락의 문제점
- 교착상태 발생 -> 서로가 락을 가진채로 서로의 자원을 필요로 할 때 발생
- 락에는 합성할 수 없는 문제가 있음
락의 문제점을 해결하고 하는 것이 트랜잭션 메모리
- 데이터베이스의 트랜잭션 기법을 메모리에 적용한 것
- 별도 버전을 작성해서 데이터를 변경하고 묶음 처리가 끝나면 변경을 원래의 버전에 반영
- 중간에 쓰기 처리가 끼어들면 작성한 별도 버전은 버리고 처음부터 다시 작업을 진행
- 쓰기 처리의 빈도가 많다면 -> '다시 고쳐서 하기'의 작업이 많아져서 성능이 저하
11장. 객체와 클래스
객체 지향은 왜 만들어 졌는가?
- 컴퓨터로 문제를 해결할 때 현실 세계의 '사물'을 컴퓨터 안에 만들 필요가 있었다
- '사물'은 원문의 object(객체)를 해석한것
- '모형' 이라고 해석한 것은 model(모델)
- 즉, 현실 세계에 있는 '사물'의 '모형'을 컴퓨터 안에 만들려면 어떻게 하면 될까? 어떻게 하면 편해질 수 있을까?
- 위의 목적을 달성하기 위해 만들어 진것이 '객체 지향'
클래스란 어디에 사용하면 좋은걸까?
- 작은 규모의 서비스라면 굳이 클래스가 필요하지 않다.
- 대규모 프로그램을 많은 사람이 나눠서 작업하는 경우 클래스를 사용해서 책임 범위를 나누는 방법이 편하다.
- 변수와 함수를 합쳐서 모형을 만들 수 있다.
12장. 상속이란?
상속데 관한 3가지 접근법
- 일반화 / 특수화
- 부모 클래스로 일반적인 기능을 구현하고, 자식 클래스로 목적에 특화된 기능을 구현한다.
- 자식 클래스는 부모 클래스를 특수화한다.
- 자식 클래스는 부모 클래스의 일종이다 -> YES
- 공통 부분을 추출
- 복수 클래스의 공통 부분을 부모 클래스로서 추출한다.
- 일반화 / 특수화 와 비슷해 보이지만 매우 다르다.
- 자식 클래스는 부모 클래스의 일종이다 -> NO
- 차분 구현
- 상속 후 변경된 부분만을 구현하면 효율이 좋다.
- 상속을 재사용을 위해 사용함으로 구현이 편해질 수 있다는 발상
- 자식 클래스는 부모 클래스의 일종이다 -> NO
칼럼의 내용들
'어떤것을 이해했다!' 라고 하려면 그것이 맞는지 검증하기 위한 결과물이 필요합니다.
그렇게 하려면 제 3자에게 검증을 받아야하는데 프로그래머는 언어처리계에게 코드를 검토 받습니다. 이는 매우 편리하고 빠른 피드백을 줍니다. 에러 메시지가 발생하면 '왠 에러가 떳어?' 하고 당황할게 아니라 언어 처리계가 '여기가 무슨 말인지 모르겠어' 라고 하는 말을 듣고 고쳐주어야 합니다. 이것이 언어 처리계와 커뮤니케이션을 해야하는 이유입니다.
'무엇을 배우면 좋을까' 라는 질문에는 '무엇을 만들고 싶은가'를 명확히 해야합니다.
무엇을 만들까? 를 정할 수 없다면 너무 처음부터 완벽한걸 만드려고 하는건 아닌지 고민해봐야합니다.
간단한것도 좋으니 무엇이라도 만들어보면 '이것은 할 수 있다', '이것은 아직 어려우니까 공부할 필요가 없어'를 알 수 있게 됩니다. 이 과정을 반복하다보면 더욱 복잡한걸 만들 수 있습니다.
진짜 회고
코딩을 지탱하는 기술은 우리가 배우고 자주 써오던 기술이 어떻게 변화해왔고 어떤 이유로 만들어졌는지에 대해 굉장히 잘 써져있습니다.
왜 만들어졌지?ㅎ 왜 변화했지?ㅎ 왜 문제가 발생하지?ㅎ 왜 문제를 그렇게 해결했지? 의 연속
예전에는 '문법이 이렇게 쓰는거구나 ㅎ.ㅎ 그냥 써야지' 라고 생각하며 '오! 향상된 for문? 이거 쓰면 더 좋다는거지?' 하고 그냥 좋다고 하면 썼습니다. ( 몸에 좋다면 일단 사고보는 ,, )
요즘 우테코에 와서 자신에게 그리고 함께 토론해주시는 고마운 분들에게 가장 많이 던지는 질문이 '왜 그런거죠? 왜 이렇게 해야하죠?' 같은 이유에 대한 질문이 많습니다. 그리고 모든 설명에는 '왜 이렇게 했어?' 라는 게 들어가있어야 합니다. 그러다 보니 이 시기에 이 책을 접한건 어떻게 보면 굉장한 행운이라고 생각합니다!
'왜?' 라는 키워드로 어떻게 공부할 수 있는지 굉장히 좋은 가이드라인이 됐습니다.
여러개의 언어들이 함께 써져있어서 예문을 읽는데 힘든점이 있었지만 그런 예문마저도 책에는 친절하게 설명이 되어있어 읽는게 큰 문제가 되는건 아니었습니다.
꽤나 텀을 두고 읽어서 책의 내용이 머리속에 뒤죽박죽이라 제대로 회고를 하지못한것 같아서 이 글은 많은 리팩토링을 거치려고 합니다.
댓글