[기술] 읽기 좋은 코드를 작성하기 위해 고민했던 것들

holyPigeon·2024년 10월 25일
1

기술

목록 보기
3/5
post-thumbnail

✋ Intro

프리코스에서는 매주 미션을 제출하고 참가자들끼리 코드 리뷰를 하는 문화가 있는데, 작년에도 느낀 부분이지만 다른 사람의 코드를 읽는 게 정말 쉽지 않았다.

물론 내가 쓴 코드가 아니라서 이해하기 어려운 부분도 있었지만, 작성자가 코드 자체를 너무 그지같이 짜서 한참을 읽어보고 나서야 이해했던 기억이 있다 ㅋㅋㅋㅋ

사실 그 때부터 혹시 다른 사람도 내 코드를 보고 이렇게 괴로워할까...? 라는 생각을 하곤 했었다.

마침 이번 프리코스 목표를 "매주 최소 20명과 코드리뷰 진행하기"로 잡기도 했고, 그 과정에서 리뷰어들에게 읽기 좋은 코드를 보여줬으면 해서 코드의 가독성에 대해 고민하는 과정을 거쳤다. 오늘은 이에 대해 이야기를 풀어보려고 한다!

🚀 미션 소개

우선 이야기에 앞서 프리코스 1주차에 구현했던 미션에 대해 간단히 설명하고 가려 한다.

미션명은 "문자열 덧셈 계산기"이며, 1,2:3과 같은 문자열을 입력받았을 때, 쉼표(,) 혹은 콜론(:)을 기준으로 숫자를 추출하고 덧셈을 진행하는 프로그램이다.

덧셈 연산 자체는 굉장히 쉬웠지만, 숫자를 뽑아내기 위한 문자열 가공 과정과 예외 처리가 까다로웠던 미션이었다.

본격적으로 핵심 로직을 작성하면서부터 코드 길이가 꽤 길어지는 것을 느꼈는데, 이 떄부터 정신 바짝 차리고 코드를 제대로 써야겠다고 생각했다!

🏷️ 변수 & 메서드 네이밍

해당 미션을 구현하면서 가장 신경썼던 부분은 바로 네이밍이었다. 사실 사람들은 남의 코드를 읽을 때 보통 보이는 부분부터 슥슥 읽기 때문에 코드의 첫 인상이 가장 중요하다고 생각했다. 그리고 이러한 첫 인상의 대부분은 네이밍으로부터 나온다.

만약 문자열로부터 숫자를 추출하는 메서더를 다음과 같은 이름으로 짓는다고 가정해보자.

List<Integer> getNumbers(String input, Separators separators);

해당 메서드는 본래 문자열(input)을 분석구분자를 통해 숫자 구분구분된 숫자들을 리스트에 저장 이라는 흐름을 가지고 있다. 그런데 getNumbers() 라는 네이밍만 보면, 앞선 가공 과정같은 건 떠올릴 수 없고 "getter의 한 종류인가?"라는 의문만 남게 된다.

List<Integer> extractNumbers(String input, Separators separators);

위와 같이 명확히 "추출"이라는 뜻을 가지고 있는 extract 키워드를 사용하면 보다 명료하게 뜻을 전달할 수 있다.

사실 딱 한 단어만 바뀐 거라 큰 차이가 없다고 생각할 수 있지만, 실제로 코드를 읽을 떄는 이런 작은 디테일 하나하나가 모여서 좋은 가독성을 완성시킨다고 느꼈다.

⏎ 개행

가독성을 신경쓰면서 한 가지 더 꺠달은 것은, 개행이란 것이 생각보다 훨씬 중요하다는 사실이었다. 단순히 줄 바꾸는 게 그렇게 큰 의미가 있나? 라고 생각할 수도 있는데 이것도 실제로는 체감이 컸다.

아래는 문자열을 분리하는 역할의 메서드인데, 리팩토링 전의 모습이다.

private String[] splitInput(String input, List<String> delimiters) {
		String processedInput = removeCustomDelimiter(input);
		validateDelimiter(processedInput);
		String regex = String.join("|", delimiters.stream().map(Pattern::quote).toArray(String[]::new));
		return processedInput.split(regex);
	}

코드 라인 자체는 4줄로 적긴 하지만, 여러가지 로직들이 혼재되어있기 때문에 의미를 파악하기 위해선 꽤 많은 시간을 투자해야 하는 상황이다.

그렇다면 개행을 적용하면 어떻게 될까? 개선된 코드를 확인해보자.

private String[] splitInput(String input, List<String> delimiters) {
		String processedInput = removeCustomDelimiter(input); // 1번
		validateDelimiter(processedInput);

		String regex = String.join("|", // 2번
			delimiters.stream()
				.map(Pattern::quote)
				.toArray(String[]::new)
		);

		return processedInput.split(regex); // 3번
	}
  1. 커스텀 구분자를 떼어낸 후 검증
  2. 구분자 목록을 가져와 정규식 생성
  3. 정규식에 따라 분류된 문자열 배열 반환

위와 같이 문자열을 분리하는 단계에 맞춰 각 로직들이 옹기종기 모여있는 모습을 확인할 수 있다. 코드 길이는 조금 길어졌지만, 이해하기 훨씬 편해진 모습이다.

📦 패키지 네이밍 및 디렉토리 구조

마지막으로 패키지의 네이밍, 그리고 디렉토리 구조가 매우 중요하다는 것을 깨달았다. 패키지는 우리가 코드를 읽기에 앞서 가장 먼저 확인할 수 있는 이정표와도 같다.

처음 프로젝트를 불러와 코드를 읽을 때는 이러한 이정표를 먼저 참고하게 되고, 특히 네이밍을 통해 해당 패키지 안에 어떤 역할의 클래스가 있을지 미리 짐작하게 된다.

이에 따라 1차적으로, 패키지명을 결정할 때는 널리 사용하는 컨벤션을 따르는 것이 중요하다.

많은 사람들이 이해할 수 있는 네이밍을 택함으로써, 보는 사람으로 하여금 코드를 빠르게 탐색할 수 있도록 돕고, 코드를 읽지도 않은 상태에서 클래스의 역할을 짐작하게 할 수 있도록 만들 수 있다.

위 이미지에서도 알 수 있듯, 사람들이 자주 사용하는 네이밍에 대해서는 IntelliJ IDE 측에서도 일반 아이콘이 아닌 맞춤 아이콘을 제공한다.

2차적으로는, 디렉토리 구조에 신경 쓸 필요가 있다.

InputFilter 라는 이름의 클래스는 사실 언뜻 봤을 때 사용자 입력과 관련된 클래스라고 생각할 수 있다. Input 이라는 키워드를 포함하고 있기 때문에 마치 view 패키지에 포함되는 느낌을 주는 것이다.

하지만 InputFilter 를 model 패키지에 배치함으로써 단순 입력보단, 비즈니스 로직에 좀 더 그 역할이 치중되어 있음을 강조할 수 있다. MVC 패턴에서의 model의 역할을 이해하는 사람이라면, 해당 코드에 대한 이해가 보다 쉬워지는 상황이 생긴다.

😀 마무리

결국 일주일 동안 미션을 구현하면서 느낀 것은, 아무리 잘 짜여져있고 최적화가 잘 된 코드라 해도, 읽기 어려우면 전부 꽝이라는 것이었다.

객체지향이니 아키텍처니 따져가면서 아무리 열심히 코드를 작성하더라도, 애초에 코드가 읽기 어려워버리면 사람들이 그 내부를 봐주지 않는 것이다. 속상할 수 있지만, 코드의 품질을 챙기면서도 가독성까지 꼭 같이 챙겨야 인기있는 개발자가 될 수 있다.

그리고 사실 코드 가독성은 기본기에 가까운 부분이라, 이걸 놓치고 있는 사람은 아직 갈 길이 멀다고 볼 수 있다... 어디 가서 서운한 취급 받지 않도록 꼭 코드를 읽기 좋게 작성해보자!

profile
언젠가 전설이 될 남자... 피존입니다.

0개의 댓글