문법 기초
45편
값이 없다는 것도 상태다
null 은 0이 아니라 가리키는 대상이 없는 것입니다. 없을 수 있다는 사실을 타입에 적어 두면 컴파일러가 봐 줍니다.
예외는 정상 흐름과 오류 흐름을 갈라 놓는다
매 줄마다 성공했는지 확인하지 않아도 됩니다. 정상 코드는 정상 코드대로 두고 오류 처리는 따로 모읍니다.
이벤트는 일이 생기면 알려 달라는 약속이다
알리는 쪽은 누가 듣는지 모르고, 듣는 쪽은 언제 일어날지 모릅니다. 서로를 몰라도 됩니다.
람다는 이름 없이 그 자리에서 만드는 메서드다
한 번 쓰고 말 동작에 이름을 붙이지 않습니다. 넘기는 자리에 바로 적습니다.
대리자는 메서드를 담는 변수다
값이 아니라 무엇을 할지를 담습니다. 부르는 쪽과 실제 동작을 떼어 놓을 수 있습니다.
프로퍼티는 필드 앞에 세운 창구다
겉보기엔 필드처럼 쓰지만 읽고 쓸 때 코드가 실행됩니다. 캡슐화를 문법으로 만든 것입니다.
foreach 는 번호를 신경 쓰지 않는다
처음부터 끝까지 하나씩 꺼내 줍니다. 인덱스가 필요 없는 반복에서 실수할 자리가 사라집니다.
T 는 여기에 타입이 들어온다는 빈칸이다
object 로 다 받으면 타입 검사가 사라지고 박싱이 일어납니다. 제네릭은 타입을 나중에 정하면서 둘 다 막습니다.
무엇인가면 추상 클래스, 무엇을 할 수 있으면 인터페이스
하나는 공통 코드를 물려주면서 빈칸을 남기고, 하나는 지켜야 할 목록만 적습니다. 문장으로 읽어 보면 답이 나옵니다.
같은 호출인데 객체마다 다르게 동작한다
부모 타입으로 불러도 그 객체가 갈아 끼운 메서드가 실행됩니다. 새 자식을 추가해도 부르는 쪽은 안 고칩니다.
상속은 물려받아 덧붙이는 것이다
겹치는 부분을 부모에 두고 다른 것만 자식에 적습니다. 자식을 부모로 보는 것은 언제나 안전합니다.
안은 감추고 창구만 열어 둔다
필드를 열어 두면 잘못된 값이 들어갈 길도 같이 열립니다. 창구를 좁히면 확인할 기회가 생깁니다.
대입하면 값이 복사되거나 주소가 복사된다
규칙은 하나입니다. 담긴 것이 복사됩니다. 담긴 것이 값이냐 주소냐에 따라 결과가 완전히 달라집니다.
생성자는 태어날 때 딱 한 번 실행된다
만들고 나서 값을 채우면 빠뜨릴 수 있습니다. 만드는 순간 채우게 하면 빠뜨릴 방법이 없습니다.
new 는 힙에 자리를 만들고 주소를 돌려준다
변수 안에 객체가 들어 있는 것이 아닙니다. 객체가 있는 곳의 주소가 들어 있습니다.
클래스는 틀이고 객체는 찍어낸 것이다
관련 있는 데이터와 그 데이터를 다루는 코드를 한 덩어리로 묶습니다. 틀은 하나지만 찍어낸 것은 여러 개입니다.
부른 순서대로 쌓이고 반대로 풀린다
메서드가 자기를 부르면 호출이 겹겹이 쌓입니다. 쌓이는 자리에는 한계가 있고, 짧은 코드가 빠른 코드는 아닙니다.
이름은 같고 투입구만 다르다
Console.WriteLine 에 숫자를 넣어도 문자열을 넣어도 되는 이유입니다. 같은 이름의 메서드가 여러 벌 있습니다.
return 은 값을 돌려주면서 메서드를 끝낸다
두 가지 일을 동시에 합니다. 끝낸다는 쪽을 이용하면 깊게 겹친 if 를 평평하게 펼 수 있습니다.
ref 는 고치러, out 은 채우러 간다
하나는 이미 있는 값을 바꾸러 가고, 하나는 없던 값을 만들어 옵니다. TryParse 가 두 번째 방식입니다.
넘어가는 것은 값의 사본이다
메서드 안에서 매개변수를 바꿔도 밖의 원본은 그대로입니다. 값을 적어 보낸 것이지 변수를 보낸 것이 아닙니다.
메서드 한 줄은 네 부분으로 되어 있다
무엇을 돌려주고, 이름이 무엇이고, 무엇을 받고, 무슨 일을 하는가. 이 넷만 정하면 메서드가 됩니다.
"123" 은 숫자 123 이 아니다
입력으로 받은 것은 언제나 문자열입니다. Parse 는 바꿀 수 없으면 프로그램을 멈추고, TryParse 는 못 바꿨다고 알려주기만 합니다.
같은지 보는 것과 줄 세우는 것은 다른 일이다
== 는 문자열의 내용을 비교합니다. 대소문자를 무시하려면 ToLower 로 맞추는 대신 비교 방식을 넘겨야 하고, 정렬 순서가 필요하면 Compare 를 씁니다.
자르고 찾는 것은 전부 새 문자열이다
Substring 의 둘째 인자는 끝 번호가 아니라 개수입니다. IndexOf 는 못 찾으면 -1 을 돌려줍니다. 두 가지만 알아도 문자열 버그의 대부분이 없어집니다.
적은 그대로가 값이 되는 문자열
따옴표는 문자열의 끝을 알리는 기호라서 안에 그대로 쓸 수 없습니다. 역슬래시로 피하는 방법과, 그마저 필요 없게 만드는 따옴표 세 개를 봅니다.
값은 그대로 두고 모양만 바꾼다
$ 는 문자열 안에 값을 끼워 넣고, 콜론 뒤에 적는 형식은 그 값을 어떻게 보여줄지만 정합니다. 캐스팅과 달리 값 자체는 건드리지 않습니다.
바깥은 행, 안쪽은 열이다
바깥이 한 번 돌 때마다 안쪽은 처음부터 끝까지 전부 돕니다. 별찍기는 줄마다 개수를 세는 문제입니다.
break 는 나가고, continue 는 건너뛴다
하나는 반복문을 끝내고 하나는 이번 회차만 포기합니다. continue 는 반복을 멈추지 않습니다.
for 는 적힌 순서대로 실행되지 않는다
괄호에 세 가지가 나란히 적혀 있지만 셋째는 본문이 끝난 뒤에 실행됩니다. 그래서 본문에서 보는 i 는 아직 증가 전입니다.
do-while 은 일단 한 번은 한다
while 은 조건을 먼저 보고 do-while 은 본문을 먼저 실행합니다. 물어보기 전에 한 번은 해야 하는 일에 씁니다.
모든 반복문은 세 부분으로 되어 있다
어디서 시작하고, 언제까지 돌고, 매번 무엇이 변하는가. 셋 중 하나가 빠지면 영원히 돌거나 한 번도 안 돕니다.
코드에 적히지 않은 약속은 없는 약속이다
case 4 가 목요일이라는 사실이 코드 어디에도 없습니다. const 와 enum 은 그 약속을 코드 안으로 옮기는 문법입니다.
switch 는 조건이 아니라 값을 본다
else if 사슬이 같은 변수를 계속 다시 묻고 있다면 switch 자리입니다. 범위는 if, 목록은 switch입니다.
조건을 쓰는 순서가 곧 우선순위다
else if 는 위에서부터 처음 맞는 하나만 고르고 나머지는 검사조차 하지 않습니다. 그래서 쓰는 순서가 결과를 바꿉니다.
갈림길을 정하는 것은 bool 하나다
괄호 안이 아무리 길어도 결과는 true 아니면 false 하나입니다. 그 한 값이 실행이 지나갈 길을 고릅니다.
문자열은 바꿀 수 없다
문자열은 문자를 나란히 놓은 것입니다. 인덱스가 0부터인 이유부터, 고칠 수 없어서 + 가 매번 새 문자열을 만드는 것까지 봅니다.
문자는 숫자로 저장된다
'A' 가 왜 하필 65인지, 대문자와 소문자가 왜 32 차이인지. 아스키코드에서 유니코드까지 봅니다.
식은 순서대로 줄어든다
컴파일러는 한 줄을 왼쪽부터 읽지 않습니다. 우선순위가 높은 곳부터 값으로 바꿔 하나만 남을 때까지 줄입니다.
연산자는 값을 만든다
+, %, ==, && 같은 기호들이 각각 무엇을 하고 어떤 값을 만드는지 한 번에 봅니다. = 와 == 의 차이부터 ++ 의 전위와 후위까지.
값을 옮기면 무슨 일이 생기나
넓은 칸으로 갈 때는 채우고, 좁은 칸으로 갈 때는 잘라냅니다. (int)는 반올림이 아니라 버림이고, 10 / 3 은 나눗셈 시점에 이미 3입니다.
0.1 은 2진수로 끝나지 않는다
0.1 + 0.2 가 0.3 이 아닌 이유. 실수는 값을 그대로 담지 않고 부호·지수·가수 세 조각으로 나눠 담습니다.
정수는 직선이 아니라 원이다
int.MaxValue 에 1을 더하면 int.MinValue 가 됩니다. 예외도 경고도 없이 값만 바뀝니다. 왜 하필 최솟값인지 그림으로 봅니다.
음수는 메모리에 어떻게 들어가 있나
음수라고 특별한 표시가 붙지 않습니다. 같은 자리에 비트만 다르게 채워질 뿐입니다. 부호 비트와 2의 보수를 그림으로 봅니다.
변수를 선언하면 무슨 일이 일어나는가
변수는 값을 담는 상자가 아니라 이름이 붙은 메모리 칸입니다. 선언·할당·사용이 각각 언제 무엇을 하는지 그림으로 봅니다.