🥨

[GetNextLine] 리팩토링

Category
GNL
Tags
Empty
Day learned
2020/04/16
Property
Empty
✔️
프로그램의 목적에 부합하는 직관적인 코드 만들기
1. get_next_line 함수를 직관적으로 수정하기
int get_next_line(int fd, char **line) { static char *backup[OPEN_MAX]; char buf[BUFFER_SIZE + 1]; int read_size; int cut_addr; if ((fd < 0) || (line == 0) || (BUFFER_SIZE <= 0)) return (-1); while ((read_size = read(fd, buf, BUFFER_SIZE)) > 0) { buf[read_size] = '\0'; backup[fd] = ft_strjoin(backup[fd], buf); if ((cut_addr = is_newline(backup[fd])) >= 0) return (split_line(&backup[fd], line, cut_addr)); } if (backup[fd] && (cut_addr = is_newline(backup[fd])) >= 0) return (split_line(&backup[fd], line, cut_addr)); return (return_zero(&backup[fd], line, read_size)); }
C
지금 내 코드에서 마음에 안드는 점이 있다면
if (backup[fd] && (cut_addr = is_newline(backup[fd])) >= 0) return (split_line(&backup[fd], line, cut_addr));
C
이 부분이 두 번이나 등장한다는 점이다. 나는 read함수로 파일을 끝까지 다 읽었을 때 while문을 탈출하게 함수를 짜버려서, 파일의 제일 마지막 줄을 읽으려면 while문 밖에서 split_line을 한번 더 호출해줘야한다.
get_next_line에서 가장 중요한 함수는 split_line 이다. 모든 작업이 끝난 후 제일 마지막에 딱 한번 return(split_line)을 해주는 것이 gnl 목적에 가장 부합하는 직관적인 코드라고 생각했다.
따라서 gnl 내부에서 반복하는 while문의 조건을 read함수가 아니라 is_newline으로 바꾸기로 했다. 파일을 끝까지 읽을 때 까지 vs 개행을 한번 만날 때 까지의 고민인데, 후자가 더 get_next_line 의미에 가까운 것 같다.
그래서 위 함수를 아래처럼 바꿔보았다.
int get_next_line(int fd, char **line) { static char *backup[OPEN_MAX]; char buf[BUFFER_SIZE + 1]; int read_size; char *cut_addr; if ((fd < 0) || (line == 0) || (BUFFER_SIZE <= 0)) return (-1); while ((cut_addr = is_newline(backup[fd])) == -1) //-1 == 개행없음 { if ((read_size = read(fd, buf, BUFFER_SIZE)) <= 0) return (return_zero(&backup[fd], line, read_size)); buf[read_size] = '\0'; backup[fd] = ft_strjoin(backup[fd], buf); } return (split_line(&backup[fd], line, cut_addr)); }
Objective-C
얼마나 깔끔하고 흐름이 명확한지!!!
2. 실패
하지만 결론은, 다시 원래대로 돌아갔다.
처음에는 segfault가 떠서 함수 한줄한줄 마다 printf를 찍어가면서 어느 부분이 문제인지 확인했다. 문제점은 is_newline에 있었다. 개행문자를 찾으면 반복문을 탈출해야하는데, 파일이 끝날 때 까지 반복한다. buf에 read도 잘 되고 join도 잘 되는데 is_newline으로 보내진 backup(개행문자를 포함하고 제일 마지막 인덱스가 널인 한 줄)에서 개행문자를 못 찾는다. 안 될 이유가 없는데 이유가 뭘까?
int is_newline(char *backup) { int i; if (backup == 0) return (0); i = 0; while (backup[i]) { if (backup[i] == '\n') return (i); i++; } return (-1); }
C
is_newline 대신 ft_strchr(backup[fd], '\n')를 써보기도 했지만, 이때는 split_line에서 cut_addr 주소값 이후로 접근했을 때 segfault가 났다.
정신이 말짱할 때 다시하면 분명 해결할 수 있을 것 같아서 기록에 [미해결]로 남겨두기로...
3. '좋은 코드'를 짜는 법
1.
처음에는 함수를 동작하게 만드는 것에 집중해서 정신 없이 코드를 짰다면,완성된 후에는 코드를 '잘' 정리하고 싶다는 욕심이 생긴다.
2.
그래서 처음으로 함수의 목적에 맞는 코드에 대해서 고민을 해봤다.
3.
프로그램의 흐름대로 코드가 한줄 한줄 나오게 하는 것이 기본이고, 프로그램이 종료되는 어느 한 시점으로 모든 경우의 갈래가 모이도록 만드는 게 좋은 것 같다.
4.
혹시 내가 불필요한 return을 만들지는 않았나 고민해보면서 말이다. return은 하나인 게 가장 좋고, 어쨌든 최소화 하는 데에 집중하자. (이게 프로그래밍의 정답인지는 모르겠지만 지금 내 수준의 비교적 간단한 프로젝트들에서는 충분히 유의미하고 실현 가능한 목표인 것 같다.)
그리고 이 위의 순서가 반대가 되었을 때도 능숙하게 코드를 짤 수 있도록 해야겠다. 지금은 직접 코드를 쳐보기 전까지 내가 목표하는 프로그램이 어떤 경우의 수를 가질 수 있는지 충분히 고려하기가 어려운 것 같다.
마무리가 참 어려웠던 get_next_line 끝 ! 🤗