코드 가독성에 대해 – 1. 도입과 원칙

프레젠테이션 개요

제가 공개한 프레젠테이션은 코드 가독성을 높이기 위한 아이디어를 정리한 것으로 아래와 같이 8개 장으로 구성되어 있습니다.  

  • 도입과 원칙: 코드 가독성의 중요성, 프로그래밍 원칙
  • 명명: 이름이 나타내는 내용, 문법, 단어 선택
  • 주석: 문서화, 인라인 주석
  • 상태: 상태 변환 관리, 불필요한 상태 삭제
  • 절차: 함수 및 메서드 흐름 명시화 및 분리
  • 종속성 I: 2가지 유형 사이의 의존도
  • 종속성 II: 종속성의 방향, 중복, 명시성
  • 리뷰: 코드 리뷰 요청, 리뷰 코멘트

전체 다 합치면 무려 700페이지가 넘는 분량입니다. 이렇게 방대한 프레젠테이션을 작성하게 된 이유는 저희 팀을 보다 ‘지속 가능한 개발을 할 수 있는 환경으로 만들고 싶다’라는 마음 때문이었습니다. 

2011년에 LINE 개발을 시작한 이후 더 좋은 사용자 체험을 제공하기 위해 끊임없이 기능 개발과 개선을 해왔습니다. 그렇게 서서히 프로젝트가 성장한 결과 Android 클라이언트 코드는 모듈을 포함하면 150만 행까지 늘어났고, 팀은 50명을 넘는 규모가 되었습니다. 하지만 특정 누군가가 계속 리팩터링하는 것만으로는 이런 환경을 구현할 수 없습니다. 거대한 프로젝트에서는 ‘코드 가독성 유지와 기술 부채의 지속적인 상환’ 자체를 확장 가능한(scalable) 상태로 만들어야 합니다. 즉, 개발에 관여하는 멤버 모두가 가독성과 기술 부채를 신경 쓸 필요가 있습니다. 가독성 높은 코드를 작성하는 것은 단순하게 기능을 구현하는 일보다 어려움이 많습니다. 이를 위해서는 어떤 코드가 잘 읽히는지에 관한 지식과 효율적으로 프로그램의 구조를 재구성하는 기술이 필요합니다. 그래서 그러한 지식과 기술을 공유하기 위해 코드 리뷰에서 자주 지적되는 예와 리팩터링 중 발견한 가독성 낮은 코드의 특징을 정리한 것이 본 프레젠테이션입니다.

 

1장: 도입과 원칙 

1장에서는 코드의 가독성이 높아야 하는 이유와 이를 위한 프로그래밍 원칙을 소개합니다.

 

코드의 가독성이 높아야 하는 이유

앞서 말씀드린 대로 상품 개발을 지속 가능하게 만들려면 코드의 가독성을 높여야 합니다. 예를 들어 5분 생각해서 변수 명을 고민하거나 적절한 주석을 다는 것으로 다른 개발자(미래의 자기 자신도 포함)가 1시간 고민하는 일을 막을 수 있을지 모릅니다. 중요한 것은 단기적이고 개인적인 생산성이 아니라 상품의 라이프 사이클 기간에 걸친 팀 전체의 생산성입니다. 반대로 해당 코드의 수명이 짧거나 사용되는 범위가 제한적인 경우에는 가독성에 관해서 고려할 필요가 줄어들 것입니다. 예를 들어 테스트 구현이나 한 번만 실행하는 스크립트가 이에 해당합니다.  

 

가독성을 중시해야 할지 말지에 관한 손익분기점은 작업 시간 중 ‘코드를 읽거나 다른 사람에게 코드를 해설하는 시간’과 ‘코드를 작성하는 시간’ 중 어느 쪽이 더 오래 걸리는 지가 하나의 기준이 됩니다. 여기서 주의해야 할 점은 코드를 작성하는 도중에 다른 코드를 참조하는 시간이나 코드 리뷰를 하는 시간도 ‘코드를 읽는 시간’에 포함해야 한다는 점입니다. 

 

프로그래밍 원칙

견고하고 가독성 높은 코드를 작성하기 위해서는 프로그래밍 원칙에 따르는 것이 유용한 경우가 많습니다. 본 장에서는 코드의 가독성을 고려할 때 특별히 중요한 것뿐만 아니라, 혹시 과도하게 적용해도 부작용이 별로 없는 프로그래밍 원칙 5개를 선정했습니다. 

 

The boy scout rule

 

이 원칙은 보이스카우트 운동의 창시자인 Robert Baden-Powell의 격언을 Robert C. Martin이 소프트웨어 개발에 적용한 것인데요. 본래의 뜻과 문맥은 다르지만, 코드를 변경할 때는 더 깔끔하게 만들자는 주장입니다. 이 원칙에 따르는 예로 어떤 코드를 변경하는 김에 알기 쉬운 이름으로 바꾼다거나, 코멘트나 테스트를 추가한다거나, 사용하지 않는 코드를 삭제하는 것을 들 수 있습니다. 이것은 ‘지저분한 코드를 더 지저분하게 만들지 말라’는 말과 같습니다. 예를 들어 거대한 switch 문에 새로운 케이스를 추가하는 것은 이 원칙에 반하며, 새로운 케이스를 추가하기 전에 전략 패턴(strategy pattern)을 사용해서 리팩터링해야 합니다. 

 

YAGNI (You Aren’t Gonna Need It)

 

이 원칙은 기능과 코드는 필요할 때 구현해야 하며, 앞으로 필요해질 것 같다는 이유로 구현해서는 안 된다고 주장합니다. 현재 필요하지 않은 코드의 예로는 사용하지 않는 유틸리티 함수, 구현이 하나 이하인 추상화 레이어, 고정값 밖에 주어지지 않는 가인수(dummy argument) 등을 들 수 있습니다. 이처럼 미래에 대비한 기능과 코드는 실제로 사용하지 않을 가능성이 높을 뿐 아니라, 다른 이유로 코드 변경이 필요할 때 장애가 될 수 있습니다. 혹시 어떤 조건 분기를 추가하려고 할 때 다른 쓸데없는 분기가 있으면 최종적인 분기 구조가 더욱 복잡해지는 것을 생각할 수 있습니다. 이러한 점에서도 앞으로 발생할 변경에 대해 유연하게 대처하기 위해서는 쓸데없는 기능 구현을 피하고 설계를 단순화하는 것이 중요합니다. 

다만 이 원칙은 코드 변경이 쉽다는 것을 전제로 하고 있습니다. 변경이 어려운 예로는 외부에 공개하는 API와 라이브러리, 마이그레이션이 어려운 데이터베이스 스키마 등이 있습니다. 이런 경우엔 앞으로 어떻게 사용될지 고려해서 설계하고 구현해야 할 수 있습니다. 

 

KISS (Keep It Simple Stupid)

 

이 원칙의 본래 뜻은 장비를 정비하기 쉽게 만들기 위해서 설계를 단순화해야 한다는 의미입니다. 이것을 소프트웨어 개발에 적용하면, ‘기능과 사양을 단순화한다’ 또는 ‘구현 기법을 단순화한다’는 의미가 될 텐데요. 특히 구현 기법에 관해서는 목적과 수단이 뒤바뀌지 않도록 유의해야 합니다. 프로그래밍 원칙과 패러다임, 코드의 아름다움과 일관성, 설계 기법은 코드의 가독성과 견고함을 높이기 위한 수단이어야 하는데, 그 자체가 목적이 되면 가독성과 견고함이 떨어질 수 있습니다. 예를 들어 리액티브 프로그래밍으로 정숫값을 포함하는 모든 값을 observable로  만들 수 있는데요. 통일성 관점에서는 ‘아름답게’ 보이지만, 코드의 가독성이 떨어지고 버그를 발견하는 게 더 어려워질 수 있습니다. 

 

Single responsibility principle

 

이 원칙은 SOLID라고 불리는 오브젝트 지향을 위한 원칙 가운데 하나로, ‘어떤 클래스가 변경되는 이유는 딱 한 가지여야 한다’는 뜻을 지니고 있습니다. 예를 들어 사용자 정보의 구조(이름, 메일 주소)와 그 집합(등록 완료 사용자 리스트)을 하나의 클래스로 구현하면, 이 클래스를 변경하는 이유가 2가지가 됩니다(사용자 정보 항목을 변경하고자 하는 경우와 사용자 등록 방법을 바꾸고 싶은 경우). 이 경우 어떤 변경이 다른 기능에 영향을 끼치지 않는다고 보장할 수 없습니다. 클래스의 책임과 관심의 범위는 하나로 좁혀야 합니다.

 

Premature optimization is the root of all evil

 

이 원칙은 효과가 작은 최적화는 하면 안 된다고 주장합니다. 코드가 복잡해지는 최적화는 효과가 클 때만 해야 하고, 이를 위해서 프로파일링과 견적이 중요합니다. 다만 코드의 가독성이 좋아지는 최적화는 꼭 그렇다고만은 할 수 없습니다. 예를 들어 ArrayList의 요소를 반복문으로 검색하는 코드를 HashMap으로 치환하면, 메모리 사용량은 늘어날지 모르지만 계산량이 줄어들고 코드도 간결해집니다. 

 

마치며

이번 글은 ‘도입과 원칙’ 편으로 해당 프레젠테이션을 전체적으로 훑고 1장의 내용을 소개했습니다. 다음 편은 ‘명명과 주석’ 편으로 프레젠테이션의 2장과 3장의 내용을 소개하겠습니다.