포인터와 참조자 이야기

자~ 간만에 C++ 이야기입니다~! C언어의 난이도를 몇배로 높여준! 많은 사람들이 C언어를 포기하게 만든 원흉! 포인터부터 얘기해보죠.

포인터(*)란 아시다시피, 메모리 주소를 가리키는 변수입니다. 가리키는 곳은 언제든 변할 수 있고, 아무곳도 가리키지 않는 상태(NULL 포인터)도 있습니다.

참조자(&)란 뭘까요? 참조자는 변수의 다른 이름입니다. 포인터와 비슷하죠? 포인터와의 차이점은 참조자는 아무것도 가리키지 않는 상태가 없다는 것입니다.

포인터가 어려운 이유는 사용법 자체가 아니라 관리가 어렵기 때문입니다.

다음 코드를 살펴 보시죠. 포인터로 인해 발생할 수 있는 몇가지 상황을 보여줍니다.

    int *pDynamic= new int;
    int *ptr= pDynamic;
    delete pDynamic;
    pDynamic= NULL; //1. 주인 잃은 포인터 : 위 코드가 실행된 이후  piPtr가 가리키는 곳은 해제된 메모리가 됐기에 주인 잃은 포인터가됨.

    *pDynamic= 6;  //2: NULL포인터 참조 : NULL포인터에값대입해서오류발생 

    if(ptr)
        delete ptr; //3: 포인터 delete 두번 : pDynamic 이 delete되면서 한번 delete 된 곳을 또 delete 했기에문제발생.

이 처럼 포인터 사용시에는 늘 주의깊게 신경써야만 합니다. 포인터로 인해 발생하는 문제들은 대부분 끔찍한 결과를 낳기 때문입니다.

그렇다면… C++에서 야심차게 도입한 참조자에는 이런 문제가 없을까요?

그렇진 않았습니다. 참조자 역시 포인터와 마찬가지로 자신이 가리키던 객체보다, 참조자가 수명이 길면 안됩니다.

아래 코드를 보시면, 참조자의 실체를 파악하실 수 있을 겁니다.

    std::queue q;
    q.push(1); //queue는 push시에 내부적으로 동적할당 함
    int & n = q.front(); //동적할당된 1이 저장된 장소의 참조자를 리턴
    q.pop(); //pop시에는 동적할당을 해제함. 현재 n은 1이 저장되었었지만 지금은 해제된 주소를 가리킴. 
    q.push(2); //싱글 스레드이기에, 1이 저장되어있었던 주소를 동적 할당받아서 2를 대입.
    printf("%d\n", n); //n은 실제로는 포인터이기에 1이 저장되었던 주소를 참조해 2를 출력함

어떤가요? 참조자도 가리키는 대상이 사라진다면 (현재의 예는 동적할당되는 객체였지만, 임시 변수도 마찬가지), 주인 잃은 참조자가 될 수 있습니다.

그렇지만 참조자는 위 문제만 주의한다면, 포인터처럼 NULL검사를 할 필요가 없습니다. 그리고 포인터처럼 니모닉 (별칭) 기능을 충실히 해주죠.

참조자를 사용할 수 있는 상황이라고 판단이 되면, 포인터보다 참조자를 사용하는 것이 좋습니다.

정리하자면, 참조자는 변수의 별칭이기에 특정 변수의 다른 이름처럼 사용 가능합니다. NULL (아무것도 가리키지 않는) 상태가 없다면 참조자를, NULL 상태가 있거나 동적할당된 데이터를 관리하려면 포인터를 써야합니다.

또한, 포인터나 참조자 모두 자신이 가리키는 대상이 사라질 때를 주의해야 한다는 점에 늘 유념해서 코딩 하셔야 합니다.

포인터와 참조자에 대해서 썰을 늘어놨는데요, 헷갈리시는 분들에게 도움이 되셨길 바라며 이만 줄입니다. (__) 꾸벅~

(서평) 잡스처럼 일한다는 것 - 뛰어난 리더가 뛰어난 기업, 뛰어난 제품을 만든다.

프로그래머 분들 중에 반MS적인 성향을 가지신 분들이 많은 것은 어제 오늘 일이 아니다.

나 개인적으로는 친MS에 가까울 정도로 윈도우의 개발 환경에 찬사를 보내곤 하지만, MS가 존경의 대상은 아니다. MS의 경쟁 업체를 견제하는 전략은 얄미울 정도로 탁월하고 효과적인 경우가 많지만, 정이 가지 않더라.

그렇다면… 윈도우즈의 모태라 할 수 있는 매킨토시는?

내가 어린시절 보아온 수 많은 잡지와, 만화에서는 매킨토시를 전문가용, 그리고 매니아용으로 간주했다.

아무래도 내가 자라온 시절 대중적인 PC는 IBM-PC였고, PC-DOS나, MS-DOS가 내가 주로 접할 수 있는 환경이었다.

프로그래밍을 시작한 이후에도 리눅스에 대해 극찬하는 프로그래머는 많았어도, 매킨토시에 대해 극찬하는 프로그래머를 찾아 볼 수 없었다.

맥에 대한 호기심도, 맥을 사용해야만 하는 상황도 없었기에 맥은 언제나 내 관심 밖이었다.

사실 계기가 전혀 없었던건 아니긴하다.

2007년 연말 회사에서 아이팟 터치를 선물로 주셨었는데, 나는 자금 사정상 뜯지도 않고 팔아서(…), 맥을 접할 기회를 날려버렸었다.

맥을 한번도 사용해보지 않은 나로썬, 이 책을 읽게 된 것은 순전히 회사 동료분의 추천 때문이었다. 난 스티브 잡스가 애플 CEO라는건 알고 있었어도 그에 대해 자세히 알진 못했었다. 당연히 애플이 어떤식으로 구성되어있고, 인재관을 가지고 있는지도 몰랐다.

사실 기술에 집착해 제품을 개발할 때에는, 많은 기술, 최신 기술만이 전부인거 같다. 하지만, 사용자 입장에서 사용할 수 없을 만큼 어렵고, 복잡하다면 그 기능은 죽은 것이다.

“Simple is best.”, “완성이란 무언가를 더이상 더 할 수 없을 때가 아니라, 무언가를 더이상 뺄 수 없을 때를 말한다.”

이런 문구를 많이 들어봤을 것이다.

하지만 단순함만으로는 좋은 제품을 만들 수 없을 것이다. 애플의 제품은 사용자에게 새로운 경험을, 더 뛰어난 경험을 제공해야 한다는 큰 가치를 두고 움직였다. 그 핵심에 잡스가 있었고, 완벽 주의자 스러울 정도인 그의 세심함은 애플의 제품을 더욱 더 빛나게 했다.

작은 부분 하나까지 신경쓰는 잡스의 성향은 같이 일하는 사람들에게 스트레스로 다가올 수도 있었겠지만, 그 것을 견뎌낸 사람들은 모두 일류가 될 수 있었다.

잡스와 일한다는 것 자체가 자신이 일류라는 증명처럼 느껴지는 것…황홀하지 않은가?

그런 사람 옆에서 일하고 싶은 마음이 드는 것, 그렇게 만든 것. 이런 면들이 결국 애플의 성공의 밑거름이 됐을 것이다.

잡스 혼자 맥, 아이팟을 만들 순 없다. 좋은 인재를 옆에 두기 위해선, 뛰어난 리더가 필요하다.

누구나 자기보다 뛰어난 사람과 같이 일하고 싶어한다. 뛰어난 사람일 수록 더욱 그렇다. 또한 자신을 알아봐주는 사람과 같이 일하고 싶어한다.

뛰어난 리더인 잡스. 그가 이끄는 애플의 성공은 어찌보면 당연한 것 아니었을까?

문제해결 노트

나의 리팩토링 기준 및 리팩토링 방법 정리

  1. 중복을 제거하라. (DRY. Don’t Repeat Yourself)
    • 같은 일을 하는 클래스, 혹은 메소드 등이 한 곳에만 존재하도록 하라.
  2. 메소드가 존재해야 할 클래스는 명확해야 한다. (직관성)
    • 기본적으로 어떤 동작을 행하는 쪽에 메소드를 만들어라. (그 일을 하는데에 필요한 멤버도 포함)
    • 예를 들어, 밥을 먹는다면 밥을 먹는 주체는 사람이다. 그렇다면 사람 클래스에 무언가를 먹는다는 메소드(Eat)가 있어야 할것이다.
  3. 메소드 이름에 해당하는 일만 해야 한다. (직관성)
    • 다른 일을 하게 될 일이 생길 경우 메소드를 분리한다.
    • 단어의 포괄적인 의미에 속하는 일을 하게 될지라도 메소드를 하나 더 만들고, 그 메소드를 호출하게 해 Function per method 구조를 지향하도록 하자.
  4. 클래스는 너무 커지지 않도록 하라. (Divide and Conquer)
    • 너무 작게 나누는 것이 능사는 아니다. 오히려 복잡도가 증가할 가능성도 있지만, 일반적으로 클래스를 작게 쪼깬다면 유지보수에 큰 잇점을 얻을 수 있다.

버그 확인 검증법

  1. 문제를 재현하라. 
    • 문제로 인한 증상을 파악한다.
    • 결함을 만든 곳만이 아니라, 감염을 통한 증상이 보여지고 보고 될 수도 있다. 증상은 문제가 있다는 보고로 생각해야지, 결함의 절대적인 원인으로 생각해서는 안된다.
  2. 문제 재현해보아라.
    • 만약 문제가 발생했다는 사실은 알지만 원인을 찾을 수 없고 재현 방법을 모른다면, 문제가 발생하는 보고를 토대로 원인을 유추해내자.
  3. 보고나 확인된 문제와 같은 증상을 보이는지 확인한다. 
    • 증상이 다르다면 다른 문제를 찾았거나, 문제가 아닐 것이니 같은 증상이 발생하는 상황을 찾을 때 까지 반복하라.
  4. 결함을 제거하라.
    • 증상을 보이는 상황을 토대로, 결함을 제거할 방법을 판단하라.
    • 결함 제거 시에 유의할 점은, 이 과정에서 새로운 버그를 만들 여지가 있다는 것이다.
    • 주로 결함의 원인을 잘못 짚은 경우나 보고된 증상 하나만 해결하려 하는 경우에 버그 제거에 실패하는데, 문제를 일으킨 원인을 찾기 전까지는 수정할 방법을 결정하지 말라.
    • 수정시 영향을 줄 수 있는 코드를 모두 찾아보고, (Find in files 등을 이용) 수정된 코드가 불려지는 모든 상황을 테스트 하라. 
    • 이 과정은 매우 번거롭기에 자동화 해두는 것이 좋다.
  5. 결함이 제거되었는지 확인하라.
    • 수정된 상태로 문제 재현을 시도하자.
    • 문제가 아직도 발생한다면, 문제 해결 방법을 잘못 결정한 것이다. 
    • 무엇 때문에 잘못된 판단을 하게 됐는지 파악하고, 기록하라. 지금의 실수를 다시 반복하지 않도록 하라.
    • 문제가 해결되었다면 실제 환경과 최대한 비슷한 환경에서 다시 한번 테스트하라.
    • 가능하다면 이 테스트는 재현 방법을 설명해주고, 다른 사람에게 테스트를 맡기는 것이 변경된 코드에서 발생할 수 있는 새 버그나, 미처 발견하지 못했거나 제거하지 못한 결함을 찾는 데에 도움이 될 가능성이 높다. 

문제 발생시 마음가짐

  1. 긴장하지 말라. 
    • 긴장은 시야를 좁게 만들어 실수를 만들 소지가 많다.
  2. 시간에 쫓기지 말라. 
    • 문제를 빠르게 수정하는 것이 중요하긴하지만, 결함을 제대로 수정하고, 확인하는 과정은 그 이상 중요하다.
  3. 절대로 확인 과정을 건너 뛰지말라. 
    • 확인 과정을 건너 뛸 경우 또 다른 결함을 만들어 내거나, 제대로 수정되지 않았을 가능성이 높다.

(서평) 게임 프로그래머를 위한 수학과 물리

사실 나는 고등학교때 수학/물리 공부를 잘 하지 못했다. 프로그래머가 되겠단 목표는 있었지만, 재미를 못느끼다보니 집중도 잘 못했고.   물론 지금 서버 프로그래머를 하고 있기에 그 필요도가 비교적 낮다고 하지만, 수학을 근간으로 발전해온 컴퓨터에서 수학에 대한 이해도는 높을 수록 좋은 것이지 절대로 불필요하지 않다.   게다가 나는 취미로 2D며, 3D며 클라이언트 작업도 하기 때문에 수학/물리에 대한 필요성은 늘 인지하고 있었다.   그럼에도 불구하고 기초라는 것이 중요하다는 것은 알지만, 절실하게 다가오는건 아니지 않은가?   개인적인 우선 순위에서 밀리기도 했고, 이런 저런 이유로 미뤄오던 수학/물리 공부였으나 스터디를 시작하게 된 것을 계기로 이 책을 보게 되었다.   같이 스터디를 진행 해 주시는 분이 워낙 설명을 잘해주신 부분도 있지만, 책 자체가 설명이 굉장히 쉬웠다.   그렇다고 아쉬운 점이 없는 것이 아닌데, 책의 내용 중에 틀린 내용이 상당수 된다는 것이다. (수학에서 작은 차이가 얼마나 큰 결과를 가져다 주는지 아는 분은 다들 아실것이다.)   물론 이런 틀린 내용을 교정해나가는 과정이 공부가 되긴했지만, 수많은 사람이 지적했을 오타와 잘못된 내용에 대한 정오표마저도 홈페이지에서 제대로 지원되지 않는건 너무나도 아쉬웠다.   하지만 적어도 나에게 있어선 수학/물리가 재밌게 느껴지도록 큰 도움이 된 책이기에 나는 그것만으로도 후한 점수를 주고 싶은 책이었다.

초심으로 돌아가기

나는 처음 프로그래밍을 배울 때, 독학 기간이 길었던터라 하나 익히는데에 (특히 포인터) 꽤나 긴 시간이 필요했고, 어떤게 좋은지 나쁜지를 대부분 경험으로써 느껴왔다.

최초 설계에 구현을 어떻게든 맞추는 일도 해보고, 설계가 존재하지 않는 run and fix 프로그래밍도 해보고, 프로토타입을 많이 만들어놓고 베스트한걸 고르기도 해봤다.

다양한 방법을 경험하던중 안좋은 습관 하나가 붙었다. 너무 바쁘게 일을 하다보니 정리의 습관이 부족해 졌단 것이다.

내가 감당하기에 힘들만큼 버겁고, 많은 일이 주어지긴했지만, 그런 것들은 결국엔 다 핑계고 조급한 맘이 문제였다.

메모의 기술에 대한 서평에서 내가 메모를 기록과 증빙의 용도로 사용한다고 얘기했지만, 사실 내 머릿속에 있는 내용을 정리하는 과정으로도 많이 사용했다.

그 중에서도 로직을 순서도로 그리는 방법은 나에게 있어 특히 효율적이었는데, UML을 몰랐고, UML이 필요하지 않았던 혼자 프로그래밍해오던 나로썬 매우 유용한 방법이었다.

어느 덧 경력도 쌓이고 일도 적응이 되자, 머릿속과 코드 만으로 로직을 이해할 수 있다고 자신했으나… 간결하게 코드를 작성해왔다고 한들, 클래스와 클래스 간의 결합도가 높아져버리면 대책없었다. 복잡도도 복잡도고, 변경이 잦은 경우 어느 선을 넘어서 복잡도가 지나치게 커지면 일관성 있는 코드를 유지하기가 힘들어졌다.

이럴때 로직을 작게 나누고 (작게 나누어지지 않는다면 결합도가 높다는 뜻이니 이 결합도를 낮추는 작업을 먼저하고), 작게 나누어진 로직에 순서도를 그렸더니 코딩이 너무 편해졌다. 순서도를 현재 로직대로 업데이트 하며 관리했더니 코드를 보지 않고도 처리 방식을 훨씬 빨리 찾고 이해할 수 있었다.

처음 프로그래밍을 배우고, 게임을 만들때의 방식을 되찾은 것이다. 어린 시절 즐겁게 프로그래밍을 처음 배웠을 때 하나 하나 알아가던 때와 같은 기분이 들어 신이 난건 덤이었다.

이런 순서도만으로 프로그램 내에 수립된 가정을 이해하긴 힘들것 같다고? 나도 그렇게 생각한다. 그래서 생각한 것이 바로 클래스 관계도다. 사실 데이터 베이스의 경우에도 ER다이어그램이라 불리는 스키마 관계도가 일반적으로 통용되고 있다.

순서도는 세부 로직별로 작성되어야 되고, 클래스 관계도는 전체적인 상관관계를 그려놓는것이 좋다. 그에 있어 기준도 명확히 기록해둔다면 해당 관계도 하나만 보고도 이해하기 쉽다.

여기서 또 중요한것이 어느정도 선까지 클래스 관계도를 디테일하게 작성할 것이냐인데… 내 생각엔 멤버 하나 하나 자세하게 나열하는 것보다는 객체와 객체간의 큰 가정(has a 관계나, is a 관계, 또는 패턴을 적용한 경우 패턴 이름 등)을 기록해둠으로써 새로운 가정이 기존 가정을 깨드리지 않도록 하는 것이 중요하다고 본다.

XP에서 강조하듯 죽은 문서는 필요 없지만, 문서화 자체가 필요없는 것은 아니다. 자신이 생각하기에 업무 능률을 높여줄 수 있는 문서화는 반드시 필요하다.

서비스에서 사실상 가장 중요한 DB작업의 경우 여러 단계의 점검 이슈 검토 작업을 거친다. 사실 처음엔 그게 굉장히 번거로웠고, 단순 반복 작업이라 느껴져 귀찮기도 했는데, 각 단계를 거치면서 사소한 실수는 많이 줄어들었다.

코드의 내용을 그대로 담는다면 중복이고 번거로운 반복 작업이 되겠지만, 클래스 관계도는 중복 조차 아니다. 요약이다. 이 얼마나 즐거운가! 우리가 싫어하는 중복이 아니란 말이다!

물론 코드 작성시마다 검토하고 수정해야 되는 번거로움 정도는 있지만, 잘 생각해보면 그 한번의 과정을 통해 잘못된 코드를 양산할 확률이 급감한다.

내가 너무 늦게 깨닳은 것인지 모르지만, 벼는 익을수록 고개를 숙이고, 프로그래머는 경력이 쌓일 수록 더 꼼꼼해져야 한다고 본다.

조금만 더 부지런해지자.