C언어 기초 학습을 마치며, 후반부에 팀원들과 함께 프로젝트를 진행했다.
워낙 짧은 기간의 프로젝트였지만 나름대로 성장의 요소가 있었다고 생각하고 추후 프로젝트를 진행할 때
더 잘할 수 있도록 회고를 간단하게 진행하려고 한다.
어떤 프로젝트?
프로젝트의 목표는 c언어를 활용해서 간단한 프로그램을 만드는 것이었다.
(순수 C, Visual Studio)
주제는 자유였고, 우리 팀은 'DB를 활용해 특정 장소 주변의 맛집을 추천해 주는 프로그램'을 만들었다.
우리는 항상 점심 메뉴 고민에 시달리는 사람들이니까..!
프로젝트 기간은 주말을 제외하면 약 4~5일 정도로 굉장히 짧은 기간이었다. 실제로 빡세게 작업한 건 대략 3일 정도인 듯..
마음가짐
아무래도 새로운 사람들, 새로운 언어, 새로운 개발 환경에서 프로젝트를 진행해야 하니 사실 부담감이 굉장히 많았다.
앱을 만드는 것도 아니고 그냥 콘솔 프로그램을 만들어야 한다는 것 자체도 감이 잘 오지 않았다.
프로젝트가 항상 그렇듯 처음에는 어떻게 하지.. 걱정만 태산이다가 막상 프로젝트에 돌입하면 뭐든 만들고 있고
끝나면 어떻게든 완성이 된다는 것이 참 재미있다. 시작도 하기 전에 수많은 걱정부터 하는 이런 태도도 좀 고쳐야 하는데😅
막무가내로 부딪혀 보는 도전 정신이 날이 갈수록 사라지는 것은 아닌가 싶다.. 이럴 때 아니면 이것저것 못해보는데 말야?
그럼에도 불구하고 본인의 할당량을 기한 내에 최대한 완벽하게 만들어내야 한다는 부분에서 많은 걱정과 부담을 느끼는 것 같다.
그래도 혼자 하는 것이 아니라, 멋진 팀원들이 있어서 괜찮았다.
기획
간단하게 맛집을 검색할 수 있는 프로그램에 필요한 기능들을 정리하며 기능 명세서를 작성했고, 기능별로 작업들을 배분했다.
또한 데이터 베이스를 활용하기로 했기 때문에 erdcloud 를 활용해서 필요한 테이블에 대한 erd를 간단하게 작성했다.
개발 세팅
C언어에 대한 활용이 중점이었기 때문에, 가장 간편하게 활용할 수 있는 DB인 sqlite를 채택했다.
https://sqlite.com/download.html
필요한 파일들을 받아서 프로젝트에 추가해 주고, 헤더 파일의 위치를 외부 include 디렉터리에 추가해 주면 끝난다.
DB Browser를 통해서 데이터 업데이트가 제대로 되는지 확인할 수 있다.
DB의 초기 데이터는 공공데이터 포털에서 다운로드한 데이터를 전처리 후 사용했다.
개발 돌입
나는 크게 새로운 1. 데이터 추가, 2. DB 업데이트, 3. 디버깅(QA)을 수행했다.
기능 명세에서 아주 추상적으로 '새로운 데이터 추가'와 같이 적혀있었기 때문에
나름대로 내 기능이 어디서 어떻게 호출될 것인지 생각한 다음 최대한 복잡하지 않게 내 선에서 모든 동작이 끝나도록 개발을 진행했다.
쪼개고 정리해
먼저 모듈화와 코드정리에 신경을 좀 썼다.
Swift로 앱 개발을 하면서 남아있는 스타일이라 생각이 드는데.. 팀원이 내 코드를 보고 되게 java스럽다고 했다.
그래도 깔끔하게 잘 정리되어 있어서 빠르게 확인할 수 있다고 해줘서 뿌듯? 했다.
새로운 식당의 정보를 유저로부터 받아서 DB에 추가하는 코드들이다.
이렇게 많이 쪼개질 줄은 몰랐는데.. 하다 보니 많아졌다.
여기서 restInput()이 부모함수로,
필요한 곳에서 restInput()만 호출하면 유저로부터 데이터를 입력받아 DB에 추가하는 모든 로직이 수행되도록 개발했다.
절차 지향과 전방 선언
C의 경우 절차 지향적인 특성이 굉장히 강해서, 위와 같이 함수의 시그니처(반환타입, 네임, 파라미터)를 전방에 선언해 주는 것이 좋다.
시그니처 선언을 하지 않아서 에러가 발생한 경우가 굉장히 많았기 때문이다..
또한 다른 사람이 봤을 때도 함수들을 살펴보고 대략적인 플로우를 확인할 수도 있다.(네이밍이 잘 되어 있다면..ㅎ)
또한 visual studio만 그런 건지는 모르겠지만, 함수를 호출할 때 파라미터를 빼먹고 호출해도 컴파일 에러를 띄우지 않는다 ;;
Swift에서처럼 옵셔널 같은 것을 선언한 것도 아닌데.. 파라미터가 추가적으로 필요해서 함수에 추가해 놓고
호출 부분에서 추가적으로 전달해주지 않아서 발생한 문제가 꽤 있었고, 이걸 발견하는 것도 쉽지 않았다.
따라서 함수를 수정하면 시그니처 전방 선언, 호출 부분 확인 두 가지 절차를 꼭 수행하는 것이 좋겠다고 느꼈다.
갖다 써주세요
또한 DB를 업데이트하는 코드도 만들었는데 (유저의 평가를 통해 테이블 값 변경)
이를 다른 팀원들이 재활용할 수 있도록 개발했고, 성공적으로 동작해서 굉장히 뿌듯했다.
부모함수의 파라미터를 극단적으로 줄였고, 각자의 로직에서 '특정 형태'로만 값을 넘겨주면 된다고 설명했다.
역시 멋진 팀원들은 부족한 나의 설명을 찰떡같이 이해하고 코드를 적용해 왔다.
예외가 왜 이리 많아?!
또한 예외 처리가 굉장히 힘들었다.
콘솔 프로그램이다 보니, 유저의 키보드 입력에만 의존해서 잘못된 입력을 전부 막아야 했다.
이를 위해 셀프 QA를 자처했는데.. 힘들었지만 돌아보니 재밌었다.
아주 간단하게 몇 가지 예외만 막을 수도 있고 예외를 전부 막을 수도 있고
예외를 막는 방법도 굉장히 다양하다.
기본적으로는 입력값을 사용하려는 변수에 바로 넣기보다는
임시 변수를 만들어 확인을 하고 사용하려는 변수에 할당하는 방식은 공통적이라 생각한다.
아주 무식한 방법일 수도 있지만.. 위와 같이 예외처리를 위해 꽤 노력했다는 점만...
한글.. 그만 깨져
그리고 어우 한글이 정말 복병이었다.
VS에서 깨지기도 하고, 터미널에서 깨지기도 하고, DB와 왔다 갔다 하면서 깨지기도 하고
정말 한글 처리를 하면서 팀원 모두가 굉장히 고생했다.(바이트 수도 다르고, 아스키 값도 고려해야 하고)
한글... 굉장히 사랑하고 자랑스러운 언어지만 개발할 때는 주석 말고는 사용하지 않는 것이 여러모로 좋다 ㅎㅎ...
동적 할당과 메모리 누수
c의 참맛은 포인터와 동적할당 아니겠는가? (몹쓸 아는 척)
모듈화를 하다 보니 데이터를 전달해야 했고, 지역변수를 사용하기보다는 동적 할당을 통해 포인터를 전달하는 방식을 활용했다.
이왕 배운 거 허접하지만 써보고 싶기도 했고, 뭔가 malloc 자체가 재밌었다. 사람마다 다르게 읽는데 나는 멜록이라고 읽는다.
필요한 만큼만 썼다가 반환하는 효율적 메모리 사용의 맛보기 느낌!
하지만 동적할당을 하면 항상 메모리 사용이 끝난 후 해제를 해줘야 된다. 그렇다 메모리 누수를 계속 신경 써줘야 한다.
프로젝트를 진행하면서 malloc을 세 번 정도 썼고,
항상 내부에서 free를 해주거나 호출부에 찾아가서 주석으로 체크해 둔 후 free를 해줬다.
어느 정도 개발이 끝나고 메모리 누수가 있나 확인해 보려고 VLD(Visual Leak Detector)를 켜봤는데 아뿔싸....
https://github.com/KindDragon/vld
엄청난 메모리 누수에 절망하고 만다.
대체 왜지??? 다 free 해줬는데? 믿기지가 않았다.
그렇게 디버깅 지옥에 빠졌고... 프로젝트 발표 당일 아침까지도 누수를 잡지 못해서 우울해하고 있었다.
팀원들과 함께 코드를 살펴보기도 하고 거북목이 점차 심해져 가던 찰나
아무래도 malloc 때문이 아닌 것 같다는 생각이 들었고,
결론적으로는 sqlite 라이브러리 함수를 제대로 사용하지 못해서 발생한 누수였다.
라이브러리 막 쓰지 마세요.
쿼리를 날리고 데이터를 받아오는 작업이 끝나면 제대로 종료시켜 줄 것 (쿼리 데이터가 힙에 남아 있다.)
DB를 열었으면 작업이 끝나고 DB를 꼭 닫아줄 것! (아니면 힙에 데이터들이 남아 있다.)
라이브러리 함수가 편리하기는 하지만, 정의나 내부 동작을 우리는 모두 알지 못하기 때문에
너무 막무가내로 쓰다가는 이런 참사를 맞이할 수도 있다는 것을 제대로 느꼈다.
내부적으로 동적할당을 통해 heap을 사용한다는 것을 누수탐지를 하고 나서야 느꼈고,
사실 누수를 잡는 과정이 굉장히 고통스러웠지만
이야 No Memory leaks detected 문장을 봤을 때는 만세를 외치지 않을 수 없었다 🙌🏻
이런 찰나의 쾌감에 개발을 지속할 수 있는 것 아닐까..
동적할당을 사용해 보면서 최신 고급 언어들의 메모리 관리에 굉장히 감사함을 느끼게 되는 계기였다.
GC, ARC 갓..
또 왜 개발자들이 메모리 해제를 놓치는지, 왜 현업에서 동적 할당을 잘 사용하지 않는지, 왜 포인터의 개념이 숨게 되었는지
많이 느끼게 되었다.
배웠던 점
1. 본인을 위해서도, 팀원을 위해서도 코드를 깔끔하게 작성하는 연습을 하자.
Naming, 모듈화는 당시에는 귀찮더라도 나중에는 큰 힘을 발휘할 수 있다고 생각한다.
2. C로 개발한다면, 함수의 시그니처 선언은 해주는 것이 좋다.
기능 플로우 확인, 컴파일 에러 방지, 파라미터 빼먹기를 방지할 수 있다.
3. 포인터와 동적 할당은 분명 어려운 개념이다. 하지만 C의 최대 장점이기 때문에 만약 사용한다면 꼭 메모리 누수 가능성을 체크하자.
직접적인 malloc, realloc 뿐만 아니라 라이브러리 사용으로 인한 메모리 누수 가능성도 고려하자.
4. 디버깅, QA
콘솔 프로그램이었기 때문에 QA에 더 신경을 쓸 수 있었다고 생각한다.
또한 여러 에러를 잡고, 메모리 누수를 잡는 작업은 당시에는 힘들었지만 돌아보니 재밌었다고 느껴진다.
이를 통해서 그때그때 메모리 누수를 확인하고, 테스트 코드를 왜 작성하며 TDD를 하는지도 조금은 느끼게 되었다.
5. 또한 팀원들과 함께 협업을 하다 보니, 최대한 간편하고 독립적으로 기능을 호출할 수 있게 개발하려는 노력이
어느 정도는 적용돼서 좋았다.
6. 다만 처음에는 github을 적극적으로 활용하려고 했지만, 후반부에 시간 부족으로 수동머지를 했던 점은 다소 아쉬운 점으로 남는다.
다음 프로젝트에서는 여유가 된다면 github 관리까지도 적극적으로 할 수 있으면 더욱 좋겠다 :)
쓰다 보니 많이 길어졌지만, 꽤 재미있었고 많이 배운 프로젝트였다.
끝!