[Get Next Line] 깨달은점 : 설계 및 구현 프로세스에 대한 고찰

Soeng_dev·2021년 1월 29일
0

42 Seoul / Get_next_line

목록 보기
3/3

깨달은점

휴학 후 주변 친구들 보다 과제를 늦게 시작하게 되면서 혼자서 정보를 찾아가며 과제를 하게되었다. 친구들에게 물어보면서 하긴 했지만, 다같이 진도를 나가고 있을때 보단 아무래도 정보공유가 부족했다.

하지만, 결과적으로 혼자서 프로젝트를 진행하고 난항을 겪어보며 더 많은 것들을 깨달을 수 있었다.

get_next_line 프로젝트에서 혼자 난관을 헤쳐가면서 생각하고 깨달은 점들을 기록하고 공유해보고자 한다.

• 소프트웨어 설계에 대한 고찰

» 요구사항 확인의 중요성

요구사항을 꼼꼼히 확인해보는 습관 을 들이는 것이 중요하단것을 깨달았다.

어떤 프로그램을 설계해야 하는지 명확히 정의하지 않거나, 세부적인 조건들을 꼼꼼히 확인하지 않고 프로그램을 작성하면 시간낭비가 심해질 수 밖에 없었다.
이번 gnl은 subject file과 c++의 getline함수를 직접 꼼꼼히 살펴보지 않은것이 문제였다.

같이 공부하던 친구에게 get_next_line 작동방식을 물어봐서 대답해주는대로 구현했다가 죄다 KO만 떠서 한동안 고생좀 했다.....
이 글을 읽는 분들은 어떤 정보던, 꼭 공신력 있는 출처에서 나온 정보를 직접 확인해보는 과정을 거치시기 바란다. 42프로젝트의 경우 subject파일을 꼼꼼히 읽는것은 중요하다!

GNL_lover를 비롯한 몇몇 테스터기에 diff값 출력이 잘 구현되어 있어서, 내가 만든 get_next_line함수의 출력과 정답간의 차이점을 살펴보면서 코드를 수정했다. 다만 이때도 설계사양을 잘못알아서 다른 테스터기에선 제대로 돌아가지 않았다.

여차저차 해서 subject파일과 getline함수가 작동하는 방식을 확인하고 문제점을 완전히 수정했지만 시간낭비가 너무 심했다.

우선 목표사양이 바뀔때마다 코드를 수정하고 매번 디버깅하느라 시간이 오래걸렸으며, 3번이나 목표하는 기능이 바뀌었지만, 제대로된 로직설계는 첫번째 목표기능(잘못파악한 기능)에 맞춰 과제를 할때 1번밖에 안했다.
이후 로직 수정을 제대로 안한채로 코드만 끼워맞춰서 수정하다보니 코드 구조가 매우 지저분해졌다.

42프로젝트의 경우 subject를 꼼꼼히 확인해야 하고, 다른 소프트웨어를 설계할때는 팀원이나 담당자등에게 목표사양 및 요구사항을 디테일하게 확인해보는게 중요하겠다는 생각이 들었다.

» 생각정리 후 구현하기 : 설계의 중요성

생각을 잘 정리한 후 코드를 작성하는게 중요하다는 것을 깨달을 수 있었다.

생각을 잘 정리한 후 작성한 코드는 구조가 간결, 깔끔해지고, 결과적으로 가독성이 좋아지며 디버깅이 쉬워졌다.
로직 설계를 깔끔하게 한 후 코드 작성하는것이 현명한 방법이란 생각이 들었다.

목표사양이 여러번 변하면서, 예전 목표사양에 맞춰 작성한 코드를
새로운 목표사양에 맞도록 억지로 끼워맞춰 수정했더니 코드 구조가 매우 지저분해져있었다.
목표사양이 바뀔때 생각을 잘 정리해서, 깔끔한 로직으로 코드를 작성한게 아니다 보니 프로그램의 흐름이 알아보기 힘들게 되어있었다.

get next line의 메인부분을 갈아엎고, 목표사양에 맞는 기능을 수행하는 로직을 깔끔하게 정리한 후 코드를 작성했더니, 흐름이 명확해졌다.

흐름이 명확해지니 디버깅도 당연히 쉬워졌고, 변수명과 함수명을 좀 더 알아보기 좋게 수정하는 것만으로 가독성이 훨씬 좋아졌다.

• 튼튼한 소프트웨어 구현

» 에러케이스 처리

에러케이스 처리는 외부에서 들어오는 모든 input의 모든 범위, 모든 경우에 대해서 고려하기
(이번 gnl에서는 fd, line, read함수(리턴값 및 읽어들인 문자열)가 여기에 해당 )

fd

fd < 0 또는 너무 커서 fd > OPEN_MAX 인 경우

line

line == NULL 또는 *line == NULL
또한 *line이 동적할당되야 하는 변수이므로,
전달되는 *line이 동적할당이 되어있을 때와 안 되어있을 때

read

read가 error 날때, 빈파일을 읽을때, 파일의 끝까지 읽었을때 등을 고려

» 자료 처리 방식 표준화

하나의 함수 또는 프로그램 내부에서의 자료처리 표준을 맞춰줄 필요가 있단점을 깨달았다.

예를 들면
포인터가 null일때 처리와 초기화 방식,

배열에 값을 할당해야 할때 초깃값을 어떻게 설정할 것인지,

int형 변수가 0일때 시작하는 index인지, 1부터 시작하는 숫자일지
만약 달라야 한다면 어디까지 index를 쓰고 어디까지 숫자를 사용할지 정한 후 넘어가는 부분 처리를 꼼꼼히 확인해야 한다.

» 디버깅

원인 추적

디버깅 시 log나 diff를 관찰후 문제가능성 있는 영역을 논리적으로 좁혀나가야 한다.
에러케이스 생기면 침착하고 꼼꼼히 살펴보기

전제(premise) 확인

아무리 꼼꼼히 살펴봐도 에러의 원인을 찾을 수 없는 경우엔, 전제를 확인해볼것
당연하다고 전제하고 있는게 사실이 아니라서 오류가 날 수도 있다.

당연하다고 착각하기 쉬운 전제
공신력없는 블로그 정보는 맹신하지 말기
확실하게 알고있는 관련 전공지식이 있다면 무조건 그걸 기준으로 생각하기, 손해볼일 없다....

설계사양을 꼼꼼히 확인해보는 습관들이기 (이번경우는 subject file과 c++의 getline함수를 직접 꼼꼼히 살펴보지 않은것이 문제)
subject를 꼼꼼히 찾아봐도 설계기준 햇갈릴땐 diff값 주어지는 test파일 여러종류 돌려보면서 파악해보기

» 경곗값 테스트

0일때 1일때 2일때, 마지막 전전 마지막 전 마지막
다른곳으로 넘어가기 직전 직전전, 직후 직후후 등등의 경곗값 꼼꼼히 살펴볼것

설계 및 구현 프로세스

앞서 이번 프로젝트를 통해 고찰해본 내용, 알고리즘 동아리에서 2주정도 문제를 풀면서 생각해봤던 문제해결 순서를 바탕으로 다음과 같이 설계 및 구현 프로세스를 생각해봤다. 물론, 간단한 기능이나 프로그램은 그냥 구현해도 된다.

• 1. 요구사항 및 목표사양 확인

앞서 말했듯이 요구사항을 꼼꼼하게 파악하고, 목표사양을 명확히 정의하지 않는다면 나중에 작성된 코드를 수정하는데 많은 시간과 노력이 낭비된다.

처음 시작할때 요구사항과 목표사양을 꼼꼼히 확인해보는 습관을 들이자

• 2. 로직 및 자료구조 설계

어떤 로직으로 풀어야

요구사항을 만족하는지, 어느 알고리즘 방법을 사용할것인지,
효율적인지, (아직은 잘 모르지만)보안상의 위협이 없는지
등등을 고민하여 로직을 설계해야한다.
이에 맞춰 데이터를 어떤방식으로 저장할지 자료구조를 정해야 한다.

수학적인 지식이나 잔머리도 써야될거같다.

• 3. 함수 선정 혹은 설계

일반적인 oop 스타일의 프로그램 설계에선 기존 함수를 이용해,
앞서 설계한 알고리즘과 로직 구현할 방법을 고민해보는 것이 바람직하다고 생각한다.
왜냐하면 구현에 들어가는 시간이 단축될 뿐만 아니라 기존에 팀에서 쓰고있던 함수들은 다른 팀원들도 익숙할것이므로 가독성 상의 이점도 존재하리라 생각되기 때문이다. (사실 아직은 잘 모르니 실무에 나가서 배워봐야 할거같다!)

설계된 로직을 만족시킬 함수가 정 없다면, 재사용성이 뛰어난 함수만들어야 할거같다.

다만, 알고리즘 문제 풀이에선 그냥 최고 효율내는 함수 만드는게 더 나을거같다.

• 4. 에러케이스 처리, 데이터처리 표준화

앞서 말한것과 같이 들어오는 모든 input에 대한 모든 범위와 경우의 수를 고려해서 에러케이스 처리를 해야 튼튼한 소프트웨어 작성이 가능하다.
(이런 부분을 고려해서 프로그램을 설계하게끔 유도하는게 42프로젝트들의 장점이라고 생각한다.)

또한 특정 함수나 프로그램 내부에서 데이터 처리를 어떻게 할지 일관성있는 규칙을 만들어야한다.

전달되는 숫자를 0에서 시작인지 1에서 시작인지 혹은 둘다써야만한다면 어디에서 어떤걸 쓸건지 범위를 명확히 하고 전달되는 부분을 꼼꼼히 체크
포인터 변수등의 초기화방법등등을 일관성있게 유지할 수 있도록 표준화된 규칙을 만들어야한다.

• 5. 디버깅, 경곗값 처리

앞서 말한 방법을 이용해 효율적으로 디버깅해야 한다.

또한, 다양한 테스트 케이스 특히 경곗값에서 소프트웨어가 제대로 작동하는지 잘 살펴보고 디버깅해야한다.

• 6. 디테일한 리팩토링

일반 소프트웨어 코드 작성이라면 가독성을 위한 변수명, 함수명등의 고민등

알고리즘 문제풀이라면 아주 디테일한 최적화 고민
(ex : 조건문 최적화,직접 연산해서 줄일수 있는 첫 한두개 연산 결과값, 함수호출 횟수, 비트연산자를 이용한 속도향상 등등)

• 설계 및 구현 프로세스의 필요성

get next line 프로젝트와 알고리즘 문제풀이 경험상 이와같은 프로세스가 필요한 이유는 2가지가 있다.

  1. 설계 및 구현 단계들간에는 위계와 종속적인 관계가 존재한다.
    그래서 윗단계에서 수정이 일어나면, 그 아랫단계들도 거의 대부분 수정해야하므로 시간낭비가 발생한다.
  1. 각 단계의 문제들 하나하나가 많은 집중력과 사고력을 요구하므로,
    한번에 여러가지 문제를 고민하다보면 헷갈리거나 판단력이 흐려질 수 있다.
    (다만, 알고리즘 패러다임 선정이나 함수 선정, 데이터 처리 표준화등은 익숙해진다면 한꺼번에 생각할 수도 있을거같다.)

1번의 예를들면
로직을 'buffer 문자열에서 개행문자를 찾아본다 -> 개행문자가 있다면 개행문자 직전까지 문자열을 cat하고 없다면 문자열 전체를 cat한다.'라고 설계하면, 필요한 함수는 strchr, strncat(또는 strlcat)등이 될것이다.

하지만 로직을 'buffer 문자열을 cat하는데 개행문자가 있다면 개행문자까지만, 없다면 문자열 전체를 cat한다
(따로 개행문자 찾는 과정을 넣지 않고 cat과정에 합친다)'라고 하면
strchr과 strncat으로는 구현해낼수 없으므로 새로운 함수를 찾거나 만들어 내야한다.

이렇듯 윗단계에서 수정이 일어나면, 아랫단계의 문제역시 새로 정의하고 풀어내야하므로 시간이 낭비된다.
되도록 각 단계를 한번할때 완벽하게 끝내는게 더 효율적인 설계 및 구현 방법이 될것이다.

profile
Software Engineer

0개의 댓글