좋은 코드, 나쁜 코드 5장

5장 가독성 높은 코드를 작성하라

서술형 명칭 사용

  • 서술적이지 않은 이름은 코드를 읽기 어렵게 만든다.
  • 주석문으로 서술적인 이름을 대체할 수 없다.
    • 주석문에 대한 유지보수도 필요해진다.
    • 클래스가 수백 줄의 길이를 가진다면, 주석을 찾기 위해 스크롤 해야한다.
  • 중복된 주석문은 유해할 수 있다.
    • 코드가 수행하는 작업을 설명하는 주석문을 추가한 경우 코드가 변경하면 주석문을 수정해야 하는 상황이 생긴다.
    • 주석문이 이해하기 어려운 코드가 있다.
    String generateId(String[] data) {
    	 // data[0]은 성이고, data[1]부터는 이름이다
    }
    
    • 헬퍼 함수를 사용하여 가독성이 높은 코드를 쉽게 작성할 수 있다.
    String generateId(String[] data) {
    	return firstName(data) + "," + lastName(data);
    }
    
    String firstName(String[] data) : data[0]
    String lastName(String[] data) : data[1]
    
  • 주석문은 코드의 이유를 설명하는 데 유용하다
    • 코드가 왜 수행하는지 코드 자체로는 설명하기 어렵다.
    • 아래와 같은 경우에 주석문을 사용하여 코드가 존재하는 이유를 설명하는게 좋다.
      • 저품 또는 비즈니스 의사 결정
      • 이상하고 명확하지 않은 버그에 대한 해결책
      • 의존하는 코드의 예상을 벗어나는 동작에 대처
  • 주석문은 유용한 상위 수준의 요약 정보를 제공할 수 있다.
    • 주석문과 문서는 마치 책을 읽을 때 줄거리와 같다.
    • 책을 읽으려고 하는데 모든 페이지의 모든 단락 앞에 한 문장짜리 줄거리가 있다면 꽤 성가시고 읽기 어려운 책이 될 것이다.
    • 책의 뒤 표지나 각 장의 시작 부분에 간략하게 요약해서 보여주면 매우 유용하게 된다.
    • 코드 기능에 대한 상위 수준에서의 개략적인 문서는 아래와 같다
      • 클래스가 수행하는 작업 . 및다른 개발자가 알고 있어야 할 중요한 세부 사항을 개괄적으로 설명하는 문서
      • 함수에 대한 입력 매개변수 또는 기능을 설명하는 문서
      • 함수의 반환값이 무엇을 나타내는지 설명하는 문서

코드 줄 수를 고정하지 말라

  • 간결하지만 이해하기 어려운 코드를 피해야 한다.
  • 더 많은 줄이 필요하더라도 가독성 높은 코드를 작성해야 한다.
  • 코드 줄 수가 높다는 것은 기존 코드를 재사용하지 않거나 무언가를 필요 이상으로 복잡하게 만들고 있다는 경고 신호가 될 수 있다.

일관된 코딩 스타일을 고수하라

  • **서비스형 소프트웨어(software as a service)**에 대해 글을 쓰고 있다고 가정해본다.
  • 일반적으로는 SaaS를 사용하지만, SAAS를 사용한 경우 서비스형 소프트웨어를 축약하는 용어로 생각하지 않을 수 있다.
  • 일관적이지 않은 코딩 스타일은 혼동을 일으킬 수 있다.
    • 파스칼 케이스(pascalCase), 캐멀 케이스(camelCase)로 작성되는 경우 전체 클래스 정의를 확인하지 않고도 클래스 내의 인스턴스 변수를 확실하게 정의 할 수 있다.
      • 파스칼 케이스 : 첫 글자로 대문자로 시작하는 클래스
      • 캐멀 케이스 : 변수 이름 첫 글자를 소문자로 시작
  • 가장 좋은 해결책은 스타일 가이드를 채택하고 따르는 것이다.
    • 일관된 코딩 스타일을 따라 코드를 작성하면 가독성이 좋아지고 버그를 예방하는 데 도움이 된다.
      • 언어의 특정 기능 사용
      • 코드 들여쓰기
      • 패키지 및 디렉터리 구조화
      • 코드 문서화 방법
    • 팀에 스타일 가이드가 없을 경우 구글의 스타일 가이드를 따르는 것이 좋다.
    • 서로 오해할 위험이 크게 줄어들고, 버그와 낭비되는 시간을 줄일 수 있다.
  • 린터 : 스타일 가이드를 따르지 않는 코드를 찾아 알려주는 도구
    • 사용하는 언어에 따라 다르며, 나쁜 관행의 코드에 대한 경고할 수 있다.
    • 일반적이고 아주 간단한 문제만 잡기 때문에 처음부터 좋은 코드를 작성하는 것을 대체할 수는 없다.
    • 하지만 코드 개선을 위해서 가장 빠르고 쉬운 방법이다.

깊이 중첩된 코드를 피하라

  • 여러 if 문을 포함하고 있는 경우, 코드는 상당히 읽기 어렵다.
  • 눈으로 따라가기 어렵고, 특정 값이 반환되는 시점을 파악하기 위해 밀집된 모든 if- else 논리를 탐색해야 하기 때문이다.
  • 이에 대한 가장 좋은 해결책은 중첩을 최소화하기 위한 구조를 변경하는 것이다.
    • 논리를 재구성한다.
    • 가독성을 향상하기 위해, 반환문이 있는 블록을 재배치한다.
    • 함수가 너무 많은 일을 하지 않도록 한다.
    • 더 작은 함수로 분리하는 것도 좋은 방법이다.

함수 호출도 가독성이 있어야 한다

  • 함수 호출 인수가 늘어나면 이해하기 힘들어질 수 있다.
  • 생성자가 많은 수의 매개변수를 가지고 있으면 이것은 근본적인 문제를 나타낼 수 있다.
  • 추상화 계층을 적절하게 정의하지 않았거나 코드가 충분히 모듈화되지 않았음을 의미할 수 있다.
  • 함수 호출 시 인수의 값이 무엇을 의미하는지 알려면 함수 정의를 확인해봐야 한다.
  • 이에 대한 해결책으로 명명된 매개변수 사용이 있다.
  • [명명된 매개변수 사용]
    • 인수 목록 내의 위치가 아닌 이름으로 일치하는 매개변수를 찾는다.
    • 함수 정의를 확인하지 않고도 sendMessage() 함수에 대한 호출을 쉽게 이해할 수 있다.
    snedMessage(message: "Hello", priority: 1, allowRetry: true);
    
    • 객체 구조 분해를 이용해 명명된 매개변수와 같은 효과를 얻는 것은 번거로움이 있지만 다른 개발자에게 익숙할 수 있다.
  • 두번째 해결 방법으로는 서술적 유형 사용이 있다.
  • [서술적 유형 사용]
    • 함수를 작성할 때 특정 유형을 만들어 그 매개변수들이 나타내는 바를 설명하는 것이다.
    • 클래스 : 메시지의 우선순위를 클래스로 표현한다.
    • 열거형 : 재시도 정책은 불리언 대신 두 가지 옵션이 있는 열거형을 사용한다.
    class MessagePriority {
    	MessagePriority(Int priority) {}	
    }
    
    enum RetryPolicy {
    	ALLOW_RETRY,
    	DISALLOW_RETRY
    }
    
    void sendMessage(
    	String message,
    	MessagePriority priority,
    	RetryPolicy retryPolicy) {
    }
    
    • 이 함수에 대한 호출은 함수 정의를 알지 못해도 이해하기 쉽다.
  • 때로는 훌륭한 해결책이 없다.
    • 만약에 BoundingBox라는 (top,left,right,bottom)을 매개변수로 받는 클래스가 있다고 생각해본다.
    • 단순히 일련의 숫자만 보여주기 때문에 이해하기 쉽지 않으며, 엉망이 순서로 호출해도 컴파일이 잘되는 코드를 작성하기 쉽다.
      • 실제로는 잘못 된 값이 들어간다.
    • 이러한 경우는 특별히 만족스러운 해결책이 없으며, 각 인수에 대한 설명 인라인 주석문을 사용하는 것 외에는 방법이 없다.
      • 인라인 주석문은 생성자 호출 코드의 가독성을 높혀준다.
  • IDE를 활용한다.
    • IDE에 따라 함수 호출을 보다 쉽게하기 위해 매개변수를 보여주는 경우가 있다.
    • 이 경우는 IDE의 도움을 통해 쉽게 매개변수를 파악할 수 있다.

설명되지 않은 값을 사용하지 말라

  • 하드 코드로 작성된 값이 필요한 경우가 많이 있다.
    • 한 수량을 다른 수량으로 변환할 때 사용하는 계수
    • 작업이 실패할 경우 재시도의 최대 횟수와 같이 조정 가능한 파라미터 값
    • 어떤 값이 채워질 수 있는 템플릿을 나타내는 문자열
  • 하드 코딩된 값에는 아래 정보를 포함해야 한다.
    • 값이 무엇인지: 컴퓨터 코드를 실행할 때 이 값을 알아야 한다.
    • 값이 무엇을 의미하는지: 개발자가 코드를 이해하려면 값의 의미를 알아야 한다.
      • 이 정보가 없으면 코드를 이해할 수 없다.
  • 해결책 : 상수의 선언
    • 값이 무엇을 의미하는지 명시해야 하는 것은 중요하다.
  • 해결책 : 잘 명명된 함수를 사용
    • 상수를 반환하는 공급자 함수 (provider function)
      • 개념적으로 상수를 사용하는 것과 동일하며, 변환 계수를 제공하는 등의 함수를 만든다.
    private static Double kilogramsPerUsTon() : 907.1847;
    
    • 변환을 수행하는 헬퍼 함수 (helper function)
      • 수량의 변환을 하위 문제로 만들어 이 기능을 전문적으로 수행하는 함수를 작성한다.
    class Vehicle {
    	Double getKineticEnergyJ(){ // 헬퍼 함수 호출 
    			return 0.5 * usTonsToKilograms(getMassUsTon()) * 
    					Math.pow(mphToMetersPerSecond(getSpeedMph()),2);
    	}
    
    • 상수나 함수 값을 표현하거나 처리하기 위해 추가 작업이 필요하지 않으면서도 가독성을 크게 높일 수 있다.

익명 함수를 적절하게 사용하라

  • 익명함수 : 이름이 없는 함수이며, 일반적으로 코드 내의 필요한 지점에서 인라인으로 정의된다.
  • 익명 함수는 매개변수의 함수를 사용하는 기법은 함수형 프로그래밍, 특히 람다 표현과 관련되어 잇다.
  • 익명 함수는 가독성이 떨어질 수 있다.
    • 간단하지만 이해하기 어려운 코드이며, 설명이 필요할 수 있다.
  • 해결책 : 명명 함수를 사용하라
    • 명명함수는 재사용하기 쉽다는 장점이 있다.
  • 익명 함수가 길면 문제가 될 수 있다.
  • 해결책 : 긴 익명 함수를 여러 개의 명ㅁ여 함수로 나누어라
    • 긴 익명 함수를 몇 개의 잘 명명된 헬퍼 함수로 나누어야 한다.
  • 한꺼번에 너무 많은 일을 하는 함수를 분리하는 것은 코드의 가독석을 개선하기 위해 좋은 방법이다.
  • 함수형 스타일의 코드를 작성할 때 이 점을 잊으면 안되며, 로직을 최대한 작은 단위의 명명 함수로 작성하는 것이 좋다.

프로그래밍 언어의 새로운 기능을 적절하게 사용하라

  • 자바 8에서 스트림을 도입한 경우 많은 개발자가 간결한 코드 작성이 가능하다는 점에 큰 이점을 느끼게 되었다.
  • 코드를 읽기 쉽고 간결하게 만들기 때문에 코드 최적화에 좋다.
  • 하지만 아래와 같은 문제가 발생한다.
    • 불분명한 기능은 혼동을 일으킬 수 있다.
      • 해당 기능이 다른 개발자에게 얼마나 알려져 있을지에 대해 고려할 필요가 있다.
      • 개선 사항이 적거나 다른 개발자가 그 기능에 익숙하지 않다면 차라리 사용하지 않는 것이 좋을 수도 있다.
    • 작업에 적절한 도구를 사용하지 않을 수도 있다.
      • 다재다능하고 많은 문제를 해결할 수 있다고 해서 최상위 방법으로 채택하는 것은 좋지 않다.
      • 합리적인 코드를 발견하고, 문제를 해결하기 위해 노력해야 한다.

요약

  • 코드의 가독성이 떨여져서 이해하기 어려울 때 아래와 같은 문제가 발생한다.
    • 다른 개발자가 코드를 이해하느라 시간을 허비함
    • 버그를 유발
    • 잘 작동하던 코드가 다른 개발자가 수정한 후 작동하지 않음
  • 코드의 가독성을 높이려면 다른 개발자의 입장을 공감하고, 그들이 코드를 읽을 때 어떻게 혼란스러워할지를 상상해보는 것이 필요하다.
  • 실제 시나리오는 다양하며 그 상황에 해당하는 어려움이 있다.
  • 가독성이 좋은 코드를 작성하려면 언제나 상식을 적용하고 판단을 사용해야 한다.