Home Minishell ⑤ 회고
Post
Cancel

Minishell ⑤ 회고

6주 동안의 minishell 과제를 마치며

Untitled

minishell

Shell을 만들어라.

Subject를 처음 봤을 때 상당히 막연했다. Shell을 직접 만들라니. 여지껏 shell = 터미널 정도로만 생각했던 나로서는 이 프로그램을 어떤 방식으로 구현해야 할지, 무슨 기능을 만들어야 할지 감도 잡히지 않았다. 그러던 차에 한 카뎃이 지식 품앗이를 열어주었고 스터디원들과 참여해 shell의 전체적인 로직과 주요 개념에 대해 배울 수 있었다.

시작하기에 앞서 과제에서 사용할 자료구조를 정하였다. “mandatory만 하려면 연결 리스트로, bonus까지 하려면 트리 구조로 구현하라”는 조언이 많았는데 우리팀은 mandatory만 할 것임에도 트리 구조를 선택했다. 이 선택에는 몇 가지 이유가 있었는데, 우선 mandatory만 진행할 것임에도 트리 구조를 선택하는 것이, 과제를 마친 이후에 bonus 파트를 재도전하게 될 때 훨씬 확장성이 좋다고 생각했다. 또한 이전 프로젝트인 libft, get next line(나는 배열을 사용하긴 했다), push swap에서 이미 접했던 연결 리스트 개념과는 다르게 트리 구조는 생소한 개념이었고, 관련 개념을 익히기에 이만한 과제가 없다고 생각했다.

파싱 방식은 하나를 정해 끝까지 고수하는 대신, 과제를 해나가면서 계속해서 수정하는 방식으로 하였다. 공식 문서에도 정확한 순서나 세부적인 방식이 나와있지 않았기에 큰 틀만 잡고 bash로 직접 확인하면서 확장과 따옴표 처리가 되는 새로운 케이스를 발견할 때마다 순서와 방식을 변경했다. 처음 1~2주 간 개념 공부와 프로그램 설계를 하고, 3주 정도를 코드로 구현하는데 사용하였고, 마지막 한 주는 프로그램의 에러를 찾았다. 첫 번째 평가에서 많은 문제가 발견되었고, 이를 토대로 수정하여 다시 평가를 받아 통과했다.

이번 과제를 하면서 파싱 과정의 변수 확장, 따옴표 제거, 실행 과정의 리다이렉션에 따라 read end, write end를 변경하고 프로세스를 생성하는 등 shell의 전체적인 동작 방식을 알 수 있었다. 또한 shell에서 다양한 명령어가 어떤 방식으로 동작하는지를 살펴볼 수 있었다. 예를 들어 shell 빌트인 명령어 중 하나인 cd는 단순하게 경로를 이동하기만 하는 것이 아니었다. 우선 인자가 없거나 -라면 각각 Home과 이전 경로로 이동하고, 인자가 있다면 존재하는 파일인지, 해당 파일이 디렉토리인지를 확인한 뒤에 이동한다. 그 후에 마지막으로 환경변수인 PWD, OLDPWD를 변경한다. 또 다른 예로, exit은 첫 번째 인자가 숫자인지 아닌지에 따라 에러 메시지와 exit 여부가 달라진다.

협업

미니셸은 두 명이 한 팀을 이루어 하는 프로젝트였다. 이전에도 팀을 이루어 스터디를 진행한 적은 있었지만 코드를 공유하고 같이 무언가를 만들어가는 것은 본과정 뿐 아니라 개발 공부를 시작한 이래로도 처음이었다. 그렇기 때문에 시작하기에 앞서 걱정도 많았지만, 동시에 팀원으로부터 새로운 것들을 배울 수 있겠다는 기대도 있었다.

팀원인 rhong님은 소프트웨어 전공생으로 개발 경험이 많은 카뎃이었다. 그렇기에 내가 헷갈려 했던 연결 리스트, 트리 등의 자료구조 관련 개념이나 Bash 명령어에 대해 많이 배울 수 있었다. 또한 내가 프로그램의 불완전함 앞에서 타협하려 할 때마다, “기능을 적게 구현하되, 구현해야 하는 기능에 한해서는 완벽하게 하자”며 나를 설득해주었다.

우리 팀은 미니셸 전체를 같이 만드는 대신, 파싱 파트와 실행&빌트인 파트로 나누어 각 파트를 나와 팀원이 나눠서 구현했다. 또한 here-doc, 파스 트리 구성과 자료구조 관리는 팀원이 맡아주었고 빌트인 명령어 cd와 코드 정리는 내가 맡았다. 각각의 파트별로 브랜치를 나누어 작업하여 충돌을 방지했고 어느정도 구현될 때마다 코드 리뷰를 하였다. 각자의 파트를 구현하는 초기에는 보통 슬랙을 통해 소통했고, 파트를 합치고 프로그램의 에러를 찾는 마지막 2주 동안은 클러스터에서 만나서 직접 문제를 해결했다.

Untitled

6주 동안 과제를 하면서 우리는 다양한 문제에 직면했다. 우선 분업을 하다보니 서로의 코드를 이해하기가 쉽지 않았다. 심지어 pipex 과제를 하지 않았던 나로서는 실행 파트를 이해하는 것이 더욱 어려웠다. Pipe, dup 등의 개념은 생소했고 here-doc의 임시파일과 식별자 관련 내용은 아무리 다시 보아도 이해가 되지 않았다. 다행히 관련 개념 공부와 전체적인 흐름 정리, bash를 통해 실습을 하고 하니 조금씩 이해할 수 있었다. 그리고 나 또한 팀원에게 설명을 해주기 위해 그림을 그려보니 코드에서 모호했던 부분을 확실하게 잡을 수 있었던 것 같다.

또한 팀원의 블랙홀이 임박해서 생긴 문제도 있었다. 애초에 과제를 시작할 당시에도 팀원의 블랙홀은 그리 여유롭지 않았다. 그런데 미니셸을 끝낼 것이라고 예상했던 기한이 다 되도록 끝날 기미는 보이지 않았고, 결국 미니셸을 잠시 중단하고 철학자 과제를 먼저 해결하기로 했다. 다행히 팀원은 밤을 새가며 철학자 과제에 매달렸고 나 또한 내 일이라는 생각으로 해당 과제를 도와주어 5일만에 과제를 마치고 미니셸을 재개할 수 있었다.

마지막으로 코드의 상당 부분을 갈아엎어야 했던 어려움이 있었다. 몇 주간 작성했던 파싱 부분의 따옴표 처리(토큰화, 확장, 따옴표 제거) 로직이 완전히 잘못되었다는 것을 깨달았을 때는 멘탈이 많이 흔들렸던 것 같다. 기존의 방식을 조금 바꾸는 것으로는 제대로된 처리가 불가능하다는 것을 알았음에도 아까운 마음에 쉽게 코드를 날릴 수가 없었다. 하지만 스터디원으로부터 로직 설명을 듣고 방향을 완전히 잡고 나니 이틀만에 완전히 복구할 수 있었다.

개선 사항

미니셸은 여태 가려져 있던 잘못된 습관들이 한꺼번에 수면위로 떠올랐던 프로젝트였던 것 같다.

개발 방식의 문제

https://blog.crisp.se/wp-content/uploads/2016/01/mvp.png

우선 처음부터 방대한 계획을 세우고 이것저것 신경쓰다보니 실행 파일 생성이 너무 늦어졌다. 위 그림처럼 처음엔 대략적인 틀을 만들고 점차 기능을 더해가며 완성시켰어야 했는데 그러지를 못했다. 토큰화 파트를 구현하기도 힘든데 norminette을 따르느라 함수당 25줄 제한과 변수 5개 이하 선언, 파일당 함수 5개 이하 사용 규칙을 신경쓰느라 정작 중요한 기능 구현은 뒷전이 됐다. 스터디를 같이 하는 다른 팀의 경우, Makefile을 먼저 만들어두고 파싱 단계의 토큰화 파트를 구현하는 동시에 실행 파일을 만들어 해당 파트를 체크하고, 변수 확장 파트를 구현하는 동시에 해당 파트를 체크하는 식으로 진행했다. 그렇게 하다보니 진행 상황을 그때그때 알 수 있었고, 결과적으로는 파싱부를 우리 팀보다 일주일가량 먼저 완성시켰다. 반면 우리 팀의 경우, 실행 파일 컴파일을 늦게 하다보니 에러 체크를 할 시간도 부족했고 이러한 문제는 첫 번째 평가에서 쏟아지는 에러들로 돌아왔다. 대표적으로 공백문자로만 이루어진 입력을 걸러주지 못한 것이나 환경변수인 PATH가 unset됐을 때 segfault가 발생하는 문제는 테스트를 조금만 했어도 해결이 가능한 문제였다.

디버깅 실력의 부족

Untitled

또한 위와 같은 궤에 있는 문제인데, 작성한 코드에 대한 디버깅에 어려움을 겪었다. 이 문제는 프로젝트의 크기가 작은 지금까지의 과제에서는 크게 드러나지 않았다. 여태는 문제가 발생하면 확인해야 하는 파일이 적으면 1개에서 많아봐야 20개가 채 되지 않았기에 printf 디버깅만으로도 충분히 문제를 파악해 해결할 수 있었다. 하지만 이번 과제의 경우 파일이 60개 가량이 되고 기능 각각에 대한 이해도도 전보다 부족했기 때문에 printf 만으로 문제를 파악하기란 여간 힘든일이 아니었다. 팀원 둘 다 디버거 사용에 미숙해서 이렇게 디버깅에 어려움을 겪고 printf로 찍어보다가 스터디 모임이 있는 날에 디버거를 잘 다루는 다른 스터디원들에게 도움을 받기를 반복했다. 결국 프로젝트 막판이 되어서야 스터디원들로부터 배운 lldb, vscode 디버거를 조금씩 다뤄가며 문제를 해결할 수 있었다.

Untitled

코드 정리의 부재

내가 작성한 함수를 글이나 그림으로 그때그때 정리하지 않아서 생긴 문제도 있었다. 변수 확장 파트는 워낙 구현 방식이 복잡한데다가 구현 이후에 norminette 규칙에 맞춰서 함수를 쪼개느라 나중 가서는 나조차도 이해하지 못하는 코드가 되어 있었다. 내 스스로가 이해를 못했으니 팀원에게 설명은 더더욱 할 수 없었다. 코드를 완벽하게 작성한 후에 한번에 정리하자고 생각했던 것이 화근이었다. 앞으로는 불완전한 코드여도 그때그때 기록해야 할 것 같다.

Ref.

Making sense of MVP (Minimum Viable Product) - and why I prefer Earliest Testable/Usable/Lovable - Crisp’s Blog

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

C++ 개념 정리 : namespace

C++ 개념 정리 : 참조자(Reference)