-
Notifications
You must be signed in to change notification settings - Fork 10
/
0302.txt
8 lines (7 loc) · 20.1 KB
/
0302.txt
1
2
3
4
5
6
7
8
예 안녕하세요 포프입니다. (음-) 오늘도 자다 일어나서 만드는데 일요일이라 저는...
오늘 할 이야기는 Null에 대해서 이야기 해보려고 해요. 그...굳이 C만이 아니라 Java를 쓰거나 C#을 쓰거나 그래도 이제 Null이라는 걸 대입을 하잖아요? 그 오브젝트 같은 거에 저도 이제 그냥 뭐 이렇게 쓰는 거구나 하고 쓰다가 언제 한번 이제 과연 null이 좋은 거냐 아닌 거냐라는 그런 여러 가지...이제 Debate? 토론이 붙은걸 보고 나서 그 중에서 어떤 사람이 한 얘기를 보고 null에 대한 이제 좀 개념이 더 확실하게 잡혔었거든요?몇 년 됐는데 좀 오래된 거 같은데 그 예를 들어서 그 뭐지?C++을 만들었던 그 아저씨... 이름 까먹었는데 BjarneStroustrup (비야네 스트럽스트롭) 뭐 이상한 이름 있잖아요? 그 아저씨는 이제 C++을 만들 때 되게 (흥-) 후회하는 게 이제 포인터를 없애지 않은 거란 이야기를 한걸 본 적이 있어요. 물론 그 아저씨가 포인터를 없앴더라면 C++이 지금까지 이렇게 써질 거 같진 않은데, 그래서 그 아저씨가 이제 좀 더 안전한 언어 디자인을 하는 부분에서는 그걸 후회할 수 있겠지만 제 생각에는 그거는 (슷-) 오히려 현재 C나 C++이..C는 아니지 C++이 이용되는 그.. 뭐랄까...현상을 죽이는 그런 상황?그래서 오히려 그런 일이 있었으면 C++은 게임 업계에서 안 쓰이고 있었을 것 같은데...그 일단 null이 쓰인다는 거는 뭐냐면 자바를 쓰는 사람들이 특히나 이제 착각을 많이 하는 것 중에 하나가 '아~ 자바에 포인터가 없어' 이런 이야기를 해요. 근데 솔직히 말하면 자바는 다 포인터에요. 그래서 (슷-) null이 있다는 거 자체가 일단은 아 뭐라 그럴까... 음....동적으로 오브젝트를 만들어서 그 오브젝트를 주고받으면서 레퍼런스로 참조를 하는 경우가 대부분이라고 보거든요? 뭐 반드시 그래야 하는 건 아니지만 어쨌든 자바도 그렇게 만들었고 C#도 그렇게 만든 거예요. 그래서 거기에는 이제 포인터 연산이 없을 뿐이지 그 포인터가 메모리 어디를 가리키면 그 메모리 위치를 바꿀 수 없다는 거뿐이지, 실제 Java에서 존재하는 모든 오브젝트는 포인터 기반이라고 하면 맞아요. 레퍼런스기 때문에..
그러면 포인터가 뭐냐? 예전에 그런 이야기를 했었어요. 비디오에서 말한 적도 있었고 영어를 알면 프로그래밍을 잘할 수 있던가? 뭐... 프로그래밍에 도움이 된다였나? 그런 비디오에서 말했던 거 같고, 포인터는 결과적으로 어떤 메모리 주소를 저장하는 변수에요. 그러면 그 메모리 주소엔 어떤 내용이 있을까? 그 내용에 따라 포인터를 참조라고 하나? 역참조라고 하나? 참조가 맞는 거 같아요... 잘 모르겠어요. 그래서 C에서 스타싸인 집어넣으면 되는 거.. 별표 싸인 그거를 하면 이제 거기서 데이터를 꺼내와서 쓰거나 읽거나 덮어쓰거나 하는 거거든요? 그래서 결과적으로 포인터는 주소를 가리키는 변수에요. 그러면 만약에 이 주소를 가리키는 변수에 null 값이 들어가면 어떻게 되냐? null이라는 게 결과적으로는 그냥 0이라는 그런 비슷한 개념이거든요? 그러면 null이 들어갔을 때는 이거를 아무것도 가리키지 않는 주소로 본다, 결과적으로 이 주소 값이 없다고 본다라고 해서 null이 나온 거에요. 그러면 재밌는 게 포인터라는 거에서는 그러면 두 가지 값이 있을수가 있어요. 하나는 제대로 된 주소 값 다른거는 제대로 되지 않 주소값...그래서 실제 포인터나 아니면 자바에서 오브젝트 같은 경우 그게 가리키는 값은, 그거에 가지는 값은 유효한 오브젝트의 주소 오브젝트 값을 한번 참조하면은 가질 수 있거나 아니면은 '아 이건아무것도 가리키고 있지 않는다 그니깐 이거는 잘못된 변수다', '현재 상태는 아 Valid 한 게 아니다 Invalid드한거다' 라는걸 나타내는 상태가 들어가는 거예요. 그래서 어떻게 보면은 포인터가 들어오고 null이 생기면서부터 이 변수에 들어가는 값은 둘 중에 하나가 되는 거죠. 제대로 된 오브젝트 아니면은 상태. 그래서 null을 지지하는 사람들의 이야기는 'null이라는 건 되게 좋은 거다. 왜냐하면은 변수 하나로 상태와 실제 변수를 표현할 수 있다'라고 이야기를 해요. 그런데 이게 함수를 많이 짜본 사람들은 알겠지만 함수에서 보통 리턴 값이 하나인 언어가 많잖아요? 파이썬 같은 건 아니지만 뭐 자바, C, C# 같은 경우는 기본적으로 리턴 값이 하나라고요. 그럼 거기서 어떤 함수에서 무슨 연산해서 반환을했는데 이 리턴 값이 올바를 수도 있지만 아니면은 그 함수에서 데이터베이스를 긁는다거나 그런 걸 못 찾았어 그런데이터베이스 엔트리를. 그럴 때 '과연 이 값을 어떻게 표현할꺼냐?'라는 문제가 있거든요. 그래서 이제 null이 있음으로 해서 변수 하나만 반환해서 그 상태와 제대로 된 값을 둘 다 표현할 수 있는 게 null이라서 null이 좋다는 이야기가 있고, 아니면 다른 쪽 이야기는 아~ 왜 하나에 두 개를 넣냐? 차라리 하나만 넣고 그런 함수를 따로 만들어라 ➡️ 아 여기에 이런 데이터가 있냐 ➡️ True(참), false(거짓), Booloean(참/거짓을 값으로 가지는 논리 자료형)을 반환한 다음에 이제 그 다음에 다시 다른 함수를 호출해서 값을 가져오는 방식. 근데 API 디자인 측면에서는 좀 애매하죠. 왜냐하면은 '이 함수를 호출하기 전에 반드시 다른 함수를 호출해야 된다'라는 강제를 해야 하는데 규격상으론 강제가 안되고 Coding Fract 상으로 강제를 하는 거니깐 좀 어려운 거고, 그래서 이제 그거를 또 해결하기 위해서 C# 중에 TryParse 이런 쪽에 계열의 함수를 보면은 Boolean을 반환하면서 제대로 됐는지 안됐는지 알려주면서 Palse 한 데이터를 out parametor(out 매개변수)로 뽑아 든 게 경우가 있죠. 래퍼런스로... 뭐... 뭐라 그러지? 변수를 줘서 그 변수의 값을 대입해서 나오는 그래서 그런 꼼수가 나왔고...(물 한모금 마심) 개인적으로 API 디자인 측면에선 TrayPalse 같은 게 훨씬 낫다고 생각을 해요. 함수 호출 하나로 두 가지를 끝낼 수 있으니깐. 근데 그런 게 아니라 오브젝트 타입이라면, 어차피 null이 반환이 되는 거라면 null을 반환함으로 해서 상태도 볼 수 있단 장점도 있죠. 그래서 이제 어떤 함수는 null은 반환하는 경우가 있고, 어떤 함수는 null을 반환하지 않는 경우가 또 있기 때문에 그 함수 내부를 까 보지 않으면 그 판단을 내리기가 매우 어려운 경우도 많고 단점도 있긴 해요. 주석을 잘다는 법도 있지만, 저 개인적인 경우에는 null을 반환하는 함수면은 언제나 리턴 값 표현하는 주석에 보통 함수 제일 위에 주석을 달잖아요 null을 반환하는 함수는 언제나 주석을 달아서 리턴 값에 null 그렇게 써놓고 If nothing found 그런 식으로 넣고 그런 식으로 해서 null을 반환하는 함수를 주석에 반드시 쓰는 습관을 들였어요. 그래서 null이 좋은 거냐 나쁜 거냐...저 개인적으로는 null이…null을 좋아해요(널 좋아해❤️). null 포인터가 들어갈 수 있는 오브젝트를 되게 편하게 생각을 해요 왜냐하면 리턴을 하나만 할 수 있다는 장점? 그리고 어차피 C#이나 자바는 모든 게 다 포인터인데 굳이 포인터에서 리턴을 두 번 함으로 해서 효율성을 뭐 차이는 없겠지만 실제 컴퓨터 도는데에서 굳이 그게 효율에도 좋지도 않고 굳이 사용할 때 훨씬 편한 것도 아닌데 굳이 그래야 되나 생각이 드니깐 그런 것도 있고 정말 표준화 되게 여러 가지를 반환하는 경우가 있어요. 그런 경우에는 아예 구조체를 만들어서 구조체를 반환하기도 해요. 구조체 같은걸 만들어서...뭐 클래스라 해야 하죠 자바에서는 뭐 만들어서 그 뭐라 그럴까 제너릭 같은 걸로 만들면 C#에서 데이터를 밑에 넣고 그 위에는 Success인지 Fail인지를 넣을 수 있는 그런 방법도 있기 때문에 그런 것도 쓰는데 그거는 이제 뭐라 그럴까? 음..Class하고 Structure를..C#에선 Class(클래스) 하고 Structure(구조체)가 달라요. Structure는 값이고 솔직히 null이 없는 게 정상이고, Class는 이제 오브젝트이기 때문에 null이 있는 게 정상이고. 그리고 이제 보통 우리가 말하는 기본형식 데이터들 있잖아요?Integer..Double.. 이런 것들도 사실은 null이 안 들어가는 그런 변수거든요? 값으로 copy하는 변수기 때문에 그런 타입이기 때문에 그런 것까지 한 번에 표현해서 null을 넣을라면은 그런 식으로 구조체(Structure)를 만들어서 하는 법이 있고 아니면 리턴 값을 Nullable로 하는 법이 있죠. C#이 자바보다 하나 나았던 건 뭐 나면 자바는 Primitive Type 빼고는(기본 타입 빼고는) 모든 게 다 그냥 클래스예요. 오브젝트 개념이고. 값을 패스할 수 있는 방법도 없고, 값을 한번 만든 다음에 이걸 복사를 안 하고 패스하면서 뭐라 그럴까? 음.. 긍까 실제 원본을 바꿀 있느냐 없느냐의 문제인 거 같아요. 값이냐 오브젝트냐는... 오브젝트를 패스하면 레퍼런스를 패스 한 거기 때문에 그 원본을 곧바로 바꿀 수 있는 반면에 Structure, C#의 Structure 값형으로 패스하면은 원본을 못 바꾸기 때문에 그 값만 쓰는 그런 또 API적인 강점이 있거든요? 내가 패스 했는데 함수가 내 걸 바꾸면 기분이 나쁘잖아. 그래서 그런 거를 강제하는 방향에서 C#에 방향성은 좋다고 생각했는데 Structure와 Class 이야기는 한 번 더 할 일이 있을 거예요. 생각보다 방향은 좋은데 쓰기 또 되게 애매한 게 그 부분이라서 정말 코딩 표준화를 잘해놓지 않으면 정말 고생을 많이 하는 부분이라서 그것도 좀 고민이 되고..어쨌든 null이 그냥 뭔지 모르고 막 null 쓰고 무조건 null 검사하고 이런 사람 되게 많아요. 그냥 String(문자열)을 자기가 만들어 넣고도 반드시 null이 아닌데, 일단 만들어 놓고도 다음 코드에 쓰면은 그 개념이 없기 때문에 무조건 null 체크하고 그런 사람들 있거든요? null이라는 건 그냥 그렇게 생각을 했으면 좋겠어요. 기본적으로 이게 오브젝트를 가리키는 맞는데, 그게 안될 때 아 '이 오브젝트가 올바른 상태가 아니다', '오브젝트가 없다' 라고 할 때 null을 집어넣는 거라 생각하면 될 거 같고, 똑같은 거를 Integer나 Double에서도 쓰긴 써요. 어떤 거냐면 예를 들어서 이제 C#에서 List라던가 C에 이제... 뭐.. 뭐…뭐지? 그 Find 같은 함수 있잖아요 STL에서 컨테이너에서. 그런 함수를 쓰면은 아무 값도 찾지 못하면 -1을 줘요.
Array(배열)에서 뭔가를 찾아야 하는데 그 element(요소)를 찾지 못할 경우에는 negative값을 주는 거야. 왜냐하면 Array는 0부터 시작해서 양수로만 가잖아요? 근데 -1을 준다는 것은 존재할 수 없는 값이기 때문에 그걸로 '상태가 없다', 'element를 찾을 수 없다' 라는걸 표현하겠다는 거죠. 그래서 결과적으로는 유효한 범위중에서 어느 한값을 빼서 이 값이 잘못된 것이라는 것을 표현할 때 쓰는 거거든요? 이것도 어차피 null이라고 똑같은 개념인데 그게 -1이 될지 -Max가 될지 뭐... Double 쓸 때는 MaxValue 도 쓰고 MinValue도 쓰잖아요? 그런 식으로 해서 어떤 특정값이 잘못된 값이다라고 하면 이제 Sentinel 값이라고 하는 거 같아요. 그 값을 표현 하겠다는 건데, 문제는 이거는 완벽히 정의된 Convention이 없어요. 내가 Array 같은 걸 뒤질 때 - 1을 sentinel Value로 쓸 수도 있고 아니면 -10을 쓸수도 있거든요? 그래서 그런 거를 쓸 때는 이제 그때는 뭐랄까 #Define을 하거나 Const를 만들어서 변수...그러니깐 이름을 정해준 Const로 쓰란 말이죠. 상수인데 이름 정해진 상수. 그래서 문제를 막자는 이야기가 있지만 이미 각각 값을 나타내는 변수에서 그렇게 하는 거를 레퍼런스로 나타내는 변수에서는 null이라는 정해진 규격이 있다고 생각하면 되는 거 같아요. null이 좋냐 나쁘냐를 논하는 사람들도 있지만 null이 뭐 나쁜 거다라고 이야기를 하지만 어차피 null을 쓰지 않는 이상 아까 말했듯이 함수를 여러 개 나눠서 Boolean으로 강제하면은 코딩 스탠다드나 coding practice에서 강요를 해야 되는데 그거는 더더욱 강제성이 없어서 조금 애매하고 어차피 뭐 만약에 무슨 어레이를 뒤지거나 이럴 때 그러면 Integger 값을 써갖고 -1 값을 반환하는 이런 방식도 쓰기 때문에 어차피 Sentinel 값을 써야 하는 상황이라면 이미 정해진 null Sentinel 값이 훨씬 더 사람들이 이해하기 쉽고 누구나 다 이해할 수 있는 거니깐 좋은 거라고 생각을 하고, 아 그리고 만약에 구조체 같은 거는 그 오브젝트를 따로 만들어서 모든 함수가 이 오브젝트를 반환하게 만든다... 그럼 이거 실패했는지 성공했는지 Boolean값이 하나 있고, 그 다음에 템플릿 제너릭으로 이제 어떤 데이터를 넣었고 데이터를 가져오게 한다면은 뭐 그것도 나쁘지 않다고 생각은 해요. 근데 모든 함수를 그렇게 짜긴 굉장히 많은 일이 있어야 하고, 어떤 라이브러리 레벨에서 그냥 아 이 라이브러리는 언제나 이런 반환 값을 반환한다라는 거에서는 나쁘지 않았어요.
물론 이렇게까지 얘기하면은 또 null 값이나 이런 거 대신에 얘 또 exception을 일부러 던져갖고 그 exception을 catch해서 그런 null 상황을 catch 하겠다, 잘못된 상태를 catch 하겠다는(널 잡겠어 ❤️) 또 그런 망상을 하는 분들도 꽤 봤는데, 그게 올바른 방향일 때도 있지만 아닌 경우를 더 많이 봤어요. exception이라는 건 그대로 내가 예상하지 못한 그런 상황이 exception일 뿐이지, 내가 이미 충분히 예측 가능한 상황은 exception은 아닐 거 같아요. 그거는 오히려 뭐라 그럴까? 음...들어 올 수 있는 그런 뭐라 그래요 유효한 값들과 무효한 값이 있는데 무효한 값들이 이미 들어올 수 있다는 가정이 있으면, 그거를 이제 제대로 처리해서 이 상태가 잘못됐다는 걸 알려주는 게 나은 거지 exception을 마구 던지면 그거는 조금 오히려 exception의 남용 같아요. 그게 저는 exception 관련 비디오에서 몇 번 이야기했지만 그리고 그렇게 코드를 하다 보면 흔히들 말하는 코드 가독성이 좋아진다고 하지만 그건 완전히 구라인 것 같고, 오히려 가독성이 되게 안 좋아지고 사람 두뇌가 코드가 중간에 여기서 저기로 튈 수 있다는 생각을 하기가 되게 어려워요. GOTO를 쓰지 말라고 했던 이유와 비슷하거든요 사실은. GOTO는 명시적으로 어디로 가라라고 써 잇지 exception은 코드가 실행되다가 하나 잘못되면 튀어나가기 때문에 그거는 아닌 거 같고 음...예전에 뭐 exception 관심 있으신 분들은 제가 만들어 놨던 비디오 중에 exception은 이제 boundary(코드의 경계)에서만 체크하라는 이야기를 제가 한 적이 있어요. 왜냐하면 boundary case는 그러니까 뭐라 그럴까 이 라이브러리에서 내 라이브러리 접근권한이 없는데 함수를 호출하는데 내가 기대하는 값과 전혀 다른 값이 들어오는 경우가 있잖아요? 그럼 이 경계선에서 exception 체크 해갖고 모든 거를 exception을 던지든 에러 코드를 돌리든 그건 거기서 처리를 하고 내 그 경계를 넘어와서 내 라이브러리에 들어오는 순간, 그 순간에는 모든 데이터가 이미 유효한 데이터니깐 그거에 가정해서 쓰란 이야기를 한 적이 있어요. 해서 그러면 왘 뭐라그래 내가 라이브러리 클래스가 천 개 있는데 천 개에서 exception 던지면서 서로 이렇게 이렇게 캐치하는 이런 이상한 짓을 안 해도 되고, 그에 비해 이제 한 군데서 exception을 캐치하니깐 여기만 주의하면은 그 안에는 유효한 값이라고 돌고 하는 거죠.
그 마지막으로 이 exception 이야기가 나왔으니깐 비유를 들자면 이게 뭐 나면 자동차를 몰다보면은 자동차에 넣어야 되는 기름이 다른 종류가 있어요. 내 차에는 경유가 들어가고, 다른 차에는 휘발유가 들어가고, 어떤 차에는 가스가 들어가는 차가 있어요. 그러면 내가 주유소에서 이 휘발유를 넣을 때, 어떤 휘발유인지 어떤 경유인지는 종류를 잘 골라서 넣는 거만 주의하면 되는 거자나요? 근데 여기서 만약에 경유를 넣을려고 그러면 경유넣는 그 구멍 있잖아요? 그거는 휘발유랑 달라요. 그래서 경유가 휘발유 차에 안 들어가거나 휘발유가 경유차에 안들어가거나, 그 노즐이 안 들어가게 돼있거든요? 그게 exception 핸들링이란 이야기예요. 그니깐 내 차에 처음부터 경유나 등유가 못 들어오게 그런 예외상황을 막고.. 인터페이스죠. 그래서 그게 끝난 경우에 내차에 이미 들어온 휘발유는 휘발유니깐, 휘발유라 가정하고 엔진을 돌리겠다는 거예요. 근데 이 exception 핸들링을 잘 못쓰면은 아 경유와 휘발유가 언제든 들어올 수 있으니깐...막 이런 막 튜브를 해갖고도 집어넣을 수도 있으니깐 그 들어온 순간 그거를 처리하기 위해서 엔진을 두 개를 단다거나, 엔진을 경유가 들어왔을 때 어떻게 작동하는지를 예외상황을 만들어놔서 그 안에서 어떻게 해결을 하면서 자동차를 계속 돌게 만든다거나, 그런 이상한 짓을 하는 거예요. 뭐 똑같은 이야기로 AAA 배터리 들어가는데 AA배터리 넣고 난리 치는 거나 뭐 그런 예가 있죠. 아니면 뭐 배터리도 마이너스 플러스 있잖아요? 마이너스 플러스 뒤집어 넣었을 때 그게 돌게 하기 위해서 내부 시스템을 만든다거나, 이런 개념이 잘못 쓴 exception 핸들링인 거 같아요. 그래서 exception 핸들링은 말 그대로 경계에서만 하자...그래서 이야기가 또 길어졌는데 언제나 그렇지만 오늘 하려고 했던 이야기는 null이라는 게 '아 그냥 null이니깐 쓰자'가 아니라 '오브젝트가 있고 그 오브젝트가 상태가 이제 제대로 된 오브젝트가 아니다. 이 오브젝트가 존재하지 않는다.' 라는걸 상태라는 개념도 포함하기 위해 null을 추가한 거고, 실제 그 클래스나 오브젝트들 그리고 포인터 C에서는 그래서 null이라는 게 존재하는 이유가 '아 이게 유효하다 아니다' 와 '이게 실제 값이 있다를 표현하기 위해서 두 개를 합쳐 놓은 거다' 그래서 그 두 개를 쓰는 건 나쁘지 않다고 생각을 하고 오히려 저는 선호하는데, 뭐 그런 여러 가지 다른 방법도 있다는 걸 말씀드리고 싶었어요. 그래서 음 그리고 null 체크를 뭐라 그럴까...그 뻔히 null이 아닌 데서 null 체크 좀 하지 말자라는 이야기도 하고 싶었던 거 같고, 최근에 그런 코드를 본적이 있어서 암이 걸렸기 때문에 그래서 'null이란 이런 거다' 라는 포인터? 비디오를 만들었네요.
네 포프였습니다