GN⁺: 코드 가독성을 떨어뜨리는 시각적 복잡성 패턴 (2023)
(seeinglogic.com)- 최근 코드베이스를 검토하면서 코드의 품질에도 불구하고 정신적으로 피로해지는 경험을 함
- 이는 코드의 복잡성보다는 가독성과 관련이 있었음
- 코드의 가독성을 높이기 위한 8가지 패턴을 도출
코드 가독성 메트릭 및 대체 복잡성 메트릭
- 코드 가독성을 측정하는 보편적이고 널리 사용되는 메트릭은 존재하지 않음
- 현실에서 사용되지 않는 학술 논문이나 개인적인 의견만 존재함
- 새로운 메트릭을 만들기보다는 누구나 쉽게 논의할 수 있는 시각적 패턴에 집중
- 복잡성 메트릭에서 중요한 조건:
- 소스 코드 스니펫이나 개별 함수에서 작동해야 함
- 알고리즘 복잡성 대신 코드 작성 방식에 초점
- 스타일 요소(변수명, 공백, 들여쓰기 등)에는 초점 두지 않음
Halstead 복잡성 메트릭
- 1970년대 Maurice Halstead가 개발한 코드 복잡성 메트릭
- 언어와 플랫폼에 상관없이 코드 작성 방식을 수치화 가능
- 연산자와 피연산자 수를 기반으로 프로그램의 길이, 볼륨, 난이도를 계산
- 주요 측정값:
- 고유 연산자 수 (
n1
) - 고유 피연산자 수 (
n2
) - 전체 연산자 수 (
N1
) - 전체 피연산자 수 (
N2
)
- 고유 연산자 수 (
- 더 많은 연산자와 피연산자가 사용될수록 코드의 복잡성 증가
- 모든 언어에서 연산자 및 피연산자의 정의가 명확하지 않아 일관된 도구 사용이 중요
Halstead 복잡성에서 얻은 인사이트
- 짧고 변수 수가 적은 함수가 가독성이 높음
- 언어별 연산자나 구문 설탕(syntactic sugar) 사용은 최소화
- 함수형 프로그래밍의 체인 사용(map/reduce/filter 등)은 너무 길어지면 가독성이 떨어짐
인지 복잡성 (Cognitive Complexity)
- SonarSource에서 개발한 복잡성 메트릭
- 코드 읽기 어려움을 보다 정확하게 측정하기 위한 시도
-
세 가지 주요 원칙:
- 단축 구문(shorthand constructs)이 읽기 난이도를 줄임
- 비선형 흐름의 단절이 난이도를 증가시킴
- 중첩된 제어 흐름이 난이도를 증가시킴
인지 복잡성에서 얻은 인사이트
- 단축 구문은 간결하지만 잠재적 버그 위험 존재
- 조건문 및 논리 연산자는 과도하게 사용하면 가독성이 떨어짐
- 예외 처리는 코드 복잡성을 높이는 주요 원인
-
goto
는 일반적으로 피해야 하지만 특정 상황에서는 유용할 수 있음 - 중첩된 제어 구조는 가능하면 줄이는 것이 좋음
함수의 모양, 패턴 및 변수
- 함수의 시각적 "모양"은 코드 가독성에 중요한 역할 수행
-
가독성을 높이는 세 가지 원칙:
- 명확하고 구체적인 변수명 사용
- 변수 중복(Shadowing) 피하기
- 시각적으로 구별 가능한 이름 사용 (
i
,j
와 같은 유사 이름 피하기)
- 변수의 수명(liveness) 단축
- 변수의 사용 범위는 짧을수록 좋음
- 함수 경계를 넘어서 오래 유지되는 변수는 복잡성 증가
- 익숙한 코드 패턴 재사용
- 일관된 코드 패턴을 유지하면 가독성 향상
- 새로운 접근 방식보다 기존에 익숙한 패턴을 우선 사용
코드 가독성을 향상시키는 8가지 패턴
- 라인/연산자/피연산자 수 감소 – 작은 함수와 적은 변수 사용이 가독성 향상
- 새로운 접근 방식 회피 – 코드베이스에서 익숙한 패턴을 유지
- 그룹화 – 긴 함수 체인, 반복자 등은 보조 함수로 분리
- 조건문의 단순화 – 조건문은 짧게 유지하고 논리 연산자 혼합 최소화
- goto 최소화 – 필요할 경우 에러 처리에서만 제한적으로 사용
- 중첩 최소화 – 중첩된 로직은 줄이고, 필요하면 함수로 분리
- 명확한 변수 이름 사용 – 구체적이고 중복되지 않는 변수명 사용
- 변수의 수명 단축 – 함수 내에서 짧게 유지하고 함수 경계를 넘지 않도록 함
결론
- 코드 가독성은 코드 품질에 중요한 요소
- Halstead 및 Cognitive Complexity는 가독성 문제를 수치화하고 개선 방향 제시 가능
- 간결하고 명확한 코드를 작성하면 코드 유지 보수가 쉬워지고 버그 발생 가능성 감소
- 최적의 코드 작성은 단순함, 일관성, 명확성을 우선시하는 것임
Hacker News 의견
-
map, reduce, filter와 같은 함수형 프로그래밍 구조를 연결하는 것은 간결하지만, 긴 체인은 가독성을 해치는 경향이 있음
- 이는 기사에서 암시된 바가 아님
- 익숙하지 않아서 나쁘다고 생각하는 일반적인 불평처럼 느껴짐
- 조금만 익숙해지면 다른 방법보다 읽고 쓰기 쉬움
- 함수형 프로그래밍의 기본을 배우는 것이 중요함
- Monad를 설명할 필요는 없지만, map과 filter를 무작위로 비난하지 않을 정도로 익숙해져야 함
-
좋은 코드의 중요한 측면은 질적이고 문학적임
- 수학적 사고방식을 가진 프로그래머와 학자들에게는 불편할 수 있음
- 도스토옙스키와 우드하우스를 좋아하지만, 그들의 글은 매우 다름
- 코드베이스의 스타일을 이해하는 데 시간이 걸림
-
코드 읽을 때 가장 피로한 문제는 가변성임
- 변수를 한 번만 "고정"할 수 있는 능력은 큰 선물임
- 메서드를 이해하는 과정은 0%에서 100%로 단조롭게 증가해야 함
- GOTOs가 해로운 이유는 가변 변수의 상태를 알기 어려워서임
-
작은 함수와 적은 변수는 일반적으로 읽기 쉬움
- "가독성"에 대한 초점이 미세 가독성에 치우쳐 있음
- 이는 코드가 지나치게 분열되게 만듦
- APL 계열 언어는 반대 극단에 있음
-
TypeScript는 코드 읽기 어렵게 만듦
- 데이터 모델이 "원자적"으로 유지되면 괜찮음
- 타입 유추에 의존하면 필드를 원래 위치로 추적하기 어려움
-
getOddness4 함수는 비대칭성을 줌
- getOddness2 함수는 대칭적 선택을 제공함
-
기사는 흥미롭지만 만족스럽지 않음
- 언어 특정 연산자나 구문 설탕 사용을 피하라는 의견에 동의하지 않음
- map, reduce, filter 같은 구조는 잘 사용하면 다른 연산자를 대체하고 "볼륨"을 줄임
-
가독성을 정의하려는 시도는 칭찬할 만함
- 많은 사람들에게 테스트를 통해 가독성의 실제 차원을 찾을 수 있을 것임
-
코드 복잡성은 구문 트리의 크기로 표현됨
- 지역적 복잡성 감소가 전체 복잡성에 큰 영향을 주지 않음
-
긴 함수 체인이나 콜백은 작은 그룹으로 나누고 잘 명명된 변수를 사용하는 것이 좋음
- 두 버전 모두 효율성 면에서 동일함
- 차이는 컴파일러에 있음