Posts [42Seoul] minishell (2) 프로젝트 회고
Post
Cancel

[42Seoul] minishell (2) 프로젝트 회고

플로우차트

간략하게 표현한 플로우차트는 다음과 같다.

1.png

gnu bash reference 를 참고해서 최대한 bash 와 유사하게 작동하도록 노력했다.

협업 방식

업무 분담

이번 프로젝트에서는 업무 분담을 따로 하지 않았다. 보통 파싱, 실행을 나누어서 진행하는데, 우리는 처음부터 모든 과정을 같이 코드를 작성하는 페어 프로그래밍 방식을 선택했다.

팀원도 나도 모두 사이드 프로젝트를 하던 시기에는 하루에 1~2시간이라도 구글 미트로 만나서 코드를 작성하고자 했다.

visual studio code 의 live share 확장 프로그램을 사용해서 함께 코드를 작성했다. 터미널을 공유할 수 있기 때문에 실행 결과도 같이 확인하고, lldb 디버깅 결과도 같이 보면서 작업을 진행했다.

어느 정도 기능 구현이 끝나고, 빌트인 함수를 만드는 부분에서만 잠시 업무를 나눠서 진행했다. 빌트인 함수는 다른 함수들에 비해 비교적 독립적으로 작동하는 성격이 강했기 때문에 작업 효율을 높이기 위해 분담해서 진행했다.

모든 과정을 같이 하다보니 속도가 안나기도 했지만, 과제에 대한 이해가 확실히 높아졌다.

다음 프로젝트에서는 처음부터 각자 할 일을 나눠서 진행하고, 필요한 경우에만 페어 프로그래밍을 진행하는 방식으로 진행하면서 어떤 방식이 내게 잘 맞는지 확인해보고 싶다.

작업 관리

2.png

구현해야 하는 기능이나 수정해야 하는 기능은 github project 의 칸반보드를 사용해서 정리했다.

이 곳에서 Issue 로 등록하면 번호가 자동으로 생성되기 때문에 commit 을 남기거나 브랜치를 생성할 때 이슈 번호를 등록해서 사용했다.

3.png

위의 사진과 같이 commit 에 이슈 번호를 입력하면 자동으로 github 에서 해당하는 이슈 번호로 링크가 생성된다.

브랜치 관리

4.png

브랜치 관리는 위와 같의 이미지와 같이 진행했다.

  • main : 완성된 버전이 통합되는 브랜치로 사용했다. 과제를 제출하기 직전에 develop 브랜치에서 작업한 내역을 모두 통합했다.
  • develop : 실질적인 개발이 이루어지는 브랜치로 사용했다. 기능 구현이나 버그를 발견하면 하위 브랜치를 생성하고, 구현이 끝나면 다시 develop 브랜치로 합쳤다. 빠르게 기능을 고쳐야 할 때는 develop 브랜치에서 모두 진행하기도 했다.
  • feat / fix : 기능 구현이나 버그 수정을 위해 사용한 하위 브랜치이다. develop 브랜치에 모든 작업을 진행하면 충돌이 날 수 있기 때문에 충돌 위험을 줄이고자 하위 브랜치를 사용했다.

5.png

브랜치를 합칠 때는 pull request 를 생성해서 합쳤다.

우리 팀은 페어 프로그래밍을 했기 때문에 실시간으로 리뷰를 하고 있어서 pull request 의 review 기능을 사용하지 않았다.

브랜치를 합치기 위한 용도로 pull request 를 사용했다고 볼 수 있다.

회고

미니쉘은 42 본과정에서 처음 해보는 팀 프로젝트이다.

약 2달에 걸쳐 코드를 완성했고, 3번의 리트라이 끝에 통과할 수 있었다.

사실 첫 1달 정도는 사이드 프로젝트를 진행하느라 과제에 온전히 집중할 수 없었다. 그럼에도 정말 시간이 많이 들었던 프로젝트였다.

코드를 이렇게 많이 작성해본 것도 처음이다. 코드 제출 당시 총 43개의 소스 파일이 있었다.

libft 과제도 파일 개수는 비슷하지만, 내부에 들어가는 코드의 양은 미니쉘이 약 4배 정도 더 많은 것 같다.

미니쉘 과제를 한 마디로 표현하면 “두더지 200마리 잡는 게임”이라고 할 수 있겠다.

에러 하나를 수정하면, 예상하지 못한 다른 부분에서 에러가 발생한 적이 엄청 많았다.

에러를 해결하기 위해 코드를 다시 쓰고, 구조를 바꿔서 다시 해보는 등 많은 시행착오를 겪었다.

깨질 수록 코드는 단단해진다.

우리 팀은 3번의 리트라이 끝에 과제를 통과했다.

이전 과제에서는 1번 또는 2번만에 과제를 통과했지만, 이번에는 결코 쉽지 않았다.

주변 동료들에게 “우리 미니쉘좀 터트려주세요.” 부탁해서 오류를 잡아보기도 했지만, 구조가 복잡해지다보니 놓치는 부분이 존재할 수 밖에 없었다.

동료 평가를 받으면서도 예상치 못한 케이스에서 원하는 대로 작동하지 않아서 다시 고쳤지만, 그 다음 평가에서는 또 다른 케이스에서 오류가 발생했다.

그렇게 계속 동료들의 리뷰를 받으면서 코드의 부족한 부분들을 채워나갈 수 있었다. 혼자서 또는 팀원과 함께 잡지 못했던 오류들을 새로 발견하며 우리의 코드는 더욱 견고해졌다.

평가를 받으면서 만났던 오류들은 다음과 같다.

메모리 누수

문자열을 복사해서 사용하는 경우가 많다보니 메모리 누수가 많이 났다.

동적 할당한 부분이 있다면 반드시 메모리 누수가 나는지 확인하면서 개발을 해야겠다.

그리고 프로그램을 실행시키는 중에 지속적으로 메모리 누수가 발생하는지 확인할 수 있는 명령어는 다음과 같다. 미니쉘 프로그램을 실행시키는 터미널 외에 추가로 터미널을 하나 더 열어서 실행시키면 된다.

1
2
3
4
5
# 현재 실행 중인 프로세스 목록에서 minishell 의 PID 를 찾는다.
ps

# 2초마다 메모리 누수 검사를 한다.
while true; do leaks [PID]; sleep 2; done;

메모리 누수가 발생하는 케이스 중 하나는 < 리다이렉션으로 존재하지 않는 파일을 열고자 시도했을 때였다.

1
< not_exsited_file cat

존재하지 않는 파일이 있으면 명령어가 실행되는 것은 막았지만, 문자열 복사를 많이 사용하다보니 이 경우에 대해서 복사한 문자열을 free 하지 않은 것이 문제였다.

리다이렉션과 파이프 연결

리다이렉션과 파이프 연결을 할 때는 첫 번째 명령어에서 > 리다이렉션이 존재하면 파이프를 열지 않았다.

1
echo hello > outfile | grep h

그러다보니 두 번째 명령어인 grepSTDIN 에서 무언가 오기를 계속 기다리는 현상이 발생했다.

이를 해결하기 위해 > 리다이렉션이 존재하든 안하든 파이프는 무조건 열어주었다. 그리고 > 리다이렉션이 존재한다면 출력 결과를 파이프의 write end가 아닌 outfile 로 나갈 수 있도록 설정했다.

단위 테스트는 함수가 완성되는 즉시 해야 한다.

bash와 최대한 유사하게 작동시키기 위해 수 많은 테스트 케이스를 넣어서 실행했다.

하지만 개발 과정에서 기능 하나를 구현해도 다양한 케이스에 대해 테스트를 진행하지 않았다. 이것이 쌓이다보니 과제를 제출하기 직전에 많은 케이스를 수정해야 하는 일이 발생했다.

그래서 개발을 하기 전에 최대한 많은 케이스를 탐색해보고, 예상되는 결과에 맞춰 개발을 하는 자세가 필요하다고 느꼈다.

그래야 함수 하나를 완성시키는 즉시 테스트를 할 수 있을 것 같다.

마찬가지로 테스트를 자동화 해놓고 개발하는 것도 중요하다고 느꼈다. 매번 손으로 명령어를 입력하고, 결과값을 비교하는 것은 굉장히 번거로웠고, 시간도 많이 들었다.

그래서 앞으로는 sh 파일로 스크립트를 만들어서 결과값을 비교하거나, 테스트를 할 수 있는 함수를 만들어서 개발하는 방법을 찾고 싶다.

생산성 is everything.

사소한 것에 시간을 쓰기 보다 본질적인 것에 집중해야 할 필요를 많이 느꼈다.

그 중 하나가 Makefile 로 오브젝트 파일을 하나의 폴더에 넣지 못한 것이었다. 기존에 작성하던 Makefile 은 소스 파일에 있는 디렉토리에 오브젝트 파일들이 함께 생성되었다.

소스 파일이 적고, 오브젝트 파일이 적을 때는 못 느꼈지만, 이번에는 소스 파일만 40개가 넘어가다보니 다른 파일로 이동할 때 굉장히 불편했다.

6.gif

오브젝트 파일이 소스 파일과 같이 생성되면서 다른 파일을 찾기 위해 한참을 내려야 했다. 여기서 낭비하는 시간이 생각보다 꽤 많았다.

그래서 오브젝트 파일을 하나의 폴더에 생성하는 방법을 찾아서 적용해봤지만, 생각보다 잘 되지 않았다.

duplicate symbol 오류가 발생하거나 오브젝트 파일이 생성되지 않는 등 오류가 발생해서 일단 기술 부채로 안고 넘어가기로 했다.

다음 프로젝트에서는 오브젝트 파일을 하나의 폴더에 생성하는 방법을 배워서 적용해봐야겠다.

그리고 자식 프로세스를 만들어서 실행하다보니 lldb 로는 추적할 수 없는 부분들이 많았다. 그래서 직접 STDERR 로 출력해보거나, 눈으로 디버깅을 했는데, 이 과정도 시간이 꽤 오래 걸렸다.

그래서 다음에는 valgrind 를 설치해서 멀티 프로세스 환경에서도 디버깅 하는 방법을 배워서 적용하고 싶다.

This post is licensed under CC BY 4.0 by the author.