프로그래밍 책

코드 계약이란? (협업을 위한 코드 작성법) #좋은코드나쁜코드 #3장

열심히 사는 우진 2023. 4. 4. 11:58
반응형

 

* 본 아티클은, '좋은 코드 나쁜 코드' 3장을 기반으로 작성된 글입니다.

 

 

 


코드 품질의 핵심 요소 6가지

  • 가독성
  • 모듈화
  • 재사용성 및 일반화성
  • 테스트 용이성
  • 예측 가능한 코드를 작성하라
  • 코드를 오용하기 어렵게 만들라
    본 장은 이 중 ‘예측 가능한 코드를 작성하라’와 ‘코드를 오용하기 어렵게 만들라’는 2개와 깊게 관련이 있다.

코드를 작성할 때 다음의 3가지를 반드시 기억해야 한다.

  • 자신에게 분명하다고 해서 다른 사람에게도 분명한 것은 아니다.
  • 다른 개발자는 무의식 중에 당신의 코드를 망가뜨릴 수 있다.
  • 시간이 지나면 자신의 코드를 기억하지 못한다.

따라서, 코드를 작성할 당시에 다른 사람(미래의 자신 포함)에게도 분명하며, 망가뜨리기 어려운 코드를 작성해야 한다.

내가 작성한 코드를 다른 사람들이 파악하는 방법

  • 이름 확인
  • 데이터 타입 확인
  • 문서 읽기

여기까지가 실용적인 방법이고,

  • 직접 묻기
  • 자세한 구현 코드 읽기

위 두 가지는 비현실적이고 비효율적인 방법이다.

코드 계약

서 다른 코드 간의 상호작용을 계약처럼 생각한다.

계약에서 정의한 대로 분명하게 실행되고, 예상과 다르게 실행되는 것이 없어야 한다.

  • 선결 조건 : 코드를 호출하기 전에 사실이어야 하는 것
    • ex: 실행 전 시스템의 상태, 코드에 공급해야 하는 입력
  • 사후 조건 : 코드가 호출된 후에 사실이어야 하는 것
    • ex: 시스템이 새로운 상태에 놓였는지, 반환값
  • 불변 사항 : 코드 호출 전후가 동일해야 하는 사항

명백한 사항 / 세부 조항

전기 스쿠터 렌탈 앱을 예로 들자.

  • 핵심 사항
    • 전기 스쿠터 대려
    • 시간 당 10달러
  • 세부 조항
    • 충돌 사고 시 수리 비용 지불
    • 시속 30마일 이상 → 벌금 부과

이중, 시속 30마일 이상에 벌금을 부과하는 항목은 당황스러운 조항일 수 있다.

코드에서는,

  • 핵심 사항
    • 함수와 클래스 이름
    • 인자 타입
    • 반환 타입
    • Checked exception
  • 세부 조항
    • 주석, 문서
    • Unchecked exception

핵심 사항으로 조건을 명백하게 하는 것이 좋다.

문서, 주석은 업데이트가 어려워 믿기 힘들다.

Unchecked exception은 처리가 강제되지 않아서 놓칠 수 있다.

세부 조항에 대한 의존성을 최대한 줄이고,

핵심 사항만으로 명확하게 코드를 설명할 수 있도록 하자.

코드 예시

class UserSettings {
    UserSettings() { ... }

    // 이 함수를 사용해 설정이 로드되기 전에 다른 함수 호출 X
    Boolean loadSettings(File location) { ... }

    // 이 함수는 loadSettings() 함수 호출 이후, 다른 함수 호출 이전에 호출해야 함
    void init() { ... }

    // 사용자가 선택한 UI의 색상 반환
    // 선택된 색상이 없으면 null 반환
    // 설정이 로드 또는 초기화되지 않았으면 null 반환
    Color? getUiColor() { ... }
}

클래스, 메서드명을 통해 역할을 충분히 파악할 수 있지만,

loadSettings() → init() → getUiColor() 순서로 실행되지 않으면 문제가 발생한다.

void setUiColor(UserSettings userSettings) {
    Color? chosenColor = userSettings.getUiColor();
    if (chosenColor == null) {
        ui.setColor(DEFAULT_UI_COLOR);
        return;
    }
    ui.setColor(chosenColor);
}

위의 setUiColor에서 chosenColor의 null 가능성을 체크하지만, 이것이 상태의 오류인지, 사용자가 선택한 색상이 없음을 나타내는 것인지 알 수 없다.

상태의 오류가 발생해도 → 프로그램은 계속 동작한다.

세부 조항을 제거하는 방법

  • 정적 팩토리 메서드 → 설정 로딩, 초기화가 완료된 인스턴스만 반환하도록 함
    • getUiColor()가 null을 반환하면 → 색상을 선택하지 않았다는 뜻임
  • 생성자를 private으로 설정
  • 설정 관련 메서드 private 설정
class UserSettings {
    private UserSettings() { ... }

    static UserSettings? create(File location) {
        UserSettings settings = new UserSettings();
        if (!settings.loaadSettings(locations)) {
            return null;
        }
        settings.init();
        return settings;
    }

    private Boolean loadSettings(File location) { ... }

    private void init() { ... }

    Color? getUiColor() { ... }
}    

상태, 가변성이 클래스 외부로 노출되는 것을 없앴다. → 버그 침투 가능성을 낮췄다.

체크, 어서션 (Check, Assertion)

  • 체크 (Check)
    • 전제 조건 검사 : 입력 인수가 올바르거나, 초기화가 수행되었거나, 일부 코드를 실행하기 전에 시스템의 유효성을 확인
    • 사후 상태 검사 : 반환값 또는 코드 실행 후 유효성 확인
  • 어서션 (Assertion)
    • 체크와 매우 비슷하지만, 배포를 위해 빌드할 때 보통 컴파일에서 제외된다.
      • 때문에, 실제 서비스 환경에서의 실패를 명백하게 보여주지는 않는다
Color? getUiColor() {
    assert(hasBeenInitialized(), "UserSettings가 초기화되지 않음");
    ...
}

위의 코드는 어서션의 예시이다. (테스트 코드에서 많이 보았을 것이다.)

두 가지 모두 개발 또는 테스트 단계에서 코드의 오용을 파악하는 것을 목적으로 하지만, 효과가 보장되지는 않는다.

코드 계약에 세부 조항이 있을 때 체크나 어서션을 사용하면 좋다.

하지만 가능하다면 애초에 세부조항을 피하는 것이 좋다.

반응형